From 50a42e1d87959e25ee2cc97a01ae2a6e3ee6bad3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 9 Mar 2021 05:45:24 +0100 Subject: [PATCH 0001/1155] 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 0002/1155] 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 0003/1155] 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 0004/1155] 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 0005/1155] 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 0006/1155] 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 727e135c728666a6e64b010d8051d050346faaa6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 10 Mar 2021 19:39:45 +0100 Subject: [PATCH 0007/1155] 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 cc3c073dc5b6402b387d848868cbbe7c4395bc36 Mon Sep 17 00:00:00 2001 From: Paco Date: Wed, 17 Mar 2021 21:24:13 +0100 Subject: [PATCH 0008/1155] 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 0009/1155] 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 f295dffd005e8af3e5f893191cdaaa9964b531dd Mon Sep 17 00:00:00 2001 From: Paco Date: Sun, 21 Mar 2021 22:27:57 +0100 Subject: [PATCH 0010/1155] 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 0011/1155] 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 0012/1155] 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 b3675051570507643f218d14b35dcb0ab034f5cd Mon Sep 17 00:00:00 2001 From: MercierCorentin Date: Thu, 25 Mar 2021 20:52:14 +0100 Subject: [PATCH 0013/1155] 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 0014/1155] 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 0015/1155] 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 913b02e4fcc32622a3a3763680333980c4c04028 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 24 Apr 2021 21:34:57 +0200 Subject: [PATCH 0016/1155] 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 e16f14f794d1f3ad5764e59b1a24021fa0f2aefd Mon Sep 17 00:00:00 2001 From: Paco Date: Tue, 27 Apr 2021 21:42:18 +0200 Subject: [PATCH 0017/1155] 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 0018/1155] ~ 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 2315e811fa1f418a832ca9b8f732aeb23c18e4b7 Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 30 Nov 2020 10:58:50 +0100 Subject: [PATCH 0019/1155] [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 0020/1155] [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 0021/1155] [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 0022/1155] [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 0023/1155] [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 0024/1155] [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 0025/1155] [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 0026/1155] [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 0027/1155] [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 0028/1155] [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 0029/1155] [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 0030/1155] [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 0031/1155] [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 0032/1155] [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 6e880c8219846ce94a1a870c535334f16c5b1bcd Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 9 May 2021 01:02:51 +0200 Subject: [PATCH 0033/1155] [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 0034/1155] [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 8cf151668fda88b57e765e742e673bd6d1227efc Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 11 May 2021 12:08:00 +0200 Subject: [PATCH 0035/1155] [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 bb140b2ba4353d7282504de573ec4f4b8d7d47ef Mon Sep 17 00:00:00 2001 From: MercierCorentin Date: Wed, 19 May 2021 03:02:13 +0200 Subject: [PATCH 0036/1155] 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 0037/1155] 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 5859028022bf63b949c7fcb16f022cf543115721 Mon Sep 17 00:00:00 2001 From: Paco Date: Fri, 21 May 2021 10:36:04 +0200 Subject: [PATCH 0038/1155] 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 0039/1155] 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 0040/1155] 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 e40f8fb861ca3c98726309e1a6424b2a2f0ad540 Mon Sep 17 00:00:00 2001 From: Corentin Mercier Date: Tue, 25 May 2021 13:46:09 +0200 Subject: [PATCH 0041/1155] 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 0042/1155] 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 ad6d31ce81183b855906df0756aa25974d4ffaf8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 2 Feb 2021 03:09:40 +0100 Subject: [PATCH 0043/1155] Bullseye: Misc updates in control file --- debian/control | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/debian/control b/debian/control index ef5061fe7..63853b161 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: yunohost Section: utils Priority: extra Maintainer: YunoHost Contributors -Build-Depends: debhelper (>=9), dh-systemd, dh-python, python3-all (>= 3.7), python3-yaml, python3-jinja2 +Build-Depends: debhelper (>=9), dh-python, python3-all (>= 3.7), python3-yaml, python3-jinja2 Standards-Version: 3.9.6 Homepage: https://yunohost.org/ @@ -13,10 +13,10 @@ 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-publicsuffix2 , apt, apt-transport-https, apt-utils, dirmngr - , php7.3-common, php7.3-fpm, php7.3-ldap, php7.3-intl - , mariadb-server, php7.3-mysql + , php7.4-common, php7.4-fpm, php7.4-ldap, php7.4-intl + , mariadb-server, php7.4-mysql , openssh-server, iptables, fail2ban, dnsutils, bind9utils , openssl, ca-certificates, netcat-openbsd, iproute2 , slapd, ldap-utils, sudo-ldap, libnss-ldapd, unscd, libpam-ldapd @@ -25,28 +25,27 @@ Depends: ${python3:Depends}, ${misc:Depends} , dovecot-core, dovecot-ldap, dovecot-lmtpd, dovecot-managesieved, dovecot-antispam , rspamd, opendkim-tools, postsrsd, procmail, mailutils , redis-server - , metronome (>=3.14.0) , acl , git, curl, wget, cron, unzip, jq, bc, at , lsb-release, haveged, fake-hwclock, equivs, lsof, whois Recommends: yunohost-admin , ntp, inetutils-ping | iputils-ping , bash-completion, rsyslog - , php7.3-gd, php7.3-curl, php-gettext + , php7.4-gd, php7.4-curl, php-gettext , python3-pip , unattended-upgrades , libdbd-ldap-perl, libnet-dns-perl -Suggests: htop, vim, rsync, acpi-support-base, udisks2 + , metronome (>=3.14.0) Conflicts: iptables-persistent , apache2 , bind9 - , nginx-extras (>= 1.16) - , openssl (>= 1.1.1g) - , slapd (>= 2.4.49) - , dovecot-core (>= 1:2.3.7) - , redis-server (>= 5:5.0.7) - , fail2ban (>= 0.11) - , iptables (>= 1.8.3) + , nginx-extras (>= 1.19) + , openssl (>= 1.1.1i-3) + , slapd (>= 2.4.58) + , dovecot-core (>= 1:2.3.14) + , redis-server (>= 5:6.0.10) + , fail2ban (>= 0.11.3) + , iptables (>= 1.8.8) Description: manageable and configured self-hosting server YunoHost aims to make self-hosting accessible to everyone. It configures an email, Web and IM server alongside a LDAP base. It also provides From 876ce488378d96dd4d146c24f133b5a1bbb5aecd Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 5 Feb 2021 00:05:00 +0100 Subject: [PATCH 0044/1155] Update changelog for 11.0 --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index 9a143f962..15af50303 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +yunohost (11.0.0~alpha) unstable; urgency=low + + - Placeholder for 11.0 + + -- Alexandre Aubin Fri, 05 Feb 2021 00:02:38 +0100 + yunohost (4.2.5.2) stable; urgency=low - Fix install in chroot ... *again* (806b7acf) From 726f4605d2a6f787a6d38cdbb63f4f810071afa1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 5 Feb 2021 02:09:59 +0100 Subject: [PATCH 0045/1155] Bullseye: idk what i'm doing but let's try to bump compat to 13 --- debian/compat | 1 - debian/control | 2 +- debian/rules | 18 +++--------------- 3 files changed, 4 insertions(+), 17 deletions(-) delete mode 100644 debian/compat diff --git a/debian/compat b/debian/compat deleted file mode 100644 index ec635144f..000000000 --- a/debian/compat +++ /dev/null @@ -1 +0,0 @@ -9 diff --git a/debian/control b/debian/control index 63853b161..4216f400c 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: yunohost Section: utils Priority: extra Maintainer: YunoHost Contributors -Build-Depends: debhelper (>=9), dh-python, python3-all (>= 3.7), python3-yaml, python3-jinja2 +Build-Depends: debhelper (>=9), debhelper-compat (= 13), dh-python, python3-all (>= 3.7), python3-yaml, python3-jinja2 Standards-Version: 3.9.6 Homepage: https://yunohost.org/ diff --git a/debian/rules b/debian/rules index 3790c0ef2..341ba2b01 100755 --- a/debian/rules +++ b/debian/rules @@ -1,11 +1,8 @@ #!/usr/bin/make -f # -*- makefile -*- -# Uncomment this to turn on verbose mode. -#export DH_VERBOSE=1 - %: - dh ${@} --with=python3,systemd + dh ${@} --with python3 override_dh_auto_build: # Generate bash completion file @@ -13,14 +10,5 @@ override_dh_auto_build: python3 doc/generate_manpages.py --gzip --output doc/yunohost.8.gz override_dh_installinit: - dh_installinit -pyunohost --name=yunohost-api --restart-after-upgrade - dh_installinit -pyunohost --name=yunohost-firewall --noscripts - -override_dh_systemd_enable: - dh_systemd_enable --name=yunohost-api \ - yunohost-api.service - dh_systemd_enable --name=yunohost-firewall --no-enable \ - yunohost-firewall.service - -#override_dh_systemd_start: -# dh_systemd_start --restart-after-upgrade yunohost-api.service + dh_installsystemd -pyunohost --name=yunohost-api --restart-after-upgrade + dh_installsystemd -pyunohost --name=yunohost-firewall --noscripts From 70174af9746d0f83b8d9396bc1b31c22bb93c28a Mon Sep 17 00:00:00 2001 From: Kayou Date: Mon, 8 Mar 2021 19:29:26 +0100 Subject: [PATCH 0046/1155] bump conflict --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 4216f400c..08daecbbe 100644 --- a/debian/control +++ b/debian/control @@ -40,7 +40,7 @@ Conflicts: iptables-persistent , apache2 , bind9 , nginx-extras (>= 1.19) - , openssl (>= 1.1.1i-3) + , openssl (>= 1.1.1j-2) , slapd (>= 2.4.58) , dovecot-core (>= 1:2.3.14) , redis-server (>= 5:6.0.10) From ffea5e40486229204d4a143fef73b064da74300a Mon Sep 17 00:00:00 2001 From: Kayou Date: Tue, 9 Mar 2021 14:26:03 +0100 Subject: [PATCH 0047/1155] php-gettext -> php-php-gettext --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 08daecbbe..e557c9ff6 100644 --- a/debian/control +++ b/debian/control @@ -31,7 +31,7 @@ Depends: ${python3:Depends}, ${misc:Depends} Recommends: yunohost-admin , ntp, inetutils-ping | iputils-ping , bash-completion, rsyslog - , php7.4-gd, php7.4-curl, php-gettext + , php7.4-gd, php7.4-curl, php-php-gettext , python3-pip , unattended-upgrades , libdbd-ldap-perl, libnet-dns-perl From 065966759072a11bd58580c49d5a348cf57e82c5 Mon Sep 17 00:00:00 2001 From: Kayou Date: Tue, 9 Mar 2021 14:48:54 +0100 Subject: [PATCH 0048/1155] Update the conflict version of redis-server --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index e557c9ff6..e78718d66 100644 --- a/debian/control +++ b/debian/control @@ -43,7 +43,7 @@ Conflicts: iptables-persistent , openssl (>= 1.1.1j-2) , slapd (>= 2.4.58) , dovecot-core (>= 1:2.3.14) - , redis-server (>= 5:6.0.10) + , redis-server (>= 5:6.0.12) , fail2ban (>= 0.11.3) , iptables (>= 1.8.8) Description: manageable and configured self-hosting server From 1f284d74c4b4960d196e3048954bba9ea0b79e39 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 9 Mar 2021 18:10:30 +0100 Subject: [PATCH 0049/1155] php 7.3 -> 7.4 --- data/helpers.d/php | 2 +- data/hooks/conf_regen/10-apt | 4 ++-- data/templates/yunohost/services.yml | 6 ++--- locales/ca.json | 2 +- locales/en.json | 2 +- locales/fr.json | 2 +- locales/it.json | 2 +- src/yunohost/app.py | 33 +++++++++++++++------------- src/yunohost/backup.py | 2 +- 9 files changed, 29 insertions(+), 26 deletions(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index 40a023e9d..bf12cd4d6 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -1,6 +1,6 @@ #!/bin/bash -readonly YNH_DEFAULT_PHP_VERSION=7.3 +readonly YNH_DEFAULT_PHP_VERSION=7.4 # 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} diff --git a/data/hooks/conf_regen/10-apt b/data/hooks/conf_regen/10-apt index bb5caf67f..c7d1f0174 100755 --- a/data/hooks/conf_regen/10-apt +++ b/data/hooks/conf_regen/10-apt @@ -53,8 +53,8 @@ Pin-Priority: -1 do_post_regen() { regen_conf_files=$1 - # Make sure php7.3 is the default version when using php in cli - update-alternatives --set php /usr/bin/php7.3 + # Make sure php7.4 is the default version when using php in cli + update-alternatives --set php /usr/bin/php7.4 } FORCE=${2:-0} diff --git a/data/templates/yunohost/services.yml b/data/templates/yunohost/services.yml index 7df563c67..b6ce7c117 100644 --- a/data/templates/yunohost/services.yml +++ b/data/templates/yunohost/services.yml @@ -22,9 +22,9 @@ nginx: test_conf: nginx -t needs_exposed_ports: [80, 443] category: web -php7.3-fpm: - log: /var/log/php7.3-fpm.log - test_conf: php-fpm7.3 --test +php7.4-fpm: + log: /var/log/php7.4-fpm.log + test_conf: php-fpm7.4 --test category: web postfix: log: [/var/log/mail.log,/var/log/mail.err] diff --git a/locales/ca.json b/locales/ca.json index 189053d94..0f0b60426 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -572,7 +572,7 @@ "migration_description_0015_migrate_to_buster": "Actualitza els sistema a Debian Buster i YunoHost 4.x", "regenconf_need_to_explicitly_specify_ssh": "La configuració ssh ha estat modificada manualment, però heu d'especificar explícitament la categoria «ssh» amb --force per fer realment els canvis.", "migration_0015_weak_certs": "S'han trobat els següents certificats que encara utilitzen algoritmes de signatura febles i s'han d'actualitzar per a ser compatibles amb la propera versió de nginx: {certs}", - "service_description_php7.3-fpm": "Executa aplicacions escrites en PHP amb NGINX", + "service_description_php7.4-fpm": "Executa aplicacions escrites en PHP amb NGINX", "migration_0018_failed_to_reset_legacy_rules": "No s'ha pogut restaurar les regles legacy iptables: {error}", "migration_0018_failed_to_migrate_iptables_rules": "No s'ha pogut migrar les regles legacy iptables a nftables: {error}", "migration_0017_not_enough_space": "Feu suficient espai disponible en {path} per a realitzar la migració.", diff --git a/locales/en.json b/locales/en.json index 84a01dfaa..813688a87 100644 --- a/locales/en.json +++ b/locales/en.json @@ -562,7 +562,7 @@ "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", - "service_description_php7.3-fpm": "Runs apps written in PHP with NGINX", + "service_description_php7.4-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_rspamd": "Filters spam, and other e-mail related features", diff --git a/locales/fr.json b/locales/fr.json index 715d82a35..130f91129 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -574,7 +574,7 @@ "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.", "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", + "service_description_php7.4-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": "É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.", diff --git a/locales/it.json b/locales/it.json index 6b15dd900..d160997df 100644 --- a/locales/it.json +++ b/locales/it.json @@ -424,7 +424,7 @@ "service_description_rspamd": "Filtra SPAM, e altre funzionalità legate alle mail", "service_description_redis-server": "Un database specializzato usato per un veloce accesso ai dati, task queue, e comunicazioni tra programmi", "service_description_postfix": "Usato per inviare e ricevere email", - "service_description_php7.3-fpm": "Esegue app scritte in PHP con NGINX", + "service_description_php7.4-fpm": "Esegue app scritte in PHP con NGINX", "service_description_nginx": "Serve o permette l'accesso a tutti i siti pubblicati sul tuo server", "service_description_mysql": "Memorizza i dati delle app (database SQL)", "service_description_metronome": "Gestisce gli account di messaggistica instantanea XMPP", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 5f001c12a..62a339315 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -3410,10 +3410,10 @@ def _assert_system_is_sane_for_app(manifest, when): services = manifest.get("services", []) - # Some apps use php-fpm or php5-fpm which is now php7.0-fpm + # Some apps use php-fpm, php5-fpm or php7.x-fpm which is now php7.4-fpm def replace_alias(service): - if service in ["php-fpm", "php5-fpm", "php7.0-fpm"]: - return "php7.3-fpm" + if service in ["php-fpm", "php5-fpm", "php7.0-fpm", "php7.3-fpm"]: + return "php7.4-fpm" else: return service @@ -3422,7 +3422,7 @@ def _assert_system_is_sane_for_app(manifest, when): # We only check those, mostly to ignore "custom" services # (added by apps) and because those are the most popular # services - service_filter = ["nginx", "php7.3-fpm", "mysql", "postfix"] + service_filter = ["nginx", "php7.4-fpm", "mysql", "postfix"] services = [str(s) for s in services if s in service_filter] if "nginx" not in services: @@ -3465,19 +3465,22 @@ def _assert_system_is_sane_for_app(manifest, when): LEGACY_PHP_VERSION_REPLACEMENTS = [ - ("/etc/php5", "/etc/php/7.3"), - ("/etc/php/7.0", "/etc/php/7.3"), - ("/var/run/php5-fpm", "/var/run/php/php7.3-fpm"), - ("/var/run/php/php7.0-fpm", "/var/run/php/php7.3-fpm"), - ("php5", "php7.3"), - ("php7.0", "php7.3"), + ("/etc/php5", "/etc/php/7.4"), + ("/etc/php/7.0", "/etc/php/7.4"), + ("/etc/php/7.3", "/etc/php/7.4"), + ("/var/run/php5-fpm", "/var/run/php/php7.4-fpm"), + ("/var/run/php/php7.0-fpm", "/var/run/php/php7.4-fpm"), + ("/var/run/php/php7.3-fpm", "/var/run/php/php7.4-fpm"), + ("php5", "php7.4"), + ("php7.0", "php7.4"), + ("php7.3", "php7.4"), ( 'phpversion="${phpversion:-7.0}"', - 'phpversion="${phpversion:-7.3}"', + 'phpversion="${phpversion:-7.4}"', ), # Many helpers like the composer ones use 7.0 by default ... ( '"$phpversion" == "7.0"', - '$(bc <<< "$phpversion >= 7.3") -eq 1', + '$(bc <<< "$phpversion >= 7.4") -eq 1', ), # patch ynh_install_php to refuse installing/removing php <= 7.3 ] @@ -3514,11 +3517,11 @@ def _patch_legacy_php_versions_in_settings(app_folder): settings = read_yaml(os.path.join(app_folder, "settings.yml")) if settings.get("fpm_config_dir") == "/etc/php/7.0/fpm": - settings["fpm_config_dir"] = "/etc/php/7.3/fpm" + settings["fpm_config_dir"] = "/etc/php/7.4/fpm" if settings.get("fpm_service") == "php7.0-fpm": - settings["fpm_service"] = "php7.3-fpm" + settings["fpm_service"] = "php7.4-fpm" if settings.get("phpversion") == "7.0": - settings["phpversion"] = "7.3" + settings["phpversion"] = "7.4" # We delete these checksums otherwise the file will appear as manually modified list_to_remove = ["checksum__etc_php_7.0_fpm_pool", "checksum__etc_nginx_conf.d"] diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 3978e835d..862c07690 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1183,7 +1183,7 @@ class RestoreManager: def _patch_legacy_php_versions_in_csv_file(self): """ - Apply dirty patch to redirect php5 and php7.0 files to php7.3 + Apply dirty patch to redirect php5 and php7.0 files to php7.4 """ backup_csv = os.path.join(self.work_dir, "backup.csv") From c70420438aa21d7dd13b7b98d6bc0029939f425a Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 9 Mar 2021 18:10:55 +0100 Subject: [PATCH 0050/1155] add python-is-python3 dependency --- debian/control | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/control b/debian/control index e78718d66..21485b114 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-publicsuffix2 + , python-is-python3 , apt, apt-transport-https, apt-utils, dirmngr , php7.4-common, php7.4-fpm, php7.4-ldap, php7.4-intl , mariadb-server, php7.4-mysql From a27a4c26df9b273089f5a9fc0e7499f135015599 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 9 Mar 2021 18:39:57 +0100 Subject: [PATCH 0051/1155] yaml load -> safe_load --- data/actionsmap/yunohost_completion.py | 2 +- data/helpers.d/setting | 4 ++-- data/hooks/conf_regen/01-yunohost | 4 ++-- 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 +- 9 files changed, 12 insertions(+), 12 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 3d65d34cd..9196e3de9 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -202,10 +202,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/src/yunohost/app.py b/src/yunohost/app.py index 62a339315..f405641c4 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1510,7 +1510,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) @@ -2167,7 +2167,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 b800cd42c..a153689a6 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 b7703c32a1e8b7b4c649ad2d7017a249923bbc9f Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 9 Mar 2021 18:52:24 +0100 Subject: [PATCH 0052/1155] publicsuffix -> publicsuffix2 --- 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 719ce4d6a..795ca1621 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 publicsuffix2 import PublicSuffixList from moulinette.utils.process import check_output From 8aae99472090a0cdddedf6785e463c6858576c70 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Wed, 10 Mar 2021 11:24:11 +0100 Subject: [PATCH 0053/1155] dirty patch to wait for services to finish reloading --- src/yunohost/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index f405641c4..4e2cd5317 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -3432,6 +3432,7 @@ def _assert_system_is_sane_for_app(manifest, when): # Wait if a service is reloading test_nb = 0 + while test_nb < 16: if not any(s for s in services if service_status(s)["status"] == "reloading"): break From 7ff1ce5432d7fb64d04a77e7b1bbadfa5401dfac Mon Sep 17 00:00:00 2001 From: Kay0u Date: Wed, 10 Mar 2021 11:30:28 +0100 Subject: [PATCH 0054/1155] change python version in tests from 3.7 to 3.9 --- .gitlab/ci/lint.gitlab-ci.yml | 13 ++++++------- tox.ini | 14 +++++++------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/.gitlab/ci/lint.gitlab-ci.yml b/.gitlab/ci/lint.gitlab-ci.yml index 9c48bd912..03861eca1 100644 --- a/.gitlab/ci/lint.gitlab-ci.yml +++ b/.gitlab/ci/lint.gitlab-ci.yml @@ -3,21 +3,20 @@ ######################################## # later we must fix lint and format-check jobs and remove "allow_failure" ---- -lint37: +lint39: stage: lint image: "before-install" needs: [] allow_failure: true script: - - tox -e py37-lint + - tox -e py39-lint -invalidcode37: +invalidcode39: stage: lint image: "before-install" needs: [] script: - - tox -e py37-invalidcode + - tox -e py39-invalidcode format-check: stage: lint @@ -25,7 +24,7 @@ format-check: allow_failure: true needs: [] script: - - tox -e py37-black-check + - tox -e py39-black-check format-run: stage: lint @@ -40,7 +39,7 @@ format-run: script: # 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 + - tox -e py39-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}" diff --git a/tox.ini b/tox.ini index c25d8bf8f..0af648d63 100644 --- a/tox.ini +++ b/tox.ini @@ -1,13 +1,13 @@ [tox] -envlist = py37-{lint,invalidcode},py37-black-{run,check} +envlist = py39-{lint,invalidcode},py39-black-{run,check} [testenv] skip_install=True deps = - py37-{lint,invalidcode}: flake8 - py37-black-{run,check}: black + py39-{lint,invalidcode}: flake8 + py39-black-{run,check}: black 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 + py39-lint: flake8 src doc data tests --ignore E402,E501,E203,W503 --exclude src/yunohost/vendor + py39-invalidcode: flake8 src data --exclude src/yunohost/tests,src/yunohost/vendor --select F + py39-black-check: black --check --diff src doc data tests + py39-black-run: black src doc data tests From 3fa96fc91b4d288a474ff8c60f167bda8020e959 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 28 May 2021 01:32:33 +0200 Subject: [PATCH 0055/1155] Naive attempt to fix test by adding an apt update --- .gitlab/ci/build.gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab/ci/build.gitlab-ci.yml b/.gitlab/ci/build.gitlab-ci.yml index 717a5ee73..82def4eb3 100644 --- a/.gitlab/ci/build.gitlab-ci.yml +++ b/.gitlab/ci/build.gitlab-ci.yml @@ -5,6 +5,7 @@ YNH_SOURCE: "https://github.com/yunohost" before_script: - mkdir -p $YNH_BUILD_DIR + - DEBIAN_FRONTEND=noninteractive apt update artifacts: paths: - $YNH_BUILD_DIR/*.deb From 1051dbb3cc839604d05d1e904488129addf1571d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 28 May 2021 02:17:40 +0200 Subject: [PATCH 0056/1155] Misc legacy cleanup --- data/actionsmap/yunohost.yml | 95 ------ data/helpers.d/backup | 13 - data/helpers.d/utils | 52 ---- data/hooks/conf_regen/01-yunohost | 12 - data/hooks/conf_regen/03-ssh | 8 - data/hooks/conf_regen/06-slapd | 5 - data/hooks/conf_regen/15-nginx | 14 - data/hooks/conf_regen/34-mysql | 23 -- src/yunohost/app.py | 144 +-------- .../data_migrations/0015_migrate_to_buster.py | 291 ------------------ .../0016_php70_to_php73_pools.py | 83 ----- .../0017_postgresql_9p6_to_11.py | 82 ----- .../data_migrations/0018_xtable_to_nftable.py | 126 -------- .../0019_extend_permissions_features.py | 107 ------- .../0020_ssh_sftp_permissions.py | 100 ------ src/yunohost/dyndns.py | 12 - src/yunohost/service.py | 36 --- src/yunohost/tools.py | 33 +- src/yunohost/user.py | 7 - src/yunohost/utils/legacy.py | 239 -------------- 20 files changed, 10 insertions(+), 1472 deletions(-) delete mode 100644 src/yunohost/data_migrations/0015_migrate_to_buster.py delete mode 100644 src/yunohost/data_migrations/0016_php70_to_php73_pools.py delete mode 100644 src/yunohost/data_migrations/0017_postgresql_9p6_to_11.py delete mode 100644 src/yunohost/data_migrations/0018_xtable_to_nftable.py delete mode 100644 src/yunohost/data_migrations/0019_extend_permissions_features.py delete mode 100644 src/yunohost/data_migrations/0020_ssh_sftp_permissions.py delete mode 100644 src/yunohost/utils/legacy.py diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 5df1c0877..0631a20c9 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -97,9 +97,6 @@ user: pattern: &pattern_lastname - !!str ^([^\W\d_]{1,30}[ ,.'-]{0,3})+$ - "pattern_lastname" - -m: - full: --mail - help: (Deprecated, see --domain) Main unique email address -p: full: --password help: User password @@ -596,9 +593,6 @@ app: app: help: Name, local path or git URL of the app to fetch the manifest of - fetchlist: - deprecated: true - ### app_list() list: action_help: List installed apps @@ -770,36 +764,6 @@ app: new_label: help: New app label - ### app_addaccess() TODO: Write help - addaccess: - action_help: Grant access right to users (everyone by default) - deprecated: true - arguments: - apps: - nargs: "+" - -u: - full: --users - nargs: "*" - - ### app_removeaccess() TODO: Write help - removeaccess: - action_help: Revoke access right to users (everyone by default) - deprecated: true - arguments: - apps: - nargs: "+" - -u: - full: --users - nargs: "*" - - ### app_clearaccess() - clearaccess: - action_help: Reset access rights for the app - deprecated: true - arguments: - apps: - nargs: "+" - subcategories: action: @@ -1023,13 +987,6 @@ service: full: --log help: Absolute path to log file to display nargs: "+" - -t: - full: --log_type - help: Type of the log (file or systemd) - nargs: "+" - choices: - - file - - systemd --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: @@ -1043,9 +1000,6 @@ service: 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: @@ -1147,35 +1101,6 @@ service: default: 50 type: int - ### service_regen_conf() - regen-conf: - action_help: Regenerate the configuration file(s) for a service - deprecated_alias: - - regenconf - arguments: - names: - help: Services name to regenerate configuration of - nargs: "*" - metavar: NAME - -d: - full: --with-diff - help: Show differences in case of configuration changes - action: store_true - -f: - full: --force - help: > - Override all manual modifications in configuration - files - action: store_true - -n: - full: --dry-run - help: Show what would have been regenerated - action: store_true - -p: - full: --list-pending - help: List pending configuration files and exit - action: store_true - ############################# # Firewall # ############################# @@ -1351,14 +1276,6 @@ dyndns: full: --ipv6 help: IPv6 address to send - ### dyndns_installcron() - installcron: - deprecated: true - - ### dyndns_removecron() - removecron: - deprecated: true - ############################# # Tools # @@ -1438,12 +1355,6 @@ tools: nargs: "?" metavar: TARGET default: all - --apps: - help: (Deprecated, see first positional arg) Fetch the application list to check which apps can be upgraded - action: store_true - --system: - help: (Deprecated, see first positional arg) Fetch available system packages upgrades (equivalent to apt update) - action: store_true ### tools_upgrade() upgrade: @@ -1456,12 +1367,6 @@ tools: - apps - system nargs: "?" - --apps: - help: (Deprecated, see first positional arg) Upgrade all applications - action: store_true - --system: - help: (Deprecated, see first positional arg) Upgrade only the system packages - action: store_true ### tools_shell() shell: diff --git a/data/helpers.d/backup b/data/helpers.d/backup index 17da0fb2e..156f12dfb 100644 --- a/data/helpers.d/backup +++ b/data/helpers.d/backup @@ -9,7 +9,6 @@ CAN_BIND=${CAN_BIND:-1} # | 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 # @@ -303,18 +302,6 @@ ynh_restore_file () { fi } -# Deprecated helper since it's a dangerous one! -# -# [internal] -# -ynh_bind_or_cp() { - local AS_ROOT=${3:-0} - local NO_ROOT=0 - [[ "${AS_ROOT}" = "1" ]] || NO_ROOT=1 - ynh_print_warn --message="This helper is deprecated, you should use ynh_backup instead" - ynh_backup "$1" "$2" 1 -} - # Calculate and store a file checksum into the app settings # # usage: ynh_store_file_checksum --file=file diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 00bec89ac..3b2f49abf 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -503,26 +503,6 @@ ynh_get_debian_release () { echo $(lsb_release --codename --short) } -# Create a directory under /tmp -# -# [internal] -# -# Deprecated helper -# -# usage: ynh_mkdir_tmp -# | ret: the created directory path -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 --directory) - - # Give rights to other users could be a security risk. - # But for retrocompatibility we need it. (This helpers is deprecated) - chmod 755 $TMP_DIR - echo $TMP_DIR -} - # Remove a file or a directory securely # # usage: ynh_secure_remove --file=path_to_remove @@ -565,38 +545,6 @@ ynh_secure_remove () { fi } -# Extract a key from a plain command output -# -# [internal] -# -# (Deprecated, use --output-as json and jq instead) -ynh_get_plain_key() { - local prefix="#" - local founded=0 - # We call this key_ so that it's not caught as - # an info to be redacted by the core - local key_=$1 - shift - while read line - do - if [[ "$founded" == "1" ]] - then - [[ "$line" =~ ^${prefix}[^#] ]] && return - echo $line - elif [[ "$line" =~ ^${prefix}${key_}$ ]] - then - if [[ -n "${1:-}" ]] - then - prefix+="#" - key_=$1 - shift - else - founded=1 - fi - fi - done -} - # Read the value of a key in a ynh manifest file # # usage: ynh_read_manifest --manifest="manifest.json" --key="key" diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index 9196e3de9..2c9df2a64 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -113,11 +113,6 @@ SHELL=/bin/bash 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 - touch $pending_dir/etc/etckeeper/etckeeper.conf - # Skip ntp if inside a container (inspired from the conf of systemd-timesyncd) mkdir -p ${pending_dir}/etc/systemd/system/ntp.service.d/ echo " @@ -242,13 +237,6 @@ 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: yaml.safe_dump(services, f, default_flow_style=False) diff --git a/data/hooks/conf_regen/03-ssh b/data/hooks/conf_regen/03-ssh index d0c4bd31c..6b0445fd0 100755 --- a/data/hooks/conf_regen/03-ssh +++ b/data/hooks/conf_regen/03-ssh @@ -7,10 +7,6 @@ set -e do_pre_regen() { pending_dir=$1 - # If the (legacy) 'from_script' flag is here, - # we won't touch anything in the ssh config. - [[ ! -f /etc/yunohost/from_script ]] || return 0 - cd /usr/share/yunohost/templates/ssh # do not listen to IPv6 if unavailable @@ -34,10 +30,6 @@ do_pre_regen() { do_post_regen() { regen_conf_files=$1 - # If the (legacy) 'from_script' flag is here, - # we won't touch anything in the ssh config. - [[ ! -f /etc/yunohost/from_script ]] || return 0 - # If no file changed, there's nothing to do [[ -n "$regen_conf_files" ]] || return 0 diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd index 3fa3a0fd2..06f89cec5 100755 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -110,11 +110,6 @@ do_pre_regen() { schema_dir="${ldap_dir}/schema" mkdir -p "$ldap_dir" "$schema_dir" - # remove legacy configuration file - [ ! -f /etc/ldap/slapd-yuno.conf ] || touch "${ldap_dir}/slapd-yuno.conf" - [ ! -f /etc/ldap/slapd.conf ] || touch "${ldap_dir}/slapd.conf" - [ ! -f /etc/ldap/schema/yunohost.schema ] || touch "${schema_dir}/yunohost.schema" - cd /usr/share/yunohost/templates/slapd # copy configuration files diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx index 8875693c6..64af48257 100755 --- a/data/hooks/conf_regen/15-nginx +++ b/data/hooks/conf_regen/15-nginx @@ -119,20 +119,6 @@ do_post_regen() { mkdir -p "/etc/nginx/conf.d/${domain}.d" done - # Get rid of legacy lets encrypt snippets - 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 - # 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 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; } diff --git a/data/hooks/conf_regen/34-mysql b/data/hooks/conf_regen/34-mysql index d5180949e..136207cc8 100755 --- a/data/hooks/conf_regen/34-mysql +++ b/data/hooks/conf_regen/34-mysql @@ -30,29 +30,6 @@ do_post_regen() { echo "" | mysql && echo "Can't connect to mysql using unix_socket auth ... something went wrong during initial configuration of mysql !?" >&2 fi - # 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 2>/dev/null - 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 2>/dev/null - 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 # c.f. https://forum.yunohost.org/t/mysql-ne-fonctionne-pas/11661 # Playing with enable/disable allows to recreate the proper symlinks. diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 4e2cd5317..0a0b73668 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -137,40 +137,13 @@ def app_search(string): return catalog_of_apps -# 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(target="apps") - - -def app_list(full=False, installed=False, filter=None): +def app_list(full=False): """ 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: @@ -1258,64 +1231,6 @@ def app_remove(operation_logger, app): _assert_system_is_sane_for_app(manifest, "post") -def app_addaccess(apps, users=[]): - """ - Grant access right to users (everyone by default) - - Keyword argument: - users - apps - - """ - from yunohost.permission import user_permission_update - - output = {} - for app in apps: - permission = user_permission_update( - app + ".main", add=users, remove="all_users" - ) - output[app] = permission["corresponding_users"] - - return {"allowed_users": output} - - -def app_removeaccess(apps, users=[]): - """ - Revoke access right to users (everyone by default) - - Keyword argument: - users - apps - - """ - from yunohost.permission import user_permission_update - - output = {} - for app in apps: - permission = user_permission_update(app + ".main", remove=users) - output[app] = permission["corresponding_users"] - - return {"allowed_users": output} - - -def app_clearaccess(apps): - """ - Reset access rights for the app - - Keyword argument: - apps - - """ - from yunohost.permission import user_permission_reset - - output = {} - for app in apps: - permission = user_permission_reset(app + ".main") - output[app] = permission["corresponding_users"] - - return {"allowed_users": output} - - @is_unit_operation() def app_makedefault(operation_logger, app, domain=None): """ @@ -1643,10 +1558,6 @@ def app_ssowatconf(): write_to_json("/etc/ssowat/conf.json", conf_dict, sort_keys=True, indent=4) - from .utils.legacy import translate_legacy_rules_in_ssowant_conf_json_persistent - - translate_legacy_rules_in_ssowant_conf_json_persistent() - logger.debug(m18n.n("ssowat_conf_generated")) @@ -3542,36 +3453,10 @@ def _patch_legacy_helpers(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": { - "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": { - "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 app initdb": {"important": True}, + "yunohost app checkport": {"important": True}, "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": { - "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, - }, + "yunohost app checkurl": {"important": True}, # Remove # Automatic diagnosis data from YunoHost # __PRE_TAG1__$(yunohost tools diagnosis | ...)__PRE_TAG2__" @@ -3582,26 +3467,11 @@ def _patch_legacy_helpers(app_folder): "important": False, }, # Old $1, $2 in backup/restore scripts... - "app=$2": { - "only_for": ["scripts/backup", "scripts/restore"], - "pattern": r"app=\$2", - "replace": r"app=$YNH_APP_INSTANCE_NAME", - "important": True, - }, + "app=$2": {"only_for": ["scripts/backup", "scripts/restore"], "important": True}, # Old $1, $2 in backup/restore scripts... - "backup_dir=$1": { - "only_for": ["scripts/backup", "scripts/restore"], - "pattern": r"backup_dir=\$1", - "replace": r"backup_dir=.", - "important": True, - }, + "backup_dir=$1": {"only_for": ["scripts/backup", "scripts/restore"], "important": True}, # Old $1, $2 in backup/restore scripts... - "restore_dir=$1": { - "only_for": ["scripts/restore"], - "pattern": r"restore_dir=\$1", - "replace": r"restore_dir=.", - "important": True, - }, + "restore_dir=$1": {"only_for": ["scripts/restore"], "important": True}, # Old $1, $2 in install scripts... # We ain't patching that shit because it ain't trivial to patch all args... "domain=$1": {"only_for": ["scripts/install"], "important": True}, diff --git a/src/yunohost/data_migrations/0015_migrate_to_buster.py b/src/yunohost/data_migrations/0015_migrate_to_buster.py deleted file mode 100644 index 4f2d4caf8..000000000 --- a/src/yunohost/data_migrations/0015_migrate_to_buster.py +++ /dev/null @@ -1,291 +0,0 @@ -import glob -import os - -from moulinette import m18n -from yunohost.utils.error import YunohostError -from moulinette.utils.log import getActionLogger -from moulinette.utils.process import check_output, call_async_output -from moulinette.utils.filesystem import read_file - -from yunohost.tools import Migration, tools_update, tools_upgrade -from yunohost.app import unstable_apps -from yunohost.regenconf import manually_modified_files -from yunohost.utils.filesystem import free_space_in_directory -from yunohost.utils.packages import ( - get_ynh_package_version, - _list_upgradable_apt_packages, -) - -logger = getActionLogger("yunohost.migration") - - -class MyMigration(Migration): - - "Upgrade the system to Debian Buster and Yunohost 4.x" - - mode = "manual" - - def run(self): - - self.check_assertions() - - logger.info(m18n.n("migration_0015_start")) - - # - # Make sure certificates do not use weak signature hash algorithms (md5, sha1) - # otherwise nginx will later refuse to start which result in - # catastrophic situation - # - self.validate_and_upgrade_cert_if_necessary() - - # - # Patch sources.list - # - logger.info(m18n.n("migration_0015_patching_sources_list")) - self.patch_apt_sources_list() - tools_update(target="system") - - # Tell libc6 it's okay to restart system stuff during the upgrade - os.system( - "echo 'libc6 libraries/restart-without-asking boolean true' | debconf-set-selections" - ) - - # Don't send an email to root about the postgresql migration. It should be handled automatically after. - os.system( - "echo 'postgresql-common postgresql-common/obsolete-major seen true' | debconf-set-selections" - ) - - # - # Specific packages upgrades - # - logger.info(m18n.n("migration_0015_specific_upgrade")) - - # Update unscd independently, was 0.53-1+yunohost on stretch (custom build of ours) but now it's 0.53-1+b1 on vanilla buster, - # which for apt appears as a lower version (hence the --allow-downgrades and the hardcoded version number) - unscd_version = check_output( - 'dpkg -s unscd | grep "^Version: " | cut -d " " -f 2' - ) - if "yunohost" in unscd_version: - new_version = check_output( - "LC_ALL=C apt policy unscd 2>/dev/null | grep -v '\\*\\*\\*' | grep http -B1 | head -n 1 | awk '{print $1}'" - ).strip() - if new_version: - self.apt_install("unscd=%s --allow-downgrades" % new_version) - else: - logger.warning("Could not identify which version of unscd to install") - - # Upgrade libpam-modules independently, small issue related to willing to overwrite a file previously provided by Yunohost - libpammodules_version = check_output( - 'dpkg -s libpam-modules | grep "^Version: " | cut -d " " -f 2' - ) - if not libpammodules_version.startswith("1.3"): - self.apt_install('libpam-modules -o Dpkg::Options::="--force-overwrite"') - - # - # Main upgrade - # - logger.info(m18n.n("migration_0015_main_upgrade")) - - apps_packages = self.get_apps_equivs_packages() - self.hold(apps_packages) - tools_upgrade(target="system", allow_yunohost_upgrade=False) - - if self.debian_major_version() == 9: - raise YunohostError("migration_0015_still_on_stretch_after_main_upgrade") - - # Clean the mess - logger.info(m18n.n("migration_0015_cleaning_up")) - os.system("apt autoremove --assume-yes") - os.system("apt clean --assume-yes") - - # - # Yunohost upgrade - # - logger.info(m18n.n("migration_0015_yunohost_upgrade")) - self.unhold(apps_packages) - tools_upgrade(target="system") - - def debian_major_version(self): - # The python module "platform" and lsb_release are not reliable because - # on some setup, they may still return Release=9 even after upgrading to - # buster ... (Apparently this is related to OVH overriding some stuff - # with /etc/lsb-release for instance -_-) - # Instead, we rely on /etc/os-release which should be the raw info from - # the distribution... - 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_ynh_package_version("yunohost")["version"].split(".")[0]) - - def check_assertions(self): - - # Be on stretch (9.x) and yunohost 3.x - # NB : we do both check to cover situations where the upgrade crashed - # in the middle and debian version could be > 9.x but yunohost package - # would still be in 3.x... - if ( - not self.debian_major_version() == 9 - and not self.yunohost_major_version() == 3 - ): - raise YunohostError("migration_0015_not_stretch") - - # Have > 1 Go free space on /var/ ? - if free_space_in_directory("/var/") / (1024 ** 3) < 1.0: - raise YunohostError("migration_0015_not_enough_free_space") - - # Check system is up to date - # (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(target="system") - upgradable_system_packages = list(_list_upgradable_apt_packages()) - if upgradable_system_packages: - raise YunohostError("migration_0015_system_not_fully_up_to_date") - - @property - def disclaimer(self): - - # Avoid having a super long disclaimer + uncessary check if we ain't - # on stretch / yunohost 3.x anymore - # NB : we do both check to cover situations where the upgrade crashed - # in the middle and debian version could be >= 10.x but yunohost package - # would still be in 3.x... - if ( - not self.debian_major_version() == 9 - and not self.yunohost_major_version() == 3 - ): - return None - - # Get list of problematic apps ? I.e. not official or community+working - problematic_apps = unstable_apps() - problematic_apps = "".join(["\n - " + app for app in problematic_apps]) - - # Manually modified files ? (c.f. yunohost service regen-conf) - modified_files = manually_modified_files() - modified_files = "".join(["\n - " + f for f in modified_files]) - - message = m18n.n("migration_0015_general_warning") - - message = ( - "N.B.: This migration has been tested by the community over the last few months but has only been declared stable recently. If your server hosts critical services and if you are not too confident with debugging possible issues, we recommend you to wait a little bit more while we gather more feedback and polish things up. If on the other hand you are relatively confident with debugging small issues that may arise, you are encouraged to run this migration ;)! You can read about remaining known issues and feedback from the community here: https://forum.yunohost.org/t/12195\n\n" - + message - ) - - if problematic_apps: - message += "\n\n" + m18n.n( - "migration_0015_problematic_apps_warning", - problematic_apps=problematic_apps, - ) - - if modified_files: - message += "\n\n" + m18n.n( - "migration_0015_modified_files", manually_modified_files=modified_files - ) - - return message - - def patch_apt_sources_list(self): - - sources_list = glob.glob("/etc/apt/sources.list.d/*.list") - sources_list.append("/etc/apt/sources.list") - - # This : - # - replace single 'stretch' occurence by 'buster' - # - comments lines containing "backports" - # - replace 'stretch/updates' by 'strech/updates' (or same with -) - for f in sources_list: - command = ( - "sed -i -e 's@ stretch @ buster @g' " - "-e '/backports/ s@^#*@#@' " - "-e 's@ stretch/updates @ buster/updates @g' " - "-e 's@ stretch-@ buster-@g' " - "{}".format(f) - ) - os.system(command) - - def get_apps_equivs_packages(self): - - command = ( - "dpkg --get-selections" - " | grep -v deinstall" - " | awk '{print $1}'" - " | { grep 'ynh-deps$' || true; }" - ) - - output = check_output(command) - - return output.split("\n") if output else [] - - def hold(self, packages): - for package in packages: - os.system("apt-mark hold {}".format(package)) - - def unhold(self, packages): - for package in packages: - os.system("apt-mark unhold {}".format(package)) - - def apt_install(self, cmd): - def is_relevant(line): - return "Reading database ..." not in line.rstrip() - - callbacks = ( - lambda l: logger.info("+ " + l.rstrip() + "\r") - if is_relevant(l) - else logger.debug(l.rstrip() + "\r"), - lambda l: logger.warning(l.rstrip()), - ) - - cmd = ( - "LC_ALL=C DEBIAN_FRONTEND=noninteractive APT_LISTCHANGES_FRONTEND=none apt install --quiet -o=Dpkg::Use-Pty=0 --fix-broken --assume-yes " - + cmd - ) - - logger.debug("Running: %s" % cmd) - - call_async_output(cmd, callbacks, shell=True) - - def validate_and_upgrade_cert_if_necessary(self): - - active_certs = set( - check_output("grep -roh '/.*crt.pem' /etc/nginx/").split("\n") - ) - - cmd = "LC_ALL=C openssl x509 -in %s -text -noout | grep -i 'Signature Algorithm:' | awk '{print $3}' | uniq" - - default_crt = "/etc/yunohost/certs/yunohost.org/crt.pem" - default_key = "/etc/yunohost/certs/yunohost.org/key.pem" - default_signature = ( - check_output(cmd % default_crt) if default_crt in active_certs else None - ) - if default_signature is not None and ( - default_signature.startswith("md5") or default_signature.startswith("sha1") - ): - logger.warning( - "%s is using a pretty old certificate incompatible with newer versions of nginx ... attempting to regenerate a fresh one" - % default_crt - ) - - os.system("mv %s %s.old" % (default_crt, default_crt)) - os.system("mv %s %s.old" % (default_key, default_key)) - ret = os.system("/usr/share/yunohost/hooks/conf_regen/02-ssl init") - - if ret != 0 or not os.path.exists(default_crt): - logger.error("Upgrading the certificate failed ... reverting") - os.system("mv %s.old %s" % (default_crt, default_crt)) - os.system("mv %s.old %s" % (default_key, default_key)) - - signatures = {cert: check_output(cmd % cert) for cert in active_certs} - - def cert_is_weak(cert): - sig = signatures[cert] - return sig.startswith("md5") or sig.startswith("sha1") - - weak_certs = [cert for cert in signatures.keys() if cert_is_weak(cert)] - if weak_certs: - raise YunohostError( - "migration_0015_weak_certs", certs=", ".join(weak_certs) - ) diff --git a/src/yunohost/data_migrations/0016_php70_to_php73_pools.py b/src/yunohost/data_migrations/0016_php70_to_php73_pools.py deleted file mode 100644 index 6b424f211..000000000 --- a/src/yunohost/data_migrations/0016_php70_to_php73_pools.py +++ /dev/null @@ -1,83 +0,0 @@ -import os -import glob -from shutil import copy2 - -from moulinette.utils.log import getActionLogger - -from yunohost.app import _is_installed, _patch_legacy_php_versions_in_settings -from yunohost.tools import Migration -from yunohost.service import _run_service_command - -logger = getActionLogger("yunohost.migration") - -PHP70_POOLS = "/etc/php/7.0/fpm/pool.d" -PHP73_POOLS = "/etc/php/7.3/fpm/pool.d" - -PHP70_SOCKETS_PREFIX = "/run/php/php7.0-fpm" -PHP73_SOCKETS_PREFIX = "/run/php/php7.3-fpm" - -MIGRATION_COMMENT = ( - "; YunoHost note : this file was automatically moved from {}".format(PHP70_POOLS) -) - - -class MyMigration(Migration): - - "Migrate php7.0-fpm 'pool' conf files to php7.3" - - dependencies = ["migrate_to_buster"] - - def run(self): - # Get list of php7.0 pool files - php70_pool_files = glob.glob("{}/*.conf".format(PHP70_POOLS)) - - # Keep only basenames - php70_pool_files = [os.path.basename(f) for f in php70_pool_files] - - # Ignore the "www.conf" (default stuff, probably don't want to touch it ?) - php70_pool_files = [f for f in php70_pool_files if f != "www.conf"] - - for f in php70_pool_files: - - # Copy the files to the php7.3 pool - src = "{}/{}".format(PHP70_POOLS, f) - dest = "{}/{}".format(PHP73_POOLS, f) - copy2(src, dest) - - # Replace the socket prefix if it's found - c = "sed -i -e 's@{}@{}@g' {}".format( - PHP70_SOCKETS_PREFIX, PHP73_SOCKETS_PREFIX, dest - ) - os.system(c) - - # Also add a comment that it was automatically moved from php7.0 - # (for human traceability and backward migration) - c = "sed -i '1i {}' {}".format(MIGRATION_COMMENT, dest) - os.system(c) - - app_id = os.path.basename(f)[: -len(".conf")] - if _is_installed(app_id): - _patch_legacy_php_versions_in_settings( - "/etc/yunohost/apps/%s/" % app_id - ) - - nginx_conf_files = glob.glob("/etc/nginx/conf.d/*.d/%s.conf" % app_id) - for f in nginx_conf_files: - # Replace the socket prefix if it's found - c = "sed -i -e 's@{}@{}@g' {}".format( - PHP70_SOCKETS_PREFIX, PHP73_SOCKETS_PREFIX, f - ) - os.system(c) - - os.system( - "rm /etc/logrotate.d/php7.0-fpm" - ) # We remove this otherwise the logrotate cron will be unhappy - - # Reload/restart the php pools - _run_service_command("restart", "php7.3-fpm") - _run_service_command("enable", "php7.3-fpm") - os.system("systemctl stop php7.0-fpm") - os.system("systemctl disable php7.0-fpm") - - # Reload nginx - _run_service_command("reload", "nginx") diff --git a/src/yunohost/data_migrations/0017_postgresql_9p6_to_11.py b/src/yunohost/data_migrations/0017_postgresql_9p6_to_11.py deleted file mode 100644 index cbdfabb1f..000000000 --- a/src/yunohost/data_migrations/0017_postgresql_9p6_to_11.py +++ /dev/null @@ -1,82 +0,0 @@ -import subprocess - -from moulinette import m18n -from yunohost.utils.error import YunohostError, YunohostValidationError -from moulinette.utils.log import getActionLogger - -from yunohost.tools import Migration -from yunohost.utils.filesystem import free_space_in_directory, space_used_by_directory - -logger = getActionLogger("yunohost.migration") - - -class MyMigration(Migration): - - "Migrate DBs from Postgresql 9.6 to 11 after migrating to Buster" - - dependencies = ["migrate_to_buster"] - - def run(self): - - if not self.package_is_installed("postgresql-9.6"): - logger.warning(m18n.n("migration_0017_postgresql_96_not_installed")) - return - - if not self.package_is_installed("postgresql-11"): - raise YunohostValidationError("migration_0017_postgresql_11_not_installed") - - # Make sure there's a 9.6 cluster - try: - self.runcmd("pg_lsclusters | grep -q '^9.6 '") - except Exception: - logger.warning( - "It looks like there's not active 9.6 cluster, so probably don't need to run this migration" - ) - return - - if not space_used_by_directory( - "/var/lib/postgresql/9.6" - ) > free_space_in_directory("/var/lib/postgresql"): - raise YunohostValidationError( - "migration_0017_not_enough_space", path="/var/lib/postgresql/" - ) - - self.runcmd("systemctl stop postgresql") - self.runcmd( - "LC_ALL=C pg_dropcluster --stop 11 main || true" - ) # We do not trigger an exception if the command fails because that probably means cluster 11 doesn't exists, which is fine because it's created during the pg_upgradecluster) - self.runcmd("LC_ALL=C pg_upgradecluster -m upgrade 9.6 main") - self.runcmd("LC_ALL=C pg_dropcluster --stop 9.6 main") - self.runcmd("systemctl start postgresql") - - def package_is_installed(self, package_name): - - (returncode, out, err) = self.runcmd( - "dpkg --list | grep '^ii ' | grep -q -w {}".format(package_name), - raise_on_errors=False, - ) - return returncode == 0 - - def runcmd(self, cmd, raise_on_errors=True): - - logger.debug("Running command: " + cmd) - - p = subprocess.Popen( - cmd, - shell=True, - executable="/bin/bash", - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - - out, err = p.communicate() - returncode = p.returncode - if raise_on_errors and returncode != 0: - raise YunohostError( - "Failed to run command '{}'.\nreturncode: {}\nstdout:\n{}\nstderr:\n{}\n".format( - cmd, returncode, out, err - ) - ) - - out = out.strip().split("\n") - return (returncode, out, err) diff --git a/src/yunohost/data_migrations/0018_xtable_to_nftable.py b/src/yunohost/data_migrations/0018_xtable_to_nftable.py deleted file mode 100644 index 94b47d944..000000000 --- a/src/yunohost/data_migrations/0018_xtable_to_nftable.py +++ /dev/null @@ -1,126 +0,0 @@ -import os -import subprocess - -from moulinette import m18n -from yunohost.utils.error import YunohostError -from moulinette.utils.log import getActionLogger - -from yunohost.firewall import firewall_reload -from yunohost.service import service_restart -from yunohost.tools import Migration - -logger = getActionLogger("yunohost.migration") - - -class MyMigration(Migration): - - "Migrate legacy iptables rules from stretch that relied on xtable and should now rely on nftable" - - dependencies = ["migrate_to_buster"] - - def run(self): - - self.do_ipv4 = os.system("iptables -w -L >/dev/null") == 0 - self.do_ipv6 = os.system("ip6tables -w -L >/dev/null") == 0 - - if not self.do_ipv4: - logger.warning(m18n.n("iptables_unavailable")) - if not self.do_ipv6: - logger.warning(m18n.n("ip6tables_unavailable")) - - backup_folder = "/home/yunohost.backup/premigration/xtable_to_nftable/" - if not os.path.exists(backup_folder): - os.makedirs(backup_folder, 0o750) - self.backup_rules_ipv4 = os.path.join(backup_folder, "legacy_rules_ipv4") - self.backup_rules_ipv6 = os.path.join(backup_folder, "legacy_rules_ipv6") - - # Backup existing legacy rules to be able to rollback - if self.do_ipv4 and not os.path.exists(self.backup_rules_ipv4): - self.runcmd( - "iptables-legacy -L >/dev/null" - ) # For some reason if we don't do this, iptables-legacy-save is empty ? - self.runcmd("iptables-legacy-save > %s" % self.backup_rules_ipv4) - assert ( - open(self.backup_rules_ipv4).read().strip() - ), "Uhoh backup of legacy ipv4 rules is empty !?" - if self.do_ipv6 and not os.path.exists(self.backup_rules_ipv6): - self.runcmd( - "ip6tables-legacy -L >/dev/null" - ) # For some reason if we don't do this, iptables-legacy-save is empty ? - self.runcmd("ip6tables-legacy-save > %s" % self.backup_rules_ipv6) - assert ( - open(self.backup_rules_ipv6).read().strip() - ), "Uhoh backup of legacy ipv6 rules is empty !?" - - # We inject the legacy rules (iptables-legacy) into the new iptable (just "iptables") - try: - if self.do_ipv4: - self.runcmd("iptables-legacy-save | iptables-restore") - if self.do_ipv6: - self.runcmd("ip6tables-legacy-save | ip6tables-restore") - except Exception as e: - self.rollback() - raise YunohostError( - "migration_0018_failed_to_migrate_iptables_rules", error=e - ) - - # Reset everything in iptables-legacy - # Stolen from https://serverfault.com/a/200642 - try: - if self.do_ipv4: - self.runcmd( - "iptables-legacy-save | awk '/^[*]/ { print $1 }" # Keep lines like *raw, *filter and *nat - ' /^:[A-Z]+ [^-]/ { print $1 " ACCEPT" ; }' # Turn all policies to accept - " /COMMIT/ { print $0; }'" # Keep the line COMMIT - " | iptables-legacy-restore" - ) - if self.do_ipv6: - self.runcmd( - "ip6tables-legacy-save | awk '/^[*]/ { print $1 }" # Keep lines like *raw, *filter and *nat - ' /^:[A-Z]+ [^-]/ { print $1 " ACCEPT" ; }' # Turn all policies to accept - " /COMMIT/ { print $0; }'" # Keep the line COMMIT - " | ip6tables-legacy-restore" - ) - except Exception as e: - self.rollback() - raise YunohostError("migration_0018_failed_to_reset_legacy_rules", error=e) - - # You might be wondering "uh but is it really useful to - # iptables-legacy-save | iptables-restore considering firewall_reload() - # flush/resets everything anyway ?" - # But the answer is : firewall_reload() only resets the *filter table. - # On more complex setups (e.g. internet cube or docker) you will also - # have rules in the *nat (or maybe *raw?) sections of iptables. - firewall_reload() - service_restart("fail2ban") - - def rollback(self): - - if self.do_ipv4: - self.runcmd("iptables-legacy-restore < %s" % self.backup_rules_ipv4) - if self.do_ipv6: - self.runcmd("iptables-legacy-restore < %s" % self.backup_rules_ipv6) - - def runcmd(self, cmd, raise_on_errors=True): - - logger.debug("Running command: " + cmd) - - p = subprocess.Popen( - cmd, - shell=True, - executable="/bin/bash", - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - - out, err = p.communicate() - returncode = p.returncode - if raise_on_errors and returncode != 0: - raise YunohostError( - "Failed to run command '{}'.\nreturncode: {}\nstdout:\n{}\nstderr:\n{}\n".format( - cmd, returncode, out, err - ) - ) - - out = out.strip().split(b"\n") - return (returncode, out, err) diff --git a/src/yunohost/data_migrations/0019_extend_permissions_features.py b/src/yunohost/data_migrations/0019_extend_permissions_features.py deleted file mode 100644 index 5d4343deb..000000000 --- a/src/yunohost/data_migrations/0019_extend_permissions_features.py +++ /dev/null @@ -1,107 +0,0 @@ -from moulinette import m18n -from moulinette.utils.log import getActionLogger - -from yunohost.tools import Migration -from yunohost.permission import user_permission_list -from yunohost.utils.legacy import migrate_legacy_permission_settings - -logger = getActionLogger("yunohost.migration") - - -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 - 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_0019_slapd_config_will_be_overwritten", - conf_backup_folder=BACKUP_CONF_DIR, - ) - ) - - # Update LDAP schema restart slapd - 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")) - ldap = _get_ldap_interface() - permission_list = user_permission_list(full=True)["permissions"] - - for permission in permission_list: - system_perms = { - "mail": "E-mail", - "xmpp": "XMPP", - "ssh": "SSH", - "sftp": "STFP", - } - if permission.split(".")[0] in system_perms: - update = { - "authHeader": ["FALSE"], - "label": [system_perms[permission.split(".")[0]]], - "showTile": ["FALSE"], - "isProtected": ["TRUE"], - } - else: - app, subperm_name = permission.split(".") - if permission.endswith(".main"): - update = { - "authHeader": ["TRUE"], - "label": [ - app - ], # Note that this is later re-changed during the call to migrate_legacy_permission_settings() if a 'label' setting exists - "showTile": ["TRUE"], - "isProtected": ["FALSE"], - } - else: - update = { - "authHeader": ["TRUE"], - "label": [subperm_name.title()], - "showTile": ["FALSE"], - "isProtected": ["TRUE"], - } - - 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_app_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) diff --git a/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py b/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py deleted file mode 100644 index f1dbcd1e7..000000000 --- a/src/yunohost/data_migrations/0020_ssh_sftp_permissions.py +++ /dev/null @@ -1,100 +0,0 @@ -import subprocess -import os - -from moulinette import m18n -from moulinette.utils.log import getActionLogger - -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") - -################################################### -# Tools used also for restoration -################################################### - - -class MyMigration(Migration): - """ - Add new permissions around SSH/SFTP features - """ - - introduced_in_version = "4.2.2" - dependencies = ["extend_permissions_features"] - - @Migration.ldap_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] - - # Add SSH and SFTP permissions - if "sftp.main" not in existing_perms: - ldap.add( - "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", - { - "cn": "ssh.main", - "gidNumber": "5003", - "objectClass": ["posixGroup", "permissionYnh"], - "groupPermission": [], - "authHeader": "FALSE", - "label": "SSH", - "showTile": "FALSE", - "isProtected": "TRUE", - }, - ) - - # 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 - ) - - 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 - ): - logger.error(m18n.n("diagnosis_sshd_config_insecure")) - - def run_after_system_restore(self): - self.run() diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index c8249e439..ae49759d2 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -375,18 +375,6 @@ 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'." - ) - - -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'." - ) - - def _guess_current_dyndns_domain(dyn_host): """ This function tries to guess which domain should be updated by diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 912662600..62f40d29c 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -48,12 +48,10 @@ 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 @@ -62,12 +60,10 @@ def service_add( name -- Service name to add description -- description of the service log -- Absolute path to log file to display - 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. 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() @@ -77,15 +73,6 @@ def service_add( 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 - if name in log: - log.remove(name) - service["log"] = log if not description: @@ -535,29 +522,6 @@ def service_log(name, number=50): return result -def service_regen_conf( - names=[], with_diff=False, force=False, dry_run=False, list_pending=False -): - - services = _get_services() - - if isinstance(names, str): - names = [names] - - for name in names: - if name not in services.keys(): - raise YunohostValidationError("service_unknown", service=name) - - if names is []: - names = list(services.keys()) - - logger.warning(m18n.n("service_regen_conf_is_deprecated")) - - from yunohost.regenconf import regen_conf - - return regen_conf(names, with_diff, force, dry_run, list_pending) - - def _run_service_command(action, service): """ Run services management command (start, stop, enable, disable, restart, reload) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index d9e057875..95b89acd0 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -329,24 +329,12 @@ def tools_regen_conf( return regen_conf(names, with_diff, force, dry_run, list_pending) -def tools_update(target=None, apps=False, system=False): +def tools_update(target=None): """ Update apps & system package cache """ - # 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: + if not target: target = "all" if target not in ["system", "apps", "all"]: @@ -455,7 +443,7 @@ def _list_upgradable_apps(): @is_unit_operation() def tools_upgrade( - operation_logger, target=None, apps=False, system=False, allow_yunohost_upgrade=True + operation_logger, target=None, allow_yunohost_upgrade=True ): """ Update apps & package cache, then display changelog @@ -473,21 +461,6 @@ def tools_upgrade( if not packages.dpkg_lock_available(): raise YunohostValidationError("dpkg_lock_not_available") - # 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)" - ) - - if (system, apps) == (True, True): - raise YunohostValidationError("tools_upgrade_cant_both") - - if (system, apps) == (False, False): - raise YunohostValidationError("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" diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 266c2774c..e7422acfd 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -98,7 +98,6 @@ def user_create( domain, password, mailbox_quota="0", - mail=None, ): from yunohost.domain import domain_list, _get_maindomain @@ -109,12 +108,6 @@ def user_create( # Ensure sufficiently complex password assert_password_is_strong_enough("user", password) - if mail is not None: - logger.warning( - "Packagers ! Using --mail in 'yunohost user create' is deprecated ... please use --domain instead." - ) - domain = mail.split("@")[-1] - # Validate domain used for email address/xmpp account if domain is None: if msettings.get("interface") == "api": diff --git a/src/yunohost/utils/legacy.py b/src/yunohost/utils/legacy.py deleted file mode 100644 index eb92dd71f..000000000 --- a/src/yunohost/utils/legacy.py +++ /dev/null @@ -1,239 +0,0 @@ -import os -from moulinette import m18n -from moulinette.utils.log import getActionLogger -from moulinette.utils.filesystem import write_to_json, read_yaml - -from yunohost.user import user_list -from yunohost.app import ( - _installed_apps, - _get_app_settings, - _set_app_settings, -) -from yunohost.permission import ( - permission_create, - user_permission_update, - permission_sync_to_user, -) - -logger = getActionLogger("yunohost.legacy") - -LEGACY_PERMISSION_LABEL = { - ("nextcloud", "skipped"): "api", # .well-known - ("libreto", "skipped"): "pad access", # /[^/]+ - ("leed", "skipped"): "api", # /action.php, for cron task ... - ("mailman", "protected"): "admin", # /admin - ("prettynoemiecms", "protected"): "admin", # /admin - ("etherpad_mypads", "skipped"): "admin", # /admin - ("baikal", "protected"): "admin", # /admin/ - ("couchpotato", "unprotected"): "api", # /api - ("freshrss", "skipped"): "api", # /api/, - ("portainer", "skipped"): "api", # /api/webhooks/ - ("jeedom", "unprotected"): "api", # /core/api/jeeApi.php - ("bozon", "protected"): "user interface", # /index.php - ( - "limesurvey", - "protected", - ): "admin", # /index.php?r=admin,/index.php?r=plugins,/scripts - ("kanboard", "unprotected"): "api", # /jsonrpc.php - ("seafile", "unprotected"): "medias", # /media - ("ttrss", "skipped"): "api", # /public.php,/api,/opml.php?op=publish - ("libreerp", "protected"): "admin", # /web/database/manager - ("z-push", "skipped"): "api", # $domain/[Aa]uto[Dd]iscover/.* - ("radicale", "skipped"): "?", # $domain$path_url - ( - "jirafeau", - "protected", - ): "user interface", # $domain$path_url/$","$domain$path_url/admin.php.*$ - ("opensondage", "protected"): "admin", # $domain$path_url/admin/ - ( - "lstu", - "protected", - ): "user interface", # $domain$path_url/login$","$domain$path_url/logout$","$domain$path_url/api$","$domain$path_url/extensions$","$domain$path_url/stats$","$domain$path_url/d/.*$","$domain$path_url/a$","$domain$path_url/$ - ( - "lutim", - "protected", - ): "user interface", # $domain$path_url/stats/?$","$domain$path_url/manifest.webapp/?$","$domain$path_url/?$","$domain$path_url/[d-m]/.*$ - ( - "lufi", - "protected", - ): "user interface", # $domain$path_url/stats$","$domain$path_url/manifest.webapp$","$domain$path_url/$","$domain$path_url/d/.*$","$domain$path_url/m/.*$ - ( - "gogs", - "skipped", - ): "api", # $excaped_domain$excaped_path/[%w-.]*/[%w-.]*/git%-receive%-pack,$excaped_domain$excaped_path/[%w-.]*/[%w-.]*/git%-upload%-pack,$excaped_domain$excaped_path/[%w-.]*/[%w-.]*/info/refs -} - - -def legacy_permission_label(app, permission_type): - return LEGACY_PERMISSION_LABEL.get( - (app, permission_type), "Legacy %s urls" % permission_type - ) - - -def migrate_legacy_permission_settings(app=None): - - logger.info(m18n.n("migrating_legacy_permission_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] - - for app in apps: - - settings = _get_app_settings(app) or {} - if settings.get("label"): - user_permission_update( - app + ".main", label=settings["label"], sync_perm=False - ) - del settings["label"] - - def _setting(name): - s = settings.get(name) - return s.split(",") if s else [] - - skipped_urls = [uri for uri in _setting("skipped_uris") if uri != "/"] - skipped_urls += ["re:" + regex for regex in _setting("skipped_regex")] - unprotected_urls = [uri for uri in _setting("unprotected_uris") if uri != "/"] - unprotected_urls += ["re:" + regex for regex in _setting("unprotected_regex")] - protected_urls = [uri for uri in _setting("protected_uris") if uri != "/"] - protected_urls += ["re:" + regex for regex in _setting("protected_regex")] - - if skipped_urls != []: - permission_create( - app + ".legacy_skipped_uris", - additional_urls=skipped_urls, - auth_header=False, - label=legacy_permission_label(app, "skipped"), - 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_label(app, "unprotected"), - 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_label(app, "protected"), - show_tile=False, - allowed=[], - protected=True, - sync_perm=False, - ) - - legacy_permission_settings = [ - "skipped_uris", - "unprotected_uris", - "protected_uris", - "skipped_regex", - "unprotected_regex", - "protected_regex", - ] - for key in legacy_permission_settings: - if key in settings: - del settings[key] - - _set_app_settings(app, settings) - - permission_sync_to_user() - - -def translate_legacy_rules_in_ssowant_conf_json_persistent(): - - persistent_file_name = "/etc/ssowat/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) - - legacy_rules = [ - "skipped_urls", - "unprotected_urls", - "protected_urls", - "skipped_regex", - "unprotected_regex", - "protected_regex", - ] - - if not any(legacy_rule in persistent for legacy_rule in legacy_rules): - return - - if not isinstance(persistent.get("permissions"), dict): - persistent["permissions"] = {} - - skipped_urls = persistent.get("skipped_urls", []) + [ - "re:" + r for r in persistent.get("skipped_regex", []) - ] - protected_urls = persistent.get("protected_urls", []) + [ - "re:" + r for r in persistent.get("protected_regex", []) - ] - unprotected_urls = persistent.get("unprotected_urls", []) + [ - "re:" + r for r in persistent.get("unprotected_regex", []) - ] - - known_users = list(user_list()["users"].keys()) - - for legacy_rule in legacy_rules: - if legacy_rule in persistent: - del persistent[legacy_rule] - - if skipped_urls: - persistent["permissions"]["custom_skipped"] = { - "users": [], - "label": "Custom permissions - skipped", - "show_tile": False, - "auth_header": False, - "public": True, - "uris": skipped_urls - + persistent["permissions"].get("custom_skipped", {}).get("uris", []), - } - - if unprotected_urls: - persistent["permissions"]["custom_unprotected"] = { - "users": [], - "label": "Custom permissions - unprotected", - "show_tile": False, - "auth_header": True, - "public": True, - "uris": unprotected_urls - + persistent["permissions"].get("custom_unprotected", {}).get("uris", []), - } - - if protected_urls: - persistent["permissions"]["custom_protected"] = { - "users": known_users, - "label": "Custom permissions - protected", - "show_tile": False, - "auth_header": True, - "public": False, - "uris": protected_urls - + persistent["permissions"].get("custom_protected", {}).get("uris", []), - } - - 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" - ) From 27a976f5a595e04721be2b17acc51f6629eade9a Mon Sep 17 00:00:00 2001 From: MercierCorentin Date: Fri, 28 May 2021 11:22:11 +0200 Subject: [PATCH 0057/1155] 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 91d3d15c32a9d5739bad3596e426ebda3b19c010 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 28 May 2021 17:22:55 +0200 Subject: [PATCH 0058/1155] Bump conflict version for openssl because version in bullseye repo is now 1.1.1k --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 21485b114..f123239eb 100644 --- a/debian/control +++ b/debian/control @@ -41,7 +41,7 @@ Conflicts: iptables-persistent , apache2 , bind9 , nginx-extras (>= 1.19) - , openssl (>= 1.1.1j-2) + , openssl (>= 1.1.1l-1) , slapd (>= 2.4.58) , dovecot-core (>= 1:2.3.14) , redis-server (>= 5:6.0.12) From 00098075fdb30812a5086456b9b7649d1ae24425 Mon Sep 17 00:00:00 2001 From: Paco Date: Sat, 29 May 2021 19:15:13 +0200 Subject: [PATCH 0059/1155] 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 0060/1155] 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 e2a3a0bedf4b0ca532578201727eca70075b81a6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 30 May 2021 17:55:39 +0200 Subject: [PATCH 0061/1155] Fix 'from_version' issue because of ~alpha in current bullseye version name --- src/yunohost/backup.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 862c07690..b94eda683 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -860,9 +860,13 @@ 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"): + + from_version = self.info.get("from_yunohost_version", "") + # Remove any '~foobar' in the version ... c.f ~alpha, ~beta version during + # early dev for next debian version + from_version = re.sub(r'~\w+', '', from_version) + + if not from_version or version.parse(from_version) < version.parse("3.8.0"): raise YunohostValidationError("restore_backup_too_old") self.archive_path = self.info["path"] From cb129e2bf17de6997833555f09e5caa5d030b37e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 30 May 2021 18:48:26 +0200 Subject: [PATCH 0062/1155] Fix compat issue in equivs helper --- data/helpers.d/apt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index c3439a583..da87e7bad 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -181,8 +181,9 @@ ynh_package_install_from_equivs () { # Build and install the package local TMPDIR=$(mktemp --directory) - # Force the compatibility level at 10, levels below are deprecated - echo 10 > /usr/share/equivs/template/debian/compat + # Make sure to delete the legacy compat file + # It's now handle somewhat magically through the control file + rm -f /usr/share/equivs/template/debian/compat # Note that the cd executes into a sub shell # Create a fake deb package with equivs-build and the given control file From 5046e49c8b8dd051319bd35f7f7b5b70c7d39c90 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 30 May 2021 19:03:44 +0200 Subject: [PATCH 0063/1155] Many app explicitly specify YNH_PHP_VERSION=7.3, then trying to install php7.3-foo which doesn't exist ... so gotta yolopatch that definition ... --- src/yunohost/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 4e2cd5317..eafff1db3 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -3475,6 +3475,7 @@ LEGACY_PHP_VERSION_REPLACEMENTS = [ ("php5", "php7.4"), ("php7.0", "php7.4"), ("php7.3", "php7.4"), + ('YNH_PHP_VERSION="7.3"', 'YNH_PHP_VERSION="7.4"'), ( 'phpversion="${phpversion:-7.0}"', 'phpversion="${phpversion:-7.4}"', From ba2428d9b9db734e1fa394f973a0d2fec6bd9d34 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 30 May 2021 19:07:30 +0200 Subject: [PATCH 0064/1155] equivs-build now sends its logs to stderr but we don't really care about what it says, redirect to debug --- 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 da87e7bad..4f48c2172 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -192,7 +192,7 @@ ynh_package_install_from_equivs () { ynh_wait_dpkg_free cp "$controlfile" "${TMPDIR}/control" (cd "$TMPDIR" - LC_ALL=C equivs-build ./control 1> /dev/null + LC_ALL=C equivs-build ./control 2>&1 LC_ALL=C dpkg --force-depends --install "./${pkgname}_${pkgversion}_all.deb" 2>&1 | tee ./dpkg_log) ynh_package_install --fix-broken || \ From c794e63f67b06407c9e180f6cbce99d3a40ced42 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 31 May 2021 16:00:23 +0200 Subject: [PATCH 0065/1155] Re-add missing nginx dependency in debian/control ..\! --- debian/control | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/control b/debian/control index f123239eb..cc39a838c 100644 --- a/debian/control +++ b/debian/control @@ -15,6 +15,7 @@ Depends: ${python3:Depends}, ${misc:Depends} , python3-miniupnpc, python3-dbus, python3-jinja2 , python3-toml, python3-packaging, python3-publicsuffix2 , python-is-python3 + , nginx, nginx-extras (>=1.18) , apt, apt-transport-https, apt-utils, dirmngr , php7.4-common, php7.4-fpm, php7.4-ldap, php7.4-intl , mariadb-server, php7.4-mysql From 473bff6e5dfeecd744d5fb88d686867669a1e722 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 31 May 2021 18:46:28 +0200 Subject: [PATCH 0066/1155] Draft for bullseye migration --- .../0021_migrate_to_bullseye.py | 252 ++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 src/yunohost/data_migrations/0021_migrate_to_bullseye.py diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py new file mode 100644 index 000000000..289e9d765 --- /dev/null +++ b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py @@ -0,0 +1,252 @@ +import glob +import os + +from moulinette import m18n +from yunohost.utils.error import YunohostError +from moulinette.utils.log import getActionLogger +from moulinette.utils.process import check_output, call_async_output +from moulinette.utils.filesystem import read_file + +from yunohost.tools import Migration, tools_update, tools_upgrade +from yunohost.app import unstable_apps +from yunohost.regenconf import manually_modified_files +from yunohost.utils.filesystem import free_space_in_directory +from yunohost.utils.packages import ( + get_ynh_package_version, + _list_upgradable_apt_packages, +) + +logger = getActionLogger("yunohost.migration") + +N_CURRENT_DEBIAN = 10 +N_CURRENT_YUNOHOST = 4 + +N_NEXT_DEBAN = 11 +N_NEXT_YUNOHOST == 11 + +class MyMigration(Migration): + + "Upgrade the system to Debian Bullseye and Yunohost 11.x" + + mode = "manual" + + def run(self): + + self.check_assertions() + + logger.info(m18n.n("migration_0021_start")) + + # + # Patch sources.list + # + logger.info(m18n.n("migration_0021_patching_sources_list")) + self.patch_apt_sources_list() + tools_update(target="system") + + # Tell libc6 it's okay to restart system stuff during the upgrade + os.system( + "echo 'libc6 libraries/restart-without-asking boolean true' | debconf-set-selections" + ) + + # Don't send an email to root about the postgresql migration. It should be handled automatically after. + os.system( + "echo 'postgresql-common postgresql-common/obsolete-major seen true' | debconf-set-selections" + ) + + # + # Specific packages upgrades + # + logger.info(m18n.n("migration_0021_specific_upgrade")) + + # + # Main upgrade + # + logger.info(m18n.n("migration_0021_main_upgrade")) + + self.patch_yunohost_conflicts() + + apps_packages = self.get_apps_equivs_packages() + self.hold(apps_packages) + tools_upgrade(target="system", allow_yunohost_upgrade=False) + + if self.debian_major_version() == N_CURRENT_DEBIAN: + raise YunohostError("migration_0021_still_on_buster_after_main_upgrade") + + # Clean the mess + logger.info(m18n.n("migration_0021_cleaning_up")) + os.system("apt autoremove --assume-yes") + os.system("apt clean --assume-yes") + + # + # Yunohost upgrade + # + logger.info(m18n.n("migration_0021_yunohost_upgrade")) + self.unhold(apps_packages) + tools_upgrade(target="system") + + def debian_major_version(self): + # The python module "platform" and lsb_release are not reliable because + # on some setup, they may still return Release=9 even after upgrading to + # buster ... (Apparently this is related to OVH overriding some stuff + # with /etc/lsb-release for instance -_-) + # Instead, we rely on /etc/os-release which should be the raw info from + # the distribution... + 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_ynh_package_version("yunohost")["version"].split(".")[0]) + + def check_assertions(self): + + # Be on buster (10.x) and yunohost 4.x + # NB : we do both check to cover situations where the upgrade crashed + # in the middle and debian version could be > 9.x but yunohost package + # would still be in 3.x... + if ( + not self.debian_major_version() == N_CURRENT_DEBIAN + and not self.yunohost_major_version() == N_CURRENT_YUNOHOST + ): + raise YunohostError("migration_0021_not_buster") + + # Have > 1 Go free space on /var/ ? + if free_space_in_directory("/var/") / (1024 ** 3) < 1.0: + raise YunohostError("migration_0021_not_enough_free_space") + + # Check system is up to date + # (but we don't if 'bullseye' is already in the sources.list ... + # which means maybe a previous upgrade crashed and we're re-running it) + if " bullseye " not in read_file("/etc/apt/sources.list"): + tools_update(target="system") + upgradable_system_packages = list(_list_upgradable_apt_packages()) + if upgradable_system_packages: + raise YunohostError("migration_0021_system_not_fully_up_to_date") + + @property + def disclaimer(self): + + # Avoid having a super long disclaimer + uncessary check if we ain't + # on buster / yunohost 4.x anymore + # NB : we do both check to cover situations where the upgrade crashed + # in the middle and debian version could be >= 10.x but yunohost package + # would still be in 4.x... + if ( + not self.debian_major_version() == N_CURRENT_DEBIAN + and not self.yunohost_major_version() == N_CURRENT_YUNOHOST + ): + return None + + # Get list of problematic apps ? I.e. not official or community+working + problematic_apps = unstable_apps() + problematic_apps = "".join(["\n - " + app for app in problematic_apps]) + + # Manually modified files ? (c.f. yunohost service regen-conf) + modified_files = manually_modified_files() + modified_files = "".join(["\n - " + f for f in modified_files]) + + message = m18n.n("migration_0021_general_warning") + + # FIXME: re-enable this message with updated topic link once we release the migration as stable + #message = ( + # "N.B.: This migration has been tested by the community over the last few months but has only been declared stable recently. If your server hosts critical services and if you are not too confident with debugging possible issues, we recommend you to wait a little bit more while we gather more feedback and polish things up. If on the other hand you are relatively confident with debugging small issues that may arise, you are encouraged to run this migration ;)! You can read about remaining known issues and feedback from the community here: https://forum.yunohost.org/t/12195\n\n" + # + message + #) + + if problematic_apps: + message += "\n\n" + m18n.n( + "migration_0021_problematic_apps_warning", + problematic_apps=problematic_apps, + ) + + if modified_files: + message += "\n\n" + m18n.n( + "migration_0021_modified_files", manually_modified_files=modified_files + ) + + return message + + def patch_apt_sources_list(self): + + sources_list = glob.glob("/etc/apt/sources.list.d/*.list") + sources_list.append("/etc/apt/sources.list") + + # This : + # - replace single 'buster' occurence by 'bulleye' + # - comments lines containing "backports" + # - replace 'buster/updates' by 'bullseye/updates' (or same with -) + for f in sources_list: + command = ( + f"sed -i {f} " + "-e 's@ buster @ bullseye @g' " + "-e '/backports/ s@^#*@#@' " + "-e 's@ buster/updates @ bullseye/updates @g' " + "-e 's@ buster-@ bullseye-@g' " + ) + os.system(command) + + def get_apps_equivs_packages(self): + + command = ( + "dpkg --get-selections" + " | grep -v deinstall" + " | awk '{print $1}'" + " | { grep 'ynh-deps$' || true; }" + ) + + output = check_output(command) + + return output.split("\n") if output else [] + + def hold(self, packages): + for package in packages: + os.system("apt-mark hold {}".format(package)) + + def unhold(self, packages): + for package in packages: + os.system("apt-mark unhold {}".format(package)) + + def apt_install(self, cmd): + def is_relevant(line): + return "Reading database ..." not in line.rstrip() + + callbacks = ( + lambda l: logger.info("+ " + l.rstrip() + "\r") + if is_relevant(l) + else logger.debug(l.rstrip() + "\r"), + lambda l: logger.warning(l.rstrip()), + ) + + cmd = ( + "LC_ALL=C DEBIAN_FRONTEND=noninteractive APT_LISTCHANGES_FRONTEND=none apt install --quiet -o=Dpkg::Use-Pty=0 --fix-broken --assume-yes " + + cmd + ) + + logger.debug("Running: %s" % cmd) + + call_async_output(cmd, callbacks, shell=True) + + + def patch_yunohost_conflicts(self): + # + # This is a super dirty hack to remove the conflicts from yunohost's debian/control file + # Those conflicts are there to prevent mistakenly upgrading critical packages + # such as dovecot, postfix, nginx, openssl, etc... usually related to mistakenly + # using backports etc. + # + # The hack consists in savagely removing the conflicts directly in /var/lib/dpkg/status + # + + # We only patch the conflict if we're on yunohost 4.x + if self.yunohost_major_version() != N_CURRENT_YUNOHOST: + return + + conflicts = check_output("dpkg-query -s yunohost | grep '^Conflicts:'").strip() + if conflicts: + # We want to keep conflicting with apache/bind9 tho + new_conflicts = "Conflicts: apache2, bind9" + + command = f"sed -i /var/lib/dpkg/status 's@{conflicts}@{new_conflicts}@g'" + os.system(command) From d6baad6f45abf70b8f062fcd73f32a2cad975261 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 31 May 2021 19:02:05 +0200 Subject: [PATCH 0067/1155] Postgresql version is 13 in bullseye --- 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 12738a922..e190ce419 100644 --- a/data/helpers.d/postgresql +++ b/data/helpers.d/postgresql @@ -1,7 +1,7 @@ #!/bin/bash PSQL_ROOT_PWD_FILE=/etc/yunohost/psql -PSQL_VERSION=11 +PSQL_VERSION=13 # Open a connection as a user # From da57653a01770e130a53c6e732ee6dac61035790 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 31 May 2021 19:12:08 +0200 Subject: [PATCH 0068/1155] Misc fixes after tests on the battlefield --- .../data_migrations/0021_migrate_to_bullseye.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py index 289e9d765..a5bb1e523 100644 --- a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py +++ b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py @@ -22,7 +22,7 @@ N_CURRENT_DEBIAN = 10 N_CURRENT_YUNOHOST = 4 N_NEXT_DEBAN = 11 -N_NEXT_YUNOHOST == 11 +N_NEXT_YUNOHOST = 11 class MyMigration(Migration): @@ -54,17 +54,17 @@ class MyMigration(Migration): ) # - # Specific packages upgrades + # Patch yunohost conflicts # - logger.info(m18n.n("migration_0021_specific_upgrade")) + logger.info(m18n.n("migration_0021_patch_yunohost_conflicts")) + + self.patch_yunohost_conflicts() # # Main upgrade # logger.info(m18n.n("migration_0021_main_upgrade")) - self.patch_yunohost_conflicts() - apps_packages = self.get_apps_equivs_packages() self.hold(apps_packages) tools_upgrade(target="system", allow_yunohost_upgrade=False) @@ -248,5 +248,6 @@ class MyMigration(Migration): # We want to keep conflicting with apache/bind9 tho new_conflicts = "Conflicts: apache2, bind9" - command = f"sed -i /var/lib/dpkg/status 's@{conflicts}@{new_conflicts}@g'" + command = f"sed -i /var/lib/dpkg/status -e 's@{conflicts}@{new_conflicts}@g'" + logger.debug(f"Running: {command}") os.system(command) From 2665c6235aba64a3fc17bc1992bf1a4908a21638 Mon Sep 17 00:00:00 2001 From: MercierCorentin Date: Thu, 3 Jun 2021 11:23:03 +0200 Subject: [PATCH 0069/1155] 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 0070/1155] 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 0071/1155] 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 008baf13509b1030ef4ea5742b99c74c31ce2b77 Mon Sep 17 00:00:00 2001 From: MercierCorentin Date: Fri, 4 Jun 2021 09:25:00 +0200 Subject: [PATCH 0072/1155] 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 0073/1155] 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 0074/1155] 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 0075/1155] =?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 24211a90bc6ea250c904454fbc6bf8ba1bc7287a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 4 Jun 2021 17:17:27 +0200 Subject: [PATCH 0076/1155] Yolo php7.3 -> php7.4 migration --- locales/en.json | 1 + src/yunohost/app.py | 16 +++- .../0022_php73_to_php74_pools.py | 83 +++++++++++++++++++ 3 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 src/yunohost/data_migrations/0022_php73_to_php74_pools.py diff --git a/locales/en.json b/locales/en.json index 813688a87..922ece34a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -426,6 +426,7 @@ "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_description_0022_php73_to_php74_pools": "Migrate php7.3-fpm 'pool' conf files to php7.4", "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.", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index eafff1db3..98a8053e7 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -3480,10 +3480,18 @@ LEGACY_PHP_VERSION_REPLACEMENTS = [ 'phpversion="${phpversion:-7.0}"', 'phpversion="${phpversion:-7.4}"', ), # Many helpers like the composer ones use 7.0 by default ... + ( + 'phpversion="${phpversion:-7.3}"', + 'phpversion="${phpversion:-7.4}"', + ), # Many helpers like the composer ones use 7.0 by default ... ( '"$phpversion" == "7.0"', '$(bc <<< "$phpversion >= 7.4") -eq 1', ), # patch ynh_install_php to refuse installing/removing php <= 7.3 + ( + '"$phpversion" == "7.3"', + '$(bc <<< "$phpversion >= 7.4") -eq 1', + ), # patch ynh_install_php to refuse installing/removing php <= 7.3 ] @@ -3518,15 +3526,15 @@ def _patch_legacy_php_versions_in_settings(app_folder): settings = read_yaml(os.path.join(app_folder, "settings.yml")) - if settings.get("fpm_config_dir") == "/etc/php/7.0/fpm": + if settings.get("fpm_config_dir") in ["/etc/php/7.0/fpm", "/etc/php/7.3/fpm"]: settings["fpm_config_dir"] = "/etc/php/7.4/fpm" - if settings.get("fpm_service") == "php7.0-fpm": + if settings.get("fpm_service") in ["php7.0-fpm", "php7.3-fpm"]: settings["fpm_service"] = "php7.4-fpm" - if settings.get("phpversion") == "7.0": + if settings.get("phpversion") in ["7.0", "7.3"]: settings["phpversion"] = "7.4" # We delete these checksums otherwise the file will appear as manually modified - list_to_remove = ["checksum__etc_php_7.0_fpm_pool", "checksum__etc_nginx_conf.d"] + list_to_remove = ["checksum__etc_php_7.3_fpm_pool", "checksum__etc_php_7.0_fpm_pool", "checksum__etc_nginx_conf.d"] settings = { k: v for k, v in settings.items() diff --git a/src/yunohost/data_migrations/0022_php73_to_php74_pools.py b/src/yunohost/data_migrations/0022_php73_to_php74_pools.py new file mode 100644 index 000000000..0157fa275 --- /dev/null +++ b/src/yunohost/data_migrations/0022_php73_to_php74_pools.py @@ -0,0 +1,83 @@ +import os +import glob +from shutil import copy2 + +from moulinette.utils.log import getActionLogger + +from yunohost.app import _is_installed, _patch_legacy_php_versions_in_settings +from yunohost.tools import Migration +from yunohost.service import _run_service_command + +logger = getActionLogger("yunohost.migration") + +OLDPHP_POOLS = "/etc/php/7.3/fpm/pool.d" +NEWPHP_POOLS = "/etc/php/7.4/fpm/pool.d" + +OLDPHP_SOCKETS_PREFIX = "/run/php/php7.3-fpm" +NEWPHP_SOCKETS_PREFIX = "/run/php/php7.4-fpm" + +MIGRATION_COMMENT = ( + "; YunoHost note : this file was automatically moved from {}".format(OLDPHP_POOLS) +) + + +class MyMigration(Migration): + + "Migrate php7.3-fpm 'pool' conf files to php7.4" + + dependencies = ["migrate_to_bullseye"] + + def run(self): + # Get list of php7.3 pool files + oldphp_pool_files = glob.glob("{}/*.conf".format(OLDPHP_POOLS)) + + # Keep only basenames + oldphp_pool_files = [os.path.basename(f) for f in oldphp_pool_files] + + # Ignore the "www.conf" (default stuff, probably don't want to touch it ?) + oldphp_pool_files = [f for f in oldphp_pool_files if f != "www.conf"] + + for f in oldphp_pool_files: + + # Copy the files to the php7.4 pool + src = "{}/{}".format(OLDPHP_POOLS, f) + dest = "{}/{}".format(NEWPHP_POOLS, f) + copy2(src, dest) + + # Replace the socket prefix if it's found + c = "sed -i -e 's@{}@{}@g' {}".format( + OLDPHP_SOCKETS_PREFIX, NEWPHP_SOCKETS_PREFIX, dest + ) + os.system(c) + + # Also add a comment that it was automatically moved from php7.3 + # (for human traceability and backward migration) + c = "sed -i '1i {}' {}".format(MIGRATION_COMMENT, dest) + os.system(c) + + app_id = os.path.basename(f)[: -len(".conf")] + if _is_installed(app_id): + _patch_legacy_php_versions_in_settings( + "/etc/yunohost/apps/%s/" % app_id + ) + + nginx_conf_files = glob.glob("/etc/nginx/conf.d/*.d/%s.conf" % app_id) + for f in nginx_conf_files: + # Replace the socket prefix if it's found + c = "sed -i -e 's@{}@{}@g' {}".format( + OLDPHP_SOCKETS_PREFIX, NEWPHP_SOCKETS_PREFIX, f + ) + os.system(c) + + os.system( + "rm /etc/logrotate.d/php7.3-fpm" + ) # We remove this otherwise the logrotate cron will be unhappy + + # Reload/restart the php pools + _run_service_command("restart", "php7.4-fpm") + _run_service_command("enable", "php7.4-fpm") + os.system("systemctl stop php7.3-fpm") + os.system("systemctl disable php7.3-fpm") + + # Reload nginx + _run_service_command("reload", "nginx") From 4e2e54676a1c23475903823ad621f765d36c3b3a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 4 Jun 2021 17:23:09 +0200 Subject: [PATCH 0077/1155] Yolo postgresql 11 -> 13 cluster migration --- locales/en.json | 4 + .../0023_postgresql_11_to_13.py | 82 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 src/yunohost/data_migrations/0023_postgresql_11_to_13.py diff --git a/locales/en.json b/locales/en.json index 922ece34a..1ab813bc4 100644 --- a/locales/en.json +++ b/locales/en.json @@ -427,6 +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_description_0022_php73_to_php74_pools": "Migrate php7.3-fpm 'pool' conf files to php7.4", + "migration_description_0023_postgresql_11_to_13": "Migrate databases from PostgreSQL 11 to 13", "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.", @@ -453,6 +454,9 @@ "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_0023_postgresql_11_not_installed": "PostgreSQL was not installed on your system. Nothing to do.", + "migration_0023_postgresql_13_not_installed": "PostgreSQL 11 is installed, but not postgresql 13!? Something weird might have happened on your system :(...", + "migration_0023_not_enough_space": "Make sufficient space available in {path} to run the migration.", "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/0023_postgresql_11_to_13.py b/src/yunohost/data_migrations/0023_postgresql_11_to_13.py new file mode 100644 index 000000000..30527f0c8 --- /dev/null +++ b/src/yunohost/data_migrations/0023_postgresql_11_to_13.py @@ -0,0 +1,82 @@ +import subprocess + +from moulinette import m18n +from yunohost.utils.error import YunohostError, YunohostValidationError +from moulinette.utils.log import getActionLogger + +from yunohost.tools import Migration +from yunohost.utils.filesystem import free_space_in_directory, space_used_by_directory + +logger = getActionLogger("yunohost.migration") + + +class MyMigration(Migration): + + "Migrate DBs from Postgresql 11 to 13 after migrating to Bullseye" + + dependencies = ["migrate_to_bullseye"] + + def run(self): + + if not self.package_is_installed("postgresql-11"): + logger.warning(m18n.n("migration_0023_postgresql_11_not_installed")) + return + + if not self.package_is_installed("postgresql-13"): + raise YunohostValidationError("migration_0023_postgresql_13_not_installed") + + # Make sure there's a 11 cluster + try: + self.runcmd("pg_lsclusters | grep -q '^11 '") + except Exception: + logger.warning( + "It looks like there's not active 11 cluster, so probably don't need to run this migration" + ) + return + + if not space_used_by_directory( + "/var/lib/postgresql/11" + ) > free_space_in_directory("/var/lib/postgresql"): + raise YunohostValidationError( + "migration_0023_not_enough_space", path="/var/lib/postgresql/" + ) + + self.runcmd("systemctl stop postgresql") + self.runcmd( + "LC_ALL=C pg_dropcluster --stop 13 main || true" + ) # We do not trigger an exception if the command fails because that probably means cluster 13 doesn't exists, which is fine because it's created during the pg_upgradecluster) + self.runcmd("LC_ALL=C pg_upgradecluster -m upgrade 11 main") + self.runcmd("LC_ALL=C pg_dropcluster --stop 11 main") + self.runcmd("systemctl start postgresql") + + def package_is_installed(self, package_name): + + (returncode, out, err) = self.runcmd( + "dpkg --list | grep '^ii ' | grep -q -w {}".format(package_name), + raise_on_errors=False, + ) + return returncode == 0 + + def runcmd(self, cmd, raise_on_errors=True): + + logger.debug("Running command: " + cmd) + + p = subprocess.Popen( + cmd, + shell=True, + executable="/bin/bash", + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + + out, err = p.communicate() + returncode = p.returncode + if raise_on_errors and returncode != 0: + raise YunohostError( + "Failed to run command '{}'.\nreturncode: {}\nstdout:\n{}\nstderr:\n{}\n".format( + cmd, returncode, out, err + ) + ) + + out = out.strip().split("\n") + return (returncode, out, err) From b233c7c08ccf689c620e46bbc8431970711e42db Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 4 Jun 2021 17:35:29 +0200 Subject: [PATCH 0078/1155] Stale strings --- locales/en.json | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/locales/en.json b/locales/en.json index 813688a87..2921e91ef 100644 --- a/locales/en.json +++ b/locales/en.json @@ -419,39 +419,10 @@ "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_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", - "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_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}.", @@ -574,7 +545,6 @@ "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 '{service:s}' removed", "service_reload_failed": "Could not reload the service '{service:s}'\n\nRecent service logs:{logs:s}", @@ -595,8 +565,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 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…", From 11a638ed70c767a8d8cbe4e326d69ecf25b51659 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 4 Jun 2021 18:22:48 +0200 Subject: [PATCH 0079/1155] Fix 'apps' undefined --- 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 95b89acd0..a7b79bb28 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -484,7 +484,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 fd3a10489deec0c99f2566408c134f1bca78c4d6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 7 Jun 2021 16:17:58 +0200 Subject: [PATCH 0080/1155] Add i18n strings for bullseye migration --- locales/en.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/locales/en.json b/locales/en.json index 84a01dfaa..a479672d7 100644 --- a/locales/en.json +++ b/locales/en.json @@ -426,6 +426,7 @@ "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_description_0021_migrate_to_bullseye": "Upgrade the system to Debian Bullseye and YunoHost 11.x", "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.", @@ -452,6 +453,21 @@ "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_0021_start" : "Starting migration to Bullseye", + "migration_0021_patching_sources_list": "Patching the sources.lists...", + "migration_0021_main_upgrade": "Starting main upgrade...", + "migration_0021_still_on_buster_after_main_upgrade": "Something went wrong during the main upgrade, the system appears to still be on Debian Buster", + "migration_0021_yunohost_upgrade" : "Starting YunoHost core upgrade...", + "migration_0021_not_buster" : "The current Debian distribution is not Buster!", + "migration_0021_not_enough_free_space" : "Free space is pretty low in /var/! You should have at least 1GB free to run this migration.", + "migration_0021_system_not_fully_up_to_date": "Your system is not fully up-to-date. Please perform a regular upgrade before running the migration to Bullseye.", + "migration_0021_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_0021_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_0021_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_0021_specific_upgrade": "Starting upgrade of system packages that needs to be upgrade independently...", + "migration_0021_cleaning_up": "Cleaning up cache and packages not useful anymore...", + "migration_0021_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_0021_patch_yunohost_conflicts": "Applying patch to workaround conflict issue...", "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 8e25895cbff9a2e374c47b9aa959574066d58248 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 7 Jun 2021 16:31:39 +0200 Subject: [PATCH 0081/1155] 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 a24ea4dafa90932675765c56ffb0f822bf56b675 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 7 Jun 2021 16:39:06 +0200 Subject: [PATCH 0082/1155] 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 43d924252fa0dc4d6ffdcfcbcb32a3a4f3ee18eb Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 7 Jun 2021 17:41:57 +0200 Subject: [PATCH 0083/1155] Fix patch of security repo in sources.list --- src/yunohost/data_migrations/0021_migrate_to_bullseye.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py index a5bb1e523..6678fa040 100644 --- a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py +++ b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py @@ -177,12 +177,14 @@ class MyMigration(Migration): # - replace single 'buster' occurence by 'bulleye' # - comments lines containing "backports" # - replace 'buster/updates' by 'bullseye/updates' (or same with -) + # Special note about the security suite: + # https://www.debian.org/releases/bullseye/amd64/release-notes/ch-information.en.html#security-archive for f in sources_list: command = ( f"sed -i {f} " "-e 's@ buster @ bullseye @g' " "-e '/backports/ s@^#*@#@' " - "-e 's@ buster/updates @ bullseye/updates @g' " + "-e 's@ buster/updates @ bullseye-security @g' " "-e 's@ buster-@ bullseye-@g' " ) os.system(command) From 43ef4f4a29e53f139439b4ab46004384abcfcf76 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 7 Jun 2021 17:44:24 +0200 Subject: [PATCH 0084/1155] Unecessary i18n strings --- locales/en.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index a479672d7..a739b93c4 100644 --- a/locales/en.json +++ b/locales/en.json @@ -464,9 +464,7 @@ "migration_0021_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_0021_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_0021_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_0021_specific_upgrade": "Starting upgrade of system packages that needs to be upgrade independently...", "migration_0021_cleaning_up": "Cleaning up cache and packages not useful anymore...", - "migration_0021_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_0021_patch_yunohost_conflicts": "Applying patch to workaround conflict issue...", "migrations_already_ran": "Those migrations are already done: {ids}", "migrations_cant_reach_migration_file": "Could not access migrations files at the path '%s'", From e22c683c3449b4d659fac5376959c05c550cd4d0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 7 Jun 2021 19:17:31 +0200 Subject: [PATCH 0085/1155] Fix conflict issue on redis-server because bullseye version changed --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index cc39a838c..09926c961 100644 --- a/debian/control +++ b/debian/control @@ -45,7 +45,7 @@ Conflicts: iptables-persistent , openssl (>= 1.1.1l-1) , slapd (>= 2.4.58) , dovecot-core (>= 1:2.3.14) - , redis-server (>= 5:6.0.12) + , redis-server (>= 5:6.1) , fail2ban (>= 0.11.3) , iptables (>= 1.8.8) Description: manageable and configured self-hosting server From 0d8b8f6ca340e25d277b1bd2a369850af1cf3d5c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 7 Jun 2021 19:22:15 +0200 Subject: [PATCH 0086/1155] (Re)Drop legacy --others_var --- data/helpers.d/fail2ban | 9 ++------- data/helpers.d/systemd | 6 +----- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/data/helpers.d/fail2ban b/data/helpers.d/fail2ban index 6ac7ae6d0..5e3eca706 100644 --- a/data/helpers.d/fail2ban +++ b/data/helpers.d/fail2ban @@ -10,9 +10,8 @@ # # ----------------------------------------------------------------------------- # -# usage 2: ynh_add_fail2ban_config --use_template [--others_var="list of others variables to replace"] +# usage 2: ynh_add_fail2ban_config --use_template # | arg: -t, --use_template - Use this helper in template mode -# | arg: -v, --others_var= - List of others variables to replace separeted by a space for example : 'var_1 var_2 ...' # # This will use a template in `../conf/f2b_jail.conf` and `../conf/f2b_filter.conf` # See the documentation of `ynh_add_config` for a description of the template @@ -65,22 +64,18 @@ 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 -A args_array=( [l]=logpath= [r]=failregex= [m]=max_retry= [p]=ports= [t]=use_template) local logpath local failregex local max_retry local ports - local others_var local use_template # Manage arguments with getopts 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" - 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 a1baff4b0..3b3a663e2 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -15,17 +15,13 @@ ynh_add_systemd_config () { # Declare an array to define the options of this helper. local legacy_args=stv - local -A args_array=( [s]=service= [t]=template= [v]=others_var=) + local -A args_array=( [s]=service= [t]=template=) local service local template - local others_var # Manage arguments with getopts ynh_handle_getopts_args "$@" 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" ynh_add_config --template="$YNH_APP_BASEDIR/conf/$template" --destination="/etc/systemd/system/$service.service" From f6857625decd69f63bc9a783409003bb2922aeac Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 7 Jun 2021 22:06:54 +0200 Subject: [PATCH 0087/1155] Stupid encoding issue in migration 0023 --- src/yunohost/data_migrations/0023_postgresql_11_to_13.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/data_migrations/0023_postgresql_11_to_13.py b/src/yunohost/data_migrations/0023_postgresql_11_to_13.py index 30527f0c8..145f15558 100644 --- a/src/yunohost/data_migrations/0023_postgresql_11_to_13.py +++ b/src/yunohost/data_migrations/0023_postgresql_11_to_13.py @@ -78,5 +78,5 @@ class MyMigration(Migration): ) ) - out = out.strip().split("\n") + out = out.strip().split(b"\n") return (returncode, out, err) From 4a22f6b390ec4277de478d50f49e9d7df8fa2773 Mon Sep 17 00:00:00 2001 From: ljf Date: Thu, 10 Jun 2021 19:28:49 +0200 Subject: [PATCH 0088/1155] [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 0089/1155] 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 653d7e8127e3485ea562802f11246c21ce813f83 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 12 Jun 2021 16:23:40 +0200 Subject: [PATCH 0090/1155] Switch to backups from 4.2 --- src/yunohost/backup.py | 2 +- src/yunohost/tests/test_backuprestore.py | 48 ++++++++++++------------ 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index b94eda683..3eb564734 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -866,7 +866,7 @@ class RestoreManager: # early dev for next debian version from_version = re.sub(r'~\w+', '', from_version) - if not from_version or version.parse(from_version) < version.parse("3.8.0"): + if not from_version or version.parse(from_version) < version.parse("4.2.0"): raise YunohostValidationError("restore_backup_too_old") self.archive_path = self.info["path"] diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index 30204fa86..2a88e4042 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_3p8" in markers: - add_archive_wordpress_from_3p8() + if "with_wordpress_archive_from_4p2" in markers: + add_archive_wordpress_from_4p2() 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_3p8" in markers: - add_archive_system_from_3p8() + if "with_system_archive_from_4p2" in markers: + add_archive_system_from_4p2() assert len(backup_list()["archives"]) == 1 if "with_permission_app_installed" in markers: @@ -148,7 +148,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_3p8") + os.path.join(get_test_apps_dir(), "backup_wordpress_from_4p2") ) assert os.path.exists(os.path.join(get_test_apps_dir(), "legacy_app_ynh")) assert os.path.exists( @@ -217,25 +217,25 @@ def install_app(app, path, additionnal_args=""): ) -def add_archive_wordpress_from_3p8(): +def add_archive_wordpress_from_4p2(): os.system("mkdir -p /home/yunohost.backup/archives") os.system( "cp " - + os.path.join(get_test_apps_dir(), "backup_wordpress_from_3p8/backup.tar.gz") - + " /home/yunohost.backup/archives/backup_wordpress_from_3p8.tar.gz" + + os.path.join(get_test_apps_dir(), "backup_wordpress_from_4p2/backup.tar") + + " /home/yunohost.backup/archives/backup_wordpress_from_4p2.tar" ) -def add_archive_system_from_3p8(): +def add_archive_system_from_4p2(): os.system("mkdir -p /home/yunohost.backup/archives") os.system( "cp " - + os.path.join(get_test_apps_dir(), "backup_system_from_3p8/backup.tar.gz") - + " /home/yunohost.backup/archives/backup_system_from_3p8.tar.gz" + + os.path.join(get_test_apps_dir(), "backup_system_from_4p2/backup.tar") + + " /home/yunohost.backup/archives/backup_system_from_4p2.tar" ) @@ -305,8 +305,8 @@ def test_backup_and_restore_all_sys(mocker): # -@pytest.mark.with_system_archive_from_3p8 -def test_restore_system_from_Ynh3p8(monkeypatch, mocker): +@pytest.mark.with_system_archive_from_4p2 +def test_restore_system_from_Ynh4p2(monkeypatch, mocker): # Backup current system with message(mocker, "backup_created"): @@ -451,9 +451,9 @@ def test_backup_using_copy_method(mocker): # -@pytest.mark.with_wordpress_archive_from_3p8 +@pytest.mark.with_wordpress_archive_from_4p2 @pytest.mark.with_custom_domain("yolo.test") -def test_restore_app_wordpress_from_Ynh3p8(mocker): +def test_restore_app_wordpress_from_Ynh4p2(mocker): with message(mocker, "restore_complete"): backup_restore( @@ -461,7 +461,7 @@ def test_restore_app_wordpress_from_Ynh3p8(mocker): ) -@pytest.mark.with_wordpress_archive_from_3p8 +@pytest.mark.with_wordpress_archive_from_4p2 @pytest.mark.with_custom_domain("yolo.test") def test_restore_app_script_failure_handling(monkeypatch, mocker): def custom_hook_exec(name, *args, **kwargs): @@ -482,7 +482,7 @@ def test_restore_app_script_failure_handling(monkeypatch, mocker): assert not _is_installed("wordpress") -@pytest.mark.with_wordpress_archive_from_3p8 +@pytest.mark.with_wordpress_archive_from_4p2 def test_restore_app_not_enough_free_space(monkeypatch, mocker): def custom_free_space_in_directory(dirpath): return 0 @@ -501,7 +501,7 @@ def test_restore_app_not_enough_free_space(monkeypatch, mocker): assert not _is_installed("wordpress") -@pytest.mark.with_wordpress_archive_from_3p8 +@pytest.mark.with_wordpress_archive_from_4p2 def test_restore_app_not_in_backup(mocker): assert not _is_installed("wordpress") @@ -517,7 +517,7 @@ def test_restore_app_not_in_backup(mocker): assert not _is_installed("yoloswag") -@pytest.mark.with_wordpress_archive_from_3p8 +@pytest.mark.with_wordpress_archive_from_4p2 @pytest.mark.with_custom_domain("yolo.test") def test_restore_app_already_installed(mocker): @@ -627,7 +627,7 @@ def test_restore_archive_with_no_json(mocker): # Create a backup with no info.json associated os.system("touch /tmp/afile") - os.system("tar -czvf /home/yunohost.backup/archives/badbackup.tar.gz /tmp/afile") + os.system("tar -cvf /home/yunohost.backup/archives/badbackup.tar /tmp/afile") assert "badbackup" in backup_list()["archives"] @@ -635,18 +635,18 @@ def test_restore_archive_with_no_json(mocker): backup_restore(name="badbackup", force=True) -@pytest.mark.with_wordpress_archive_from_3p8 +@pytest.mark.with_wordpress_archive_from_4p2 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_bad.tar.gz" + "head -n 1000 /home/yunohost.backup/archives/backup_wordpress_from_4p2.tar > /home/yunohost.backup/archives/backup_wordpress_from_4p2_bad.tar" ) - assert "backup_wordpress_from_3p8_bad" in backup_list()["archives"] + assert "backup_wordpress_from_4p2_bad" in backup_list()["archives"] with raiseYunohostError(mocker, "backup_archive_corrupted"): - backup_restore(name="backup_wordpress_from_3p8_bad", force=True) + backup_restore(name="backup_wordpress_from_4p2_bad", force=True) clean_tmp_backup_directory() From 7026a8cfd5cbc413cea9805298fafebf828f1366 Mon Sep 17 00:00:00 2001 From: Krakinou Date: Sat, 12 Jun 2021 16:49:05 +0200 Subject: [PATCH 0091/1155] 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 82812bac2fb607a2ce2573efb96135b79c88e1ed Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 12 Jun 2021 18:01:43 +0200 Subject: [PATCH 0092/1155] Change service status hack for postgresql, 11 -> 13 --- 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 62f40d29c..b015d37cd 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -665,7 +665,7 @@ def _get_services(): if "postgresql" in services: if "description" in services["postgresql"]: del services["postgresql"]["description"] - services["postgresql"]["actual_systemd_service"] = "postgresql@11-main" + services["postgresql"]["actual_systemd_service"] = "postgresql@13-main" return services From 0843d35cc56dd19300dd33623caaee7e07984ee8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 12 Jun 2021 18:02:12 +0200 Subject: [PATCH 0093/1155] Remove old hack for vpnclient --- src/yunohost/service.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index b015d37cd..302b96167 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -653,9 +653,6 @@ def _get_services(): # 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"] From 20b5843b42dff6bea64200cc3fa367b517e9004d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 12 Jun 2021 18:06:22 +0200 Subject: [PATCH 0094/1155] Remove php7.3-fpm from service list --- data/templates/yunohost/services.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/data/templates/yunohost/services.yml b/data/templates/yunohost/services.yml index b6ce7c117..5e19cd4c6 100644 --- a/data/templates/yunohost/services.yml +++ b/data/templates/yunohost/services.yml @@ -67,4 +67,5 @@ spamassassin: null rmilter: null php5-fpm: null php7.0-fpm: null +php7.3-fpm: nslcd: null From 075526303eff445b93ebdb1002a740abd77e29a1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 14 Jun 2021 17:00:41 +0200 Subject: [PATCH 0095/1155] 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 0096/1155] 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 0097/1155] 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 0098/1155] 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 0099/1155] 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 0100/1155] [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 0101/1155] 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 0102/1155] 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 0103/1155] 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 0104/1155] 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 0105/1155] 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 0106/1155] 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 0107/1155] 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 0108/1155] 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 0109/1155] 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 0110/1155] 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 0111/1155] 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 0112/1155] 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 0113/1155] 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 0114/1155] 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 0115/1155] 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 0116/1155] 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 0117/1155] 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 0118/1155] 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 0119/1155] 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 0120/1155] 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 0121/1155] 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 0122/1155] 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 0123/1155] 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 0124/1155] 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 0125/1155] 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 0126/1155] 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 a274b6a8fca4ceebac9f685b57d862c4554be67f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 3 Jul 2021 18:14:24 +0200 Subject: [PATCH 0127/1155] fix: Remove legacy --installed / filter for app_list in actionmap --- data/actionsmap/yunohost.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 0631a20c9..f86db5495 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -602,12 +602,6 @@ 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: From 2478f5a0baba3a99fd0ff578e445813e1e8a40af Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Sun, 4 Jul 2021 17:02:46 +0000 Subject: [PATCH 0128/1155] 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 0129/1155] 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 0130/1155] 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 0131/1155] 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 0132/1155] 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 0133/1155] 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 0134/1155] 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 0135/1155] 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 0136/1155] 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 713612a6898d7fd9ed1b70d6c679ea65da134990 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 9 Jul 2021 21:50:41 +0200 Subject: [PATCH 0137/1155] Get rid of legacy script to reset ldap password --- debian/install | 1 - sbin/yunohost-reset-ldap-password | 3 --- 2 files changed, 4 deletions(-) delete mode 100755 sbin/yunohost-reset-ldap-password diff --git a/debian/install b/debian/install index 1691a4849..bc3cc1f48 100644 --- a/debian/install +++ b/debian/install @@ -1,5 +1,4 @@ bin/* /usr/bin/ -sbin/* /usr/sbin/ data/bash-completion.d/yunohost /etc/bash_completion.d/ doc/yunohost.8.gz /usr/share/man/man8/ data/actionsmap/* /usr/share/moulinette/actionsmap/ diff --git a/sbin/yunohost-reset-ldap-password b/sbin/yunohost-reset-ldap-password deleted file mode 100755 index 95f84875f..000000000 --- a/sbin/yunohost-reset-ldap-password +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -echo "Warning: this script is now deprecated. You can simply type 'yunohost tools adminpw' to change the root/admin password." -yunohost tools adminpw From 4e4173d1b65ed55e797f898e97036af0c25f1245 Mon Sep 17 00:00:00 2001 From: Kayou Date: Sat, 10 Jul 2021 02:47:11 +0200 Subject: [PATCH 0138/1155] 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 0139/1155] [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 0140/1155] [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 0141/1155] 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 0142/1155] 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 0143/1155] 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 0144/1155] 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 0145/1155] 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 0146/1155] 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 0147/1155] 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 0148/1155] [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 0149/1155] [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 0150/1155] [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 0151/1155] [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 0152/1155] 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 0153/1155] 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 0154/1155] 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 0155/1155] 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 0156/1155] 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 0157/1155] 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 0158/1155] [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 0159/1155] 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 0160/1155] 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 0161/1155] [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 0162/1155] [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 0163/1155] [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 0164/1155] [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 0165/1155] [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 0166/1155] [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 0167/1155] 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 0168/1155] 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 0169/1155] 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 0170/1155] 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 0171/1155] [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 0172/1155] 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 0173/1155] 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 0174/1155] 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 0175/1155] 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 0176/1155] 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 0177/1155] 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 0178/1155] 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 0179/1155] 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 0180/1155] 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 0181/1155] 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 0182/1155] 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 0183/1155] 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 0184/1155] 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 0185/1155] 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 0186/1155] 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 0187/1155] 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 0188/1155] 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 0189/1155] 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 0190/1155] [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 0191/1155] [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 0192/1155] 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 0193/1155] 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 0194/1155] 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 0195/1155] 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 0196/1155] 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 0197/1155] 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 0198/1155] 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 0199/1155] 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 0200/1155] 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 0201/1155] 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 0202/1155] [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 0203/1155] [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 0204/1155] 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 0205/1155] 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 0206/1155] [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 0207/1155] {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 0208/1155] 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 0209/1155] [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 0210/1155] 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 16b0411ea98732e36b71e76353a889768a824cb8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Aug 2021 15:45:35 +0200 Subject: [PATCH 0211/1155] Re-add legacy_permission_label so that we don't break the old legacy permission settings --- src/yunohost/utils/legacy.py | 51 ++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 src/yunohost/utils/legacy.py diff --git a/src/yunohost/utils/legacy.py b/src/yunohost/utils/legacy.py new file mode 100644 index 000000000..f3243eb52 --- /dev/null +++ b/src/yunohost/utils/legacy.py @@ -0,0 +1,51 @@ +LEGACY_PERMISSION_LABEL = { + ("nextcloud", "skipped"): "api", # .well-known + ("libreto", "skipped"): "pad access", # /[^/]+ + ("leed", "skipped"): "api", # /action.php, for cron task ... + ("mailman", "protected"): "admin", # /admin + ("prettynoemiecms", "protected"): "admin", # /admin + ("etherpad_mypads", "skipped"): "admin", # /admin + ("baikal", "protected"): "admin", # /admin/ + ("couchpotato", "unprotected"): "api", # /api + ("freshrss", "skipped"): "api", # /api/, + ("portainer", "skipped"): "api", # /api/webhooks/ + ("jeedom", "unprotected"): "api", # /core/api/jeeApi.php + ("bozon", "protected"): "user interface", # /index.php + ( + "limesurvey", + "protected", + ): "admin", # /index.php?r=admin,/index.php?r=plugins,/scripts + ("kanboard", "unprotected"): "api", # /jsonrpc.php + ("seafile", "unprotected"): "medias", # /media + ("ttrss", "skipped"): "api", # /public.php,/api,/opml.php?op=publish + ("libreerp", "protected"): "admin", # /web/database/manager + ("z-push", "skipped"): "api", # $domain/[Aa]uto[Dd]iscover/.* + ("radicale", "skipped"): "?", # $domain$path_url + ( + "jirafeau", + "protected", + ): "user interface", # $domain$path_url/$","$domain$path_url/admin.php.*$ + ("opensondage", "protected"): "admin", # $domain$path_url/admin/ + ( + "lstu", + "protected", + ): "user interface", # $domain$path_url/login$","$domain$path_url/logout$","$domain$path_url/api$","$domain$path_url/extensions$","$domain$path_url/stats$","$domain$path_url/d/.*$","$domain$path_url/a$","$domain$path_url/$ + ( + "lutim", + "protected", + ): "user interface", # $domain$path_url/stats/?$","$domain$path_url/manifest.webapp/?$","$domain$path_url/?$","$domain$path_url/[d-m]/.*$ + ( + "lufi", + "protected", + ): "user interface", # $domain$path_url/stats$","$domain$path_url/manifest.webapp$","$domain$path_url/$","$domain$path_url/d/.*$","$domain$path_url/m/.*$ + ( + "gogs", + "skipped", + ): "api", # $excaped_domain$excaped_path/[%w-.]*/[%w-.]*/git%-receive%-pack,$excaped_domain$excaped_path/[%w-.]*/[%w-.]*/git%-upload%-pack,$excaped_domain$excaped_path/[%w-.]*/[%w-.]*/info/refs +} + + +def legacy_permission_label(app, permission_type): + return LEGACY_PERMISSION_LABEL.get( + (app, permission_type), "Legacy %s urls" % permission_type + ) From 3535b374aed13faf1e1ce72b1830fab12b09e920 Mon Sep 17 00:00:00 2001 From: tituspijean Date: Wed, 11 Aug 2021 16:19:51 +0000 Subject: [PATCH 0212/1155] [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 0213/1155] [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 0214/1155] [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 0215/1155] 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 0216/1155] 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 0217/1155] 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 0218/1155] 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 0219/1155] 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 0220/1155] 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 0221/1155] 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 0222/1155] 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 0223/1155] 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 0224/1155] [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 0225/1155] 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 0226/1155] 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 0227/1155] 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 0228/1155] 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 0229/1155] 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 0230/1155] 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 0231/1155] 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 0232/1155] 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 0233/1155] 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 0234/1155] [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 996a399f94f3e15f507dbf1334ebff37c4fa2e22 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 13 Aug 2021 17:58:46 +0200 Subject: [PATCH 0235/1155] bullseye migration: add new apg .deb signing key --- src/yunohost/data_migrations/0021_migrate_to_bullseye.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py index 6678fa040..4b40fcbe0 100644 --- a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py +++ b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py @@ -36,6 +36,13 @@ class MyMigration(Migration): logger.info(m18n.n("migration_0021_start")) + # + # Add new apt .deb signing key + # + + new_apt_key = "https://forge.yunohost.org/yunohost_bullseye.asc" + check_output(f"wget -O- {new_apt_key} -q | apt-key add -qq -") + # # Patch sources.list # From f0590907c9e2eebe46d28146698387a33a8aea76 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sat, 14 Aug 2021 11:44:52 +0200 Subject: [PATCH 0236/1155] 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 0237/1155] [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 0238/1155] 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 0239/1155] 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 0240/1155] [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 0241/1155] [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 0242/1155] 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 0243/1155] 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 0244/1155] 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 0245/1155] [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 0246/1155] [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 0247/1155] [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 0248/1155] [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 0249/1155] [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 0250/1155] [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 0251/1155] [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 0252/1155] [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 0253/1155] [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 0254/1155] [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 0255/1155] [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 0256/1155] 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 0257/1155] 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 0258/1155] 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 0259/1155] 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 0260/1155] 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 0261/1155] 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 0262/1155] [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 0263/1155] 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 0264/1155] 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 0265/1155] 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 0266/1155] 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 0267/1155] 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 0268/1155] [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 0269/1155] [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 0270/1155] [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 0271/1155] [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 0272/1155] [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 0273/1155] [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 0274/1155] [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 0275/1155] 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 0276/1155] 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 0277/1155] [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 0278/1155] [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 0279/1155] [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 0280/1155] [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 0281/1155] [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 0282/1155] [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 0283/1155] [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 0284/1155] [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 0285/1155] [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 0286/1155] [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 0287/1155] [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 0288/1155] [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 0289/1155] [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 0290/1155] 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 0291/1155] 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 0292/1155] 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 0293/1155] 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 0294/1155] 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 0295/1155] 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 0296/1155] [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 0297/1155] 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 0298/1155] 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 0299/1155] 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 0300/1155] 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 0301/1155] 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 0302/1155] 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 0303/1155] 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 0304/1155] 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 0305/1155] 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 0306/1155] 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 0307/1155] 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 0308/1155] 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 0309/1155] 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 0310/1155] [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 0311/1155] [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 0312/1155] [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 0313/1155] [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 0314/1155] 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 0315/1155] 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 0316/1155] 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 0317/1155] 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 0318/1155] 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 0319/1155] 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 0320/1155] 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 0321/1155] 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 0322/1155] 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 0323/1155] 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 0324/1155] 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 0325/1155] 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 0326/1155] 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 0327/1155] [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 0328/1155] 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 0329/1155] 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 0330/1155] 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 0331/1155] [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 0332/1155] [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 0333/1155] 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 0334/1155] 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 0335/1155] [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 0336/1155] 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 0337/1155] 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 0338/1155] 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 0339/1155] 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 0340/1155] 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 0341/1155] 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 0342/1155] 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 0343/1155] 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 0344/1155] 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 0345/1155] 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 0346/1155] 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 0347/1155] 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 0348/1155] 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 0349/1155] 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 0350/1155] 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 0351/1155] 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 0352/1155] 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 0353/1155] [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 0354/1155] [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 0355/1155] 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 0356/1155] [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 0357/1155] [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 0358/1155] [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 0359/1155] 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 0360/1155] 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 0361/1155] 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 0362/1155] [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 0363/1155] [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 0364/1155] 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 0365/1155] 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 0366/1155] 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 0367/1155] 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 0368/1155] 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 0369/1155] 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 0370/1155] 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 0371/1155] 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 0372/1155] 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 0373/1155] 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 0374/1155] 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 0375/1155] 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 0376/1155] 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 0377/1155] 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 0378/1155] 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 0379/1155] 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 0380/1155] 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 0381/1155] 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 0382/1155] 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 0383/1155] 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 0384/1155] 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 0385/1155] 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 0386/1155] 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 0387/1155] 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 0388/1155] 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 0389/1155] 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 0390/1155] [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 0391/1155] 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 0392/1155] 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 0393/1155] 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 0394/1155] 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 0395/1155] 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 0396/1155] 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 0397/1155] 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 0398/1155] 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 0399/1155] 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 0400/1155] 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 0401/1155] [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 0402/1155] (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 0403/1155] (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 0404/1155] (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 0405/1155] [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 0406/1155] [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 0407/1155] 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 0408/1155] 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 0409/1155] 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 0410/1155] 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 0411/1155] 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 0412/1155] [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 0413/1155] [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 0414/1155] 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 0415/1155] [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 0416/1155] 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 0417/1155] 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 0418/1155] 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 0419/1155] 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 0420/1155] [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 0421/1155] [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 0422/1155] 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 0423/1155] [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 0424/1155] [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 0425/1155] 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 0426/1155] 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 0427/1155] 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 0428/1155] 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 0429/1155] 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 0430/1155] 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 0431/1155] 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 0432/1155] 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 0433/1155] 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 0434/1155] [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 0435/1155] [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 0436/1155] 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 0437/1155] 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 0438/1155] 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 0439/1155] 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 0440/1155] [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 0441/1155] 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 0442/1155] 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 0443/1155] 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 0444/1155] 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 0445/1155] [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 0446/1155] [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 0447/1155] [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 0448/1155] 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 0449/1155] 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 0450/1155] 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 0451/1155] 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 0452/1155] [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 0453/1155] 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 0454/1155] --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 0455/1155] 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 0456/1155] 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 0457/1155] 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 0458/1155] 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 0459/1155] 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 0460/1155] 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 0461/1155] 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 0462/1155] 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 0463/1155] 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 0464/1155] 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 0465/1155] 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 0466/1155] 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 0467/1155] 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 0468/1155] [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 0469/1155] 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 0470/1155] 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 0471/1155] 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 0472/1155] [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 0473/1155] [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 0474/1155] 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 0475/1155] 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 0476/1155] 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 0477/1155] 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 0478/1155] [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 0479/1155] 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 0480/1155] 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 0481/1155] =?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 0482/1155] [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 0483/1155] [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 0484/1155] [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 0485/1155] [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 0486/1155] [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 0487/1155] [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 0488/1155] [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 0489/1155] [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 0490/1155] [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 0491/1155] [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 0492/1155] [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 0493/1155] [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 0494/1155] 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 0495/1155] [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 0496/1155] 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 0497/1155] [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 0498/1155] [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 0499/1155] 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 0500/1155] 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 0501/1155] 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 0502/1155] 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 0503/1155] 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 0504/1155] 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 0505/1155] 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 0506/1155] 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 0507/1155] 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 0508/1155] 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 0509/1155] 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 0510/1155] 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 0511/1155] 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 0512/1155] 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 0513/1155] 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 0514/1155] 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 0515/1155] [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 0516/1155] [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 0517/1155] [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 0518/1155] 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 0519/1155] [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 0520/1155] [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 0521/1155] 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 0522/1155] [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 0523/1155] 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 0524/1155] 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 0525/1155] 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 0526/1155] [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 0527/1155] 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 0528/1155] [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 0529/1155] 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 0530/1155] 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 0531/1155] 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 0532/1155] 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 0533/1155] 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 0534/1155] 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 0535/1155] 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 0536/1155] 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 0537/1155] 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 0538/1155] 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 0539/1155] 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 0540/1155] 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 0541/1155] 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 0542/1155] 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 0543/1155] 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 0544/1155] [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 0545/1155] [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 0546/1155] 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 0547/1155] 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 0548/1155] 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 0549/1155] 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 0550/1155] [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 0551/1155] 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 0552/1155] 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 0553/1155] 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 0554/1155] 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 0555/1155] 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 0556/1155] 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 0557/1155] 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 0558/1155] 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 0559/1155] 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 0560/1155] 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 0561/1155] 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 0562/1155] 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 0563/1155] 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 0564/1155] 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 0565/1155] 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 0566/1155] 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 0567/1155] 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 0568/1155] 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 0569/1155] [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 0570/1155] 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 0571/1155] [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 0572/1155] 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 0573/1155] 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 0574/1155] 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 0575/1155] 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 0576/1155] 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 0577/1155] [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 0578/1155] 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 0579/1155] 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 0580/1155] 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 0581/1155] 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 0582/1155] 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 0583/1155] 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 0584/1155] 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 0585/1155] 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 0586/1155] 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 0587/1155] 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 0588/1155] [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 0589/1155] 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 0590/1155] 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 0591/1155] 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 0592/1155] 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 0593/1155] [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 0594/1155] 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 0595/1155] 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 0596/1155] 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 0597/1155] 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 0598/1155] 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 0599/1155] =?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 0600/1155] =?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 0601/1155] =?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 0602/1155] 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 0603/1155] 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 0604/1155] 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 0605/1155] 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 0606/1155] 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 0607/1155] 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 0608/1155] 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 0609/1155] 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 0610/1155] 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 0611/1155] 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 0612/1155] 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 0613/1155] 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 0614/1155] 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 0615/1155] 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 0616/1155] 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 0617/1155] 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 0618/1155] 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 0619/1155] 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 0620/1155] 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 0621/1155] 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 0622/1155] 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 0623/1155] 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 0624/1155] 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 0625/1155] 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 0626/1155] 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 0627/1155] 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 0628/1155] 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 0629/1155] 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 0630/1155] 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 0631/1155] 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 0632/1155] 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 0633/1155] 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 0634/1155] 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 0635/1155] 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 0636/1155] 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 0637/1155] 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 0638/1155] 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 0639/1155] 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 0640/1155] 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 0641/1155] 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 0642/1155] 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 0643/1155] 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 0644/1155] 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 0645/1155] 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 0646/1155] 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 0647/1155] 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 0648/1155] 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 0649/1155] 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 0650/1155] 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 0651/1155] 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 0652/1155] 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 0653/1155] 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 0654/1155] 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 0655/1155] 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 0656/1155] 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 0657/1155] [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 0658/1155] [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 0659/1155] 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 0660/1155] 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 0661/1155] 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 0662/1155] 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 0663/1155] 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 0664/1155] 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 9bcb7e0b9ef6e54e9eb6b42913343cfd14e742cc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 21 Sep 2021 16:08:24 +0200 Subject: [PATCH 0665/1155] certificate.py: drop legacy 000-acmechallenge.conf stuff --- src/yunohost/certificate.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 817f9d57a..eb9d19c0b 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -500,13 +500,7 @@ Subject: %s def _check_acme_challenge_configuration(domain): 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) + return "include /etc/nginx/conf.d/acme-challenge.conf.inc" in read_file(domain_conf) def _fetch_and_enable_new_certificate(domain, staging=False, no_checks=False): From a6c72566a5f2e733aa1377497e36208a68f47d59 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 21 Sep 2021 16:09:06 +0200 Subject: [PATCH 0666/1155] services.py: drop legacy hack for glances --- src/yunohost/regenconf.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py index ef3c29b32..afcfb4360 100644 --- a/src/yunohost/regenconf.py +++ b/src/yunohost/regenconf.py @@ -125,16 +125,6 @@ def regen_conf( if not names: names = hook_list("conf_regen", list_by="name", show_info=False)["hooks"] - # 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 - if "glances" in names: - names.remove("glances") - # [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) @@ -451,10 +441,6 @@ def _save_regenconf_infos(infos): categories -- A dict containing the regenconf infos """ - # Ugly hack to get rid of legacy glances stuff - if "glances" in infos: - del infos["glances"] - try: with open(REGEN_CONF_FILE, "w") as f: yaml.safe_dump(infos, f, default_flow_style=False) From 2318ffc49356309683141d5dbd5b12fa3b950df4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 21 Sep 2021 16:12:21 +0200 Subject: [PATCH 0667/1155] regenconf: drop old legacy code --- data/hooks/conf_regen/01-yunohost | 4 ---- data/hooks/conf_regen/37-mdns | 6 ------ 2 files changed, 10 deletions(-) diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index ef0bc09fc..6301708d5 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -71,10 +71,6 @@ do_pre_regen() { cd /usr/share/yunohost/templates/yunohost - # 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/systemd/system mkdir -p $pending_dir/etc/cron.d/ mkdir -p $pending_dir/etc/cron.daily/ diff --git a/data/hooks/conf_regen/37-mdns b/data/hooks/conf_regen/37-mdns index 17f7bb8e2..a9ae402fb 100755 --- a/data/hooks/conf_regen/37-mdns +++ b/data/hooks/conf_regen/37-mdns @@ -51,12 +51,6 @@ 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 f597bfe556b42c676d3cc16599327c8060992485 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 21 Sep 2021 16:32:00 +0200 Subject: [PATCH 0668/1155] No sbin anymore --- debian/install | 1 - 1 file changed, 1 deletion(-) diff --git a/debian/install b/debian/install index 8c6ba01dd..4680bc6be 100644 --- a/debian/install +++ b/debian/install @@ -1,5 +1,4 @@ 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/ From 2d158b5a6d20275bee9d76f0a8611e942b235f35 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 21 Sep 2021 20:21:05 +0200 Subject: [PATCH 0669/1155] 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 0670/1155] 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 0671/1155] 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 0672/1155] 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 0673/1155] 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 0674/1155] 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 0675/1155] 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 0676/1155] 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 0677/1155] 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 0678/1155] 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 0679/1155] 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 0680/1155] 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 0681/1155] 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 0682/1155] 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 0683/1155] 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 0684/1155] [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 0685/1155] 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 0686/1155] 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 0687/1155] 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 0688/1155] 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 0689/1155] 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 0690/1155] 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 0691/1155] 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 0692/1155] 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 0693/1155] 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 0694/1155] [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 0695/1155] 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 0696/1155] 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) From 919ec7587709269acc5f223daf2e586b544a0c2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Sun, 26 Sep 2021 10:37:26 +0200 Subject: [PATCH 0697/1155] Revamp of yunomdns : * Use ifaddr (also used by zeroconf) to find ip addresses * Use python type hinting * small cleanups --- bin/yunomdns | 166 ++++++++++++++++----------------------------------- 1 file changed, 53 insertions(+), 113 deletions(-) diff --git a/bin/yunomdns b/bin/yunomdns index 862a1f477..8202b93c4 100755 --- a/bin/yunomdns +++ b/bin/yunomdns @@ -4,104 +4,42 @@ Pythonic declaration of mDNS .local domains for YunoHost """ -import subprocess -import re import sys import yaml - -import socket from time import sleep from typing import List, Dict +import ifaddr from zeroconf import Zeroconf, ServiceInfo -# 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. + +def get_network_local_interfaces() -> Dict[str, Dict[str, List[str]]]: """ - return ( - subprocess.check_output(args, stderr=stderr, shell=shell, **kwargs) - .decode("utf-8") - .strip() - ) - -# Helper command taken from Moulinette -def _extract_inet(string, skip_netmask=False, skip_loopback=True): + Returns interfaces with their associated local IPs """ - 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 + def islocal(ip: str) -> bool: + local_prefixes = ["192.168.", "10.", "172.16.", "fc00:"] + return any(ip.startswith(prefix) for prefix in local_prefixes) - 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 --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 = { - name: _extract_inet(addrs) - for name, addrs in devices_raw.items() - if name != "lo" + interfaces = { + adapter.name: { + "ipv4": [ip.ip for ip in adapter.ips if ip.is_IPv4 and islocal(ip.ip)], + "ipv6": [ip.ip[0] for ip in adapter.ips if ip.is_IPv6 and islocal(ip.ip[0])], + } + for adapter in ifaddr.get_adapters() + if adapter.name != "lo" } + return interfaces - return devices - -if __name__ == '__main__': +def main() -> bool: ### # CONFIG ### with open('/etc/yunohost/mdns.yml', 'r') as f: config = yaml.safe_load(f) or {} - updated = False required_fields = ["interfaces", "domains"] missing_fields = [field for field in required_fields if field not in config] @@ -111,47 +49,44 @@ if __name__ == '__main__': if config['interfaces'] is None: print('No interface listed for broadcast.') - sys.exit(0) + return True if 'yunohost.local' not in config['domains']: config['domains'].append('yunohost.local') - zcs = {} - interfaces = get_network_interfaces() + zcs: Dict[Zeroconf, List[ServiceInfo]] = {} + + interfaces = get_network_local_interfaces() for interface in config['interfaces']: - infos = [] # List of ServiceInfo objects, to feed Zeroconf - ips = [] # Human-readable IPs - b_ips = [] # Binary-convered IPs + if interface not in interfaces: + print(f'Interface {interface} of config file is not present on system.') + continue - 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)) + ips: List[str] = interfaces[interface]['ipv4'] + interfaces[interface]['ipv6'] # If at least one IP is listed - if ips: - # Create a Zeroconf object, and store the ServiceInfos - zc = Zeroconf(interfaces=ips) - zcs[zc]=[] - for d in config['domains']: - d_domain=d.replace('.local','') - 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) + if not ips: + continue + # Create a Zeroconf object, and store the ServiceInfos + zc = Zeroconf(interfaces=ips) # type: ignore + zcs[zc] = [] + + for d in config['domains']: + d_domain = d.replace('.local', '') + if '.' in d_domain: + print(f'{d_domain}.local: subdomains are not supported.') + continue + # Create a ServiceInfo object for each .local domain + zcs[zc].append( + ServiceInfo( + type_='_device-info._tcp.local.', + name=f'{interface}: {d_domain}._device-info._tcp.local.', + parsed_addresses=ips, + port=80, + server=f'{d}.', + ) + ) + print(f'Adding {d} with addresses {ips} on interface {interface}') # Run registration print("Registering...") @@ -168,6 +103,11 @@ if __name__ == '__main__': finally: print("Unregistering...") for zc, infos in zcs.items(): - for info in infos: - zc.unregister_service(info) + zc.unregister_all_services() zc.close() + + return True + + +if __name__ == "__main__": + sys.exit(0 if main() else 1) From d4395f2b4aa69424245218601993e3c37c141df8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Sun, 26 Sep 2021 10:42:57 +0200 Subject: [PATCH 0698/1155] Use double-quotes --- bin/yunomdns | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/bin/yunomdns b/bin/yunomdns index 8202b93c4..f31132514 100755 --- a/bin/yunomdns +++ b/bin/yunomdns @@ -38,31 +38,31 @@ def main() -> bool: # CONFIG ### - with open('/etc/yunohost/mdns.yml', 'r') as f: + with open("/etc/yunohost/mdns.yml", "r") as f: config = yaml.safe_load(f) or {} required_fields = ["interfaces", "domains"] missing_fields = [field for field in required_fields if field not in config] if missing_fields: - print("The fields %s are required" % ', '.join(missing_fields)) + print("The fields %s are required" % ", ".join(missing_fields)) - if config['interfaces'] is None: - print('No interface listed for broadcast.') + if config["interfaces"] is None: + print("No interface listed for broadcast.") return True - if 'yunohost.local' not in config['domains']: - config['domains'].append('yunohost.local') + if "yunohost.local" not in config["domains"]: + config["domains"].append("yunohost.local") zcs: Dict[Zeroconf, List[ServiceInfo]] = {} interfaces = get_network_local_interfaces() - for interface in config['interfaces']: + for interface in config["interfaces"]: if interface not in interfaces: - print(f'Interface {interface} of config file is not present on system.') + print(f"Interface {interface} of config file is not present on system.") continue - ips: List[str] = interfaces[interface]['ipv4'] + interfaces[interface]['ipv6'] + ips: List[str] = interfaces[interface]["ipv4"] + interfaces[interface]["ipv6"] # If at least one IP is listed if not ips: @@ -71,22 +71,22 @@ def main() -> bool: zc = Zeroconf(interfaces=ips) # type: ignore zcs[zc] = [] - for d in config['domains']: - d_domain = d.replace('.local', '') - if '.' in d_domain: - print(f'{d_domain}.local: subdomains are not supported.') + for d in config["domains"]: + d_domain = d.replace(".local", "") + if "." in d_domain: + print(f"{d_domain}.local: subdomains are not supported.") continue # Create a ServiceInfo object for each .local domain zcs[zc].append( ServiceInfo( - type_='_device-info._tcp.local.', - name=f'{interface}: {d_domain}._device-info._tcp.local.', + type_="_device-info._tcp.local.", + name=f"{interface}: {d_domain}._device-info._tcp.local.", parsed_addresses=ips, port=80, - server=f'{d}.', + server=f"{d}.", ) ) - print(f'Adding {d} with addresses {ips} on interface {interface}') + print(f"Adding {d} with addresses {ips} on interface {interface}") # Run registration print("Registering...") From 358885dc622d9e18c2cc283c4a1c52236537b73b Mon Sep 17 00:00:00 2001 From: tituspijean Date: Mon, 20 Sep 2021 22:07:45 +0200 Subject: [PATCH 0699/1155] [mdns] Avoid miserably failing if another service exists on the network also, service names do not clash anymore accross same device but different interfaces --- bin/yunomdns | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/yunomdns b/bin/yunomdns index f31132514..dfd58deee 100755 --- a/bin/yunomdns +++ b/bin/yunomdns @@ -92,7 +92,7 @@ def main() -> bool: print("Registering...") for zc, infos in zcs.items(): for info in infos: - zc.register_service(info) + zc.register_service(info, allow_name_change=True, cooperating_responders=True) try: print("Registered. Press Ctrl+C or stop service to stop.") From bcb48b4948a900fd153e87e325ba07c8c56f6895 Mon Sep 17 00:00:00 2001 From: tituspijean Date: Mon, 20 Sep 2021 22:14:00 +0200 Subject: [PATCH 0700/1155] [mdns] Make the service logs appear in systemd journal --- data/templates/mdns/yunomdns.service | 1 + 1 file changed, 1 insertion(+) diff --git a/data/templates/mdns/yunomdns.service b/data/templates/mdns/yunomdns.service index ce2641b5d..c1f1b7b06 100644 --- a/data/templates/mdns/yunomdns.service +++ b/data/templates/mdns/yunomdns.service @@ -6,6 +6,7 @@ After=network.target User=mdns Group=mdns Type=simple +Environment=PYTHONUNBUFFERED=1 ExecStart=/usr/bin/yunomdns StandardOutput=syslog From a313a86b8b6eb39b8836c47cc8c107058a6da6ed Mon Sep 17 00:00:00 2001 From: tituspijean Date: Tue, 21 Sep 2021 00:09:49 +0200 Subject: [PATCH 0701/1155] [mdns] Allow for multiple yunohost.local --- bin/yunomdns | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/bin/yunomdns b/bin/yunomdns index dfd58deee..aa0697f5f 100755 --- a/bin/yunomdns +++ b/bin/yunomdns @@ -10,7 +10,7 @@ from time import sleep from typing import List, Dict import ifaddr -from zeroconf import Zeroconf, ServiceInfo +from zeroconf import Zeroconf, ServiceInfo, ServiceBrowser def get_network_local_interfaces() -> Dict[str, Dict[str, List[str]]]: @@ -32,6 +32,23 @@ def get_network_local_interfaces() -> Dict[str, Dict[str, List[str]]]: } return interfaces +# Listener class, to detect duplicates on the network +# Stores the list of servers in its list property +class Listener: + + def __init__(self): + self.list = [] + + def remove_service(self, zeroconf, type, name): + info = zeroconf.get_service_info(type, name) + self.list.remove(info.server) + + def update_service(self, zeroconf, type, name): + pass + + def add_service(self, zeroconf, type, name): + info = zeroconf.get_service_info(type, name) + self.list.append(info.server[:-1]) def main() -> bool: ### @@ -51,8 +68,25 @@ def main() -> bool: print("No interface listed for broadcast.") return True - if "yunohost.local" not in config["domains"]: - config["domains"].append("yunohost.local") + # Let's discover currently published .local domains accross the network + zc = Zeroconf() + listener = Listener() + browser = ServiceBrowser(zc, "_device-info._tcp.local.", listener) + sleep(2) + browser.cancel() + zc.close() + # If yunohost.local already exists, try yunohost-2.local, and so on. + def yunohost_local(i): + return "yunohost.local" if i < 2 else "yunohost-"+str(i)+".local" + i=1 + while yunohost_local(i) in listener.list: + print("Uh oh, "+yunohost_local(i)+" already exists on the network...") + if yunohost_local(i) in config['domains']: + config['domains'].remove(yunohost_local(i)) + i += 1 + if yunohost_local(i) not in config['domains']: + print("Adding "+yunohost_local(i)+" to the domains to publish.") + config['domains'].append(yunohost_local(i)) zcs: Dict[Zeroconf, List[ServiceInfo]] = {} From 07c381cec4690df90022e11c0574dfca78906c2f Mon Sep 17 00:00:00 2001 From: tituspijean Date: Sun, 26 Sep 2021 12:05:35 +0200 Subject: [PATCH 0702/1155] Use ipaddress lib to find private addresses --- bin/yunomdns | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/bin/yunomdns b/bin/yunomdns index aa0697f5f..f50afd964 100755 --- a/bin/yunomdns +++ b/bin/yunomdns @@ -10,6 +10,7 @@ from time import sleep from typing import List, Dict import ifaddr +from ipaddress import ip_address from zeroconf import Zeroconf, ServiceInfo, ServiceBrowser @@ -18,14 +19,10 @@ def get_network_local_interfaces() -> Dict[str, Dict[str, List[str]]]: Returns interfaces with their associated local IPs """ - def islocal(ip: str) -> bool: - local_prefixes = ["192.168.", "10.", "172.16.", "fc00:"] - return any(ip.startswith(prefix) for prefix in local_prefixes) - interfaces = { adapter.name: { - "ipv4": [ip.ip for ip in adapter.ips if ip.is_IPv4 and islocal(ip.ip)], - "ipv6": [ip.ip[0] for ip in adapter.ips if ip.is_IPv6 and islocal(ip.ip[0])], + "ipv4": [ip.ip for ip in adapter.ips if ip.is_IPv4 and ip_address(ip.ip).is_private], + "ipv6": [ip.ip[0] for ip in adapter.ips if ip.is_IPv6 and ip_address(ip.ip[0]).is_private], } for adapter in ifaddr.get_adapters() if adapter.name != "lo" From 63504febaaf17a7efbf93f111c409764e84a0b17 Mon Sep 17 00:00:00 2001 From: tituspijean Date: Sun, 26 Sep 2021 19:40:46 +0200 Subject: [PATCH 0703/1155] [mdns] refine ipv6 selection with ipaddress lib Co-authored-by: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= --- bin/yunomdns | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/yunomdns b/bin/yunomdns index f50afd964..f302f1f8c 100755 --- a/bin/yunomdns +++ b/bin/yunomdns @@ -22,7 +22,7 @@ def get_network_local_interfaces() -> Dict[str, Dict[str, List[str]]]: interfaces = { adapter.name: { "ipv4": [ip.ip for ip in adapter.ips if ip.is_IPv4 and ip_address(ip.ip).is_private], - "ipv6": [ip.ip[0] for ip in adapter.ips if ip.is_IPv6 and ip_address(ip.ip[0]).is_private], + "ipv6": [ip.ip[0] for ip in adapter.ips if ip.is_IPv6 and ip_address(ip.ip[0]).is_private and not ip_address(ip.ip[0]).is_link_local], } for adapter in ifaddr.get_adapters() if adapter.name != "lo" From da1b9089bf6fd6e39d0c905bbe8e6573658315a9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 28 Sep 2021 23:36:24 +0200 Subject: [PATCH 0704/1155] Fix dns zone for dynette domains (otherwise breaks dyndns update) --- src/yunohost/dns.py | 5 +++-- src/yunohost/tests/test_dns.py | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 0581fa82c..c003b7a56 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -428,7 +428,8 @@ def _get_dns_zone_for_domain(domain): # ... 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 + # Keep only foo.nohost.me even if we have subsub.sub.foo.nohost.me + return '.'.join(domain.rsplit('.', 3)[-3:]) # Check cache cache_folder = "/var/cache/yunohost/dns_zones" @@ -520,7 +521,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: + if any(dns_zone.endswith('.' + ynh_dyndns_domain) for ynh_dyndns_domain in YNH_DYNDNS_DOMAINS): registrar_infos["registrar"] = OrderedDict( { "type": "alert", diff --git a/src/yunohost/tests/test_dns.py b/src/yunohost/tests/test_dns.py index 497cab2fd..cdd37b2ea 100644 --- a/src/yunohost/tests/test_dns.py +++ b/src/yunohost/tests/test_dns.py @@ -34,8 +34,10 @@ 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.nohost.me") == "yolo.nohost.me" + assert _get_dns_zone_for_domain("foo.yolo.nohost.me") == "yolo.nohost.me" + assert _get_dns_zone_for_domain("bar.foo.yolo.nohost.me") == "yolo.nohost.me" + assert _get_dns_zone_for_domain("yolo.tld") == "yolo.tld" assert _get_dns_zone_for_domain("foo.yolo.tld") == "yolo.tld" From 7d4ab4a886fd568179d642759947aef5ff26d714 Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Tue, 28 Sep 2021 21:52:29 +0000 Subject: [PATCH 0705/1155] [CI] Format code --- src/yunohost/dns.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index c003b7a56..e62469a53 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -429,7 +429,7 @@ def _get_dns_zone_for_domain(domain): for ynh_dyndns_domain in YNH_DYNDNS_DOMAINS: if domain.endswith("." + ynh_dyndns_domain): # Keep only foo.nohost.me even if we have subsub.sub.foo.nohost.me - return '.'.join(domain.rsplit('.', 3)[-3:]) + return ".".join(domain.rsplit(".", 3)[-3:]) # Check cache cache_folder = "/var/cache/yunohost/dns_zones" @@ -521,7 +521,10 @@ 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 any(dns_zone.endswith('.' + ynh_dyndns_domain) for ynh_dyndns_domain in YNH_DYNDNS_DOMAINS): + if any( + dns_zone.endswith("." + ynh_dyndns_domain) + for ynh_dyndns_domain in YNH_DYNDNS_DOMAINS + ): registrar_infos["registrar"] = OrderedDict( { "type": "alert", From dc8c16c96da7bc7f5676af31f372ce2887250512 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Sep 2021 18:33:31 +0200 Subject: [PATCH 0706/1155] Wording --- 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 cf24cfc09..1977f8278 100644 --- a/locales/en.json +++ b/locales/en.json @@ -201,7 +201,7 @@ "diagnosis_domain_expiration_warning": "Some domains will expire soon!", "diagnosis_domain_expires_in": "{domain} expires in {days} days.", "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_everything_ok": "Everything looks OK 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}!", diff --git a/locales/fr.json b/locales/fr.json index 46535719e..bd29ad774 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -431,7 +431,7 @@ "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} !", + "diagnosis_everything_ok": "Tout semble OK 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.", @@ -676,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 +} From ad3602a24fffe9fa16c0d767a1d151e0ea966de6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Sep 2021 18:36:24 +0200 Subject: [PATCH 0707/1155] YNH_APP_PURGE to be 1 or 0 to be consistent with other bash bools --- 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 0013fcd82..56e4c344b 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1160,7 +1160,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"] = str(purge) + env_dict["YNH_APP_PURGE"] = str(1 if purge else 0) env_dict["YNH_APP_BASEDIR"] = tmp_workdir_for_app operation_logger.extra.update({"env": env_dict}) From 14d3265389ad122c31944f54c5b17db65f20fb7b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Sep 2021 18:40:31 +0200 Subject: [PATCH 0708/1155] Try to include diagnosis hooks in coverage report --- .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 b3aea606f..6a422ea22 100644 --- a/.gitlab/ci/test.gitlab-ci.yml +++ b/.gitlab/ci/test.gitlab-ci.yml @@ -36,7 +36,7 @@ full-tests: - *install_debs - 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 + - python3 -m pytest --cov=yunohost tests/ src/yunohost/tests/ data/hooks/diagnosis/ --junitxml=report.xml - cd tests - bash test_helpers.sh needs: From ab1100048b91dc1b9404c0996642ccca68b09062 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Sep 2021 19:00:05 +0200 Subject: [PATCH 0709/1155] yunomdns: Minor cleanups --- bin/yunomdns | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/bin/yunomdns b/bin/yunomdns index f302f1f8c..41a9ede3e 100755 --- a/bin/yunomdns +++ b/bin/yunomdns @@ -29,6 +29,7 @@ def get_network_local_interfaces() -> Dict[str, Dict[str, List[str]]]: } return interfaces + # Listener class, to detect duplicates on the network # Stores the list of servers in its list property class Listener: @@ -47,6 +48,7 @@ class Listener: info = zeroconf.get_service_info(type, name) self.list.append(info.server[:-1]) + def main() -> bool: ### # CONFIG @@ -59,7 +61,8 @@ def main() -> bool: missing_fields = [field for field in required_fields if field not in config] if missing_fields: - print("The fields %s are required" % ", ".join(missing_fields)) + print(f"The fields {missing_fields} are required in mdns.yml") + return False if config["interfaces"] is None: print("No interface listed for broadcast.") @@ -72,17 +75,20 @@ def main() -> bool: sleep(2) browser.cancel() zc.close() + # If yunohost.local already exists, try yunohost-2.local, and so on. def yunohost_local(i): - return "yunohost.local" if i < 2 else "yunohost-"+str(i)+".local" - i=1 + return "yunohost.local" if i < 2 else f"yunohost-{i}.local" + + i = 1 while yunohost_local(i) in listener.list: - print("Uh oh, "+yunohost_local(i)+" already exists on the network...") + print(f"Uh oh, {yunohost_local(i)} already exists on the network...") if yunohost_local(i) in config['domains']: config['domains'].remove(yunohost_local(i)) i += 1 + if yunohost_local(i) not in config['domains']: - print("Adding "+yunohost_local(i)+" to the domains to publish.") + print(f"Adding {yunohost_local(i)} to the domains to publish.") config['domains'].append(yunohost_local(i)) zcs: Dict[Zeroconf, List[ServiceInfo]] = {} From f49666d22e7e17cedfab49a98a1789c12f62fc7e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Sep 2021 19:14:21 +0200 Subject: [PATCH 0710/1155] yunomdns: fallback to domain-i.local if domain.local already published --- bin/yunomdns | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/bin/yunomdns b/bin/yunomdns index 41a9ede3e..8a9682ff9 100755 --- a/bin/yunomdns +++ b/bin/yunomdns @@ -76,20 +76,27 @@ def main() -> bool: browser.cancel() zc.close() - # If yunohost.local already exists, try yunohost-2.local, and so on. - def yunohost_local(i): - return "yunohost.local" if i < 2 else f"yunohost-{i}.local" + # Always attempt to publish yunohost.local + if "yunohost.local" not in config["domains"]: + config["domains"].append("yunohost.local") - i = 1 - while yunohost_local(i) in listener.list: - print(f"Uh oh, {yunohost_local(i)} already exists on the network...") - if yunohost_local(i) in config['domains']: - config['domains'].remove(yunohost_local(i)) - i += 1 + def find_domain_not_already_published(domain): - if yunohost_local(i) not in config['domains']: - print(f"Adding {yunohost_local(i)} to the domains to publish.") - config['domains'].append(yunohost_local(i)) + # Try domain.local ... but if it's already published by another entity, + # try domain-2.local, domain-3.local, ... + + i = 1 + domain_i = domain + + while domain_i in listener.list: + print(f"Uh oh, {domain_i} already exists on the network...") + + i += 1 + domain_i = domain.replace(".local", f"-{i}.local") + + return domain_i + + config['domains'] = [find_domain_not_already_published(domain) for domain in config['domains']] zcs: Dict[Zeroconf, List[ServiceInfo]] = {} From a5e1d7e318158da38b1e3dc415a92f0e6250a460 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Sep 2021 20:02:32 +0200 Subject: [PATCH 0711/1155] yunomdns: broadcast on all interfaces with local IP by default + add a 'ban_interfaces' setting in config --- bin/yunomdns | 23 +++++++++++++++++------ data/hooks/conf_regen/37-mdns | 7 ------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/bin/yunomdns b/bin/yunomdns index 8a9682ff9..b9f8cf2a7 100755 --- a/bin/yunomdns +++ b/bin/yunomdns @@ -57,16 +57,23 @@ def main() -> bool: with open("/etc/yunohost/mdns.yml", "r") as f: config = yaml.safe_load(f) or {} - required_fields = ["interfaces", "domains"] + required_fields = ["domains"] missing_fields = [field for field in required_fields if field not in config] + interfaces = get_network_local_interfaces() if missing_fields: print(f"The fields {missing_fields} are required in mdns.yml") return False - if config["interfaces"] is None: - print("No interface listed for broadcast.") - return True + if "interfaces" not in config: + config["interfaces"] = [interface + for interface, local_ips in interfaces.items() + if local_ips["ipv4"]] + + if "ban_interfaces" in config: + config["interfaces"] = [interface + for interface in config["interfaces"] + if interface not in config["ban_interfaces"]] # Let's discover currently published .local domains accross the network zc = Zeroconf() @@ -100,10 +107,10 @@ def main() -> bool: zcs: Dict[Zeroconf, List[ServiceInfo]] = {} - interfaces = get_network_local_interfaces() for interface in config["interfaces"]: + if interface not in interfaces: - print(f"Interface {interface} of config file is not present on system.") + print(f"Interface {interface} listed in config file is not present on system.") continue ips: List[str] = interfaces[interface]["ipv4"] + interfaces[interface]["ipv6"] @@ -111,6 +118,10 @@ def main() -> bool: # If at least one IP is listed if not ips: continue + + print(f"Publishing on {interface} ...") + print(ips) + # Create a Zeroconf object, and store the ServiceInfos zc = Zeroconf(interfaces=ips) # type: ignore zcs[zc] = [] diff --git a/data/hooks/conf_regen/37-mdns b/data/hooks/conf_regen/37-mdns index 1d7381e26..b2a3efe95 100755 --- a/data/hooks/conf_regen/37-mdns +++ b/data/hooks/conf_regen/37-mdns @@ -12,13 +12,6 @@ _generate_config() { [[ "$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() { From 391de52ff214a46efe1f990c6bd7164eebc852ad Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Sep 2021 20:10:00 +0200 Subject: [PATCH 0712/1155] yunomdns: disable ipv6 for now + remove debug stuff --- bin/yunomdns | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bin/yunomdns b/bin/yunomdns index b9f8cf2a7..0aee28195 100755 --- a/bin/yunomdns +++ b/bin/yunomdns @@ -113,15 +113,17 @@ def main() -> bool: print(f"Interface {interface} listed in config file is not present on system.") continue - ips: List[str] = interfaces[interface]["ipv4"] + interfaces[interface]["ipv6"] + # Only broadcast IPv4 because IPv6 is buggy ... because we ain't using python3-ifaddr >= 0.1.7 + # Buster only ships 0.1.6 + # Bullseye ships 0.1.7 + # To be re-enabled once we're on bullseye... + # ips: List[str] = interfaces[interface]["ipv4"] + interfaces[interface]["ipv6"] + ips: List[str] = interfaces[interface]["ipv4"] # If at least one IP is listed if not ips: continue - print(f"Publishing on {interface} ...") - print(ips) - # Create a Zeroconf object, and store the ServiceInfos zc = Zeroconf(interfaces=ips) # type: ignore zcs[zc] = [] From 17aafe6f6ad74e136802221d7073160b9aa2c8f8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Sep 2021 20:33:07 +0200 Subject: [PATCH 0713/1155] General improvement for special-use TLD / ynh dyndns domains --- data/hooks/diagnosis/12-dnsrecords.py | 18 ++++-------- data/hooks/diagnosis/21-web.py | 5 ++-- locales/en.json | 5 ++-- src/yunohost/dns.py | 40 ++++++++++++++++++--------- src/yunohost/tests/test_dns.py | 3 ++ src/yunohost/utils/dns.py | 12 ++++++++ 6 files changed, 53 insertions(+), 30 deletions(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 16841721f..368c3d878 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -8,13 +8,11 @@ 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, YNH_DYNDNS_DOMAINS, is_yunohost_dyndns_domain, is_special_use_tld from yunohost.diagnosis import Diagnoser from yunohost.domain import domain_list, _get_maindomain from yunohost.dns import _build_dns_conf, _get_dns_zone_for_domain -SPECIAL_USE_TLDS = ["local", "localhost", "onion", "test"] - class DNSRecordsDiagnoser(Diagnoser): @@ -29,13 +27,10 @@ class DNSRecordsDiagnoser(Diagnoser): all_domains = domain_list(exclude_subdomains=True)["domains"] for domain in all_domains: self.logger_debug("Diagnosing DNS conf for %s" % domain) - is_specialusedomain = any( - domain.endswith("." + tld) for tld in SPECIAL_USE_TLDS - ) + for report in self.check_domain( domain, domain == main_domain, - is_specialusedomain=is_specialusedomain, ): yield report @@ -53,7 +48,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_specialusedomain): + def check_domain(self, domain, is_main_domain): base_dns_zone = _get_dns_zone_for_domain(domain) basename = domain.replace(base_dns_zone, "").rstrip(".") or "@" @@ -64,7 +59,7 @@ class DNSRecordsDiagnoser(Diagnoser): categories = ["basic", "mail", "xmpp", "extra"] - if is_specialusedomain: + if is_special_use_tld(domain): categories = [] yield dict( meta={"domain": domain}, @@ -140,10 +135,7 @@ class DNSRecordsDiagnoser(Diagnoser): if discrepancies: # For ynh-managed domains (nohost.me etc...), tell people to try to "yunohost dyndns update --force" - if any( - domain.endswith(ynh_dyndns_domain) - for ynh_dyndns_domain in YNH_DYNDNS_DOMAINS - ): + if is_yunohost_dyndns_domain(domain): output["details"] = ["diagnosis_dns_try_dyndns_update_force"] # Otherwise point to the documentation else: diff --git a/data/hooks/diagnosis/21-web.py b/data/hooks/diagnosis/21-web.py index 2072937e5..450296e7e 100644 --- a/data/hooks/diagnosis/21-web.py +++ b/data/hooks/diagnosis/21-web.py @@ -8,6 +8,7 @@ from moulinette.utils.filesystem import read_file from yunohost.diagnosis import Diagnoser from yunohost.domain import domain_list +from yunohost.utils.dns import is_special_use_tld DIAGNOSIS_SERVER = "diagnosis.yunohost.org" @@ -34,11 +35,11 @@ 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 is_special_use_tld(domain): yield dict( meta={"domain": domain}, status="INFO", - summary="diagnosis_http_localdomain", + summary="diagnosis_http_special_use_tld", ) else: domains_to_check.append(domain) diff --git a/locales/en.json b/locales/en.json index 1977f8278..5d242b2e8 100644 --- a/locales/en.json +++ b/locales/en.json @@ -192,7 +192,7 @@ "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_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_specialusedomain": "Domain {domain} is based on a special-use top-level domain (TLD) such as .local or .test 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", @@ -214,7 +214,7 @@ "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_special_use_tld": "Domain {domain} is based on a special-use top-level domain (TLD) such as .local or .test and is therefore 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.", @@ -308,6 +308,7 @@ "domain_deleted": "Domain deleted", "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_dns_conf_special_use_tld": "This domain is based on a special-use top-level domain (TLD) such as .local or .test and is therefore not expected to have actual DNS records.", "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", diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index e62469a53..72c9f7c72 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -41,7 +41,7 @@ from yunohost.domain import ( _get_domain_settings, _set_domain_settings, ) -from yunohost.utils.dns import dig, YNH_DYNDNS_DOMAINS +from yunohost.utils.dns import dig, is_yunohost_dyndns_domain, is_special_use_tld from yunohost.utils.error import YunohostValidationError, YunohostError from yunohost.utils.network import get_public_ip from yunohost.log import is_unit_operation @@ -61,6 +61,9 @@ def domain_dns_suggest(domain): """ + if is_special_use_tld(domain): + return m18n.n("domain_dns_conf_special_use_tld") + _assert_domain_exists(domain) dns_conf = _build_dns_conf(domain) @@ -169,10 +172,7 @@ 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 is_yunohost_dyndns_domain(base_domain): subdomains = [] else: subdomains = _list_subdomains_of(base_domain) @@ -426,10 +426,14 @@ def _get_dns_zone_for_domain(domain): # 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): - # Keep only foo.nohost.me even if we have subsub.sub.foo.nohost.me - return ".".join(domain.rsplit(".", 3)[-3:]) + if is_yunohost_dyndns_domain(domain): + # Keep only foo.nohost.me even if we have subsub.sub.foo.nohost.me + return ".".join(domain.rsplit(".", 3)[-3:]) + + # Same thing with .local, .test, ... domains + if is_special_use_tld(domain): + # Keep only foo.local even if we have subsub.sub.foo.local + return ".".join(domain.rsplit(".", 2)[-2:]) # Check cache cache_folder = "/var/cache/yunohost/dns_zones" @@ -521,10 +525,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 any( - dns_zone.endswith("." + ynh_dyndns_domain) - for ynh_dyndns_domain in YNH_DYNDNS_DOMAINS - ): + if is_yunohost_dyndns_domain(dns_zone): registrar_infos["registrar"] = OrderedDict( { "type": "alert", @@ -534,6 +535,15 @@ def _get_registrar_config_section(domain): } ) return OrderedDict(registrar_infos) + elif is_special_use_tld(dns_zone): + registrar_infos["registrar"] = OrderedDict( + { + "type": "alert", + "style": "info", + "ask": m18n.n("domain_dns_conf_special_use_tld"), + "value": None, + } + ) try: registrar = _relevant_provider_for_domain(dns_zone)[0] @@ -607,6 +617,10 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= _assert_domain_exists(domain) + if is_special_use_tld(domain): + logger.info(m18n.n("domain_dns_conf_special_use_tld")) + return {} + if not registrar or registrar == "None": # yes it's None as a string raise YunohostValidationError("domain_dns_push_not_applicable", domain=domain) diff --git a/src/yunohost/tests/test_dns.py b/src/yunohost/tests/test_dns.py index cdd37b2ea..a23ac7982 100644 --- a/src/yunohost/tests/test_dns.py +++ b/src/yunohost/tests/test_dns.py @@ -38,6 +38,9 @@ def test_get_dns_zone_from_domain_existing(): assert _get_dns_zone_for_domain("foo.yolo.nohost.me") == "yolo.nohost.me" assert _get_dns_zone_for_domain("bar.foo.yolo.nohost.me") == "yolo.nohost.me" + assert _get_dns_zone_for_domain("yolo.test") == "yolo.test" + assert _get_dns_zone_for_domain("foo.yolo.test") == "yolo.test" + assert _get_dns_zone_for_domain("yolo.tld") == "yolo.tld" assert _get_dns_zone_for_domain("foo.yolo.tld") == "yolo.tld" diff --git a/src/yunohost/utils/dns.py b/src/yunohost/utils/dns.py index 3db75f949..4ab17416d 100644 --- a/src/yunohost/utils/dns.py +++ b/src/yunohost/utils/dns.py @@ -23,6 +23,8 @@ from typing import List from moulinette.utils.filesystem import read_file +SPECIAL_USE_TLDS = ["local", "localhost", "onion", "test"] + YNH_DYNDNS_DOMAINS = ["nohost.me", "noho.st", "ynh.fr"] # Lazy dev caching to avoid re-reading the file multiple time when calling @@ -30,6 +32,16 @@ YNH_DYNDNS_DOMAINS = ["nohost.me", "noho.st", "ynh.fr"] external_resolvers_: List[str] = [] +def is_yunohost_dyndns_domain(domain): + + return any(domain.endswith(f".{dyndns_domain}") for dyndns_domain in YNH_DYNDNS_DOMAINS) + + +def is_special_use_tld(domain): + + return any(domain.endswith(f".{tld}") for tld in SPECIAL_USE_TLDS) + + def external_resolvers(): global external_resolvers_ From 85e6ddbeae1c2c2f43f2d84cda2faa52e8479917 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Sep 2021 20:50:01 +0200 Subject: [PATCH 0714/1155] firewall.py: fix encoding issue, remove prependline stuff --- src/yunohost/firewall.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/yunohost/firewall.py b/src/yunohost/firewall.py index 4be6810ec..a1c0b187f 100644 --- a/src/yunohost/firewall.py +++ b/src/yunohost/firewall.py @@ -31,7 +31,6 @@ from moulinette import m18n from yunohost.utils.error import YunohostError, YunohostValidationError from moulinette.utils import process from moulinette.utils.log import getActionLogger -from moulinette.utils.text import prependlines FIREWALL_FILE = "/etc/yunohost/firewall.yml" UPNP_CRON_JOB = "/etc/cron.d/yunohost-firewall-upnp" @@ -240,7 +239,7 @@ def firewall_reload(skip_upnp=False): except process.CalledProcessError as e: logger.debug( "iptables seems to be not available, it outputs:\n%s", - prependlines(e.output.rstrip(), "> "), + e.output.decode().strip(), ) logger.warning(m18n.n("iptables_unavailable")) else: @@ -273,7 +272,7 @@ def firewall_reload(skip_upnp=False): except process.CalledProcessError as e: logger.debug( "ip6tables seems to be not available, it outputs:\n%s", - prependlines(e.output.rstrip(), "> "), + e.output.decode().strip(), ) logger.warning(m18n.n("ip6tables_unavailable")) else: @@ -526,6 +525,6 @@ def _on_rule_command_error(returncode, cmd, output): '"%s" returned non-zero exit status %d:\n%s', cmd, returncode, - prependlines(output.rstrip(), "> "), + output.decode().strip(), ) return True From c44560b6f7cac9486d96a7f111059bfb2b4a4ec3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Sep 2021 21:38:03 +0200 Subject: [PATCH 0715/1155] Misc cleanups in dns diagnoser --- data/hooks/diagnosis/12-dnsrecords.py | 33 ++++++++++++++++----------- src/yunohost/dns.py | 6 +++++ 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 368c3d878..85f837af5 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -24,8 +24,8 @@ class DNSRecordsDiagnoser(Diagnoser): main_domain = _get_maindomain() - all_domains = domain_list(exclude_subdomains=True)["domains"] - for domain in all_domains: + major_domains = domain_list(exclude_subdomains=True)["domains"] + for domain in major_domains: self.logger_debug("Diagnosing DNS conf for %s" % domain) for report in self.check_domain( @@ -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.get_public_suffix(domain) for domain in major_domains ] domains_from_registrar = [ domain for domain in domains_from_registrar if "." in domain @@ -50,15 +50,6 @@ class DNSRecordsDiagnoser(Diagnoser): def check_domain(self, domain, is_main_domain): - 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"] - if is_special_use_tld(domain): categories = [] yield dict( @@ -68,6 +59,15 @@ class DNSRecordsDiagnoser(Diagnoser): summary="diagnosis_dns_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 category in categories: records = expected_configuration[category] @@ -79,7 +79,8 @@ class DNSRecordsDiagnoser(Diagnoser): id_ = r["type"] + ":" + r["name"] 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... + # 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, @@ -126,6 +127,12 @@ class DNSRecordsDiagnoser(Diagnoser): status = "SUCCESS" summary = "diagnosis_dns_good_conf" + # If status is okay and there's actually no expected records + # (e.g. XMPP disabled) + # then let's not yield any diagnosis line + if not records and "status" == "SUCCESS": + continue + output = dict( meta={"domain": domain, "category": category}, data=results, diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 72c9f7c72..493d7ad86 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -297,6 +297,12 @@ def _build_dns_conf(base_domain, include_empty_AAAA_if_no_ipv6=False): # Defined by custom hooks ships in apps for example ... + # FIXME : this ain't practical for apps that may want to add + # custom dns records for a subdomain ... there's no easy way for + # an app to compare the base domain is the parent of the subdomain ? + # (On the other hand, in sep 2021, it looks like no app is using + # this mechanism...) + hook_results = hook_callback("custom_dns_rules", args=[base_domain]) for hook_name, results in hook_results.items(): # From b8cb374a4925b2afa44d1f24abdb92831a92ee18 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Sep 2021 21:43:52 +0200 Subject: [PATCH 0716/1155] Add a proper _get_parent_domain --- src/yunohost/dns.py | 14 ++------------ src/yunohost/domain.py | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 493d7ad86..fdceee26b 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -40,6 +40,8 @@ from yunohost.domain import ( domain_config_get, _get_domain_settings, _set_domain_settings, + _get_parent_domain_of, + _list_subdomains_of, ) from yunohost.utils.dns import dig, is_yunohost_dyndns_domain, is_special_use_tld from yunohost.utils.error import YunohostValidationError, YunohostError @@ -107,18 +109,6 @@ def domain_dns_suggest(domain): return result -def _list_subdomains_of(parent_domain): - - _assert_domain_exists(parent_domain) - - out = [] - for domain in domain_list()["domains"]: - if domain.endswith(f".{parent_domain}"): - out.append(domain) - - return out - - def _build_dns_conf(base_domain, include_empty_AAAA_if_no_ipv6=False): """ Internal function that will returns a data structure containing the needed diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 1f96ced8a..f5b14748d 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -105,6 +105,33 @@ def _assert_domain_exists(domain): raise YunohostValidationError("domain_name_unknown", domain=domain) +def _list_subdomains_of(parent_domain): + + _assert_domain_exists(parent_domain) + + out = [] + for domain in domain_list()["domains"]: + if domain.endswith(f".{parent_domain}"): + out.append(domain) + + return out + + +def _get_parent_domain_of(domain): + + _assert_domain_exists(domain) + + if "." not in domain: + return domain + + parent_domain = domain.split(".", 1)[-1] + if parent_domain not in domain_list()["domains"]: + return domain # Domain is its own parent + + else: + return _get_parent_domain_of(parent_domain) + + @is_unit_operation() def domain_add(operation_logger, domain, dyndns=False): """ From d75c1a61e82738853a22f0aaec28acb2b207d6fb Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Sep 2021 22:11:24 +0200 Subject: [PATCH 0717/1155] Adapt ready_for_ACME check to the new dnsrecord result format... --- src/yunohost/certificate.py | 43 ++++++++++++++++++++++++++----------- src/yunohost/dns.py | 1 - 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 817f9d57a..0df428c77 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -851,14 +851,9 @@ def _backup_current_cert(domain): def _check_domain_is_ready_for_ACME(domain): - dnsrecords = ( - Diagnoser.get_cached_report( - "dnsrecords", - item={"domain": domain, "category": "basic"}, - warn_if_no_cache=False, - ) - or {} - ) + from yunohost.domain import _get_parent_domain_of + from yunohost.dns import _get_dns_zone_for_domain + httpreachable = ( Diagnoser.get_cached_report( "web", item={"domain": domain}, warn_if_no_cache=False @@ -866,16 +861,38 @@ def _check_domain_is_ready_for_ACME(domain): or {} ) - if not dnsrecords or not httpreachable: + parent_domain = _get_parent_domain_of(domain) + + dnsrecords = ( + Diagnoser.get_cached_report( + "dnsrecords", + item={"domain": parent_domain, "category": "basic"}, + warn_if_no_cache=False, + ) + or {} + ) + + base_dns_zone = _get_dns_zone_for_domain(domain) + record_name = domain.replace(f".{base_dns_zone}", "") if domain != base_dns_zone else "@" + A_record_status = dnsrecords.get("data").get(f"A:{record_name}") + AAAA_record_status = dnsrecords.get("data").get(f"AAAA:{record_name}") + + # Fallback to wildcard in case no result yet for the DNS name? + if not A_record_status: + A_record_status = dnsrecords.get("data").get(f"A:*") + if not AAAA_record_status: + AAAA_record_status = dnsrecords.get("data").get(f"AAAA:*") + + if not httpreachable or not dnsrecords.get("data") or (A_record_status, AAAA_record_status) == (None, None): 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 + # - 'MISSING' for IPv6 ain't critical for ACME + # - IPv4 can be None assuming there's at least an IPv6, and viveversa + # - (the case where both are None is checked before) + if not (A_record_status in [None, "OK"] and AAAA_record_status in [None, "OK", "MISSING"]): raise YunohostValidationError( "certmanager_domain_dns_ip_differs_from_public_ip", domain=domain ) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index fdceee26b..689950490 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -40,7 +40,6 @@ from yunohost.domain import ( domain_config_get, _get_domain_settings, _set_domain_settings, - _get_parent_domain_of, _list_subdomains_of, ) from yunohost.utils.dns import dig, is_yunohost_dyndns_domain, is_special_use_tld From 2e96cb28bb0bf79520f1e6e9d210f5a6ee1780b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Sun, 19 Sep 2021 21:12:58 +0000 Subject: [PATCH 0718/1155] Translated using Weblate (French) Currently translated at 99.8% (707 of 708 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index bd29ad774..29e6da673 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -675,5 +675,35 @@ "log_app_config_set": "Appliquer la configuration à l'application '{}'", "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" + "app_argument_password_help_optional": "Tapez un espace pour vider le mot de passe", + "domain_registrar_is_not_configured": "Le registrar n'est pas encore configuré pour le domaine {domain}.", + "domain_dns_push_not_applicable": "La fonction de configuration DNS automatique n'est pas applicable au domaine {domain}. Vous devez configurer manuellement vos enregistrements DNS en suivant la documentation sur https://yunohost.org/dns_config.", + "domain_dns_registrar_yunohost": "Ce domaine est nohost.me / nohost.st / ynh.fr et sa configuration DNS est donc automatiquement gérée par YunoHost sans autre configuration. (voir la commande 'yunohost dyndns update')", + "domain_dns_registrar_supported": "YunoHost a détecté automatiquement que ce domaine est géré par le registrar **{registrar}**. Si vous le souhaitez, YunoHost configurera automatiquement cette zone DNS, si vous lui fournissez les identifiants API appropriés. Vous pouvez trouver de la documentation sur la façon d'obtenir vos identifiants API sur cette page : https://yunohost.org/registar_api_{registrar}. (Vous pouvez également configurer manuellement vos enregistrements DNS en suivant la documentation sur https://yunohost.org/dns )", + "domain_config_features_disclaimer": "Jusqu'à présent, l'activation/désactivation des fonctionnalités de messagerie ou XMPP n'a d'impact que sur la configuration DNS recommandée et automatique, et non sur les configurations système !", + "domain_dns_push_managed_in_parent_domain": "La fonctionnalité de configuration DNS automatique est gérée dans le domaine parent {parent_domain}.", + "domain_dns_registrar_managed_in_parent_domain": "Ce domaine est un sous-domaine de {parent_domain_link}. La configuration du registrar DNS doit être gérée dans le panneau de configuration de {parent_domain}.", + "domain_dns_registrar_not_supported": "YunoHost n'a pas pu détecter automatiquement le bureau d'enregistrement gérant ce domaine. Vous devez configurer manuellement vos enregistrements DNS en suivant la documentation sur https://yunohost.org/dns.", + "domain_dns_registrar_experimental": "Jusqu'à présent, l'interface avec l'API de **{registrar}** n'a pas été correctement testée et revue par la communauté YunoHost. L'assistance est **très expérimentale** - soyez prudent !", + "domain_dns_push_failed_to_authenticate": "Échec de l'authentification sur l'API du bureau d'enregistrement pour le domaine « {domain} ». Très probablement les informations d'identification sont incorrectes ? (Error: {error})", + "domain_dns_push_failed_to_list": "Échec de la liste des enregistrements actuels à l'aide de l'API du registraire : {error}", + "domain_dns_push_already_up_to_date": "Dossiers déjà à jour.", + "domain_dns_pushing": "Transmission des enregistrements DNS...", + "domain_dns_push_record_failed": "Échec de l'enregistrement {action} {type}/{name} : {error}", + "domain_dns_push_success": "Enregistrements DNS mis à jour !", + "domain_dns_push_failed": "La mise à jour des enregistrements DNS a échoué.", + "domain_dns_push_partial_failure": "Enregistrements DNS partiellement mis à jour : certains avertissements/erreurs ont été signalés.", + "domain_config_mail_in": "Emails entrants", + "domain_config_mail_out": "Emails sortants", + "domain_config_xmpp": "Messagerie instantanée (XMPP)", + "domain_config_auth_token": "Jeton d'authentification", + "domain_config_auth_key": "Clé d'authentification", + "domain_config_auth_secret": "Secret d'authentification", + "domain_config_api_protocol": "Protocole API", + "domain_config_auth_entrypoint": "Point d'entrée API", + "domain_config_auth_application_key": "Clé d'application", + "domain_config_auth_application_secret": "Clé secrète de l'application", + "ldap_attribute_already_exists": "L'attribut LDAP '{attribute}' existe déjà avec la valeur '{value}'", + "log_domain_config_set": "Mettre à jour la configuration du domaine '{}'", + "log_domain_dns_push": "Pousser les enregistrements DNS pour le domaine '{}'" } From df0cdd483d33e5bab5e6f1c1603f690835a74425 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Sep 2021 22:20:29 +0200 Subject: [PATCH 0719/1155] Black --- data/hooks/diagnosis/12-dnsrecords.py | 7 ++++++- src/yunohost/certificate.py | 15 ++++++++++++--- src/yunohost/utils/dns.py | 4 +++- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 85f837af5..677a947a7 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -8,7 +8,12 @@ from publicsuffix import PublicSuffixList from moulinette.utils.process import check_output -from yunohost.utils.dns import dig, YNH_DYNDNS_DOMAINS, is_yunohost_dyndns_domain, is_special_use_tld +from yunohost.utils.dns import ( + dig, + YNH_DYNDNS_DOMAINS, + is_yunohost_dyndns_domain, + is_special_use_tld, +) from yunohost.diagnosis import Diagnoser from yunohost.domain import domain_list, _get_maindomain from yunohost.dns import _build_dns_conf, _get_dns_zone_for_domain diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 0df428c77..e960098f4 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -873,7 +873,9 @@ def _check_domain_is_ready_for_ACME(domain): ) base_dns_zone = _get_dns_zone_for_domain(domain) - record_name = domain.replace(f".{base_dns_zone}", "") if domain != base_dns_zone else "@" + record_name = ( + domain.replace(f".{base_dns_zone}", "") if domain != base_dns_zone else "@" + ) A_record_status = dnsrecords.get("data").get(f"A:{record_name}") AAAA_record_status = dnsrecords.get("data").get(f"AAAA:{record_name}") @@ -883,7 +885,11 @@ def _check_domain_is_ready_for_ACME(domain): if not AAAA_record_status: AAAA_record_status = dnsrecords.get("data").get(f"AAAA:*") - if not httpreachable or not dnsrecords.get("data") or (A_record_status, AAAA_record_status) == (None, None): + if ( + not httpreachable + or not dnsrecords.get("data") + or (A_record_status, AAAA_record_status) == (None, None) + ): raise YunohostValidationError( "certmanager_domain_not_diagnosed_yet", domain=domain ) @@ -892,7 +898,10 @@ def _check_domain_is_ready_for_ACME(domain): # - 'MISSING' for IPv6 ain't critical for ACME # - IPv4 can be None assuming there's at least an IPv6, and viveversa # - (the case where both are None is checked before) - if not (A_record_status in [None, "OK"] and AAAA_record_status in [None, "OK", "MISSING"]): + if not ( + A_record_status in [None, "OK"] + and AAAA_record_status in [None, "OK", "MISSING"] + ): raise YunohostValidationError( "certmanager_domain_dns_ip_differs_from_public_ip", domain=domain ) diff --git a/src/yunohost/utils/dns.py b/src/yunohost/utils/dns.py index 4ab17416d..ccb6c5406 100644 --- a/src/yunohost/utils/dns.py +++ b/src/yunohost/utils/dns.py @@ -34,7 +34,9 @@ external_resolvers_: List[str] = [] def is_yunohost_dyndns_domain(domain): - return any(domain.endswith(f".{dyndns_domain}") for dyndns_domain in YNH_DYNDNS_DOMAINS) + return any( + domain.endswith(f".{dyndns_domain}") for dyndns_domain in YNH_DYNDNS_DOMAINS + ) def is_special_use_tld(domain): From 55598151c0de151ccfb662249e5776f1b6c3cecf Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Sep 2021 22:29:28 +0200 Subject: [PATCH 0720/1155] Unhappy linter is unhappy --- src/yunohost/certificate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index e960098f4..fe350bf95 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -881,9 +881,9 @@ def _check_domain_is_ready_for_ACME(domain): # Fallback to wildcard in case no result yet for the DNS name? if not A_record_status: - A_record_status = dnsrecords.get("data").get(f"A:*") + A_record_status = dnsrecords.get("data").get("A:*") if not AAAA_record_status: - AAAA_record_status = dnsrecords.get("data").get(f"AAAA:*") + AAAA_record_status = dnsrecords.get("data").get("AAAA:*") if ( not httpreachable From 5c309ecc14edce88d3769fedec3bab70c530d619 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Sep 2021 22:30:44 +0200 Subject: [PATCH 0721/1155] Update changelog for 4.3.1 --- debian/changelog | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/debian/changelog b/debian/changelog index e6bd5180e..84a29ed70 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,18 @@ +yunohost (4.3.1) testing; urgency=low + + - [fix] diagnosis: new app diagnosis grep reporing comments as issues ([#1333](https://github.com/YunoHost/yunohost/pull/1333)) + - [enh] configpanel: Bind function for hotspot (79126809) + - [enh] cli: Rework/improve prompt mecanic ([#1338](https://github.com/YunoHost/yunohost/pull/1338)) + - [fix] dyndns update broke because of buggy dns record names (da1b9089) + - [enh] dns: general improvement for special-use TLD / ynh dyndns domains (17aafe6f) + - [fix] yunomdns: various fixes/improvements ([#1335](https://github.com/YunoHost/yunohost/pull/1335)) + - [fix] certs: Adapt ready_for_ACME check to the new dnsrecord result format... (d75c1a61) + - [i18n] Translations updated for French + + Thanks to all contributors <3 ! (Éric Gaspar, Félix Piédallu, Kayou, ljf, tituspijean) + + -- Alexandre Aubin Wed, 29 Sep 2021 22:22:42 +0200 + yunohost (4.3.0) testing; urgency=low - [users] Import/export users from/to CSV ([#1089](https://github.com/YunoHost/yunohost/pull/1089)) From 80661ddca9bd2b997fb5ad80dc3454c03e9f910f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Sep 2021 22:43:38 +0200 Subject: [PATCH 0722/1155] debian: Bump moulinette/ssowat version requirements --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 90bac0a0d..fe18b1de8 100644 --- a/debian/control +++ b/debian/control @@ -10,7 +10,7 @@ Package: yunohost Essential: yes Architecture: all Depends: ${python3:Depends}, ${misc:Depends} - , moulinette (>= 4.2), ssowat (>= 4.0) + , moulinette (>= 4.3), ssowat (>= 4.3) , python3-psutil, python3-requests, python3-dnspython, python3-openssl , python3-miniupnpc, python3-dbus, python3-jinja2 , python3-toml, python3-packaging, python3-publicsuffix, From f78167a99cb721b3dd18224179ffc94e36b5de6b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 30 Sep 2021 01:08:22 +0200 Subject: [PATCH 0723/1155] swag: Add test coverage badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9fc93740d..b256bb563 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@
[![Build status](https://shields.io/gitlab/pipeline/yunohost/yunohost/dev)](https://gitlab.com/yunohost/yunohost/-/pipelines) +![Test coverage](https://img.shields.io/gitlab/coverage/yunohost/yunohost/dev) [![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 8cdd5e43d743fcba0cb2ec1b53a454787eb5ffb6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 Oct 2021 02:18:35 +0200 Subject: [PATCH 0724/1155] Simplify / fix inconsistencies in app files kept after install/upgrade --- src/yunohost/app.py | 77 ++++++++++++--------------------------------- 1 file changed, 20 insertions(+), 57 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 56e4c344b..c111de405 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -79,6 +79,17 @@ re_app_instance_name = re.compile( r"^(?P[\w-]+?)(__(?P[1-9][0-9]*))?$" ) +APP_FILES_TO_COPY = [ + "manifest.json", + "manifest.toml", + "actions.json", + "actions.toml", + "config_panel.toml", + "scripts", + "conf", + "hooks", + "doc", +] def app_catalog(full=False, with_categories=False): """ @@ -710,38 +721,11 @@ def app_upgrade(app=[], url=None, file=None, force=False, no_safety_backup=False hook_add(app_instance_name, extracted_app_folder + "/hooks/" + hook) # Replace scripts and manifest and conf (if exists) - os.system( - 'rm -rf "%s/scripts" "%s/manifest.toml %s/manifest.json %s/conf"' - % ( - app_setting_path, - app_setting_path, - app_setting_path, - app_setting_path, - ) - ) - - if os.path.exists(os.path.join(extracted_app_folder, "manifest.json")): - os.system( - 'mv "%s/manifest.json" "%s/scripts" %s' - % (extracted_app_folder, extracted_app_folder, app_setting_path) - ) - if os.path.exists(os.path.join(extracted_app_folder, "manifest.toml")): - os.system( - 'mv "%s/manifest.toml" "%s/scripts" %s' - % (extracted_app_folder, extracted_app_folder, app_setting_path) - ) - - for file_to_copy in [ - "actions.json", - "actions.toml", - "config_panel.toml", - "conf", - ]: + # Move scripts and manifest to the right place + for file_to_copy in APP_FILES_TO_COPY: + os.system(f"rm -rf '{app_setting_path}/{file_to_copy}'") 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) - ) + os.system(f"cp -R '{extracted_app_folder}/{file_to_copy} {app_setting_path}'") # Clean and set permissions shutil.rmtree(extracted_app_folder) @@ -945,23 +929,9 @@ def app_install( _set_app_settings(app_instance_name, app_settings) # 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)) - if os.path.exists(os.path.join(extracted_app_folder, "manifest.toml")): - os.system("cp %s/manifest.toml %s" % (extracted_app_folder, app_setting_path)) - os.system("cp -R %s/scripts %s" % (extracted_app_folder, app_setting_path)) - - for file_to_copy in [ - "actions.json", - "actions.toml", - "config_panel.toml", - "conf", - ]: + for file_to_copy in APP_FILES_TO_COPY: 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) - ) + os.system(f"cp -R '{extracted_app_folder}/{file_to_copy}' '{app_setting_path}'" # Initialize the main permission for the app # The permission is initialized with no url associated, and with tile disabled @@ -1038,11 +1008,7 @@ def app_install( logger.warning(m18n.n("app_remove_after_failed_install")) # 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) - env_dict_remove["YNH_APP_MANIFEST_VERSION"] = manifest.get("version", "?") + env_dict_remove = _make_environment_for_app_script(app_instance_name) env_dict_remove["YNH_APP_BASEDIR"] = extracted_app_folder # Execute remove script @@ -1156,12 +1122,9 @@ def app_remove(operation_logger, app, purge=False): env_dict = {} app_id, app_instance_nb = _parse_app_instance_name(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", "?") - env_dict["YNH_APP_PURGE"] = str(1 if purge else 0) + env_dict = _make_environment_for_app_script(app) env_dict["YNH_APP_BASEDIR"] = tmp_workdir_for_app + env_dict["YNH_APP_PURGE"] = str(1 if purge else 0) operation_logger.extra.update({"env": env_dict}) operation_logger.flush() From a1ff2ced37a3ef01966e359dfeebe62114f28673 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 Oct 2021 02:21:31 +0200 Subject: [PATCH 0725/1155] Clarify code that extract app from folder/gitrepo + misc other small refactors --- src/yunohost/app.py | 364 ++++++++++++++++++++------------------------ 1 file changed, 163 insertions(+), 201 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index c111de405..88deb5b6b 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -566,22 +566,22 @@ def app_upgrade(app=[], url=None, file=None, force=False, no_safety_backup=False 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] - ) + new_app_src = file[app_instance_name] elif file: - manifest, extracted_app_folder = _extract_app_from_file(file) + new_app_src = file elif url: - manifest, extracted_app_folder = _fetch_app_from_git(url) + new_app_src = url elif app_dict["upgradable"] == "url_required": logger.warning(m18n.n("custom_app_url_required", app=app_instance_name)) continue elif app_dict["upgradable"] == "yes" or force: - manifest, extracted_app_folder = _fetch_app_from_git(app_instance_name) + new_app_src = app_dict["id"] else: logger.success(m18n.n("app_already_up_to_date", app=app_instance_name)) continue + manifest, extracted_app_folder = _extract_app(new_app_src) + # Manage upgrade type and avoid any upgrade if there is nothing to do upgrade_type = "UNKNOWN" # Get current_version and new version @@ -748,12 +748,7 @@ 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") + manifest, extracted_app_folder = _extract_app(app) shutil.rmtree(extracted_app_folder) @@ -796,19 +791,28 @@ def app_install( ) from yunohost.regenconf import manually_modified_files - def confirm_install(confirm): + # Check if disk space available + if free_space_in_directory("/") <= 512 * 1000 * 1000: + raise YunohostValidationError("disk_space_not_sufficient_install") + + def confirm_install(app): + # 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 Moulinette.interface.type == "api": + if force or Moulinette.interface.type == "api": + return + + quality = app_quality(app) + if quality == "success": return # i18n: confirm_app_install_warning # i18n: confirm_app_install_danger # i18n: confirm_app_install_thirdparty - if confirm in ["danger", "thirdparty"]: + if quality in ["danger", "thirdparty"]: answer = Moulinette.prompt( - m18n.n("confirm_app_install_" + confirm, answers="Yes, I understand"), + m18n.n("confirm_app_install_" + quality, answers="Yes, I understand"), color="red", ) if answer != "Yes, I understand": @@ -816,51 +820,13 @@ def app_install( else: answer = Moulinette.prompt( - m18n.n("confirm_app_install_" + confirm, answers="Y/N"), color="yellow" + m18n.n("confirm_app_install_" + quality, answers="Y/N"), color="yellow" ) if answer.upper() != "Y": raise YunohostError("aborting") - raw_app_list = _load_apps_catalog()["apps"] - - 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: - 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", "") - else: - # FIXME : watdo if '@' in app ? - app_name_to_test = None - - 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 >= 5: - confirm = None - elif isinstance(level, int) and level > 0: - confirm = "warning" - else: - confirm = "thirdparty" - - confirm_install(confirm) - - manifest, extracted_app_folder = _fetch_app_from_git(app) - elif os.path.exists(app): - confirm_install("thirdparty") - manifest, extracted_app_folder = _extract_app_from_file(app) - else: - raise YunohostValidationError("app_unknown") - - # Check if disk space available - if free_space_in_directory("/") <= 512 * 1000 * 1000: - raise YunohostValidationError("disk_space_not_sufficient_install") + confirm_install(app) + manifest, extracted_app_folder = _extract_app(app) # Check ID if "id" not in manifest or "__" in manifest["id"] or "." in manifest["id"]: @@ -874,7 +840,7 @@ def app_install( _assert_system_is_sane_for_app(manifest, "pre") # Check if app can be forked - instance_number = _installed_instance_number(app_id, last=True) + 1 + instance_number = _next_instance_number_for_app(app_id) if instance_number > 1: if "multi_instance" not in manifest or not is_true(manifest["multi_instance"]): raise YunohostValidationError("app_already_installed", app=app_id) @@ -1914,55 +1880,6 @@ def _set_app_settings(app_id, settings): yaml.safe_dump(settings, f, default_flow_style=False) -def _extract_app_from_file(path): - """ - Unzip / untar / copy application tarball or directory to a tmp work directory - - Keyword arguments: - path -- Path of the tarball or directory - """ - logger.debug(m18n.n("extracting")) - - path = os.path.abspath(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" - ) - elif ".tar" in path: - 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] != "/": - path = path + "/" - extract_result = os.system(f"cp -a '{path}' {extracted_app_folder}") - else: - extract_result = 1 - - if extract_result != 0: - raise YunohostError("app_extraction_failed") - - try: - if len(os.listdir(extracted_app_folder)) == 1: - for folder in os.listdir(extracted_app_folder): - extracted_app_folder = extracted_app_folder + "/" + folder - manifest = _get_manifest_of_app(extracted_app_folder) - manifest["lastUpdate"] = int(time.time()) - except IOError: - raise YunohostError("app_install_files_invalid") - except ValueError as e: - raise YunohostError("app_manifest_invalid", error=e) - - logger.debug(m18n.n("done")) - - manifest["remote"] = {"type": "file", "path": path} - return manifest, extracted_app_folder - - def _get_manifest_of_app(path): "Get app manifest stored in json or in toml" @@ -2158,143 +2075,169 @@ def _set_default_ask_questions(arguments): return arguments -def _get_git_last_commit_hash(repository, reference="HEAD"): +def _app_quality(app: str) -> str: """ - Attempt to retrieve the last commit hash of a git repository - - Keyword arguments: - repository -- The URL or path of the repository - + app may in fact be an app name, an url, or a path """ - try: - cmd = "git ls-remote --exit-code {0} {1} | awk '{{print $1}}'".format( - repository, reference - ) - commit = check_output(cmd) - except subprocess.CalledProcessError: - logger.error("unable to get last commit from %s", repository) - raise ValueError("Unable to get last commit with git") + + raw_app_catalog = _load_apps_catalog()["apps"] + if app in raw_app_catalog 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: + 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", "") + else: + # FIXME : watdo if '@' in app ? + app_name_to_test = None + + 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) + if state in ["working", "validated"]: + if isinstance(level, int) and level >= 5: + return "success" + elif isinstance(level, int) and level > 0: + return "warning" + return "danger" + else: + return "thirdparty" + + elif os.path.exists(app): + return "thirdparty" else: - return commit.strip() + raise YunohostValidationError("app_unknown") -def _fetch_app_from_git(app): +def _extract_app(src: str) -> Tuple[Dict, str]: """ - Unzip or untar application tarball to a tmp directory - - Keyword arguments: - app -- App_id or git repo URL + src may be an app name, an url, or a path """ - # Extract URL, branch and revision to download - if ("@" in app) or ("http://" in app) or ("https://" in app): - url = app - branch = "master" - if "/tree/" in url: - url, branch = url.split("/tree/", 1) - revision = "HEAD" - else: - app_dict = _load_apps_catalog()["apps"] + raw_app_catalog = _load_apps_catalog()["apps"] - app_id, _ = _parse_app_instance_name(app) - - if app_id not in app_dict: - raise YunohostValidationError("app_unknown") - elif "git" not in app_dict[app_id]: + # App is an appname in the catalog + if src in raw_app_catalog: + if "git" not in raw_app_catalog[src]: raise YunohostValidationError("app_unsupported_remote_type") - app_info = app_dict[app_id] + app_info = raw_app_catalog[src] url = app_info["git"]["url"] branch = app_info["git"]["branch"] revision = str(app_info["git"]["revision"]) + return _extract_app_from_gitrepo(url, branch, revision, app_info) + # App is a git repo url + elif ("@" in src) or ("http://" in src) or ("https://" in src): + url = src + branch = "master" + revision = "HEAD" + if "/tree/" in url: + url, branch = url.split("/tree/", 1) + return _extract_app_from_gitrepo(url, branch, revision, {}) + # App is a local folder + elif os.path.exists(src): + return _extract_app_from_folder(src) + else: + raise YunohostValidationError("app_unknown") + + +def _extract_app_from_folder(path: str) -> Tuple[Dict, str]: + """ + Unzip / untar / copy application tarball or directory to a tmp work directory + + Keyword arguments: + path -- Path of the tarball or directory + """ + logger.debug(m18n.n("extracting")) + + path = os.path.abspath(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" + ) + elif ".tar" in path: + 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] != "/": + path = path + "/" + extract_result = os.system(f"cp -a '{path}' {extracted_app_folder}") + else: + extract_result = 1 + + if extract_result != 0: + raise YunohostError("app_extraction_failed") + + try: + if len(os.listdir(extracted_app_folder)) == 1: + for folder in os.listdir(extracted_app_folder): + extracted_app_folder = extracted_app_folder + "/" + folder + except IOError: + raise YunohostError("app_install_files_invalid") + + manifest = _get_manifest_of_app(extracted_app_folder) + manifest["lastUpdate"] = int(time.time()) + + logger.debug(m18n.n("done")) + + manifest["remote"] = {"type": "file", "path": path} + return manifest, extracted_app_folder + + +def _extract_app_from_gitrepo(url: str, branch: str, revision: str, app_info={}: Dict) -> Tuple[Dict, str]: + logger.debug(m18n.n("downloading")) + extracted_app_folder = _make_tmp_workdir_for_app() + # Download only this commit try: # We don't use git clone because, git clone can't download # a specific revision only + ref = branch if revision == "HEAD" else revision run_commands([["git", "init", extracted_app_folder]], shell=False) run_commands( [ ["git", "remote", "add", "origin", url], - [ - "git", - "fetch", - "--depth=1", - "origin", - branch if revision == "HEAD" else revision, - ], + ["git", "fetch", "--depth=1", "origin", ref], ["git", "reset", "--hard", "FETCH_HEAD"], ], cwd=extracted_app_folder, shell=False, ) - manifest = _get_manifest_of_app(extracted_app_folder) except subprocess.CalledProcessError: raise YunohostError("app_sources_fetch_failed") - except ValueError as e: - raise YunohostError("app_manifest_invalid", error=e) else: logger.debug(m18n.n("done")) + manifest = _get_manifest_of_app(extracted_app_folder) + # Store remote repository info into the returned manifest manifest["remote"] = {"type": "git", "url": url, "branch": branch} if revision == "HEAD": try: - manifest["remote"]["revision"] = _get_git_last_commit_hash(url, branch) + # Get git last commit hash + cmd = f"git ls-remote --exit-code {url} {branch} | awk '{{print $1}}'" + manifest["remote"]["revision"] = check_output(cmd) except Exception as e: - logger.debug("cannot get last commit hash because: %s ", e) + logger.warning("cannot get last commit hash because: %s ", e) else: manifest["remote"]["revision"] = revision - manifest["lastUpdate"] = app_info["lastUpdate"] + manifest["lastUpdate"] = app_info.get("lastUpdate") return manifest, extracted_app_folder -def _installed_instance_number(app, last=False): - """ - Check if application is installed and return instance number - - Keyword arguments: - app -- id of App to check - last -- Return only last instance number - - Returns: - Number of last installed instance | List or instances - - """ - if last: - number = 0 - try: - installed_apps = os.listdir(APPS_SETTING_PATH) - except OSError: - os.makedirs(APPS_SETTING_PATH) - return 0 - - for installed_app in installed_apps: - if number == 0 and app == installed_app: - number = 1 - elif "__" in installed_app: - if app == installed_app[: installed_app.index("__")]: - if int(installed_app[installed_app.index("__") + 2 :]) > number: - number = int(installed_app[installed_app.index("__") + 2 :]) - - return number - - else: - instance_number_list = [] - instances_dict = app_map(app=app, raw=True) - for key, domain in instances_dict.items(): - for key, path in domain.items(): - instance_number_list.append(path["instance"]) - - return sorted(instance_number_list) - - -def _is_installed(app): +def _is_installed(app: str) -> bool: """ Check if application is installed @@ -2308,18 +2251,18 @@ def _is_installed(app): return os.path.isdir(APPS_SETTING_PATH + app) -def _assert_is_installed(app): +def _assert_is_installed(app: str) -> None: if not _is_installed(app): raise YunohostValidationError( "app_not_installed", app=app, all_apps=_get_all_installed_apps_id() ) -def _installed_apps(): +def _installed_apps() -> List[str]: return os.listdir(APPS_SETTING_PATH) -def _check_manifest_requirements(manifest, app_instance_name): +def _check_manifest_requirements(manifest: Dict, app: str): """Check if required packages are met from the manifest""" packaging_format = int(manifest.get("packaging_format", 0)) @@ -2331,7 +2274,7 @@ def _check_manifest_requirements(manifest, app_instance_name): if not requirements: return - logger.debug(m18n.n("app_requirements_checking", app=app_instance_name)) + logger.debug(m18n.n("app_requirements_checking", app=app)) # Iterate over requirements for pkgname, spec in requirements.items(): @@ -2342,7 +2285,7 @@ def _check_manifest_requirements(manifest, app_instance_name): pkgname=pkgname, version=version, spec=spec, - app=app_instance_name, + app=app, ) @@ -2513,6 +2456,25 @@ def _parse_app_instance_name(app_instance_name): return (appid, app_instance_nb) +def _next_instance_number_for_app(app): + + # Get list of sibling apps, such as {app}, {app}__2, {app}__4 + apps = _installed_apps() + sibling_app_ids = [a for a in apps if a == app or a.startswith(f"{app}__")] + + # Find the list of ids, such as [1, 2, 4] + sibling_ids = [_parse_app_instance_name(a)[1] for a in sibling_app_ids] + + # Find the first 'i' that's not in the sibling_ids list already + i = 1 + while True: + if i not in sibling_ids: + return i + else: + i += 1 + + + # # ############################### # # Applications list management # From f84d8618bcc01bcd6f569723b7db8bc9e146a16b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 Oct 2021 02:22:23 +0200 Subject: [PATCH 0726/1155] Offload legacy app patching stuff to utils/legacy.py to limit the size of app.py --- src/yunohost/app.py | 212 +---------------- src/yunohost/backup.py | 6 +- .../0016_php70_to_php73_pools.py | 3 +- src/yunohost/utils/legacy.py | 217 +++++++++++++++++- 4 files changed, 222 insertions(+), 216 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 88deb5b6b..011c953e0 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -534,6 +534,7 @@ def app_upgrade(app=[], url=None, file=None, force=False, no_safety_backup=False ) from yunohost.permission import permission_sync_to_user from yunohost.regenconf import manually_modified_files + from yunohost.utils.legacy import _patch_legacy_php_versions, _patch_legacy_helpers apps = app # Check if disk space available @@ -790,6 +791,7 @@ def app_install( permission_sync_to_user, ) from yunohost.regenconf import manually_modified_files + from yunohost.utils.legacy import _patch_legacy_php_versions, _patch_legacy_helpers # Check if disk space available if free_space_in_directory("/") <= 512 * 1000 * 1000: @@ -2769,213 +2771,3 @@ def _assert_system_is_sane_for_app(manifest, when): raise YunohostValidationError("dpkg_is_broken") elif when == "post": raise YunohostError("this_action_broke_dpkg") - - -LEGACY_PHP_VERSION_REPLACEMENTS = [ - ("/etc/php5", "/etc/php/7.3"), - ("/etc/php/7.0", "/etc/php/7.3"), - ("/var/run/php5-fpm", "/var/run/php/php7.3-fpm"), - ("/var/run/php/php7.0-fpm", "/var/run/php/php7.3-fpm"), - ("php5", "php7.3"), - ("php7.0", "php7.3"), - ( - 'phpversion="${phpversion:-7.0}"', - 'phpversion="${phpversion:-7.3}"', - ), # Many helpers like the composer ones use 7.0 by default ... - ( - '"$phpversion" == "7.0"', - '$(bc <<< "$phpversion >= 7.3") -eq 1', - ), # patch ynh_install_php to refuse installing/removing php <= 7.3 -] - - -def _patch_legacy_php_versions(app_folder): - - files_to_patch = [] - files_to_patch.extend(glob.glob("%s/conf/*" % app_folder)) - files_to_patch.extend(glob.glob("%s/scripts/*" % app_folder)) - files_to_patch.extend(glob.glob("%s/scripts/*/*" % app_folder)) - files_to_patch.extend(glob.glob("%s/scripts/.*" % app_folder)) - files_to_patch.append("%s/manifest.json" % app_folder) - files_to_patch.append("%s/manifest.toml" % app_folder) - - for filename in files_to_patch: - - # Ignore non-regular files - if not os.path.isfile(filename): - continue - - c = ( - "sed -i " - + "".join( - "-e 's@{pattern}@{replace}@g' ".format(pattern=p, replace=r) - for p, r in LEGACY_PHP_VERSION_REPLACEMENTS - ) - + "%s" % filename - ) - os.system(c) - - -def _patch_legacy_php_versions_in_settings(app_folder): - - settings = read_yaml(os.path.join(app_folder, "settings.yml")) - - if settings.get("fpm_config_dir") == "/etc/php/7.0/fpm": - settings["fpm_config_dir"] = "/etc/php/7.3/fpm" - if settings.get("fpm_service") == "php7.0-fpm": - settings["fpm_service"] = "php7.3-fpm" - if settings.get("phpversion") == "7.0": - settings["phpversion"] = "7.3" - - # We delete these checksums otherwise the file will appear as manually modified - list_to_remove = ["checksum__etc_php_7.0_fpm_pool", "checksum__etc_nginx_conf.d"] - settings = { - k: v - for k, v in settings.items() - if not any(k.startswith(to_remove) for to_remove in list_to_remove) - } - - write_to_yaml(app_folder + "/settings.yml", settings) - - -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": { - "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": { - "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": {"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": { - "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, - }, - # Old $1, $2 in backup/restore scripts... - "app=$2": { - "only_for": ["scripts/backup", "scripts/restore"], - "pattern": r"app=\$2", - "replace": r"app=$YNH_APP_INSTANCE_NAME", - "important": True, - }, - # Old $1, $2 in backup/restore scripts... - "backup_dir=$1": { - "only_for": ["scripts/backup", "scripts/restore"], - "pattern": r"backup_dir=\$1", - "replace": r"backup_dir=.", - "important": True, - }, - # Old $1, $2 in backup/restore scripts... - "restore_dir=$1": { - "only_for": ["scripts/restore"], - "pattern": r"restore_dir=\$1", - "replace": r"restore_dir=.", - "important": True, - }, - # Old $1, $2 in install scripts... - # We ain't patching that shit because it ain't trivial to patch all args... - "domain=$1": {"only_for": ["scripts/install"], "important": True}, - } - - 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: - - # Ignore non-regular files - if not os.path.isfile(filename): - continue - - try: - content = read_file(filename) - except MoulinetteError: - continue - - replaced_stuff = False - show_warning = False - - for helper, infos in stuff_to_replace.items(): - - # Ignore if not relevant for this file - if infos.get("only_for") and not any( - filename.endswith(f) for f in infos["only_for"] - ): - continue - - # If helper is used, attempt to patch the file - 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 helper 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 and infos["important"]: - 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, - ) - - 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) - - if show_warning: - # And complain about those damn deprecated helpers - logger.error( - r"/!\ 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 a47fba5f7..b650cb3da 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -49,10 +49,6 @@ from yunohost.app import ( app_info, _is_installed, _make_environment_for_app_script, - _patch_legacy_helpers, - _patch_legacy_php_versions, - _patch_legacy_php_versions_in_settings, - LEGACY_PHP_VERSION_REPLACEMENTS, _make_tmp_workdir_for_app, ) from yunohost.hook import ( @@ -1190,6 +1186,7 @@ class RestoreManager: """ Apply dirty patch to redirect php5 and php7.0 files to php7.3 """ + from yunohost.utils.legacy import LEGACY_PHP_VERSION_REPLACEMENTS backup_csv = os.path.join(self.work_dir, "backup.csv") @@ -1351,6 +1348,7 @@ class RestoreManager: app_instance_name -- (string) The app name to restore (no app with this name should be already install) """ + from yunohost.utils.legacy import _patch_legacy_php_versions, _patch_legacy_php_versions_in_settings, _patch_legacy_helpers from yunohost.user import user_group_list from yunohost.permission import ( permission_create, diff --git a/src/yunohost/data_migrations/0016_php70_to_php73_pools.py b/src/yunohost/data_migrations/0016_php70_to_php73_pools.py index 6b424f211..fed96c9c8 100644 --- a/src/yunohost/data_migrations/0016_php70_to_php73_pools.py +++ b/src/yunohost/data_migrations/0016_php70_to_php73_pools.py @@ -4,7 +4,8 @@ from shutil import copy2 from moulinette.utils.log import getActionLogger -from yunohost.app import _is_installed, _patch_legacy_php_versions_in_settings +from yunohost.app import _is_installed +from yunohost.utils.legacy import _patch_legacy_php_versions_in_settings from yunohost.tools import Migration from yunohost.service import _run_service_command diff --git a/src/yunohost/utils/legacy.py b/src/yunohost/utils/legacy.py index eb92dd71f..7ae98bed8 100644 --- a/src/yunohost/utils/legacy.py +++ b/src/yunohost/utils/legacy.py @@ -1,7 +1,10 @@ import os +import re +import glob from moulinette import m18n +from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger -from moulinette.utils.filesystem import write_to_json, read_yaml +from moulinette.utils.filesystem import read_file, write_to_file, write_to_json, write_to_yaml, read_yaml from yunohost.user import user_list from yunohost.app import ( @@ -14,6 +17,8 @@ from yunohost.permission import ( user_permission_update, permission_sync_to_user, ) +from yunohost.utils.error import YunohostValidationError + logger = getActionLogger("yunohost.legacy") @@ -237,3 +242,213 @@ def translate_legacy_rules_in_ssowant_conf_json_persistent(): logger.warning( "YunoHost automatically translated some legacy rules in /etc/ssowat/conf.json.persistent to match the new permission system" ) + + +LEGACY_PHP_VERSION_REPLACEMENTS = [ + ("/etc/php5", "/etc/php/7.3"), + ("/etc/php/7.0", "/etc/php/7.3"), + ("/var/run/php5-fpm", "/var/run/php/php7.3-fpm"), + ("/var/run/php/php7.0-fpm", "/var/run/php/php7.3-fpm"), + ("php5", "php7.3"), + ("php7.0", "php7.3"), + ( + 'phpversion="${phpversion:-7.0}"', + 'phpversion="${phpversion:-7.3}"', + ), # Many helpers like the composer ones use 7.0 by default ... + ( + '"$phpversion" == "7.0"', + '$(bc <<< "$phpversion >= 7.3") -eq 1', + ), # patch ynh_install_php to refuse installing/removing php <= 7.3 +] + + +def _patch_legacy_php_versions(app_folder): + + files_to_patch = [] + files_to_patch.extend(glob.glob("%s/conf/*" % app_folder)) + files_to_patch.extend(glob.glob("%s/scripts/*" % app_folder)) + files_to_patch.extend(glob.glob("%s/scripts/*/*" % app_folder)) + files_to_patch.extend(glob.glob("%s/scripts/.*" % app_folder)) + files_to_patch.append("%s/manifest.json" % app_folder) + files_to_patch.append("%s/manifest.toml" % app_folder) + + for filename in files_to_patch: + + # Ignore non-regular files + if not os.path.isfile(filename): + continue + + c = ( + "sed -i " + + "".join( + "-e 's@{pattern}@{replace}@g' ".format(pattern=p, replace=r) + for p, r in LEGACY_PHP_VERSION_REPLACEMENTS + ) + + "%s" % filename + ) + os.system(c) + + +def _patch_legacy_php_versions_in_settings(app_folder): + + settings = read_yaml(os.path.join(app_folder, "settings.yml")) + + if settings.get("fpm_config_dir") == "/etc/php/7.0/fpm": + settings["fpm_config_dir"] = "/etc/php/7.3/fpm" + if settings.get("fpm_service") == "php7.0-fpm": + settings["fpm_service"] = "php7.3-fpm" + if settings.get("phpversion") == "7.0": + settings["phpversion"] = "7.3" + + # We delete these checksums otherwise the file will appear as manually modified + list_to_remove = ["checksum__etc_php_7.0_fpm_pool", "checksum__etc_nginx_conf.d"] + settings = { + k: v + for k, v in settings.items() + if not any(k.startswith(to_remove) for to_remove in list_to_remove) + } + + write_to_yaml(app_folder + "/settings.yml", settings) + + +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": { + "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": { + "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": {"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": { + "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, + }, + # Old $1, $2 in backup/restore scripts... + "app=$2": { + "only_for": ["scripts/backup", "scripts/restore"], + "pattern": r"app=\$2", + "replace": r"app=$YNH_APP_INSTANCE_NAME", + "important": True, + }, + # Old $1, $2 in backup/restore scripts... + "backup_dir=$1": { + "only_for": ["scripts/backup", "scripts/restore"], + "pattern": r"backup_dir=\$1", + "replace": r"backup_dir=.", + "important": True, + }, + # Old $1, $2 in backup/restore scripts... + "restore_dir=$1": { + "only_for": ["scripts/restore"], + "pattern": r"restore_dir=\$1", + "replace": r"restore_dir=.", + "important": True, + }, + # Old $1, $2 in install scripts... + # We ain't patching that shit because it ain't trivial to patch all args... + "domain=$1": {"only_for": ["scripts/install"], "important": True}, + } + + 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: + + # Ignore non-regular files + if not os.path.isfile(filename): + continue + + try: + content = read_file(filename) + except MoulinetteError: + continue + + replaced_stuff = False + show_warning = False + + for helper, infos in stuff_to_replace.items(): + + # Ignore if not relevant for this file + if infos.get("only_for") and not any( + filename.endswith(f) for f in infos["only_for"] + ): + continue + + # If helper is used, attempt to patch the file + 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 helper 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 and infos["important"]: + 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, + ) + + 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) + + if show_warning: + # And complain about those damn deprecated helpers + logger.error( + r"/!\ 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 206b430a9f66c698821f5fc56d1cfaa967e1361c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 Oct 2021 02:50:30 +0200 Subject: [PATCH 0727/1155] Offload appcatalog stuff to a separate file to limit the size of app.py --- .gitlab/ci/test.gitlab-ci.yml | 4 +- src/yunohost/app.py | 296 ++---------------- ...est_appscatalog.py => test_app_catalog.py} | 2 +- src/yunohost/tools.py | 4 +- 4 files changed, 31 insertions(+), 275 deletions(-) rename src/yunohost/tests/{test_appscatalog.py => test_app_catalog.py} (99%) diff --git a/.gitlab/ci/test.gitlab-ci.yml b/.gitlab/ci/test.gitlab-ci.yml index 6a422ea22..1aad46fbe 100644 --- a/.gitlab/ci/test.gitlab-ci.yml +++ b/.gitlab/ci/test.gitlab-ci.yml @@ -113,10 +113,10 @@ test-apps: test-appscatalog: extends: .test-stage script: - - python3 -m pytest src/yunohost/tests/test_appscatalog.py + - python3 -m pytest src/yunohost/tests/test_app_catalog.py only: changes: - - src/yunohost/app.py + - src/yunohost/app_calalog.py test-appurl: extends: .test-stage diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 011c953e0..7cddca3e9 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -31,15 +31,12 @@ import yaml import time import re import subprocess -import glob import tempfile from collections import OrderedDict -from typing import List +from typing import List, Tuple, Dict from moulinette import Moulinette, m18n -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 moulinette.utils.filesystem import ( read_file, @@ -48,8 +45,6 @@ from moulinette.utils.filesystem import ( read_yaml, write_to_file, write_to_json, - write_to_yaml, - mkdir, ) from yunohost.utils import packages @@ -64,17 +59,13 @@ 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 +from yunohost.app_catalog import app_catalog, app_search, _load_apps_catalog, app_fetchlist # noqa logger = getActionLogger("yunohost.app") APPS_SETTING_PATH = "/etc/yunohost/apps/" 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" -APPS_CATALOG_API_VERSION = 2 -APPS_CATALOG_DEFAULT_URL = "https://app.yunohost.org/default" - re_app_instance_name = re.compile( r"^(?P[\w-]+?)(__(?P[1-9][0-9]*))?$" ) @@ -91,80 +82,6 @@ APP_FILES_TO_COPY = [ "doc", ] -def app_catalog(full=False, with_categories=False): - """ - Return a dict of apps available to installation from Yunohost's app catalog - """ - - # Get app list from catalog cache - catalog = _load_apps_catalog() - installed_apps = set(_installed_apps()) - - # 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["apps"][app] = { - "description": infos["manifest"]["description"], - "level": infos["level"], - } - else: - infos["manifest"]["arguments"] = _set_default_ask_questions( - infos["manifest"].get("arguments", {}) - ) - - # 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"]) - for subtags in category.get("subtags", []): - subtags["title"] = _value_for_locale(subtags["title"]) - - 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_search(string): - """ - Return a dict of apps whose description or name match the search string - """ - - # Retrieve a simple dict listing all apps - 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 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] - - return matching_apps - - -# 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(target="apps") - def app_list(full=False, installed=False, filter=None): """ @@ -1728,22 +1645,6 @@ ynh_app_config_run $1 return values -def _get_all_installed_apps_id(): - """ - Return something like: - ' * app1 - * app2 - * ...' - """ - - all_apps_ids = sorted(_installed_apps()) - - all_apps_ids_formatted = "\n * ".join(all_apps_ids) - all_apps_ids_formatted = "\n * " + all_apps_ids_formatted - - return all_apps_ids_formatted - - def _get_app_actions(app_id): "Get app config panel stored in json or in toml" actions_toml_path = os.path.join(APPS_SETTING_PATH, app_id, "actions.toml") @@ -2239,6 +2140,13 @@ def _extract_app_from_gitrepo(url: str, branch: str, revision: str, app_info={}: return manifest, extracted_app_folder +# +# ############################### # +# Small utilities # +# ############################### # +# + + def _is_installed(app: str) -> bool: """ Check if application is installed @@ -2264,6 +2172,22 @@ def _installed_apps() -> List[str]: return os.listdir(APPS_SETTING_PATH) +def _get_all_installed_apps_id(): + """ + Return something like: + ' * app1 + * app2 + * ...' + """ + + all_apps_ids = sorted(_installed_apps()) + + all_apps_ids_formatted = "\n * ".join(all_apps_ids) + all_apps_ids_formatted = "\n * " + all_apps_ids_formatted + + return all_apps_ids_formatted + + def _check_manifest_requirements(manifest: Dict, app: str): """Check if required packages are met from the manifest""" @@ -2476,176 +2400,6 @@ def _next_instance_number_for_app(app): i += 1 - -# -# ############################### # -# Applications list management # -# ############################### # -# - - -def _initialize_apps_catalog_system(): - """ - This function is meant to intialize the apps_catalog system with YunoHost's default app catalog. - """ - - default_apps_catalog_list = [{"id": "default", "url": APPS_CATALOG_DEFAULT_URL}] - - try: - logger.debug( - "Initializing apps catalog system with YunoHost's default app list" - ) - write_to_yaml(APPS_CATALOG_CONF, default_apps_catalog_list) - except Exception as e: - raise YunohostError( - "Could not initialize the apps catalog system... : %s" % str(e) - ) - - logger.success(m18n.n("apps_catalog_init_success")) - - -def _read_apps_catalog_list(): - """ - Read the json corresponding to the list of apps catalogs - """ - - try: - 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 apps_catalog list ... : %s" % str(e)) - - -def _actual_apps_catalog_api_url(base_url): - - return "{base_url}/v{version}/apps.json".format( - base_url=base_url, version=APPS_CATALOG_API_VERSION - ) - - -def _update_apps_catalog(): - """ - Fetches the json for each apps_catalog and update the cache - - apps_catalog_list is for example : - [ {"id": "default", "url": "https://app.yunohost.org/default/"} ] - - 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 - """ - - apps_catalog_list = _read_apps_catalog_list() - - logger.info(m18n.n("apps_catalog_updating")) - - # Create cache folder if needed - 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 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: - apps_catalog_content = download_json(actual_api_url) - except Exception as e: - raise YunohostError( - "apps_catalog_failed_to_download", - apps_catalog=apps_catalog_id, - error=str(e), - ) - - # Remember the apps_catalog api version for later - apps_catalog_content["from_api_version"] = APPS_CATALOG_API_VERSION - - # 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, apps_catalog_content) - except Exception as e: - raise YunohostError( - "Unable to write cache data for %s apps_catalog : %s" - % (apps_catalog_id, str(e)) - ) - - logger.success(m18n.n("apps_catalog_update_success")) - - -def _load_apps_catalog(): - """ - Read all the apps catalog cache files and build a single dict (merged_catalog) - corresponding to all known apps and categories - """ - - merged_catalog = {"apps": {}, "categories": []} - - for apps_catalog_id in [L["id"] for L in _read_apps_catalog_list()]: - - # 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: - apps_catalog_content = ( - read_json(cache_file) if os.path.exists(cache_file) else None - ) - except Exception as e: - raise YunohostError( - "Unable to read cache for apps_catalog %s : %s" % (cache_file, e), - raw_msg=True, - ) - - # 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 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 apps_catalog_content["from_api_version"] - - # Add apps from this catalog to the output - 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 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 - merged_catalog["apps"][app] = info - - # Annnnd categories - merged_catalog["categories"] += apps_catalog_content["categories"] - - return merged_catalog - - -# -# ############################### # -# Small utilities # -# ############################### # -# - - def _make_tmp_workdir_for_app(app=None): # Create parent dir if it doesn't exists yet diff --git a/src/yunohost/tests/test_appscatalog.py b/src/yunohost/tests/test_app_catalog.py similarity index 99% rename from src/yunohost/tests/test_appscatalog.py rename to src/yunohost/tests/test_app_catalog.py index a2619a660..8423b868e 100644 --- a/src/yunohost/tests/test_appscatalog.py +++ b/src/yunohost/tests/test_app_catalog.py @@ -9,7 +9,7 @@ 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 ( +from yunohost.app_catalog import ( _initialize_apps_catalog_system, _read_apps_catalog_list, _update_apps_catalog, diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index ed8c04153..4cdd62d8f 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -37,10 +37,12 @@ from moulinette.utils.process import check_output, call_async_output from moulinette.utils.filesystem import read_yaml, write_to_yaml from yunohost.app import ( - _update_apps_catalog, app_info, app_upgrade, +) +from yunohost.app_catalog import ( _initialize_apps_catalog_system, + _update_apps_catalog, ) from yunohost.domain import domain_add from yunohost.dyndns import _dyndns_available, _dyndns_provides From 1e046dcd37a2f6d0365be4003e86622e58dd285d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 Oct 2021 02:50:38 +0200 Subject: [PATCH 0728/1155] Misc typoz --- src/yunohost/app.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 7cddca3e9..8dcb5aef3 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -609,7 +609,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( @@ -664,8 +664,6 @@ def app_upgrade(app=[], url=None, file=None, force=False, no_safety_backup=False def app_manifest(app): - raw_app_list = _load_apps_catalog()["apps"] - manifest, extracted_app_folder = _extract_app(app) shutil.rmtree(extracted_app_folder) @@ -721,7 +719,7 @@ def app_install( if force or Moulinette.interface.type == "api": return - quality = app_quality(app) + quality = _app_quality(app) if quality == "success": return @@ -816,7 +814,7 @@ def app_install( # Move scripts and manifest to the right place for file_to_copy in APP_FILES_TO_COPY: if os.path.exists(os.path.join(extracted_app_folder, file_to_copy)): - os.system(f"cp -R '{extracted_app_folder}/{file_to_copy}' '{app_setting_path}'" + os.system(f"cp -R '{extracted_app_folder}/{file_to_copy}' '{app_setting_path}'") # Initialize the main permission for the app # The permission is initialized with no url associated, and with tile disabled @@ -976,6 +974,7 @@ def app_remove(operation_logger, app, purge=False): purge -- Remove with all app data """ + from yunohost.utils.legacy import _patch_legacy_php_versions, _patch_legacy_helpers from yunohost.hook import hook_exec, hook_remove, hook_callback from yunohost.permission import ( user_permission_list, @@ -1987,7 +1986,7 @@ def _app_quality(app: str) -> str: if app in raw_app_catalog 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: + if app in raw_app_catalog: 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 @@ -1997,10 +1996,10 @@ def _app_quality(app: str) -> str: # FIXME : watdo if '@' in app ? app_name_to_test = None - if app_name_to_test in raw_app_list: + if app_name_to_test in raw_app_catalog: - state = raw_app_list[app_name_to_test].get("state", "notworking") - level = raw_app_list[app_name_to_test].get("level", None) + state = raw_app_catalog[app_name_to_test].get("state", "notworking") + level = raw_app_catalog[app_name_to_test].get("level", None) if state in ["working", "validated"]: if isinstance(level, int) and level >= 5: return "success" @@ -2096,7 +2095,7 @@ def _extract_app_from_folder(path: str) -> Tuple[Dict, str]: return manifest, extracted_app_folder -def _extract_app_from_gitrepo(url: str, branch: str, revision: str, app_info={}: Dict) -> Tuple[Dict, str]: +def _extract_app_from_gitrepo(url: str, branch: str, revision: str, app_info: Dict = {}) -> Tuple[Dict, str]: logger.debug(m18n.n("downloading")) From 80f4b892e65b6838146bc520da8feb7991327a32 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 Oct 2021 03:05:14 +0200 Subject: [PATCH 0729/1155] Prevent full-domain app to be moved to a subpath with change-url ? --- src/yunohost/app.py | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 8dcb5aef3..f221e2406 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -33,7 +33,7 @@ import re import subprocess import tempfile from collections import OrderedDict -from typing import List, Tuple, Dict +from typing import List, Tuple, Dict, Any from moulinette import Moulinette, m18n from moulinette.utils.log import getActionLogger @@ -51,7 +51,6 @@ from yunohost.utils import packages from yunohost.utils.config import ( ConfigPanel, ask_questions_and_parse_answers, - Question, DomainQuestion, PathQuestion, ) @@ -384,8 +383,9 @@ def app_change_url(operation_logger, app, domain, path): "app_change_url_identical_domains", domain=domain, path=path ) - # Check the url is available - _assert_no_conflicting_apps(domain, path, ignore_app=app) + app_setting_path = os.path.join(APPS_SETTING_PATH, app) + path_requirement = _guess_webapp_path_requirement(app_setting_path) + _validate_webpath_requirement({"domain": domain, "path": path}, path_requirement, ignore_app=app) tmp_workdir_for_app = _make_tmp_workdir_for_app(app=app) @@ -777,8 +777,8 @@ def app_install( } # Validate domain / path availability for webapps - path_requirement = _guess_webapp_path_requirement(questions, extracted_app_folder) - _validate_webpath_requirement(questions, path_requirement) + path_requirement = _guess_webapp_path_requirement(extracted_app_folder) + _validate_webpath_requirement(args, path_requirement) # Attempt to patch legacy helpers ... _patch_legacy_helpers(extracted_app_folder) @@ -2214,13 +2214,16 @@ def _check_manifest_requirements(manifest: Dict, app: str): ) -def _guess_webapp_path_requirement(questions: List[Question], app_folder: str) -> str: +def _guess_webapp_path_requirement(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_questions = [question for question in questions if question.type == "domain"] - path_questions = [question for question in questions if question.type == "path"] + manifest = _get_manifest_of_app(app_folder) + raw_questions = manifest.get("arguments", {}).get("install", {}) + + domain_questions = [question for question in raw_questions if question.get("type") == "domain"] + path_questions = [question for question in raw_questions if question.get("type") == "path"] if len(domain_questions) == 0 and len(path_questions) == 0: return "" @@ -2248,22 +2251,17 @@ def _guess_webapp_path_requirement(questions: List[Question], app_folder: str) - def _validate_webpath_requirement( - questions: List[Question], path_requirement: str + args: Dict[str, Any], path_requirement: str, ignore_app=None ) -> None: - domain_questions = [question for question in questions if question.type == "domain"] - path_questions = [question for question in questions if question.type == "path"] + domain = args.get("domain") + path = args.get("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) + _assert_no_conflicting_apps(domain, path, ignore_app=ignore_app) elif path_requirement == "full_domain": - - domain = domain_questions[0].value - _assert_no_conflicting_apps(domain, "/", full_domain=True) + _assert_no_conflicting_apps(domain, "/", full_domain=True, ignore_app=ignore_app) def _get_conflicting_apps(domain, path, ignore_app=None): From 5fc493058df2766097494b710f5f628fafaf81a2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 Oct 2021 03:10:26 +0200 Subject: [PATCH 0730/1155] Aaaaaand I forgot to commit app_catalog.py ... --- src/yunohost/app_catalog.py | 255 ++++++++++++++++++++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 src/yunohost/app_catalog.py diff --git a/src/yunohost/app_catalog.py b/src/yunohost/app_catalog.py new file mode 100644 index 000000000..e4ffa1db6 --- /dev/null +++ b/src/yunohost/app_catalog.py @@ -0,0 +1,255 @@ +import os +import re + +from moulinette import m18n +from moulinette.utils.log import getActionLogger +from moulinette.utils.network import download_json +from moulinette.utils.filesystem import ( + read_json, + read_yaml, + write_to_json, + write_to_yaml, + mkdir, +) + +from yunohost.utils.i18n import _value_for_locale +from yunohost.utils.error import YunohostError + +logger = getActionLogger("yunohost.app_catalog") + +APPS_CATALOG_CACHE = "/var/cache/yunohost/repo" +APPS_CATALOG_CONF = "/etc/yunohost/apps_catalog.yml" +APPS_CATALOG_API_VERSION = 2 +APPS_CATALOG_DEFAULT_URL = "https://app.yunohost.org/default" + + +# 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(target="apps") + + +def app_catalog(full=False, with_categories=False): + """ + Return a dict of apps available to installation from Yunohost's app catalog + """ + + from yunohost.app import _installed_apps, _set_default_ask_questions + + # Get app list from catalog cache + catalog = _load_apps_catalog() + installed_apps = set(_installed_apps()) + + # 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["apps"][app] = { + "description": infos["manifest"]["description"], + "level": infos["level"], + } + else: + infos["manifest"]["arguments"] = _set_default_ask_questions( + infos["manifest"].get("arguments", {}) + ) + + # 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"]) + for subtags in category.get("subtags", []): + subtags["title"] = _value_for_locale(subtags["title"]) + + 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_search(string): + """ + Return a dict of apps whose description or name match the search string + """ + + # Retrieve a simple dict listing all apps + 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 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] + + return matching_apps + + +def _initialize_apps_catalog_system(): + """ + This function is meant to intialize the apps_catalog system with YunoHost's default app catalog. + """ + + default_apps_catalog_list = [{"id": "default", "url": APPS_CATALOG_DEFAULT_URL}] + + try: + logger.debug( + "Initializing apps catalog system with YunoHost's default app list" + ) + write_to_yaml(APPS_CATALOG_CONF, default_apps_catalog_list) + except Exception as e: + raise YunohostError( + "Could not initialize the apps catalog system... : %s" % str(e) + ) + + logger.success(m18n.n("apps_catalog_init_success")) + + +def _read_apps_catalog_list(): + """ + Read the json corresponding to the list of apps catalogs + """ + + try: + 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 apps_catalog list ... : %s" % str(e)) + + +def _actual_apps_catalog_api_url(base_url): + + return "{base_url}/v{version}/apps.json".format( + base_url=base_url, version=APPS_CATALOG_API_VERSION + ) + + +def _update_apps_catalog(): + """ + Fetches the json for each apps_catalog and update the cache + + apps_catalog_list is for example : + [ {"id": "default", "url": "https://app.yunohost.org/default/"} ] + + 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 + """ + + apps_catalog_list = _read_apps_catalog_list() + + logger.info(m18n.n("apps_catalog_updating")) + + # Create cache folder if needed + 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 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: + apps_catalog_content = download_json(actual_api_url) + except Exception as e: + raise YunohostError( + "apps_catalog_failed_to_download", + apps_catalog=apps_catalog_id, + error=str(e), + ) + + # Remember the apps_catalog api version for later + apps_catalog_content["from_api_version"] = APPS_CATALOG_API_VERSION + + # 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, apps_catalog_content) + except Exception as e: + raise YunohostError( + "Unable to write cache data for %s apps_catalog : %s" + % (apps_catalog_id, str(e)) + ) + + logger.success(m18n.n("apps_catalog_update_success")) + + +def _load_apps_catalog(): + """ + Read all the apps catalog cache files and build a single dict (merged_catalog) + corresponding to all known apps and categories + """ + + merged_catalog = {"apps": {}, "categories": []} + + for apps_catalog_id in [L["id"] for L in _read_apps_catalog_list()]: + + # 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: + apps_catalog_content = ( + read_json(cache_file) if os.path.exists(cache_file) else None + ) + except Exception as e: + raise YunohostError( + "Unable to read cache for apps_catalog %s : %s" % (cache_file, e), + raw_msg=True, + ) + + # 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 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 apps_catalog_content["from_api_version"] + + # Add apps from this catalog to the output + 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 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 + merged_catalog["apps"][app] = info + + # Annnnd categories + merged_catalog["categories"] += apps_catalog_content["categories"] + + return merged_catalog From 313897d18421e150a119e3de0d66d3fb1f50e340 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 Oct 2021 03:33:12 +0200 Subject: [PATCH 0731/1155] Simplify _check_manifest_requirements --- src/yunohost/app.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index f221e2406..87b02cd19 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -538,7 +538,7 @@ def app_upgrade(app=[], url=None, file=None, force=False, no_safety_backup=False upgrade_type = "UPGRADE_FULL" # Check requirements - _check_manifest_requirements(manifest, app_instance_name=app_instance_name) + _check_manifest_requirements(manifest) _assert_system_is_sane_for_app(manifest, "pre") app_setting_path = os.path.join(APPS_SETTING_PATH, app_instance_name) @@ -753,7 +753,7 @@ def app_install( label = label if label else manifest["name"] # Check requirements - _check_manifest_requirements(manifest, app_id) + _check_manifest_requirements(manifest) _assert_system_is_sane_for_app(manifest, "pre") # Check if app can be forked @@ -2187,7 +2187,7 @@ def _get_all_installed_apps_id(): return all_apps_ids_formatted -def _check_manifest_requirements(manifest: Dict, app: str): +def _check_manifest_requirements(manifest: Dict): """Check if required packages are met from the manifest""" packaging_format = int(manifest.get("packaging_format", 0)) @@ -2199,6 +2199,8 @@ def _check_manifest_requirements(manifest: Dict, app: str): if not requirements: return + app = manifest.get("id", "?") + logger.debug(m18n.n("app_requirements_checking", app=app)) # Iterate over requirements From a26145ece0c41c5b84e359e1b157cc42cb587a85 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 Oct 2021 03:42:59 +0200 Subject: [PATCH 0732/1155] Simplify(?) YNH_APP_BASEDIR creation, to happen in _make_env_for_app_script --- src/yunohost/app.py | 23 ++++++++++------------- src/yunohost/backup.py | 13 ++++--------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 87b02cd19..df4dd089c 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -390,12 +390,11 @@ def app_change_url(operation_logger, app, domain, path): 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) + env_dict = _make_environment_for_app_script(app, workdir=tmp_workdir_for_app) 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)) @@ -544,12 +543,11 @@ 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) # Prepare env. var. to pass to script - env_dict = _make_environment_for_app_script(app_instance_name) + env_dict = _make_environment_for_app_script(app_instance_name, workdir=extracted_app_folder) 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"] = "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() @@ -829,8 +827,7 @@ def app_install( ) # Prepare env. var. to pass to script - env_dict = _make_environment_for_app_script(app_instance_name, args=args) - env_dict["YNH_APP_BASEDIR"] = extracted_app_folder + env_dict = _make_environment_for_app_script(app_instance_name, args=args, workdir=extracted_app_folder) env_dict_for_logging = env_dict.copy() for question in questions: @@ -891,8 +888,7 @@ def app_install( logger.warning(m18n.n("app_remove_after_failed_install")) # Setup environment for remove script - env_dict_remove = _make_environment_for_app_script(app_instance_name) - env_dict_remove["YNH_APP_BASEDIR"] = extracted_app_folder + env_dict_remove = _make_environment_for_app_script(app_instance_name, workdir=extracted_app_folder) # Execute remove script operation_logger_remove = OperationLogger( @@ -1006,8 +1002,7 @@ def app_remove(operation_logger, app, purge=False): env_dict = {} app_id, app_instance_nb = _parse_app_instance_name(app) - env_dict = _make_environment_for_app_script(app) - env_dict["YNH_APP_BASEDIR"] = tmp_workdir_for_app + env_dict = _make_environment_for_app_script(app, workdir=tmp_workdir_for_app) env_dict["YNH_APP_PURGE"] = str(1 if purge else 0) operation_logger.extra.update({"env": env_dict}) @@ -1501,9 +1496,8 @@ def app_action_run(operation_logger, app, action, args=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_", workdir=tmp_workdir_for_app) env_dict["YNH_ACTION"] = action - env_dict["YNH_APP_BASEDIR"] = tmp_workdir_for_app _, action_script = tempfile.mkstemp(dir=tmp_workdir_for_app) @@ -2328,7 +2322,7 @@ def _assert_no_conflicting_apps(domain, path, ignore_app=None, full_domain=False ) -def _make_environment_for_app_script(app, args={}, args_prefix="APP_ARG_"): +def _make_environment_for_app_script(app, args={}, args_prefix="APP_ARG_", workdir=None): app_setting_path = os.path.join(APPS_SETTING_PATH, app) @@ -2342,6 +2336,9 @@ def _make_environment_for_app_script(app, args={}, args_prefix="APP_ARG_"): "YNH_APP_MANIFEST_VERSION": manifest.get("version", "?"), } + if workdir: + env_dict["YNH_APP_BASEDIR"] = workdir + for arg_name, arg_value in args.items(): env_dict["YNH_%s%s" % (args_prefix, arg_name.upper())] = str(arg_value) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index b650cb3da..5664af3a0 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1483,7 +1483,9 @@ class RestoreManager: 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) + # FIXME : workdir should be a tmp workdir + app_workdir = os.path.join(self.work_dir, "apps", app_instance_name, "settings") + env_dict = _make_environment_for_app_script(app_instance_name, workdir=app_workdir) env_dict.update( { "YNH_BACKUP_DIR": self.work_dir, @@ -1491,9 +1493,6 @@ 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" - ), } ) @@ -1530,11 +1529,7 @@ class RestoreManager: 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) - env_dict_remove["YNH_APP_BASEDIR"] = os.path.join( - self.work_dir, "apps", app_instance_name, "settings" - ) - + env_dict_remove = _make_environment_for_app_script(app_instance_name, workdir=app_workdir) remove_operation_logger = OperationLogger( "remove_on_failed_restore", [("app", app_instance_name)], From 680e13c0442a9adc590e2f6ae78d245b69da3e4d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 Oct 2021 03:45:40 +0200 Subject: [PATCH 0733/1155] Make mypy happier --- 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 df4dd089c..0f4c14b7a 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1988,7 +1988,7 @@ def _app_quality(app: str) -> str: app_name_to_test = app.strip("/").split("/")[-1].replace("_ynh", "") else: # FIXME : watdo if '@' in app ? - app_name_to_test = None + return "thirdparty" if app_name_to_test in raw_app_catalog: From 2bda85c80042f0687415c9ed436db53695d04545 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 Oct 2021 03:46:13 +0200 Subject: [PATCH 0734/1155] Stale i18n string --- locales/en.json | 1 - 1 file changed, 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 5d242b2e8..53b0a7e83 100644 --- a/locales/en.json +++ b/locales/en.json @@ -36,7 +36,6 @@ "app_manifest_install_ask_is_public": "Should this app be exposed to anonymous visitors?", "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", From 11be8cc4c02f0b60f00f2b2e1aca3f8c83075dcf Mon Sep 17 00:00:00 2001 From: ericgaspar Date: Fri, 1 Oct 2021 09:43:39 +0200 Subject: [PATCH 0735/1155] 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 a796b68fd..06b948d00 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -1,6 +1,6 @@ #!/bin/bash -n_version=7.3.0 +n_version=7.5.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=b908b0fc86922ede37e89d1030191285209d7d521507bf136e62895e5797847f" > "$YNH_APP_BASEDIR/conf/n.src" +SOURCE_SUM=d4da7ea91f680de0c9b5876e097e2a793e8234fcd0f7ca87a0599b925be087a3" > "$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 171b3b25d9d4cb7aad8b2cbbc5aeb7eee6243a1f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 Oct 2021 16:13:57 +0200 Subject: [PATCH 0736/1155] Improve check that arg is a yunohost repo url --- src/yunohost/app.py | 25 +++++++++++++++++++++++-- src/yunohost/tests/test_appurl.py | 30 +++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 0f4c14b7a..5b6d1a7f8 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -69,6 +69,10 @@ re_app_instance_name = re.compile( r"^(?P[\w-]+?)(__(?P[1-9][0-9]*))?$" ) +APP_REPO_URL = re.compile( + r"^https://[a-zA-Z0-9-_.]+/[a-zA-Z0-9-_./]+/[a-zA-Z0-9-_.]+_ynh(/?(-/)?tree/[a-zA-Z0-9-_]+)?(\.git)?/?$" +) + APP_FILES_TO_COPY = [ "manifest.json", "manifest.toml", @@ -1971,13 +1975,24 @@ def _set_default_ask_questions(arguments): return arguments +def _is_app_repo_url(string: str) -> bool: + + string = string.strip() + + # Dummy test for ssh-based stuff ... should probably be improved somehow + if '@' in string: + return True + + return APP_REPO_URL.match(string) + + def _app_quality(app: str) -> str: """ app may in fact be an app name, an url, or a path """ raw_app_catalog = _load_apps_catalog()["apps"] - if app in raw_app_catalog or ("@" in app) or ("http://" in app) or ("https://" in app): + if app in raw_app_catalog or _is_app_repo_url(app): # If we got an app name directly (e.g. just "wordpress"), we gonna test this name if app in raw_app_catalog: @@ -2027,10 +2042,14 @@ def _extract_app(src: str) -> Tuple[Dict, str]: revision = str(app_info["git"]["revision"]) return _extract_app_from_gitrepo(url, branch, revision, app_info) # App is a git repo url - elif ("@" in src) or ("http://" in src) or ("https://" in src): + elif _is_app_repo_url(src): url = src branch = "master" revision = "HEAD" + # gitlab urls may look like 'https://domain/org/group/repo/-/tree/testing' + # compated to github urls looking like 'https://domain/org/repo/tree/testing' + if '/-/' in url: + url = url.replace('/-/', '/') if "/tree/" in url: url, branch = url.split("/tree/", 1) return _extract_app_from_gitrepo(url, branch, revision, {}) @@ -2038,6 +2057,8 @@ def _extract_app(src: str) -> Tuple[Dict, str]: elif os.path.exists(src): return _extract_app_from_folder(src) else: + if "http://" in src or "https://" in src: + logger.error(f"{src} is not a valid app url: app url are expected to look like https://domain.tld/path/to/repo_ynh") raise YunohostValidationError("app_unknown") diff --git a/src/yunohost/tests/test_appurl.py b/src/yunohost/tests/test_appurl.py index 5954d239a..d50877445 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 +from yunohost.app import app_install, app_remove, _is_app_repo_url from yunohost.domain import _get_maindomain, domain_url_available from yunohost.permission import _validate_and_sanitize_permission_url @@ -28,6 +28,34 @@ def teardown_function(function): pass +def test_repo_url_definition(): + assert _is_app_repo_url("https://github.com/YunoHost-Apps/foobar123_ynh") + assert _is_app_repo_url("https://github.com/YunoHost-Apps/foobar123_ynh/") + assert _is_app_repo_url("https://github.com/YunoHost-Apps/foobar123_ynh.git") + assert _is_app_repo_url("https://github.com/YunoHost-Apps/foobar123_ynh/tree/testing") + assert _is_app_repo_url("https://github.com/YunoHost-Apps/foobar123_ynh/tree/testing/") + assert _is_app_repo_url("https://github.com/YunoHost-Apps/foo-bar-123_ynh") + assert _is_app_repo_url("https://github.com/YunoHost-Apps/foo_bar_123_ynh") + assert _is_app_repo_url("https://github.com/YunoHost-Apps/FooBar123_ynh") + assert _is_app_repo_url("https://github.com/labriqueinternet/vpnclient_ynh") + assert _is_app_repo_url("https://framagit.org/YunoHost/apps/nodebb_ynh") + assert _is_app_repo_url("https://framagit.org/YunoHost/apps/nodebb_ynh/-/tree/testing") + assert _is_app_repo_url("https://gitlab.com/yunohost-apps/foobar_ynh") + assert _is_app_repo_url("https://code.antopie.org/miraty/qr_ynh") + assert _is_app_repo_url("https://gitlab.domainepublic.net/Neutrinet/neutrinet_ynh/-/tree/unstable") + assert _is_app_repo_url("git@github.com:YunoHost-Apps/foobar_ynh.git") + + assert not _is_app_repo_url("github.com/YunoHost-Apps/foobar_ynh") + assert not _is_app_repo_url("http://github.com/YunoHost-Apps/foobar_ynh") + assert not _is_app_repo_url("https://github.com/YunoHost-Apps/foobar_wat") + assert not _is_app_repo_url("https://github.com/YunoHost-Apps/foobar_ynh_wat") + assert not _is_app_repo_url("https://github.com/YunoHost-Apps/foobar/tree/testing") + assert not _is_app_repo_url("https://github.com/YunoHost-Apps/foobar_ynh_wat/tree/testing") + assert not _is_app_repo_url("https://framagit.org/YunoHost/apps/") + assert not _is_app_repo_url("https://framagit.org/YunoHost/apps/pwet") + assert not _is_app_repo_url("https://framagit.org/YunoHost/apps/pwet_foo") + + def test_urlavailable(): # Except the maindomain/macnuggets to be available From 34d9573121a4bd5caea2974f27c20be3cf20726b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 Oct 2021 16:46:05 +0200 Subject: [PATCH 0737/1155] Misc fixes for app repo url check / parsing --- src/yunohost/app.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 5b6d1a7f8..3fede8dcd 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1986,21 +1986,21 @@ def _is_app_repo_url(string: str) -> bool: return APP_REPO_URL.match(string) -def _app_quality(app: str) -> str: +def _app_quality(src: str) -> str: """ app may in fact be an app name, an url, or a path """ raw_app_catalog = _load_apps_catalog()["apps"] - if app in raw_app_catalog or _is_app_repo_url(app): + if src in raw_app_catalog or _is_app_repo_url(src): # If we got an app name directly (e.g. just "wordpress"), we gonna test this name - if app in raw_app_catalog: - app_name_to_test = app + if src in raw_app_catalog: + app_name_to_test = src # 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", "") + elif ("http://" in src) or ("https://" in src): + app_name_to_test = src.strip("/").split("/")[-1].replace("_ynh", "") else: # FIXME : watdo if '@' in app ? return "thirdparty" @@ -2018,9 +2018,11 @@ def _app_quality(app: str) -> str: else: return "thirdparty" - elif os.path.exists(app): + elif os.path.exists(src): return "thirdparty" else: + if "http://" in src or "https://" in src: + logger.error(f"{src} is not a valid app url: app url are expected to look like https://domain.tld/path/to/repo_ynh") raise YunohostValidationError("app_unknown") @@ -2043,7 +2045,7 @@ def _extract_app(src: str) -> Tuple[Dict, str]: return _extract_app_from_gitrepo(url, branch, revision, app_info) # App is a git repo url elif _is_app_repo_url(src): - url = src + url = src.strip().strip("/") branch = "master" revision = "HEAD" # gitlab urls may look like 'https://domain/org/group/repo/-/tree/testing' From 3883343e1a414fe32a1e29adf2087e4f5051fbfe Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 Oct 2021 17:51:05 +0200 Subject: [PATCH 0738/1155] swag: Add version badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b256bb563..df3a4bb9f 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@
+![Version](https://img.shields.io/github/v/tag/yunohost/yunohost?label=version&sort=semver) [![Build status](https://shields.io/gitlab/pipeline/yunohost/yunohost/dev)](https://gitlab.com/yunohost/yunohost/-/pipelines) ![Test coverage](https://img.shields.io/gitlab/coverage/yunohost/yunohost/dev) [![GitHub license](https://img.shields.io/github/license/YunoHost/yunohost)](https://github.com/YunoHost/yunohost/blob/dev/LICENSE) From 310b664fab6c9ebef3b94575956f3654c366211f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 Oct 2021 19:20:21 +0200 Subject: [PATCH 0739/1155] Clarify return type for mypy --- 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 3fede8dcd..36b65f873 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1983,7 +1983,7 @@ def _is_app_repo_url(string: str) -> bool: if '@' in string: return True - return APP_REPO_URL.match(string) + return bool(APP_REPO_URL.match(string)) def _app_quality(src: str) -> str: From 0f0f26f5c48af161da16eaf4775e95e6bcab25fc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 Oct 2021 19:25:32 +0200 Subject: [PATCH 0740/1155] For some reason these tests now wanted to interactively ask for is_public arg ... don't know why it was working before ... --- src/yunohost/tests/test_permission.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index b33c2f213..00799d0fd 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -1049,7 +1049,7 @@ def test_permission_app_remove(): def test_permission_app_change_url(): app_install( os.path.join(get_test_apps_dir(), "permissions_app_ynh"), - args="domain=%s&domain_2=%s&path=%s&admin=%s" + args="domain=%s&domain_2=%s&path=%s&is_public=1&admin=%s" % (maindomain, other_domains[0], "/urlpermissionapp", "alice"), force=True, ) @@ -1072,7 +1072,7 @@ def test_permission_app_change_url(): def test_permission_protection_management_by_helper(): app_install( os.path.join(get_test_apps_dir(), "permissions_app_ynh"), - args="domain=%s&domain_2=%s&path=%s&admin=%s" + args="domain=%s&domain_2=%s&path=%s&is_public=1&admin=%s" % (maindomain, other_domains[0], "/urlpermissionapp", "alice"), force=True, ) @@ -1135,7 +1135,7 @@ def test_permission_legacy_app_propagation_on_ssowat(): app_install( os.path.join(get_test_apps_dir(), "legacy_app_ynh"), - args="domain=%s&domain_2=%s&path=%s" + args="domain=%s&domain_2=%s&path=%s&is_public=1" % (maindomain, other_domains[0], "/legacy"), force=True, ) From 3daac24443eb02f449d5e1c6ce6b2b91d38c5e7b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 Oct 2021 19:25:36 +0200 Subject: [PATCH 0741/1155] Typos --- 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 36b65f873..b6150d152 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -645,7 +645,7 @@ def app_upgrade(app=[], url=None, file=None, force=False, no_safety_backup=False for file_to_copy in APP_FILES_TO_COPY: os.system(f"rm -rf '{app_setting_path}/{file_to_copy}'") if os.path.exists(os.path.join(extracted_app_folder, file_to_copy)): - os.system(f"cp -R '{extracted_app_folder}/{file_to_copy} {app_setting_path}'") + os.system(f"cp -R '{extracted_app_folder}/{file_to_copy}' '{app_setting_path}'") # Clean and set permissions shutil.rmtree(extracted_app_folder) @@ -2089,7 +2089,7 @@ def _extract_app_from_folder(path: str) -> Tuple[Dict, str]: shutil.rmtree(extracted_app_folder) if path[-1] != "/": path = path + "/" - extract_result = os.system(f"cp -a '{path}' {extracted_app_folder}") + extract_result = os.system(f"cp -a '{path}' '{extracted_app_folder}'") else: extract_result = 1 From 6c03cf2b118b1c0178aad905819e6fe623ff5ec2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 Oct 2021 20:22:59 +0200 Subject: [PATCH 0742/1155] Yoloattempt to try to cleanup some ugly os.system --- src/yunohost/app.py | 44 +++++++++++++++++++----------------------- src/yunohost/dns.py | 4 ++-- src/yunohost/domain.py | 4 ++-- src/yunohost/dyndns.py | 19 +++++++++--------- src/yunohost/hook.py | 5 ++--- src/yunohost/tools.py | 26 +++++++++++-------------- 6 files changed, 46 insertions(+), 56 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index b6150d152..cf4b47681 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -45,6 +45,10 @@ from moulinette.utils.filesystem import ( read_yaml, write_to_file, write_to_json, + cp, + rm, + chown, + chmod, ) from yunohost.utils import packages @@ -643,15 +647,15 @@ def app_upgrade(app=[], url=None, file=None, force=False, no_safety_backup=False # Replace scripts and manifest and conf (if exists) # Move scripts and manifest to the right place for file_to_copy in APP_FILES_TO_COPY: - os.system(f"rm -rf '{app_setting_path}/{file_to_copy}'") + rm(f'{app_setting_path}/{file_to_copy}', recursive=True, force=True) if os.path.exists(os.path.join(extracted_app_folder, file_to_copy)): - os.system(f"cp -R '{extracted_app_folder}/{file_to_copy}' '{app_setting_path}'") + cp(f'{extracted_app_folder}/{file_to_copy}', f'{app_setting_path}/{file_to_copy}', recursive=True) # 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) + chmod(app_setting_path, 0o600) + chmod(f"{app_setting_path}/settings.yml", 0o400) + chown(app_setting_path, "root", recursive=True) # So much win logger.success(m18n.n("app_upgraded", app=app_instance_name)) @@ -816,7 +820,7 @@ def app_install( # Move scripts and manifest to the right place for file_to_copy in APP_FILES_TO_COPY: if os.path.exists(os.path.join(extracted_app_folder, file_to_copy)): - os.system(f"cp -R '{extracted_app_folder}/{file_to_copy}' '{app_setting_path}'") + cp(f'{extracted_app_folder}/{file_to_copy}', f'{app_setting_path}/{file_to_copy}', recursive=True) # Initialize the main permission for the app # The permission is initialized with no url associated, and with tile disabled @@ -955,9 +959,9 @@ def app_install( # 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) + chmod(app_setting_path, 0o600) + chmod(f"{app_setting_path}/settings.yml", 0o400) + chown(app_setting_path, "root", recursive=True) logger.success(m18n.n("installation_complete")) @@ -1153,7 +1157,7 @@ def app_makedefault(operation_logger, app, domain=None): write_to_json( "/etc/ssowat/conf.json.persistent", ssowat_conf, sort_keys=True, indent=4 ) - os.system("chmod 644 /etc/ssowat/conf.json.persistent") + chmod("/etc/ssowat/conf.json.persistent", 0o644) logger.success(m18n.n("ssowat_conf_updated")) @@ -2077,24 +2081,16 @@ def _extract_app_from_folder(path: str) -> Tuple[Dict, str]: 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" - ) - elif ".tar" in path: - extract_result = os.system( - f"tar -xf '{path}' -C {extracted_app_folder} > /dev/null 2>&1" - ) - elif os.path.isdir(path): + if os.path.isdir(path): shutil.rmtree(extracted_app_folder) if path[-1] != "/": path = path + "/" - extract_result = os.system(f"cp -a '{path}' '{extracted_app_folder}'") + cp(path, extracted_app_folder, recursive=True) else: - extract_result = 1 - - if extract_result != 0: - raise YunohostError("app_extraction_failed") + try: + shutil.unpack_archive(path, extracted_app_folder) + except Exception: + raise YunohostError("app_extraction_failed") try: if len(os.listdir(extracted_app_folder)) == 1: diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 689950490..b0d40db9e 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -32,7 +32,7 @@ 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 moulinette.utils.filesystem import read_file, write_to_file, read_toml, mkdir from yunohost.domain import ( domain_list, @@ -471,7 +471,7 @@ def _get_dns_zone_for_domain(domain): # 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}") + mkdir(cache_folder, parents=True) write_to_file(cache_file, parent) return parent diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index f5b14748d..b40831d25 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -29,7 +29,7 @@ from typing import Dict, Any 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 write_to_file, read_yaml, write_to_yaml, rm from yunohost.app import ( app_ssowatconf, @@ -328,7 +328,7 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False): ] for stuff in stuff_to_delete: - os.system("rm -rf {stuff}") + rm(stuff, force=True, recursive=True) # 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 519fbc8f0..e33cf4f22 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, read_file +from moulinette.utils.filesystem import write_to_file, read_file, rm, chown, chmod from moulinette.utils.network import download_json from yunohost.utils.error import YunohostError, YunohostValidationError @@ -152,13 +152,12 @@ def dyndns_subscribe( os.system( "cd /etc/yunohost/dyndns && " - "dnssec-keygen -a hmac-sha512 -b 512 -r /dev/urandom -n USER %s" - % domain - ) - os.system( - "chmod 600 /etc/yunohost/dyndns/*.key /etc/yunohost/dyndns/*.private" + f"dnssec-keygen -a hmac-sha512 -b 512 -r /dev/urandom -n USER {domain}" ) + chmod("/etc/yunohost/dyndns", 0o600, recursive=True) + chown("/etc/yunohost/dyndns", "root", recursive=True) + private_file = glob.glob("/etc/yunohost/dyndns/*%s*.private" % domain)[0] key_file = glob.glob("/etc/yunohost/dyndns/*%s*.key" % domain)[0] with open(key_file) as f: @@ -175,12 +174,12 @@ def dyndns_subscribe( timeout=30, ) except Exception as e: - os.system("rm -f %s" % private_file) - os.system("rm -f %s" % key_file) + rm(private_file, force=True) + rm(key_file, force=True) raise YunohostError("dyndns_registration_failed", error=str(e)) if r.status_code != 201: - os.system("rm -f %s" % private_file) - os.system("rm -f %s" % key_file) + rm(private_file, force=True) + rm(key_file, force=True) try: error = json.loads(r.text)["error"] except Exception: diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index c55809fce..20757bf3c 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -34,7 +34,7 @@ from importlib import import_module from moulinette import m18n, Moulinette from yunohost.utils.error import YunohostError, YunohostValidationError from moulinette.utils import log -from moulinette.utils.filesystem import read_yaml +from moulinette.utils.filesystem import read_yaml, cp HOOK_FOLDER = "/usr/share/yunohost/hooks/" CUSTOM_HOOK_FOLDER = "/etc/yunohost/hooks.d/" @@ -60,8 +60,7 @@ def hook_add(app, file): os.makedirs(CUSTOM_HOOK_FOLDER + action) finalpath = CUSTOM_HOOK_FOLDER + action + "/" + priority + "-" + app - os.system("cp %s %s" % (file, finalpath)) - os.system("chown -hR admin: %s" % HOOK_FOLDER) + cp(file, finalpath) return {"hook": finalpath} diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 4cdd62d8f..671f8768c 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -34,7 +34,7 @@ from typing import List 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 +from moulinette.utils.filesystem import read_yaml, write_to_yaml, cp, mkdir, rm from yunohost.app import ( app_info, @@ -1147,13 +1147,11 @@ class Migration(object): backup_folder = "/home/yunohost.backup/premigration/" + time.strftime( "%Y%m%d-%H%M%S", time.gmtime() ) - os.makedirs(backup_folder, 0o750) + mkdir(backup_folder, 0o750, parents=True) 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" - ) + cp("/etc/ldap", f"{backup_folder}/ldap_config", recursive=True) + cp("/var/lib/ldap", f"{backup_folder}/ldap_db", recursive=True) + cp("/etc/yunohost/apps", f"{backup_folder}/apps_settings", recursive=True) except Exception as e: raise YunohostError( "migration_ldap_can_not_backup_before_migration", error=str(e) @@ -1169,17 +1167,15 @@ class Migration(object): ) 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/" - ) + rm("/etc/ldap/slapd.d", force=True, recursive=True) + cp(f"{backup_folder}/ldap_config", "/etc/ldap", recursive=True) + cp(f"{backup_folder}/ldap_db", "/var/lib/ldap", recursive=True) + cp(f"{backup_folder}/apps_settings", "/etc/yunohost/apps", recursive=True) os.system("systemctl start slapd") - os.system(f"rm -r {backup_folder}") + rm(backup_folder, force=True, recursive=True) logger.info(m18n.n("migration_ldap_rollback_success")) raise else: - os.system(f"rm -r {backup_folder}") + rm(backup_folder, force=True, recursive=True) return func From 4fda262bc0c69b68d2288d9773215b581bab945c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 2 Oct 2021 01:59:16 +0200 Subject: [PATCH 0743/1155] fix tests: Missing mkdir on force --- 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 b0d40db9e..534ade918 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -471,7 +471,7 @@ def _get_dns_zone_for_domain(domain): # 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": - mkdir(cache_folder, parents=True) + mkdir(cache_folder, parents=True, force=True) write_to_file(cache_file, parent) return parent From a40ecdc98610c67dee5cfd75f83f596e8ba4d634 Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Sat, 2 Oct 2021 02:29:22 +0000 Subject: [PATCH 0744/1155] [CI] Format code --- src/yunohost/app.py | 81 ++++++++++++++++++++++--------- src/yunohost/backup.py | 14 ++++-- src/yunohost/tests/test_appurl.py | 20 ++++++-- src/yunohost/tools.py | 12 ++++- src/yunohost/utils/legacy.py | 8 ++- 5 files changed, 102 insertions(+), 33 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index cf4b47681..fd088bce9 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -62,7 +62,12 @@ 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 -from yunohost.app_catalog import app_catalog, app_search, _load_apps_catalog, app_fetchlist # noqa +from yunohost.app_catalog import ( + app_catalog, + app_search, + _load_apps_catalog, + app_fetchlist, +) # noqa logger = getActionLogger("yunohost.app") @@ -393,7 +398,9 @@ def app_change_url(operation_logger, app, domain, path): app_setting_path = os.path.join(APPS_SETTING_PATH, app) path_requirement = _guess_webapp_path_requirement(app_setting_path) - _validate_webpath_requirement({"domain": domain, "path": path}, path_requirement, ignore_app=app) + _validate_webpath_requirement( + {"domain": domain, "path": path}, path_requirement, ignore_app=app + ) tmp_workdir_for_app = _make_tmp_workdir_for_app(app=app) @@ -551,7 +558,9 @@ 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) # Prepare env. var. to pass to script - env_dict = _make_environment_for_app_script(app_instance_name, workdir=extracted_app_folder) + env_dict = _make_environment_for_app_script( + app_instance_name, workdir=extracted_app_folder + ) 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) @@ -615,7 +624,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( @@ -647,9 +656,13 @@ def app_upgrade(app=[], url=None, file=None, force=False, no_safety_backup=False # Replace scripts and manifest and conf (if exists) # Move scripts and manifest to the right place for file_to_copy in APP_FILES_TO_COPY: - rm(f'{app_setting_path}/{file_to_copy}', recursive=True, force=True) + rm(f"{app_setting_path}/{file_to_copy}", recursive=True, force=True) if os.path.exists(os.path.join(extracted_app_folder, file_to_copy)): - cp(f'{extracted_app_folder}/{file_to_copy}', f'{app_setting_path}/{file_to_copy}', recursive=True) + cp( + f"{extracted_app_folder}/{file_to_copy}", + f"{app_setting_path}/{file_to_copy}", + recursive=True, + ) # Clean and set permissions shutil.rmtree(extracted_app_folder) @@ -820,7 +833,11 @@ def app_install( # Move scripts and manifest to the right place for file_to_copy in APP_FILES_TO_COPY: if os.path.exists(os.path.join(extracted_app_folder, file_to_copy)): - cp(f'{extracted_app_folder}/{file_to_copy}', f'{app_setting_path}/{file_to_copy}', recursive=True) + cp( + f"{extracted_app_folder}/{file_to_copy}", + f"{app_setting_path}/{file_to_copy}", + recursive=True, + ) # Initialize the main permission for the app # The permission is initialized with no url associated, and with tile disabled @@ -835,7 +852,9 @@ def app_install( ) # Prepare env. var. to pass to script - env_dict = _make_environment_for_app_script(app_instance_name, args=args, workdir=extracted_app_folder) + env_dict = _make_environment_for_app_script( + app_instance_name, args=args, workdir=extracted_app_folder + ) env_dict_for_logging = env_dict.copy() for question in questions: @@ -896,7 +915,9 @@ def app_install( logger.warning(m18n.n("app_remove_after_failed_install")) # Setup environment for remove script - env_dict_remove = _make_environment_for_app_script(app_instance_name, workdir=extracted_app_folder) + env_dict_remove = _make_environment_for_app_script( + app_instance_name, workdir=extracted_app_folder + ) # Execute remove script operation_logger_remove = OperationLogger( @@ -1504,7 +1525,9 @@ def app_action_run(operation_logger, app, action, args=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_", workdir=tmp_workdir_for_app) + env_dict = _make_environment_for_app_script( + app, args=args, args_prefix="ACTION_", workdir=tmp_workdir_for_app + ) env_dict["YNH_ACTION"] = action _, action_script = tempfile.mkstemp(dir=tmp_workdir_for_app) @@ -1984,7 +2007,7 @@ def _is_app_repo_url(string: str) -> bool: string = string.strip() # Dummy test for ssh-based stuff ... should probably be improved somehow - if '@' in string: + if "@" in string: return True return bool(APP_REPO_URL.match(string)) @@ -1992,7 +2015,7 @@ def _is_app_repo_url(string: str) -> bool: def _app_quality(src: str) -> str: """ - app may in fact be an app name, an url, or a path + app may in fact be an app name, an url, or a path """ raw_app_catalog = _load_apps_catalog()["apps"] @@ -2026,13 +2049,15 @@ def _app_quality(src: str) -> str: return "thirdparty" else: if "http://" in src or "https://" in src: - logger.error(f"{src} is not a valid app url: app url are expected to look like https://domain.tld/path/to/repo_ynh") + logger.error( + f"{src} is not a valid app url: app url are expected to look like https://domain.tld/path/to/repo_ynh" + ) raise YunohostValidationError("app_unknown") def _extract_app(src: str) -> Tuple[Dict, str]: """ - src may be an app name, an url, or a path + src may be an app name, an url, or a path """ raw_app_catalog = _load_apps_catalog()["apps"] @@ -2054,8 +2079,8 @@ def _extract_app(src: str) -> Tuple[Dict, str]: revision = "HEAD" # gitlab urls may look like 'https://domain/org/group/repo/-/tree/testing' # compated to github urls looking like 'https://domain/org/repo/tree/testing' - if '/-/' in url: - url = url.replace('/-/', '/') + if "/-/" in url: + url = url.replace("/-/", "/") if "/tree/" in url: url, branch = url.split("/tree/", 1) return _extract_app_from_gitrepo(url, branch, revision, {}) @@ -2064,7 +2089,9 @@ def _extract_app(src: str) -> Tuple[Dict, str]: return _extract_app_from_folder(src) else: if "http://" in src or "https://" in src: - logger.error(f"{src} is not a valid app url: app url are expected to look like https://domain.tld/path/to/repo_ynh") + logger.error( + f"{src} is not a valid app url: app url are expected to look like https://domain.tld/path/to/repo_ynh" + ) raise YunohostValidationError("app_unknown") @@ -2108,7 +2135,9 @@ def _extract_app_from_folder(path: str) -> Tuple[Dict, str]: return manifest, extracted_app_folder -def _extract_app_from_gitrepo(url: str, branch: str, revision: str, app_info: Dict = {}) -> Tuple[Dict, str]: +def _extract_app_from_gitrepo( + url: str, branch: str, revision: str, app_info: Dict = {} +) -> Tuple[Dict, str]: logger.debug(m18n.n("downloading")) @@ -2237,8 +2266,12 @@ def _guess_webapp_path_requirement(app_folder: str) -> str: manifest = _get_manifest_of_app(app_folder) raw_questions = manifest.get("arguments", {}).get("install", {}) - domain_questions = [question for question in raw_questions if question.get("type") == "domain"] - path_questions = [question for question in raw_questions if question.get("type") == "path"] + domain_questions = [ + question for question in raw_questions if question.get("type") == "domain" + ] + path_questions = [ + question for question in raw_questions if question.get("type") == "path" + ] if len(domain_questions) == 0 and len(path_questions) == 0: return "" @@ -2276,7 +2309,9 @@ def _validate_webpath_requirement( _assert_no_conflicting_apps(domain, path, ignore_app=ignore_app) elif path_requirement == "full_domain": - _assert_no_conflicting_apps(domain, "/", full_domain=True, ignore_app=ignore_app) + _assert_no_conflicting_apps( + domain, "/", full_domain=True, ignore_app=ignore_app + ) def _get_conflicting_apps(domain, path, ignore_app=None): @@ -2341,7 +2376,9 @@ def _assert_no_conflicting_apps(domain, path, ignore_app=None, full_domain=False ) -def _make_environment_for_app_script(app, args={}, args_prefix="APP_ARG_", workdir=None): +def _make_environment_for_app_script( + app, args={}, args_prefix="APP_ARG_", workdir=None +): app_setting_path = os.path.join(APPS_SETTING_PATH, app) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 5664af3a0..cce66597a 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1348,7 +1348,11 @@ class RestoreManager: app_instance_name -- (string) The app name to restore (no app with this name should be already install) """ - from yunohost.utils.legacy import _patch_legacy_php_versions, _patch_legacy_php_versions_in_settings, _patch_legacy_helpers + from yunohost.utils.legacy import ( + _patch_legacy_php_versions, + _patch_legacy_php_versions_in_settings, + _patch_legacy_helpers, + ) from yunohost.user import user_group_list from yunohost.permission import ( permission_create, @@ -1485,7 +1489,9 @@ class RestoreManager: # Prepare env. var. to pass to script # FIXME : workdir should be a tmp workdir app_workdir = os.path.join(self.work_dir, "apps", app_instance_name, "settings") - env_dict = _make_environment_for_app_script(app_instance_name, workdir=app_workdir) + env_dict = _make_environment_for_app_script( + app_instance_name, workdir=app_workdir + ) env_dict.update( { "YNH_BACKUP_DIR": self.work_dir, @@ -1529,7 +1535,9 @@ class RestoreManager: 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, workdir=app_workdir) + env_dict_remove = _make_environment_for_app_script( + app_instance_name, workdir=app_workdir + ) remove_operation_logger = OperationLogger( "remove_on_failed_restore", [("app", app_instance_name)], diff --git a/src/yunohost/tests/test_appurl.py b/src/yunohost/tests/test_appurl.py index d50877445..186b76cdf 100644 --- a/src/yunohost/tests/test_appurl.py +++ b/src/yunohost/tests/test_appurl.py @@ -32,17 +32,25 @@ def test_repo_url_definition(): assert _is_app_repo_url("https://github.com/YunoHost-Apps/foobar123_ynh") assert _is_app_repo_url("https://github.com/YunoHost-Apps/foobar123_ynh/") assert _is_app_repo_url("https://github.com/YunoHost-Apps/foobar123_ynh.git") - assert _is_app_repo_url("https://github.com/YunoHost-Apps/foobar123_ynh/tree/testing") - assert _is_app_repo_url("https://github.com/YunoHost-Apps/foobar123_ynh/tree/testing/") + assert _is_app_repo_url( + "https://github.com/YunoHost-Apps/foobar123_ynh/tree/testing" + ) + assert _is_app_repo_url( + "https://github.com/YunoHost-Apps/foobar123_ynh/tree/testing/" + ) assert _is_app_repo_url("https://github.com/YunoHost-Apps/foo-bar-123_ynh") assert _is_app_repo_url("https://github.com/YunoHost-Apps/foo_bar_123_ynh") assert _is_app_repo_url("https://github.com/YunoHost-Apps/FooBar123_ynh") assert _is_app_repo_url("https://github.com/labriqueinternet/vpnclient_ynh") assert _is_app_repo_url("https://framagit.org/YunoHost/apps/nodebb_ynh") - assert _is_app_repo_url("https://framagit.org/YunoHost/apps/nodebb_ynh/-/tree/testing") + assert _is_app_repo_url( + "https://framagit.org/YunoHost/apps/nodebb_ynh/-/tree/testing" + ) assert _is_app_repo_url("https://gitlab.com/yunohost-apps/foobar_ynh") assert _is_app_repo_url("https://code.antopie.org/miraty/qr_ynh") - assert _is_app_repo_url("https://gitlab.domainepublic.net/Neutrinet/neutrinet_ynh/-/tree/unstable") + assert _is_app_repo_url( + "https://gitlab.domainepublic.net/Neutrinet/neutrinet_ynh/-/tree/unstable" + ) assert _is_app_repo_url("git@github.com:YunoHost-Apps/foobar_ynh.git") assert not _is_app_repo_url("github.com/YunoHost-Apps/foobar_ynh") @@ -50,7 +58,9 @@ def test_repo_url_definition(): assert not _is_app_repo_url("https://github.com/YunoHost-Apps/foobar_wat") assert not _is_app_repo_url("https://github.com/YunoHost-Apps/foobar_ynh_wat") assert not _is_app_repo_url("https://github.com/YunoHost-Apps/foobar/tree/testing") - assert not _is_app_repo_url("https://github.com/YunoHost-Apps/foobar_ynh_wat/tree/testing") + assert not _is_app_repo_url( + "https://github.com/YunoHost-Apps/foobar_ynh_wat/tree/testing" + ) assert not _is_app_repo_url("https://framagit.org/YunoHost/apps/") assert not _is_app_repo_url("https://framagit.org/YunoHost/apps/pwet") assert not _is_app_repo_url("https://framagit.org/YunoHost/apps/pwet_foo") diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 671f8768c..e89081abd 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -1151,7 +1151,11 @@ class Migration(object): os.system("systemctl stop slapd") cp("/etc/ldap", f"{backup_folder}/ldap_config", recursive=True) cp("/var/lib/ldap", f"{backup_folder}/ldap_db", recursive=True) - cp("/etc/yunohost/apps", f"{backup_folder}/apps_settings", recursive=True) + cp( + "/etc/yunohost/apps", + f"{backup_folder}/apps_settings", + recursive=True, + ) except Exception as e: raise YunohostError( "migration_ldap_can_not_backup_before_migration", error=str(e) @@ -1170,7 +1174,11 @@ class Migration(object): rm("/etc/ldap/slapd.d", force=True, recursive=True) cp(f"{backup_folder}/ldap_config", "/etc/ldap", recursive=True) cp(f"{backup_folder}/ldap_db", "/var/lib/ldap", recursive=True) - cp(f"{backup_folder}/apps_settings", "/etc/yunohost/apps", recursive=True) + cp( + f"{backup_folder}/apps_settings", + "/etc/yunohost/apps", + recursive=True, + ) os.system("systemctl start slapd") rm(backup_folder, force=True, recursive=True) logger.info(m18n.n("migration_ldap_rollback_success")) diff --git a/src/yunohost/utils/legacy.py b/src/yunohost/utils/legacy.py index 7ae98bed8..87c163f1b 100644 --- a/src/yunohost/utils/legacy.py +++ b/src/yunohost/utils/legacy.py @@ -4,7 +4,13 @@ import glob from moulinette import m18n from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger -from moulinette.utils.filesystem import read_file, write_to_file, write_to_json, write_to_yaml, read_yaml +from moulinette.utils.filesystem import ( + read_file, + write_to_file, + write_to_json, + write_to_yaml, + read_yaml, +) from yunohost.user import user_list from yunohost.app import ( From b88074b00752f0af193253294e936d7ae53143eb Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 2 Oct 2021 20:23:23 +0200 Subject: [PATCH 0745/1155] Make linter happy --- 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 fd088bce9..a65ac76c7 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -62,12 +62,12 @@ 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 -from yunohost.app_catalog import ( +from yunohost.app_catalog import ( # noqa app_catalog, app_search, _load_apps_catalog, app_fetchlist, -) # noqa +) logger = getActionLogger("yunohost.app") From 4640d4577d5a9f146a9211acb65b6f69e674aa7d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 2 Oct 2021 20:24:13 +0200 Subject: [PATCH 0746/1155] Moar mypy --- 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 a65ac76c7..4f9c3147f 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2401,7 +2401,7 @@ def _make_environment_for_app_script( return env_dict -def _parse_app_instance_name(app_instance_name): +def _parse_app_instance_name(app_instance_name: str) -> Tuple[str, int]: """ Parse a Yunohost app instance name and extracts the original appid and the application instance number From bf21f9d0f2289f1d9385d55c96875c84c01ad433 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 2 Oct 2021 20:46:34 +0200 Subject: [PATCH 0747/1155] Stale i18n string --- locales/en.json | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/locales/en.json b/locales/en.json index caf19b44b..54d46f342 100644 --- a/locales/en.json +++ b/locales/en.json @@ -485,28 +485,6 @@ "mailbox_used_space_dovecot_down": "The Dovecot mailbox service needs to be up if you want to fetch used mailbox space", "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_0021_start" : "Starting migration to Bullseye", "migration_0021_patching_sources_list": "Patching the sources.lists...", "migration_0021_main_upgrade": "Starting main upgrade...", @@ -523,12 +501,6 @@ "migration_0023_postgresql_11_not_installed": "PostgreSQL was not installed on your system. Nothing to do.", "migration_0023_postgresql_13_not_installed": "PostgreSQL 11 is installed, but not postgresql 13!? Something weird might have happened on your system :(...", "migration_0023_not_enough_space": "Make sufficient space available in {path} to run the migration.", - "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", - "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_description_0021_migrate_to_bullseye": "Upgrade the system to Debian Bullseye and YunoHost 11.x", "migration_description_0022_php73_to_php74_pools": "Migrate php7.3-fpm 'pool' conf files to php7.4", "migration_description_0023_postgresql_11_to_13": "Migrate databases from PostgreSQL 11 to 13", @@ -536,7 +508,6 @@ "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...", "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}.", @@ -659,7 +630,6 @@ "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 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}", "service_reloaded": "Service '{service}' reloaded", From 8375ef7d5b95d37f4cfa9cddc81b547b636f8462 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 2 Oct 2021 20:49:26 +0200 Subject: [PATCH 0748/1155] Re-drop app_fetchlist legacy --- src/yunohost/app.py | 1 - src/yunohost/app_catalog.py | 10 ---------- 2 files changed, 11 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 57e5698d0..3ca7f13d0 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -66,7 +66,6 @@ from yunohost.app_catalog import ( # noqa app_catalog, app_search, _load_apps_catalog, - app_fetchlist, ) logger = getActionLogger("yunohost.app") diff --git a/src/yunohost/app_catalog.py b/src/yunohost/app_catalog.py index e4ffa1db6..e73493366 100644 --- a/src/yunohost/app_catalog.py +++ b/src/yunohost/app_catalog.py @@ -23,16 +23,6 @@ APPS_CATALOG_API_VERSION = 2 APPS_CATALOG_DEFAULT_URL = "https://app.yunohost.org/default" -# 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(target="apps") - - def app_catalog(full=False, with_categories=False): """ Return a dict of apps available to installation from Yunohost's app catalog From 9c4ea1ccc6fa42f8ddb5905715bfce0254e7f9fc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 4 Oct 2021 01:16:20 +0200 Subject: [PATCH 0749/1155] Try to make mypy happy --- src/yunohost/app.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 4f9c3147f..f4dd2aa1f 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2424,13 +2424,16 @@ def _parse_app_instance_name(app_instance_name: str) -> Tuple[str, int]: True """ match = re_app_instance_name.match(app_instance_name) - assert match, "Could not parse app instance name : %s" % app_instance_name + assert match, f"Could not parse app instance name : {app_instance_name}" appid = match.groupdict().get("appid") - app_instance_nb = ( - int(match.groupdict().get("appinstancenb")) - if match.groupdict().get("appinstancenb") is not None - else 1 - ) + app_instance_nb = match.groupdict().get("appinstancenb") or "1" + if not appid: + raise Exception(f"Could not parse app instance name : {app_instance_name}") + if not str(app_instance_nb).isdigit(): + raise Exception(f"Could not parse app instance name : {app_instance_name}") + else: + app_instance_nb = int(str(app_instance_nb)) + return (appid, app_instance_nb) From 42da17181910761dc993ab5cbee3bb9012d8c1bc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 4 Oct 2021 01:24:16 +0200 Subject: [PATCH 0750/1155] Add proper test for parse_app_instance_name --- src/yunohost/app.py | 24 ++++++++---------------- src/yunohost/tests/test_appurl.py | 14 +++++++++++++- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index f4dd2aa1f..1c05ce20b 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2406,22 +2406,14 @@ def _parse_app_instance_name(app_instance_name: str) -> Tuple[str, int]: Parse a Yunohost app instance name and extracts the original appid and the application instance number - >>> _parse_app_instance_name('yolo') == ('yolo', 1) - True - >>> _parse_app_instance_name('yolo1') == ('yolo1', 1) - True - >>> _parse_app_instance_name('yolo__0') == ('yolo__0', 1) - True - >>> _parse_app_instance_name('yolo__1') == ('yolo', 1) - True - >>> _parse_app_instance_name('yolo__23') == ('yolo', 23) - True - >>> _parse_app_instance_name('yolo__42__72') == ('yolo__42', 72) - True - >>> _parse_app_instance_name('yolo__23qdqsd') == ('yolo__23qdqsd', 1) - True - >>> _parse_app_instance_name('yolo__23qdqsd56') == ('yolo__23qdqsd56', 1) - True + 'yolo' -> ('yolo', 1) + 'yolo1' -> ('yolo1', 1) + 'yolo__0' -> ('yolo__0', 1) + 'yolo__1' -> ('yolo', 1) + 'yolo__23' -> ('yolo', 23) + 'yolo__42__72' -> ('yolo__42', 72) + 'yolo__23qdqsd' -> ('yolo__23qdqsd', 1) + 'yolo__23qdqsd56' -> ('yolo__23qdqsd56', 1) """ match = re_app_instance_name.match(app_instance_name) assert match, f"Could not parse app instance name : {app_instance_name}" diff --git a/src/yunohost/tests/test_appurl.py b/src/yunohost/tests/test_appurl.py index 186b76cdf..ca953dcf7 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, _is_app_repo_url +from yunohost.app import app_install, app_remove, _is_app_repo_url, _parse_app_instance_name from yunohost.domain import _get_maindomain, domain_url_available from yunohost.permission import _validate_and_sanitize_permission_url @@ -28,6 +28,18 @@ def teardown_function(function): pass +def test_parse_app_instance_name(): + + assert _parse_app_instance_name('yolo') == ('yolo', 1) + assert _parse_app_instance_name('yolo1') == ('yolo1', 1) + assert _parse_app_instance_name('yolo__0') == ('yolo__0', 1) + assert _parse_app_instance_name('yolo__1') == ('yolo', 1) + assert _parse_app_instance_name('yolo__23') == ('yolo', 23) + assert _parse_app_instance_name('yolo__42__72') == ('yolo__42', 72) + assert _parse_app_instance_name('yolo__23qdqsd') == ('yolo__23qdqsd', 1) + assert _parse_app_instance_name('yolo__23qdqsd56') == ('yolo__23qdqsd56', 1) + + def test_repo_url_definition(): assert _is_app_repo_url("https://github.com/YunoHost-Apps/foobar123_ynh") assert _is_app_repo_url("https://github.com/YunoHost-Apps/foobar123_ynh/") From 6ad07d2c837bc894f89671e1dcc1d4d815c597ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Thu, 30 Sep 2021 04:30:15 +0000 Subject: [PATCH 0751/1155] Translated using Weblate (Galician) Currently translated at 100.0% (707 of 707 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 44 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/locales/gl.json b/locales/gl.json index ebb65be02..d70d7a561 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -17,7 +17,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}' no lugar de '{value}'", + "app_argument_choice_invalid": "Elixe un valor válido para o argumento '{name}': '{value}' non está entre as opcións dispoñibles ({choices})", "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}", @@ -102,7 +102,7 @@ "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}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}", "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.", @@ -455,7 +455,7 @@ "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 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.", + "diagnosis_dns_specialusedomain": "O dominio {domain} baséase un dominio de nivel alto e uso especial (TLD) como .local ou .test 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}", @@ -675,5 +675,39 @@ "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}" -} \ No newline at end of file + "service_not_reloading_because_conf_broken": "Non se recargou/reiniciou o servizo '{name}' porque a súa configuración está estragada: {errors}", + "diagnosis_http_special_use_tld": "O dominio {domain} baséase nun dominio de alto-nivel (TLD) especial como .local ou .test e por isto non é de agardar que esté exposto fóra da rede local.", + "domain_dns_conf_special_use_tld": "Este dominio baséase nun dominio de alto-nivel (TLD) de uso especial como .local ou .test e por isto non é de agardar que teña rexistros DNS asociados.", + "domain_dns_registrar_managed_in_parent_domain": "Este dominio é un subdominio de {parent_domain_link}. A configuración DNS debe xestionarse no panel de configuración de {parent_domain}'s.", + "domain_dns_registrar_not_supported": "YunoHost non é quen de detectar a rexistradora que xestiona o dominio. Debes configurar manualmente os seus rexistros DNS seguindo a documentación en https://yunohost.org/dns.", + "domain_dns_registrar_experimental": "Ata o momento, a interface coa API de **{registar}** aínda non foi comprobada e revisada pola comunidade YunoHost. O soporte é **moi experimental** - ten coidado!", + "domain_dns_push_failed_to_list": "Non se pode mostrar a lista actual de rexistros na API da rexistradora: {error}", + "domain_dns_push_already_up_to_date": "Rexistros ao día, nada que facer.", + "domain_dns_pushing": "Enviando rexistros DNS...", + "domain_dns_push_record_failed": "Fallou {action} do rexistro {type}/{name}: {error}", + "domain_dns_push_success": "Rexistros DNS actualizados!", + "domain_dns_push_failed": "Fallou completamente a actualización dos rexistros DNS.", + "domain_config_features_disclaimer": "Ata o momento, activar/desactivar as funcións de email ou XMPP só ten impacto na configuración automática da configuración DNS, non na configuración do sistema!", + "domain_config_mail_in": "Emails entrantes", + "domain_config_mail_out": "Emails saíntes", + "domain_config_xmpp": "Mensaxería instantánea (XMPP)", + "domain_config_auth_secret": "Segreda de autenticación", + "domain_config_api_protocol": "Protocolo API", + "domain_config_auth_application_key": "Chave da aplicación", + "domain_config_auth_application_secret": "Chave segreda da aplicación", + "domain_config_auth_consumer_key": "Chave consumidora", + "log_domain_dns_push": "Enviar rexistros DNS para o dominio '{}'", + "other_available_options": "... e outras {n} opcións dispoñibles non mostradas", + "domain_dns_registrar_yunohost": "Este dominio un dos de nohost.me / nohost.st / ynh.fr e a configuración DNS xestionaa directamente YunoHost se máis requisitos. (mira o comando 'yunohost dyndns update')", + "domain_dns_registrar_supported": "YunoHost detectou automáticamente que este dominio está xestionado pola rexistradora **{registar}**. Se queres, YunoHost pode configurar automáticamente as súas zonas DNS, se proporcionas as credenciais de acceso á API. Podes ver a documentación sobre como obter as credenciais da API nesta páxina: https://yunohost.org/registar_api_{registrar}. (Tamén podes configurar manualmente os rexistros DNS seguindo a documentación en https://yunohost.org/dns )", + "domain_dns_push_partial_failure": "Actualización parcial dos rexistros DNS: informouse dalgúns avisos/erros.", + "domain_config_auth_token": "Token de autenticación", + "domain_config_auth_key": "Chave de autenticación", + "domain_config_auth_entrypoint": "Punto de entrada da API", + "domain_dns_push_failed_to_authenticate": "Fallou a autenticación na API da rexistradora do dominio '{domain}'. Comprobaches que sexan as credenciais correctas? (Erro: {error})", + "domain_registrar_is_not_configured": "A rexistradora non aínda non está configurada para o dominio {domain}.", + "domain_dns_push_not_applicable": "A función de rexistro DNS automático non é aplicable ao dominio {domain}. Debes configurar manualmente os teus rexistros DNS seguindo a documentación de https://yunohost.org/dns_config.", + "domain_dns_push_managed_in_parent_domain": "A función de rexistro DNS automático está xestionada polo dominio nai {parent_domain}.", + "ldap_attribute_already_exists": "Xa existe o atributo LDAP '{attribute}' con valor '{value}'", + "log_domain_config_set": "Actualizar configuración para o dominio '{}'" +} From 707866e222105a744403cb286078dea472f52be3 Mon Sep 17 00:00:00 2001 From: Tymofii-Lytvynenko Date: Thu, 30 Sep 2021 19:03:23 +0000 Subject: [PATCH 0752/1155] Translated using Weblate (Ukrainian) Currently translated at 96.1% (680 of 707 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/uk/ --- locales/uk.json | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/locales/uk.json b/locales/uk.json index 35923908f..814fa56a9 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -16,7 +16,7 @@ "app_argument_required": "Аргумент '{name}' необхідний", "app_argument_password_no_default": "Помилка під час розбору аргументу пароля '{name}': аргумент пароля не може мати типове значення з причин безпеки", "app_argument_invalid": "Виберіть правильне значення для аргументу '{name}': {error}", - "app_argument_choice_invalid": "Використовуйте один з цих варіантів '{choices}' для аргументу '{name}' замість '{value}'", + "app_argument_choice_invalid": "Виберіть дійсне значення для аргументу '{name}': '{value}' не є серед доступних варіантів ({choices})", "app_already_up_to_date": "{app} має найостаннішу версію", "app_already_installed_cant_change_url": "Цей застосунок уже встановлено. URL-адреса не може бути змінена тільки цією функцією. Перевірте в `app changeurl`, якщо вона доступна.", "app_already_installed": "{app} уже встановлено", @@ -482,7 +482,7 @@ "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_specialusedomain": "Домен {domain} заснований на домені верхнього рівня спеціального призначення (TLD) такого як .local або .test і тому не очікується, що у нього будуть актуальні записи 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}
Назва: {name}
Поточне значення: {current}
Очікуване значення: {value}", @@ -504,7 +504,7 @@ "diagnosis_ip_connected_ipv4": "Сервер під'єднаний до Інтернету через IPv4!", "diagnosis_no_cache": "Для категорії «{category}» ще немає кеша діагностики", "diagnosis_failed": "Не вдалося отримати результат діагностики для категорії '{category}': {error}", - "diagnosis_everything_ok": "Усе виглядає добре для {category}!", + "diagnosis_everything_ok": "Здається, для категорії '{category}' все справно!", "diagnosis_found_warnings": "Знайдено {warnings} пунктів, які можна поліпшити для {category}.", "diagnosis_found_errors_and_warnings": "Знайдено {errors} істотний (і) питання (и) (і {warnings} попередження (я)), що відносяться до {category}!", "diagnosis_found_errors": "Знайдена {errors} важлива проблема (і), пов'язана з {category}!", @@ -675,5 +675,13 @@ "log_app_config_set": "Застосувати конфігурацію до застосунку '{}'", "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 + "app_argument_password_help_keep": "Натисніть Enter, щоб зберегти поточне значення", + "domain_registrar_is_not_configured": "Реєстратор ще не конфігуровано для домену {domain}.", + "domain_dns_push_not_applicable": "Функція автоматичної конфігурації DNS не застосовується до домену {domain}. Вам слід вручну конфігурувати записи DNS відповідно до документації за адресою https://yunohost.org/dns_config.", + "domain_dns_registrar_not_supported": "YunoHost не зміг автоматично виявити реєстратора, який обробляє цей домен. Вам слід вручну конфігурувати записи DNS відповідно до документації за адресою https://yunohost.org/dns.", + "diagnosis_http_special_use_tld": "Домен {domain} базується на спеціальному домені верхнього рівня (TLD), такому як .local або .test, і тому не очікується, що він буде відкритий за межами локальної мережі.", + "domain_dns_push_managed_in_parent_domain": "Функцією автоконфігурації DNS керує батьківський домен {parent_domain}.", + "domain_dns_registrar_managed_in_parent_domain": "Цей домен є піддоменом {parent_domain_link}. Конфігурацією реєстратора DNS слід керувати на панелі конфігурації {parent_domain}.", + "domain_dns_registrar_yunohost": "Цей домен є nohost.me/nohost.st/ynh.fr, тому його конфігурація DNS автоматично обробляється YunoHost без будь-якої подальшої конфігурації. (див. команду 'yunohost dyndns update')", + "domain_dns_conf_special_use_tld": "Цей домен засновано на спеціальному домені верхнього рівня (TLD), такому як .local або .test, і тому не очікується, що він матиме актуальні записи DNS." +} From fe82fafa6d4cf565903e206c5bf918f5de28a577 Mon Sep 17 00:00:00 2001 From: ppr Date: Sun, 3 Oct 2021 09:15:09 +0000 Subject: [PATCH 0753/1155] Translated using Weblate (French) Currently translated at 99.1% (700 of 706 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 29e6da673..ba2042ffb 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}'. Les valeurs acceptées sont {choices}, au lieu de '{value}'", + "app_argument_choice_invalid": "Choisir une valeur valide pour l'argument '{name}' : '{value}' ne fait pas partie des choix disponibles ({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", @@ -632,7 +632,7 @@ "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.", + "diagnosis_dns_specialusedomain": "Le domaine {domain} est basé sur un domaine de premier niveau (TLD) à usage spécial comme .local ou .test 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", @@ -705,5 +705,7 @@ "domain_config_auth_application_secret": "Clé secrète de l'application", "ldap_attribute_already_exists": "L'attribut LDAP '{attribute}' existe déjà avec la valeur '{value}'", "log_domain_config_set": "Mettre à jour la configuration du domaine '{}'", - "log_domain_dns_push": "Pousser les enregistrements DNS pour le domaine '{}'" + "log_domain_dns_push": "Pousser les enregistrements DNS pour le domaine '{}'", + "diagnosis_http_special_use_tld": "Le domaine {domain} est basé sur un domaine de premier niveau (TLD) à usage spécial tel que .local ou .test et n'est donc pas censé être exposé en dehors du réseau local.", + "domain_dns_conf_special_use_tld": "Ce domaine est basé sur un domaine de premier niveau (TLD) à usage spécial tel que .local ou .test et ne devrait donc pas avoir d'enregistrements DNS réels." } From 9019d75cb8281873b62b4d51aa0211f0734bca0a Mon Sep 17 00:00:00 2001 From: ppr Date: Sun, 3 Oct 2021 09:18:06 +0000 Subject: [PATCH 0754/1155] Translated using Weblate (French) Currently translated at 99.7% (704 of 706 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index ba2042ffb..10f05a848 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -593,7 +593,7 @@ "diagnosis_package_installed_from_sury": "Des paquets du système devraient être rétrogradé de version", "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_regex": "Vous ne pouvez pas activer 'show_tile' pour le moment, cela 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}'", "regex_with_only_domain": "Vous ne pouvez pas utiliser une expression régulière pour le domaine, uniquement pour le chemin", "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", @@ -678,7 +678,7 @@ "app_argument_password_help_optional": "Tapez un espace pour vider le mot de passe", "domain_registrar_is_not_configured": "Le registrar n'est pas encore configuré pour le domaine {domain}.", "domain_dns_push_not_applicable": "La fonction de configuration DNS automatique n'est pas applicable au domaine {domain}. Vous devez configurer manuellement vos enregistrements DNS en suivant la documentation sur https://yunohost.org/dns_config.", - "domain_dns_registrar_yunohost": "Ce domaine est nohost.me / nohost.st / ynh.fr et sa configuration DNS est donc automatiquement gérée par YunoHost sans autre configuration. (voir la commande 'yunohost dyndns update')", + "domain_dns_registrar_yunohost": "Ce domaine est de type nohost.me / nohost.st / ynh.fr et sa configuration DNS est donc automatiquement gérée par YunoHost sans qu'il n'y ait d'autre configuration à faire. (voir la commande 'yunohost dyndns update')", "domain_dns_registrar_supported": "YunoHost a détecté automatiquement que ce domaine est géré par le registrar **{registrar}**. Si vous le souhaitez, YunoHost configurera automatiquement cette zone DNS, si vous lui fournissez les identifiants API appropriés. Vous pouvez trouver de la documentation sur la façon d'obtenir vos identifiants API sur cette page : https://yunohost.org/registar_api_{registrar}. (Vous pouvez également configurer manuellement vos enregistrements DNS en suivant la documentation sur https://yunohost.org/dns )", "domain_config_features_disclaimer": "Jusqu'à présent, l'activation/désactivation des fonctionnalités de messagerie ou XMPP n'a d'impact que sur la configuration DNS recommandée et automatique, et non sur les configurations système !", "domain_dns_push_managed_in_parent_domain": "La fonctionnalité de configuration DNS automatique est gérée dans le domaine parent {parent_domain}.", @@ -707,5 +707,7 @@ "log_domain_config_set": "Mettre à jour la configuration du domaine '{}'", "log_domain_dns_push": "Pousser les enregistrements DNS pour le domaine '{}'", "diagnosis_http_special_use_tld": "Le domaine {domain} est basé sur un domaine de premier niveau (TLD) à usage spécial tel que .local ou .test et n'est donc pas censé être exposé en dehors du réseau local.", - "domain_dns_conf_special_use_tld": "Ce domaine est basé sur un domaine de premier niveau (TLD) à usage spécial tel que .local ou .test et ne devrait donc pas avoir d'enregistrements DNS réels." + "domain_dns_conf_special_use_tld": "Ce domaine est basé sur un domaine de premier niveau (TLD) à usage spécial tel que .local ou .test et ne devrait donc pas avoir d'enregistrements DNS réels.", + "other_available_options": "... et {n} autres options disponibles non affichées", + "domain_config_auth_consumer_key": "Consumer key" } From 98c411ec9e09fe332346f7f68c208da170903045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Sun, 3 Oct 2021 10:03:26 +0000 Subject: [PATCH 0755/1155] Translated using Weblate (French) Currently translated at 100.0% (706 of 706 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 10f05a848..08224a2a0 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -685,7 +685,7 @@ "domain_dns_registrar_managed_in_parent_domain": "Ce domaine est un sous-domaine de {parent_domain_link}. La configuration du registrar DNS doit être gérée dans le panneau de configuration de {parent_domain}.", "domain_dns_registrar_not_supported": "YunoHost n'a pas pu détecter automatiquement le bureau d'enregistrement gérant ce domaine. Vous devez configurer manuellement vos enregistrements DNS en suivant la documentation sur https://yunohost.org/dns.", "domain_dns_registrar_experimental": "Jusqu'à présent, l'interface avec l'API de **{registrar}** n'a pas été correctement testée et revue par la communauté YunoHost. L'assistance est **très expérimentale** - soyez prudent !", - "domain_dns_push_failed_to_authenticate": "Échec de l'authentification sur l'API du bureau d'enregistrement pour le domaine « {domain} ». Très probablement les informations d'identification sont incorrectes ? (Error: {error})", + "domain_dns_push_failed_to_authenticate": "Échec de l'authentification sur l'API du bureau d'enregistrement pour le domaine « {domain} ». Très probablement les informations d'identification sont incorrectes ? (Erreur : {error})", "domain_dns_push_failed_to_list": "Échec de la liste des enregistrements actuels à l'aide de l'API du registraire : {error}", "domain_dns_push_already_up_to_date": "Dossiers déjà à jour.", "domain_dns_pushing": "Transmission des enregistrements DNS...", From fb4d870e1f871764e65db267c7c6f55ddaab9c64 Mon Sep 17 00:00:00 2001 From: mifegui Date: Sun, 3 Oct 2021 15:56:23 +0000 Subject: [PATCH 0756/1155] Translated using Weblate (Portuguese) Currently translated at 29.4% (208 of 706 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/pt/ --- locales/pt.json | 71 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 67 insertions(+), 4 deletions(-) diff --git a/locales/pt.json b/locales/pt.json index 534e0cb27..d285948be 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -109,7 +109,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}' em vez de '{value}'", + "app_argument_choice_invalid": "Escolha um valor válido para o argumento '{name}' : '{value}' não está entre as opções disponíveis ({choices})", "app_argument_invalid": "Escolha um valor válido para o argumento '{name}': {error}", "app_argument_required": "O argumento '{name}' é obrigatório", "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}", @@ -182,7 +182,7 @@ "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.", @@ -191,5 +191,68 @@ "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" -} \ No newline at end of file + "backup_output_directory_required": "Você deve especificar um diretório de saída para o backup", + "diagnosis_description_apps": "Aplicações", + "diagnosis_apps_allgood": "Todos os apps instalados respeitam práticas básicas de empacotamento", + "diagnosis_apps_issue": "Um problema foi encontrado para o app {app}", + "diagnosis_apps_not_in_app_catalog": "Esta aplicação não está no catálogo de aplicações do YunoHost. Se estava no passado e foi removida, você deve considerar desinstalar este app já que ele não mais receberá atualizações e pode comprometer a integridade e segurança do seu sistema.", + "diagnosis_apps_broken": "Esta aplicação está atualmente marcada como quebrada no catálogo de apps do YunoHost. Isto pode ser um problema temporário enquanto os mantenedores consertam o problema. Enquanto isso, atualizar este app está desabilitado.", + "diagnosis_apps_bad_quality": "Esta aplicação está atualmente marcada como quebrada no catálogo de apps do YunoHost. Isto pode ser um problema temporário enquanto os mantenedores consertam o problema. Enquanto isso, atualizar este app está desabilitado.", + "diagnosis_apps_outdated_ynh_requirement": "A versão instalada deste app requer tão somente yunohost >= 2.x, o que tende a indicar que o app não está atualizado com as práticas de empacotamento recomendadas. Você deve considerar seriamente atualizá-lo.", + "diagnosis_apps_deprecated_practices": "A versão instalada deste app usa práticas de empacotamento extremamente velhas que não são mais usadas. Você deve considerar seriamente atualizá-lo.", + "certmanager_domain_http_not_working": "O domínio {domain} não parece estar acessível por HTTP. Por favor cheque a categoria 'Web' no diagnóstico para mais informações. (Se você sabe o que está fazendo, use '--no-checks' para desativar estas checagens.)", + "diagnosis_description_regenconf": "Configurações do sistema", + "diagnosis_description_services": "Cheque de status dos serviços", + "diagnosis_basesystem_hardware": "A arquitetura hardware do servidor é {virt} {arch}", + "diagnosis_description_web": "Web", + "diagnosis_basesystem_ynh_single_version": "Versão {package}: {version} ({repo})", + "diagnosis_basesystem_ynh_main_version": "O servidor está rodando YunoHost {main_version} ({repo})", + "app_config_unable_to_apply": "Falha ao aplicar valores do painel de configuração.", + "app_config_unable_to_read": "Falha ao ler valores do painel de configuração.", + "config_apply_failed": "Aplicar as novas configuração falhou: {error}", + "config_cant_set_value_on_section": "Você não pode setar um único valor na seção de configuração inteira.", + "config_validate_time": "Deve ser um horário válido como HH:MM", + "config_validate_url": "Deve ser uma URL válida", + "config_version_not_supported": "Versões do painel de configuração '{version}' não são suportadas.", + "danger": "Perigo:", + "diagnosis_basesystem_ynh_inconsistent_versions": "Você está executando versões inconsistentes dos pacotes YunoHost... provavelmente por causa de uma atualização parcial ou que falhou.", + "diagnosis_description_basesystem": "Sistema base", + "certmanager_cert_signing_failed": "Não foi possível assinar o novo certificado", + "certmanager_unable_to_parse_self_CA_name": "Não foi possível processar nome da autoridade de auto-assinatura (arquivo: {file})", + "confirm_app_install_warning": "Aviso: Pode ser que essa aplicação funcione, mas ela não está bem integrada ao YunoHost. Algumas funcionalidades como single sign-on e backup/restauração podem não estar disponíveis. Instalar mesmo assim? [{answers}] ", + "config_forbidden_keyword": "A palavra chave '{keyword}' é reservada, você não pode criar ou usar um painel de configuração com uma pergunta com esse id.", + "config_no_panel": "Painel de configuração não encontrado.", + "config_unknown_filter_key": "A chave de filtro '{filter_key}' está incorreta.", + "config_validate_color": "Deve ser uma cor RGB hexadecimal válida", + "config_validate_date": "Deve ser uma data válida como no formato AAAA-MM-DD", + "config_validate_email": "Deve ser um email válido", + "diagnosis_basesystem_kernel": "O servidor está rodando Linux kernel {kernel_version}", + "diagnosis_cache_still_valid": "(O cache para a categoria de diagnóstico {category} ainda é valido. Não será diagnosticada novamente ainda)", + "diagnosis_cant_run_because_of_dep": "Impossível fazer diagnóstico para {category} enquanto ainda existem problemas importantes relacionados a {dep}.", + "diagnosis_diskusage_low": "Unidade de armazenamento {mountpoint} (no dispositivo {device}_) tem somente {free} ({free_percent}%) de espaço restante (de {total}). Tenha cuidado.", + "diagnosis_description_ip": "Conectividade internet", + "diagnosis_description_dnsrecords": "Registros DNS", + "diagnosis_description_mail": "Email", + "certmanager_domain_not_diagnosed_yet": "Ainda não há resultado de diagnóstico para o domínio {domain}. Por favor re-execute um diagnóstico para as categorias 'Registros DNS' e 'Web' na seção de diagnósticos para checar se o domínio está pronto para o Let's Encrypt. (Ou, se você souber o que está fazendo, use '--no-checks' para desativar estas checagens.)", + "diagnosis_basesystem_host": "O Servidor está rodando Debian {debian_version}", + "diagnosis_description_systemresources": "Recursos do sistema", + "certmanager_acme_not_configured_for_domain": "O challenge ACME não pode ser realizado para {domain} porque o código correspondente na configuração do nginx está ausente... Por favor tenha certeza de que sua configuração do nginx está atualizada executando o comando `yunohost tools regen-conf nginx --dry-run --with-diff`.", + "certmanager_attempt_to_renew_nonLE_cert": "O certificado para o domínio '{domain}' não foi emitido pelo Let's Encrypt. Não é possível renová-lo automaticamente!", + "certmanager_attempt_to_renew_valid_cert": "O certificado para o domínio '{domain}' não esta prestes a expirar! (Você pode usar --force se saber o que está fazendo)", + "certmanager_cannot_read_cert": "Algo de errado aconteceu ao tentar abrir o atual certificado para o domínio {domain} (arquivo: {file}), motivo: {reason}", + "certmanager_cert_install_success": "Certificado Let's Encrypt foi instalado para o domínio '{domain}'", + "certmanager_cert_install_success_selfsigned": "Certificado autoassinado foi instalado para o domínio '{domain}'", + "certmanager_certificate_fetching_or_enabling_failed": "Tentativa de usar o novo certificado para o domínio {domain} não funcionou...", + "certmanager_domain_cert_not_selfsigned": "O certificado para o domínio {domain} não é autoassinado. Você tem certeza que quer substituí-lo? (Use '--force' para fazê-lo)", + "certmanager_domain_dns_ip_differs_from_public_ip": "O registro de DNS para o domínio '{domain}' é diferente do IP deste servidor. Por favor cheque a categoria 'Registros DNS' (básico) no diagnóstico para mais informações. Se você modificou recentemente o registro 'A', espere um tempo para ele se propagar (alguns serviços de checagem de propagação de DNS estão disponíveis online). (Se você sabe o que está fazendo, use '--no-checks' para desativar estas checagens.)", + "certmanager_hit_rate_limit": "Foram emitidos certificados demais para este conjunto de domínios {domain} recentemente. Por favor tente novamente mais tarde. Veja https://letsencrypt.org/docs/rate-limits/ para mais detalhes", + "certmanager_no_cert_file": "Não foi possível ler o arquivo de certificado para o domínio {domain} (arquivo: {file})", + "certmanager_self_ca_conf_file_not_found": "Não foi possível encontrar o arquivo de configuração para a autoridade de auto-assinatura (arquivo: {file})", + "confirm_app_install_danger": "ATENÇÃO! Sabe-se que esta aplicação ainda é experimental (isso se não que explicitamente não funciona)! Você provavelmente NÃO deve instalar ela a não ser que você saiba o que você está fazendo. NENHUM SUPORTE será fornecido se esta aplicação não funcionar ou quebrar o seu sistema... Se você está disposto a tomar esse rico de toda forma, digite '{answers}'", + "confirm_app_install_thirdparty": "ATENÇÃO! Essa aplicação não faz parte do catálogo do YunoHost. Instalar aplicações de terceiros pode comprometer a integridade e segurança do seu sistema. Você provavelmente NÃO deve instalá-la a não ser que você saiba o que você está fazendo. NENHUM SUPORTE será fornecido se este app não funcionar ou quebrar seu sistema... Se você está disposto a tomar este risco de toda forma, digite '{answers}'", + "diagnosis_description_ports": "Exposição de portas", + "diagnosis_basesystem_hardware_model": "O modelo do servidor é {model}", + "diagnosis_backports_in_sources_list": "Parece que o apt (o gerenciador de pacotes) está configurado para usar o repositório backport. A não ser que você saiba o que você esteá fazendo, desencorajamos fortemente a instalação de pacotes de backports porque é provável que crie instabilidades ou conflitos no seu sistema.", + "certmanager_cert_renew_success": "Certificado Let's Encrypt renovado para o domínio '{domain}'", + "certmanager_warning_subdomain_dns_record": "O subdomínio '{subdomain}' não resolve para o mesmo IP que '{domain}'. Algumas funcionalidades não estarão disponíveis até que você conserte isto e regenere o certificado." +} From 30ed2cd0b0d59328f7bb228d876406e7bac78ffe Mon Sep 17 00:00:00 2001 From: Tymofii-Lytvynenko Date: Sun, 3 Oct 2021 14:20:03 +0000 Subject: [PATCH 0757/1155] Translated using Weblate (Ukrainian) Currently translated at 100.0% (706 of 706 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/uk/ --- locales/uk.json | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/locales/uk.json b/locales/uk.json index 814fa56a9..b54d81fbd 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -683,5 +683,31 @@ "domain_dns_push_managed_in_parent_domain": "Функцією автоконфігурації DNS керує батьківський домен {parent_domain}.", "domain_dns_registrar_managed_in_parent_domain": "Цей домен є піддоменом {parent_domain_link}. Конфігурацією реєстратора DNS слід керувати на панелі конфігурації {parent_domain}.", "domain_dns_registrar_yunohost": "Цей домен є nohost.me/nohost.st/ynh.fr, тому його конфігурація DNS автоматично обробляється YunoHost без будь-якої подальшої конфігурації. (див. команду 'yunohost dyndns update')", - "domain_dns_conf_special_use_tld": "Цей домен засновано на спеціальному домені верхнього рівня (TLD), такому як .local або .test, і тому не очікується, що він матиме актуальні записи DNS." + "domain_dns_conf_special_use_tld": "Цей домен засновано на спеціальному домені верхнього рівня (TLD), такому як .local або .test, і тому не очікується, що він матиме актуальні записи DNS.", + "domain_dns_registrar_supported": "YunoHost автоматично визначив, що цей домен обслуговується реєстратором **{registrar}**. Якщо ви хочете, YunoHost автоматично налаштує цю DNS-зону, якщо ви надасте йому відповідні облікові дані API. Ви можете знайти документацію про те, як отримати реєстраційні дані API на цій сторінці: https://yunohost.org/registar_api_{registrar}. (Ви також можете вручну налаштувати свої DNS-записи, дотримуючись документації на https://yunohost.org/dns)", + "domain_dns_registrar_experimental": "Поки що інтерфейс з API **{registrar}** не був належним чином протестований і перевірений спільнотою YunoHost. Підтримка є **дуже експериментальною** - будьте обережні!", + "domain_dns_push_success": "Записи DNS оновлено!", + "domain_dns_push_failed": "Оновлення записів DNS зазнало невдачі.", + "domain_dns_push_partial_failure": "DNS-записи частково оновлено: повідомлялося про деякі попередження/помилки.", + "domain_config_mail_in": "Вхідні електронні листи", + "domain_config_mail_out": "Вихідні електронні листи", + "domain_config_auth_token": "Токен автентифікації", + "domain_config_auth_entrypoint": "Точка входу API", + "domain_config_auth_consumer_key": "Ключ споживача", + "domain_dns_push_failed_to_authenticate": "Неможливо пройти автентифікацію на API реєстратора для домену '{domain}'. Ймовірно, облікові дані недійсні? (Помилка: {error})", + "domain_dns_push_failed_to_list": "Не вдалося скласти список поточних записів за допомогою API реєстратора: {error}", + "domain_dns_push_record_failed": "Не вдалося виконати дію {action} запису {type}/{name} : {error}", + "domain_config_features_disclaimer": "Поки що вмикання/вимикання функцій пошти або XMPP впливає тільки на рекомендовану та автоконфігурацію DNS, але не на конфігурацію системи!", + "domain_config_xmpp": "Миттєвий обмін повідомленнями (XMPP)", + "domain_config_auth_key": "Ключ автентифікації", + "domain_config_auth_secret": "Секрет автентифікації", + "domain_config_api_protocol": "API-протокол", + "domain_config_auth_application_key": "Ключ застосунку", + "domain_config_auth_application_secret": "Таємний ключ застосунку", + "log_domain_config_set": "Оновлення конфігурації для домену '{}'", + "log_domain_dns_push": "Передавання записів DNS для домену '{}'", + "other_available_options": "...і {n} інших доступних опцій, які не показано", + "domain_dns_pushing": "Передання записів DNS...", + "ldap_attribute_already_exists": "Атрибут LDAP '{attribute}' вже існує зі значенням '{value}'", + "domain_dns_push_already_up_to_date": "Записи вже оновлені, нічого не потрібно робити." } From 592a998230eb44a3365bd8a795c53598dc76e35b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 4 Oct 2021 01:28:49 +0200 Subject: [PATCH 0758/1155] 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 08224a2a0..123270bd6 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": "Choisir une valeur valide pour l'argument '{name}' : '{value}' ne fait pas partie des choix disponibles ({choices})", + "app_argument_choice_invalid": "Choisissez une valeur valide pour l'argument '{name}' : '{value}' ne fait pas partie des choix disponibles ({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", From a552700ca344f21fa390f03e67a5bfedd64bb4db Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 4 Oct 2021 01:34:16 +0200 Subject: [PATCH 0759/1155] Update changelog for 4.3.1.1 --- debian/changelog | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/debian/changelog b/debian/changelog index 84a29ed70..a10c83888 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,13 @@ +yunohost (4.3.1.1) testing; urgency=low + + - [enh] app helpers: Update n version ([#1347](https://github.com/YunoHost/yunohost/pull/1347)) + - [enh] Misc app.py refactoring + Prevent change_url from being used to move a fulldomain app to a subpath ([#1346](https://github.com/YunoHost/yunohost/pull/1346)) + - [i18n] Translations updated for French, Galician, Portuguese, Ukrainian + + Thanks to all contributors <3 ! (Éric Gaspar, José M, mifegui, ppr, Tymofii-Lytvynenko) + + -- Alexandre Aubin Mon, 04 Oct 2021 01:33:22 +0200 + yunohost (4.3.1) testing; urgency=low - [fix] diagnosis: new app diagnosis grep reporing comments as issues ([#1333](https://github.com/YunoHost/yunohost/pull/1333)) From a68f98d8007be816d07695125502a8346d1dbbab Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Sun, 3 Oct 2021 23:56:11 +0000 Subject: [PATCH 0760/1155] [CI] Format code --- src/yunohost/tests/test_appurl.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/yunohost/tests/test_appurl.py b/src/yunohost/tests/test_appurl.py index ca953dcf7..cf2c6c2c3 100644 --- a/src/yunohost/tests/test_appurl.py +++ b/src/yunohost/tests/test_appurl.py @@ -4,7 +4,12 @@ import os from .conftest import get_test_apps_dir from yunohost.utils.error import YunohostError -from yunohost.app import app_install, app_remove, _is_app_repo_url, _parse_app_instance_name +from yunohost.app import ( + app_install, + app_remove, + _is_app_repo_url, + _parse_app_instance_name, +) from yunohost.domain import _get_maindomain, domain_url_available from yunohost.permission import _validate_and_sanitize_permission_url @@ -30,14 +35,14 @@ def teardown_function(function): def test_parse_app_instance_name(): - assert _parse_app_instance_name('yolo') == ('yolo', 1) - assert _parse_app_instance_name('yolo1') == ('yolo1', 1) - assert _parse_app_instance_name('yolo__0') == ('yolo__0', 1) - assert _parse_app_instance_name('yolo__1') == ('yolo', 1) - assert _parse_app_instance_name('yolo__23') == ('yolo', 23) - assert _parse_app_instance_name('yolo__42__72') == ('yolo__42', 72) - assert _parse_app_instance_name('yolo__23qdqsd') == ('yolo__23qdqsd', 1) - assert _parse_app_instance_name('yolo__23qdqsd56') == ('yolo__23qdqsd56', 1) + assert _parse_app_instance_name("yolo") == ("yolo", 1) + assert _parse_app_instance_name("yolo1") == ("yolo1", 1) + assert _parse_app_instance_name("yolo__0") == ("yolo__0", 1) + assert _parse_app_instance_name("yolo__1") == ("yolo", 1) + assert _parse_app_instance_name("yolo__23") == ("yolo", 23) + assert _parse_app_instance_name("yolo__42__72") == ("yolo__42", 72) + assert _parse_app_instance_name("yolo__23qdqsd") == ("yolo__23qdqsd", 1) + assert _parse_app_instance_name("yolo__23qdqsd56") == ("yolo__23qdqsd56", 1) def test_repo_url_definition(): From 75b36f3b4a816e9410c909043a759649ff253f8a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 4 Oct 2021 03:40:41 +0200 Subject: [PATCH 0761/1155] tests: Try to fix mypy again /o\ --- src/yunohost/app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 1c05ce20b..821ef06b0 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2418,13 +2418,13 @@ def _parse_app_instance_name(app_instance_name: str) -> Tuple[str, int]: match = re_app_instance_name.match(app_instance_name) assert match, f"Could not parse app instance name : {app_instance_name}" appid = match.groupdict().get("appid") - app_instance_nb = match.groupdict().get("appinstancenb") or "1" + app_instance_nb_ = match.groupdict().get("appinstancenb") or "1" if not appid: raise Exception(f"Could not parse app instance name : {app_instance_name}") - if not str(app_instance_nb).isdigit(): + if not str(app_instance_nb_).isdigit(): raise Exception(f"Could not parse app instance name : {app_instance_name}") else: - app_instance_nb = int(str(app_instance_nb)) + app_instance_nb = int(str(app_instance_nb_)) return (appid, app_instance_nb) From 2157576bf6446f95243a3a848bf0ec990ee015c0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 4 Oct 2021 03:47:12 +0200 Subject: [PATCH 0762/1155] i18n typos --- locales/gl.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/gl.json b/locales/gl.json index d70d7a561..987093df8 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -680,7 +680,7 @@ "domain_dns_conf_special_use_tld": "Este dominio baséase nun dominio de alto-nivel (TLD) de uso especial como .local ou .test e por isto non é de agardar que teña rexistros DNS asociados.", "domain_dns_registrar_managed_in_parent_domain": "Este dominio é un subdominio de {parent_domain_link}. A configuración DNS debe xestionarse no panel de configuración de {parent_domain}'s.", "domain_dns_registrar_not_supported": "YunoHost non é quen de detectar a rexistradora que xestiona o dominio. Debes configurar manualmente os seus rexistros DNS seguindo a documentación en https://yunohost.org/dns.", - "domain_dns_registrar_experimental": "Ata o momento, a interface coa API de **{registar}** aínda non foi comprobada e revisada pola comunidade YunoHost. O soporte é **moi experimental** - ten coidado!", + "domain_dns_registrar_experimental": "Ata o momento, a interface coa API de **{registrar}** aínda non foi comprobada e revisada pola comunidade YunoHost. O soporte é **moi experimental** - ten coidado!", "domain_dns_push_failed_to_list": "Non se pode mostrar a lista actual de rexistros na API da rexistradora: {error}", "domain_dns_push_already_up_to_date": "Rexistros ao día, nada que facer.", "domain_dns_pushing": "Enviando rexistros DNS...", @@ -699,7 +699,7 @@ "log_domain_dns_push": "Enviar rexistros DNS para o dominio '{}'", "other_available_options": "... e outras {n} opcións dispoñibles non mostradas", "domain_dns_registrar_yunohost": "Este dominio un dos de nohost.me / nohost.st / ynh.fr e a configuración DNS xestionaa directamente YunoHost se máis requisitos. (mira o comando 'yunohost dyndns update')", - "domain_dns_registrar_supported": "YunoHost detectou automáticamente que este dominio está xestionado pola rexistradora **{registar}**. Se queres, YunoHost pode configurar automáticamente as súas zonas DNS, se proporcionas as credenciais de acceso á API. Podes ver a documentación sobre como obter as credenciais da API nesta páxina: https://yunohost.org/registar_api_{registrar}. (Tamén podes configurar manualmente os rexistros DNS seguindo a documentación en https://yunohost.org/dns )", + "domain_dns_registrar_supported": "YunoHost detectou automáticamente que este dominio está xestionado pola rexistradora **{registrar}**. Se queres, YunoHost pode configurar automáticamente as súas zonas DNS, se proporcionas as credenciais de acceso á API. Podes ver a documentación sobre como obter as credenciais da API nesta páxina: https://yunohost.org/registrar_api_{registrar}. (Tamén podes configurar manualmente os rexistros DNS seguindo a documentación en https://yunohost.org/dns )", "domain_dns_push_partial_failure": "Actualización parcial dos rexistros DNS: informouse dalgúns avisos/erros.", "domain_config_auth_token": "Token de autenticación", "domain_config_auth_key": "Chave de autenticación", From 4739859598cc7240a58a6175941f109cb78b8ab6 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Mon, 4 Oct 2021 11:52:12 +0200 Subject: [PATCH 0763/1155] fix app upgrade --- 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 821ef06b0..81b79ff48 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -507,7 +507,7 @@ def app_upgrade(app=[], url=None, file=None, force=False, no_safety_backup=False logger.warning(m18n.n("custom_app_url_required", app=app_instance_name)) continue elif app_dict["upgradable"] == "yes" or force: - new_app_src = app_dict["id"] + new_app_src = app_dict["manifest"]["id"] else: logger.success(m18n.n("app_already_up_to_date", app=app_instance_name)) continue From 9863263a284c933a29f8e001b73996db087f3629 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Mon, 4 Oct 2021 12:21:10 +0200 Subject: [PATCH 0764/1155] test to install/upgrade/remove an app from the manifest --- src/yunohost/tests/test_apps.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/yunohost/tests/test_apps.py b/src/yunohost/tests/test_apps.py index 43125341b..db75aad02 100644 --- a/src/yunohost/tests/test_apps.py +++ b/src/yunohost/tests/test_apps.py @@ -189,6 +189,29 @@ def test_legacy_app_install_main_domain(): assert app_is_not_installed(main_domain, "legacy_app") +def test_app_from_catalog(): + main_domain = _get_maindomain() + + app_install("my_webapp", args="domain={main_domain}&path=/site&with_sftp=0&password=superpassword&is_public=1&with_mysql=0") + app_map_ = app_map(raw=True) + assert main_domain in app_map_ + assert "/site" in app_map_[main_domain] + assert "id" in app_map_[main_domain]["/site"] + assert app_map_[main_domain]["/site"]["id"] == "my_webapp" + + assert app_is_installed(main_domain, "my_webapp") + assert app_is_exposed_on_http(main_domain, "/site", "Custom Web App") + + # Try upgrade, should do nothing + app_upgrade("my_webapp") + # Force upgrade, should upgrade to the same version + app_upgrade("my_webapp", force=True) + + app_remove("my_webapp") + + assert app_is_not_installed(main_domain, "my_webapp") + + def test_legacy_app_install_secondary_domain(secondary_domain): install_legacy_app(secondary_domain, "/legacy") From 96e7f1444d866d1603014373756d5dea74e2de12 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Mon, 4 Oct 2021 13:29:46 +0200 Subject: [PATCH 0765/1155] fix string in test_apps --- 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 db75aad02..bba769ec4 100644 --- a/src/yunohost/tests/test_apps.py +++ b/src/yunohost/tests/test_apps.py @@ -192,7 +192,7 @@ def test_legacy_app_install_main_domain(): def test_app_from_catalog(): main_domain = _get_maindomain() - app_install("my_webapp", args="domain={main_domain}&path=/site&with_sftp=0&password=superpassword&is_public=1&with_mysql=0") + app_install("my_webapp", args=f"domain={main_domain}&path=/site&with_sftp=0&password=superpassword&is_public=1&with_mysql=0") app_map_ = app_map(raw=True) assert main_domain in app_map_ assert "/site" in app_map_[main_domain] From 32998b118586259481b950f4b7874473e9f57fd5 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Mon, 4 Oct 2021 14:33:30 +0200 Subject: [PATCH 0766/1155] my_webapp is to clean too --- 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 bba769ec4..33af7256f 100644 --- a/src/yunohost/tests/test_apps.py +++ b/src/yunohost/tests/test_apps.py @@ -41,7 +41,7 @@ def clean(): os.system("mkdir -p /etc/ssowat/") app_ssowatconf() - test_apps = ["break_yo_system", "legacy_app", "legacy_app__2", "full_domain_app"] + test_apps = ["break_yo_system", "legacy_app", "legacy_app__2", "full_domain_app", "my_webapp"] for test_app in test_apps: From 27721749da10db3979b5629fdb3bbe83f3e00b89 Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Mon, 4 Oct 2021 12:50:57 +0000 Subject: [PATCH 0767/1155] [CI] Format code --- src/yunohost/tests/test_apps.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/yunohost/tests/test_apps.py b/src/yunohost/tests/test_apps.py index 33af7256f..22e18ec9a 100644 --- a/src/yunohost/tests/test_apps.py +++ b/src/yunohost/tests/test_apps.py @@ -41,7 +41,13 @@ def clean(): os.system("mkdir -p /etc/ssowat/") app_ssowatconf() - test_apps = ["break_yo_system", "legacy_app", "legacy_app__2", "full_domain_app", "my_webapp"] + test_apps = [ + "break_yo_system", + "legacy_app", + "legacy_app__2", + "full_domain_app", + "my_webapp", + ] for test_app in test_apps: @@ -192,7 +198,10 @@ def test_legacy_app_install_main_domain(): def test_app_from_catalog(): main_domain = _get_maindomain() - app_install("my_webapp", args=f"domain={main_domain}&path=/site&with_sftp=0&password=superpassword&is_public=1&with_mysql=0") + app_install( + "my_webapp", + args=f"domain={main_domain}&path=/site&with_sftp=0&password=superpassword&is_public=1&with_mysql=0", + ) app_map_ = app_map(raw=True) assert main_domain in app_map_ assert "/site" in app_map_[main_domain] From b677157924defca575aa0632fb9f9f45ebde1a6b Mon Sep 17 00:00:00 2001 From: Kay0u Date: Mon, 4 Oct 2021 15:17:59 +0200 Subject: [PATCH 0768/1155] fix mypy test --- .gitlab/ci/lint.gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab/ci/lint.gitlab-ci.yml b/.gitlab/ci/lint.gitlab-ci.yml index bffd3d7c4..0010e756a 100644 --- a/.gitlab/ci/lint.gitlab-ci.yml +++ b/.gitlab/ci/lint.gitlab-ci.yml @@ -23,7 +23,7 @@ mypy: image: "before-install" needs: [] script: - - tox -e py37-mypy + - tox -e py39-mypy format-check: stage: lint From 54d901ad7820f3bb1895fba86ff651add08a048b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Oct 2021 12:26:21 +0200 Subject: [PATCH 0769/1155] config: handle case where file quetion didnt get modified from webadmin, in which case self.value contains a path --- src/yunohost/utils/config.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 27a9e1533..2a1159042 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -1019,10 +1019,9 @@ class FileQuestion(Question): FileQuestion.upload_dirs += [upload_dir] logger.debug(f"Saving file {self.name} for file question into {file_path}") - if Moulinette.interface.type != "api": + if Moulinette.interface.type != "api" or (self.value.startswith("/") and os.path.exists(self.value)): content = read_file(str(self.value), file_mode="rb") - - if Moulinette.interface.type == "api": + else: content = b64decode(self.value) write_to_file(file_path, content, file_mode="wb") From 753c4e34ed37889efc7846951042fff2c7836589 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Oct 2021 12:32:23 +0200 Subject: [PATCH 0770/1155] Typo/wording --- 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 d12316996..accedfce3 100644 --- a/data/helpers.d/config +++ b/data/helpers.d/config @@ -104,7 +104,7 @@ _ynh_app_config_apply_one() { 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}" + ynh_print_info --message="File '$bind_file' overwritten with ${!short_setting}" fi # Save value in app settings @@ -124,7 +124,7 @@ _ynh_app_config_apply_one() { 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" + ynh_print_info --message="File '$bind_file' overwritten with the content provided in question '${short_setting}'" # Set value into a kind of key/value file else From 941cc29438e08a9a5f6579e099dd4fd7661c1758 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Oct 2021 12:35:06 +0200 Subject: [PATCH 0771/1155] bind_key -> bind_key_ to prevent yunohost from redacting key names which leads to broken log metadata.yml somehow --- data/helpers.d/config | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/data/helpers.d/config b/data/helpers.d/config index accedfce3..3f856ffa4 100644 --- a/data/helpers.d/config +++ b/data/helpers.d/config @@ -51,15 +51,15 @@ _ynh_app_config_get_one() { 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" == *">"* ]]; + 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)" + 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}")" + old[$short_setting]="$(ynh_read_var_in_file --file="${bind_file}" --key="${bind_key_}" --after="${bind_after}")" fi } @@ -129,22 +129,22 @@ _ynh_app_config_apply_one() { # 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" == *">"* ]]; + 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)" + 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_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" + ynh_print_info --message="Configuration key '$bind_key_' edited into $bind_file" fi fi From 61ec02c97cb6d39b7a5ce3c665cbe1700e7493fa Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Oct 2021 12:47:32 +0200 Subject: [PATCH 0772/1155] lint: Kill bare excepts --- src/yunohost/log.py | 2 +- src/yunohost/permission.py | 2 +- tox.ini | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index c99c1bbc9..d73a62cd0 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -407,7 +407,7 @@ def is_unit_operation( if isinstance(value, IOBase): try: context[field] = value.name - except: + except Exception: context[field] = "IOBase" operation_logger = OperationLogger(op_key, related_to, args=context) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 80d3b8602..1856046d6 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -474,7 +474,7 @@ def permission_create( protected=protected, sync_perm=sync_perm, ) - except: + except Exception: permission_delete(permission, force=True) raise diff --git a/tox.ini b/tox.ini index e79c70fec..733a0613d 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ deps = 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-invalidcode: flake8 src data --exclude src/yunohost/tests,src/yunohost/vendor --select F,E722 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 b0e8a58b244037db65480450521f3fa3014a4915 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Oct 2021 12:49:33 +0200 Subject: [PATCH 0773/1155] lint: Invalid escape sequences --- data/hooks/diagnosis/80-apps.py | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/hooks/diagnosis/80-apps.py b/data/hooks/diagnosis/80-apps.py index a75193a45..5aec48ed8 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 -hr '{deprecated_helper}' {app['setting_path']}/scripts/ | grep -v -q '^\s*#'" + f"grep -hr '{deprecated_helper}' {app['setting_path']}/scripts/ | grep -v -q '^\\s*#'" ) == 0 ): diff --git a/tox.ini b/tox.ini index 733a0613d..267134e57 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ deps = 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,E722 + py37-invalidcode: flake8 src data --exclude src/yunohost/tests,src/yunohost/vendor --select F,E722,W605 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 de4b3825ab80a7b0f57639908812b0e00b7fc49d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Oct 2021 12:50:25 +0200 Subject: [PATCH 0774/1155] Ambiguous var name --- 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 7d89af443..c9f70e152 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -677,7 +677,7 @@ def user_import(operation_logger, csvfile, update=False, delete=False): def to_list(str_list): L = str_list.split(",") if str_list else [] - L = [l.strip() for l in L] + L = [element.strip() for element in L] return L existing_users = user_list()["users"] From ef2a8c8dbd66b490f48348cbbfe2051e4ac221f5 Mon Sep 17 00:00:00 2001 From: ericgaspar Date: Tue, 5 Oct 2021 13:02:51 +0200 Subject: [PATCH 0775/1155] Update logrotate --- data/helpers.d/logrotate | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data/helpers.d/logrotate b/data/helpers.d/logrotate index 2d9ab6b72..27803bafd 100644 --- a/data/helpers.d/logrotate +++ b/data/helpers.d/logrotate @@ -96,6 +96,10 @@ $logfile { EOF 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) + + ynh_user_exists "$app" || chown $app:$app "/var/log/$app" + chmod o-rwx "/var/log/$app" + } # Remove the app's logrotate config. From 423eef7a620192389170a158dfa77bc0469ccb12 Mon Sep 17 00:00:00 2001 From: ericgaspar Date: Tue, 5 Oct 2021 13:06:21 +0200 Subject: [PATCH 0776/1155] Update logrotate --- data/helpers.d/logrotate | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/logrotate b/data/helpers.d/logrotate index 27803bafd..e4b354e03 100644 --- a/data/helpers.d/logrotate +++ b/data/helpers.d/logrotate @@ -97,8 +97,8 @@ EOF 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) - ynh_user_exists "$app" || chown $app:$app "/var/log/$app" - chmod o-rwx "/var/log/$app" + ynh_user_exists "$app" || chown $app:$app "$logfile" + chmod o-rwx "$logfile" } From 1baebeba6d5d095e8c7f88cd785b668fe83941ef Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Tue, 5 Oct 2021 11:13:38 +0000 Subject: [PATCH 0777/1155] [CI] Format code --- src/yunohost/utils/config.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 2a1159042..2363545cf 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -1019,7 +1019,9 @@ class FileQuestion(Question): FileQuestion.upload_dirs += [upload_dir] logger.debug(f"Saving file {self.name} for file question into {file_path}") - if Moulinette.interface.type != "api" or (self.value.startswith("/") and os.path.exists(self.value)): + if Moulinette.interface.type != "api" or ( + self.value.startswith("/") and os.path.exists(self.value) + ): content = read_file(str(self.value), file_mode="rb") else: content = b64decode(self.value) From 4cd5e9b632113dc595940de766a83be13aebfb31 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Oct 2021 13:46:06 +0200 Subject: [PATCH 0778/1155] app_info: return a new is_webapp info meant to be used by API --- src/yunohost/app.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 81b79ff48..2b8d71abf 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -168,6 +168,9 @@ def app_info(app, full=False): 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["is_webapp"] = ("domain" in settings and "path" in settings) + ret["supports_change_url"] = os.path.exists( os.path.join(setting_path, "scripts", "change_url") ) From 93a72a7b5fe4c7efbbecd9a1c6285ab5d9b97337 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= <46165813+ericgaspar@users.noreply.github.com> Date: Tue, 5 Oct 2021 13:52:45 +0200 Subject: [PATCH 0779/1155] Update data/helpers.d/logrotate Co-authored-by: Kayou --- data/helpers.d/logrotate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/logrotate b/data/helpers.d/logrotate index e4b354e03..a4548512d 100644 --- a/data/helpers.d/logrotate +++ b/data/helpers.d/logrotate @@ -97,7 +97,7 @@ EOF 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) - ynh_user_exists "$app" || chown $app:$app "$logfile" + ynh_user_exists --username="$app" || chown $app:$app "$logfile" chmod o-rwx "$logfile" } From 74256845525799c674df5984afe2ec18974dbcd0 Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 6 Oct 2021 02:37:27 +0200 Subject: [PATCH 0780/1155] [enh] Add visible attribute support in cli --- src/yunohost/tests/test_questions.py | 96 ++++++++++++++ src/yunohost/utils/config.py | 182 ++++++++++++++++++++++++--- 2 files changed, 259 insertions(+), 19 deletions(-) diff --git a/src/yunohost/tests/test_questions.py b/src/yunohost/tests/test_questions.py index cf4e67733..b39990b73 100644 --- a/src/yunohost/tests/test_questions.py +++ b/src/yunohost/tests/test_questions.py @@ -15,6 +15,7 @@ from yunohost.utils.config import ( PathQuestion, BooleanQuestion, FileQuestion, + evaluate_simple_js_expression ) from yunohost.utils.error import YunohostError, YunohostValidationError @@ -2093,3 +2094,98 @@ def test_normalize_path(): assert PathQuestion.normalize("/macnuggets/") == "/macnuggets" assert PathQuestion.normalize("macnuggets/") == "/macnuggets" assert PathQuestion.normalize("////macnuggets///") == "/macnuggets" + +def test_simple_evaluate(): + context = { + 'a1': 1, + 'b2': 2, + 'c10': 10, + 'foo': 'bar', + 'comp': '1>2', + 'empty': '', + 'lorem': 'Lorem ipsum dolor et si qua met!', + 'warning': 'Warning! This sentence will fail!', + 'quote': "Je s'apelle Groot", + 'and_': '&&', + 'object': { 'a': 'Security risk' } + } + supported = { + '42': 42, + '9.5': 9.5, + "'bopbidibopbopbop'": 'bopbidibopbopbop', + 'true': True, + 'false': False, + 'null': None, + + # Math + '1 * (2 + 3 * (4 - 3))': 5, + '1 * (2 + 3 * (4 - 3)) > 10 - 2 || 3 * 2 > 9 - 2 * 3': True, + '(9 - 2) * 3 - 10': 11, + '12 - 2 * -2 + (3 - 4) * 3.1': 12.9, + '9 / 12 + 12 * 3 - 5': 31.75, + '9 / 12 + 12 * (3 - 5)': -23.25, + '12 > 13.1': False, + '12 < 14': True, + '12 <= 14': True, + '12 >= 14': False, + '12 == 14': False, + '12 % 5 > 3': False, + '12 != 14': True, + '9 - 1 > 10 && 3 * 5 > 10': False, + '9 - 1 > 10 || 3 * 5 > 10': True, + 'a1 > 0 || a1 < -12': True, + 'a1 > 0 && a1 < -12': False, + 'a1 + 1 > 0 && -a1 > -12': True, + '-(a1 + 1) < 0 || -(a1 + 2) > -12': True, + '-a1 * 2': -2, + '(9 - 2) * 3 - c10': 11, + '(9 - b2) * 3 - c10': 11, + 'c10 > b2': True, + + # String + "foo == 'bar'":True, + "foo != 'bar'":False, + 'foo == "bar" && 1 > 0':True, + '!!foo': True, + '!foo': False, + 'foo': 'bar', + '!(foo > "baa") || 1 > 2': False, + '!(foo > "baa") || 1 < 2': True, + 'empty == ""': True, + '1 == "1"': True, + '1.0 == "1"': True, + '1 == "aaa"': False, + "'I am ' + b2 + ' years'": 'I am 2 years', + "quote == 'Je s\\'apelle Groot'": True, + "lorem == 'Lorem ipsum dolor et si qua met!'": True, + "and_ == '&&'": True, + "warning == 'Warning! This sentence will fail!'": True, + + # Match + "match(lorem, '^Lorem [ia]psumE?')": bool, + "match(foo, '^Lorem [ia]psumE?')": None, + "match(lorem, '^Lorem [ia]psumE?') && 1 == 1": bool, + + # No code + "": False, + " ": False, + } + trigger_errors = { + "object.a": YunohostError, # Keep unsupported, for security reasons + 'a1 ** b2': YunohostError, # Keep unsupported, for security reasons + '().__class__.__bases__[0].__subclasses__()': YunohostError, # Very dangerous code + 'a1 > 11 ? 1 : 0': SyntaxError, + 'c10 > b2 == false': YunohostError, # JS and Python doesn't do the same thing for this situation + 'c10 > b2 == true': YunohostError, + } + + for expression, result in supported.items(): + if result == bool: + assert bool(evaluate_simple_js_expression(expression, context)), expression + else: + assert evaluate_simple_js_expression(expression, context) == result, expression + + for expression, error in trigger_errors.items(): + with pytest.raises(error): + evaluate_simple_js_expression(expression, context) + diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 2363545cf..0f18fad0d 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -24,6 +24,8 @@ import re import urllib.parse import tempfile import shutil +import ast +import operator as op from collections import OrderedDict from typing import Optional, Dict, List, Union, Any, Mapping @@ -46,6 +48,138 @@ from yunohost.log import OperationLogger logger = getActionLogger("yunohost.config") CONFIG_PANEL_VERSION_SUPPORTED = 1.0 +# Those js-like evaluate functions are used to eval safely visible attributes +# The goal is to evaluate in the same way than js simple-evaluate +# https://github.com/shepherdwind/simple-evaluate +def evaluate_simple_ast(node, context={}): + operators = { + ast.Not: op.not_, + ast.Mult: op.mul, + ast.Div: op.truediv, # number + ast.Mod: op.mod, # number + ast.Add: op.add, #str + ast.Sub: op.sub, #number + ast.USub: op.neg, # Negative number + ast.Gt: op.gt, + ast.Lt: op.lt, + ast.GtE: op.ge, + ast.LtE: op.le, + ast.Eq: op.eq, + ast.NotEq: op.ne + } + context['true'] = True + context['false'] = False + context['null'] = None + + # Variable + if isinstance(node, ast.Name): # Variable + return context[node.id] + + # Python <=3.7 String + elif isinstance(node, ast.Str): + return node.s + + # Python <=3.7 Number + elif isinstance(node, ast.Num): + return node.n + + # Boolean, None and Python 3.8 for Number, Boolean, String and None + elif isinstance(node, (ast.Constant, ast.NameConstant)): + return node.value + + # + - * / % + elif isinstance(node, ast.BinOp) and type(node.op) in operators: # + left = evaluate_simple_ast(node.left, context) + right = evaluate_simple_ast(node.right, context) + if type(node.op) == ast.Add: + if isinstance(left, str) or isinstance(right, str): # support 'I am ' + 42 + left = str(left) + right = str(right) + elif type(left) != type(right): # support "111" - "1" -> 110 + left = float(left) + right = float(right) + + return operators[type(node.op)](left, right) + + # Comparison + # JS and Python don't give the same result for multi operators + # like True == 10 > 2. + elif isinstance(node, ast.Compare) and len(node.comparators) == 1: # + left = evaluate_simple_ast(node.left, context) + right = evaluate_simple_ast(node.comparators[0], context) + operator = node.ops[0] + if isinstance(left, (int, float)) or isinstance(right, (int, float)): + try: + left = float(left) + right = float(right) + except ValueError: + return type(operator) == ast.NotEq + try: + return operators[type(operator)](left, right) + except TypeError: # support "e" > 1 -> False like in JS + return False + + # and / or + elif isinstance(node, ast.BoolOp): # + values = node.values + for value in node.values: + value = evaluate_simple_ast(value, context) + if isinstance(node.op, ast.And) and not value: + return False + elif isinstance(node.op, ast.Or) and value: + return True + return isinstance(node.op, ast.And) + + # not / USub (it's negation number -\d) + elif isinstance(node, ast.UnaryOp): # e.g., -1 + return operators[type(node.op)](evaluate_simple_ast(node.operand, context)) + + # match function call + elif isinstance(node, ast.Call) and node.func.__dict__.get('id') == 'match': + return re.match( + evaluate_simple_ast(node.args[1], context), + context[node.args[0].id] + ) + + # Unauthorized opcode + else: + opcode = str(type(node)) + raise YunohostError(f"Unauthorize opcode '{opcode}' in visible attribute", raw_msg=True) + +def js_to_python(expr): + in_string = None + py_expr = "" + i = 0 + escaped = False + for char in expr: + if char in r"\"'": + # Start a string + if not in_string: + in_string = char + + # Finish a string + elif in_string == char and not escaped: + in_string = None + + # If we are not in a string, replace operators + elif not in_string: + if char == "!" and expr[i +1] != "=": + char = "not " + elif char in "|&" and py_expr[-1:] == char: + py_expr = py_expr[:-1] + char = " and " if char == "&" else " or " + + # Determine if next loop will be in escaped mode + escaped = char == "\\" and not escaped + py_expr += char + i+=1 + return py_expr + +def evaluate_simple_js_expression(expr, context={}): + if not expr.strip(): + return False + node = ast.parse(js_to_python(expr), mode="eval").body + return evaluate_simple_ast(node, context) class ConfigPanel: def __init__(self, config_path, save_path=None): @@ -466,11 +600,13 @@ class Question(object): hide_user_input_in_prompt = False pattern: Optional[Dict] = None - def __init__(self, question: Dict[str, Any]): + def __init__(self, question: Dict[str, Any], context: Dict[str, Any] = {}): self.name = question["name"] self.type = question.get("type", "string") self.default = question.get("default", None) self.optional = question.get("optional", False) + self.visible = question.get("visible", None) + self.context = context self.choices = question.get("choices", []) self.pattern = question.get("pattern", self.pattern) self.ask = question.get("ask", {"en": self.name}) @@ -512,6 +648,15 @@ class Question(object): ) def ask_if_needed(self): + + if self.visible and not evaluate_simple_js_expression(self.visible, context=self.context): + # FIXME There could be several use case if the question is not displayed: + # - we doesn't want to give a specific value + # - we want to keep the previous value + # - we want the default value + self.value = None + return self.value + for i in range(5): # Display question if no value filled or if it's a readonly message if Moulinette.interface.type == "cli" and os.isatty(1): @@ -577,7 +722,7 @@ class Question(object): # Prevent displaying a shitload of choices # (e.g. 100+ available users when choosing an app admin...) choices = ( - list(self.choices.values()) + list(self.choices.keys()) if isinstance(self.choices, dict) else self.choices ) @@ -710,8 +855,8 @@ class PasswordQuestion(Question): default_value = "" forbidden_chars = "{}" - def __init__(self, question): - super().__init__(question) + def __init__(self, question, context: Dict[str, Any] = {}): + super().__init__(question, context) self.redact = True if self.default is not None: raise YunohostValidationError( @@ -829,8 +974,8 @@ class BooleanQuestion(Question): choices="yes/no", ) - def __init__(self, question): - super().__init__(question) + def __init__(self, question, context: Dict[str, Any] = {}): + super().__init__(question, context) self.yes = question.get("yes", 1) self.no = question.get("no", 0) if self.default is None: @@ -850,10 +995,10 @@ class BooleanQuestion(Question): class DomainQuestion(Question): argument_type = "domain" - def __init__(self, question): + def __init__(self, question, context: Dict[str, Any] = {}): from yunohost.domain import domain_list, _get_maindomain - super().__init__(question) + super().__init__(question, context) if self.default is None: self.default = _get_maindomain() @@ -876,11 +1021,11 @@ class DomainQuestion(Question): class UserQuestion(Question): argument_type = "user" - def __init__(self, question): + def __init__(self, question, context: Dict[str, Any] = {}): from yunohost.user import user_list, user_info from yunohost.domain import _get_maindomain - super().__init__(question) + super().__init__(question, context) self.choices = list(user_list()["users"].keys()) if not self.choices: @@ -902,8 +1047,8 @@ class NumberQuestion(Question): argument_type = "number" default_value = None - def __init__(self, question): - super().__init__(question) + def __init__(self, question, context: Dict[str, Any] = {}): + super().__init__(question, context) self.min = question.get("min", None) self.max = question.get("max", None) self.step = question.get("step", None) @@ -954,8 +1099,8 @@ class DisplayTextQuestion(Question): argument_type = "display_text" readonly = True - def __init__(self, question): - super().__init__(question) + def __init__(self, question, context: Dict[str, Any] = {}): + super().__init__(question, context) self.optional = True self.style = question.get( @@ -989,8 +1134,8 @@ class FileQuestion(Question): if os.path.exists(upload_dir): shutil.rmtree(upload_dir) - def __init__(self, question): - super().__init__(question) + def __init__(self, question, context: Dict[str, Any] = {}): + super().__init__(question, context) self.accept = question.get("accept", "") def _prevalidate(self): @@ -1089,9 +1234,8 @@ def ask_questions_and_parse_answers( for question in questions: question_class = ARGUMENTS_TYPE_PARSERS[question.get("type", "string")] question["value"] = prefilled_answers.get(question["name"]) - question = question_class(question) - - question.ask_if_needed() + question = question_class(question, prefilled_answers) + prefilled_answers[question.name] = question.ask_if_needed() out.append(question) return out From eb8a59751ec112fc74526e53cb7c107be9b5228a Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Wed, 6 Oct 2021 00:57:04 +0000 Subject: [PATCH 0781/1155] [CI] Format code --- src/yunohost/app.py | 2 +- src/yunohost/tests/test_questions.py | 118 +++++++++++++-------------- src/yunohost/utils/config.py | 58 +++++++------ 3 files changed, 93 insertions(+), 85 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 2b8d71abf..fe5281384 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -169,7 +169,7 @@ def app_info(app, full=False): ret["from_catalog"] = _load_apps_catalog()["apps"].get(absolute_app_name, {}) ret["upgradable"] = _app_upgradable(ret) - ret["is_webapp"] = ("domain" in settings and "path" in settings) + ret["is_webapp"] = "domain" in settings and "path" in settings ret["supports_change_url"] = os.path.exists( os.path.join(setting_path, "scripts", "change_url") diff --git a/src/yunohost/tests/test_questions.py b/src/yunohost/tests/test_questions.py index b39990b73..c21ff8c40 100644 --- a/src/yunohost/tests/test_questions.py +++ b/src/yunohost/tests/test_questions.py @@ -15,7 +15,7 @@ from yunohost.utils.config import ( PathQuestion, BooleanQuestion, FileQuestion, - evaluate_simple_js_expression + evaluate_simple_js_expression, ) from yunohost.utils.error import YunohostError, YunohostValidationError @@ -2095,97 +2095,95 @@ def test_normalize_path(): assert PathQuestion.normalize("macnuggets/") == "/macnuggets" assert PathQuestion.normalize("////macnuggets///") == "/macnuggets" + def test_simple_evaluate(): context = { - 'a1': 1, - 'b2': 2, - 'c10': 10, - 'foo': 'bar', - 'comp': '1>2', - 'empty': '', - 'lorem': 'Lorem ipsum dolor et si qua met!', - 'warning': 'Warning! This sentence will fail!', - 'quote': "Je s'apelle Groot", - 'and_': '&&', - 'object': { 'a': 'Security risk' } + "a1": 1, + "b2": 2, + "c10": 10, + "foo": "bar", + "comp": "1>2", + "empty": "", + "lorem": "Lorem ipsum dolor et si qua met!", + "warning": "Warning! This sentence will fail!", + "quote": "Je s'apelle Groot", + "and_": "&&", + "object": {"a": "Security risk"}, } supported = { - '42': 42, - '9.5': 9.5, - "'bopbidibopbopbop'": 'bopbidibopbopbop', - 'true': True, - 'false': False, - 'null': None, - + "42": 42, + "9.5": 9.5, + "'bopbidibopbopbop'": "bopbidibopbopbop", + "true": True, + "false": False, + "null": None, # Math - '1 * (2 + 3 * (4 - 3))': 5, - '1 * (2 + 3 * (4 - 3)) > 10 - 2 || 3 * 2 > 9 - 2 * 3': True, - '(9 - 2) * 3 - 10': 11, - '12 - 2 * -2 + (3 - 4) * 3.1': 12.9, - '9 / 12 + 12 * 3 - 5': 31.75, - '9 / 12 + 12 * (3 - 5)': -23.25, - '12 > 13.1': False, - '12 < 14': True, - '12 <= 14': True, - '12 >= 14': False, - '12 == 14': False, - '12 % 5 > 3': False, - '12 != 14': True, - '9 - 1 > 10 && 3 * 5 > 10': False, - '9 - 1 > 10 || 3 * 5 > 10': True, - 'a1 > 0 || a1 < -12': True, - 'a1 > 0 && a1 < -12': False, - 'a1 + 1 > 0 && -a1 > -12': True, - '-(a1 + 1) < 0 || -(a1 + 2) > -12': True, - '-a1 * 2': -2, - '(9 - 2) * 3 - c10': 11, - '(9 - b2) * 3 - c10': 11, - 'c10 > b2': True, - + "1 * (2 + 3 * (4 - 3))": 5, + "1 * (2 + 3 * (4 - 3)) > 10 - 2 || 3 * 2 > 9 - 2 * 3": True, + "(9 - 2) * 3 - 10": 11, + "12 - 2 * -2 + (3 - 4) * 3.1": 12.9, + "9 / 12 + 12 * 3 - 5": 31.75, + "9 / 12 + 12 * (3 - 5)": -23.25, + "12 > 13.1": False, + "12 < 14": True, + "12 <= 14": True, + "12 >= 14": False, + "12 == 14": False, + "12 % 5 > 3": False, + "12 != 14": True, + "9 - 1 > 10 && 3 * 5 > 10": False, + "9 - 1 > 10 || 3 * 5 > 10": True, + "a1 > 0 || a1 < -12": True, + "a1 > 0 && a1 < -12": False, + "a1 + 1 > 0 && -a1 > -12": True, + "-(a1 + 1) < 0 || -(a1 + 2) > -12": True, + "-a1 * 2": -2, + "(9 - 2) * 3 - c10": 11, + "(9 - b2) * 3 - c10": 11, + "c10 > b2": True, # String - "foo == 'bar'":True, - "foo != 'bar'":False, - 'foo == "bar" && 1 > 0':True, - '!!foo': True, - '!foo': False, - 'foo': 'bar', + "foo == 'bar'": True, + "foo != 'bar'": False, + 'foo == "bar" && 1 > 0': True, + "!!foo": True, + "!foo": False, + "foo": "bar", '!(foo > "baa") || 1 > 2': False, '!(foo > "baa") || 1 < 2': True, 'empty == ""': True, '1 == "1"': True, '1.0 == "1"': True, '1 == "aaa"': False, - "'I am ' + b2 + ' years'": 'I am 2 years', + "'I am ' + b2 + ' years'": "I am 2 years", "quote == 'Je s\\'apelle Groot'": True, "lorem == 'Lorem ipsum dolor et si qua met!'": True, "and_ == '&&'": True, "warning == 'Warning! This sentence will fail!'": True, - # Match "match(lorem, '^Lorem [ia]psumE?')": bool, "match(foo, '^Lorem [ia]psumE?')": None, "match(lorem, '^Lorem [ia]psumE?') && 1 == 1": bool, - # No code "": False, " ": False, } trigger_errors = { - "object.a": YunohostError, # Keep unsupported, for security reasons - 'a1 ** b2': YunohostError, # Keep unsupported, for security reasons - '().__class__.__bases__[0].__subclasses__()': YunohostError, # Very dangerous code - 'a1 > 11 ? 1 : 0': SyntaxError, - 'c10 > b2 == false': YunohostError, # JS and Python doesn't do the same thing for this situation - 'c10 > b2 == true': YunohostError, + "object.a": YunohostError, # Keep unsupported, for security reasons + "a1 ** b2": YunohostError, # Keep unsupported, for security reasons + "().__class__.__bases__[0].__subclasses__()": YunohostError, # Very dangerous code + "a1 > 11 ? 1 : 0": SyntaxError, + "c10 > b2 == false": YunohostError, # JS and Python doesn't do the same thing for this situation + "c10 > b2 == true": YunohostError, } for expression, result in supported.items(): if result == bool: assert bool(evaluate_simple_js_expression(expression, context)), expression else: - assert evaluate_simple_js_expression(expression, context) == result, expression + assert ( + evaluate_simple_js_expression(expression, context) == result + ), expression for expression, error in trigger_errors.items(): with pytest.raises(error): evaluate_simple_js_expression(expression, context) - diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 0f18fad0d..e38cfbb3a 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -55,24 +55,24 @@ def evaluate_simple_ast(node, context={}): operators = { ast.Not: op.not_, ast.Mult: op.mul, - ast.Div: op.truediv, # number - ast.Mod: op.mod, # number - ast.Add: op.add, #str - ast.Sub: op.sub, #number - ast.USub: op.neg, # Negative number + ast.Div: op.truediv, # number + ast.Mod: op.mod, # number + ast.Add: op.add, # str + ast.Sub: op.sub, # number + ast.USub: op.neg, # Negative number ast.Gt: op.gt, ast.Lt: op.lt, ast.GtE: op.ge, ast.LtE: op.le, ast.Eq: op.eq, - ast.NotEq: op.ne + ast.NotEq: op.ne, } - context['true'] = True - context['false'] = False - context['null'] = None + context["true"] = True + context["false"] = False + context["null"] = None # Variable - if isinstance(node, ast.Name): # Variable + if isinstance(node, ast.Name): # Variable return context[node.id] # Python <=3.7 String @@ -88,14 +88,16 @@ def evaluate_simple_ast(node, context={}): return node.value # + - * / % - elif isinstance(node, ast.BinOp) and type(node.op) in operators: # + elif ( + isinstance(node, ast.BinOp) and type(node.op) in operators + ): # left = evaluate_simple_ast(node.left, context) right = evaluate_simple_ast(node.right, context) if type(node.op) == ast.Add: - if isinstance(left, str) or isinstance(right, str): # support 'I am ' + 42 + if isinstance(left, str) or isinstance(right, str): # support 'I am ' + 42 left = str(left) right = str(right) - elif type(left) != type(right): # support "111" - "1" -> 110 + elif type(left) != type(right): # support "111" - "1" -> 110 left = float(left) right = float(right) @@ -104,7 +106,9 @@ def evaluate_simple_ast(node, context={}): # Comparison # JS and Python don't give the same result for multi operators # like True == 10 > 2. - elif isinstance(node, ast.Compare) and len(node.comparators) == 1: # + elif ( + isinstance(node, ast.Compare) and len(node.comparators) == 1 + ): # left = evaluate_simple_ast(node.left, context) right = evaluate_simple_ast(node.comparators[0], context) operator = node.ops[0] @@ -116,11 +120,11 @@ def evaluate_simple_ast(node, context={}): return type(operator) == ast.NotEq try: return operators[type(operator)](left, right) - except TypeError: # support "e" > 1 -> False like in JS + except TypeError: # support "e" > 1 -> False like in JS return False # and / or - elif isinstance(node, ast.BoolOp): # + elif isinstance(node, ast.BoolOp): # values = node.values for value in node.values: value = evaluate_simple_ast(value, context) @@ -131,20 +135,22 @@ def evaluate_simple_ast(node, context={}): return isinstance(node.op, ast.And) # not / USub (it's negation number -\d) - elif isinstance(node, ast.UnaryOp): # e.g., -1 + elif isinstance(node, ast.UnaryOp): # e.g., -1 return operators[type(node.op)](evaluate_simple_ast(node.operand, context)) # match function call - elif isinstance(node, ast.Call) and node.func.__dict__.get('id') == 'match': + elif isinstance(node, ast.Call) and node.func.__dict__.get("id") == "match": return re.match( - evaluate_simple_ast(node.args[1], context), - context[node.args[0].id] + evaluate_simple_ast(node.args[1], context), context[node.args[0].id] ) # Unauthorized opcode else: opcode = str(type(node)) - raise YunohostError(f"Unauthorize opcode '{opcode}' in visible attribute", raw_msg=True) + raise YunohostError( + f"Unauthorize opcode '{opcode}' in visible attribute", raw_msg=True + ) + def js_to_python(expr): in_string = None @@ -163,7 +169,7 @@ def js_to_python(expr): # If we are not in a string, replace operators elif not in_string: - if char == "!" and expr[i +1] != "=": + if char == "!" and expr[i + 1] != "=": char = "not " elif char in "|&" and py_expr[-1:] == char: py_expr = py_expr[:-1] @@ -172,15 +178,17 @@ def js_to_python(expr): # Determine if next loop will be in escaped mode escaped = char == "\\" and not escaped py_expr += char - i+=1 + i += 1 return py_expr + def evaluate_simple_js_expression(expr, context={}): if not expr.strip(): return False node = ast.parse(js_to_python(expr), mode="eval").body return evaluate_simple_ast(node, context) + class ConfigPanel: def __init__(self, config_path, save_path=None): self.config_path = config_path @@ -649,7 +657,9 @@ class Question(object): def ask_if_needed(self): - if self.visible and not evaluate_simple_js_expression(self.visible, context=self.context): + if self.visible and not evaluate_simple_js_expression( + self.visible, context=self.context + ): # FIXME There could be several use case if the question is not displayed: # - we doesn't want to give a specific value # - we want to keep the previous value From 99d2637cfe55fdfe2c654710a3e331abf7d9a925 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 Oct 2021 13:04:24 +0200 Subject: [PATCH 0782/1155] FileQuestion: self.value may not be an str --- 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 0f18fad0d..e0fe1416b 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -1164,9 +1164,11 @@ class FileQuestion(Question): FileQuestion.upload_dirs += [upload_dir] logger.debug(f"Saving file {self.name} for file question into {file_path}") - if Moulinette.interface.type != "api" or ( - self.value.startswith("/") and os.path.exists(self.value) - ): + + def is_file_path(s): + return isinstance(s, str) and s.startswith("/") and os.path.exists(s) + + if Moulinette.interface.type != "api" or is_file_path(self.value): content = read_file(str(self.value), file_mode="rb") else: content = b64decode(self.value) From 23bd32b3cdf435013ffc8fd4229ffa0c185daac0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 Oct 2021 13:45:15 +0200 Subject: [PATCH 0783/1155] Fix linters --- src/yunohost/utils/config.py | 43 ++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index e0fe1416b..fbaecfbe0 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -121,7 +121,6 @@ def evaluate_simple_ast(node, context={}): # and / or elif isinstance(node, ast.BoolOp): # - values = node.values for value in node.values: value = evaluate_simple_ast(value, context) if isinstance(node.op, ast.And) and not value: @@ -600,7 +599,7 @@ class Question(object): hide_user_input_in_prompt = False pattern: Optional[Dict] = None - def __init__(self, question: Dict[str, Any], context: Dict[str, Any] = {}): + def __init__(self, question: Dict[str, Any], context: Mapping[str, Any] = {}): self.name = question["name"] self.type = question.get("type", "string") self.default = question.get("default", None) @@ -855,7 +854,7 @@ class PasswordQuestion(Question): default_value = "" forbidden_chars = "{}" - def __init__(self, question, context: Dict[str, Any] = {}): + def __init__(self, question, context: Mapping[str, Any] = {}): super().__init__(question, context) self.redact = True if self.default is not None: @@ -974,7 +973,7 @@ class BooleanQuestion(Question): choices="yes/no", ) - def __init__(self, question, context: Dict[str, Any] = {}): + def __init__(self, question, context: Mapping[str, Any] = {}): super().__init__(question, context) self.yes = question.get("yes", 1) self.no = question.get("no", 0) @@ -995,7 +994,7 @@ class BooleanQuestion(Question): class DomainQuestion(Question): argument_type = "domain" - def __init__(self, question, context: Dict[str, Any] = {}): + def __init__(self, question, context: Mapping[str, Any] = {}): from yunohost.domain import domain_list, _get_maindomain super().__init__(question, context) @@ -1021,7 +1020,7 @@ class DomainQuestion(Question): class UserQuestion(Question): argument_type = "user" - def __init__(self, question, context: Dict[str, Any] = {}): + def __init__(self, question, context: Mapping[str, Any] = {}): from yunohost.user import user_list, user_info from yunohost.domain import _get_maindomain @@ -1047,7 +1046,7 @@ class NumberQuestion(Question): argument_type = "number" default_value = None - def __init__(self, question, context: Dict[str, Any] = {}): + def __init__(self, question, context: Mapping[str, Any] = {}): super().__init__(question, context) self.min = question.get("min", None) self.max = question.get("max", None) @@ -1099,7 +1098,7 @@ class DisplayTextQuestion(Question): argument_type = "display_text" readonly = True - def __init__(self, question, context: Dict[str, Any] = {}): + def __init__(self, question, context: Mapping[str, Any] = {}): super().__init__(question, context) self.optional = True @@ -1134,7 +1133,7 @@ class FileQuestion(Question): if os.path.exists(upload_dir): shutil.rmtree(upload_dir) - def __init__(self, question, context: Dict[str, Any] = {}): + def __init__(self, question, context: Mapping[str, Any] = {}): super().__init__(question, context) self.accept = question.get("accept", "") @@ -1205,15 +1204,15 @@ ARGUMENTS_TYPE_PARSERS = { def ask_questions_and_parse_answers( - questions: Dict, prefilled_answers: Union[str, Mapping[str, Any]] = {} + raw_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. Keyword arguments: - questions -- the arguments description store in yunohost - format from actions.json/toml, manifest.json/toml - or config_panel.json/toml + raw_questions -- the arguments description store in yunohost + format from actions.json/toml, manifest.json/toml + or config_panel.json/toml 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"} """ @@ -1224,20 +1223,22 @@ def ask_questions_and_parse_answers( # 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( + answers = dict( urllib.parse.parse_qsl(prefilled_answers or "", keep_blank_values=True) ) + elif isinstance(prefilled_answers, Mapping): + answers = {**prefilled_answers} + else: + answers = {} - 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, prefilled_answers) - prefilled_answers[question.name] = question.ask_if_needed() + for raw_question in raw_questions: + question_class = ARGUMENTS_TYPE_PARSERS[raw_question.get("type", "string")] + raw_question["value"] = answers.get(raw_question["name"]) + question = question_class(raw_question, context=answers) + answers[question.name] = question.ask_if_needed() out.append(question) return out From 344ed7252c996c7e907319853ba6decfabf90406 Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Wed, 6 Oct 2021 12:15:29 +0000 Subject: [PATCH 0784/1155] [CI] Format code --- src/yunohost/utils/config.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 4ee944126..4ee62c6f7 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -1241,7 +1241,6 @@ def ask_questions_and_parse_answers( else: answers = {} - out = [] for raw_question in raw_questions: From 644cdd41d8aa3cd27915dccbe472084961b742cc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 15 Jun 2020 16:36:41 +0200 Subject: [PATCH 0785/1155] Allow to re-run ynh_install_app_dependencies multiple times --- data/helpers.d/apt | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index c3439a583..aee022da7 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -210,6 +210,8 @@ ynh_package_install_from_equivs () { ynh_package_is_installed "$pkgname" } +YNH_INSTALL_APP_DEPENDENCIES_REPLACE="true" + # Define and install dependencies with a equivs control file # # This helper can/should only be called once per app @@ -248,6 +250,24 @@ ynh_install_app_dependencies () { dependencies="$(echo "$dependencies" | sed 's/\([^(\<=\>]\)\([\<=\>]\+\)\([^,]\+\)/\1 (\2 \3)/g')" fi + # The first time we run ynh_install_app_dependencies, we will replace the + # entire control file (This is in particular meant to cover the case of + # upgrade script where ynh_install_app_dependencies is called with this + # expected effect) Otherwise, any subsequent call will add dependencies + # to those already present in the equivs control file. + if [[ $YNH_INSTALL_APP_DEPENDENCIES_REPLACE == "true" ]] + then + YNH_INSTALL_APP_DEPENDENCIES_REPLACE="false" + else + local current_dependencies="" + 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// | /|} + dependencies="$current_dependencies $dependencies" + 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 @@ -284,6 +304,9 @@ EOF ynh_app_setting_set --app=$app --key=apt_dependencies --value="$dependencies" } + + + # Add dependencies to install with ynh_install_app_dependencies # # usage: ynh_add_app_dependencies --package=phpversion [--replace] From 76aaaab74e80a827fbbb2474676b110e04fb6ad5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 15 Jun 2020 16:47:51 +0200 Subject: [PATCH 0786/1155] Factorize sury repo configuration into ynh_add_sury --- data/helpers.d/apt | 19 ++++++++++++++++++- data/helpers.d/php | 3 +-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index aee022da7..8ba5bbe3e 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -283,7 +283,7 @@ ynh_install_app_dependencies () { if ! grep --recursive --quiet "^ *deb.*sury" /etc/apt/sources.list* then # Re-add sury - 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 --priority=600 + ynh_add_sury fi fi fi @@ -304,7 +304,24 @@ EOF ynh_app_setting_set --app=$app --key=apt_dependencies --value="$dependencies" } +# Add sury repository with adequate pin strategy +# +# [internal] +# +# usage: ynh_add_sury +# +ynh_add_sury() { + # Add an extra repository for those packages + 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 --priority=600 + + # Pin this extra repository after packages are installed to prevent sury of doing shit + for package_to_not_upgrade in "php" "php-fpm" "php-mysql" "php-xml" "php-zip" "php-mbstring" "php-ldap" "php-gd" "php-curl" "php-bz2" "php-json" "php-sqlite3" "php-intl" "openssl" "libssl1.1" "libssl-dev" + do + ynh_pin_repo --package="$package_to_not_upgrade" --pin="origin \"packages.sury.org\"" --priority="-1" --name=extra_php_version --append + done + +} # Add dependencies to install with ynh_install_app_dependencies diff --git a/data/helpers.d/php b/data/helpers.d/php index 7c91d89d2..6d47fdc13 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -348,8 +348,7 @@ ynh_install_php () { 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/ $(ynh_get_debian_release) main" --key="https://packages.sury.org/php/apt.gpg" --name=extra_php_version --priority=600 + ynh_add_sury # Install requested dependencies from this extra repository. # Install PHP-FPM first, otherwise PHP will install apache as a dependency. From 040be532ad759b952c00a898136f5975d2e285ed Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 15 Jun 2020 17:39:16 +0200 Subject: [PATCH 0787/1155] During ynh_install_app_dependencies, if the dependency list contains specific php version stuff, add sury and other tweaks --- data/helpers.d/apt | 57 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index 8ba5bbe3e..0eedfd601 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -250,6 +250,25 @@ ynh_install_app_dependencies () { dependencies="$(echo "$dependencies" | sed 's/\([^(\<=\>]\)\([\<=\>]\+\)\([^,]\+\)/\1 (\2 \3)/g')" fi + # Check for specific php dependencies which requires sury + # This grep will for example return "7.4" if dependencies is "foo bar php7.4-pwet php-gni" + local specific_php_version=$(echo $dependencies | tr '-' ' ' | grep -o -E "\" | sed 's/php//g' | sort | uniq) + + # Ignore case where the php version found is the one available in debian vanilla + [[ "$specific_php_version" != "$YNH_DEFAULT_PHP_VERSION" ]] || specific_php_version="" + + if [[ -n "$specific_php_version" ]] + then + # Cover a small edge case where a packager could have specified "php7.4-pwet php5-gni" which is confusing + [[ $(echo $specific_php_version | wc -l) -eq 1 ]] \ + || ynh_die --message="Inconsistent php versions in dependencies ... found : $specific_php_version" + + dependencies+=", php${specific_php_version}, php${specific_php_version}-fpm, php${specific_php_version}-common" + + ynh_add_sury + fi + + # The first time we run ynh_install_app_dependencies, we will replace the # entire control file (This is in particular meant to cover the case of # upgrade script where ynh_install_app_dependencies is called with this @@ -263,9 +282,9 @@ ynh_install_app_dependencies () { if ynh_package_is_installed --package="${dep_app}-ynh-deps" then current_dependencies="$(dpkg-query --show --showformat='${Depends}' ${dep_app}-ynh-deps) " + current_dependencies=${current_dependencies// | /|} fi - current_dependencies=${current_dependencies// | /|} - dependencies="$current_dependencies $dependencies" + dependencies="$current_dependencies, $dependencies" fi # @@ -301,7 +320,20 @@ EOF ynh_package_install_from_equivs /tmp/${dep_app}-ynh-deps.control \ || ynh_die --message="Unable to install dependencies" # Install the fake package and its dependencies rm /tmp/${dep_app}-ynh-deps.control + ynh_app_setting_set --app=$app --key=apt_dependencies --value="$dependencies" + + if [[ -n "$specific_php_version" ]] + then + # Set the default php version back as the default version for php-cli. + update-alternatives --set php /usr/bin/php$YNH_DEFAULT_PHP_VERSION + + # Store phpversion into the config of this app + ynh_app_setting_set $app phpversion $specific_php_version + + # Integrate new php-fpm service in yunohost + yunohost service add php${specific_php_version}-fpm --log "/var/log/php${phpversion}-fpm.log" + fi } # Add sury repository with adequate pin strategy @@ -315,7 +347,7 @@ ynh_add_sury() { # Add an extra repository for those packages 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 --priority=600 - # Pin this extra repository after packages are installed to prevent sury of doing shit + # Pin this extra repository after packages are installed to prevent sury from doing shit for package_to_not_upgrade in "php" "php-fpm" "php-mysql" "php-xml" "php-zip" "php-mbstring" "php-ldap" "php-gd" "php-curl" "php-bz2" "php-json" "php-sqlite3" "php-intl" "openssl" "libssl1.1" "libssl-dev" do ynh_pin_repo --package="$package_to_not_upgrade" --pin="origin \"packages.sury.org\"" --priority="-1" --name=extra_php_version --append @@ -365,7 +397,26 @@ ynh_add_app_dependencies () { # Requires YunoHost version 2.6.4 or higher. ynh_remove_app_dependencies () { local dep_app=${app//_/-} # Replace all '_' by '-' + + local current_dependencies="" + if ynh_package_is_installed --package="${dep_app}-ynh-deps" + then + current_dependencies="$(dpkg-query --show --showformat='${Depends}' ${dep_app}-ynh-deps) " + current_dependencies=${current_dependencies// | /|} + fi + ynh_package_autopurge ${dep_app}-ynh-deps # Remove the fake package and its dependencies if they not still used. + + # Check if this app used a specific php version ... in which case we check + # if the corresponding php-fpm is still there. Otherwise, we remove the + # service from yunohost as well + + local specific_php_version=$(echo $dependencies | tr '-' ' ' | grep -o -E "\" | sed 's/php//g' | sort | uniq) + [[ "$specific_php_version" != "$YNH_DEFAULT_PHP_VERSION" ]] || specific_php_version="" + if [[ -n "$specific_php_version" ]] && ! ynh_package_is_installed --package="php${specific_php_version}-fpm"; + then + yunohost service remove php${specific_php_version}-fpm + fi } # Install packages from an extra repository properly. From 76b60890c6f772f6a8e1aa105899f24877853dd3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 15 Jun 2020 18:23:32 +0200 Subject: [PATCH 0788/1155] Propagate changes on other apt/php helpers... --- data/helpers.d/apt | 19 ++------------- data/helpers.d/php | 59 ++++------------------------------------------ 2 files changed, 6 insertions(+), 72 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index 0eedfd601..f662c58e4 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -360,7 +360,6 @@ ynh_add_sury() { # # 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 () { @@ -368,24 +367,10 @@ ynh_add_app_dependencies () { 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 - - current_dependencies=${current_dependencies// | /|} - fi - - ynh_install_app_dependencies "${current_dependencies}${package}" + ynh_install_app_dependencies "${package}" } # Remove fake package and its dependencies @@ -450,7 +435,7 @@ ynh_install_extra_app_dependencies () { ynh_install_extra_repo --repo="$repo" $key --priority=995 --name=$name # Install requested dependencies from this extra repository. - ynh_add_app_dependencies --package="$package" + ynh_install_app_dependencies --package="$package" # Remove this extra repository after packages are installed ynh_remove_extra_repo --name=$app diff --git a/data/helpers.d/php b/data/helpers.d/php index 6d47fdc13..2191b0d22 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -111,7 +111,7 @@ ynh_add_fpm_config () { elif [ -n "$package" ] then # Install the additionnal packages from the default repository - ynh_add_app_dependencies --package="$package" + ynh_install_app_dependencies "$package" fi if [ $dedicated_service -eq 1 ] @@ -330,36 +330,13 @@ ynh_install_php () { ynh_handle_getopts_args "$@" package=${package:-} - # Store phpversion into the config of this app - ynh_app_setting_set $app phpversion $phpversion - if [ "$phpversion" == "$YNH_DEFAULT_PHP_VERSION" ] then 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 - 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 - - ynh_add_sury - - # 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 - - # Advertise service in admin panel - yunohost service add php${phpversion}-fpm --log "/var/log/php${phpversion}-fpm.log" + ynh_install_app_dependencies "$package" + ynh_app_setting_set $app phpversion $phpversion } # Remove the specific version of PHP used by the app. @@ -370,35 +347,7 @@ 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=$app --key=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 - - # 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 - # 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 + ynh_remove_app_dependencies } # Define the values to configure PHP-FPM From e07e1a95f4e223808d15fed8098d67f27de01653 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 16 Jun 2020 21:31:24 +0200 Subject: [PATCH 0789/1155] Apply suggestions from code review Co-authored-by: Kayou --- data/helpers.d/apt | 4 ++-- data/helpers.d/php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index f662c58e4..8b284d4fc 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -329,7 +329,7 @@ EOF update-alternatives --set php /usr/bin/php$YNH_DEFAULT_PHP_VERSION # Store phpversion into the config of this app - ynh_app_setting_set $app phpversion $specific_php_version + ynh_app_setting_set --app=$app --key=phpversion --value=$specific_php_version # Integrate new php-fpm service in yunohost yunohost service add php${specific_php_version}-fpm --log "/var/log/php${phpversion}-fpm.log" @@ -435,7 +435,7 @@ ynh_install_extra_app_dependencies () { ynh_install_extra_repo --repo="$repo" $key --priority=995 --name=$name # Install requested dependencies from this extra repository. - ynh_install_app_dependencies --package="$package" + ynh_install_app_dependencies "$package" # Remove this extra repository after packages are installed ynh_remove_extra_repo --name=$app diff --git a/data/helpers.d/php b/data/helpers.d/php index 2191b0d22..d383c1e4f 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -336,7 +336,7 @@ ynh_install_php () { fi ynh_install_app_dependencies "$package" - ynh_app_setting_set $app phpversion $phpversion + ynh_app_setting_set --app=$app --key=phpversion --value=$specific_php_version } # Remove the specific version of PHP used by the app. From 5054397a5a73d13eb3e50a8f52b01c693d3c871d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 Oct 2021 15:11:09 +0200 Subject: [PATCH 0790/1155] helpers: Add deprecation warning to ynh_add_app_dependencies --- data/helpers.d/apt | 1 + 1 file changed, 1 insertion(+) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index 8b284d4fc..b182edc6c 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -370,6 +370,7 @@ ynh_add_app_dependencies () { # Manage arguments with getopts ynh_handle_getopts_args "$@" + ynh_print_warn --message="Packagers: ynh_add_app_dependencies is deprecated and is now only an alias to ynh_install_app_dependencies" ynh_install_app_dependencies "${package}" } From 9ee631c1c451f1befe59fa3c5abeed325a4d1b5d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 Oct 2021 15:16:18 +0200 Subject: [PATCH 0791/1155] helpers: Typo in ynh_remove_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 b182edc6c..6f1db90f8 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -397,7 +397,7 @@ ynh_remove_app_dependencies () { # if the corresponding php-fpm is still there. Otherwise, we remove the # service from yunohost as well - local specific_php_version=$(echo $dependencies | tr '-' ' ' | grep -o -E "\" | sed 's/php//g' | sort | uniq) + local specific_php_version=$(echo $current_dependencies | tr '-' ' ' | grep -o -E "\" | sed 's/php//g' | sort | uniq) [[ "$specific_php_version" != "$YNH_DEFAULT_PHP_VERSION" ]] || specific_php_version="" if [[ -n "$specific_php_version" ]] && ! ynh_package_is_installed --package="php${specific_php_version}-fpm"; then From 13d012bb4fe8ef05a1c6c8cfdab87a88b7b6cc87 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 Oct 2021 15:34:26 +0200 Subject: [PATCH 0792/1155] helpers apt: save phpversion in settings even when using php default version --- 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 6f1db90f8..235cc0067 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -293,7 +293,7 @@ ynh_install_app_dependencies () { # https://github.com/YunoHost/issues/issues/1407 # # If we require to install php dependency - if echo $dependencies | grep --quiet 'php' + if grep --quiet 'php' <<< "$dependencies" 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 --quiet --invert-match "7.0.33-0+deb9" @@ -333,6 +333,9 @@ EOF # Integrate new php-fpm service in yunohost yunohost service add php${specific_php_version}-fpm --log "/var/log/php${phpversion}-fpm.log" + elif grep --quiet 'php' <<< "$dependencies" + # Store phpversion into the config of this app + ynh_app_setting_set --app=$app --key=phpversion --value=$YNH_DEFAULT_PHP_VERSION fi } From a98552ef0e3a39b0870414bad574b10cfe55da59 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 Oct 2021 15:55:52 +0200 Subject: [PATCH 0793/1155] helpers: Fix weird 0 syntax which shfmt ain't happy with (dangling 0) --- 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 71998763e..d11fc578f 100644 --- a/data/helpers.d/logging +++ b/data/helpers.d/logging @@ -249,7 +249,7 @@ ynh_script_progression () { 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 )) + local weight_values=$(( $(echo "$weight_valuesA" "$weight_valuesB" | grep -v -E '^\s*$' | tr '\n' '+' | sed 's/+$/+0/g') )) # max_progression is a total number of calls to this helper. # Less the number of calls with a weight value. From 93cc413f4aa22dde6f50b2dd3c4e5a8207ede442 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 Oct 2021 16:01:53 +0200 Subject: [PATCH 0794/1155] helpers: lint/reformat with shfmt -bn -i 4 -w $FILE --- data/helpers.d/apt | 148 ++++++++++++------------- data/helpers.d/backup | 143 ++++++++++-------------- data/helpers.d/config | 172 +++++++++++------------------ data/helpers.d/fail2ban | 18 ++- data/helpers.d/getopts | 69 +++++------- data/helpers.d/hardware | 36 +++--- data/helpers.d/logging | 83 +++++++------- data/helpers.d/logrotate | 34 +++--- data/helpers.d/multimedia | 41 ++++--- data/helpers.d/mysql | 37 +++---- data/helpers.d/network | 38 +++---- data/helpers.d/nginx | 8 +- data/helpers.d/nodejs | 42 ++++--- data/helpers.d/permission | 95 ++++++---------- data/helpers.d/php | 177 ++++++++++++------------------ data/helpers.d/postgresql | 23 ++-- data/helpers.d/setting | 17 ++- data/helpers.d/string | 28 ++--- data/helpers.d/systemd | 55 ++++------ data/helpers.d/user | 37 +++---- data/helpers.d/utils | 223 ++++++++++++++++---------------------- 21 files changed, 627 insertions(+), 897 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index c3439a583..e77fb8a5c 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -12,31 +12,27 @@ 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 + for try in $(seq 1 17); do # Check if /var/lib/dpkg/lock is used by another process - if lsof /var/lib/dpkg/lock > /dev/null - then + if lsof /var/lib/dpkg/lock >/dev/null; then echo "apt is already in use..." # Sleep an exponential time at each round - sleep $(( try * try )) + sleep $((try * try)) else # Check if dpkg hasn't been interrupted and is fully available. # See this for more information: https://sources.debian.org/src/apt/1.4.9/apt-pkg/deb/debsystem.cc/#L141-L174 local dpkg_dir="/var/lib/dpkg/updates/" # For each file in $dpkg_dir - while read dpkg_file <&9 - do + while read dpkg_file <&9; do # Check if the name of this file contains only numbers. - if echo "$dpkg_file" | grep --perl-regexp --quiet "^[[:digit:]]+$" - then + if echo "$dpkg_file" | grep --perl-regexp --quiet "^[[:digit:]]+$"; then # If so, that a remaining of dpkg. ynh_print_err "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)" + done 9<<<"$(ls -1 $dpkg_dir)" set -o xtrace # set -x return 0 fi @@ -57,7 +53,7 @@ ynh_wait_dpkg_free() { ynh_package_is_installed() { # Declare an array to define the options of this helper. local legacy_args=p - local -A args_array=( [p]=package= ) + local -A args_array=([p]=package=) local package # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -79,13 +75,12 @@ ynh_package_is_installed() { ynh_package_version() { # Declare an array to define the options of this helper. local legacy_args=p - local -A args_array=( [p]=package= ) + local -A args_array=([p]=package=) local package # Manage arguments with getopts ynh_handle_getopts_args "$@" - if ynh_package_is_installed "$package" - then + if ynh_package_is_installed "$package"; then dpkg-query --show --showformat='${Version}' "$package" 2>/dev/null else echo '' @@ -166,14 +161,14 @@ ynh_package_autopurge() { # | arg: controlfile - path of the equivs control file # # Requires YunoHost version 2.2.4 or higher. -ynh_package_install_from_equivs () { +ynh_package_install_from_equivs() { local controlfile=$1 # retrieve package information - 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 + 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. + && ynh_die --message="Invalid control file" # Check if this 2 variables aren't empty. # Update packages cache ynh_package_update @@ -182,7 +177,7 @@ ynh_package_install_from_equivs () { local TMPDIR=$(mktemp --directory) # Force the compatibility level at 10, levels below are deprecated - echo 10 > /usr/share/equivs/template/debian/compat + echo 10 >/usr/share/equivs/template/debian/compat # Note that the cd executes into a sub shell # Create a fake deb package with equivs-build and the given control file @@ -190,21 +185,24 @@ ynh_package_install_from_equivs () { # Install missing dependencies with ynh_package_install ynh_wait_dpkg_free cp "$controlfile" "${TMPDIR}/control" - (cd "$TMPDIR" - LC_ALL=C equivs-build ./control 1> /dev/null - LC_ALL=C dpkg --force-depends --install "./${pkgname}_${pkgversion}_all.deb" 2>&1 | tee ./dpkg_log) + ( + cd "$TMPDIR" + LC_ALL=C equivs-build ./control 1>/dev/null + 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 - # (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:") - local problematic_dependencies="$(cat $TMPDIR/dpkg_log | grep -oP '(?<=-ynh-deps depends on ).*(?=; however)' | tr '\n' ' ')" - # Fake an install of those dependencies to see the errors - # The sed command here is, Print only from 'Reading state info' to the end. - [[ -n "$problematic_dependencies" ]] && ynh_package_install $problematic_dependencies --dry-run 2>&1 | sed --quiet '/Reading state info/,$p' | grep -v "fix-broken\|Reading state info" >&2 - ynh_die --message="Unable to install dependencies"; } - [[ -n "$TMPDIR" ]] && rm --recursive --force $TMPDIR # Remove the temp dir. + ynh_package_install --fix-broken \ + || { # 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:") + local problematic_dependencies="$(cat $TMPDIR/dpkg_log | grep -oP '(?<=-ynh-deps depends on ).*(?=; however)' | tr '\n' ' ')" + # Fake an install of those dependencies to see the errors + # The sed command here is, Print only from 'Reading state info' to the end. + [[ -n "$problematic_dependencies" ]] && ynh_package_install $problematic_dependencies --dry-run 2>&1 | sed --quiet '/Reading state info/,$p' | grep -v "fix-broken\|Reading state info" >&2 + ynh_die --message="Unable to install dependencies" + } + [[ -n "$TMPDIR" ]] && rm --recursive --force $TMPDIR # Remove the temp dir. # check if the package is actually installed ynh_package_is_installed "$pkgname" @@ -221,7 +219,7 @@ ynh_package_install_from_equivs () { # | 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 () { +ynh_install_app_dependencies() { local 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')" @@ -232,11 +230,10 @@ ynh_install_app_dependencies () { if [ -z "${version}" ] || [ "$version" == "null" ]; then version="1.0" fi - local dep_app=${app//_/-} # Replace all '_' by '-' + local dep_app=${app//_/-} # Replace all '_' by '-' # Handle specific versions - if [[ "$dependencies" =~ [\<=\>] ]] - then + if [[ "$dependencies" =~ [\<=\>] ]]; then # Replace version specifications by relationships syntax # https://www.debian.org/doc/debian-policy/ch-relationships.html # Sed clarification @@ -254,21 +251,18 @@ ynh_install_app_dependencies () { # https://github.com/YunoHost/issues/issues/1407 # # If we require to install php dependency - if echo $dependencies | grep --quiet 'php' - then + 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 --quiet --invert-match "7.0.33-0+deb9" - then + if dpkg --list | grep "php7.0" | grep --quiet --invert-match "7.0.33-0+deb9"; then # And sury ain't already in sources.lists - if ! grep --recursive --quiet "^ *deb.*sury" /etc/apt/sources.list* - then + if ! grep --recursive --quiet "^ *deb.*sury" /etc/apt/sources.list*; then # Re-add sury 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 --priority=600 fi fi fi - cat > /tmp/${dep_app}-ynh-deps.control << EOF # Make a control file for equivs-build + cat >/tmp/${dep_app}-ynh-deps.control < /dev/null + 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 @@ -449,10 +437,10 @@ ynh_install_extra_repo () { # | 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 () { +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 -A args_array=([n]=name=) local name # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -461,8 +449,8 @@ ynh_remove_extra_repo () { 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 --file="/etc/apt/trusted.gpg.d/$name.gpg" > /dev/null - ynh_secure_remove --file="/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 @@ -484,10 +472,10 @@ ynh_remove_extra_repo () { # ynh_add_repo --uri=http://forge.yunohost.org/debian/ --suite=stretch --component=stable # # Requires YunoHost version 3.8.1 or higher. -ynh_add_repo () { +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 -A args_array=([u]=uri= [s]=suite= [c]=component= [n]=name= [a]=append) local uri local suite local component @@ -498,8 +486,7 @@ ynh_add_repo () { name="${name:-$app}" append=${append:-0} - if [ $append -eq 1 ] - then + if [ $append -eq 1 ]; then append="tee --append" else append="tee" @@ -525,10 +512,10 @@ 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 () { +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 -A args_array=([p]=package= [i]=pin= [r]=priority= [n]=name= [a]=append) local package local pin local priority @@ -541,8 +528,7 @@ ynh_pin_repo () { name="${name:-$app}" append=${append:-0} - if [ $append -eq 1 ] - then + if [ $append -eq 1 ]; then append="tee --append" else append="tee" @@ -556,5 +542,5 @@ ynh_pin_repo () { 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 21ca2d7f0..27ffa015c 100644 --- a/data/helpers.d/backup +++ b/data/helpers.d/backup @@ -67,7 +67,7 @@ ynh_backup() { # Declare an array to define the options of this helper. local legacy_args=sdbm - local -A 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 @@ -83,10 +83,8 @@ 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 ] ) - then - if [ $BACKUP_CORE_ONLY -eq 1 ] - then + 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 ynh_print_info --message="$src_path will not be saved, because 'BACKUP_CORE_ONLY' is set." else ynh_print_info --message="$src_path will not be saved, because 'do_not_backup_data' is set." @@ -98,14 +96,11 @@ ynh_backup() { # Format correctly source and destination paths # ============================================================================== # Be sure the source path is not empty - if [ ! -e "$src_path" ] - then + if [ ! -e "$src_path" ]; then ynh_print_warn --message="Source path '${src_path}' does not exist" - if [ "$not_mandatory" == "0" ] - then + 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 + 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 @@ -123,13 +118,11 @@ 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) @@ -153,8 +146,7 @@ ynh_backup() { fi # Check if dest_path already exists in tmp archive - if [[ -e "${dest_path}" ]] - then + if [[ -e "${dest_path}" ]]; then ynh_print_err --message="Destination path '${dest_path}' already exist" return 1 fi @@ -171,7 +163,7 @@ ynh_backup() { # ============================================================================== 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}" + echo "\"${src}\",\"${dest}\"" >>"${YNH_BACKUP_CSV}" # ============================================================================== @@ -185,19 +177,18 @@ ynh_backup() { # usage: ynh_restore # # Requires YunoHost version 2.6.4 or higher. -ynh_restore () { +ynh_restore() { # Deduce the relative path of $YNH_CWD local REL_DIR="${YNH_CWD#$YNH_BACKUP_DIR/}" REL_DIR="${REL_DIR%/}/" # For each destination path begining by $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 --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 + 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 --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 } # Return the path in the archive where has been stocked the origin path @@ -205,7 +196,7 @@ ynh_restore () { # [internal] # # usage: _get_archive_path ORIGIN_PATH -_get_archive_path () { +_get_archive_path() { # For security reasons we use csv python library to read the CSV python3 -c " import sys @@ -217,7 +208,7 @@ with open(sys.argv[1], 'r') as backup_file: print(row['dest']) sys.exit(0) raise Exception('Original path for %s not found' % sys.argv[2]) - " "${YNH_BACKUP_CSV}" "$1" + " "${YNH_BACKUP_CSV}" "$1" return $? } @@ -245,10 +236,10 @@ with open(sys.argv[1], 'r') as backup_file: # # Requires YunoHost version 2.6.4 or higher. # Requires YunoHost version 3.5.0 or higher for the argument --not_mandatory -ynh_restore_file () { +ynh_restore_file() { # Declare an array to define the options of this helper. local legacy_args=odm - local -A 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 dest_path local not_mandatory @@ -261,10 +252,8 @@ ynh_restore_file () { 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 - if [ "$not_mandatory" == "0" ] - 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\")" else return 0 @@ -272,14 +261,12 @@ ynh_restore_file () { fi # Move the old directory if it already exists - if [[ -e "${dest_path}" ]] - then + if [[ -e "${dest_path}" ]]; then # Check if the file/dir size is less than 500 Mo - if [[ $(du --summarize --bytes ${dest_path} | cut --delimiter="/" --fields=1) -le "500000000" ]] - then + 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 --parents "$(dirname "$backup_file")" - mv "${dest_path}" "$backup_file" # Move the current file or directory + mv "${dest_path}" "$backup_file" # Move the current file or directory else ynh_secure_remove --file=${dest_path} fi @@ -289,10 +276,8 @@ ynh_restore_file () { mkdir --parents $(dirname "$dest_path") # Do a copy if it's just a mounting point - if mountpoint --quiet $YNH_BACKUP_DIR - then - if [[ -d "${archive_path}" ]] - then + if mountpoint --quiet $YNH_BACKUP_DIR; then + if [[ -d "${archive_path}" ]]; then archive_path="${archive_path}/." mkdir --parents "$dest_path" fi @@ -323,10 +308,10 @@ ynh_bind_or_cp() { # $app should be defined when calling this helper # # Requires YunoHost version 2.6.4 or higher. -ynh_store_file_checksum () { +ynh_store_file_checksum() { # Declare an array to define the options of this helper. local legacy_args=f - local -A args_array=( [f]=file= [u]=update_only ) + local -A args_array=([f]=file= [u]=update_only) local file local update_only update_only="${update_only:-0}" @@ -334,22 +319,21 @@ ynh_store_file_checksum () { # Manage arguments with getopts ynh_handle_getopts_args "$@" - local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_' - + 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 + if [ $update_only -eq 1 ]; then local checksum_value=$(ynh_app_setting_get --app=$app --key=$checksum_setting_name) - if [ -z "${checksum_value}" ] ; then + 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 - if [ -n "${backup_file_checksum-}" ] - then + if [ -n "${backup_file_checksum-}" ]; then # Print the diff between the previous file and the new one. # diff return 1 if the files are different, so the || true diff --report-identical-files --unified --color=always $backup_file_checksum $file >&2 || true @@ -368,27 +352,25 @@ ynh_store_file_checksum () { # modified config files. # # Requires YunoHost version 2.6.4 or higher. -ynh_backup_if_checksum_is_different () { +ynh_backup_if_checksum_is_different() { # 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=) local file # Manage arguments with getopts ynh_handle_getopts_args "$@" - local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_' + local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_' local checksum_value=$(ynh_app_setting_get --app=$app --key=$checksum_setting_name) # backup_file_checksum isn't declare as local, so it can be reuse by ynh_store_file_checksum backup_file_checksum="" - if [ -n "$checksum_value" ] - then # Proceed only if a value was stored into the app settings - if [ -e $file ] && ! echo "$checksum_value $file" | md5sum --check --status - then # If the checksum is now different + if [ -n "$checksum_value" ]; then # Proceed only if a value was stored into the app settings + 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 --parents "$(dirname "$backup_file_checksum")" - cp --archive "$file" "$backup_file_checksum" # Backup the current file + 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 + echo "$backup_file_checksum" # Return the name of the backup file fi fi } @@ -401,15 +383,15 @@ ynh_backup_if_checksum_is_different () { # $app should be defined when calling this helper # # Requires YunoHost version 3.3.1 or higher. -ynh_delete_file_checksum () { +ynh_delete_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=) local file # Manage arguments with getopts ynh_handle_getopts_args "$@" - local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_' + local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_' ynh_app_setting_delete --app=$app --key=$checksum_setting_name } @@ -417,7 +399,7 @@ ynh_delete_file_checksum () { # # [internal] # -ynh_backup_archive_exists () { +ynh_backup_archive_exists() { yunohost backup list --output-as json --quiet \ | jq -e --arg archive "$1" '.archives | index($archive)' >/dev/null } @@ -436,22 +418,19 @@ ynh_backup_archive_exists () { # ``` # # Requires YunoHost version 2.7.2 or higher. -ynh_backup_before_upgrade () { - if [ ! -e "/etc/yunohost/apps/$app/scripts/backup" ] - then +ynh_backup_before_upgrade() { + if [ ! -e "/etc/yunohost/apps/$app/scripts/backup" ]; then ynh_print_warn --message="This app doesn't have any backup script." return fi backup_number=1 local old_backup_number=2 - local app_bck=${app//_/-} # Replace all '_' by '-' + local app_bck=${app//_/-} # Replace all '_' by '-' NO_BACKUP_UPGRADE=${NO_BACKUP_UPGRADE:-0} - if [ "$NO_BACKUP_UPGRADE" -eq 0 ] - then + if [ "$NO_BACKUP_UPGRADE" -eq 0 ]; then # Check if a backup already exists with the prefix 1 - if ynh_backup_archive_exists "$app_bck-pre-upgrade1" - then + if ynh_backup_archive_exists "$app_bck-pre-upgrade1"; then # Prefix becomes 2 to preserve the previous backup backup_number=2 old_backup_number=1 @@ -459,13 +438,11 @@ ynh_backup_before_upgrade () { # Create backup BACKUP_CORE_ONLY=1 yunohost backup create --apps $app --name $app_bck-pre-upgrade$backup_number --debug - if [ "$?" -eq 0 ] - then + if [ "$?" -eq 0 ]; then # If the backup succeeded, remove the previous backup - if ynh_backup_archive_exists "$app_bck-pre-upgrade$old_backup_number" - then + if ynh_backup_archive_exists "$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 + yunohost backup delete $app_bck-pre-upgrade$old_backup_number >/dev/null fi else ynh_die --message="Backup failed, the upgrade process was aborted." @@ -489,17 +466,15 @@ ynh_backup_before_upgrade () { # ``` # # Requires YunoHost version 2.7.2 or higher. -ynh_restore_upgradebackup () { +ynh_restore_upgradebackup() { ynh_print_err --message="Upgrade failed." - local app_bck=${app//_/-} # Replace all '_' by '-' + local app_bck=${app//_/-} # Replace all '_' by '-' NO_BACKUP_UPGRADE=${NO_BACKUP_UPGRADE:-0} - if [ "$NO_BACKUP_UPGRADE" -eq 0 ] - then + if [ "$NO_BACKUP_UPGRADE" -eq 0 ]; then # Check if an existing backup can be found before removing and restoring the application. - if ynh_backup_archive_exists "$app_bck-pre-upgrade$backup_number" - then + if ynh_backup_archive_exists "$app_bck-pre-upgrade$backup_number"; then # Remove the application then restore it yunohost app remove $app # Restore the backup diff --git a/data/helpers.d/config b/data/helpers.d/config index 3f856ffa4..247d12d6f 100644 --- a/data/helpers.d/config +++ b/data/helpers.d/config @@ -1,60 +1,49 @@ #!/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 + 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 + 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 + + 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 --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)" + 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 --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)" + 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 + 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 + if [[ "$bind_key_" == *">"* ]]; then bind_after="$(echo "${bind_key_}" | cut -d'>' -f1)" bind_key_="$(echo "${bind_key_}" | cut -d'>' -f2)" fi @@ -68,39 +57,31 @@ _ynh_app_config_apply_one() { 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" == *"("* ]] && type -t "set__${bind%%(*}" 2>/dev/null | grep -q '^function$' 2>/dev/null; - then + 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 + + 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 --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 + 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 + if [[ "${!short_setting}" != "$bind_file" ]]; then cp "${!short_setting}" "$bind_file" fi ynh_store_file_checksum --file="$bind_file" --update_only @@ -108,21 +89,18 @@ _ynh_app_config_apply_one() { fi # Save value in app settings - elif [[ "$bind" == "settings" ]] - then + 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 + 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" + echo "${!short_setting}" >"$bind_file" ynh_store_file_checksum --file="$bind_file" --update_only ynh_print_info --message="File '$bind_file' overwritten with the content provided in question '${short_setting}'" @@ -131,8 +109,7 @@ _ynh_app_config_apply_one() { local bind_after="" local bind_key_="$(echo "$bind" | cut -d: -f1)" bind_key_=${bind_key_:-$short_setting} - if [[ "$bind_key_" == *">"* ]]; - then + if [[ "$bind_key_" == *">"* ]]; then bind_after="$(echo "${bind_key_}" | cut -d'>' -f1)" bind_key_="$(echo "${bind_key_}" | cut -d'>' -f2)" fi @@ -152,7 +129,8 @@ _ynh_app_config_apply_one() { _ynh_app_config_get() { # From settings local lines - lines=$(python3 << EOL + lines=$( + python3 </dev/null; - then + 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 + 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 + if [ -n "$result" ]; then # # Return a yaml such as: # @@ -287,8 +246,7 @@ _ynh_app_config_validate() { # # We use changes_validated to know if this is # the first validation error - if [[ "$changes_validated" == true ]] - then + if [[ "$changes_validated" == true ]]; then ynh_return "validation_errors:" fi ynh_return " ${short_setting}: \"$result\"" @@ -298,8 +256,7 @@ _ynh_app_config_validate() { # 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 + if [[ "$changes_validated" == "false" ]]; then exit 0 fi @@ -337,21 +294,20 @@ ynh_app_config_run() { declare -Ag formats=() case $1 in - 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_app_config_get + 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_app_config_get - ynh_app_config_validate + 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 - ;; + ynh_script_progression --message="Applying the new configuration..." + ynh_app_config_apply + ynh_script_progression --message="Configuration of $app completed" --last + ;; esac } - diff --git a/data/helpers.d/fail2ban b/data/helpers.d/fail2ban index 26c899d93..2b976cb8f 100644 --- a/data/helpers.d/fail2ban +++ b/data/helpers.d/fail2ban @@ -62,10 +62,10 @@ # ``` # # Requires YunoHost version 4.1.0 or higher. -ynh_add_fail2ban_config () { +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 -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 @@ -81,8 +81,7 @@ ynh_add_fail2ban_config () { [[ -z "$others_var" ]] || ynh_print_warn --message="Packagers: using --others_var is unecessary since YunoHost 4.2" - if [ $use_template -ne 1 ] - then + if [ $use_template -ne 1 ]; then # Usage 1, no template. Build a config file from scratch. 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." @@ -94,15 +93,15 @@ port = __PORTS__ filter = __APP__ logpath = __LOGPATH__ maxretry = __MAX_RETRY__ -" > $YNH_APP_BASEDIR/conf/f2b_jail.conf +" >$YNH_APP_BASEDIR/conf/f2b_jail.conf - echo " + echo " [INCLUDES] before = common.conf [Definition] failregex = __FAILREGEX__ ignoreregex = -" > $YNH_APP_BASEDIR/conf/f2b_filter.conf +" >$YNH_APP_BASEDIR/conf/f2b_filter.conf fi ynh_add_config --template="$YNH_APP_BASEDIR/conf/f2b_jail.conf" --destination="/etc/fail2ban/jail.d/$app.conf" @@ -111,8 +110,7 @@ ignoreregex = ynh_systemd_action --service_name=fail2ban --action=reload --line_match="(Started|Reloaded) Fail2Ban Service" --log_path=systemd local fail2ban_error="$(journalctl --no-hostname --unit=fail2ban | tail --lines=50 | grep "WARNING.*$app.*")" - if [[ -n "$fail2ban_error" ]] - then + 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 @@ -123,7 +121,7 @@ ignoreregex = # usage: ynh_remove_fail2ban_config # # Requires YunoHost version 3.5.0 or higher. -ynh_remove_fail2ban_config () { +ynh_remove_fail2ban_config() { 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/getopts b/data/helpers.d/getopts index 8d9e55826..e912220e4 100644 --- a/data/helpers.d/getopts +++ b/data/helpers.d/getopts @@ -45,11 +45,10 @@ # e.g. for `my_helper "val1" val2`, arg1 will be filled with val1, and arg2 with val2. # # Requires YunoHost version 3.2.2 or higher. -ynh_handle_getopts_args () { +ynh_handle_getopts_args() { # Manage arguments only if there's some provided set +o xtrace # set +x - if [ $# -ne 0 ] - then + if [ $# -ne 0 ]; then # Store arguments in an array to keep each argument separated local arguments=("$@") @@ -58,14 +57,12 @@ ynh_handle_getopts_args () { # ${!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 + 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 + 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 @@ -74,8 +71,7 @@ ynh_handle_getopts_args () { # Check each argument given to the function local arg="" # ${#arguments[@]} is the size of the array - for arg in `seq 0 $(( ${#arguments[@]} - 1 ))` - do + 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 @@ -89,10 +85,9 @@ ynh_handle_getopts_args () { # Read and parse all the arguments # Use a function here, to use standart arguments $@ and be able to use shift. - parse_arg () { + parse_arg() { # Read all arguments, until no arguments are left - while [ $# -ne 0 ] - do + 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 @@ -100,11 +95,9 @@ ynh_handle_getopts_args () { local parameter="" getopts ":$getopts_parameters" parameter || true - if [ "$parameter" = "?" ] - then + if [ "$parameter" = "?" ]; then ynh_die --message="Invalid argument: -${OPTARG:-}" - elif [ "$parameter" = ":" ] - then + elif [ "$parameter" = ":" ]; then ynh_die --message="-$OPTARG parameter requires an argument." else local shift_value=1 @@ -115,8 +108,7 @@ ynh_handle_getopts_args () { 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 + if [ "${args_array[$parameter]: -1}" != "=" ]; then # 'eval ${option_var}' will use the content of 'option_var' eval ${option_var}=1 else @@ -126,41 +118,35 @@ ynh_handle_getopts_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 + 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} }" # At this point, if all_args[0] start with "-", then the argument is not well formed - if [ "${all_args[0]:0:1}" == "-" ] - then + if [ "${all_args[0]:0:1}" == "-" ]; then ynh_die --message="Argument \"${all_args[0]}\" not valid! Did you use a single \"-\" instead of two?" fi # Reduce the value of shift, because the option has been removed manually - shift_value=$(( shift_value - 1 )) + 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 + for i in $(seq 0 $((${#all_args[@]} - 1))); do # If this argument is an option, end here. - if [ "${all_args[$i]:0:1}" == "-" ] - then + 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 # Ignore empty parameters - if [ -n "${all_args[$i]}" ] - then + 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 [ -n "${!option_var}" ]; then # If there's already another value for this option, add a ; before adding the new value eval ${option_var}+="\;" fi @@ -177,7 +163,7 @@ ynh_handle_getopts_args () { eval ${option_var}+='"${all_args[$i]}"' fi - shift_value=$(( shift_value + 1 )) + shift_value=$((shift_value + 1)) fi done fi @@ -190,24 +176,23 @@ ynh_handle_getopts_args () { # LEGACY MODE # Check if there's getopts arguments - if [ "${arguments[0]:0:1}" != "-" ] - then + 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 + set -x + echo "! Helper used in legacy mode !" >/dev/null + set +x local i - for i in `seq 0 $(( ${#arguments[@]} -1 ))` - do + 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//:}} + 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 + 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) diff --git a/data/helpers.d/hardware b/data/helpers.d/hardware index 6d1c314fa..9f276b806 100644 --- a/data/helpers.d/hardware +++ b/data/helpers.d/hardware @@ -10,10 +10,10 @@ # | ret: the amount of free ram, in MB (MegaBytes) # # Requires YunoHost version 3.8.1 or higher. -ynh_get_ram () { +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 -A args_array=([f]=free [t]=total [s]=ignore_swap [o]=only_swap) local free local total local ignore_swap @@ -25,41 +25,34 @@ ynh_get_ram () { free=${free:-0} total=${total:-0} - if [ $free -eq $total ] - then + 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 + 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 )) + 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 ] - then + if [ $ignore_swap -eq 1 ]; then # Use only the amount of free ram ram=$free_ram - elif [ $only_swap -eq 1 ] - then + elif [ $only_swap -eq 1 ]; then # Use only the amount of free swap ram=$free_swap fi - elif [ $total -eq 1 ] - then + 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 total_ram_swap=$((total_ram + total_swap)) local ram=$total_ram_swap - if [ $ignore_swap -eq 1 ] - then + if [ $ignore_swap -eq 1 ]; then # Use only the amount of free ram ram=$total_ram - elif [ $only_swap -eq 1 ] - then + elif [ $only_swap -eq 1 ]; then # Use only the amount of free swap ram=$total_swap fi @@ -79,10 +72,10 @@ ynh_get_ram () { # | ret: 1 if the ram is under the requirement, 0 otherwise. # # Requires YunoHost version 3.8.1 or higher. -ynh_require_ram () { +ynh_require_ram() { # Declare an array to define the options of this helper. local legacy_args=rftso - local -A 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 @@ -100,8 +93,7 @@ ynh_require_ram () { local ram=$(ynh_get_ram $free $total $ignore_swap $only_swap) - if [ $ram -lt $required ] - then + if [ $ram -lt $required ]; then return 1 else return 0 diff --git a/data/helpers.d/logging b/data/helpers.d/logging index d11fc578f..7a8be30e9 100644 --- a/data/helpers.d/logging +++ b/data/helpers.d/logging @@ -10,7 +10,7 @@ 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 -A args_array=([m]=message= [c]=ret_code=) local message local ret_code # Manage arguments with getopts @@ -30,7 +30,7 @@ ynh_die() { ynh_print_info() { # Declare an array to define the options of this helper. local legacy_args=m - local -A args_array=( [m]=message= ) + local -A args_array=([m]=message=) local message # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -62,7 +62,7 @@ ynh_no_log() { # [internal] # # Requires YunoHost version 3.2.0 or higher. -ynh_print_log () { +ynh_print_log() { echo -e "${1}" } @@ -72,10 +72,10 @@ ynh_print_log () { # | arg: -m, --message= - The text to print # # Requires YunoHost version 3.2.0 or higher. -ynh_print_warn () { +ynh_print_warn() { # Declare an array to define the options of this helper. local legacy_args=m - local -A args_array=( [m]=message= ) + local -A args_array=([m]=message=) local message # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -89,10 +89,10 @@ ynh_print_warn () { # | arg: -m, --message= - The text to print # # Requires YunoHost version 3.2.0 or higher. -ynh_print_err () { +ynh_print_err() { # Declare an array to define the options of this helper. local legacy_args=m - local -A args_array=( [m]=message= ) + local -A args_array=([m]=message=) local message # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -110,7 +110,7 @@ ynh_print_err () { # If the command to execute uses double quotes, they have to be escaped or they will be interpreted and removed. # # Requires YunoHost version 3.2.0 or higher. -ynh_exec_err () { +ynh_exec_err() { ynh_print_err "$(eval $@)" } @@ -124,7 +124,7 @@ ynh_exec_err () { # If the command to execute uses double quotes, they have to be escaped or they will be interpreted and removed. # # Requires YunoHost version 3.2.0 or higher. -ynh_exec_warn () { +ynh_exec_warn() { ynh_print_warn "$(eval $@)" } @@ -138,7 +138,7 @@ ynh_exec_warn () { # If the command to execute uses double quotes, they have to be escaped or they will be interpreted and removed. # # Requires YunoHost version 3.2.0 or higher. -ynh_exec_warn_less () { +ynh_exec_warn_less() { eval $@ 2>&1 } @@ -152,8 +152,8 @@ ynh_exec_warn_less () { # If the command to execute uses double quotes, they have to be escaped or they will be interpreted and removed. # # Requires YunoHost version 3.2.0 or higher. -ynh_exec_quiet () { - eval $@ > /dev/null +ynh_exec_quiet() { + eval $@ >/dev/null } # Execute a command and redirect stdout and stderr in /dev/null @@ -166,8 +166,8 @@ ynh_exec_quiet () { # If the command to execute uses double quotes, they have to be escaped or they will be interpreted and removed. # # Requires YunoHost version 3.2.0 or higher. -ynh_exec_fully_quiet () { - eval $@ > /dev/null 2>&1 +ynh_exec_fully_quiet() { + eval $@ >/dev/null 2>&1 } # Remove any logs for all the following commands. @@ -177,7 +177,7 @@ ynh_exec_fully_quiet () { # WARNING: You should be careful with this helper, and never forget to use ynh_print_ON as soon as possible to restore the logging. # # Requires YunoHost version 3.2.0 or higher. -ynh_print_OFF () { +ynh_print_OFF() { exec {BASH_XTRACEFD}>/dev/null } @@ -186,10 +186,10 @@ ynh_print_OFF () { # usage: ynh_print_ON # # Requires YunoHost version 3.2.0 or higher. -ynh_print_ON () { +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 + echo ynh_print_ON >/dev/null } # Initial definitions for ynh_script_progression @@ -214,11 +214,11 @@ base_time=$(date +%s) # | 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 () { +ynh_script_progression() { 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 ) + local -A args_array=([m]=message= [w]=weight= [t]=time [l]=last) local message local weight local time @@ -232,12 +232,11 @@ ynh_script_progression () { last=${last:-0} # Get execution time since the last $base_time - local exec_time=$(( $(date +%s) - $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 + 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 @@ -249,23 +248,22 @@ ynh_script_progression () { 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" "$weight_valuesB" | grep -v -E '^\s*$' | tr '\n' '+' | sed 's/+$/+0/g') )) + local weight_values=$(($(echo "$weight_valuesA" "$weight_valuesB" | grep -v -E '^\s*$' | tr '\n' '+' | sed 's/+$/+0/g'))) # 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 )) + 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 )) + 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 -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 @@ -273,19 +271,17 @@ ynh_script_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 + 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 ))" + 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 + if [ $time -eq 1 ]; then print_exec_time=" [$(date +%Hh%Mm,%Ss --date="0 + $exec_time sec")]" fi @@ -299,8 +295,8 @@ ynh_script_progression () { # usage: ynh_return somedata # # Requires YunoHost version 3.6.0 or higher. -ynh_return () { - echo "$1" >> "$YNH_STDRETURN" +ynh_return() { + echo "$1" >>"$YNH_STDRETURN" } # Debugger for app packagers @@ -310,12 +306,12 @@ ynh_return () { # | arg: -t, --trace= - Turn on or off the trace of the script. Usefull to trace nonly a small part of a script. # # Requires YunoHost version 3.5.0 or higher. -ynh_debug () { +ynh_debug() { # Disable set xtrace for the helper itself, to not pollute the debug log 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= ) + local -A args_array=([m]=message= [t]=trace=) local message local trace # Manage arguments with getopts @@ -325,13 +321,11 @@ ynh_debug () { message=${message:-} trace=${trace:-} - if [ -n "$message" ] - then + if [ -n "$message" ]; then ynh_print_log "[Debug] ${message}" >&2 fi - if [ "$trace" == "1" ] - then + if [ "$trace" == "1" ]; then ynh_debug --message="Enable debugging" set +o xtrace # set +x # Get the current file descriptor of xtrace @@ -343,8 +337,7 @@ ynh_debug () { # Force stdout to stderr exec 1>&2 fi - if [ "$trace" == "0" ] - then + if [ "$trace" == "0" ]; then ynh_debug --message="Disable debugging" set +o xtrace # set +x # Put xtrace back to its original fild descriptor @@ -366,6 +359,6 @@ ynh_debug () { # If the command to execute uses double quotes, they have to be escaped or they will be interpreted and removed. # # Requires YunoHost version 3.5.0 or higher. -ynh_debug_exec () { +ynh_debug_exec() { ynh_debug --message="$(eval $@)" } diff --git a/data/helpers.d/logrotate b/data/helpers.d/logrotate index 2d9ab6b72..6f9726beb 100644 --- a/data/helpers.d/logrotate +++ b/data/helpers.d/logrotate @@ -15,10 +15,10 @@ # # Requires YunoHost version 2.6.4 or higher. # Requires YunoHost version 3.2.0 or higher for the argument `--specific_user` -ynh_use_logrotate () { +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 ) + 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 @@ -30,22 +30,18 @@ ynh_use_logrotate () { specific_user="${specific_user:-}" # LEGACY CODE - PRE GETOPTS - if [ $# -gt 0 ] && [ "$1" == "--non-append" ] - then + if [ $# -gt 0 ] && [ "$1" == "--non-append" ]; then nonappend=1 # Destroy this argument for the next command. shift - elif [ $# -gt 1 ] && [ "$2" == "--non-append" ] - then + elif [ $# -gt 1 ] && [ "$2" == "--non-append" ]; then nonappend=1 fi - if [ $# -gt 0 ] && [ "$(echo ${1:0:1})" != "-" ] - then + 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 + 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 @@ -58,22 +54,20 @@ ynh_use_logrotate () { 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. + 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 + 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 < /dev/null # Append this config to the existing config file, or replace the whole config file (depending on $customtee) + 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) } # Remove the app's logrotate config. @@ -103,7 +97,7 @@ EOF # usage: ynh_remove_logrotate # # Requires YunoHost version 2.6.4 or higher. -ynh_remove_logrotate () { +ynh_remove_logrotate() { if [ -e "/etc/logrotate.d/$app" ]; then rm "/etc/logrotate.d/$app" fi diff --git a/data/helpers.d/multimedia b/data/helpers.d/multimedia index 552b8c984..abeb9ed2c 100644 --- a/data/helpers.d/multimedia +++ b/data/helpers.d/multimedia @@ -22,8 +22,7 @@ ynh_multimedia_build_main_dir() { 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 + 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" @@ -66,22 +65,22 @@ ynh_multimedia_addfolder() { # 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 + 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" + 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" + ## 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 @@ -91,14 +90,14 @@ 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. +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 - ynh_handle_getopts_args "$@" + 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 + groupadd -f multimedia + usermod -a -G multimedia $user_name } diff --git a/data/helpers.d/mysql b/data/helpers.d/mysql index 091dfaf40..822159f27 100644 --- a/data/helpers.d/mysql +++ b/data/helpers.d/mysql @@ -15,7 +15,7 @@ ynh_mysql_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 -A args_array=([u]=user= [p]=password= [d]=database=) local user local password local database @@ -36,19 +36,18 @@ ynh_mysql_connect_as() { ynh_mysql_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 -A args_array=([s]=sql= [d]=database=) local sql local database # Manage arguments with getopts ynh_handle_getopts_args "$@" database="${database:-}" - if [ -n "$database" ] - then + if [ -n "$database" ]; then database="--database=$database" fi - mysql -B "$database" <<< "$sql" + mysql -B "$database" <<<"$sql" } # Execute a command from a file as root user @@ -61,19 +60,18 @@ 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 - local -A args_array=( [f]=file= [d]=database= ) + local -A args_array=([f]=file= [d]=database=) local file local database # Manage arguments with getopts ynh_handle_getopts_args "$@" database="${database:-}" - if [ -n "$database" ] - then + if [ -n "$database" ]; then database="--database=$database" fi - mysql -B "$database" < "$file" + mysql -B "$database" <"$file" } # Create a database and grant optionnaly privilegies to a user @@ -92,8 +90,7 @@ ynh_mysql_create_db() { local sql="CREATE DATABASE ${db};" # grant all privilegies to user - if [[ $# -gt 1 ]] - then + if [[ $# -gt 1 ]]; then sql+=" GRANT ALL PRIVILEGES ON ${db}.* TO '${2}'@'localhost'" if [[ -n ${3:-} ]]; then sql+=" IDENTIFIED BY '${3}'" @@ -131,7 +128,7 @@ ynh_mysql_drop_db() { ynh_mysql_dump_db() { # Declare an array to define the options of this helper. local legacy_args=d - local -A args_array=( [d]=database= ) + local -A args_array=([d]=database=) local database # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -160,17 +157,15 @@ ynh_mysql_create_user() { # | ret: 0 if the user exists, 1 otherwise. # # Requires YunoHost version 2.2.4 or higher. -ynh_mysql_user_exists() -{ +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 -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 + if [[ -z $(ynh_mysql_execute_as_root --sql="SELECT User from mysql.user WHERE User = '$user';") ]]; then return 1 else return 0 @@ -200,10 +195,10 @@ ynh_mysql_drop_user() { # It will also be stored as "`mysqlpwd`" into the app settings. # # Requires YunoHost version 2.6.4 or higher. -ynh_mysql_setup_db () { +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 -A args_array=([u]=db_user= [n]=db_name= [p]=db_pwd=) local db_user local db_name db_pwd="" @@ -226,10 +221,10 @@ ynh_mysql_setup_db () { # | arg: -n, --db_name= - Name of the database # # Requires YunoHost version 2.6.4 or higher. -ynh_mysql_remove_db () { +ynh_mysql_remove_db() { # Declare an array to define the options of this helper. local legacy_args=un - local -Ar 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 diff --git a/data/helpers.d/network b/data/helpers.d/network index 4e536a8db..d6c15060a 100644 --- a/data/helpers.d/network +++ b/data/helpers.d/network @@ -9,18 +9,17 @@ # example: port=$(ynh_find_port --port=8080) # # Requires YunoHost version 2.6.4 or higher. -ynh_find_port () { +ynh_find_port() { # Declare an array to define the options of this helper. local legacy_args=p - local -A args_array=( [p]=port= ) + 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 ! ynh_port_available --port=$port - do - port=$((port+1)) + while ! ynh_port_available --port=$port; do + port=$((port + 1)) done echo $port } @@ -34,28 +33,25 @@ ynh_find_port () { # 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 () { +ynh_port_available() { # Declare an array to define the options of this helper. local legacy_args=p - local -A args_array=( [p]=port= ) + local -A args_array=([p]=port=) local port # Manage arguments with getopts ynh_handle_getopts_args "$@" # Check if the port is free - if ss --numeric --listening --tcp --udp | awk '{print$5}' | grep --quiet --extended-regexp ":$port$" - then + 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 + elif grep -q "port: '$port'" /etc/yunohost/apps/*/settings.yml; then return 1 else return 0 fi } - # Validate an IP address # # [internal] @@ -66,13 +62,12 @@ ynh_port_available () { # example: ynh_validate_ip 4 111.222.333.444 # # Requires YunoHost version 2.2.4 or higher. -ynh_validate_ip() -{ +ynh_validate_ip() { # http://stackoverflow.com/questions/319279/how-to-validate-ip-address-in-python#319298 # Declare an array to define the options of this helper. local legacy_args=fi - local -A 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 @@ -80,7 +75,7 @@ ynh_validate_ip() [ "$family" == "4" ] || [ "$family" == "6" ] || return 1 - python3 /dev/stdin << EOF + python3 /dev/stdin < "$YNH_APP_BASEDIR/conf/n.src" +SOURCE_SUM=d4da7ea91f680de0c9b5876e097e2a793e8234fcd0f7ca87a0599b925be087a3" >"$YNH_APP_BASEDIR/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) + ( + cd "$n_install_dir/git" + PREFIX=$N_PREFIX make install 2>&1 + ) } # Load the version of node for an app, and set variables. @@ -69,7 +71,7 @@ SOURCE_SUM=d4da7ea91f680de0c9b5876e097e2a793e8234fcd0f7ca87a0599b925be087a3" > " # - $nodejs_version: Just the version number of node for this app. Stored as 'nodejs_version' in settings.yml. # # Requires YunoHost version 2.7.12 or higher. -ynh_use_nodejs () { +ynh_use_nodejs() { nodejs_version=$(ynh_app_setting_get --app=$app --key=nodejs_version) # Get the absolute path of this version of node @@ -109,12 +111,12 @@ ynh_use_nodejs () { # Refer to `ynh_use_nodejs` for more information about available commands and variables # # Requires YunoHost version 2.7.12 or higher. -ynh_install_nodejs () { +ynh_install_nodejs() { # Use n, https://github.com/tj/n to manage the nodejs versions # Declare an array to define the options of this helper. local legacy_args=n - local -A args_array=( [n]=nodejs_version= ) + local -A args_array=([n]=nodejs_version=) local nodejs_version # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -132,11 +134,9 @@ ynh_install_nodejs () { test -x /usr/bin/npm && mv /usr/bin/npm /usr/bin/npm_n # If n is not previously setup, install it - if ! $n_install_dir/bin/n --version > /dev/null 2>&1 - then + if ! $n_install_dir/bin/n --version >/dev/null 2>&1; then ynh_install_n - elif dpkg --compare-versions "$($n_install_dir/bin/n --version)" lt $n_version - then + elif dpkg --compare-versions "$($n_install_dir/bin/n --version)" lt $n_version; then ynh_install_n fi @@ -152,8 +152,7 @@ ynh_install_nodejs () { # Install the requested version of nodejs uname=$(uname --machine) - if [[ $uname =~ aarch64 || $uname =~ arm64 ]] - then + if [[ $uname =~ aarch64 || $uname =~ arm64 ]]; then n $nodejs_version --arch=arm64 else n $nodejs_version @@ -164,8 +163,7 @@ ynh_install_nodejs () { 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 + 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 @@ -190,21 +188,19 @@ ynh_install_nodejs () { # - If no other app uses node, n will be also removed. # # Requires YunoHost version 2.7.12 or higher. -ynh_remove_nodejs () { +ynh_remove_nodejs() { nodejs_version=$(ynh_app_setting_get --app=$app --key=nodejs_version) # Remove the line for this app sed --in-place "/$YNH_APP_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 + 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 + 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 @@ -221,9 +217,9 @@ ynh_remove_nodejs () { # usage: ynh_cron_upgrade_node # # Requires YunoHost version 2.7.12 or higher. -ynh_cron_upgrade_node () { +ynh_cron_upgrade_node() { # Build the update script - cat > "$n_install_dir/node_update.sh" << EOF + cat >"$n_install_dir/node_update.sh" < "/etc/cron.daily/node_update" << EOF + cat >"/etc/cron.daily/node_update" <> $n_install_dir/node_update.log diff --git a/data/helpers.d/permission b/data/helpers.d/permission index c04b4145b..6c2fa7ef8 100644 --- a/data/helpers.d/permission +++ b/data/helpers.d/permission @@ -66,7 +66,7 @@ ynh_permission_create() { # Declare an array to define the options of this helper. local legacy_args=puAhaltP - local -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 @@ -84,13 +84,11 @@ ynh_permission_create() { show_tile=${show_tile:-} protected=${protected:-} - if [[ -n $url ]] - then + if [[ -n $url ]]; then url=",url='$url'" fi - if [[ -n $additional_urls ]] - then + if [[ -n $additional_urls ]]; then # Convert a list from getopts to python list # Note that getopts separate the args with ';' # By example: @@ -100,18 +98,15 @@ ynh_permission_create() { additional_urls=",additional_urls=['${additional_urls//;/\',\'}']" 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" fi fi - if [[ -n $allowed ]] - then + if [[ -n $allowed ]]; then # Convert a list from getopts to python list # Note that getopts separate the args with ';' # By example: @@ -127,20 +122,16 @@ ynh_permission_create() { label=",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" @@ -161,7 +152,7 @@ ynh_permission_create() { ynh_permission_delete() { # Declare an array to define the options of this helper. local legacy_args=p - local -A args_array=( [p]=permission= ) + local -A args_array=([p]=permission=) local permission ynh_handle_getopts_args "$@" @@ -178,7 +169,7 @@ ynh_permission_delete() { ynh_permission_exists() { # Declare an array to define the options of this helper. local legacy_args=p - local -A args_array=( [p]=permission= ) + local -A args_array=([p]=permission=) local permission ynh_handle_getopts_args "$@" @@ -201,7 +192,7 @@ ynh_permission_exists() { ynh_permission_url() { # Declare an array to define the options of this helper. local legacy_args=puarhc - local -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 @@ -215,13 +206,11 @@ ynh_permission_url() { auth_header=${auth_header:-} clear_urls=${clear_urls:-} - if [[ -n $url ]] - then + if [[ -n $url ]]; then url=",url='$url'" fi - if [[ -n $add_url ]] - then + if [[ -n $add_url ]]; then # Convert a list from getopts to python list # Note that getopts separate the args with ';' # For example: @@ -231,8 +220,7 @@ ynh_permission_url() { add_url=",add_url=['${add_url//;/\',\'}']" fi - if [[ -n $remove_url ]] - then + if [[ -n $remove_url ]]; then # Convert a list from getopts to python list # Note that getopts separate the args with ';' # For example: @@ -242,25 +230,21 @@ ynh_permission_url() { 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" fi fi - if [[ -n $clear_urls ]] && [ $clear_urls -eq 1 ] - then + 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" ...]] @@ -276,7 +260,7 @@ ynh_permission_url() { ynh_permission_update() { # Declare an array to define the options of this helper. local legacy_args=parltP - local -A args_array=( [p]=permission= [a]=add= [r]=remove= [l]=label= [t]=show_tile= [P]=protected= ) + local -A args_array=([p]=permission= [a]=add= [r]=remove= [l]=label= [t]=show_tile= [P]=protected=) local permission local add local remove @@ -290,8 +274,7 @@ ynh_permission_update() { show_tile=${show_tile:-} protected=${protected:-} - if [[ -n $add ]] - then + if [[ -n $add ]]; then # Convert a list from getopts to python list # Note that getopts separate the args with ';' # For example: @@ -300,8 +283,7 @@ ynh_permission_update() { # add=['alice', 'bob'] add=",add=['${add//';'/"','"}']" fi - if [[ -n $remove ]] - then + if [[ -n $remove ]]; then # Convert a list from getopts to python list # Note that getopts separate the args with ';' # For example: @@ -311,15 +293,12 @@ ynh_permission_update() { remove=",remove=['${remove//';'/"','"}']" fi - if [[ -n $label ]] - then + if [[ -n $label ]]; then 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" @@ -327,8 +306,7 @@ ynh_permission_update() { fi if [[ -n $protected ]]; then - if [ $protected == "true" ] - then + if [ $protected == "true" ]; then protected=",protected=True" else protected=",protected=False" @@ -351,23 +329,20 @@ ynh_permission_update() { ynh_permission_has_user() { local legacy_args=pu # Declare an array to define the options of this helper. - local -A args_array=( [p]=permission= [u]=user= ) + local -A args_array=([p]=permission= [u]=user=) local permission local user # Manage arguments with getopts ynh_handle_getopts_args "$@" - if ! ynh_permission_exists --permission=$permission - then + if ! ynh_permission_exists --permission=$permission; then return 1 fi # Check both allowed and corresponding_users sections in the json - for section in "allowed" "corresponding_users" - do + 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 + | jq -e --arg user $user --arg section $section '.[$section] | index($user)' >/dev/null; then return 0 fi done @@ -381,9 +356,8 @@ ynh_permission_has_user() { # | exit: Return 1 if the permission doesn't exist, 0 otherwise # # Requires YunoHost version 4.1.2 or higher. -ynh_legacy_permissions_exists () { - for permission in "skipped" "unprotected" "protected" - do +ynh_legacy_permissions_exists() { + for permission in "skipped" "unprotected" "protected"; do if ynh_permission_exists --permission="legacy_${permission}_uris"; then return 0 fi @@ -402,9 +376,8 @@ ynh_legacy_permissions_exists () { # # You can recreate the required permissions here with ynh_permission_create # fi # Requires YunoHost version 4.1.2 or higher. -ynh_legacy_permissions_delete_all () { - for permission in "skipped" "unprotected" "protected" - do +ynh_legacy_permissions_delete_all() { + for permission in "skipped" "unprotected" "protected"; do if ynh_permission_exists --permission="legacy_${permission}_uris"; then ynh_permission_delete --permission="legacy_${permission}_uris" fi diff --git a/data/helpers.d/php b/data/helpers.d/php index 7c91d89d2..0aff50ee3 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -56,10 +56,10 @@ YNH_PHP_VERSION=${YNH_PHP_VERSION:-$YNH_DEFAULT_PHP_VERSION} # children ready to answer. # # Requires YunoHost version 4.1.0 or higher. -ynh_add_fpm_config () { +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 -A args_array=([v]=phpversion= [t]=use_template [u]=usage= [f]=footprint= [p]=package= [d]=dedicated_service) local phpversion local use_template local usage @@ -86,8 +86,7 @@ ynh_add_fpm_config () { local old_phpversion=$(ynh_app_setting_get --app=$app --key=phpversion) # If the PHP version changed, remove the old fpm conf - if [ -n "$old_phpversion" ] && [ "$old_phpversion" != "$phpversion" ] - then + if [ -n "$old_phpversion" ] && [ "$old_phpversion" != "$phpversion" ]; then local old_php_fpm_config_dir=$(ynh_app_setting_get --app=$app --key=fpm_config_dir) local old_php_finalphpconf="$old_php_fpm_config_dir/pool.d/$app.conf" @@ -97,25 +96,21 @@ ynh_add_fpm_config () { fi # If the requested PHP version is not the default version for YunoHost - if [ "$phpversion" != "$YNH_DEFAULT_PHP_VERSION" ] - then + 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 + 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 + 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 + if [ $dedicated_service -eq 1 ]; then local fpm_service="${app}-phpfpm" local fpm_config_dir="/etc/php/$phpversion/dedicated-fpm" else @@ -132,12 +127,10 @@ ynh_add_fpm_config () { ynh_app_setting_set --app=$app --key=phpversion --value=$phpversion # Migrate from mutual PHP service to dedicated one. - if [ $dedicated_service -eq 1 ] - then + 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 + 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" @@ -148,8 +141,7 @@ ynh_add_fpm_config () { fi fi - if [ $use_template -eq 1 ] - then + if [ $use_template -eq 1 ]; then # Usage 1, use the template in conf/php-fpm.conf local phpfpm_path="$YNH_APP_BASEDIR/conf/php-fpm.conf" # Make sure now that the template indeed exists @@ -181,49 +173,45 @@ pm = __PHP_PM__ pm.max_children = __PHP_MAX_CHILDREN__ pm.max_requests = 500 request_terminate_timeout = 1d -" > $phpfpm_path +" >$phpfpm_path - if [ "$php_pm" = "dynamic" ] - then + if [ "$php_pm" = "dynamic" ]; then echo " pm.start_servers = __PHP_START_SERVERS__ pm.min_spare_servers = __PHP_MIN_SPARE_SERVERS__ pm.max_spare_servers = __PHP_MAX_SPARE_SERVERS__ -" >> $phpfpm_path +" >>$phpfpm_path - elif [ "$php_pm" = "ondemand" ] - then + elif [ "$php_pm" = "ondemand" ]; then echo " pm.process_idle_timeout = 10s -" >> $phpfpm_path +" >>$phpfpm_path fi # Concatene the extra config. if [ -e $YNH_APP_BASEDIR/conf/extra_php-fpm.conf ]; then - cat $YNH_APP_BASEDIR/conf/extra_php-fpm.conf >> "$phpfpm_path" + 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 "$YNH_APP_BASEDIR/conf/php-fpm.ini" ] - then + 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="$YNH_APP_BASEDIR/conf/php-fpm.ini" --destination="$fpm_config_dir/conf.d/20-$app.ini" fi - if [ $dedicated_service -eq 1 ] - then + if [ $dedicated_service -eq 1 ]; then # Create a dedicated php-fpm.conf for the service local globalphpconf=$fpm_config_dir/php-fpm-$app.conf -echo "[global] + echo "[global] pid = /run/php/php__PHPVERSION__-fpm-__APP__.pid error_log = /var/log/php/fpm-php.__APP__.log syslog.ident = php-fpm-__APP__ include = __FINALPHPCONF__ -" > $YNH_APP_BASEDIR/conf/php-fpm-$app.conf +" >$YNH_APP_BASEDIR/conf/php-fpm-$app.conf ynh_add_config --template="$YNH_APP_BASEDIR/conf/php-fpm-$app.conf" --destination="$globalphpconf" @@ -240,7 +228,7 @@ ExecReload=/bin/kill -USR2 \$MAINPID [Install] WantedBy=multi-user.target -" > $YNH_APP_BASEDIR/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 @@ -252,8 +240,7 @@ WantedBy=multi-user.target ynh_systemd_action --service_name=$fpm_service --action=restart else # Validate that the new php conf doesn't break php-fpm entirely - if ! php-fpm${phpversion} --test 2>/dev/null - then + if ! php-fpm${phpversion} --test 2>/dev/null; then php-fpm${phpversion} --test || true ynh_secure_remove --file="$finalphpconf" ynh_die --message="The new configuration broke php-fpm?" @@ -267,7 +254,7 @@ WantedBy=multi-user.target # usage: ynh_remove_fpm_config # # Requires YunoHost version 2.7.2 or higher. -ynh_remove_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) local dedicated_service=$(ynh_app_setting_get --app=$app --key=fpm_dedicated_service) @@ -279,20 +266,17 @@ ynh_remove_fpm_config () { phpversion="${phpversion:-$YNH_DEFAULT_PHP_VERSION}" # Assume default PHP files if not set - if [ -z "$fpm_config_dir" ] - then + 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" - if [ -e $fpm_config_dir/conf.d/20-$app.ini ] - then + 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 + 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 @@ -304,8 +288,7 @@ ynh_remove_fpm_config () { fi # If the PHP version used is not the default version for YunoHost - if [ "$phpversion" != "$YNH_DEFAULT_PHP_VERSION" ] - then + if [ "$phpversion" != "$YNH_DEFAULT_PHP_VERSION" ]; then # Remove this specific version of PHP ynh_remove_php fi @@ -320,10 +303,10 @@ ynh_remove_fpm_config () { # | arg: -p, --package= - Additionnal PHP packages to install # # Requires YunoHost version 3.8.1 or higher. -ynh_install_php () { +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 -A args_array=([v]=phpversion= [p]=package=) local phpversion local package # Manage arguments with getopts @@ -333,8 +316,7 @@ ynh_install_php () { # Store phpversion into the config of this app ynh_app_setting_set $app phpversion $phpversion - if [ "$phpversion" == "$YNH_DEFAULT_PHP_VERSION" ] - then + if [ "$phpversion" == "$YNH_DEFAULT_PHP_VERSION" ]; then ynh_die --message="Do not use ynh_install_php to install php$YNH_DEFAULT_PHP_VERSION" fi @@ -342,8 +324,7 @@ ynh_install_php () { 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 + 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 @@ -370,14 +351,12 @@ ynh_install_php () { # usage: ynh_install_php # # Requires YunoHost version 3.8.1 or higher. -ynh_remove_php () { +ynh_remove_php() { # Get the version of PHP used by this app local phpversion=$(ynh_app_setting_get --app=$app --key=phpversion) - if [ "$phpversion" == "$YNH_DEFAULT_PHP_VERSION" ] || [ -z "$phpversion" ] - then - if [ "$phpversion" == "$YNH_DEFAULT_PHP_VERSION" ] - then + 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 @@ -390,8 +369,7 @@ ynh_remove_php () { 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 + 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 @@ -421,10 +399,10 @@ ynh_remove_php () { # high - High usage, frequently visited website. # # | arg: -p, --print - Print the result (intended for debug purpose only when packaging the app) -ynh_get_scalable_phpfpm () { +ynh_get_scalable_phpfpm() { local legacy_args=ufp # Declare an array to define the options of this helper. - local -A args_array=( [u]=usage= [f]=footprint= [p]=print ) + local -A args_array=([u]=usage= [f]=footprint= [p]=print) local usage local footprint local print @@ -435,38 +413,30 @@ ynh_get_scalable_phpfpm () { usage=${usage,,} print=${print:-0} - if [ "$footprint" = "low" ] - then + if [ "$footprint" = "low" ]; then footprint=20 - elif [ "$footprint" = "medium" ] - then + elif [ "$footprint" = "medium" ]; then footprint=35 - elif [ "$footprint" = "high" ] - then + elif [ "$footprint" = "high" ]; then footprint=50 fi # Define the factor to determine min_spare_servers # to avoid having too few children ready to start for heavy apps - if [ $footprint -le 20 ] - then + if [ $footprint -le 20 ]; then min_spare_servers_factor=8 - elif [ $footprint -le 35 ] - then + 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 + if [ "$usage" = "low" ]; then php_pm=ondemand - elif [ "$usage" = "medium" ] - then + elif [ "$usage" = "medium" ]; then php_pm=dynamic - elif [ "$usage" = "high" ] - then + elif [ "$usage" = "high" ]; then php_pm=static else ynh_die --message="Does not recognize '$usage' as an usage value." @@ -477,8 +447,7 @@ ynh_get_scalable_phpfpm () { at_least_one() { # Do not allow value below 1 - if [ $1 -le 0 ] - then + if [ $1 -le 0 ]; then echo 1 else echo $1 @@ -488,20 +457,18 @@ ynh_get_scalable_phpfpm () { # 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 )) + 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 )) + if [ "$php_pm" = "static" ]; then + php_max_children=$(($php_max_children / 2)) fi 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) - local max_proc=$(( $core_number * 4 )) - if [ $php_max_children -gt $max_proc ] - then + local max_proc=$(($core_number * 4)) + if [ $php_max_children -gt $max_proc ]; then php_max_children=$max_proc fi @@ -511,16 +478,15 @@ ynh_get_scalable_phpfpm () { php_max_children=$php_forced_max_children fi - if [ "$php_pm" = "dynamic" ] - then + 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 / $min_spare_servers_factor )) + 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 )) + php_max_spare_servers=$(($php_max_children / 2)) 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=$(($php_min_spare_servers + ($php_max_spare_servers - $php_min_spare_servers) / 2)) php_start_servers=$(at_least_one $php_start_servers) else php_min_spare_servers=0 @@ -528,27 +494,22 @@ ynh_get_scalable_phpfpm () { php_start_servers=0 fi - if [ $print -eq 1 ] - then + 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 ))" + 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 ))" + 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 + 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" @@ -569,10 +530,10 @@ YNH_COMPOSER_VERSION=${YNH_COMPOSER_VERSION:-$YNH_DEFAULT_COMPOSER_VERSION} # | arg: -c, --commands - Commands to execute. # # Requires YunoHost version 4.2 or higher. -ynh_composer_exec () { +ynh_composer_exec() { # Declare an array to define the options of this helper. local legacy_args=vwc - declare -Ar args_array=( [v]=phpversion= [w]=workdir= [c]=commands= ) + declare -Ar args_array=([v]=phpversion= [w]=workdir= [c]=commands=) local phpversion local workdir local commands @@ -595,10 +556,10 @@ ynh_composer_exec () { # | arg: -c, --composerversion - Composer version to install # # Requires YunoHost version 4.2 or higher. -ynh_install_composer () { +ynh_install_composer() { # Declare an array to define the options of this helper. local legacy_args=vwac - declare -Ar args_array=( [v]=phpversion= [w]=workdir= [a]=install_args= [c]=composerversion=) + declare -Ar args_array=([v]=phpversion= [w]=workdir= [a]=install_args= [c]=composerversion=) local phpversion local workdir local install_args @@ -612,7 +573,7 @@ ynh_install_composer () { curl -sS https://getcomposer.org/installer \ | COMPOSER_HOME="$workdir/.composer" \ - php${phpversion} -- --quiet --install-dir="$workdir" --version=$composerversion \ + php${phpversion} -- --quiet --install-dir="$workdir" --version=$composerversion \ || ynh_die --message="Unable to install Composer." # install dependencies diff --git a/data/helpers.d/postgresql b/data/helpers.d/postgresql index 12738a922..992474dd5 100644 --- a/data/helpers.d/postgresql +++ b/data/helpers.d/postgresql @@ -46,8 +46,7 @@ ynh_psql_execute_as_root() { ynh_handle_getopts_args "$@" database="${database:-}" - if [ -n "$database" ] - then + if [ -n "$database" ]; then database="--database=$database" fi @@ -72,8 +71,7 @@ ynh_psql_execute_file_as_root() { ynh_handle_getopts_args "$@" database="${database:-}" - if [ -n "$database" ] - then + if [ -n "$database" ]; then database="--database=$database" fi @@ -175,8 +173,7 @@ ynh_psql_user_exists() { # 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 + 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 @@ -198,8 +195,7 @@ ynh_psql_database_exists() { # 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 + 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 @@ -269,16 +265,14 @@ ynh_psql_remove_db() { # 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 + 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 + 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" @@ -310,8 +304,7 @@ ynh_psql_test_if_first_run() { # If this is the very first time, we define the root password # and configure a few things - if [ ! -f "$PSQL_ROOT_PWD_FILE" ] - then + if [ ! -f "$PSQL_ROOT_PWD_FILE" ]; then local pg_hba=/etc/postgresql/$PSQL_VERSION/main/pg_hba.conf local psql_root_password="$(ynh_string_random)" diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 66bce9717..cd231c6ba 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 - local -A args_array=( [a]=app= [k]=key= ) + local -A args_array=([a]=app= [k]=key=) local app local key # Manage arguments with getopts @@ -34,7 +34,7 @@ ynh_app_setting_get() { ynh_app_setting_set() { # Declare an array to define the options of this helper. local legacy_args=akv - local -A args_array=( [a]=app= [k]=key= [v]=value= ) + local -A args_array=([a]=app= [k]=key= [v]=value=) local app local key local value @@ -58,7 +58,7 @@ ynh_app_setting_set() { ynh_app_setting_delete() { # Declare an array to define the options of this helper. local legacy_args=ak - local -A args_array=( [a]=app= [k]=key= ) + local -A args_array=([a]=app= [k]=key=) local app local key # Manage arguments with getopts @@ -76,8 +76,7 @@ ynh_app_setting_delete() { # # [internal] # -ynh_app_setting() -{ +ynh_app_setting() { set +o xtrace # set +x ACTION="$1" APP="$2" KEY="$3" VALUE="${4:-}" python3 - < /dev/null \ + dd if=/dev/urandom bs=1 count=1000 2>/dev/null \ | tr --complement --delete 'A-Za-z0-9' \ | sed --quiet 's/\(.\{'"$length"'\}\).*/\1/p' } @@ -34,10 +34,10 @@ ynh_string_random() { # sub-expressions can be used (see sed manual page for more information) # # Requires YunoHost version 2.6.4 or higher. -ynh_replace_string () { +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 -A args_array=([m]=match_string= [r]=replace_string= [f]=target_file=) local match_string local replace_string local target_file @@ -65,10 +65,10 @@ ynh_replace_string () { # characters, you can't use some regular expressions and sub-expressions. # # Requires YunoHost version 2.7.7 or higher. -ynh_replace_special_string () { +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 -A args_array=([m]=match_string= [r]=replace_string= [f]=target_file=) local match_string local replace_string local target_file @@ -97,10 +97,10 @@ ynh_replace_special_string () { # Underscorify the string (replace - and . by _) # # Requires YunoHost version 2.2.4 or higher. -ynh_sanitize_dbid () { +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 -A args_array=([n]=db_name=) local db_name # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -127,20 +127,20 @@ ynh_sanitize_dbid () { # | arg: -p, --path_url= - URL path to normalize before using it # # Requires YunoHost version 2.6.4 or higher. -ynh_normalize_url_path () { +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 -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 + 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 + 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 d0f88b5f7..71b605181 100644 --- a/data/helpers.d/systemd +++ b/data/helpers.d/systemd @@ -12,10 +12,10 @@ # format and how placeholders are replaced with actual variables. # # Requires YunoHost version 4.1.0 or higher. -ynh_add_systemd_config () { +ynh_add_systemd_config() { # Declare an array to define the options of this helper. local legacy_args=stv - local -A args_array=( [s]=service= [t]=template= [v]=others_var=) + local -A args_array=([s]=service= [t]=template= [v]=others_var=) local service local template local others_var @@ -39,18 +39,17 @@ ynh_add_systemd_config () { # | arg: -s, --service= - Service name (optionnal, $app by default) # # Requires YunoHost version 2.7.2 or higher. -ynh_remove_systemd_config () { +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 -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 + if [ -e "$finalsystemdconf" ]; then ynh_systemd_action --service_name=$service --action=stop systemctl disable $service --quiet ynh_secure_remove --file="$finalsystemdconf" @@ -72,7 +71,7 @@ ynh_remove_systemd_config () { ynh_systemd_action() { # Declare an array to define the options of this helper. local legacy_args=nalpte - local -A 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 @@ -89,25 +88,22 @@ ynh_systemd_action() { timeout=${timeout:-300} # Manage case of service already stopped - if [ "$action" == "stop" ] && ! systemctl is-active --quiet $service_name - then + if [ "$action" == "stop" ] && ! systemctl is-active --quiet $service_name; then return 0 fi # Start to read the log - if [[ -n "$line_match" ]] - then + if [[ -n "$line_match" ]]; 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" & + journalctl --unit=$service_name --follow --since=-0 --quiet >"$templog" & # Get the PID of the journalctl command local pid_tail=$! else # Read the specified log file - tail --follow=name --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 @@ -119,13 +115,11 @@ ynh_systemd_action() { fi # If the service fails to perform the action - if ! systemctl $action $service_name - then + if ! systemctl $action $service_name; then # Show syslog for this service ynh_exec_err journalctl --quiet --no-hostname --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 + if [ -e "$log_path" ]; then ynh_exec_err tail --lines=$length "$log_path" fi ynh_clean_check_starting @@ -133,15 +127,12 @@ ynh_systemd_action() { fi # Start the timeout and try to find line_match - if [[ -n "${line_match:-}" ]] - then + if [[ -n "${line_match:-}" ]]; then set +x local i=0 - for i in $(seq 1 $timeout) - do + 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 --extended-regexp --quiet "$line_match" "$templog" - then + if grep --extended-regexp --quiet "$line_match" "$templog"; then ynh_print_info --message="The service $service_name has correctly executed the action ${action}." break fi @@ -154,13 +145,11 @@ ynh_systemd_action() { if [ $i -ge 3 ]; then echo "" >&2 fi - if [ $i -eq $timeout ] - then + if [ $i -eq $timeout ]; 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:" ynh_exec_warn journalctl --quiet --no-hostname --no-pager --lines=$length --unit=$service_name - if [ -e "$log_path" ] - then + if [ -e "$log_path" ]; then ynh_print_warn --message="\-\-\-" ynh_exec_warn tail --lines=$length "$log_path" fi @@ -174,14 +163,12 @@ ynh_systemd_action() { # [internal] # # Requires YunoHost version 3.5.0 or higher. -ynh_clean_check_starting () { - if [ -n "${pid_tail:-}" ] - then +ynh_clean_check_starting() { + if [ -n "${pid_tail:-}" ]; then # Stop the execution of tail. kill -SIGTERM $pid_tail 2>&1 fi - if [ -n "${templog:-}" ] - then + if [ -n "${templog:-}" ]; then ynh_secure_remove --file="$templog" 2>&1 fi } diff --git a/data/helpers.d/user b/data/helpers.d/user index d5ede9f73..aecbd740e 100644 --- a/data/helpers.d/user +++ b/data/helpers.d/user @@ -12,7 +12,7 @@ ynh_user_exists() { # Declare an array to define the options of this helper. local legacy_args=u - local -A args_array=( [u]=username= ) + local -A args_array=([u]=username=) local username # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -33,7 +33,7 @@ ynh_user_exists() { ynh_user_get_info() { # Declare an array to define the options of this helper. local legacy_args=uk - local -A args_array=( [u]=username= [k]=key= ) + local -A args_array=([u]=username= [k]=key=) local username local key # Manage arguments with getopts @@ -64,7 +64,7 @@ ynh_user_list() { ynh_system_user_exists() { # Declare an array to define the options of this helper. local legacy_args=u - local -A args_array=( [u]=username= ) + local -A args_array=([u]=username=) local username # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -82,7 +82,7 @@ ynh_system_user_exists() { ynh_system_group_exists() { # Declare an array to define the options of this helper. local legacy_args=g - local -A args_array=( [g]=group= ) + local -A args_array=([g]=group=) local group # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -108,10 +108,10 @@ ynh_system_group_exists() { # ``` # # Requires YunoHost version 2.6.4 or higher. -ynh_system_user_create () { +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 [g]=groups= ) + local -A args_array=([u]=username= [h]=home_dir= [s]=use_shell [g]=groups=) local username local home_dir local use_shell @@ -123,17 +123,15 @@ ynh_system_user_create () { 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 - if [ -n "$home_dir" ] - then # If a home dir is mentioned + if ! ynh_system_user_exists "$username"; then # Check if the user exists on the system + # If the user doesn't exist + if [ -n "$home_dir" ]; then # If a home dir is mentioned local user_home_dir="--home-dir $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 + 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 @@ -141,8 +139,7 @@ ynh_system_user_create () { fi local group - for group in $groups - do + for group in $groups; do usermod -a -G "$group" "$username" done } @@ -153,25 +150,23 @@ ynh_system_user_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 () { +ynh_system_user_delete() { # Declare an array to define the options of this helper. local legacy_args=u - local -A args_array=( [u]=username= ) + local -A args_array=([u]=username=) local username # Manage arguments with getopts ynh_handle_getopts_args "$@" # Check if the user exists on the system - if ynh_system_user_exists "$username" - then + if ynh_system_user_exists "$username"; then 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 + if ynh_system_group_exists "$username"; then delgroup $username fi } diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 061ff324d..eb823b5f0 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -19,25 +19,25 @@ YNH_APP_BASEDIR=${YNH_APP_BASEDIR:-$(realpath ..)} # 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 () { +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 + exit 0 # Exit without error if the script ended correctly fi - trap '' EXIT # Ignore new exit signals + trap '' EXIT # Ignore new exit signals # Do not exit anymore if a command fail or if a variable is empty - set +o errexit # set +e - set +o nounset # set +u + 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 - 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. + 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 # Exit with error status @@ -55,10 +55,10 @@ ynh_exit_properly () { # and a call to `ynh_clean_setup` is triggered if it has been defined by your script. # # Requires YunoHost version 2.6.4 or higher. -ynh_abort_if_errors () { - set -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 +ynh_abort_if_errors() { + 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 } # Download, check integrity, uncompress and patch the source from app.src @@ -99,10 +99,10 @@ ynh_abort_if_errors () { # - 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 () { +ynh_setup_source() { # Declare an array to define the options of this helper. local legacy_args=dsk - local -A args_array=( [d]=dest_dir= [s]=source_id= [k]=keep= ) + local -A args_array=([d]=dest_dir= [s]=source_id= [k]=keep=) local dest_dir local source_id local keep @@ -133,15 +133,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 + if test -e "$local_src"; then cp $local_src $src_filename else [ -n "$src_url" ] || ynh_die "Couldn't parse SOURCE_URL from $src_file_path ?" @@ -162,15 +160,12 @@ ynh_setup_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 + 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 + 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 @@ -180,20 +175,16 @@ ynh_setup_source () { # Extract source into the app dir mkdir --parents "$dest_dir" - if [ -n "${final_path:-}" ] && [ "$dest_dir" == "$final_path" ] - then + if [ -n "${final_path:-}" ] && [ "$dest_dir" == "$final_path" ]; then _ynh_apply_default_permissions $dest_dir fi - if ! "$src_extract" - then + if ! "$src_extract"; then mv $src_filename $dest_dir - elif [ "$src_format" = "zip" ] - then + elif [ "$src_format" = "zip" ]; 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 --directory) unzip -quo $src_filename -d "$tmp_dir" cp --archive $tmp_dir/*/. "$dest_dir" @@ -204,18 +195,15 @@ ynh_setup_source () { ynh_secure_remove --file="$src_filename" else local strip="" - if [ "$src_in_subdir" != "false" ] - then - if [ "$src_in_subdir" == "true" ] - then + if [ "$src_in_subdir" != "false" ]; then + if [ "$src_in_subdir" == "true" ]; then local sub_dirs=1 else local sub_dirs="$src_in_subdir" fi strip="--strip-components $sub_dirs" fi - if [[ "$src_format" =~ ^tar.gz|tar.bz2|tar.xz$ ]] - then + if [[ "$src_format" =~ ^tar.gz|tar.bz2|tar.xz$ ]]; then tar --extract --file=$src_filename --directory="$dest_dir" $strip else ynh_die --message="Archive format unrecognized." @@ -224,17 +212,16 @@ ynh_setup_source () { fi # Apply patches - if [ -d "$YNH_APP_BASEDIR/sources/patches/" ] - then + if [ -d "$YNH_APP_BASEDIR/sources/patches/" ]; then 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" + 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 @@ -245,14 +232,11 @@ ynh_setup_source () { # Keep files to be backup/restored at the end of the helper # Assuming $dest_dir already exists - if [ -n "$keep" ] - then + 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 + 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 @@ -276,7 +260,7 @@ ynh_setup_source () { # `$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 () { +ynh_local_curl() { # Define url of page to curl local local_page=$(ynh_normalize_url_path $1) local full_path=$path_url$local_page @@ -290,12 +274,10 @@ ynh_local_curl () { # Concatenate all other arguments with '&' to prepare POST data local POST_data="" local arg="" - for arg in "${@:2}" - do + for arg in "${@:2}"; do POST_data="${POST_data}${arg}&" done - if [ -n "$POST_data" ] - then + if [ -n "$POST_data" ]; then # Add --data arg and remove the last character, which is an unecessary '&' POST_data="--data ${POST_data::-1}" fi @@ -353,10 +335,10 @@ ynh_local_curl () { # into the app settings when configuration is done. # # Requires YunoHost version 4.1.0 or higher. -ynh_add_config () { +ynh_add_config() { # Declare an array to define the options of this helper. local legacy_args=tdv - local -A args_array=( [t]=template= [d]=destination= ) + local -A args_array=([t]=template= [d]=destination=) local template local destination # Manage arguments with getopts @@ -414,17 +396,16 @@ ynh_add_config () { # __VAR_2__ by $var_2 # # Requires YunoHost version 4.1.0 or higher. -ynh_replace_vars () { +ynh_replace_vars() { # 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=) local file # Manage arguments with getopts ynh_handle_getopts_args "$@" # Replace specific YunoHost variables - if test -n "${path_url:-}" - then + 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="$file" @@ -448,12 +429,11 @@ ynh_replace_vars () { # Replace others variables # List other unique (__ __) variables in $file - local uniques_vars=( $(grep -oP '__[A-Z0-9]+?[A-Z0-9_]*?[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=@ - for one_var in "${uniques_vars[@]}" - do + for one_var in "${uniques_vars[@]}"; do # Validate that one_var is indeed defined # -v checks if the variable is defined, for example: # -v FOO tests if $FOO is defined @@ -509,7 +489,7 @@ ynh_replace_vars () { ynh_read_var_in_file() { # Declare an array to define the options of this helper. local legacy_args=fka - local -A args_array=( [f]=file= [k]=key= [a]=after=) + local -A args_array=([f]=file= [k]=key= [a]=after=) local file local key local after @@ -523,11 +503,9 @@ ynh_read_var_in_file() { # Get the line number after which we search for the variable local line_number=1 - if [[ -n "$after" ]]; - then + if [[ -n "$after" ]]; then line_number=$(grep -n $after $file | cut -d: -f1) - if [[ -z "$line_number" ]]; - then + if [[ -z "$line_number" ]]; then set -o xtrace # set -x return 1 fi @@ -545,7 +523,7 @@ ynh_read_var_in_file() { if [[ "$ext" =~ ^ini|env$ ]]; then comments="[;#]" fi - if [[ "php" == "$ext" ]] || [[ "$ext" == "js" ]]; then + if [[ "php" == "$ext" ]] || [[ "$ext" == "js" ]]; then comments="//" fi local list='\[\s*['$string']?\w+['$string']?\]' @@ -567,10 +545,10 @@ ynh_read_var_in_file() { 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 - echo "$expression" | grep -m1 -o -P '"\K([^"](\\")?)*[^\\](?=")' | head -n1 | sed 's/\\"/"/g' - elif [[ "$first_char" == "'" ]] ; then - echo "$expression" | grep -m1 -o -P "'\K([^'](\\\\')?)*[^\\\\](?=')" | head -n1 | sed "s/\\\\'/'/g" + if [[ "$first_char" == '"' ]]; then + echo "$expression" | grep -m1 -o -P '"\K([^"](\\")?)*[^\\](?=")' | head -n1 | sed 's/\\"/"/g' + elif [[ "$first_char" == "'" ]]; then + echo "$expression" | grep -m1 -o -P "'\K([^'](\\\\')?)*[^\\\\](?=')" | head -n1 | sed "s/\\\\'/'/g" else echo "$expression" fi @@ -588,7 +566,7 @@ ynh_read_var_in_file() { ynh_write_var_in_file() { # Declare an array to define the options of this helper. local legacy_args=fkva - local -A args_array=( [f]=file= [k]=key= [v]=value= [a]=after=) + local -A args_array=([f]=file= [k]=key= [v]=value= [a]=after=) local file local key local value @@ -603,11 +581,9 @@ ynh_write_var_in_file() { # Get the line number after which we search for the variable local line_number=1 - if [[ -n "$after" ]]; - then + if [[ -n "$after" ]]; then line_number=$(grep -n $after $file | cut -d: -f1) - if [[ -z "$line_number" ]]; - then + if [[ -z "$line_number" ]]; then set -o xtrace # set -x return 1 fi @@ -626,7 +602,7 @@ ynh_write_var_in_file() { if [[ "$ext" =~ ^ini|env$ ]]; then comments="[;#]" fi - if [[ "php" == "$ext" ]] || [[ "$ext" == "js" ]]; then + if [[ "php" == "$ext" ]] || [[ "$ext" == "js" ]]; then comments="//" fi local list='\[\s*['$string']?\w+['$string']?\]' @@ -650,22 +626,22 @@ ynh_write_var_in_file() { value="$(echo "$value" | sed 's/\\/\\\\/g')" local first_char="${expression:0:1}" delimiter=$'\001' - if [[ "$first_char" == '"' ]] ; then + 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 "${range}s$delimiter"'(^'"${var_part}"'")([^"]|\\")*("[\s;,]*)(\s*'$comments'.*)?$'$delimiter'\1'"${value}"'"'"${endline}${delimiter}i" ${file} - elif [[ "$first_char" == "'" ]] ; then + 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 "${range}s$delimiter(^${var_part}')([^']|\\')*('"'[\s,;]*)(\s*'$comments'.*)?$'$delimiter'\1'"${value}'${endline}${delimiter}i" ${file} else - if [[ "$value" == *"'"* ]] || [[ "$value" == *'"'* ]] || [[ "$ext" =~ ^php|py|json|js$ ]] ; then + if [[ "$value" == *"'"* ]] || [[ "$value" == *'"'* ]] || [[ "$ext" =~ ^php|py|json|js$ ]]; then value='\"'"$(echo "$value" | sed 's/"/\\\\"/g')"'\"' fi - if [[ "$ext" =~ ^yaml|yml$ ]] ; then + if [[ "$ext" =~ ^yaml|yml$ ]]; then value=" $value" fi sed -ri "${range}s$delimiter(^${var_part}).*\$$delimiter\1${value}${endline}${delimiter}i" ${file} @@ -673,7 +649,6 @@ ynh_write_var_in_file() { set -o xtrace # set -x } - # Render templates with Jinja2 # # [internal] @@ -691,7 +666,7 @@ ynh_render_template() { # Taken from https://stackoverflow.com/a/35009576 python3 -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 @@ -700,7 +675,7 @@ ynh_render_template() { # | ret: The Debian release codename (i.e. jessie, stretch, ...) # # Requires YunoHost version 2.7.12 or higher. -ynh_get_debian_release () { +ynh_get_debian_release() { echo $(lsb_release --codename --short) } @@ -730,10 +705,10 @@ properly with chmod/chown." # | arg: -f, --file= - File or directory to remove # # Requires YunoHost version 2.6.4 or higher. -ynh_secure_remove () { +ynh_secure_remove() { # 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=) local file # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -743,24 +718,21 @@ ynh_secure_remove () { /var/www \ /home/yunohost.app" - if [ $# -ge 2 ] - then + if [ $# -ge 2 ]; then 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 [[ -z "$file" ]] - then + if [[ -z "$file" ]]; then ynh_print_warn --message="ynh_secure_remove called with empty argument, ignoring." - elif [[ "$forbidden_path" =~ "$file" \ - # Match all paths or subpaths in $forbidden_path - || "$file" =~ ^/[[:alnum:]]+$ \ + elif [[ "$forbidden_path" =~ "$file" || + + "$file" =~ ^/[[:alnum:]]+$ || + + "${file:${#file}-1}" = "/" ]]; then # Match all paths or subpaths in $forbidden_path # 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="Not deleting '$file' because it is not an acceptable path to delete." - elif [ -e "$file" ] - then + elif [ -e "$file" ]; then rm --recursive "$file" else ynh_print_info --message="'$file' wasn't deleted because it doesn't exist." @@ -781,16 +753,12 @@ 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 @@ -809,10 +777,10 @@ ynh_get_plain_key() { # | ret: the value associate to that key # # Requires YunoHost version 3.5.0 or higher. -ynh_read_manifest () { +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 -A args_array=([m]=manifest= [k]=manifest_key=) local manifest local manifest_key # Manage arguments with getopts @@ -839,20 +807,19 @@ ynh_read_manifest () { # 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 () { +ynh_app_upstream_version() { # Declare an array to define the options of this helper. local legacy_args=m - local -A args_array=( [m]=manifest= ) + local -A args_array=([m]=manifest=) local manifest # Manage arguments with getopts ynh_handle_getopts_args "$@" manifest="${manifest:-}" - if [[ "$manifest" != "" ]] && [[ -e "$manifest" ]]; - then - version_key_=$(ynh_read_manifest --manifest="$manifest" --manifest_key="version") + if [[ "$manifest" != "" ]] && [[ -e "$manifest" ]]; then + version_key_=$(ynh_read_manifest --manifest="$manifest" --manifest_key="version") else - version_key_=$YNH_APP_MANIFEST_VERSION + version_key_=$YNH_APP_MANIFEST_VERSION fi echo "${version_key_/~ynh*/}" @@ -869,10 +836,10 @@ ynh_app_upstream_version () { # 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 () { +ynh_app_package_version() { # Declare an array to define the options of this helper. local legacy_args=m - local -A args_array=( [m]=manifest= ) + local -A args_array=([m]=manifest=) local manifest # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -894,11 +861,10 @@ ynh_app_package_version () { # sudo yunohost app upgrade --force # ``` # Requires YunoHost version 3.5.0 or higher. -ynh_check_app_version_changed () { +ynh_check_app_version_changed() { local return_value=${YNH_APP_UPGRADE_TYPE} - if [ "$return_value" == "UPGRADE_FULL" ] || [ "$return_value" == "UPGRADE_FORCED" ] || [ "$return_value" == "DOWNGRADE_FORCED" ] - then + if [ "$return_value" == "UPGRADE_FULL" ] || [ "$return_value" == "UPGRADE_FORCED" ] || [ "$return_value" == "DOWNGRADE_FORCED" ]; then return_value="UPGRADE_APP" fi @@ -927,7 +893,7 @@ ynh_check_app_version_changed () { # Requires YunoHost version 3.8.0 or higher. ynh_compare_current_package_version() { local legacy_args=cv - declare -Ar args_array=( [c]=comparison= [v]=version= ) + declare -Ar args_array=([c]=comparison= [v]=version=) local version local comparison # Manage arguments with getopts @@ -936,8 +902,7 @@ ynh_compare_current_package_version() { local current_version=$YNH_APP_CURRENT_VERSION # Check the syntax of the versions - if [[ ! $version =~ '~ynh' ]] || [[ ! $current_version =~ '~ynh' ]] - then + if [[ ! $version =~ '~ynh' ]] || [[ ! $current_version =~ '~ynh' ]]; then ynh_die --message="Invalid argument for version." fi @@ -972,13 +937,11 @@ _ynh_apply_default_permissions() { local ynh_requirement=$(jq -r '.requirements.yunohost' $YNH_APP_BASEDIR/manifest.json | tr -d '>= ') - if [ -z "$ynh_requirement" ] || [ "$ynh_requirement" == "null" ] || dpkg --compare-versions $ynh_requirement ge 4.2 - then + if [ -z "$ynh_requirement" ] || [ "$ynh_requirement" == "null" ] || dpkg --compare-versions $ynh_requirement ge 4.2; then chmod o-rwx $target chmod g-w $target chown -R root:root $target - if ynh_system_user_exists $app - then + if ynh_system_user_exists $app; then chown $app:$app $target fi fi From 6048d1b0b35c7f1ad3618c5e4afbe5bf1ae4ff49 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 Oct 2021 16:24:33 +0200 Subject: [PATCH 0795/1155] Typo ogod --- 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 eb823b5f0..0e38423f2 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -748,13 +748,13 @@ ynh_secure_remove() { # (Deprecated, use --output-as json and jq instead) ynh_get_plain_key() { local prefix="#" - local founded=0 + local found=0 # We call this key_ so that it's not caught as # an info to be redacted by the core local key_=$1 shift while read line; do - if [[ "$founded" == "1" ]]; then + if [[ "$found" == "1" ]]; then [[ "$line" =~ ^${prefix}[^#] ]] && return echo $line elif [[ "$line" =~ ^${prefix}${key_}$ ]]; then @@ -763,7 +763,7 @@ ynh_get_plain_key() { key_=$1 shift else - founded=1 + found=1 fi fi done From 5a7a719661424fe7d283d253438f0ab4bd988c65 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 Oct 2021 16:29:14 +0200 Subject: [PATCH 0796/1155] Also lint/reformat core bash hooks --- .../backup/50-conf_manually_modified_files | 7 +- data/hooks/conf_regen/01-yunohost | 314 +++++++++--------- data/hooks/conf_regen/02-ssl | 97 +++--- data/hooks/conf_regen/06-slapd | 260 +++++++-------- data/hooks/conf_regen/09-nslcd | 16 +- data/hooks/conf_regen/10-apt | 15 +- data/hooks/conf_regen/12-metronome | 102 +++--- data/hooks/conf_regen/15-nginx | 226 +++++++------ data/hooks/conf_regen/19-postfix | 114 ++++--- data/hooks/conf_regen/25-dovecot | 80 ++--- data/hooks/conf_regen/31-rspamd | 88 ++--- data/hooks/conf_regen/34-mysql | 94 +++--- data/hooks/conf_regen/35-redis | 8 +- data/hooks/conf_regen/37-mdns | 63 ++-- data/hooks/conf_regen/43-dnsmasq | 111 +++---- data/hooks/conf_regen/46-nsswitch | 16 +- data/hooks/conf_regen/52-fail2ban | 24 +- data/hooks/post_user_create/ynh_multimedia | 2 +- data/hooks/restore/05-conf_ldap | 12 +- .../restore/50-conf_manually_modified_files | 3 +- 20 files changed, 819 insertions(+), 833 deletions(-) diff --git a/data/hooks/backup/50-conf_manually_modified_files b/data/hooks/backup/50-conf_manually_modified_files index 2cca11afb..bdea14113 100644 --- a/data/hooks/backup/50-conf_manually_modified_files +++ b/data/hooks/backup/50-conf_manually_modified_files @@ -6,13 +6,12 @@ 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 +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 +for file in $(cat ./manually_modified_files_list); do [[ -e $file ]] && ynh_backup --src_path="$file" done - + ynh_backup --src_path="/etc/ssowat/conf.json.persistent" diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index 14af66933..341efce9e 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -3,129 +3,128 @@ set -e do_init_regen() { - if [[ $EUID -ne 0 ]]; then - echo "You must be root to run this script" 1>&2 - exit 1 - fi + if [[ $EUID -ne 0 ]]; then + echo "You must be root to run this script" 1>&2 + exit 1 + fi - cd /usr/share/yunohost/templates/yunohost + cd /usr/share/yunohost/templates/yunohost - [[ -d /etc/yunohost ]] || mkdir -p /etc/yunohost + [[ -d /etc/yunohost ]] || mkdir -p /etc/yunohost - # set default current_host - [[ -f /etc/yunohost/current_host ]] \ - || echo "yunohost.org" > /etc/yunohost/current_host + # set default current_host + [[ -f /etc/yunohost/current_host ]] \ + || echo "yunohost.org" >/etc/yunohost/current_host - # copy default services and firewall - [[ -f /etc/yunohost/firewall.yml ]] \ - || cp firewall.yml /etc/yunohost/firewall.yml + # copy default services and firewall + [[ -f /etc/yunohost/firewall.yml ]] \ + || cp firewall.yml /etc/yunohost/firewall.yml - # allow users to access /media directory - [[ -d /etc/skel/media ]] \ - || (mkdir -p /media && ln -s /media /etc/skel/media) + # allow users to access /media directory + [[ -d /etc/skel/media ]] \ + || (mkdir -p /media && ln -s /media /etc/skel/media) - # Cert folders - mkdir -p /etc/yunohost/certs - chown -R root:ssl-cert /etc/yunohost/certs - chmod 750 /etc/yunohost/certs + # Cert folders + mkdir -p /etc/yunohost/certs + chown -R root:ssl-cert /etc/yunohost/certs + chmod 750 /etc/yunohost/certs - # App folders - mkdir -p /etc/yunohost/apps - chmod 700 /etc/yunohost/apps - mkdir -p /home/yunohost.app - chmod 755 /home/yunohost.app + # App folders + mkdir -p /etc/yunohost/apps + chmod 700 /etc/yunohost/apps + mkdir -p /home/yunohost.app + chmod 755 /home/yunohost.app - # Domain settings - mkdir -p /etc/yunohost/domains - chmod 700 /etc/yunohost/domains + # 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 - chown root:root /home/yunohost.backup/archives # This is later changed to admin:root once admin user exists + # Backup folders + mkdir -p /home/yunohost.backup/archives + chmod 750 /home/yunohost.backup/archives + chown root:root /home/yunohost.backup/archives # This is later changed to admin:root once admin user exists - # Empty ssowat json persistent conf - echo "{}" > '/etc/ssowat/conf.json.persistent' - chmod 644 /etc/ssowat/conf.json.persistent - chown root:root /etc/ssowat/conf.json.persistent + # Empty ssowat json persistent conf + echo "{}" >'/etc/ssowat/conf.json.persistent' + chmod 644 /etc/ssowat/conf.json.persistent + chown root:root /etc/ssowat/conf.json.persistent - # Empty service conf - touch /etc/yunohost/services.yml + # 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 + 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 + 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 + # 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() { - pending_dir=$1 + pending_dir=$1 - cd /usr/share/yunohost/templates/yunohost + cd /usr/share/yunohost/templates/yunohost - # 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())" + # 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/systemd/system - mkdir -p $pending_dir/etc/cron.d/ - mkdir -p $pending_dir/etc/cron.daily/ + mkdir -p $pending_dir/etc/systemd/system + 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. - cat > $pending_dir/etc/cron.d/yunohost-diagnosis << EOF + # 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. + cat >$pending_dir/etc/cron.d/yunohost-diagnosis < /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 + # Cron job that upgrade the app list everyday + cat >$pending_dir/etc/cron.daily/yunohost-fetch-apps-catalog < /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 + # Cron job that renew lets encrypt certificates if there's any that needs renewal + cat >$pending_dir/etc/cron.daily/yunohost-certificate-renew </dev/null - then - cat > $pending_dir/etc/cron.d/yunohost-dyndns << 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 <> /dev/null EOF - fi + 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 - touch $pending_dir/etc/etckeeper/etckeeper.conf + # 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 + touch $pending_dir/etc/etckeeper/etckeeper.conf - # Skip ntp if inside a container (inspired from the conf of systemd-timesyncd) - mkdir -p ${pending_dir}/etc/systemd/system/ntp.service.d/ - echo " + # Skip ntp if inside a container (inspired from the conf of systemd-timesyncd) + mkdir -p ${pending_dir}/etc/systemd/system/ntp.service.d/ + echo " [Unit] ConditionCapability=CAP_SYS_TIME ConditionVirtualization=!container -" > ${pending_dir}/etc/systemd/system/ntp.service.d/ynh-override.conf +" >${pending_dir}/etc/systemd/system/ntp.service.d/ynh-override.conf - # Make nftable conflict with yunohost-firewall - mkdir -p ${pending_dir}/etc/systemd/system/nftables.service.d/ - cat > ${pending_dir}/etc/systemd/system/nftables.service.d/ynh-override.conf << EOF + # Make nftable conflict with yunohost-firewall + mkdir -p ${pending_dir}/etc/systemd/system/nftables.service.d/ + cat >${pending_dir}/etc/systemd/system/nftables.service.d/ynh-override.conf < ${pending_dir}/etc/systemd/logind.conf.d/ynh-override.conf << EOF + # Don't suspend computer on LidSwitch + mkdir -p ${pending_dir}/etc/systemd/logind.conf.d/ + cat >${pending_dir}/etc/systemd/logind.conf.d/ynh-override.conf </dev/null) - chmod 600 $(ls /etc/yunohost/{*.yml,*.yaml,*.json,mysql,psql} 2>/dev/null) + # 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) - [[ ! -e /etc/yunohost/domains ]] || (chown root /etc/yunohost/domains && chmod 700 /etc/yunohost/domains) + # 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 - grep -q '^sftp.app:' /etc/group || groupadd sftp.app + # 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 - [[ ! "$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 + # 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" =~ "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 + # 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_$1_regen ${@:2} diff --git a/data/hooks/conf_regen/02-ssl b/data/hooks/conf_regen/02-ssl index 2b40c77a2..03478552c 100755 --- a/data/hooks/conf_regen/02-ssl +++ b/data/hooks/conf_regen/02-ssl @@ -23,7 +23,7 @@ regen_local_ca() { # (Update the serial so that it's specific to this very instance) # N.B. : the weird RANDFILE thing comes from: # https://stackoverflow.com/questions/94445/using-openssl-what-does-unable-to-write-random-state-mean - RANDFILE=.rnd openssl rand -hex 19 > serial + RANDFILE=.rnd openssl rand -hex 19 >serial rm -f index.txt touch index.txt cp /usr/share/yunohost/templates/ssl/openssl.cnf openssl.ca.cnf @@ -50,73 +50,72 @@ regen_local_ca() { do_init_regen() { - LOGFILE=/tmp/yunohost-ssl-init - echo "" > $LOGFILE - chown root:root $LOGFILE - chmod 640 $LOGFILE + LOGFILE=/tmp/yunohost-ssl-init + echo "" >$LOGFILE + chown root:root $LOGFILE + chmod 640 $LOGFILE - # Make sure this conf exists - mkdir -p ${ssl_dir} - cp /usr/share/yunohost/templates/ssl/openssl.cnf ${ssl_dir}/openssl.ca.cnf + # Make sure this conf exists + mkdir -p ${ssl_dir} + cp /usr/share/yunohost/templates/ssl/openssl.cnf ${ssl_dir}/openssl.ca.cnf - # create default certificates - if [[ ! -f "$ynh_ca" ]]; then - regen_local_ca yunohost.org >>$LOGFILE - fi + # create default certificates + if [[ ! -f "$ynh_ca" ]]; then + regen_local_ca yunohost.org >>$LOGFILE + fi - if [[ ! -f "$ynh_crt" ]]; then - echo -e "\n# Creating initial key and certificate \n" >>$LOGFILE + if [[ ! -f "$ynh_crt" ]]; then + echo -e "\n# Creating initial key and certificate \n" >>$LOGFILE - openssl req -new \ - -config "$openssl_conf" \ - -days 730 \ - -out "${ssl_dir}/certs/yunohost_csr.pem" \ - -keyout "${ssl_dir}/certs/yunohost_key.pem" \ - -nodes -batch &>>$LOGFILE + openssl req -new \ + -config "$openssl_conf" \ + -days 730 \ + -out "${ssl_dir}/certs/yunohost_csr.pem" \ + -keyout "${ssl_dir}/certs/yunohost_key.pem" \ + -nodes -batch &>>$LOGFILE - openssl ca \ - -config "$openssl_conf" \ - -days 730 \ - -in "${ssl_dir}/certs/yunohost_csr.pem" \ - -out "${ssl_dir}/certs/yunohost_crt.pem" \ - -batch &>>$LOGFILE + openssl ca \ + -config "$openssl_conf" \ + -days 730 \ + -in "${ssl_dir}/certs/yunohost_csr.pem" \ + -out "${ssl_dir}/certs/yunohost_crt.pem" \ + -batch &>>$LOGFILE - chmod 640 "${ssl_dir}/certs/yunohost_key.pem" - chmod 640 "${ssl_dir}/certs/yunohost_crt.pem" + chmod 640 "${ssl_dir}/certs/yunohost_key.pem" + chmod 640 "${ssl_dir}/certs/yunohost_crt.pem" - cp "${ssl_dir}/certs/yunohost_key.pem" "$ynh_key" - cp "${ssl_dir}/certs/yunohost_crt.pem" "$ynh_crt" - ln -sf "$ynh_crt" /etc/ssl/certs/yunohost_crt.pem - ln -sf "$ynh_key" /etc/ssl/private/yunohost_key.pem - fi + cp "${ssl_dir}/certs/yunohost_key.pem" "$ynh_key" + cp "${ssl_dir}/certs/yunohost_crt.pem" "$ynh_crt" + ln -sf "$ynh_crt" /etc/ssl/certs/yunohost_crt.pem + ln -sf "$ynh_key" /etc/ssl/private/yunohost_key.pem + fi - chown -R root:ssl-cert /etc/yunohost/certs/yunohost.org/ - chmod o-rwx /etc/yunohost/certs/yunohost.org/ + chown -R root:ssl-cert /etc/yunohost/certs/yunohost.org/ + chmod o-rwx /etc/yunohost/certs/yunohost.org/ - install -D -m 644 $openssl_conf "${ssl_dir}/openssl.cnf" + install -D -m 644 $openssl_conf "${ssl_dir}/openssl.cnf" } do_pre_regen() { - pending_dir=$1 + pending_dir=$1 - cd /usr/share/yunohost/templates/ssl + cd /usr/share/yunohost/templates/ssl - install -D -m 644 openssl.cnf "${pending_dir}/${ssl_dir}/openssl.cnf" + install -D -m 644 openssl.cnf "${pending_dir}/${ssl_dir}/openssl.cnf" } do_post_regen() { - regen_conf_files=$1 + regen_conf_files=$1 - current_local_ca_domain=$(openssl x509 -in $ynh_ca -text | tr ',' '\n' | grep Issuer | awk '{print $4}') - main_domain=$(cat /etc/yunohost/current_host) + current_local_ca_domain=$(openssl x509 -in $ynh_ca -text | tr ',' '\n' | grep Issuer | awk '{print $4}') + main_domain=$(cat /etc/yunohost/current_host) - if [[ "$current_local_ca_domain" != "$main_domain" ]] - then - regen_local_ca $main_domain - # Idk how useful this is, but this was in the previous python code (domain.main_domain()) - ln -sf /etc/yunohost/certs/$domain/crt.pem /etc/ssl/certs/yunohost_crt.pem - ln -sf /etc/yunohost/certs/$domain/key.pem /etc/ssl/private/yunohost_key.pem - fi + if [[ "$current_local_ca_domain" != "$main_domain" ]]; then + regen_local_ca $main_domain + # Idk how useful this is, but this was in the previous python code (domain.main_domain()) + ln -sf /etc/yunohost/certs/$domain/crt.pem /etc/ssl/certs/yunohost_crt.pem + ln -sf /etc/yunohost/certs/$domain/key.pem /etc/ssl/private/yunohost_key.pem + fi } do_$1_regen ${@:2} diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd index 49b1bf354..f7a7acf64 100755 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -8,19 +8,19 @@ 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 - exit 1 - fi + if [[ $EUID -ne 0 ]]; then + echo "You must be root to run this script" 1>&2 + exit 1 + fi - do_pre_regen "" + do_pre_regen "" - # Drop current existing slapd data + # Drop current existing slapd data - rm -rf /var/backups/*.ldapdb - rm -rf /var/backups/slapd-* + rm -rf /var/backups/*.ldapdb + rm -rf /var/backups/slapd-* - debconf-set-selections << EOF + debconf-set-selections <&1 \ - | grep -v "none elapsed\|Closing DB" || true - chown -R openldap: /etc/ldap/slapd.d + rm -rf /etc/ldap/slapd.d + mkdir -p /etc/ldap/slapd.d + 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 "$db_init" 2>&1 \ - | grep -v "none elapsed\|Closing DB" || true - chown -R openldap: /var/lib/ldap + rm -rf /var/lib/ldap + mkdir -p /var/lib/ldap + 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 - nscd -i group || true - nscd -i passwd || true + nscd -i group || true + nscd -i passwd || true - systemctl restart slapd + systemctl restart slapd - # 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 + # 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() { - # Validate the new slapd config - # To do so, we have to use the .ldif to generate the config directory - # 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 "$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 + # Validate the new slapd config + # To do so, we have to use the .ldif to generate the config directory + # 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 "$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 - # "Commit" / apply the new config (meaning we delete the old one and replace - # it with the new one) - rm -Rf /etc/ldap/slapd.d - mv /etc/ldap/slapd_new.d /etc/ldap/slapd.d + # "Commit" / apply the new config (meaning we delete the old one and replace + # it with the new one) + rm -Rf /etc/ldap/slapd.d + mv /etc/ldap/slapd_new.d /etc/ldap/slapd.d - chown -R openldap:openldap /etc/ldap/slapd.d/ + chown -R openldap:openldap /etc/ldap/slapd.d/ } do_pre_regen() { - pending_dir=$1 + pending_dir=$1 - # remove temporary backup file - rm -f "$tmp_backup_dir_file" + # remove temporary backup file + rm -f "$tmp_backup_dir_file" - # Define if we need to migrate from hdb to mdb - curr_backend=$(grep '^database' /etc/ldap/slapd.conf 2>/dev/null | awk '{print $2}') - if [ -e /etc/ldap/slapd.conf ] && [ -n "$curr_backend" ] && \ - [ $curr_backend != 'mdb' ]; then - backup_dir="/var/backups/dc=yunohost,dc=org-${curr_backend}-$(date +%s)" - mkdir -p "$backup_dir" - slapcat -b dc=yunohost,dc=org -l "${backup_dir}/dc=yunohost-dc=org.ldif" - echo "$backup_dir" > "$tmp_backup_dir_file" - fi + # Define if we need to migrate from hdb to mdb + curr_backend=$(grep '^database' /etc/ldap/slapd.conf 2>/dev/null | awk '{print $2}') + if [ -e /etc/ldap/slapd.conf ] && [ -n "$curr_backend" ] \ + && [ $curr_backend != 'mdb' ]; then + backup_dir="/var/backups/dc=yunohost,dc=org-${curr_backend}-$(date +%s)" + mkdir -p "$backup_dir" + slapcat -b dc=yunohost,dc=org -l "${backup_dir}/dc=yunohost-dc=org.ldif" + echo "$backup_dir" >"$tmp_backup_dir_file" + fi - # create needed directories - ldap_dir="${pending_dir}/etc/ldap" - schema_dir="${ldap_dir}/schema" - mkdir -p "$ldap_dir" "$schema_dir" + # create needed directories + ldap_dir="${pending_dir}/etc/ldap" + schema_dir="${ldap_dir}/schema" + mkdir -p "$ldap_dir" "$schema_dir" - # remove legacy configuration file - [ ! -f /etc/ldap/slapd-yuno.conf ] || touch "${ldap_dir}/slapd-yuno.conf" - [ ! -f /etc/ldap/slapd.conf ] || touch "${ldap_dir}/slapd.conf" - [ ! -f /etc/ldap/schema/yunohost.schema ] || touch "${schema_dir}/yunohost.schema" + # remove legacy configuration file + [ ! -f /etc/ldap/slapd-yuno.conf ] || touch "${ldap_dir}/slapd-yuno.conf" + [ ! -f /etc/ldap/slapd.conf ] || touch "${ldap_dir}/slapd.conf" + [ ! -f /etc/ldap/schema/yunohost.schema ] || touch "${schema_dir}/yunohost.schema" - cd /usr/share/yunohost/templates/slapd + cd /usr/share/yunohost/templates/slapd - # copy configuration files - cp -a ldap.conf "$ldap_dir" - cp -a sudo.ldif mailserver.ldif permission.ldif "$schema_dir" + # copy configuration files + 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/ - cp systemd-override.conf ${pending_dir}/etc/systemd/system/slapd.service.d/ynh-override.conf + 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" + install -D -m 644 slapd.default "${pending_dir}/etc/default/slapd" } do_post_regen() { - regen_conf_files=$1 + regen_conf_files=$1 - # fix some permissions - 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 -R openldap:openldap /etc/ldap/schema/ - chown -R openldap:openldap /etc/ldap/slapd.d/ + # fix some permissions + 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 -R openldap:openldap /etc/ldap/schema/ + chown -R openldap:openldap /etc/ldap/slapd.d/ - # 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 - sleep 3 - fi + # 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 + 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 + # 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" - chown -R openldap: /var/lib/ldap - systemctl restart slapd - nscd -i group - fi + chown -R openldap: /var/lib/ldap + systemctl restart slapd + nscd -i group + fi - [ -z "$regen_conf_files" ] && exit 0 + [ -z "$regen_conf_files" ] && exit 0 - # regenerate LDAP config directory from slapd.conf - echo "Regenerate LDAP config directory from config.ldif" - _regenerate_slapd_conf + # regenerate LDAP config directory from slapd.conf + echo "Regenerate LDAP config directory from config.ldif" + _regenerate_slapd_conf - # If there's a backup, re-import its data - backup_dir=$(cat "$tmp_backup_dir_file" 2>/dev/null || true) - if [[ -n "$backup_dir" && -f "${backup_dir}/dc=yunohost-dc=org.ldif" ]]; then - # regenerate LDAP config directory and import database as root - echo "Import the database using slapadd" - slapadd -F /etc/ldap/slapd.d -b dc=yunohost,dc=org -l "${backup_dir}/dc=yunohost-dc=org.ldif" - chown -R openldap:openldap /var/lib/ldap 2>&1 - fi + # If there's a backup, re-import its data + backup_dir=$(cat "$tmp_backup_dir_file" 2>/dev/null || true) + if [[ -n "$backup_dir" && -f "${backup_dir}/dc=yunohost-dc=org.ldif" ]]; then + # regenerate LDAP config directory and import database as root + echo "Import the database using slapadd" + slapadd -F /etc/ldap/slapd.d -b dc=yunohost,dc=org -l "${backup_dir}/dc=yunohost-dc=org.ldif" + chown -R openldap:openldap /var/lib/ldap 2>&1 + fi - echo "Running slapdindex" - su openldap -s "/bin/bash" -c "/usr/sbin/slapindex" + echo "Running slapdindex" + su openldap -s "/bin/bash" -c "/usr/sbin/slapindex" - echo "Reloading slapd" - systemctl force-reload slapd + echo "Reloading slapd" + 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 - # so we'll wait either until we are able to log as admin or until a timeout - # is reached - # we need to do this because the next hooks executed after this one during - # postinstall requires to run as admin thus breaking postinstall on slow - # hardware which mean yunohost can't be correctly installed on those hardware - # and this sucks - # wait a maximum time of 5 minutes - # yes, force-reload behave like a restart - number_of_wait=0 - while ! su admin -c '' && ((number_of_wait < 60)) - do - sleep 5 - ((number_of_wait += 1)) - done + # 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 + # so we'll wait either until we are able to log as admin or until a timeout + # is reached + # we need to do this because the next hooks executed after this one during + # postinstall requires to run as admin thus breaking postinstall on slow + # hardware which mean yunohost can't be correctly installed on those hardware + # and this sucks + # wait a maximum time of 5 minutes + # yes, force-reload behave like a restart + number_of_wait=0 + while ! su admin -c '' && ((number_of_wait < 60)); do + sleep 5 + ((number_of_wait += 1)) + done } do_$1_regen ${@:2} diff --git a/data/hooks/conf_regen/09-nslcd b/data/hooks/conf_regen/09-nslcd index cefd05cd3..ff1c05433 100755 --- a/data/hooks/conf_regen/09-nslcd +++ b/data/hooks/conf_regen/09-nslcd @@ -3,23 +3,23 @@ set -e do_init_regen() { - do_pre_regen "" - systemctl restart nslcd + do_pre_regen "" + systemctl restart nslcd } do_pre_regen() { - pending_dir=$1 + pending_dir=$1 - cd /usr/share/yunohost/templates/nslcd + cd /usr/share/yunohost/templates/nslcd - install -D -m 644 nslcd.conf "${pending_dir}/etc/nslcd.conf" + install -D -m 644 nslcd.conf "${pending_dir}/etc/nslcd.conf" } do_post_regen() { - regen_conf_files=$1 + regen_conf_files=$1 - [[ -z "$regen_conf_files" ]] \ - || systemctl restart nslcd + [[ -z "$regen_conf_files" ]] \ + || systemctl restart nslcd } do_$1_regen ${@:2} diff --git a/data/hooks/conf_regen/10-apt b/data/hooks/conf_regen/10-apt index 1c80b6706..da0620e59 100755 --- a/data/hooks/conf_regen/10-apt +++ b/data/hooks/conf_regen/10-apt @@ -8,15 +8,14 @@ do_pre_regen() { mkdir --parents "${pending_dir}/etc/apt/preferences.d" packages_to_refuse_from_sury="php php-fpm php-mysql php-xml php-zip php-mbstring php-ldap php-gd php-curl php-bz2 php-json php-sqlite3 php-intl openssl libssl1.1 libssl-dev" - for package in $packages_to_refuse_from_sury - do + for package in $packages_to_refuse_from_sury; do echo " Package: $package Pin: origin \"packages.sury.org\" -Pin-Priority: -1" >> "${pending_dir}/etc/apt/preferences.d/extra_php_version" +Pin-Priority: -1" >>"${pending_dir}/etc/apt/preferences.d/extra_php_version" done - echo " + echo " # PLEASE READ THIS WARNING AND DON'T EDIT THIS FILE @@ -43,15 +42,15 @@ Pin-Priority: -1 Package: bind9 Pin: release * Pin-Priority: -1 -" >> "${pending_dir}/etc/apt/preferences.d/ban_packages" +" >>"${pending_dir}/etc/apt/preferences.d/ban_packages" } do_post_regen() { - regen_conf_files=$1 + regen_conf_files=$1 - # Make sure php7.3 is the default version when using php in cli - update-alternatives --set php /usr/bin/php7.3 + # Make sure php7.3 is the default version when using php in cli + update-alternatives --set php /usr/bin/php7.3 } do_$1_regen ${@:2} diff --git a/data/hooks/conf_regen/12-metronome b/data/hooks/conf_regen/12-metronome index ab9fca173..5dfa7b5dc 100755 --- a/data/hooks/conf_regen/12-metronome +++ b/data/hooks/conf_regen/12-metronome @@ -3,71 +3,71 @@ set -e do_pre_regen() { - pending_dir=$1 + pending_dir=$1 - cd /usr/share/yunohost/templates/metronome + cd /usr/share/yunohost/templates/metronome - # create directories for pending conf - metronome_dir="${pending_dir}/etc/metronome" - metronome_conf_dir="${metronome_dir}/conf.d" - mkdir -p "$metronome_conf_dir" + # create directories for pending conf + metronome_dir="${pending_dir}/etc/metronome" + metronome_conf_dir="${metronome_dir}/conf.d" + mkdir -p "$metronome_conf_dir" - # retrieve variables - main_domain=$(cat /etc/yunohost/current_host) + # retrieve variables + main_domain=$(cat /etc/yunohost/current_host) - # install main conf file - cat metronome.cfg.lua \ - | sed "s/{{ main_domain }}/${main_domain}/g" \ - > "${metronome_dir}/metronome.cfg.lua" + # install main conf file + cat metronome.cfg.lua \ + | sed "s/{{ main_domain }}/${main_domain}/g" \ + >"${metronome_dir}/metronome.cfg.lua" - # add domain conf files - for domain in $YNH_DOMAINS; do - cat domain.tpl.cfg.lua \ - | sed "s/{{ domain }}/${domain}/g" \ - > "${metronome_conf_dir}/${domain}.cfg.lua" - done + # add domain conf files + for domain in $YNH_DOMAINS; do + cat domain.tpl.cfg.lua \ + | sed "s/{{ domain }}/${domain}/g" \ + >"${metronome_conf_dir}/${domain}.cfg.lua" + done - # remove old domain conf files - conf_files=$(ls -1 /etc/metronome/conf.d \ - | awk '/^[^\.]+\.[^\.]+.*\.cfg\.lua$/ { print $1 }') - for file in $conf_files; do - domain=${file%.cfg.lua} - [[ $YNH_DOMAINS =~ $domain ]] \ - || touch "${metronome_conf_dir}/${file}" - done + # remove old domain conf files + conf_files=$(ls -1 /etc/metronome/conf.d \ + | awk '/^[^\.]+\.[^\.]+.*\.cfg\.lua$/ { print $1 }') + for file in $conf_files; do + domain=${file%.cfg.lua} + [[ $YNH_DOMAINS =~ $domain ]] \ + || touch "${metronome_conf_dir}/${file}" + done } do_post_regen() { - regen_conf_files=$1 + regen_conf_files=$1 - # retrieve variables - main_domain=$(cat /etc/yunohost/current_host) - - # FIXME : small optimization to do to avoid calling a yunohost command ... - # maybe another env variable like YNH_MAIN_DOMAINS idk - domain_list=$(yunohost domain list --exclude-subdomains --output-as plain --quiet) + # retrieve variables + main_domain=$(cat /etc/yunohost/current_host) - # 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" - # 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" - done + # FIXME : small optimization to do to avoid calling a yunohost command ... + # maybe another env variable like YNH_MAIN_DOMAINS idk + domain_list=$(yunohost domain list --exclude-subdomains --output-as plain --quiet) - # fix some permissions - [ ! -e '/var/xmpp-upload' ] || chown -R metronome:www-data "/var/xmpp-upload/" - [ ! -e '/var/xmpp-upload' ] || chmod 750 "/var/xmpp-upload/" + # 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" + # 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" + done - # 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/ + # fix some permissions + [ ! -e '/var/xmpp-upload' ] || chown -R metronome:www-data "/var/xmpp-upload/" + [ ! -e '/var/xmpp-upload' ] || chmod 750 "/var/xmpp-upload/" - [[ -z "$regen_conf_files" ]] \ - || systemctl restart metronome + # 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/ + + [[ -z "$regen_conf_files" ]] \ + || systemctl restart metronome } do_$1_regen ${@:2} diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx index c158ecd09..dd47651e8 100755 --- a/data/hooks/conf_regen/15-nginx +++ b/data/hooks/conf_regen/15-nginx @@ -5,148 +5,156 @@ set -e . /usr/share/yunohost/helpers do_init_regen() { - if [[ $EUID -ne 0 ]]; then - echo "You must be root to run this script" 1>&2 - exit 1 - fi + if [[ $EUID -ne 0 ]]; then + echo "You must be root to run this script" 1>&2 + exit 1 + fi - cd /usr/share/yunohost/templates/nginx + cd /usr/share/yunohost/templates/nginx - nginx_dir="/etc/nginx" - nginx_conf_dir="${nginx_dir}/conf.d" - mkdir -p "$nginx_conf_dir" + nginx_dir="/etc/nginx" + nginx_conf_dir="${nginx_dir}/conf.d" + mkdir -p "$nginx_conf_dir" - # install plain conf files - cp plain/* "$nginx_conf_dir" + # install plain conf files + cp plain/* "$nginx_conf_dir" - # probably run with init: just disable default site, restart NGINX and exit - rm -f "${nginx_dir}/sites-enabled/default" + # probably run with init: just disable default site, restart NGINX and exit + 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" - 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" + 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/ + mkdir -p $nginx_conf_dir/default.d/ + cp "redirect_to_admin.conf" $nginx_conf_dir/default.d/ - # Restart nginx if conf looks good, otherwise display error and exit unhappy - nginx -t 2>/dev/null || { nginx -t; exit 1; } - systemctl restart nginx || { journalctl --no-pager --lines=10 -u nginx >&2; exit 1; } + # Restart nginx if conf looks good, otherwise display error and exit unhappy + nginx -t 2>/dev/null || { + nginx -t + exit 1 + } + systemctl restart nginx || { + journalctl --no-pager --lines=10 -u nginx >&2 + exit 1 + } - exit 0 + exit 0 } do_pre_regen() { - pending_dir=$1 + pending_dir=$1 - cd /usr/share/yunohost/templates/nginx + cd /usr/share/yunohost/templates/nginx - nginx_dir="${pending_dir}/etc/nginx" - nginx_conf_dir="${nginx_dir}/conf.d" - mkdir -p "$nginx_conf_dir" + nginx_dir="${pending_dir}/etc/nginx" + nginx_conf_dir="${nginx_dir}/conf.d" + mkdir -p "$nginx_conf_dir" - # 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 + # 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) + # retrieve variables + 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" + # 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" - cert_status=$(yunohost domain cert status --json) + cert_status=$(yunohost domain cert status --json) - # add domain conf files - 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/" - mkdir -p "$mail_autoconfig_dir" + # add domain conf files + 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/" + mkdir -p "$mail_autoconfig_dir" - # NGINX server configuration - export domain - export domain_cert_ca=$(echo $cert_status \ - | jq ".certificates.\"$domain\".CA_type" \ - | tr -d '"') + # NGINX server configuration + export domain + export domain_cert_ca=$(echo $cert_status \ + | jq ".certificates.\"$domain\".CA_type" \ + | tr -d '"') - ynh_render_template "server.tpl.conf" "${nginx_conf_dir}/${domain}.conf" - ynh_render_template "autoconfig.tpl.xml" "${mail_autoconfig_dir}/config-v1.1.xml" + ynh_render_template "server.tpl.conf" "${nginx_conf_dir}/${domain}.conf" + ynh_render_template "autoconfig.tpl.xml" "${mail_autoconfig_dir}/config-v1.1.xml" - touch "${domain_conf_dir}/yunohost_local.conf" # Clean legacy conf files + touch "${domain_conf_dir}/yunohost_local.conf" # Clean legacy conf files - done + 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/ + 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/ - # remove old domain conf files - conf_files=$(ls -1 /etc/nginx/conf.d \ - | awk '/^[^\.]+\.[^\.]+.*\.conf$/ { print $1 }') - for file in $conf_files; do - domain=${file%.conf} - [[ $YNH_DOMAINS =~ $domain ]] \ - || touch "${nginx_conf_dir}/${file}" - done + # remove old domain conf files + conf_files=$(ls -1 /etc/nginx/conf.d \ + | awk '/^[^\.]+\.[^\.]+.*\.conf$/ { print $1 }') + for file in $conf_files; do + domain=${file%.conf} + [[ $YNH_DOMAINS =~ $domain ]] \ + || touch "${nginx_conf_dir}/${file}" + done - # remove old mail-autoconfig files - 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)/../..)) - [[ $YNH_DOMAINS =~ $domain ]] \ - || (mkdir -p "$(dirname ${pending_dir}/${file})" && touch "${pending_dir}/${file}") - done + # remove old mail-autoconfig files + 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)/../..)) + [[ $YNH_DOMAINS =~ $domain ]] \ + || (mkdir -p "$(dirname ${pending_dir}/${file})" && touch "${pending_dir}/${file}") + done - # disable default site - mkdir -p "${nginx_dir}/sites-enabled" - touch "${nginx_dir}/sites-enabled/default" + # disable default site + mkdir -p "${nginx_dir}/sites-enabled" + touch "${nginx_dir}/sites-enabled/default" } do_post_regen() { - regen_conf_files=$1 + regen_conf_files=$1 - [ -z "$regen_conf_files" ] && exit 0 + [ -z "$regen_conf_files" ] && exit 0 - # create NGINX conf directories for domains - for domain in $YNH_DOMAINS; do - mkdir -p "/etc/nginx/conf.d/${domain}.d" - done + # create NGINX conf directories for domains + 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 $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 - # 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 + # Get rid of legacy lets encrypt snippets + 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 + # 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 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; } + # 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 + } } do_$1_regen ${@:2} diff --git a/data/hooks/conf_regen/19-postfix b/data/hooks/conf_regen/19-postfix index c569e1ca1..7865cd312 100755 --- a/data/hooks/conf_regen/19-postfix +++ b/data/hooks/conf_regen/19-postfix @@ -5,78 +5,76 @@ set -e . /usr/share/yunohost/helpers do_pre_regen() { - pending_dir=$1 + pending_dir=$1 - cd /usr/share/yunohost/templates/postfix + cd /usr/share/yunohost/templates/postfix - postfix_dir="${pending_dir}/etc/postfix" - mkdir -p "$postfix_dir" + postfix_dir="${pending_dir}/etc/postfix" + mkdir -p "$postfix_dir" - default_dir="${pending_dir}/etc/default/" - mkdir -p "$default_dir" + default_dir="${pending_dir}/etc/default/" + mkdir -p "$default_dir" - # install plain conf files - cp plain/* "$postfix_dir" + # install plain conf files + cp plain/* "$postfix_dir" - # prepare main.cf conf file - main_domain=$(cat /etc/yunohost/current_host) + # prepare main.cf conf file + main_domain=$(cat /etc/yunohost/current_host) - # 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_port="" - export relay_user="" - export relay_host="$(yunohost settings get 'smtp.relay.host')" - if [ -n "${relay_host}" ] - then - relay_port="$(yunohost settings get 'smtp.relay.port')" - relay_user="$(yunohost settings get 'smtp.relay.user')" - relay_password="$(yunohost settings get 'smtp.relay.password')" - - # Avoid to display "Relay account paswword" to other users - touch ${postfix_dir}/sasl_passwd - chmod 750 ${postfix_dir}/sasl_passwd - # Avoid "postmap: warning: removing zero-length database file" - chown postfix ${pending_dir}/etc/postfix - chown postfix ${pending_dir}/etc/postfix/sasl_passwd + # Support different strategy for security configurations + export compatibility="$(yunohost settings get 'security.postfix.compatibility')" - 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" - ynh_render_template "main.cf" "${postfix_dir}/main.cf" + # Add possibility to specify a relay + # Could be useful with some isp with no 25 port open or more complex setup + export relay_port="" + export relay_user="" + export relay_host="$(yunohost settings get 'smtp.relay.host')" + if [ -n "${relay_host}" ]; then + relay_port="$(yunohost settings get 'smtp.relay.port')" + relay_user="$(yunohost settings get 'smtp.relay.user')" + relay_password="$(yunohost settings get 'smtp.relay.password')" - cat postsrsd \ - | sed "s/{{ main_domain }}/${main_domain}/g" \ - | sed "s/{{ domain_list }}/${YNH_DOMAINS}/g" \ - > "${default_dir}/postsrsd" + # Avoid to display "Relay account paswword" to other users + touch ${postfix_dir}/sasl_passwd + chmod 750 ${postfix_dir}/sasl_passwd + # Avoid "postmap: warning: removing zero-length database file" + chown postfix ${pending_dir}/etc/postfix + chown postfix ${pending_dir}/etc/postfix/sasl_passwd - # adapt it for IPv4-only hosts - 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' \ - "${postfix_dir}/main.cf" - sed -i \ - 's/inet_interfaces = all/&\ninet_protocols = ipv4/' \ - "${postfix_dir}/main.cf" - fi + 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" + ynh_render_template "main.cf" "${postfix_dir}/main.cf" + + cat postsrsd \ + | sed "s/{{ main_domain }}/${main_domain}/g" \ + | sed "s/{{ domain_list }}/${YNH_DOMAINS}/g" \ + >"${default_dir}/postsrsd" + + # adapt it for IPv4-only hosts + 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' \ + "${postfix_dir}/main.cf" + sed -i \ + 's/inet_interfaces = all/&\ninet_protocols = ipv4/' \ + "${postfix_dir}/main.cf" + fi } do_post_regen() { - regen_conf_files=$1 + regen_conf_files=$1 - if [ -e /etc/postfix/sasl_passwd ] - then - chmod 750 /etc/postfix/sasl_passwd* - chown postfix:root /etc/postfix/sasl_passwd* - fi + if [ -e /etc/postfix/sasl_passwd ]; then + chmod 750 /etc/postfix/sasl_passwd* + chown postfix:root /etc/postfix/sasl_passwd* + fi - [[ -z "$regen_conf_files" ]] \ - || { systemctl restart postfix && systemctl restart postsrsd; } + [[ -z "$regen_conf_files" ]] \ + || { systemctl restart postfix && systemctl restart postsrsd; } } diff --git a/data/hooks/conf_regen/25-dovecot b/data/hooks/conf_regen/25-dovecot index a0663a4a6..e95816604 100755 --- a/data/hooks/conf_regen/25-dovecot +++ b/data/hooks/conf_regen/25-dovecot @@ -5,62 +5,62 @@ set -e . /usr/share/yunohost/helpers do_pre_regen() { - pending_dir=$1 + pending_dir=$1 - cd /usr/share/yunohost/templates/dovecot + cd /usr/share/yunohost/templates/dovecot - dovecot_dir="${pending_dir}/etc/dovecot" - mkdir -p "${dovecot_dir}/global_script" + dovecot_dir="${pending_dir}/etc/dovecot" + mkdir -p "${dovecot_dir}/global_script" - # copy simple conf files - cp dovecot-ldap.conf "${dovecot_dir}/dovecot-ldap.conf" - cp dovecot.sieve "${dovecot_dir}/global_script/dovecot.sieve" + # copy simple conf files + cp dovecot-ldap.conf "${dovecot_dir}/dovecot-ldap.conf" + cp dovecot.sieve "${dovecot_dir}/global_script/dovecot.sieve" - export pop3_enabled="$(yunohost settings get 'pop3.enabled')" - export main_domain=$(cat /etc/yunohost/current_host) + 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" + ynh_render_template "dovecot.conf" "${dovecot_dir}/dovecot.conf" - # adapt it for IPv4-only hosts - if [ ! -f /proc/net/if_inet6 ]; then - sed -i \ - 's/^\(listen =\).*/\1 */' \ - "${dovecot_dir}/dovecot.conf" - fi + # adapt it for IPv4-only hosts + if [ ! -f /proc/net/if_inet6 ]; then + sed -i \ + 's/^\(listen =\).*/\1 */' \ + "${dovecot_dir}/dovecot.conf" + fi - mkdir -p "${dovecot_dir}/yunohost.d" - cp pre-ext.conf "${dovecot_dir}/yunohost.d" - cp post-ext.conf "${dovecot_dir}/yunohost.d" + mkdir -p "${dovecot_dir}/yunohost.d" + cp pre-ext.conf "${dovecot_dir}/yunohost.d" + cp post-ext.conf "${dovecot_dir}/yunohost.d" } do_post_regen() { - regen_conf_files=$1 + regen_conf_files=$1 - mkdir -p "/etc/dovecot/yunohost.d/pre-ext.d" - 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 \ - || adduser --system --ingroup mail --uid 500 vmail --home /var/vmail --no-create-home + # create vmail user + id vmail >/dev/null 2>&1 \ + || 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 + # 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 - 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 ]] && { - sievec /etc/dovecot/global_script/dovecot.sieve + # fix permissions chown -R vmail:mail /etc/dovecot/global_script - } + chmod 770 /etc/dovecot/global_script + chown root:mail /var/mail + chmod 1775 /var/mail - systemctl restart dovecot + [ -z "$regen_conf_files" ] && exit 0 + + # compile sieve script + [[ "$regen_conf_files" =~ dovecot\.sieve ]] && { + sievec /etc/dovecot/global_script/dovecot.sieve + chown -R vmail:mail /etc/dovecot/global_script + } + + systemctl restart dovecot } do_$1_regen ${@:2} diff --git a/data/hooks/conf_regen/31-rspamd b/data/hooks/conf_regen/31-rspamd index da9b35dfe..72a35fdcc 100755 --- a/data/hooks/conf_regen/31-rspamd +++ b/data/hooks/conf_regen/31-rspamd @@ -3,60 +3,60 @@ set -e do_pre_regen() { - pending_dir=$1 + pending_dir=$1 - cd /usr/share/yunohost/templates/rspamd + cd /usr/share/yunohost/templates/rspamd - install -D -m 644 metrics.local.conf \ - "${pending_dir}/etc/rspamd/local.d/metrics.conf" - install -D -m 644 dkim_signing.conf \ - "${pending_dir}/etc/rspamd/local.d/dkim_signing.conf" - install -D -m 644 rspamd.sieve \ - "${pending_dir}/etc/dovecot/global_script/rspamd.sieve" + install -D -m 644 metrics.local.conf \ + "${pending_dir}/etc/rspamd/local.d/metrics.conf" + install -D -m 644 dkim_signing.conf \ + "${pending_dir}/etc/rspamd/local.d/dkim_signing.conf" + install -D -m 644 rspamd.sieve \ + "${pending_dir}/etc/dovecot/global_script/rspamd.sieve" } do_post_regen() { - ## - ## DKIM key generation - ## + ## + ## DKIM key generation + ## - # create DKIM directory with proper permission - mkdir -p /etc/dkim - chown _rspamd /etc/dkim + # create DKIM directory with proper permission + mkdir -p /etc/dkim + chown _rspamd /etc/dkim - # create DKIM key for domains - 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 - # handle 2048... - opendkim-genkey --domain="$domain" \ - --selector=mail --directory=/etc/dkim -b 1024 - mv /etc/dkim/mail.private "$domain_key" - mv /etc/dkim/mail.txt "/etc/dkim/${domain}.mail.txt" + # create DKIM key for domains + 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 + # handle 2048... + opendkim-genkey --domain="$domain" \ + --selector=mail --directory=/etc/dkim -b 1024 + mv /etc/dkim/mail.private "$domain_key" + mv /etc/dkim/mail.txt "/etc/dkim/${domain}.mail.txt" + } + done + + # fix DKIM keys permissions + chown _rspamd /etc/dkim/*.mail.key + chmod 400 /etc/dkim/*.mail.key + + [ ! -e /var/log/rspamd ] || chown -R _rspamd:_rspamd /var/log/rspamd + + regen_conf_files=$1 + [ -z "$regen_conf_files" ] && exit 0 + + # compile sieve script + [[ "$regen_conf_files" =~ rspamd\.sieve ]] && { + sievec /etc/dovecot/global_script/rspamd.sieve + chown -R vmail:mail /etc/dovecot/global_script + systemctl restart dovecot } - done - # fix DKIM keys permissions - chown _rspamd /etc/dkim/*.mail.key - chmod 400 /etc/dkim/*.mail.key - - [ ! -e /var/log/rspamd ] || chown -R _rspamd:_rspamd /var/log/rspamd - - regen_conf_files=$1 - [ -z "$regen_conf_files" ] && exit 0 - - # compile sieve script - [[ "$regen_conf_files" =~ rspamd\.sieve ]] && { - 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 - systemctl -q restart rspamd.service + # Restart rspamd due to the upgrade + # https://rspamd.com/announce/2016/08/01/rspamd-1.3.1.html + systemctl -q restart rspamd.service } do_$1_regen ${@:2} diff --git a/data/hooks/conf_regen/34-mysql b/data/hooks/conf_regen/34-mysql index 41afda110..8b4d59288 100755 --- a/data/hooks/conf_regen/34-mysql +++ b/data/hooks/conf_regen/34-mysql @@ -4,69 +4,65 @@ set -e . /usr/share/yunohost/helpers do_pre_regen() { - pending_dir=$1 + pending_dir=$1 - cd /usr/share/yunohost/templates/mysql + cd /usr/share/yunohost/templates/mysql - install -D -m 644 my.cnf "${pending_dir}/etc/mysql/my.cnf" + install -D -m 644 my.cnf "${pending_dir}/etc/mysql/my.cnf" } do_post_regen() { - regen_conf_files=$1 + 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 - MYSQL_PKG="$(dpkg --list | sed -ne 's/^ii \(mariadb-server-[[:digit:].]\+\) .*$/\1/p')" - dpkg-reconfigure -freadline -u "$MYSQL_PKG" 2>&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 + 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 \ - || systemctl start mariadb + systemctl -q is-active mariadb.service \ + || systemctl start mariadb - sleep 5 + sleep 5 - echo "" | mysql && echo "Can't connect to mysql using unix_socket auth ... something went wrong during initial configuration of mysql !?" >&2 - fi + echo "" | mysql && echo "Can't connect to mysql using unix_socket auth ... something went wrong during initial configuration of mysql !?" >&2 + fi - # 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 + # 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 2>/dev/null - 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 + # 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 2>/dev/null; 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 2>/dev/null - 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 + # If now we're able to login without password, drop the mysql password + if echo "" | mysql 2>/dev/null; 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 - # c.f. https://forum.yunohost.org/t/mysql-ne-fonctionne-pas/11661 - # Playing with enable/disable allows to recreate the proper symlinks. - if [ ! -e /etc/systemd/system/mysql.service ] - then - systemctl stop mysql -q - systemctl disable mysql -q - systemctl disable mariadb -q - systemctl enable mariadb -q - systemctl is-active mariadb -q || systemctl start mariadb - 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. + if [ ! -e /etc/systemd/system/mysql.service ]; then + systemctl stop mysql -q + systemctl disable mysql -q + systemctl disable mariadb -q + systemctl enable mariadb -q + systemctl is-active mariadb -q || systemctl start mariadb + fi - [[ -z "$regen_conf_files" ]] \ - || systemctl restart mysql + [[ -z "$regen_conf_files" ]] \ + || systemctl restart mysql } do_$1_regen ${@:2} diff --git a/data/hooks/conf_regen/35-redis b/data/hooks/conf_regen/35-redis index da5eac4c9..ac486f373 100755 --- a/data/hooks/conf_regen/35-redis +++ b/data/hooks/conf_regen/35-redis @@ -1,13 +1,13 @@ #!/bin/bash do_pre_regen() { - : + : } do_post_regen() { - # Enforce these damn permissions because for some reason in some weird cases - # they are spontaneously replaced by root:root -_- - chown -R redis:adm /var/log/redis + # Enforce these damn permissions because for some reason in some weird cases + # they are spontaneously replaced by root:root -_- + chown -R redis:adm /var/log/redis } do_$1_regen ${@:2} diff --git a/data/hooks/conf_regen/37-mdns b/data/hooks/conf_regen/37-mdns index b8112a6c1..8cb364084 100755 --- a/data/hooks/conf_regen/37-mdns +++ b/data/hooks/conf_regen/37-mdns @@ -3,55 +3,52 @@ 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 "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 } do_init_regen() { - do_pre_regen - do_post_regen /etc/systemd/system/yunomdns.service - systemctl enable yunomdns + do_pre_regen + do_post_regen /etc/systemd/system/yunomdns.service + systemctl enable yunomdns } do_pre_regen() { - pending_dir="$1" + pending_dir="$1" - cd /usr/share/yunohost/templates/mdns - mkdir -p ${pending_dir}/etc/systemd/system/ - cp yunomdns.service ${pending_dir}/etc/systemd/system/ + 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 + 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 + mkdir -p ${pending_dir}/etc/yunohost + _generate_config >${pending_dir}/etc/yunohost/mdns.yml } do_post_regen() { - regen_conf_files="$1" + regen_conf_files="$1" - chown mdns:mdns /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$" - then - systemctl daemon-reload - fi + # 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 - # 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 + # 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 + [[ -z "$regen_conf_files" ]] \ + || systemctl restart yunomdns } do_$1_regen ${@:2} diff --git a/data/hooks/conf_regen/43-dnsmasq b/data/hooks/conf_regen/43-dnsmasq index f3bed7b04..0c016bcb9 100755 --- a/data/hooks/conf_regen/43-dnsmasq +++ b/data/hooks/conf_regen/43-dnsmasq @@ -4,80 +4,77 @@ set -e . /usr/share/yunohost/helpers do_pre_regen() { - pending_dir=$1 + pending_dir=$1 - cd /usr/share/yunohost/templates/dnsmasq + cd /usr/share/yunohost/templates/dnsmasq - # create directory for pending conf - dnsmasq_dir="${pending_dir}/etc/dnsmasq.d" - mkdir -p "$dnsmasq_dir" - etcdefault_dir="${pending_dir}/etc/default" - mkdir -p "$etcdefault_dir" + # create directory for pending conf + dnsmasq_dir="${pending_dir}/etc/dnsmasq.d" + mkdir -p "$dnsmasq_dir" + etcdefault_dir="${pending_dir}/etc/default" + mkdir -p "$etcdefault_dir" - # add general conf files - cp plain/etcdefault ${pending_dir}/etc/default/dnsmasq - cp plain/dnsmasq.conf ${pending_dir}/etc/dnsmasq.conf + # add general conf files + cp plain/etcdefault ${pending_dir}/etc/default/dnsmasq + cp plain/dnsmasq.conf ${pending_dir}/etc/dnsmasq.conf - # add resolver file - cat plain/resolv.dnsmasq.conf | grep "^nameserver" | shuf > ${pending_dir}/etc/resolv.dnsmasq.conf + # add resolver file + cat plain/resolv.dnsmasq.conf | grep "^nameserver" | shuf >${pending_dir}/etc/resolv.dnsmasq.conf - # retrieve variables - ipv4=$(curl -s -4 https://ip.yunohost.org 2>/dev/null || true) - ynh_validate_ip4 "$ipv4" || ipv4='127.0.0.1' - ipv6=$(curl -s -6 https://ip6.yunohost.org 2>/dev/null || true) - ynh_validate_ip6 "$ipv6" || ipv6='' + # retrieve variables + ipv4=$(curl -s -4 https://ip.yunohost.org 2>/dev/null || true) + ynh_validate_ip4 "$ipv4" || ipv4='127.0.0.1' + ipv6=$(curl -s -6 https://ip6.yunohost.org 2>/dev/null || true) + ynh_validate_ip6 "$ipv6" || ipv6='' - export ipv4 - export ipv6 + export ipv4 + export ipv6 - # add domain conf files - for domain in $YNH_DOMAINS; do - export domain - ynh_render_template "domain.tpl" "${dnsmasq_dir}/${domain}" - done + # add domain conf files + for domain in $YNH_DOMAINS; do + export domain + ynh_render_template "domain.tpl" "${dnsmasq_dir}/${domain}" + done - # remove old domain conf files - conf_files=$(ls -1 /etc/dnsmasq.d \ - | awk '/^[^\.]+\.[^\.]+.*$/ { print $1 }') - for domain in $conf_files; do - [[ $YNH_DOMAINS =~ $domain ]] \ - || touch "${dnsmasq_dir}/${domain}" - done + # remove old domain conf files + conf_files=$(ls -1 /etc/dnsmasq.d \ + | awk '/^[^\.]+\.[^\.]+.*$/ { print $1 }') + for domain in $conf_files; do + [[ $YNH_DOMAINS =~ $domain ]] \ + || touch "${dnsmasq_dir}/${domain}" + done } do_post_regen() { - regen_conf_files=$1 + 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 + # 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 + 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 - # 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 "\n127.0.0.1\t$short_hostname" >>/etc/hosts + # 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 "\n127.0.0.1\t$short_hostname" >>/etc/hosts - [[ -n "$regen_conf_files" ]] || return + [[ -n "$regen_conf_files" ]] || return - # Remove / disable services likely to conflict with dnsmasq - for SERVICE in systemd-resolved bind9 - do - systemctl is-enabled $SERVICE &>/dev/null && systemctl disable $SERVICE 2>/dev/null - systemctl is-active $SERVICE &>/dev/null && systemctl stop $SERVICE - done + # Remove / disable services likely to conflict with dnsmasq + for SERVICE in systemd-resolved bind9; do + systemctl is-enabled $SERVICE &>/dev/null && systemctl disable $SERVICE 2>/dev/null + systemctl is-active $SERVICE &>/dev/null && systemctl stop $SERVICE + done - systemctl restart dnsmasq + systemctl restart dnsmasq } do_$1_regen ${@:2} diff --git a/data/hooks/conf_regen/46-nsswitch b/data/hooks/conf_regen/46-nsswitch index be5cb2b86..2c984a905 100755 --- a/data/hooks/conf_regen/46-nsswitch +++ b/data/hooks/conf_regen/46-nsswitch @@ -3,23 +3,23 @@ set -e do_init_regen() { - do_pre_regen "" - systemctl restart unscd + do_pre_regen "" + systemctl restart unscd } do_pre_regen() { - pending_dir=$1 + pending_dir=$1 - cd /usr/share/yunohost/templates/nsswitch + cd /usr/share/yunohost/templates/nsswitch - install -D -m 644 nsswitch.conf "${pending_dir}/etc/nsswitch.conf" + install -D -m 644 nsswitch.conf "${pending_dir}/etc/nsswitch.conf" } do_post_regen() { - regen_conf_files=$1 + regen_conf_files=$1 - [[ -z "$regen_conf_files" ]] \ - || systemctl restart unscd + [[ -z "$regen_conf_files" ]] \ + || systemctl restart unscd } do_$1_regen ${@:2} diff --git a/data/hooks/conf_regen/52-fail2ban b/data/hooks/conf_regen/52-fail2ban index 7aef72ebc..6cbebbfb1 100755 --- a/data/hooks/conf_regen/52-fail2ban +++ b/data/hooks/conf_regen/52-fail2ban @@ -5,26 +5,26 @@ set -e . /usr/share/yunohost/helpers do_pre_regen() { - pending_dir=$1 + pending_dir=$1 - cd /usr/share/yunohost/templates/fail2ban + cd /usr/share/yunohost/templates/fail2ban - fail2ban_dir="${pending_dir}/etc/fail2ban" - mkdir -p "${fail2ban_dir}/filter.d" - mkdir -p "${fail2ban_dir}/jail.d" + fail2ban_dir="${pending_dir}/etc/fail2ban" + mkdir -p "${fail2ban_dir}/filter.d" + mkdir -p "${fail2ban_dir}/jail.d" - cp yunohost.conf "${fail2ban_dir}/filter.d/yunohost.conf" - cp jail.conf "${fail2ban_dir}/jail.conf" + cp yunohost.conf "${fail2ban_dir}/filter.d/yunohost.conf" + cp jail.conf "${fail2ban_dir}/jail.conf" - export ssh_port="$(yunohost settings get 'security.ssh.port')" - ynh_render_template "yunohost-jails.conf" "${fail2ban_dir}/jail.d/yunohost-jails.conf" + 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() { - regen_conf_files=$1 + regen_conf_files=$1 - [[ -z "$regen_conf_files" ]] \ - || systemctl reload fail2ban + [[ -z "$regen_conf_files" ]] \ + || systemctl reload fail2ban } do_$1_regen ${@:2} diff --git a/data/hooks/post_user_create/ynh_multimedia b/data/hooks/post_user_create/ynh_multimedia index 26282cdc9..5b4b31b88 100644 --- a/data/hooks/post_user_create/ynh_multimedia +++ b/data/hooks/post_user_create/ynh_multimedia @@ -1,7 +1,7 @@ #!/bin/bash user=$1 - + readonly MEDIA_GROUP=multimedia readonly MEDIA_DIRECTORY=/home/yunohost.multimedia diff --git a/data/hooks/restore/05-conf_ldap b/data/hooks/restore/05-conf_ldap index c2debe018..a9eb10b1c 100644 --- a/data/hooks/restore/05-conf_ldap +++ b/data/hooks/restore/05-conf_ldap @@ -14,11 +14,11 @@ die() { # Restore saved configuration and database [[ $state -ge 1 ]] \ - && (rm -rf /etc/ldap/slapd.d && - mv "${TMPDIR}/slapd.d" /etc/ldap/slapd.d) + && (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) + && (rm -rf /var/lib/ldap \ + && mv "${TMPDIR}/ldap" /var/lib/ldap) chown -R openldap: /etc/ldap/slapd.d /var/lib/ldap systemctl start slapd @@ -38,7 +38,7 @@ cp -a "${backup_dir}/ldap.conf" /etc/ldap/ldap.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" + || die 1 "Unable to restore LDAP configuration" chown -R openldap: /etc/ldap/slapd.d # Restore the database @@ -46,7 +46,7 @@ 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" + || die 2 "Unable to restore LDAP database" chown -R openldap: /var/lib/ldap systemctl start slapd diff --git a/data/hooks/restore/50-conf_manually_modified_files b/data/hooks/restore/50-conf_manually_modified_files index 2d0943043..b23b95ec9 100644 --- a/data/hooks/restore/50-conf_manually_modified_files +++ b/data/hooks/restore/50-conf_manually_modified_files @@ -5,8 +5,7 @@ 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 +for file in $(cat ./manually_modified_files_list); do ynh_restore_file --origin_path="$file" --not_mandatory done From 28b1bbcc86ab1557c500c22636137b003585e538 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 Oct 2021 16:36:08 +0200 Subject: [PATCH 0797/1155] Also lint/reformat debian scripts --- debian/postinst | 59 ++++++++++++++++++++++++------------------------- debian/postrm | 6 ++--- 2 files changed, 32 insertions(+), 33 deletions(-) diff --git a/debian/postinst b/debian/postinst index ceeed3cdf..0dd1dedd0 100644 --- a/debian/postinst +++ b/debian/postinst @@ -3,36 +3,35 @@ set -e do_configure() { - rm -rf /var/cache/moulinette/* + 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 + 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 ... - if [ -d /etc/yunohost/apps/ ] && ls /etc/yunohost/apps/* >/dev/null 2>&1 - then - echo "Sounds like /etc/yunohost/installed mysteriously disappeared ... You should probably contact the Yunohost support ..." - else - bash /usr/share/yunohost/hooks/conf_regen/01-yunohost init - bash /usr/share/yunohost/hooks/conf_regen/02-ssl init - bash /usr/share/yunohost/hooks/conf_regen/09-nslcd init - 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..." - yunohost tools regen-conf --output-as none + if [ ! -f /etc/yunohost/installed ]; then + # If apps/ is not empty, we're probably already installed in the past and + # something funky happened ... + if [ -d /etc/yunohost/apps/ ] && ls /etc/yunohost/apps/* >/dev/null 2>&1; then + echo "Sounds like /etc/yunohost/installed mysteriously disappeared ... You should probably contact the Yunohost support ..." + else + bash /usr/share/yunohost/hooks/conf_regen/01-yunohost init + bash /usr/share/yunohost/hooks/conf_regen/02-ssl init + bash /usr/share/yunohost/hooks/conf_regen/09-nslcd init + 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..." + yunohost tools regen-conf --output-as none - echo "Launching migrations..." - yunohost tools migrations run --auto + echo "Launching migrations..." + yunohost tools migrations run --auto - echo "Re-diagnosing server health..." - yunohost diagnosis run --force - fi + echo "Re-diagnosing server health..." + yunohost diagnosis run --force + fi } @@ -50,13 +49,13 @@ do_configure() { case "$1" in configure) do_configure - ;; - abort-upgrade|abort-remove|abort-deconfigure) - ;; + ;; + abort-upgrade | abort-remove | abort-deconfigure) ;; + *) echo "postinst called with unknown argument \`$1'" >&2 exit 1 - ;; + ;; esac #DEBHELPER# diff --git a/debian/postrm b/debian/postrm index 63e42b4d4..ceadd5bce 100644 --- a/debian/postrm +++ b/debian/postrm @@ -6,12 +6,12 @@ set -e if [ "$1" = "purge" ]; then - update-rc.d yunohost-firewall remove >/dev/null - rm -f /etc/yunohost/installed + update-rc.d yunohost-firewall remove >/dev/null + rm -f /etc/yunohost/installed fi if [ "$1" = "remove" ]; then - rm -f /etc/yunohost/installed + rm -f /etc/yunohost/installed fi # Reset dpkg vendor to debian From c9cdfc6b0f9417578704c1cfd32c69d5460129ff Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 Oct 2021 16:56:47 +0200 Subject: [PATCH 0798/1155] Typo :| --- 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 235cc0067..f563757fb 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -333,7 +333,7 @@ EOF # Integrate new php-fpm service in yunohost yunohost service add php${specific_php_version}-fpm --log "/var/log/php${phpversion}-fpm.log" - elif grep --quiet 'php' <<< "$dependencies" + elif grep --quiet 'php' <<< "$dependencies"; then # Store phpversion into the config of this app ynh_app_setting_set --app=$app --key=phpversion --value=$YNH_DEFAULT_PHP_VERSION fi From fd1cc5e6ce7544e6d1f7bf51e70e73e6b2399bdf Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 Oct 2021 18:56:17 +0200 Subject: [PATCH 0799/1155] Fix stuff reported by shellcheck --- data/helpers.d/config | 2 +- data/helpers.d/utils | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/config b/data/helpers.d/config index 247d12d6f..5999387db 100644 --- a/data/helpers.d/config +++ b/data/helpers.d/config @@ -66,7 +66,7 @@ _ynh_app_config_apply_one() { "set__${bind%%(*}" $short_setting $type $bind elif [[ "$bind" == "null" ]]; then - continue + return # Save in a file elif [[ "$type" == "file" ]]; then diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 0e38423f2..a4afdaf88 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -542,7 +542,7 @@ ynh_read_var_in_file() { fi # Remove comments if needed - local expression="$(echo "$expression_with_comment" | sed "s@$comments[^$string]*\$@@g" | 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 @@ -620,7 +620,7 @@ ynh_write_var_in_file() { fi # Remove comments if needed - local expression="$(echo "$expression_with_comment" | sed "s@$comments[^$string]*\$@@g" | sed "s@\s*[$endline]*\s*]*\$@@")" + 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')" From 1d2e4e78f24b891e6cbe2a6d6e2f7a437f280759 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 Oct 2021 18:57:56 +0200 Subject: [PATCH 0800/1155] Safer, clearer ynh_secure_remove --- data/helpers.d/utils | 49 +++++++------ tests/test_helpers.d/ynhtest_secure_remove.sh | 71 +++++++++++++++++++ 2 files changed, 100 insertions(+), 20 deletions(-) create mode 100644 tests/test_helpers.d/ynhtest_secure_remove.sh diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 061ff324d..a2d7855b9 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -724,6 +724,28 @@ properly with chmod/chown." echo $TMP_DIR } +_acceptable_path_to_delete() { + local file=$1 + + local forbidden_paths=$(ls -d / /* /{var,home,usr}/* /etc/{default,sudoers.d,yunohost,cron*}) + + # Legacy : A couple apps still have data in /home/$app ... + if [[ -n "$app" ]] + then + forbidden_paths=$(echo "$forbidden_paths" | grep -v "/home/$app") + fi + + # Use realpath to normalize the path .. + # i.e convert ///foo//bar//..///baz//// to /foo/baz + file=$(realpath --no-symlinks $file) + if [ -z "$file" ] || grep -q -x -F "$file" <<< "$forbidden_paths"; then + return 1 + else + return 0 + fi +} + + # Remove a file or a directory securely # # usage: ynh_secure_remove --file=path_to_remove @@ -739,31 +761,18 @@ ynh_secure_remove () { ynh_handle_getopts_args "$@" set +o xtrace # set +x - local forbidden_path=" \ - /var/www \ - /home/yunohost.app" - - if [ $# -ge 2 ] - then + if [ $# -ge 2 ]; then 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 [[ -z "$file" ]] - then + if [[ -z "$file" ]]; then ynh_print_warn --message="ynh_secure_remove called with empty argument, ignoring." - elif [[ "$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="Not deleting '$file' because it is not an acceptable path to delete." - elif [ -e "$file" ] - then - rm --recursive "$file" - else + elif [[ ! -e $file ]]; then ynh_print_info --message="'$file' wasn't deleted because it doesn't exist." + elif ! _acceptable_path_to_delete "$file"; then + ynh_print_warn --message="Not deleting '$file' because it is not an acceptable path to delete." + else + rm --recursive "$file" fi set -o xtrace # set -x diff --git a/tests/test_helpers.d/ynhtest_secure_remove.sh b/tests/test_helpers.d/ynhtest_secure_remove.sh new file mode 100644 index 000000000..04d85fa7a --- /dev/null +++ b/tests/test_helpers.d/ynhtest_secure_remove.sh @@ -0,0 +1,71 @@ +ynhtest_acceptable_path_to_delete() { + + mkdir -p /home/someuser + mkdir -p /home/$app + mkdir -p /home/yunohost.app/$app + mkdir -p /var/www/$app + touch /var/www/$app/bar + touch /etc/cron.d/$app + + ! _acceptable_path_to_delete / + ! _acceptable_path_to_delete //// + ! _acceptable_path_to_delete " //// " + ! _acceptable_path_to_delete /var + ! _acceptable_path_to_delete /var/www + ! _acceptable_path_to_delete /var/cache + ! _acceptable_path_to_delete /usr + ! _acceptable_path_to_delete /usr/bin + ! _acceptable_path_to_delete /home + ! _acceptable_path_to_delete /home/yunohost.backup + ! _acceptable_path_to_delete /home/yunohost.app + ! _acceptable_path_to_delete /home/yunohost.app/ + ! _acceptable_path_to_delete ///home///yunohost.app/// + ! _acceptable_path_to_delete /home/yunohost.app/$app/.. + ! _acceptable_path_to_delete ///home///yunohost.app///$app///..// + ! _acceptable_path_to_delete /home/yunohost.app/../$app/.. + ! _acceptable_path_to_delete /home/someuser + ! _acceptable_path_to_delete /home/yunohost.app//../../$app + ! _acceptable_path_to_delete " /home/yunohost.app/// " + ! _acceptable_path_to_delete /etc/cron.d/ + ! _acceptable_path_to_delete /etc/yunohost/ + + _acceptable_path_to_delete /home/yunohost.app/$app + _acceptable_path_to_delete /home/yunohost.app/$app/bar + _acceptable_path_to_delete /etc/cron.d/$app + _acceptable_path_to_delete /var/www/$app/bar + _acceptable_path_to_delete /var/www/$app + + rm /var/www/$app/bar + rm /etc/cron.d/$app + rmdir /home/yunohost.app/$app + rmdir /home/$app + rmdir /home/someuser + rmdir /var/www/$app +} + +ynhtest_secure_remove() { + + mkdir -p /home/someuser + mkdir -p /home/yunohost.app/$app + mkdir -p /var/www/$app + mkdir -p /var/whatever + touch /var/www/$app/bar + touch /etc/cron.d/$app + + ! ynh_secure_remove --file="/home/someuser" + ! ynh_secure_remove --file="/home/yunohost.app/" + ! ynh_secure_remove --file="/var/whatever" + ynh_secure_remove --file="/home/yunohost.app/$app" + ynh_secure_remove --file="/var/www/$app" + ynh_secure_remove --file="/etc/cron.d/$app" + + test -e /home/someuser + test -e /home/yunohost.app + test -e /var/whatever + ! test -e /home/yunohost.app/$app + ! test -e /var/www/$app + ! test -e /etc/cron.d/$app + + rmdir /home/someuser + rmdir /var/whatever +} From e563a366ef5eb85ed9e90dc39fea6a2bbb670018 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 7 Oct 2021 01:46:09 +0200 Subject: [PATCH 0801/1155] helpers apt: Use smarter grep with lookbehind to extract php version from dependency list Co-authored-by: Florent --- 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 f563757fb..46b769804 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -252,7 +252,8 @@ ynh_install_app_dependencies () { # Check for specific php dependencies which requires sury # This grep will for example return "7.4" if dependencies is "foo bar php7.4-pwet php-gni" - local specific_php_version=$(echo $dependencies | tr '-' ' ' | grep -o -E "\" | sed 's/php//g' | sort | uniq) + # The (?<=php) syntax corresponds to lookbehind ;) + local specific_php_version=$(echo $dependencies | grep -oP '(?<=php)[0-9.]+(?=-|\>)' | sort -u) # Ignore case where the php version found is the one available in debian vanilla [[ "$specific_php_version" != "$YNH_DEFAULT_PHP_VERSION" ]] || specific_php_version="" From 560162dd96e47aca8e49bbe0bbd7351a5c772cf3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 7 Oct 2021 10:29:03 +0200 Subject: [PATCH 0802/1155] Sury pinning is managed in the core, c.f. 346728e5 --- data/helpers.d/apt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index 46b769804..2f5df175c 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -351,12 +351,6 @@ ynh_add_sury() { # Add an extra repository for those packages 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 --priority=600 - # Pin this extra repository after packages are installed to prevent sury from doing shit - for package_to_not_upgrade in "php" "php-fpm" "php-mysql" "php-xml" "php-zip" "php-mbstring" "php-ldap" "php-gd" "php-curl" "php-bz2" "php-json" "php-sqlite3" "php-intl" "openssl" "libssl1.1" "libssl-dev" - do - ynh_pin_repo --package="$package_to_not_upgrade" --pin="origin \"packages.sury.org\"" --priority="-1" --name=extra_php_version --append - done - } From bde5590783f1e9a81b5b9ae33b1101c217fcf000 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 7 Oct 2021 10:33:33 +0200 Subject: [PATCH 0803/1155] Update data/helpers.d/logrotate --- data/helpers.d/logrotate | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/logrotate b/data/helpers.d/logrotate index a4548512d..1844cc5c7 100644 --- a/data/helpers.d/logrotate +++ b/data/helpers.d/logrotate @@ -97,8 +97,10 @@ EOF 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) - ynh_user_exists --username="$app" || chown $app:$app "$logfile" - chmod o-rwx "$logfile" + if ynh_user_exists --username="$app"; then + chown $app:$app "$logfile" + chmod o-rwx "$logfile" + fi } From f769c40f9602d9ae85eb4c99d332ee6da9781f95 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 7 Oct 2021 10:37:05 +0200 Subject: [PATCH 0804/1155] Double quotes to prevent bash apocalypse --- 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 a2d7855b9..1e1930010 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -737,7 +737,7 @@ _acceptable_path_to_delete() { # Use realpath to normalize the path .. # i.e convert ///foo//bar//..///baz//// to /foo/baz - file=$(realpath --no-symlinks $file) + file=$(realpath --no-symlinks "$file") if [ -z "$file" ] || grep -q -x -F "$file" <<< "$forbidden_paths"; then return 1 else From c8e14133d5757cefec3236a41c2914cb0a4feddd Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 7 Oct 2021 10:46:18 +0200 Subject: [PATCH 0805/1155] Update changelog for 4.3.1.2 --- debian/changelog | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/debian/changelog b/debian/changelog index a10c83888..f778e0e5d 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,17 @@ +yunohost (4.3.1.2) testing; urgency=low + + - [fix] apps: upgrade was broken because of typo ([#1350](https://github.com/YunoHost/yunohost/pull/1350)) + - [enh] apps: in app_info, return a new is_webapp info meant to be used by API/webadmin (4cd5e9b6) + - [fix] configpanel: handle case where file question didnt get modified from webadmin, in which case self.value contains a path (54d901ad) + - [fix] configpanel: bind_key -> bind_key_ to prevent yunohost from redacting key names which leads to broken log metadata.yml somehow (941cc294) + - [enh] questions: Add visible attribute support in cli (74256845) + - [enh] helpers: Simplify apt/php dependencies helpers ([#1018](https://github.com/YunoHost/yunohost/pull/1018)) + - [enh] helpers: In logrotate helper, enforce decent permissions on log file if app user exists ([#1352](https://github.com/YunoHost/yunohost/pull/1352)) + + Thanks to all contributors <3 ! (Éric Gaspar, Kay0u, ljf) + + -- Alexandre Aubin Thu, 07 Oct 2021 10:42:06 +0200 + yunohost (4.3.1.1) testing; urgency=low - [enh] app helpers: Update n version ([#1347](https://github.com/YunoHost/yunohost/pull/1347)) From 38cff4a98e9d52668a7360b53e0e685cfd77b612 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 7 Oct 2021 11:24:46 +0200 Subject: [PATCH 0806/1155] Fix app url regex, branch names may contain dots --- src/yunohost/app.py | 2 +- src/yunohost/tests/test_appurl.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index fe5281384..fb544cab2 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -79,7 +79,7 @@ re_app_instance_name = re.compile( ) APP_REPO_URL = re.compile( - r"^https://[a-zA-Z0-9-_.]+/[a-zA-Z0-9-_./]+/[a-zA-Z0-9-_.]+_ynh(/?(-/)?tree/[a-zA-Z0-9-_]+)?(\.git)?/?$" + r"^https://[a-zA-Z0-9-_.]+/[a-zA-Z0-9-_./]+/[a-zA-Z0-9-_.]+_ynh(/?(-/)?tree/[a-zA-Z0-9-_.]+)?(\.git)?/?$" ) APP_FILES_TO_COPY = [ diff --git a/src/yunohost/tests/test_appurl.py b/src/yunohost/tests/test_appurl.py index cf2c6c2c3..28f33d998 100644 --- a/src/yunohost/tests/test_appurl.py +++ b/src/yunohost/tests/test_appurl.py @@ -68,6 +68,7 @@ def test_repo_url_definition(): assert _is_app_repo_url( "https://gitlab.domainepublic.net/Neutrinet/neutrinet_ynh/-/tree/unstable" ) + assert _is_app_repo_url("https://github.com/YunoHost-Apps/foobar/tree/1.23.4") assert _is_app_repo_url("git@github.com:YunoHost-Apps/foobar_ynh.git") assert not _is_app_repo_url("github.com/YunoHost-Apps/foobar_ynh") From 0b2ef5d16fa2f3be229bdd3ae211390ff388ae7b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 7 Oct 2021 18:31:59 +0200 Subject: [PATCH 0807/1155] Update changelog for 4.3.1.3 --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index f778e0e5d..def37d6b2 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +yunohost (4.3.1.3) testing; urgency=low + + - [fix] app: repo url branch names may contain dots (38cff4a9) + + -- Alexandre Aubin Thu, 07 Oct 2021 18:31:09 +0200 + yunohost (4.3.1.2) testing; urgency=low - [fix] apps: upgrade was broken because of typo ([#1350](https://github.com/YunoHost/yunohost/pull/1350)) From df02f898ee2ee5fe9260d0875e40dc490ec72926 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 9 Oct 2021 00:32:49 +0200 Subject: [PATCH 0808/1155] [enh] Don't generate dnsmasq conf for .local domains --- data/hooks/conf_regen/43-dnsmasq | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/data/hooks/conf_regen/43-dnsmasq b/data/hooks/conf_regen/43-dnsmasq index f3bed7b04..687fc704f 100755 --- a/data/hooks/conf_regen/43-dnsmasq +++ b/data/hooks/conf_regen/43-dnsmasq @@ -32,6 +32,7 @@ do_pre_regen() { # add domain conf files for domain in $YNH_DOMAINS; do + [[ ! $domain =~ \.local$ ]] || continue export domain ynh_render_template "domain.tpl" "${dnsmasq_dir}/${domain}" done @@ -40,8 +41,10 @@ do_pre_regen() { conf_files=$(ls -1 /etc/dnsmasq.d \ | awk '/^[^\.]+\.[^\.]+.*$/ { print $1 }') for domain in $conf_files; do - [[ $YNH_DOMAINS =~ $domain ]] \ - || touch "${dnsmasq_dir}/${domain}" + if [[ ! $YNH_DOMAINS =~ $domain ]] && [[ ! $domain =~ \.local$ ]] + then + touch "${dnsmasq_dir}/${domain}" + fi done } From e521fef23d204570735b2cf97382deb9f72902a5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 9 Oct 2021 02:20:08 +0200 Subject: [PATCH 0809/1155] Fix typo in tests @_@ --- src/yunohost/tests/test_appurl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/tests/test_appurl.py b/src/yunohost/tests/test_appurl.py index 28f33d998..7b4c6e2e3 100644 --- a/src/yunohost/tests/test_appurl.py +++ b/src/yunohost/tests/test_appurl.py @@ -68,7 +68,7 @@ def test_repo_url_definition(): assert _is_app_repo_url( "https://gitlab.domainepublic.net/Neutrinet/neutrinet_ynh/-/tree/unstable" ) - assert _is_app_repo_url("https://github.com/YunoHost-Apps/foobar/tree/1.23.4") + assert _is_app_repo_url("https://github.com/YunoHost-Apps/foobar_ynh/tree/1.23.4") assert _is_app_repo_url("git@github.com:YunoHost-Apps/foobar_ynh.git") assert not _is_app_repo_url("github.com/YunoHost-Apps/foobar_ynh") From dab3dc6f370e262ed427a3459aebde35f4f92da6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 11 Oct 2021 19:42:39 +0200 Subject: [PATCH 0810/1155] dovecot: add conf snippet to get rid of stupid stats-writer errors in mail.log --- data/templates/dovecot/dovecot.conf | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/data/templates/dovecot/dovecot.conf b/data/templates/dovecot/dovecot.conf index ee8511f83..c7e937979 100644 --- a/data/templates/dovecot/dovecot.conf +++ b/data/templates/dovecot/dovecot.conf @@ -78,6 +78,20 @@ service quota-warning { } } +service stats { + unix_listener stats-reader { + user = vmail + group = mail + mode = 0660 + } + + unix_listener stats-writer { + user = vmail + group = mail + mode = 0660 + } +} + plugin { sieve = /var/mail/sievescript/%n/.dovecot.sieve sieve_dir = /var/mail/sievescript/%n/scripts/ From 910364f9c4a3309e0411a87b15e8987996bd4cfc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 12 Oct 2021 15:49:08 +0200 Subject: [PATCH 0811/1155] helpers: Drop obsolete/unused/weird logging helpers --- data/helpers.d/logging | 86 ------------------------------------------ data/helpers.d/php | 24 ++++++------ 2 files changed, 12 insertions(+), 98 deletions(-) diff --git a/data/helpers.d/logging b/data/helpers.d/logging index 71998763e..9075fc7aa 100644 --- a/data/helpers.d/logging +++ b/data/helpers.d/logging @@ -38,25 +38,6 @@ ynh_print_info() { echo "$message" >&$YNH_STDINFO } -# Ignore the yunohost-cli log to prevent errors with conditional commands -# -# [internal] -# -# usage: ynh_no_log COMMAND -# -# Simply duplicate the log, execute the yunohost command and replace the log without the result of this command -# It's a very badly hack... -# -# Requires YunoHost version 2.6.4 or higher. -ynh_no_log() { - local ynh_cli_log=/var/log/yunohost/yunohost-cli.log - cp --archive ${ynh_cli_log} ${ynh_cli_log}-move - eval $@ - local exit_code=$? - mv ${ynh_cli_log}-move ${ynh_cli_log} - return $exit_code -} - # Main printer, just in case in the future we have to change anything about that. # # [internal] @@ -302,70 +283,3 @@ ynh_script_progression () { ynh_return () { echo "$1" >> "$YNH_STDRETURN" } - -# Debugger for app packagers -# -# usage: ynh_debug [--message=message] [--trace=1/0] -# | arg: -m, --message= - The text to print -# | arg: -t, --trace= - Turn on or off the trace of the script. Usefull to trace nonly a small part of a script. -# -# Requires YunoHost version 3.5.0 or higher. -ynh_debug () { - # Disable set xtrace for the helper itself, to not pollute the debug log - 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= ) - local message - local trace - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - # Re-disable xtrace, ynh_handle_getopts_args set it back - set +o xtrace # set +x - message=${message:-} - trace=${trace:-} - - if [ -n "$message" ] - then - ynh_print_log "[Debug] ${message}" >&2 - fi - - if [ "$trace" == "1" ] - then - ynh_debug --message="Enable debugging" - 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. - 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 +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 -o xtrace # set -x -} - -# Execute a command and print the result as debug -# -# 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. -# -# Requires YunoHost version 3.5.0 or higher. -ynh_debug_exec () { - ynh_debug --message="$(eval $@)" -} diff --git a/data/helpers.d/php b/data/helpers.d/php index d383c1e4f..63f3af653 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -478,28 +478,28 @@ ynh_get_scalable_phpfpm () { 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" + ynh_print_warn --message="Footprint=${footprint}Mb by pool." + ynh_print_warn --message="Process manager=$php_pm" + ynh_print_warn --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 ))" + ynh_print_warn --message="\nMax estimated footprint=$(( $php_max_children * $footprint ))" + ynh_print_warn --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 ))" + ynh_print_warn --message="Estimated average footprint=$(( $php_max_spare_servers * $footprint ))" elif [ "$php_pm" = "static" ] then - ynh_debug --message="Estimated footprint=$(( $php_max_children * $footprint ))" + ynh_print_warn --message="Estimated footprint=$(( $php_max_children * $footprint ))" fi - ynh_debug --message="\nRaw php-fpm values:" - ynh_debug --message="pm.max_children = $php_max_children" + ynh_print_warn --message="\nRaw php-fpm values:" + ynh_print_warn --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" + ynh_print_warn --message="pm.start_servers = $php_start_servers" + ynh_print_warn --message="pm.min_spare_servers = $php_min_spare_servers" + ynh_print_warn --message="pm.max_spare_servers = $php_max_spare_servers" fi fi } From a3c0ca4da69525eee6045c63b3dfaecbf9f4f032 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 12 Oct 2021 15:49:46 +0200 Subject: [PATCH 0812/1155] helpers: Don't use eval in ynh_exec_* helpers to prevent issues with special chars --- data/helpers.d/logging | 85 ++++++++++++++++------- tests/test_helpers.d/ynhtest_logging.sh | 92 +++++++++++++++++++++++++ 2 files changed, 152 insertions(+), 25 deletions(-) create mode 100644 tests/test_helpers.d/ynhtest_logging.sh diff --git a/data/helpers.d/logging b/data/helpers.d/logging index 9075fc7aa..b8deef26e 100644 --- a/data/helpers.d/logging +++ b/data/helpers.d/logging @@ -83,72 +83,107 @@ ynh_print_err () { # Execute a command and print the result as an error # -# usage: ynh_exec_err "your_command [ | other_command ]" +# usage: ynh_exec_err your command and args # | 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. +# Note that you should NOT quote the command but only prefix it with ynh_exec_err # # Requires YunoHost version 3.2.0 or higher. ynh_exec_err () { - ynh_print_err "$(eval $@)" + # Boring legacy handling for when people calls ynh_exec_* wrapping the command in quotes, + # (because in the past eval was used) ... + # we detect this by checking that there's no 2nd arg, and $1 contains a space + if [[ "$#" -eq 1 ]] && [[ "$1" == *" "* ]] + then + ynh_print_err "$(eval $@)" + else + # Note that "$@" is used and not $@, c.f. https://unix.stackexchange.com/a/129077 + ynh_print_err "$("$@")" + fi } # Execute a command and print the result as a warning # -# usage: ynh_exec_warn "your_command [ | other_command ]" +# usage: ynh_exec_warn your command and args # | 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. +# Note that you should NOT quote the command but only prefix it with ynh_exec_warn # # Requires YunoHost version 3.2.0 or higher. ynh_exec_warn () { - ynh_print_warn "$(eval $@)" + # Boring legacy handling for when people calls ynh_exec_* wrapping the command in quotes, + # (because in the past eval was used) ... + # we detect this by checking that there's no 2nd arg, and $1 contains a space + if [[ "$#" -eq 1 ]] && [[ "$1" == *" "* ]] + then + ynh_print_warn "$(eval $@)" + else + # Note that "$@" is used and not $@, c.f. https://unix.stackexchange.com/a/129077 + ynh_print_warn "$("$@")" + fi } # Execute a command and force the result to be printed on stdout # -# usage: ynh_exec_warn_less "your_command [ | other_command ]" +# usage: ynh_exec_warn_less your command and args # | 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. +# Note that you should NOT quote the command but only prefix it with ynh_exec_warn # # Requires YunoHost version 3.2.0 or higher. ynh_exec_warn_less () { - eval $@ 2>&1 + # Boring legacy handling for when people calls ynh_exec_* wrapping the command in quotes, + # (because in the past eval was used) ... + # we detect this by checking that there's no 2nd arg, and $1 contains a space + if [[ "$#" -eq 1 ]] && [[ "$1" == *" "* ]] + then + eval $@ 2>&1 + else + # Note that "$@" is used and not $@, c.f. https://unix.stackexchange.com/a/129077 + "$@" 2>&1 + fi } # Execute a command and redirect stdout in /dev/null # -# usage: ynh_exec_quiet "your_command [ | other_command ]" +# usage: ynh_exec_quiet your command and args # | 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. +# Note that you should NOT quote the command but only prefix it with ynh_exec_warn # # Requires YunoHost version 3.2.0 or higher. ynh_exec_quiet () { - eval $@ > /dev/null + # Boring legacy handling for when people calls ynh_exec_* wrapping the command in quotes, + # (because in the past eval was used) ... + # we detect this by checking that there's no 2nd arg, and $1 contains a space + if [[ "$#" -eq 1 ]] && [[ "$1" == *" "* ]] + then + eval $@ > /dev/null + else + # Note that "$@" is used and not $@, c.f. https://unix.stackexchange.com/a/129077 + "$@" > /dev/null + fi } # Execute a command and redirect stdout and stderr in /dev/null # -# usage: ynh_exec_fully_quiet "your_command [ | other_command ]" +# usage: ynh_exec_quiet your command and args # | 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. +# Note that you should NOT quote the command but only prefix it with ynh_exec_quiet # # Requires YunoHost version 3.2.0 or higher. ynh_exec_fully_quiet () { - eval $@ > /dev/null 2>&1 + # Boring legacy handling for when people calls ynh_exec_* wrapping the command in quotes, + # (because in the past eval was used) ... + # we detect this by checking that there's no 2nd arg, and $1 contains a space + if [[ "$#" -eq 1 ]] && [[ "$1" == *" "* ]] + then + eval $@ > /dev/null 2>&1 + else + # Note that "$@" is used and not $@, c.f. https://unix.stackexchange.com/a/129077 + "$@" > /dev/null 2>&1 + fi } # Remove any logs for all the following commands. diff --git a/tests/test_helpers.d/ynhtest_logging.sh b/tests/test_helpers.d/ynhtest_logging.sh new file mode 100644 index 000000000..bb1241614 --- /dev/null +++ b/tests/test_helpers.d/ynhtest_logging.sh @@ -0,0 +1,92 @@ +ynhtest_exec_warn_less() { + + FOO='foo' + bar="" + BAR='$bar' + FOOBAR="foo bar" + + # These looks like stupid edge case + # but in fact happens when dealing with passwords + # (which could also contain bash chars like [], {}, ...) + # or urls containing &, ... + FOOANDBAR="foo&bar" + FOO1QUOTEBAR="foo'bar" + FOO2QUOTEBAR="foo\"bar" + + ynh_exec_warn_less uptime + + test ! -e $FOO + ynh_exec_warn_less touch $FOO + test -e $FOO + rm $FOO + + test ! -e $FOO1QUOTEBAR + ynh_exec_warn_less touch $FOO1QUOTEBAR + test -e $FOO1QUOTEBAR + rm $FOO1QUOTEBAR + + test ! -e $FOO2QUOTEBAR + ynh_exec_warn_less touch $FOO2QUOTEBAR + test -e $FOO2QUOTEBAR + rm $FOO2QUOTEBAR + + test ! -e $BAR + ynh_exec_warn_less touch $BAR + test -e $BAR + rm $BAR + + test ! -e "$FOOBAR" + ynh_exec_warn_less touch "$FOOBAR" + test -e "$FOOBAR" + rm "$FOOBAR" + + test ! -e "$FOOANDBAR" + ynh_exec_warn_less touch $FOOANDBAR + test -e "$FOOANDBAR" + rm "$FOOANDBAR" + + ########################### + # Legacy stuff using eval # + ########################### + + test ! -e $FOO + ynh_exec_warn_less "touch $FOO" + test -e $FOO + rm $FOO + + test ! -e $FOO1QUOTEBAR + ynh_exec_warn_less "touch \"$FOO1QUOTEBAR\"" + # (this works but expliciy *double* quotes have to be provided) + test -e $FOO1QUOTEBAR + rm $FOO1QUOTEBAR + + #test ! -e $FOO2QUOTEBAR + #ynh_exec_warn_less "touch \'$FOO2QUOTEBAR\'" + ## (this doesn't work with simple or double quotes) + #test -e $FOO2QUOTEBAR + #rm $FOO2QUOTEBAR + + test ! -e $BAR + ynh_exec_warn_less 'touch $BAR' + # That one works because $BAR is only interpreted during eval + test -e $BAR + rm $BAR + + #test ! -e $BAR + #ynh_exec_warn_less "touch $BAR" + # That one doesn't work because $bar gets interpreted as empty var by eval... + #test -e $BAR + #rm $BAR + + test ! -e "$FOOBAR" + ynh_exec_warn_less "touch \"$FOOBAR\"" + # (works but requires explicit double quotes otherwise eval would interpret 'foo bar' as two separate args..) + test -e "$FOOBAR" + rm "$FOOBAR" + + test ! -e "$FOOANDBAR" + ynh_exec_warn_less "touch \"$FOOANDBAR\"" + # (works but requires explicit double quotes otherwise eval would interpret '&' as a "run command in background" and also bar is not a valid command) + test -e "$FOOANDBAR" + rm "$FOOANDBAR" +} From fe959bd7faee506c4691ac78813256f1e2ed1e68 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 12 Oct 2021 16:53:01 +0200 Subject: [PATCH 0813/1155] helpers: Flag ynh_print_ON/OFF as internal to not advertise them in the doc --- data/helpers.d/logging | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data/helpers.d/logging b/data/helpers.d/logging index 7a8be30e9..72403e7e1 100644 --- a/data/helpers.d/logging +++ b/data/helpers.d/logging @@ -174,6 +174,8 @@ ynh_exec_fully_quiet() { # # usage: ynh_print_OFF # +# [internal] +# # WARNING: You should be careful with this helper, and never forget to use ynh_print_ON as soon as possible to restore the logging. # # Requires YunoHost version 3.2.0 or higher. @@ -185,6 +187,8 @@ ynh_print_OFF() { # # usage: ynh_print_ON # +# [internal] +# # Requires YunoHost version 3.2.0 or higher. ynh_print_ON() { exec {BASH_XTRACEFD}>&1 From 55bacd7405b381f21557ae9ef7a5f3cfa4aa2685 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 13 Oct 2021 15:37:39 +0200 Subject: [PATCH 0814/1155] dyndns cron: validate that we're connected to the internet before triggering yunohost dyndns update --- data/hooks/conf_regen/01-yunohost | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index 341efce9e..34679af07 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -105,7 +105,12 @@ EOF if ls -l /etc/yunohost/dyndns/K*.private 2>/dev/null; then cat >$pending_dir/etc/cron.d/yunohost-dyndns <> /dev/null +# Every 10 minutes, +# - (sleep random 60 is here to spread requests over a 1-min window) +# - if ip.yunohost.org answers ping (basic check to validate that we're connected to the internet and yunohost infra aint down) +# - and if lock ain't already taken by another command +# - trigger yunohost dyndns update +*/10 * * * * root : YunoHost DynDNS update; sleep \$((RANDOM\\%60)); ! ping -q -W5 -c1 ip.yunohost.org >/dev/null 2>&1 || test -e /var/run/moulinette_yunohost.lock || yunohost dyndns update >> /dev/null EOF fi From cb835a2d6ba73965b26cdbf019faa6cc063c1e85 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 13 Oct 2021 15:38:57 +0200 Subject: [PATCH 0815/1155] dyndns: Delete dyndns cron in regenconf if no dyndns domain found --- data/hooks/conf_regen/01-yunohost | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index 34679af07..ad10fa863 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -112,6 +112,9 @@ SHELL=/bin/bash # - trigger yunohost dyndns update */10 * * * * root : YunoHost DynDNS update; sleep \$((RANDOM\\%60)); ! ping -q -W5 -c1 ip.yunohost.org >/dev/null 2>&1 || test -e /var/run/moulinette_yunohost.lock || yunohost dyndns update >> /dev/null EOF + else + # (Delete cron if no dyndns domain found) + touch $pending_dir/etc/cron.d/yunohost-dyndns fi # legacy stuff to avoid yunohost reporting etckeeper as manually modified From 8dbc93c674d3b5c421a458c47074b2ed1d5874e4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 13 Oct 2021 15:45:07 +0200 Subject: [PATCH 0816/1155] Update changelog for 4.3.1.4 --- debian/changelog | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/debian/changelog b/debian/changelog index def37d6b2..31ebc1824 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,16 @@ +yunohost (4.3.1.4) testing; urgency=low + + - [mod] codequality: Safer, clearer ynh_secure_remove ([#1357](https://github.com/YunoHost/yunohost/pull/1357)) + - [mod] codequality: Lint/autoformat helpers, hooks and debian scripts ([#1356](https://github.com/YunoHost/yunohost/pull/1356)) + - [mod] helpers: Flag ynh_print_ON/OFF as internal to not advertise them in the doc (fe959bd7) + - [fix] helpers: Eval mecanism in ynh_exec_* lead to epic bugs ([#1358](https://github.com/YunoHost/yunohost/pull/1358)) + - [enh] dyndns: validate that we're connected to the internet before triggering yunohost dyndns update (55bacd74) + - [enh] regenconf/dyndns: Delete dyndns cron in regenconf if no dyndns domain found (cb835a2d) + - [fix] regenconf/dovecot: add conf snippet to get rid of stupid stats-writer errors in mail.log (dab3dc6f) + - [enh] regenconf/dnsmasq: Don't generate dnsmasq conf for .local domains (df02f898) + + -- Alexandre Aubin Wed, 13 Oct 2021 15:41:21 +0200 + yunohost (4.3.1.3) testing; urgency=low - [fix] app: repo url branch names may contain dots (38cff4a9) From 9f7fb61b508a46112f020e23263ec3b156a04f82 Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 13 Oct 2021 19:28:15 +0200 Subject: [PATCH 0817/1155] [enh] config panel python method hook --- src/yunohost/app.py | 19 ++-- src/yunohost/domain.py | 11 +-- src/yunohost/utils/config.py | 173 +++++++++++++++++++++++++++-------- 3 files changed, 143 insertions(+), 60 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index fb544cab2..8d441c00d 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1608,14 +1608,9 @@ def app_config_set( 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) + entity_type = "app" + save_path_tpl = os.path.join(APPS_SETTING_PATH, "{entity}/settings.yml") + config_path_tpl = os.path.join(APPS_SETTING_PATH, "{entity}/config_panel.yml") def _load_current_values(self): self.values = self._call_config_script("show") @@ -1639,7 +1634,7 @@ class AppConfigPanel(ConfigPanel): from yunohost.hook import hook_exec # Add default config script if needed - config_script = os.path.join(APPS_SETTING_PATH, self.app, "scripts", "config") + config_script = os.path.join(APPS_SETTING_PATH, self.entity, "scripts", "config") if not os.path.exists(config_script): logger.debug("Adding a default config script") default_script = """#!/bin/bash @@ -1651,15 +1646,15 @@ 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) + app_id, app_instance_nb = _parse_app_instance_name(self.entity) settings = _get_app_settings(app_id) env.update( { "app_id": app_id, - "app": self.app, + "app": self.entity, "app_instance_nb": str(app_instance_nb), "final_path": settings.get("final_path", ""), - "YNH_APP_BASEDIR": os.path.join(APPS_SETTING_PATH, self.app), + "YNH_APP_BASEDIR": os.path.join(APPS_SETTING_PATH, self.entity), } ) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index b40831d25..b353badb8 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -452,14 +452,9 @@ def domain_config_set( 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", - ) + entity_type = "domain" + save_path_tpl = f"{DOMAIN_SETTINGS_PATH}/{entity}.yml") + save_mode = "diff" def _get_toml(self): from yunohost.dns import _get_registrar_config_section diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 4ee62c6f7..880cd09f3 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -19,6 +19,7 @@ """ +import glob import os import re import urllib.parse @@ -27,7 +28,7 @@ import shutil import ast import operator as op from collections import OrderedDict -from typing import Optional, Dict, List, Union, Any, Mapping +from typing import Optional, Dict, List, Union, Any, Mapping, Callable from moulinette.interfaces.cli import colorize from moulinette import Moulinette, m18n @@ -189,13 +190,45 @@ def evaluate_simple_js_expression(expr, context={}): class ConfigPanel: - def __init__(self, config_path, save_path=None): + entity_type = "config" + save_path_tpl = None + config_path_tpl = "/usr/share/yunohost/other/config_{entity}.toml" + + @classmethod + def list(cls): + """ + List available config panel + """ + try: + entities = [re.match("^" + cls.save_path_tpl.format(entity="(?p)") + "$", f).group('entity') + for f in glob.glob(cls.save_path_tpl.format(entity="*")) + if os.path.isfile(f)] + except FileNotFoundError: + entities = [] + return entities + + def __init__(self, entity, config_path=None, save_path=None, creation=False): + self.entity = entity self.config_path = config_path + if not config_path: + self.config_path = self.config_path_tpl.format(entity=entity) self.save_path = save_path + if not save_path and self.save_path_tpl: + self.save_path = self.save_path_tpl.format(entity=entity) self.config = {} self.values = {} self.new_values = {} + if self.save_path and not creation and not os.path.exists(self.save_path): + raise YunohostError(f"{self.entity_type}_doesnt_exists", name=entity) + if self.save_path and creation and os.path.exists(self.save_path): + raise YunohostError(f"{self.entity_type}_already_exists", name=entity) + + # Search for hooks in the config panel + self.hooks = {func: getattr(self, func) + for func in dir(self) + if callable(getattr(self, func)) and re.match("^(validate|post_ask)__", func)} + def get(self, key="", mode="classic"): self.filter_key = key or "" @@ -274,19 +307,12 @@ class ConfigPanel: # 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} + self._parse_pre_answered(args, value, args_file) # Read or get values and hydrate the config self._load_current_values() self._hydrate() + Question.operation_logger = operation_logger self._ask() if operation_logger: @@ -525,7 +551,12 @@ class ConfigPanel: display_header(f"\n# {name}") # Check and ask unanswered questions - questions = ask_questions_and_parse_answers(section["options"], self.args) + questions = ask_questions_and_parse_answers( + section["options"], + prefilled_answers=self.args, + current_values=self.values, + hooks=self.hooks + ) self.new_values.update( { question.name: question.value @@ -543,20 +574,42 @@ class ConfigPanel: if "default" in option } + @property + def future_values(self): # TODO put this in ConfigPanel ? + return {**self.values, **self.new_values} + + def __getattr__(self, name): + if "new_values" in self.__dict__ and name in self.new_values: + return self.new_values[name] + + if "values" in self.__dict__ and name in self.values: + return self.values[name] + + return self.__dict__[name] + 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) + + # Retrieve entries in the YAML + if os.path.exists(self.save_path) and os.path.isfile(self.save_path): + self.values.update(read_yaml(self.save_path) or {}) + + def _parse_pre_answered(self, args, value, args_file): + 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} def _apply(self): logger.info("Saving the new configuration...") @@ -564,7 +617,7 @@ class ConfigPanel: if not os.path.exists(dir_path): mkdir(dir_path, mode=0o700) - values_to_save = {**self.values, **self.new_values} + values_to_save = self.future_values if self.save_mode == "diff": defaults = self._get_default_values() values_to_save = { @@ -587,8 +640,8 @@ class ConfigPanel: if services_to_reload: logger.info("Reloading services...") for service in services_to_reload: - if hasattr(self, "app"): - service = service.replace("__APP__", self.app) + if hasattr(self, "entity"): + service = service.replace("__APP__", self.entity) service_reload_or_restart(service) def _iterate(self, trigger=["option"]): @@ -607,13 +660,16 @@ class Question(object): hide_user_input_in_prompt = False pattern: Optional[Dict] = None - def __init__(self, question: Dict[str, Any], context: Mapping[str, Any] = {}): + def __init__(self, question: Dict[str, Any], + context: Mapping[str, Any] = {}, + hooks: Dict[str, Callable] = {}): self.name = question["name"] + self.context = context + self.hooks = hooks self.type = question.get("type", "string") self.default = question.get("default", None) self.optional = question.get("optional", False) self.visible = question.get("visible", None) - self.context = context self.choices = question.get("choices", []) self.pattern = question.get("pattern", self.pattern) self.ask = question.get("ask", {"en": self.name}) @@ -623,6 +679,8 @@ class Question(object): self.current_value = question.get("current_value") # .value is the "proposed" value which we got from the user self.value = question.get("value") + # Use to return several values in case answer is in mutipart + self.values = {} # Empty value is parsed as empty string if self.default == "": @@ -663,8 +721,8 @@ class Question(object): # - we doesn't want to give a specific value # - we want to keep the previous value # - we want the default value - self.value = None - return self.value + self.value = self.values[self.name] = None + return self.values for i in range(5): # Display question if no value filled or if it's a readonly message @@ -698,9 +756,18 @@ class Question(object): break - self.value = self._post_parse_value() + self.value = self.values[self.name] = self._post_parse_value() +<<<<<<< HEAD return self.value +======= + # Search for post actions in hooks + post_hook = f"post_ask__{self.name}" + if post_hook in self.hooks: + self.values.update(self.hooks[post_hook](self)) + + return self.values +>>>>>>> c0cd8dbf... [enh] config panel python method hook def _prevalidate(self): if self.value in [None, ""] and not self.optional: @@ -864,8 +931,10 @@ class PasswordQuestion(Question): default_value = "" forbidden_chars = "{}" - def __init__(self, question, context: Mapping[str, Any] = {}): - super().__init__(question, context) + def __init__(self, question, + context: Mapping[str, Any] = {}, + hooks: Dict[str, Callable] = {}): + super().__init__(question, context, hooks) self.redact = True if self.default is not None: raise YunohostValidationError( @@ -983,8 +1052,9 @@ class BooleanQuestion(Question): choices="yes/no", ) - def __init__(self, question, context: Mapping[str, Any] = {}): - super().__init__(question, context) + def __init__(self, question, context: Mapping[str, Any] = {}, + hooks: Dict[str, Callable] = {}): + super().__init__(question, context, hooks) self.yes = question.get("yes", 1) self.no = question.get("no", 0) if self.default is None: @@ -1004,10 +1074,11 @@ class BooleanQuestion(Question): class DomainQuestion(Question): argument_type = "domain" - def __init__(self, question, context: Mapping[str, Any] = {}): + def __init__(self, question, context: Mapping[str, Any] = {}, + hooks: Dict[str, Callable] = {}): from yunohost.domain import domain_list, _get_maindomain - super().__init__(question, context) + super().__init__(question, context, hooks) if self.default is None: self.default = _get_maindomain() @@ -1030,11 +1101,12 @@ class DomainQuestion(Question): class UserQuestion(Question): argument_type = "user" - def __init__(self, question, context: Mapping[str, Any] = {}): + def __init__(self, question, context: Mapping[str, Any] = {}, + hooks: Dict[str, Callable] = {}): from yunohost.user import user_list, user_info from yunohost.domain import _get_maindomain - super().__init__(question, context) + super().__init__(question, context, hooks) self.choices = list(user_list()["users"].keys()) if not self.choices: @@ -1056,8 +1128,9 @@ class NumberQuestion(Question): argument_type = "number" default_value = None - def __init__(self, question, context: Mapping[str, Any] = {}): - super().__init__(question, context) + def __init__(self, question, context: Mapping[str, Any] = {}, + hooks: Dict[str, Callable] = {}): + super().__init__(question, context, hooks) self.min = question.get("min", None) self.max = question.get("max", None) self.step = question.get("step", None) @@ -1108,8 +1181,9 @@ class DisplayTextQuestion(Question): argument_type = "display_text" readonly = True - def __init__(self, question, context: Mapping[str, Any] = {}): - super().__init__(question, context) + def __init__(self, question, context: Mapping[str, Any] = {}, + hooks: Dict[str, Callable] = {}): + super().__init__(question, context, hooks) self.optional = True self.style = question.get( @@ -1143,8 +1217,9 @@ class FileQuestion(Question): if os.path.exists(upload_dir): shutil.rmtree(upload_dir) - def __init__(self, question, context: Mapping[str, Any] = {}): - super().__init__(question, context) + def __init__(self, question, context: Mapping[str, Any] = {}, + hooks: Dict[str, Callable] = {}): + super().__init__(question, context, hooks) self.accept = question.get("accept", "") def _prevalidate(self): @@ -1214,7 +1289,14 @@ ARGUMENTS_TYPE_PARSERS = { def ask_questions_and_parse_answers( +<<<<<<< HEAD raw_questions: Dict, prefilled_answers: Union[str, Mapping[str, Any]] = {} +======= + raw_questions: Dict, + prefilled_answers: Union[str, Mapping[str, Any]] = {}, + current_values: Union[str, Mapping[str, Any]] = {}, + hooks: Dict[str, Callable[[], None]] = {} +>>>>>>> c0cd8dbf... [enh] config panel python method hook ) -> 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. @@ -1241,13 +1323,24 @@ def ask_questions_and_parse_answers( else: answers = {} +<<<<<<< HEAD +======= + context = {**current_values, **answers} +>>>>>>> c0cd8dbf... [enh] config panel python method hook out = [] for raw_question in raw_questions: question_class = ARGUMENTS_TYPE_PARSERS[raw_question.get("type", "string")] raw_question["value"] = answers.get(raw_question["name"]) +<<<<<<< HEAD question = question_class(raw_question, context=answers) answers[question.name] = question.ask_if_needed() +======= + question = question_class(raw_question, context=context, hooks=hooks) + new_values = question.ask_if_needed() + answers.update(new_values) + context.update(new_values) +>>>>>>> c0cd8dbf... [enh] config panel python method hook out.append(question) return out From ca62c01d69fee199db02abb99e07ffe1a9620419 Mon Sep 17 00:00:00 2001 From: ljf Date: Wed, 13 Oct 2021 19:33:41 +0200 Subject: [PATCH 0818/1155] [fix] Bad conflict resolution --- src/yunohost/utils/config.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 880cd09f3..b19cecf3b 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -758,16 +758,12 @@ class Question(object): self.value = self.values[self.name] = self._post_parse_value() -<<<<<<< HEAD - return self.value -======= # Search for post actions in hooks post_hook = f"post_ask__{self.name}" if post_hook in self.hooks: self.values.update(self.hooks[post_hook](self)) return self.values ->>>>>>> c0cd8dbf... [enh] config panel python method hook def _prevalidate(self): if self.value in [None, ""] and not self.optional: @@ -1289,14 +1285,10 @@ ARGUMENTS_TYPE_PARSERS = { def ask_questions_and_parse_answers( -<<<<<<< HEAD - raw_questions: Dict, prefilled_answers: Union[str, Mapping[str, Any]] = {} -======= raw_questions: Dict, prefilled_answers: Union[str, Mapping[str, Any]] = {}, current_values: Union[str, Mapping[str, Any]] = {}, hooks: Dict[str, Callable[[], None]] = {} ->>>>>>> c0cd8dbf... [enh] config panel python method hook ) -> 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. @@ -1323,24 +1315,16 @@ def ask_questions_and_parse_answers( else: answers = {} -<<<<<<< HEAD -======= context = {**current_values, **answers} ->>>>>>> c0cd8dbf... [enh] config panel python method hook out = [] for raw_question in raw_questions: question_class = ARGUMENTS_TYPE_PARSERS[raw_question.get("type", "string")] raw_question["value"] = answers.get(raw_question["name"]) -<<<<<<< HEAD - question = question_class(raw_question, context=answers) - answers[question.name] = question.ask_if_needed() -======= question = question_class(raw_question, context=context, hooks=hooks) new_values = question.ask_if_needed() answers.update(new_values) context.update(new_values) ->>>>>>> c0cd8dbf... [enh] config panel python method hook out.append(question) return out From a9d736a0aebef577a95483b45831c8206280506e Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Wed, 13 Oct 2021 19:54:40 +0200 Subject: [PATCH 0819/1155] [fix] save_path_tpl in DomainConfigPanel --- 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 b353badb8..5ebeded13 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -453,7 +453,7 @@ def domain_config_set( class DomainConfigPanel(ConfigPanel): entity_type = "domain" - save_path_tpl = f"{DOMAIN_SETTINGS_PATH}/{entity}.yml") + save_path_tpl = "{DOMAIN_SETTINGS_PATH}/{entity}.yml" save_mode = "diff" def _get_toml(self): From 59afe6988abd34772096bfe6e37aa22456b6e96d Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Thu, 14 Oct 2021 00:35:19 +0200 Subject: [PATCH 0820/1155] [fix] Bad const in domain config panel --- 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 5ebeded13..1397df2ff 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -453,7 +453,7 @@ def domain_config_set( class DomainConfigPanel(ConfigPanel): entity_type = "domain" - save_path_tpl = "{DOMAIN_SETTINGS_PATH}/{entity}.yml" + save_path_tpl = "{DOMAIN_SETTINGS_DIR}/{entity}.yml" save_mode = "diff" def _get_toml(self): From 6fb1c636fe6c88936d158901aab3192844250e5e Mon Sep 17 00:00:00 2001 From: ljf Date: Thu, 14 Oct 2021 10:58:56 +0200 Subject: [PATCH 0821/1155] [fix] DomainConfigPanel save path --- 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 1397df2ff..4729b0a1d 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -453,7 +453,7 @@ def domain_config_set( class DomainConfigPanel(ConfigPanel): entity_type = "domain" - save_path_tpl = "{DOMAIN_SETTINGS_DIR}/{entity}.yml" + save_path_tpl = f"{DOMAIN_SETTINGS_DIR}/{{entity}}.yml" save_mode = "diff" def _get_toml(self): From c5064775513e098aca7b01d28e3eae0cefeee2a4 Mon Sep 17 00:00:00 2001 From: ljf Date: Thu, 14 Oct 2021 12:03:26 +0200 Subject: [PATCH 0822/1155] [fix] i18n keys --- locales/en.json | 2 +- src/yunohost/domain.py | 2 +- src/yunohost/tests/test_user-group.py | 2 +- src/yunohost/utils/config.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/locales/en.json b/locales/en.json index 53b0a7e83..2c9652b9c 100644 --- a/locales/en.json +++ b/locales/en.json @@ -312,7 +312,7 @@ "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_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_registrar_is_not_configured": "The registrar is not yet configured for domain {domain}.", diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 4729b0a1d..acb0f0168 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -102,7 +102,7 @@ def domain_list(exclude_subdomains=False): def _assert_domain_exists(domain): if domain not in domain_list()["domains"]: - raise YunohostValidationError("domain_name_unknown", domain=domain) + raise YunohostValidationError("domain_unknown", domain=domain) def _list_subdomains_of(parent_domain): diff --git a/src/yunohost/tests/test_user-group.py b/src/yunohost/tests/test_user-group.py index 60e748108..d65366a9a 100644 --- a/src/yunohost/tests/test_user-group.py +++ b/src/yunohost/tests/test_user-group.py @@ -221,7 +221,7 @@ def test_create_user_already_exists(mocker): def test_create_user_with_domain_that_doesnt_exists(mocker): - with raiseYunohostError(mocker, "domain_name_unknown"): + with raiseYunohostError(mocker, "domain_unknown"): user_create("alice", "Alice", "White", "doesnt.exists", "test123Ynh") diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index b19cecf3b..faa2821d1 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -220,9 +220,9 @@ class ConfigPanel: self.new_values = {} if self.save_path and not creation and not os.path.exists(self.save_path): - raise YunohostError(f"{self.entity_type}_doesnt_exists", name=entity) + raise YunohostError(f"{self.entity_type}_unknown", name=entity) if self.save_path and creation and os.path.exists(self.save_path): - raise YunohostError(f"{self.entity_type}_already_exists", name=entity) + raise YunohostError(f"{self.entity_type}_exists", name=entity) # Search for hooks in the config panel self.hooks = {func: getattr(self, func) From 4532c75f2931776ad648d05517f89a2f6ed0d81e Mon Sep 17 00:00:00 2001 From: ljf Date: Thu, 14 Oct 2021 12:53:15 +0200 Subject: [PATCH 0823/1155] [fix] args for config panel i18n unknown key --- 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 faa2821d1..c3e08c5c2 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -220,9 +220,9 @@ class ConfigPanel: self.new_values = {} if self.save_path and not creation and not os.path.exists(self.save_path): - raise YunohostError(f"{self.entity_type}_unknown", name=entity) + raise YunohostError(f"{self.entity_type}_unknown", **{self.entity_type:entity}) if self.save_path and creation and os.path.exists(self.save_path): - raise YunohostError(f"{self.entity_type}_exists", name=entity) + raise YunohostError(f"{self.entity_type}_exists", **{self.entity_type:entity}) # Search for hooks in the config panel self.hooks = {func: getattr(self, func) From cc20b36550dbe95985bb57e6afd0181604f48a4e Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Thu, 14 Oct 2021 11:18:02 +0000 Subject: [PATCH 0824/1155] [CI] Format code --- src/yunohost/app.py | 4 +- src/yunohost/utils/config.py | 78 ++++++++++++++++++++++-------------- 2 files changed, 52 insertions(+), 30 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 8d441c00d..6cd8eebf5 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1634,7 +1634,9 @@ class AppConfigPanel(ConfigPanel): from yunohost.hook import hook_exec # Add default config script if needed - config_script = os.path.join(APPS_SETTING_PATH, self.entity, "scripts", "config") + config_script = os.path.join( + APPS_SETTING_PATH, self.entity, "scripts", "config" + ) if not os.path.exists(config_script): logger.debug("Adding a default config script") default_script = """#!/bin/bash diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index c3e08c5c2..d29384bfe 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -200,9 +200,13 @@ class ConfigPanel: List available config panel """ try: - entities = [re.match("^" + cls.save_path_tpl.format(entity="(?p)") + "$", f).group('entity') - for f in glob.glob(cls.save_path_tpl.format(entity="*")) - if os.path.isfile(f)] + entities = [ + re.match( + "^" + cls.save_path_tpl.format(entity="(?p)") + "$", f + ).group("entity") + for f in glob.glob(cls.save_path_tpl.format(entity="*")) + if os.path.isfile(f) + ] except FileNotFoundError: entities = [] return entities @@ -220,14 +224,21 @@ class ConfigPanel: self.new_values = {} if self.save_path and not creation and not os.path.exists(self.save_path): - raise YunohostError(f"{self.entity_type}_unknown", **{self.entity_type:entity}) + raise YunohostError( + f"{self.entity_type}_unknown", **{self.entity_type: entity} + ) if self.save_path and creation and os.path.exists(self.save_path): - raise YunohostError(f"{self.entity_type}_exists", **{self.entity_type:entity}) + raise YunohostError( + f"{self.entity_type}_exists", **{self.entity_type: entity} + ) # Search for hooks in the config panel - self.hooks = {func: getattr(self, func) - for func in dir(self) - if callable(getattr(self, func)) and re.match("^(validate|post_ask)__", func)} + self.hooks = { + func: getattr(self, func) + for func in dir(self) + if callable(getattr(self, func)) + and re.match("^(validate|post_ask)__", func) + } def get(self, key="", mode="classic"): self.filter_key = key or "" @@ -555,7 +566,7 @@ class ConfigPanel: section["options"], prefilled_answers=self.args, current_values=self.values, - hooks=self.hooks + hooks=self.hooks, ) self.new_values.update( { @@ -575,7 +586,7 @@ class ConfigPanel: } @property - def future_values(self): # TODO put this in ConfigPanel ? + def future_values(self): # TODO put this in ConfigPanel ? return {**self.values, **self.new_values} def __getattr__(self, name): @@ -660,9 +671,12 @@ class Question(object): hide_user_input_in_prompt = False pattern: Optional[Dict] = None - def __init__(self, question: Dict[str, Any], - context: Mapping[str, Any] = {}, - hooks: Dict[str, Callable] = {}): + def __init__( + self, + question: Dict[str, Any], + context: Mapping[str, Any] = {}, + hooks: Dict[str, Callable] = {}, + ): self.name = question["name"] self.context = context self.hooks = hooks @@ -927,9 +941,9 @@ class PasswordQuestion(Question): default_value = "" forbidden_chars = "{}" - def __init__(self, question, - context: Mapping[str, Any] = {}, - hooks: Dict[str, Callable] = {}): + def __init__( + self, question, context: Mapping[str, Any] = {}, hooks: Dict[str, Callable] = {} + ): super().__init__(question, context, hooks) self.redact = True if self.default is not None: @@ -1048,8 +1062,9 @@ class BooleanQuestion(Question): choices="yes/no", ) - def __init__(self, question, context: Mapping[str, Any] = {}, - hooks: Dict[str, Callable] = {}): + def __init__( + self, question, context: Mapping[str, Any] = {}, hooks: Dict[str, Callable] = {} + ): super().__init__(question, context, hooks) self.yes = question.get("yes", 1) self.no = question.get("no", 0) @@ -1070,8 +1085,9 @@ class BooleanQuestion(Question): class DomainQuestion(Question): argument_type = "domain" - def __init__(self, question, context: Mapping[str, Any] = {}, - hooks: Dict[str, Callable] = {}): + def __init__( + self, question, context: Mapping[str, Any] = {}, hooks: Dict[str, Callable] = {} + ): from yunohost.domain import domain_list, _get_maindomain super().__init__(question, context, hooks) @@ -1097,8 +1113,9 @@ class DomainQuestion(Question): class UserQuestion(Question): argument_type = "user" - def __init__(self, question, context: Mapping[str, Any] = {}, - hooks: Dict[str, Callable] = {}): + def __init__( + self, question, context: Mapping[str, Any] = {}, hooks: Dict[str, Callable] = {} + ): from yunohost.user import user_list, user_info from yunohost.domain import _get_maindomain @@ -1124,8 +1141,9 @@ class NumberQuestion(Question): argument_type = "number" default_value = None - def __init__(self, question, context: Mapping[str, Any] = {}, - hooks: Dict[str, Callable] = {}): + def __init__( + self, question, context: Mapping[str, Any] = {}, hooks: Dict[str, Callable] = {} + ): super().__init__(question, context, hooks) self.min = question.get("min", None) self.max = question.get("max", None) @@ -1177,8 +1195,9 @@ class DisplayTextQuestion(Question): argument_type = "display_text" readonly = True - def __init__(self, question, context: Mapping[str, Any] = {}, - hooks: Dict[str, Callable] = {}): + def __init__( + self, question, context: Mapping[str, Any] = {}, hooks: Dict[str, Callable] = {} + ): super().__init__(question, context, hooks) self.optional = True @@ -1213,8 +1232,9 @@ class FileQuestion(Question): if os.path.exists(upload_dir): shutil.rmtree(upload_dir) - def __init__(self, question, context: Mapping[str, Any] = {}, - hooks: Dict[str, Callable] = {}): + def __init__( + self, question, context: Mapping[str, Any] = {}, hooks: Dict[str, Callable] = {} + ): super().__init__(question, context, hooks) self.accept = question.get("accept", "") @@ -1288,7 +1308,7 @@ def ask_questions_and_parse_answers( raw_questions: Dict, prefilled_answers: Union[str, Mapping[str, Any]] = {}, current_values: Union[str, Mapping[str, Any]] = {}, - hooks: Dict[str, Callable[[], None]] = {} + hooks: Dict[str, Callable[[], None]] = {}, ) -> 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. From 2f0bf90cda22b8b9d9b51cc594dda09bb85ed141 Mon Sep 17 00:00:00 2001 From: ljf Date: Thu, 14 Oct 2021 17:23:51 +0200 Subject: [PATCH 0825/1155] [fix] App Config Panel path --- src/yunohost/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 8d441c00d..ed7840835 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1610,7 +1610,7 @@ def app_config_set( class AppConfigPanel(ConfigPanel): entity_type = "app" save_path_tpl = os.path.join(APPS_SETTING_PATH, "{entity}/settings.yml") - config_path_tpl = os.path.join(APPS_SETTING_PATH, "{entity}/config_panel.yml") + config_path_tpl = os.path.join(APPS_SETTING_PATH, "{entity}/config_panel.toml") def _load_current_values(self): self.values = self._call_config_script("show") From 5744e8eb169d2373705075ee886431cf79430e5b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 14 Oct 2021 22:14:58 +0200 Subject: [PATCH 0826/1155] Moar fiks test --- 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 d29384bfe..f04561aec 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -224,11 +224,11 @@ class ConfigPanel: self.new_values = {} if self.save_path and not creation and not os.path.exists(self.save_path): - raise YunohostError( + raise YunohostValidationError( f"{self.entity_type}_unknown", **{self.entity_type: entity} ) if self.save_path and creation and os.path.exists(self.save_path): - raise YunohostError( + raise YunohostValidationError( f"{self.entity_type}_exists", **{self.entity_type: entity} ) From 3dd7aa9b59f6f31ec8fcd85263441270cc1de0e1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 14 Oct 2021 22:17:57 +0200 Subject: [PATCH 0827/1155] ci: Remove format-check, only keep format-run, renamed to black --- .gitlab/ci/lint.gitlab-ci.yml | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/.gitlab/ci/lint.gitlab-ci.yml b/.gitlab/ci/lint.gitlab-ci.yml index aaddb5a0a..12ddebf13 100644 --- a/.gitlab/ci/lint.gitlab-ci.yml +++ b/.gitlab/ci/lint.gitlab-ci.yml @@ -26,15 +26,7 @@ mypy: script: - tox -e py37-mypy -format-check: - stage: lint - image: "before-install" - allow_failure: true - needs: [] - script: - - tox -e py37-black-check - -format-run: +black: stage: lint image: "before-install" needs: [] @@ -49,9 +41,9 @@ format-run: - 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 commit -am "[CI] Format code with Black" || 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 + - hub pull-request -m "[CI] Format code with Black" -b Yunohost:dev -p || true # GITHUB_USER and GITHUB_TOKEN registered here https://gitlab.com/yunohost/yunohost/-/settings/ci_cd only: refs: - dev From 759b828c8a78c82f02e55f7c95fb64a24e556783 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 15 Oct 2021 16:40:25 +0200 Subject: [PATCH 0828/1155] configpanels: Fill prefilled_answers with answers from previous sections/panels, needed by the visible mecanism --- src/yunohost/utils/config.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index f04561aec..5d380f7af 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -562,9 +562,12 @@ class ConfigPanel: display_header(f"\n# {name}") # Check and ask unanswered questions + prefilled_answers = self.args.copy() + prefilled_answers.update(self.new_values) + questions = ask_questions_and_parse_answers( section["options"], - prefilled_answers=self.args, + prefilled_answers=prefilled_answers, current_values=self.values, hooks=self.hooks, ) From af3d6dd7df13a8749d780c607918def603279be7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 17 Oct 2021 20:28:46 +0200 Subject: [PATCH 0829/1155] Force-disable old avahi-daemon --- data/hooks/conf_regen/37-mdns | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/data/hooks/conf_regen/37-mdns b/data/hooks/conf_regen/37-mdns index 8cb364084..fa1ccbd09 100755 --- a/data/hooks/conf_regen/37-mdns +++ b/data/hooks/conf_regen/37-mdns @@ -44,7 +44,10 @@ do_post_regen() { # 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 + systemctl disable avahi-daemon --now || true + sleep 2 + systemctl enable yunomdns --now + sleep 2 fi [[ -z "$regen_conf_files" ]] \ From 3a07a780689550c225e5cb443fb036e6bd1664fc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 17 Oct 2021 20:37:27 +0200 Subject: [PATCH 0830/1155] In fact we probably always want to disable avahi-daemon on all systems --- data/hooks/conf_regen/37-mdns | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/data/hooks/conf_regen/37-mdns b/data/hooks/conf_regen/37-mdns index fa1ccbd09..52213c297 100755 --- a/data/hooks/conf_regen/37-mdns +++ b/data/hooks/conf_regen/37-mdns @@ -42,10 +42,11 @@ do_post_regen() { systemctl daemon-reload fi + systemctl disable avahi-daemon.socket --now 2>&1|| true + systemctl disable avahi-daemon --now 2>&1 || true + # 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 disable avahi-daemon --now || true - sleep 2 systemctl enable yunomdns --now sleep 2 fi From 3f50247b27ad2bcf36c611a9719243bde0c08c1b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 17 Oct 2021 21:00:52 +0200 Subject: [PATCH 0831/1155] Update changelog for 4.3.1.5 --- debian/changelog | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/debian/changelog b/debian/changelog index 31ebc1824..e35be5cf0 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,13 @@ +yunohost (4.3.1.5) testing; urgency=low + + - [enh] configpanel: Add hook mecanism between questions (9f7fb61b) + - [fix] configpanel: Issue with visible-if context missing between section + - [mod] Force-disable old avahi-daemon (af3d6dd7, 3a07a780) + + Thanks to all contributors <3 ! (ljf) + + -- Alexandre Aubin Sun, 17 Oct 2021 20:44:33 +0200 + yunohost (4.3.1.4) testing; urgency=low - [mod] codequality: Safer, clearer ynh_secure_remove ([#1357](https://github.com/YunoHost/yunohost/pull/1357)) From 07c1ddceb773b0b9586d6c271663e26a0d355a27 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Sun, 17 Oct 2021 23:48:44 +0200 Subject: [PATCH 0832/1155] [fix] Unknown domain --- 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 5d380f7af..f5a7ed828 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -223,7 +223,7 @@ class ConfigPanel: self.values = {} self.new_values = {} - if self.save_path and not creation and not os.path.exists(self.save_path): + if self.save_path and self.save_mode != "diff" and not creation and not os.path.exists(self.save_path): raise YunohostValidationError( f"{self.entity_type}_unknown", **{self.entity_type: entity} ) From 33d973bab4e1ba8c5963898d8390f3b00f2dc0ee Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Sun, 17 Oct 2021 22:03:50 +0000 Subject: [PATCH 0833/1155] [CI] Format code with Black --- src/yunohost/utils/config.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index f5a7ed828..e14b50f6f 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -223,7 +223,12 @@ class ConfigPanel: self.values = {} self.new_values = {} - if self.save_path and self.save_mode != "diff" and not creation and not os.path.exists(self.save_path): + if ( + self.save_path + and self.save_mode != "diff" + and not creation + and not os.path.exists(self.save_path) + ): raise YunohostValidationError( f"{self.entity_type}_unknown", **{self.entity_type: entity} ) From eae826b2a60663db74b040d22174c61e4c39054a Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Mon, 18 Oct 2021 00:14:38 +0200 Subject: [PATCH 0834/1155] [fix] App config panel save mode missing --- src/yunohost/utils/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index e14b50f6f..914612086 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -193,6 +193,7 @@ class ConfigPanel: entity_type = "config" save_path_tpl = None config_path_tpl = "/usr/share/yunohost/other/config_{entity}.toml" + save_mode = "full" @classmethod def list(cls): From ff69067d9c7d23cf1ec3a63225f634ef0c0e6099 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Mon, 18 Oct 2021 00:42:37 +0200 Subject: [PATCH 0835/1155] [fix] Config param should be global --- 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 5999387db..9c7272b85 100644 --- a/data/helpers.d/config +++ b/data/helpers.d/config @@ -193,7 +193,7 @@ _ynh_app_config_validate() { 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]}" + declare -g "$short_setting"="${old[$short_setting]}" continue fi if [ ! -z "${file_hash[${short_setting}]}" ]; then From caee4e297933a2cd38641a11f5f18cf121bfb3f5 Mon Sep 17 00:00:00 2001 From: Semen Turchikhin Date: Tue, 12 Oct 2021 20:33:44 +0000 Subject: [PATCH 0836/1155] Translated using Weblate (Russian) Currently translated at 2.6% (19 of 706 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ru/ --- locales/ru.json | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/locales/ru.json b/locales/ru.json index 5a74524bf..03634e9ff 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -5,17 +5,17 @@ "admin_password_changed": "Пароль администратора был изменен", "app_already_installed": "{app} уже установлено", "app_already_installed_cant_change_url": "Это приложение уже установлено. URL не может быть изменен только с помощью этой функции. Изучите `app changeurl`, если это доступно.", - "app_argument_choice_invalid": "Неверный выбор для аргумента '{name}', Это должно быть '{choices}'", + "app_argument_choice_invalid": "Выберите корректное значение аргумента '{name}'; '{value}' не входит в число возможных вариантов: '{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_change_url_no_script": "Приложение '{app_name}' не поддерживает изменение URL. Возможно, вам нужно обновить приложение.", + "app_change_url_success": "Успешно изменён URL {app} на {domain}{path}", + "app_extraction_failed": "Невозможно извлечь файлы для установки", + "app_id_invalid": "Неправильный ID приложения", "app_install_files_invalid": "Неправильные файлы инсталляции", - "app_location_unavailable": "Этот url отсутствует или конфликтует с уже установленным приложением или приложениями: {apps}", + "app_location_unavailable": "Этот URL отсутствует или конфликтует с уже установленным приложением или приложениями:\n{apps}", "app_manifest_invalid": "Недопустимый манифест приложения: {error}", "app_not_correctly_installed": "{app} , кажется, установлены неправильно", "app_not_installed": "{app} не установлены", @@ -29,5 +29,7 @@ "app_upgrade_some_app_failed": "Невозможно обновить некоторые приложения", "app_upgraded": "{app} обновлено", "installation_complete": "Установка завершена", - "password_too_simple_1": "Пароль должен быть не менее 8 символов" -} \ No newline at end of file + "password_too_simple_1": "Пароль должен быть не менее 8 символов", + "admin_password_too_long": "Выберите пароль короче 127 символов", + "password_listed": "Этот пароль является одним из наиболее часто используемых паролей в мире. Пожалуйста, выберите что-то более уникальное." +} From 5a097db694b6b74189126dee4dce59947107e28c Mon Sep 17 00:00:00 2001 From: Weblate Date: Tue, 12 Oct 2021 22:39:31 +0000 Subject: [PATCH 0837/1155] Added translation using Weblate (Slovenian) --- locales/sl.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 locales/sl.json diff --git a/locales/sl.json b/locales/sl.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/locales/sl.json @@ -0,0 +1 @@ +{} From dcb885c901e18a97820110bc9b3bad01eb086ead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Fri, 15 Oct 2021 05:15:10 +0000 Subject: [PATCH 0838/1155] Translated using Weblate (Galician) Currently translated at 100.0% (706 of 706 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 987093df8..0d7d1afee 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -709,5 +709,6 @@ "domain_dns_push_not_applicable": "A función de rexistro DNS automático non é aplicable ao dominio {domain}. Debes configurar manualmente os teus rexistros DNS seguindo a documentación de https://yunohost.org/dns_config.", "domain_dns_push_managed_in_parent_domain": "A función de rexistro DNS automático está xestionada polo dominio nai {parent_domain}.", "ldap_attribute_already_exists": "Xa existe o atributo LDAP '{attribute}' con valor '{value}'", - "log_domain_config_set": "Actualizar configuración para o dominio '{}'" + "log_domain_config_set": "Actualizar configuración para o dominio '{}'", + "domain_unknown": "Dominio '{domain}' descoñecido" } From c927c3c654cd6f3a0a8a958a180f2052d080705a Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 16 Oct 2021 21:22:24 +0000 Subject: [PATCH 0839/1155] Translated using Weblate (German) Currently translated at 89.5% (632 of 706 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 69 ++++++++++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 29 deletions(-) diff --git a/locales/de.json b/locales/de.json index 199718c2b..46c314f34 100644 --- a/locales/de.json +++ b/locales/de.json @@ -124,13 +124,13 @@ "upnp_disabled": "UPnP deaktiviert", "upnp_enabled": "UPnP aktiviert", "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", - "user_deletion_failed": "Benutzer konnte nicht gelöscht werden {user}: {error}", + "user_created": "Benutzer:in erstellt", + "user_creation_failed": "Benutzer:in konnte nicht erstellt werden {user}: {error}", + "user_deleted": "Benutzer:in gelöscht", + "user_deletion_failed": "Benutzer:in 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_unknown": "Unbekannte:r Benutzer:in : {user}", + "user_update_failed": "Benutzer:in konnte nicht aktualisiert werden {user}: {error}", "user_updated": "Benutzerinformationen wurden aktualisiert", "yunohost_already_installed": "YunoHost ist bereits installiert", "yunohost_configured": "YunoHost ist nun konfiguriert", @@ -290,13 +290,13 @@ "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": "(Der Cache für die Diagnose {category} ist immer noch gültig . Es wird momentan keine neue Diagnose durchgeführt!)", + "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?", "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_dns_good_conf": "DNS Einträge korrekt konfiguriert für die Domäne {domain} (Kategorie {category})", "diagnosis_ignored_issues": "(+ {nb_ignored} ignorierte(s) Problem(e))", "diagnosis_basesystem_hardware": "Server Hardware Architektur ist {virt} {arch}", "diagnosis_found_errors": "Habe {errors} erhebliche(s) Problem(e) in Verbindung mit {category} gefunden!", @@ -311,7 +311,7 @@ "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", - "mail_unavailable": "Diese E-Mail Adresse ist reserviert und wird dem ersten Benutzer automatisch zugewiesen", + "mail_unavailable": "Diese E-Mail Adresse ist reserviert und wird dem/der ersten Benutzer:in automatisch zugewiesen", "diagnosis_services_conf_broken": "Die Konfiguration für den Dienst {service} ist fehlerhaft!", "diagnosis_services_running": "Dienst {service} läuft!", "diagnosis_domain_expires_in": "{domain} läuft in {days} Tagen ab.", @@ -319,11 +319,11 @@ "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.", - "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_try_dyndns_update_force": "Die DNS Konfiguration der Domäne sollte von Yunohost kontrolliert werden. Andernfalls, kannst du mit yunohost dyndns update --force ein Update erzwingen.", + "diagnosis_dns_point_to_doc": "Bitte schaue in die Dokumentation unter https://yunohost.org/dns_config wenn du hilfe bei der Konfiguration der DNS Einträge brauchst.", + "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}", - "diagnosis_dns_bad_conf": "Einige DNS-Einträge für die Domäne {domain} fehlen oder sind nicht korrekt (Kategorie {category})", + "diagnosis_dns_bad_conf": "Einige DNS Einträge für die Domäne {domain} fehlen oder sind nicht korrekt (Kategorie {category})", "diagnosis_ip_local": "Lokale IP: {local}", "diagnosis_ip_global": "Globale IP: {global}", "diagnosis_ip_no_ipv6_tip": "Die Verwendung von IPv6 ist nicht Voraussetzung für das Funktionieren Ihres Servers, trägt aber zur Gesundheit des Internet als Ganzes bei. IPv6 sollte normalerweise automatisch von Ihrem Server oder Ihrem Provider konfiguriert werden, sofern verfügbar. Andernfalls müßen Sie einige Dinge manuell konfigurieren. Weitere Informationen finden Sie hier: https://yunohost.org/#/ipv6. Wenn Sie IPv6 nicht aktivieren können oder Ihnen das zu technisch ist, können Sie diese Warnung gefahrlos ignorieren.", @@ -351,8 +351,8 @@ "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}' 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_domain_expiration_not_found_details": "Die WHOIS Informationen für die Domäne {domain} scheinen 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}.", "diagnosis_swap_none": "Das System hat gar keinen Swap. Sie sollten sich überlegen mindestens {recommended} an Swap einzurichten, um Situationen zu verhindern, in welchen der RAM des Systems knapp wird.", @@ -436,8 +436,8 @@ "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}", "domain_name_unknown": "Domäne '{domain}' unbekannt", - "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_user_not_in_group": "Benutzer:in {user} ist nicht in der Gruppe {group}", + "group_user_already_in_group": "Benutzer:in {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...", @@ -460,7 +460,7 @@ "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", - "mailbox_disabled": "E-Mail für Benutzer {user} deaktiviert", + "mailbox_disabled": "E-Mail für Benutzer:in {user} deaktiviert", "log_tools_reboot": "Server neustarten", "log_tools_shutdown": "Server ausschalten", "log_tools_upgrade": "Systempakete aktualisieren", @@ -469,12 +469,12 @@ "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_update": "Aktualisiere Information für Benutzer:in '{}'", "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_user_delete": "Lösche Benutzer:in '{}'", + "log_user_create": "Füge Benutzer:in '{}' hinzu", "log_permission_url": "Aktualisiere URL, die mit der Berechtigung '{}' verknüpft ist", "log_permission_delete": "Lösche Berechtigung '{}'", "log_permission_create": "Erstelle Berechtigung '{}'", @@ -541,14 +541,14 @@ "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.", + "permission_require_account": "Berechtigung {permission} ist nur für Benutzer:innen mit einem Konto sinnvoll und kann daher nicht für Besucher:innen 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}' aktualisiert", "permission_update_failed": "Die Berechtigung '{permission}' kann nicht aktualisiert werden : {error}", "permission_not_found": "Berechtigung '{permission}' nicht gefunden", "permission_deletion_failed": "Entfernung der Berechtigung nicht möglich '{permission}': {error}", "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_currently_allowed_for_all_users": "Diese Berechtigung wird derzeit allen Benutzer:innen 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", "permission_cannot_remove_main": "Entfernung einer Hauptberechtigung nicht genehmigt", @@ -587,13 +587,13 @@ "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.", "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.", + "permission_cant_add_to_all_users": "Die Berechtigung {permission} konnte nicht allen Benutzer:innen gegeben werden.", "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)", "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_slapd": "Speichert Benutzer:innen, 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", @@ -623,12 +623,23 @@ "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.", - "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", + "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": "Benutzer:in '{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}", "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", - "danger": "Warnung:" -} \ No newline at end of file + "danger": "Warnung:", + "diagnosis_apps_bad_quality": "Diese App ist im YunoHost App Katalog momentan als kaputt gekennzeichnet. Dies mag ein temporäres Problem darstellen, das von den Maintainern versucht wird zu beheben. In der Zwischenzeit ist das Upgrade dieser App nicht möglich.", + "config_apply_failed": "Die neue Konfiguration umzusetzen ist fehlgeschlagen: {error}", + "config_validate_date": "Sollte ein zulässiges Datum in folgendem Format sein: YYYY-MM-DD", + "config_validate_email": "Sollte eine zulässige eMail sein", + "config_forbidden_keyword": "Das Keyword '{keyword}' ist reserviert. Mit dieser id kannst du keine Konfigurationspanel erstellen", + "config_no_panel": "Kein Konfigurationspanel gefunden.", + "config_validate_color": "Sollte eine zulässige RGB hexadezimal Farbe sein", + "diagnosis_apps_issue": "Ein Problem für die App {app} ist aufgetreten", + "config_validate_time": "Sollte eine zulässige Zeit wie HH:MM sein", + "config_validate_url": "Sollte eine zulässige web URL sein", + "config_version_not_supported": "Konfigurationspanel Versionen '{version}' sind nicht unterstützt." +} From b438775d966af26f02cc36bb177a4e2de4b257c4 Mon Sep 17 00:00:00 2001 From: Colin Wawrik Date: Sat, 16 Oct 2021 21:21:50 +0000 Subject: [PATCH 0840/1155] Translated using Weblate (German) Currently translated at 89.5% (632 of 706 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 46c314f34..5dd8c059d 100644 --- a/locales/de.json +++ b/locales/de.json @@ -424,7 +424,7 @@ "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.", "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", + "diagnosis_description_ports": "Geöffnete 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}'", "app_label_deprecated": "Dieser Befehl ist veraltet! Bitte nutzen Sie den neuen Befehl 'yunohost user permission update' um das Applabel zu verwalten.", From d260ec51c5cca21981fbecb4b1e20493fe1254c3 Mon Sep 17 00:00:00 2001 From: Semen Turchikhin Date: Mon, 18 Oct 2021 00:14:01 +0000 Subject: [PATCH 0841/1155] Translated using Weblate (Russian) Currently translated at 10.6% (75 of 706 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ru/ --- locales/ru.json | 63 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 10 deletions(-) diff --git a/locales/ru.json b/locales/ru.json index 03634e9ff..5c9a39322 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -6,7 +6,7 @@ "app_already_installed": "{app} уже установлено", "app_already_installed_cant_change_url": "Это приложение уже установлено. URL не может быть изменен только с помощью этой функции. Изучите `app changeurl`, если это доступно.", "app_argument_choice_invalid": "Выберите корректное значение аргумента '{name}'; '{value}' не входит в число возможных вариантов: '{choices}'", - "app_argument_invalid": "Недопустимое значение аргумента '{name}': {error}'", + "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}'), ничего делать не надо.", @@ -14,22 +14,65 @@ "app_change_url_success": "Успешно изменён URL {app} на {domain}{path}", "app_extraction_failed": "Невозможно извлечь файлы для установки", "app_id_invalid": "Неправильный ID приложения", - "app_install_files_invalid": "Неправильные файлы инсталляции", + "app_install_files_invalid": "Эти файлы не могут быть установлены", "app_location_unavailable": "Этот URL отсутствует или конфликтует с уже установленным приложением или приложениями:\n{apps}", "app_manifest_invalid": "Недопустимый манифест приложения: {error}", "app_not_correctly_installed": "{app} , кажется, установлены неправильно", - "app_not_installed": "{app} не установлены", + "app_not_installed": "{app} не найдено в списке установленных приложений: {all_apps}", "app_not_properly_removed": "{app} удалены неправильно", "app_removed": "{app} удалено", - "app_requirements_checking": "Проверяю необходимые пакеты для {app}...", - "app_sources_fetch_failed": "Невозможно получить исходные файлы", + "app_requirements_checking": "Проверка необходимых пакетов для {app}...", + "app_sources_fetch_failed": "Невозможно получить исходные файлы, проверьте правильность URL", "app_unknown": "Неизвестное приложение", - "app_upgrade_app_name": "Обновление приложения {app}...", - "app_upgrade_failed": "Невозможно обновить {app}", - "app_upgrade_some_app_failed": "Невозможно обновить некоторые приложения", + "app_upgrade_app_name": "Обновление {app}...", + "app_upgrade_failed": "Невозможно обновить {app}: {error}", + "app_upgrade_some_app_failed": "Некоторые приложения не удалось обновить", "app_upgraded": "{app} обновлено", "installation_complete": "Установка завершена", "password_too_simple_1": "Пароль должен быть не менее 8 символов", - "admin_password_too_long": "Выберите пароль короче 127 символов", - "password_listed": "Этот пароль является одним из наиболее часто используемых паролей в мире. Пожалуйста, выберите что-то более уникальное." + "admin_password_too_long": "Пожалуйста, выберите пароль короче 127 символов", + "password_listed": "Этот пароль является одним из наиболее часто используемых паролей в мире. Пожалуйста, выберите что-то более уникальное.", + "backup_applying_method_copy": "Копирование всех файлов в резервную копию...", + "domain_dns_conf_is_just_a_recommendation": "Эта страница показывает вам *рекомендуемую* конфигурацию. Она *не* создаёт для вас конфигурацию DNS. Вы должны сами конфигурировать зону вашего DNS у вашего регистратора в соответствии с этой рекомендацией.", + "good_practices_about_user_password": "Выберите пароль пользователя длиной не менее 8 символов, хотя рекомендуется использовать более длинные (например, парольную фразу) и / или использовать символы различного типа (прописные, строчные буквы, цифры и специальные символы).", + "password_too_simple_3": "Пароль должен содержать не менее 8 символов и содержать цифры, заглавные и строчные буквы и специальные символы", + "upnp_enabled": "UPnP включён", + "user_deleted": "Пользователь удалён", + "ask_lastname": "Фамилия", + "app_action_broke_system": "Это действие, по-видимому, нарушило эти важные службы: {services}", + "already_up_to_date": "Ничего делать не требуется. Всё уже обновлено.", + "operation_interrupted": "Действие было прервано вручную?", + "user_created": "Пользователь создан", + "aborting": "Прерывание.", + "ask_firstname": "Имя", + "ask_main_domain": "Основной домен", + "ask_new_admin_password": "Новый пароль администратора", + "ask_new_domain": "Новый домен", + "ask_new_path": "Новый путь", + "ask_password": "Пароль", + "app_remove_after_failed_install": "Удаление приложения после сбоя установки...", + "app_upgrade_script_failed": "Внутри скрипта обновления приложения произошла ошибка", + "upnp_disabled": "UPnP отключён", + "app_manifest_install_ask_domain": "Выберите домен, в котором должно быть установлено это приложение", + "app_manifest_install_ask_path": "Выберите URL путь (часть после домена), по которому должно быть установлено это приложение", + "app_manifest_install_ask_admin": "Выберите пользователя администратора для этого приложения", + "app_manifest_install_ask_password": "Выберите пароль администратора для этого приложения", + "app_manifest_install_ask_is_public": "Должно ли это приложение быть открыто для анонимных посетителей?", + "apps_already_up_to_date": "Все приложения уже обновлены", + "app_full_domain_unavailable": "Извините, это приложение должно быть установлено в собственном домене, но другие приложения уже установлены в домене '{domain}'. Вместо этого вы можете использовать отдельный поддомен для этого приложения.", + "app_install_script_failed": "Произошла ошибка в скрипте установки приложения", + "apps_catalog_update_success": "Каталог приложений был обновлён!", + "apps_catalog_updating": "Обновление каталога приложений...", + "yunohost_installing": "Установка YunoHost...", + "app_start_remove": "Удаление {app}...", + "app_label_deprecated": "Эта команда устарела! Пожалуйста, используйте новую команду 'yunohost user permission update', чтобы управлять ярлыком приложения.", + "app_start_restore": "Восстановление {app}...", + "app_upgrade_several_apps": "Будут обновлены следующие приложения: {apps}", + "password_too_simple_2": "Пароль должен содержать не менее 8 символов и включать цифры, заглавные и строчные буквы", + "password_too_simple_4": "Пароль должен содержать не менее 12 символов и включать цифры, заглавные и строчные буквы и специальные символы", + "upgrade_complete": "Обновление завершено", + "user_unknown": "Неизвестный пользователь: {user}", + "yunohost_already_installed": "YunoHost уже установлен", + "yunohost_configured": "Теперь YunoHost настроен", + "upgrading_packages": "Обновление пакетов..." } From 922fc132af265168c2915114e5904e277399ce3b Mon Sep 17 00:00:00 2001 From: Tymofii-Lytvynenko Date: Mon, 18 Oct 2021 12:55:28 +0000 Subject: [PATCH 0842/1155] Translated using Weblate (Ukrainian) Currently translated at 100.0% (706 of 706 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/uk/ --- locales/uk.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/uk.json b/locales/uk.json index b54d81fbd..648c97fca 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -709,5 +709,6 @@ "other_available_options": "...і {n} інших доступних опцій, які не показано", "domain_dns_pushing": "Передання записів DNS...", "ldap_attribute_already_exists": "Атрибут LDAP '{attribute}' вже існує зі значенням '{value}'", - "domain_dns_push_already_up_to_date": "Записи вже оновлені, нічого не потрібно робити." + "domain_dns_push_already_up_to_date": "Записи вже оновлені, нічого не потрібно робити.", + "domain_unknown": "Домен '{domain}' є невідомим" } From 18affd4fa0a344c09dbcf82e6d7cec5819fd6e9d Mon Sep 17 00:00:00 2001 From: punkrockgirl Date: Mon, 18 Oct 2021 15:11:45 +0000 Subject: [PATCH 0843/1155] Translated using Weblate (Basque) Currently translated at 0.9% (7 of 706 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eu/ --- locales/eu.json | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/locales/eu.json b/locales/eu.json index 1891e00a3..0eac41bf4 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -1,3 +1,10 @@ { - "password_too_simple_1": "Pasahitzak gutxienez 8 karaktere izan behar ditu" -} \ No newline at end of file + "password_too_simple_1": "Pasahitzak gutxienez 8 karaktere izan behar ditu", + "action_invalid": "'{action}' ekintza baliogabea", + "aborting": "Bertan behera uzten.", + "admin_password_changed": "Administrazio-pasahitza aldatu da", + "admin_password_change_failed": "Ezinezkoa izan da pasahitza aldatzea", + "additional_urls_already_added": "'{url}' URL gehigarria '{permission}' baimenerako gehitu da dagoeneko", + "additional_urls_already_removed": "'{url}' URL gehigarriari '{permission}' baimena kendu zaio dagoeneko", + "admin_password": "Administrazio-pasahitza" +} From c57e868ce2d6f4f29c3667ff7220041e102a3a81 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 18 Oct 2021 18:58:30 +0200 Subject: [PATCH 0844/1155] Update changelog for 4.3.1.6 --- debian/changelog | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/debian/changelog b/debian/changelog index e35be5cf0..d359526d0 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,12 @@ +yunohost (4.3.1.6) testing; urgency=low + + - [fix] configpanel: Various technical fixes (07c1ddce, eae826b2, ff69067d) + - [i18n] Translations updated for Basque, Galician, German, Russian, Ukrainian + + Thanks to all contributors <3 ! (Colin Wawrik, Daniel, José M, ljf, punkrockgirl, Semen Turchikhin, Tymofii-Lytvynenko) + + -- Alexandre Aubin Mon, 18 Oct 2021 18:50:00 +0200 + yunohost (4.3.1.5) testing; urgency=low - [enh] configpanel: Add hook mecanism between questions (9f7fb61b) From 341059d07da0c5a34f0bbb4e735c5d819dffc5c8 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Mon, 18 Oct 2021 20:51:05 +0200 Subject: [PATCH 0845/1155] [fix] Default config toml path --- 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 914612086..aaed1ffd5 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -192,7 +192,7 @@ def evaluate_simple_js_expression(expr, context={}): class ConfigPanel: entity_type = "config" save_path_tpl = None - config_path_tpl = "/usr/share/yunohost/other/config_{entity}.toml" + config_path_tpl = "/usr/share/yunohost/other/config_{entity_type}.toml" save_mode = "full" @classmethod @@ -216,7 +216,7 @@ class ConfigPanel: self.entity = entity self.config_path = config_path if not config_path: - self.config_path = self.config_path_tpl.format(entity=entity) + self.config_path = self.config_path_tpl.format(entity=entity, entity_type=self.entity_type) self.save_path = save_path if not save_path and self.save_path_tpl: self.save_path = self.save_path_tpl.format(entity=entity) From 9c22329e2b02f9a1fe65bcfdb85ed53b95b9c88e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 19 Oct 2021 12:44:50 +0200 Subject: [PATCH 0846/1155] Moar fixes... --- 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 acb0f0168..8eb14a36f 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -462,9 +462,9 @@ class DomainConfigPanel(ConfigPanel): toml = super()._get_toml() toml["feature"]["xmpp"]["xmpp"]["default"] = ( - 1 if self.domain == _get_maindomain() else 0 + 1 if self.entity == _get_maindomain() else 0 ) - toml["dns"]["registrar"] = _get_registrar_config_section(self.domain) + toml["dns"]["registrar"] = _get_registrar_config_section(self.entity) # FIXME: Ugly hack to save the registar id/value and reinject it in _load_current_values ... self.registar_id = toml["dns"]["registrar"]["registrar"]["value"] From d09e3e79b33c124e48deae69ddbb3913d94e3414 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 19 Oct 2021 12:46:38 +0200 Subject: [PATCH 0847/1155] ci: test now returns a YunohostError instead of YunohostValidationError --- src/yunohost/tests/test_domains.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/tests/test_domains.py b/src/yunohost/tests/test_domains.py index 02d60ead4..50a6eef34 100644 --- a/src/yunohost/tests/test_domains.py +++ b/src/yunohost/tests/test_domains.py @@ -114,5 +114,5 @@ def test_domain_config_set(): def test_domain_configs_unknown(): - with pytest.raises(YunohostValidationError): + with pytest.raises(YunohostError): domain_config_get(TEST_DOMAINS[2], "feature.xmpp.xmpp.xmpp") From 41876f950aec38025d8c784745b59ec6dc97f23d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 19 Oct 2021 14:25:54 +0200 Subject: [PATCH 0848/1155] ci/tests: sigh, YunohostError undefined --- src/yunohost/tests/test_domains.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/tests/test_domains.py b/src/yunohost/tests/test_domains.py index 50a6eef34..95a33e0ba 100644 --- a/src/yunohost/tests/test_domains.py +++ b/src/yunohost/tests/test_domains.py @@ -3,7 +3,7 @@ import os from moulinette.core import MoulinetteError -from yunohost.utils.error import YunohostValidationError +from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.domain import ( DOMAIN_SETTINGS_DIR, _get_maindomain, From 3d19c9f8e00a15f9000fb917f7b7bbae741f0517 Mon Sep 17 00:00:00 2001 From: punkrockgirl Date: Mon, 18 Oct 2021 23:38:25 +0000 Subject: [PATCH 0849/1155] Translated using Weblate (Basque) Currently translated at 34.2% (242 of 706 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eu/ --- locales/eu.json | 240 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 239 insertions(+), 1 deletion(-) diff --git a/locales/eu.json b/locales/eu.json index 0eac41bf4..99735e9ce 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -6,5 +6,243 @@ "admin_password_change_failed": "Ezinezkoa izan da pasahitza aldatzea", "additional_urls_already_added": "'{url}' URL gehigarria '{permission}' baimenerako gehitu da dagoeneko", "additional_urls_already_removed": "'{url}' URL gehigarriari '{permission}' baimena kendu zaio dagoeneko", - "admin_password": "Administrazio-pasahitza" + "admin_password": "Administrazio-pasahitza", + "diagnosis_ip_global": "IP orokorra: {global}", + "app_argument_password_no_default": "Errorea egon da '{name}' pasahitzaren argumentua ikuskatzean: pasahitzaren argumentuak ezin du balio hori izan segurtasun urria duela-eta", + "app_extraction_failed": "Ezinezkoa izan da instalazio fitxategiak ateratzea", + "app_requirements_unmeet": "{app}(e)k behar dituen baldintzak ez dira betetzen, {pkgname} ({version}) paketea {spec} izan behar da", + "backup_deleted": "Babeskopia ezabatuta", + "app_argument_required": "'{name}' argumentua beharrezkoa da", + "certmanager_acme_not_configured_for_domain": "Ezin da ACME azterketa {domain} domeinurako burutu une honetan nginx konfigurazioak ez duelako beharrezko kodea… Baieztatu nginx-en konfigurazioa egunean dagoela 'yunohost tools regen-conf nginx --dry-run --with-diff' komandoa exekutatuz.", + "certmanager_domain_dns_ip_differs_from_public_ip": "'{domain}' domeinurako DNS balioak ez datoz bat zerbitzariaren IParekin. Mesedez, egiaztatu 'DNS balioak' (oinarrizkoa) kategoria diagnostikoen atalean. A balioak duela gutxi aldatu badituzu, itxaron hedatu daitezen (badaude DNSen hedapena ikusteko erramintak interneten). (Zertan ari zeren baldin badakizu, erabili '--no-checks' egiaztapen horiek desgaitzeko.)", + "confirm_app_install_thirdparty": "KONTUZ! Aplikazio hau ez da YunoHosten aplikazioen katalogokoa. Kanpoko aplikazioek sistemaren integritate eta segurtasuna arriskuan jarri dezakete. Ziur asko EZ zenuke instalatu beharko zertan ari zaren ez badakizu. Aplikazio hau ez badabil edo sistema hondatzen badu EZ DA LAGUNTZARIK EMANGO… aurrera jarraitu nahi duzu hala ere? Aukeratu '{answers}'", + "app_start_remove": "{app} ezabatzen…", + "diagnosis_http_hairpinning_issue_details": "Litekeena da erantzulea zure kable-modem / routerra izatea. Honen eraginez, saretik kanpo daudenek zerbitzaria arazorik gabe erabili ahal izango dute, baina sarean bertan daudenek (ziur asko zure kasua) ezingo dute kanpoko IPa edo domeinu izena erabili zerbitzarira konektatzeko. Egoera hobetu edo guztiz konpontzeko, irakurri dokumentazioa. [Itzultzailearen oharra: SBC merke batean Pi-Hole instalatu eta bertako Local DNS > DNS records baliatu arazo hau ekiditeko]", + "diagnosis_http_special_use_tld": "{domain} domeinua top-level domain (TLD) motakoa da .local edo .test bezala eta ez du sare lokaletik kanpo eskuragarri zertan egon.", + "diagnosis_ip_weird_resolvconf_details": "/etc/resolv.conf fitxategia symlink bat izan beharko litzateke /etc/resolvconf/run/resolv.conf fitxategira 127.0.0.1ra adi dagoena (dnsmasq). DNS ebazleak eskuz konfiguratu nahi badituzu, mesedez aldatu /etc/resolv.dnsmasq.conf fitxategia.", + "diagnosis_ip_connected_ipv4": "Zerbitzaria IPv4 bidez dago internetera konektatuta!", + "diagnosis_basesystem_ynh_inconsistent_versions": "YunoHost paketeen bertsioak ez datoz bat… ziur asko noizbait eguneraketa batek kale egin edo erabat amaitu ez zuelako.", + "diagnosis_high_number_auth_failures": "Azken aldian kale egin duten saio-hasiera saiakera ugari egon dira. Egiaztatu fail2ban martxan dabilela eta egoki konfiguratuta dagoela, edo erabili beste ataka bat SSHrako dokumentazioan azaldu bezala.", + "diagnosis_mail_ehlo_could_not_diagnose": "Ezin izan da egiaztatu postfix posta zerbitzaria IPv{ipversion}az kanpo eskuragarri dagoenik.", + "app_id_invalid": "Aplikazio ID okerra", + "app_install_files_invalid": "Fitxategi hauek ezin dira instalatu", + "diagnosis_description_ip": "Internet konexioa", + "diagnosis_description_dnsrecords": "DNS erregistroak", + "app_label_deprecated": "Komando hau zaharkitua dago! Mesedez erabili 'yunohost user permission update' komando berria aplikazioaren etiketa kudeatzeko.", + "confirm_app_install_danger": "KONTUZ! Aplikazio hau esperimentala da (edo ez dabil)! Ez zenuke instalatu beharko zertan ari zaren ez badakizu. Aplikazio hau ez badabil edo sistema hondatzen badu, EZ DA LAGUNTZARIK EMANGO… aurrera jarraitu nahi al duzu hala ere? Aukeratu '{answers}'", + "diagnosis_description_systemresources": "Sistemaren baliabideak", + "backup_csv_addition_failed": "Ezin izan dira fitxategiak CSV fitxategira kopiatu", + "backup_no_uncompress_archive_dir": "Ez dago horrelako deskonprimatutako fitxategi katalogorik", + "danger": "Arriskua:", + "diagnosis_dns_discrepancy": "Ez dirudi ondorengo DNS balioak bat datozenik proposatutako konfigurazioarekin:
Mota: {type}
Izena: {name}
Oraingo balioa: {current}
Proposatutako balioa: {value}", + "diagnosis_dns_specialusedomain": "{domain} domeinua top-level domain (TLD) erabilera berezikoa da .local edo .test bezala eta horregatik ez du DNS erregistrorik erabiltzen.", + "diagnosis_http_bad_status_code": "Badirudi zerbitzaria ez den beste gailu batek erantzun diola eskaerari (agian routerrak).
1. Honen arrazoi ohikoena 80 (eta 443) ataka zerbitzarira ondo birbidaltzen ez dela da.
2. Konfigurazio konplexua badarabilzu, egiaztatu suebakiak edo reverse-proxyk oztopatzen ez dutela.", + "diagnosis_http_timeout": "Denbora agortu da sare lokaletik kanpo zure zerbitzarira konexioa gauzatzeko ahaleginean. Eskuragarri ez dagoela dirudi.
1. 80 (eta 443) ataka zerbitzarira modu egokian birbidaltzen ez direla da ohiko arrazoia.
2. Badaezpada egiaztatu nginx martxan dagoela.
3. Konfigurazio konplexuetan, egiaztatu suebakiak edo reverse-proxyk konexioa oztopatzen ez dutela.", + "app_sources_fetch_failed": "Ezin izan dira fitxategiak eskuratu, zuzena al da URLa?", + "app_make_default_location_already_used": "Ezinezkoa izan da '{app}' domeinuko aplikazio nagusi ezartzea, '{other_app}'(e)k dagoeneko '{domain}' erabiltzen duelako", + "app_already_installed_cant_change_url": "Aplikazio hau instalatuta dago dagoeneko. URLa ezin da aldatu aukera honekin. Markatu `app changeurl` markatzeko moduan badago.", + "diagnosis_ip_not_connected_at_all": "Badirudi zerbitzaria ez dagoela internetera konektatuta!?", + "app_already_up_to_date": "{app} aplikazioa egunean da dagoeneko", + "app_change_url_success": "{app} aplikazioaren URLa {domain}{path} da orain", + "admin_password_too_long": "Mesedez aukeratu 127 karaktere baino laburragoa den pasahitz bat", + "app_action_broke_system": "Ekintza honek {services} zerbitzu garrantzitsuak hondatu dituela dirudi", + "diagnosis_basesystem_hardware_model": "Zerbitzariaren modeloa {model} da", + "already_up_to_date": "Ez dago egiteko ezer. Guztia dago egunean.", + "backup_permission": "{app}(r)entzat babeskopia baimena", + "config_validate_date": "UUUU-HH-EE formatua duen data bat izan behar da", + "config_validate_email": "Benetazko posta elektronikoa izan behar da", + "config_validate_time": "OO:MM formatua duen ordu bat izan behar da", + "config_validate_url": "Benetazko URL bat izan behar da", + "config_version_not_supported": "Ezin da konfigurazio-panelaren '{version}' bertsioa erabili.", + "app_restore_script_failed": "Errorea gertatu da aplikazioa lehengoratzeko aginduan", + "app_upgrade_some_app_failed": "Ezin izan dira aplikazio batzuk eguneratu", + "app_install_failed": "Ezinezkoa izan da {app} instalatzea: {error}", + "diagnosis_basesystem_kernel": "Zerbitzariak Linuxen {kernel_version} kernela darabil", + "app_argument_invalid": "Aukeratu balio onargarri bat {name}' argumenturako: {error}", + "app_already_installed": "{app} instalatuta dago dagoeneko", + "app_config_unable_to_apply": "Ezinezkoa izan da konfigurazio aukerak ezartzea.", + "app_config_unable_to_read": "Ezinezkoa izan da konfigurazio aukerak irakurtzea.", + "config_apply_failed": "Ezin izan da konfigurazio berria ezarri: {error}", + "config_cant_set_value_on_section": "Ezin da balio bakar bat ezarri konfigurazio atal oso batean.", + "config_no_panel": "Ez da konfigurazio-panelik aurkitu.", + "diagnosis_found_errors_and_warnings": "{category} atalari dago(z)kion {errors} arazoa(k) (eta {warnings} abisua(k)) aurkitu d(ir)a!", + "diagnosis_description_regenconf": "Sistemaren ezarpenak", + "app_upgrade_script_failed": "Errore bat gertatu da aplikazioaren eguneratze aginduan", + "diagnosis_basesystem_hardware": "Zerbitzariaren arkitektura {virt} {arch} da", + "diagnosis_mail_ehlo_ok": "SMTP posta zerbitzaria eskuragarri dago kanpoko saretik eta beraz, posta elektronikoa jasotzeko gai da!", + "app_unknown": "Aplikazio ezezaguna", + "diagnosis_mail_ehlo_bad_answer": "SMTP ez den zerbitzu batek erantzun du IPv{ipversion}ko 25. atakan", + "diagnosis_mail_ehlo_could_not_diagnose_details": "Errorea: {error}", + "diagnosis_mail_blacklist_ok": "Zerbitzari honek darabiltzan IPak eta domeinuak ez dirudi inolako zerrenda beltzetan daudenik", + "diagnosis_domain_expiration_error": "Domeinu batzuk IRAUNGITZEAR daude!", + "diagnosis_domain_expiration_success": "Domeinuak erregistratuta daude eta ez dira oraingoz iraungiko.", + "app_manifest_install_ask_is_public": "Saiorik hasi gabeko bisitarientzat ikusgai egon beharko litzateke aplikazio hau?", + "diagnosis_domain_expires_in": "{domain} {days} egun barru iraungiko da.", + "app_manifest_install_ask_domain": "Aukeratu zein domeinutan instalatu nahi duzun aplikazio hau", + "custom_app_url_required": "URL bat zehaztu behar duzu {app} eguneratzeko", + "app_change_url_identical_domains": "Domeinu zahar eta berriaren bidea bera dira: ('{domain}{path}'), ez dago ezer egitekorik.", + "app_upgrade_failed": "Ezinezkoa {app} eguneratzea: {error}", + "app_upgrade_app_name": "Orain {app} eguneratzen…", + "app_upgraded": "{app} eguneratu da", + "ask_firstname": "Izena", + "ask_lastname": "Abizena", + "ask_main_domain": "Domeinu nagusia", + "config_forbidden_keyword": "'{keyword}' etiketa sistemak bakarrik erabil dezake; ezin da ID hau daukan baliorik sortu edo erabili.", + "config_unknown_filter_key": "'{filter_key}' filtroaren kakoa ez da zuzena.", + "config_validate_color": "RGB hamaseitar kolore bat izan behar da", + "diagnosis_cant_run_because_of_dep": "Ezin da diagnosia abiarazi {category} atalerako {dep}(r)i lotutako arazo garrantzitsuek dirauen artean.", + "diagnosis_dns_missing_record": "Proposatutako DNS konfigurazioaren arabera, ondorengo informazioa gehitu beharko zenuke DNS erregistroan:
Mota: {type}
Izena: {name}
Balioa: {value}", + "diagnosis_http_nginx_conf_not_up_to_date": "Domeinu honen nginx konfigurazioa eskuz moldatu dela dirudi eta YunoHostek ezin du egiaztatu HTTP bidez eskuragarri dagoen.", + "ask_new_admin_password": "Administrazio-pasahitz berria", + "ask_new_domain": "Domeinu berria", + "ask_new_path": "Bide berria", + "ask_password": "Pasahitza", + "backup_abstract_method": "Babeskopia modu hau oraindik ez da go erabilgarri", + "backup_applying_method_custom": "'{method}' neurrira egindako babeskopia sortzen…", + "backup_applying_method_copy": "Babeskopiarako fitxategi guztiak kopiatzen…", + "backup_archive_app_not_found": "Ezin izan da {app} aurkitu babeskopia fitxategian", + "backup_applying_method_tar": "Babeskopiaren TAR fitxategia sortzen…", + "backup_archive_broken_link": "Ezin izan da babeskopiaren fitxategia eskuratu ({path}ra esteka okerra)", + "backup_creation_failed": "Ezin izan da babeskopiaren fitxategia sortu", + "backup_csv_creation_failed": "Ezin izan da lehengoratzeko beharrezkoak diren CSV fitxategiak sortu", + "backup_custom_mount_error": "Neurrira egindako babeskopiak ezin izan du 'muntatu' urratsetik haratago egin", + "backup_delete_error": "Ezin izan da '{path}' ezabatu", + "backup_method_copy_finished": "Babeskopiak amaitu du", + "backup_hook_unknown": "Babeskopiaren '{hook}' kakoa ez da ezagutzen", + "backup_method_custom_finished": "'{method}' neurrira egindako babeskopiak amaitu du", + "backup_method_tar_finished": "TAR babeskopia artxiboa sortu da", + "backup_mount_archive_for_restore": "Lehengoratzeko fitxategoak prestatzen…", + "backup_nothings_done": "Ez dago gordetzeko ezer", + "backup_output_directory_required": "Babeskopia non gorde nahi duzun zehaztu behar duzu", + "backup_system_part_failed": "Ezin izan da sistemaren '{part}' atalaren babeskopia egin", + "apps_catalog_updating": "Aplikazioen katalogoa eguneratzen…", + "certmanager_cert_signing_failed": "Ezin izan da ziurtagiri berria sinatu", + "certmanager_cert_renew_success": "Let's Encrypt ziurtagiria berriztu da '{domain}' domeinurako", + "app_requirements_checking": "{app}(e)k behar dituen paketeak ikuskatzen…", + "certmanager_unable_to_parse_self_CA_name": "Ezin izan da norberak sinatutako ziurtagiriaren izena prozesatu (fitxategia: {file})", + "app_remove_after_failed_install": "Aplikazioa ezabatzen instalatzerakoan errorea dela-eta…", + "diagnosis_basesystem_ynh_single_version": "{package} bertsioa: {version} ({repo})", + "diagnosis_failed_for_category": "'{category}' ataleko diagnostikoak kale egin du: {error}", + "diagnosis_cache_still_valid": "(Cachea oraindik baliogarria da {category} (ar)en diagnosirako. Ez da berrabiaraziko!)", + "diagnosis_found_errors": "{category} atalari dago(z)kion {errors} arazoa(k) aurkitu d(ir)a!", + "diagnosis_found_warnings": "{category} atalari dagokion eta hobetu daite(z)keen {warnings} abisua(k) aurkitu d(ir)a.", + "diagnosis_ip_connected_ipv6": "Zerbitzaria IPv6 bidez dago internetera konektatuta!", + "diagnosis_everything_ok": "Itxura ona dauka {category} atalak!", + "diagnosis_ip_no_ipv4": "Zerbitzariak ez du dabilen IPv4rik.", + "diagnosis_ip_no_ipv6": "Zerbitzariak ez du dabilen IPv6rik.", + "diagnosis_ip_broken_dnsresolution": "Domeinu izenaren ebazpena hondatuta dagoela dirudi… Suebakiren bat ote dago DNS eskaerak oztopatzen?", + "diagnosis_diskusage_low": "{mountpoint} euskarriak ({device} gailuan) edukieraren {free} ({free_percent}%) bakarrik du erabilgarri ({total} orotara). Kontuz ibili.", + "diagnosis_dns_good_conf": "DNS ezarpenak zuzen konfiguratuta daude {domain} domeinurako ({category} atala)", + "diagnosis_diskusage_verylow": "{mountpoint} euskarriak ({device} gailuan) edukieraren {free} ({free_percent}%) bakarrik du erabilgarri ({total} orotara). Zertxobait hustu beharko zenuke!", + "diagnosis_description_basesystem": "Sistemaren oinarria", + "diagnosis_description_services": "Zerbitzuen egoeraren egiaztapena", + "diagnosis_http_could_not_diagnose": "Ezin izan da egiaztatu domeinuak IPv{ipversion} kanpotik eskuragarri daudenik.", + "diagnosis_http_ok": "Ezin da {domain} domeinua HTTP bidez bisitatu sare lokaletik kanpo.", + "diagnosis_http_unreachable": "Badirudi {domain} domeinua ez dagoela eskuragarri HTTP bidez sare lokaletik kanpo.", + "apps_catalog_failed_to_download": "Ezinezkoa izan da {apps_catalog} aplikazioen zerrenda eskuratzea: {error}", + "apps_catalog_init_success": "Abiarazi da aplikazioen katalogo sistema!", + "apps_catalog_obsolete_cache": "Aplikazioen katalogoaren cachea hutsik edo zaharkituta dago.", + "diagnosis_description_mail": "Posta elektronikoa", + "diagnosis_http_connection_error": "Arazoa konexioan: ezin izan da domeinu horretara konektatu, litekeena da eskuragaitza izatea.", + "diagnosis_description_web": "Weba", + "diagnosis_display_tip": "Aurkitu diren arazoak ikusteko joan Diagnosien atalera administrazio-webgunean, edo exekutatu 'yunohost diagnosis show --issues --human-readable' komandoak nahiago badituzu.", + "diagnosis_dns_point_to_doc": "Mesedez, irakurri dokumentazioa DNS erregistroekin laguntza behar baduzu.", + "diagnosis_mail_ehlo_unreachable": "SMTP posta zerbitzaria ez dago eskuragarri IPv{ipversion}ko sare lokaletik kanpo eta beraz, ez da posta elektronikoa jasotzeko gai.", + "diagnosis_mail_ehlo_bad_answer_details": "Litekeena da zure zerbitzaria ez den beste gailu batek erantzun izana.", + "diagnosis_mail_blacklist_listed_by": "Zure IP edo {item} domeinua {blacklist_name} zerrenda beltzean ageri da", + "diagnosis_mail_blacklist_website": "Zerrenda beltzean zergatik zauden ulertu eta konpondu ondoren, {blacklist_website} webgunean zure IP edo domeinua bertatik atera dezatela eska dezakezu", + "diagnosis_http_could_not_diagnose_details": "Errorea: {error}", + "diagnosis_http_hairpinning_issue": "Dirudienez zure sareak ez du hairpinninga gaituta.", + "diagnosis_http_partially_unreachable": "Badirudi {domain} domeinua ezin dela bisitatu HTTP bidez IPv{failed} sare lokaletik kanpo, bai ordea IPv{passed} erabiliz.", + "backup_archive_cant_retrieve_info_json": "Ezin izan da '{archive}' fitxategiko informazioa eskuratu… info.json ezin izan da eskuratu (edo ez da baliozko jsona).", + "diagnosis_domain_expiration_not_found": "Ezin izan da domeinu batzuen iraungitze data egiaztatu", + "diagnosis_domain_expiration_not_found_details": "Dirudienez {domain} domeinuari buruzko WHOIS informazioak ez du zehazten noiz iraungiko den.", + "certmanager_domain_not_diagnosed_yet": "Oraindik ez dago {domain} domeinurako diagnostikorik. Mesedez, berrabiarazi diagnostikoak 'DNS balioak' eta 'Web' ataletarako diagnostikoen gunean Let's Encrypt ziurtagirirako prest ote dagoen egiaztatzeko. (Edo zertan ari zaren baldin badakizu, erabili '--no-checks' egiaztatzea desgaitzeko.)", + "diagnosis_domain_expiration_warning": "Domeinu batzuk iraungitzear daude!", + "app_packaging_format_not_supported": "Aplikazio hau ezin da instalatu YunoHostek ez duelako paketea ezagutzen. Sistema eguneratzea hausnartu beharko zenuke ziur asko.", + "diagnosis_dns_try_dyndns_update_force": "Domeinu honen DNS konfigurazioa YunoHostek kudeatu beharko luke automatikoki. Gertatuko ez balitz, eguneratzera behartu zenezake yunohost dyndns update --force erabiliz.", + "app_manifest_install_ask_path": "Aukeratu aplikazio hau instalatzeko URLaren bidea (domeinuaren atzeko aldean)", + "app_manifest_install_ask_admin": "Aukeratu administrati bat aplikazio honetarako", + "app_manifest_install_ask_password": "Aukeratu administrazio-pasahitz bat aplikazio honetarako", + "ask_user_domain": "Erabiltzailearen posta elektroniko eta XMPP konturako erabiliko den domeinua", + "app_action_cannot_be_ran_because_required_services_down": "{services} zerbitzuak martxan egon beharko lirateke ekintza hau gauzatu ahal izateko. Saia zaitez zerbitzuok berrabiarazten (eta ikertu zergatik abiarazi ez diren).", + "apps_already_up_to_date": "Egunean daude dagoeneko aplikazio guztiak", + "app_full_domain_unavailable": "Aplikazio honek bere domeinu propioa behar du, baina beste aplikazio batzuk daude dagoeneko instalatuta '{domain}' domeinuan. Azpidomeinu bat erabil zenezake instalatu nahi duzun aplikaziorako.", + "app_install_script_failed": "Errore bat gertatu da aplikazioaren instalatzailearen aginduetan", + "diagnosis_basesystem_host": "Zerbitzariak Debian {debian_version} darabil", + "diagnosis_ignored_issues": "(kontuan hartu ez d(ir)en + {nb_ignored} arazoa(k))", + "diagnosis_ip_dnsresolution_working": "Domeinu izenaren ebazpena badabil!", + "diagnosis_failed": "Ezin izan da '{category}' ataleko diagnostikoa lortu: {error}", + "diagnosis_ip_weird_resolvconf": "DNS ebazpena badabilela dirudi, baina antza denez moldatutako /etc/resolv.conf fitxategia erabiltzen ari zara.", + "diagnosis_dns_bad_conf": "DNS balio batzuk falta dira edo ez dira zuzenak {domain} domeinurako ({category} atala)", + "diagnosis_diskusage_ok": "{mountpoint} euskarriak ({device} gailuan) edukieraren {free} ({free_percent}%) du erabilgarri oraindik ({total} orotara)!", + "apps_catalog_update_success": "Aplikazioen katalogoa eguneratu da!", + "certmanager_warning_subdomain_dns_record": "'{subdomain}' azpidomeinuak ez dauka '{domain}'(e)k duen IP bera. Ezaugarri batzuk ez dira erabilgarri egongo hau zuzendu arte eta ziurtagiri bat birsortu arte.", + "app_argument_choice_invalid": "Aukeratu ({choices}) aukeretako bat '{name}' argumenturako: '{value}' ez dago aukera horien artean", + "backup_create_size_estimation": "Fitxategiak {size} datu inguru izango ditu.", + "diagnosis_basesystem_ynh_main_version": "Zerbitzariak YunoHosten {main_version} ({repo}) darabil", + "backup_custom_backup_error": "Neurrira egindako babeskopiak ezin izan du 'babeskopia egin' urratsetik haratago egin", + "diagnosis_ip_broken_resolvconf": "Zure zerbitzarian domeinu izenaren ebazpena hondatuta dagoela dirudi, antza denez /etc/resolv.conf fitxategia ez dago 127.0.0.1ra adi.", + "diagnosis_ip_no_ipv6_tip": "Dabilen IPv6 izatea ez da derrigorrezkoa zerbitzariaren funtzionamendurako, baina egokiena da interneten osasunerako. IPv6 automatikoki konfiguratu beharko luke sistemak edo telefono-konpainiak. Bestela, eskuz konfiguratu beharko zenituzke hainbat gauza dokumentazioan azaltzen den bezala. Ezin baduzu edo IPv6 gaitzea zuretzat kontu teknikoegia baldin bada, ez duzu abisu hau zertan kontutan hartu.", + "diagnosis_http_nginx_conf_not_up_to_date_details": "Egoera konpontzeko, ikuskatu desberdintasunak yunohost tools regen-conf nginx --dry-run --with-diff komandoren bidez eta, proposatutako aldaketak onartzen badituzu, ezarri itzazu yunohost tools regen-conf nginx --force erabiliz.", + "diagnosis_domain_not_found_details": "{domain} domeinua ez da WHOISen datubasean existitzen edo iraungi da!", + "app_start_backup": "{app}(r)en babes-kopia egiteko fitxategiak hartzen…", + "app_change_url_no_script": "'{app_name}' aplikazioak ez du URLa moldatzerik onartzen momentuz. Agian eguneratu beharko zenuke.", + "app_location_unavailable": "URL hau ez dago erabilgarri edota dagoeneko instalatutako aplikazioren batekin talka egiten du:\n{apps}", + "app_not_upgraded": "'{failed_app}' aplikazioa ezin izan da eguneratu, eta horregatik ondorengo aplikazioen eguneraketak bertan behera utzi dira: {apps}", + "app_not_correctly_installed": "Ez dirudi {app} ondo instalatuta dagoenik", + "app_not_installed": "Ezinezkoa izan da {app} aurkitzea instalatutako aplikazioen zerrendan: {all_apps}", + "app_not_properly_removed": "Ezinezkoa izan da {app} guztiz ezabatzea", + "app_start_install": "{app} instalatzen…", + "app_start_restore": "{app} lehengoratzen…", + "app_unsupported_remote_type": "Aplikazioak darabilen urruneko motak ez du babesik", + "app_upgrade_several_apps": "Ondorengo aplikazioak eguneratuko dira: {apps}", + "backup_app_failed": "Ezinezkoa izan da {app}(r)en babeskopia egitea", + "backup_actually_backuping": "Bildutako fitxategiekin babeskopia sortzen…", + "backup_archive_name_exists": "Dagoeneko existitzen da izen bera duen babeskopia fitxategi bat.", + "backup_archive_name_unknown": "Ez da '{name}' izeneko babeskopia ezagutzen", + "backup_archive_open_failed": "Ezin izan da babeskopien fitxategia ireki", + "backup_archive_system_part_not_available": "'{part}' sistemaren atala ez dago erabilgarri babeskopia honetan", + "backup_archive_writing_error": "Ezin izan da '{source}' ('{dest}' fitxategiak eskatu dituenak) fitxategia '{archive}' konprimatutako babeskopian sartu", + "backup_ask_for_copying_if_needed": "Denbora batez {size}MB erabili nahi dituzu babeskopia gauzatu ahal izateko? (Horrela egiten da fitxategi batzuk ezin direlako modu eraginkorragoan prestatu.)", + "backup_cant_mount_uncompress_archive": "Ezin izan da deskonprimatutako fitxategia muntatu idazketa-babesa duelako", + "backup_created": "Egin da babeskopia", + "backup_copying_to_organize_the_archive": "{size}MB kopiatzen fitxategia antolatzeko", + "backup_couldnt_bind": "Ezin izan da {src} {dest}-ra lotu.", + "backup_output_directory_forbidden": "Aukeratu beste katalogo bat emaitza gordetzeko. Babeskopiak ezin dira sortu /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var or /home/yunohost.backup/archives azpi-katalogoetan", + "backup_output_directory_not_empty": "Aukeratu hutsik dagoen katalogo bat", + "backup_running_hooks": "Babeskopien kakoak exekutatzen…", + "backup_unable_to_organize_files": "Ezin izan da modu azkarra erabili fitxategiko artxiboak prestatzeko", + "backup_output_symlink_dir_broken": "'{path}' fitxategi-katalogoaren symlink-a ez dabil. Agian [ber]muntatzea ahaztu zaizu edo euskarria atakara konektatzea ahaztu duzu.", + "backup_with_no_backup_script_for_app": "'{app}' aplikazioak ez du babeskopia egiteko agindurik. Ez da kontutan hartuko.", + "backup_with_no_restore_script_for_app": "{app}(e)k ez du lehengoratzeko agindurik, ezingo duzu aplikazio hau automatikoki lehengoratu.", + "certmanager_attempt_to_renew_nonLE_cert": "'{domain}' domeinurako ziurtagiria ez da Let's Encryptek jaulkitakoa. Ezin da automatikoki berriztu!", + "certmanager_attempt_to_renew_valid_cert": "'{domain}' domeinurako ziurtagiria iraungitzear dago! (Zertan ari zaren baldin badakizu, --force erabil dezakezu)", + "certmanager_cannot_read_cert": "Arazoren bat egon da {domain} (fitxategia: {file}) domeinurako oraingo ziurtagiria irekitzen saiatzerakoan, arrazoia: {reason}", + "certmanager_cert_install_success": "Let's Encrypt ziurtagiria instalatu da '{domain}' domeinurako", + "certmanager_cert_install_success_selfsigned": "Norberak sinatutako ziurtagiria instalatu da '{domain}' domeinurako", + "certmanager_domain_cert_not_selfsigned": "{domain} domeinurako ziurtagiria ez da norberak sinatutakoa. Ziur al zaude ordezkatzeaz? (Erabili '--force' hori egiteko.)", + "certmanager_certificate_fetching_or_enabling_failed": "{domain} domeinurako ziurtagiri berriak kale egin du…", + "certmanager_domain_http_not_working": "Ez dirudi {domain} domeinua HTTP bidez ikusgai dagoenik. Mesedez, egiaztatu 'Web' kategoria diagnosien gunean informazio gehiagorako. (zertan ari zaren baldin badakizu, erabili '--no-checks' egiaztapen horiek desgaitzeko.)", + "certmanager_hit_rate_limit": "{domain} domeinu-multzurako ziurtagiri gehiegi jaulki dira dagoeneko. Mesedez, saia saitez geroago. Ikus https://letsencrypt.org/docs/rate-limits/ xehetasun gehiagorako", + "certmanager_no_cert_file": "Ezin izan da ziurtagiri fitxategia irakurri {domain} (fitxategia: {file}) domeinurako", + "certmanager_self_ca_conf_file_not_found": "Ezin izan da konfigurazio-fitxategia aurkitu norberak sinatutako ziurtagirirako (fitxategia: {file})", + "confirm_app_install_warning": "Adi: litekeena da aplikazio hau ibiltzea baina ez dago YunoHostera egina. Ezaugarri batzuk, SSO edo babeskopia/lehengoratzea esaterako, desgaituta egon daitezke. Instalatu hala ere? [{answers}] ", + "diagnosis_description_ports": "Ataken irisgarritasuna", + "backup_archive_corrupted": "Badirudi '{archive}' babeskopia fitxategia hondatuta dagoela: {error}", + "diagnosis_ip_local": "IP lokala: {local}", + "diagnosis_mail_blacklist_reason": "Zerrenda beltzean egotearen arrazoia zera da: {reason}", + "app_removed": "{app} desinstalatu da", + "backup_cleaning_failed": "Ezin izan da behin-behineko babeskopien karpeta hustu", + "certmanager_attempt_to_replace_valid_cert": "{domain} domeinurako egokia eta baliogarria den ziurtagiri bat ordezkatzen saiatzen ari zara! (Erabili --force mezu hau deuseztatu eta ziurtagiria ordezkatzeko)", + "diagnosis_backports_in_sources_list": "Dirudienez apt (pakete kudeatzailea) backports biltegia erabiltzeko konfiguratuta dago. Zertan ari zaren ez badakizu, ez zenuke backports biltegietako aplikaziorik instalatu beharko, ezegonkortasun eta gatazkak eragin ditzaketelako sistemarekin.", + "app_restore_failed": "Ezinezkoa izan da {app} lehengoratzea: {error}", + "diagnosis_apps_allgood": "Instalatutako aplikazioek oinarrizko pakete-jarraibideekin bat egiten dute", + "diagnosis_apps_bad_quality": "Aplikazio hau hondatuta dagoela dio YunoHosten aplikazioen katalogoak. Agian behin-behineko kontua da arduradunak arazoa konpondu bitartean. Momentuz, ezin da aplikazioa eguneratu.", + "diagnosis_apps_broken": "Aplikazio hau hondatuta dagoela ageri da YunoHosten aplikazioen katalogoan. Agian, behin-behineko kontua da arduradunak konpondu bitartean. Momentuz, ezin da aplikazioa eguneratu.", + "diagnosis_apps_deprecated_practices": "Instalatutako aplikazio honen bertsioak oraindik darabil zaharkitutako pakete-jarraibideak. Eguneratzea hausnartu beharko zenuke.", + "diagnosis_apps_issue": "Arazo bat dago {app} aplikazioarekin", + "diagnosis_apps_not_in_app_catalog": "Aplikazio hau ez da YunoHosten aplikazioen katalogokoa. Iraganean egon bazen eta ezabatu izan balitz, desinstalatzea litzateke onena, ez baitu eguneraketarik jasoko eta sistemaren integritate eta segurtasuna arriskuan jarri lezake.", + "diagnosis_apps_outdated_ynh_requirement": "Instalatutako aplikazio honen bertsioak yunohost >= 2.x baino ez du behar, eta horrek egungo pakete-jardunbideekin bat ez datorrela iradokitzen du. Eguneratzen saiatu beharko zinateke.", + "diagnosis_description_apps": "Aplikazioak" } From d846adb093c846d5e9b48a2c5b9cbc726a3e1796 Mon Sep 17 00:00:00 2001 From: ppr Date: Mon, 18 Oct 2021 18:11:11 +0000 Subject: [PATCH 0850/1155] Translated using Weblate (French) Currently translated at 100.0% (706 of 706 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 123270bd6..cadc2e9dc 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -685,7 +685,7 @@ "domain_dns_registrar_managed_in_parent_domain": "Ce domaine est un sous-domaine de {parent_domain_link}. La configuration du registrar DNS doit être gérée dans le panneau de configuration de {parent_domain}.", "domain_dns_registrar_not_supported": "YunoHost n'a pas pu détecter automatiquement le bureau d'enregistrement gérant ce domaine. Vous devez configurer manuellement vos enregistrements DNS en suivant la documentation sur https://yunohost.org/dns.", "domain_dns_registrar_experimental": "Jusqu'à présent, l'interface avec l'API de **{registrar}** n'a pas été correctement testée et revue par la communauté YunoHost. L'assistance est **très expérimentale** - soyez prudent !", - "domain_dns_push_failed_to_authenticate": "Échec de l'authentification sur l'API du bureau d'enregistrement pour le domaine « {domain} ». Très probablement les informations d'identification sont incorrectes ? (Erreur : {error})", + "domain_dns_push_failed_to_authenticate": "Échec de l'authentification sur l'API du registrar gérant la réservation de votre nom de domaine internet pour '{domain}'. Il est très probable que les informations d'identification soient incorrectes ? (Erreur : {error})", "domain_dns_push_failed_to_list": "Échec de la liste des enregistrements actuels à l'aide de l'API du registraire : {error}", "domain_dns_push_already_up_to_date": "Dossiers déjà à jour.", "domain_dns_pushing": "Transmission des enregistrements DNS...", @@ -709,5 +709,6 @@ "diagnosis_http_special_use_tld": "Le domaine {domain} est basé sur un domaine de premier niveau (TLD) à usage spécial tel que .local ou .test et n'est donc pas censé être exposé en dehors du réseau local.", "domain_dns_conf_special_use_tld": "Ce domaine est basé sur un domaine de premier niveau (TLD) à usage spécial tel que .local ou .test et ne devrait donc pas avoir d'enregistrements DNS réels.", "other_available_options": "... et {n} autres options disponibles non affichées", - "domain_config_auth_consumer_key": "Consumer key" + "domain_config_auth_consumer_key": "Consumer key", + "domain_unknown": "Domaine '{domain}' inconnu" } From 3ca4cc4f7e5c2deccbb8ded35b5313243b114b56 Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Tue, 19 Oct 2021 12:38:33 +0000 Subject: [PATCH 0851/1155] [CI] Format code with Black --- src/yunohost/utils/config.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index aaed1ffd5..2e0fa5b81 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -216,7 +216,9 @@ class ConfigPanel: self.entity = entity self.config_path = config_path if not config_path: - self.config_path = self.config_path_tpl.format(entity=entity, entity_type=self.entity_type) + self.config_path = self.config_path_tpl.format( + entity=entity, entity_type=self.entity_type + ) self.save_path = save_path if not save_path and self.save_path_tpl: self.save_path = self.save_path_tpl.format(entity=entity) From ebed74514066a6e8e8e86514c19e2c871d46c193 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 19 Oct 2021 15:32:04 +0200 Subject: [PATCH 0852/1155] Update changelog for 4.3.1.7 --- debian/changelog | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/debian/changelog b/debian/changelog index d359526d0..651f20a75 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,12 @@ +yunohost (4.3.1.7) testing; urgency=low + + - [fix] configpanel: Misc technical fixes ... (341059d0, 9c22329e) + - [i18n] Translations updated for Basque, French + + Thanks to all contributors <3 ! (ljf, ppr, punkrockgirl) + + -- Alexandre Aubin Tue, 19 Oct 2021 15:30:50 +0200 + yunohost (4.3.1.6) testing; urgency=low - [fix] configpanel: Various technical fixes (07c1ddce, eae826b2, ff69067d) From a61d02319895e42254006f169ed0e28c07ca53a1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 19 Oct 2021 18:40:57 +0200 Subject: [PATCH 0853/1155] dyndns update: Fix for ipv6-only hosts --- src/yunohost/dyndns.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index e33cf4f22..2ded9909e 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -257,13 +257,17 @@ def dyndns_update( def resolve_domain(domain, rdtype): - # FIXME make this work for IPv6-only hosts too.. 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, raw_msg=True) + dyn_host_ipv4 = result[0] if ok == "ok" and len(result) else None + if not dyn_host_ipv4: + raise YunohostError("Failed to resolve IPv4 for %s ?" % dyn_host, raw_msg=True) + + ok, result = dig(dyn_host, "AAAA") + dyn_host_ipv6 = result[0] if ok == "ok" and len(result) else None + if not dyn_host_ipv6: + raise YunohostError("Failed to resolve IPv6 for %s ?" % dyn_host, raw_msg=True) - ok, result = dig(domain, rdtype, resolvers=[dyn_host_ip]) + ok, result = dig(domain, rdtype, resolvers=[dyn_host_ipv4, dyn_host_ipv6]) if ok == "ok": return result[0] if len(result) else None elif result[0] == "Timeout": From 57081f443317706e39298c781f69b0d0baade63f Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Tue, 19 Oct 2021 16:59:31 +0000 Subject: [PATCH 0854/1155] [CI] Format code with Black --- src/yunohost/dyndns.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 2ded9909e..cdc293421 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -260,12 +260,16 @@ def dyndns_update( ok, result = dig(dyn_host, "A") dyn_host_ipv4 = result[0] if ok == "ok" and len(result) else None if not dyn_host_ipv4: - raise YunohostError("Failed to resolve IPv4 for %s ?" % dyn_host, raw_msg=True) - + raise YunohostError( + "Failed to resolve IPv4 for %s ?" % dyn_host, raw_msg=True + ) + ok, result = dig(dyn_host, "AAAA") dyn_host_ipv6 = result[0] if ok == "ok" and len(result) else None if not dyn_host_ipv6: - raise YunohostError("Failed to resolve IPv6 for %s ?" % dyn_host, raw_msg=True) + raise YunohostError( + "Failed to resolve IPv6 for %s ?" % dyn_host, raw_msg=True + ) ok, result = dig(domain, rdtype, resolvers=[dyn_host_ipv4, dyn_host_ipv6]) if ok == "ok": From 2687121f6da14cb55d123605be3c4bdb7b34a030 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 20 Oct 2021 19:25:23 +0200 Subject: [PATCH 0855/1155] diagnosis: typo / don't run dnsrecords tests for special tlds --- data/hooks/diagnosis/12-dnsrecords.py | 1 + 1 file changed, 1 insertion(+) diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py index 677a947a7..7e681484d 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -63,6 +63,7 @@ class DNSRecordsDiagnoser(Diagnoser): status="INFO", summary="diagnosis_dns_specialusedomain", ) + return base_dns_zone = _get_dns_zone_for_domain(domain) basename = domain.replace(base_dns_zone, "").rstrip(".") or "@" From 146fba7d481676850179e62ed36b7de1d5386838 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 20 Oct 2021 19:43:06 +0200 Subject: [PATCH 0856/1155] regenconf: yunohost hook was failing because grep may return exit code != 0 --- data/hooks/conf_regen/01-yunohost | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index ad10fa863..1aba1f03c 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -61,9 +61,11 @@ do_init_regen() { # 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 + if readlink -f /etc/dpkg/origins/default | grep -q debian; + then + rm -f /etc/dpkg/origins/default \ + ln -s /etc/dpkg/origins/yunohost /etc/dpkg/origins/default + fi } do_pre_regen() { @@ -238,9 +240,11 @@ do_post_regen() { # 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 + if readlink -f /etc/dpkg/origins/default | grep -q debian; + then + rm -f /etc/dpkg/origins/default + ln -s /etc/dpkg/origins/yunohost /etc/dpkg/origins/default + fi } do_$1_regen ${@:2} From 86a9cb37ce96a0ef83e3eeac6a54970dff5d5401 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 20 Oct 2021 19:47:34 +0200 Subject: [PATCH 0857/1155] regenconf: gotta explicitly return *0* ... --- 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 ee2ff1a1f..13c442158 100755 --- a/data/hooks/conf_regen/43-dnsmasq +++ b/data/hooks/conf_regen/43-dnsmasq @@ -69,7 +69,7 @@ do_post_regen() { short_hostname=$(hostname -s) grep -q "127.0.0.1.*$short_hostname" /etc/hosts || echo -e "\n127.0.0.1\t$short_hostname" >>/etc/hosts - [[ -n "$regen_conf_files" ]] || return + [[ -n "$regen_conf_files" ]] || return 0 # Remove / disable services likely to conflict with dnsmasq for SERVICE in systemd-resolved bind9; do From d82f14a4f91376c44059aa2d798470242ff1921b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 20 Oct 2021 20:01:45 +0200 Subject: [PATCH 0858/1155] Typo T_T --- 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 1aba1f03c..1703dccd1 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -63,7 +63,7 @@ do_init_regen() { # see https://wiki.debian.org/Derivatives/Guidelines#Vendor if readlink -f /etc/dpkg/origins/default | grep -q debian; then - rm -f /etc/dpkg/origins/default \ + rm -f /etc/dpkg/origins/default ln -s /etc/dpkg/origins/yunohost /etc/dpkg/origins/default fi } From 93cc48ca688981f9ad428c9925cc82b2927f8618 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 20 Oct 2021 20:56:33 +0200 Subject: [PATCH 0859/1155] Drop customization of my.cnf, keep default conf --- data/hooks/conf_regen/34-mysql | 2 +- data/templates/mysql/my.cnf | 92 ---------------------------------- 2 files changed, 1 insertion(+), 93 deletions(-) delete mode 100644 data/templates/mysql/my.cnf diff --git a/data/hooks/conf_regen/34-mysql b/data/hooks/conf_regen/34-mysql index da66d5b1c..13730e0bb 100755 --- a/data/hooks/conf_regen/34-mysql +++ b/data/hooks/conf_regen/34-mysql @@ -8,7 +8,7 @@ do_pre_regen() { cd /usr/share/yunohost/templates/mysql - install -D -m 644 my.cnf "${pending_dir}/etc/mysql/my.cnf" + # Nothing to do } do_post_regen() { diff --git a/data/templates/mysql/my.cnf b/data/templates/mysql/my.cnf deleted file mode 100644 index 3da4377e1..000000000 --- a/data/templates/mysql/my.cnf +++ /dev/null @@ -1,92 +0,0 @@ -# Example MySQL config file for small systems. -# -# This is for a system with little memory (<= 64M) where MySQL is only used -# from time to time and it's important that the mysqld daemon -# doesn't use much resources. -# -# MySQL programs look for option files in a set of -# locations which depend on the deployment platform. -# You can copy this option file to one of those -# locations. For information about these locations, see: -# http://dev.mysql.com/doc/mysql/en/option-files.html -# -# In this file, you can use all long options that a program supports. -# If you want to know which options a program supports, run the program -# with the "--help" option. - -# The following options will be passed to all MySQL clients -[client] -#password = your_password -port = 3306 -socket = /var/run/mysqld/mysqld.sock - -# Here follows entries for some specific programs - -# The MySQL server -[mysqld] -port = 3306 -socket = /var/run/mysqld/mysqld.sock -skip-external-locking -key_buffer_size = 16K -max_allowed_packet = 16M -table_open_cache = 4 -sort_buffer_size = 4M -read_buffer_size = 256K -read_rnd_buffer_size = 256K -net_buffer_length = 2K -thread_stack = 128K - -# to avoid corruption on powerfailure -default-storage-engine=innodb - -# Don't listen on a TCP/IP port at all. This can be a security enhancement, -# if all processes that need to connect to mysqld run on the same host. -# All interaction with mysqld must be made via Unix sockets or named pipes. -# Note that using this option without enabling named pipes on Windows -# (using the "enable-named-pipe" option) will render mysqld useless! -# -#skip-networking -server-id = 1 - -# Uncomment the following if you want to log updates -#log-bin=mysql-bin - -# binary logging format - mixed recommended -#binlog_format=mixed - -# Causes updates to non-transactional engines using statement format to be -# written directly to binary log. Before using this option make sure that -# there are no dependencies between transactional and non-transactional -# tables such as in the statement INSERT INTO t_myisam SELECT * FROM -# t_innodb; otherwise, slaves may diverge from the master. -#binlog_direct_non_transactional_updates=TRUE - -# Uncomment the following if you are using InnoDB tables -#innodb_data_home_dir = /var/lib/mysql -#innodb_data_file_path = ibdata1:10M:autoextend -#innodb_log_group_home_dir = /var/lib/mysql -# You can set .._buffer_pool_size up to 50 - 80 % -# of RAM but beware of setting memory usage too high -#innodb_buffer_pool_size = 16M -#innodb_additional_mem_pool_size = 2M -# Set .._log_file_size to 25 % of buffer pool size -#innodb_log_file_size = 5M -#innodb_log_buffer_size = 8M -#innodb_flush_log_at_trx_commit = 1 -#innodb_lock_wait_timeout = 50 - -[mysqldump] -quick -max_allowed_packet = 16M - -[mysql] -no-auto-rehash -# Remove the next comment character if you are not familiar with SQL -#safe-updates - -[myisamchk] -key_buffer_size = 8M -sort_buffer_size = 8M - -[mysqlhotcopy] -interactive-timeout From 4dc53d5a8e49447d5368b699676c41eee9ac0e9e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 20 Oct 2021 21:07:53 +0200 Subject: [PATCH 0860/1155] migrate_to_bullseye: get rid of custom my.cnf --- .../data_migrations/0021_migrate_to_bullseye.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py index 4b40fcbe0..db10777bf 100644 --- a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py +++ b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py @@ -5,11 +5,11 @@ from moulinette import m18n from yunohost.utils.error import YunohostError from moulinette.utils.log import getActionLogger from moulinette.utils.process import check_output, call_async_output -from moulinette.utils.filesystem import read_file +from moulinette.utils.filesystem import read_file, rm from yunohost.tools import Migration, tools_update, tools_upgrade from yunohost.app import unstable_apps -from yunohost.regenconf import manually_modified_files +from yunohost.regenconf import manually_modified_files, _force_clear_hashes from yunohost.utils.filesystem import free_space_in_directory from yunohost.utils.packages import ( get_ynh_package_version, @@ -67,6 +67,16 @@ class MyMigration(Migration): self.patch_yunohost_conflicts() + # + # Specific tweaking to get rid of custom my.cnf and use debian's default one + # (my.cnf is actually a symlink to mariadb.cnf) + # + + _force_clear_hashes(["/etc/mysql/my.cnf"]) + rm("/etc/mysql/mariadb.cnf", force=True) + rm("/etc/mysql/my.cnf", force=True) + self.apt_install("mariadb-common --reinstall -o Dpkg::Options::='--force-confmiss'") + # # Main upgrade # From 4ebcaf8d7e9467ccf319db0d2bd238a2dc48db6d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 24 Oct 2021 15:59:48 +0200 Subject: [PATCH 0861/1155] [fix] helpers: composer not explaining why the hell it can't install shit --- 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 79c69b50c..42fed7707 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -496,7 +496,7 @@ ynh_composer_exec() { COMPOSER_HOME="$workdir/.composer" COMPOSER_MEMORY_LIMIT=-1 \ php${phpversion} "$workdir/composer.phar" $commands \ - -d "$workdir" --quiet --no-interaction + -d "$workdir" --no-interaction --no-ansi 2>&1 } # Install and initialize Composer in the given directory From 4e917b5e964b043dd9223eac9f014aebac0dbfd0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 24 Oct 2021 16:09:34 +0200 Subject: [PATCH 0862/1155] [enh] logging: we don't care about 'update-rc.d: warning: start and stop actions are no longer supported; falling back to defaults' --- src/yunohost/hook.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index 20757bf3c..98b624f12 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -359,6 +359,7 @@ def hook_exec( r"Created symlink /etc/systemd", r"dpkg: warning: while removing .* not empty so not removed", r"apt-key output should not be parsed", + r"update-rc.d: ", ] return all(not re.search(w, msg) for w in irrelevant_warnings) From 974ea71fc84bfc3b3f5e1fc64d3a97e2914449ff Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 24 Oct 2021 16:59:23 +0200 Subject: [PATCH 0863/1155] Simplify the whole ynh_install/remove_php story as no app is using them directly --- data/helpers.d/php | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index 42fed7707..b0e9fa59d 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -95,17 +95,8 @@ ynh_add_fpm_config() { ynh_remove_fpm_config 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 + # Legacy args (packager should just list their php dependency as regular apt dependencies... + if [ -n "$package" ]; then # Install the additionnal packages from the default repository ynh_install_app_dependencies "$package" fi @@ -289,8 +280,8 @@ ynh_remove_fpm_config() { # 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 + # Remove app dependencies ... but ideally should happen via an explicit call from packager + ynh_remove_app_dependencies fi } @@ -298,6 +289,8 @@ ynh_remove_fpm_config() { # # [internal] # +# Legacy, to be remove on bullseye +# # usage: ynh_install_php --phpversion=phpversion [--package=packages] # | arg: -v, --phpversion= - Version of PHP to install. # | arg: -p, --package= - Additionnal PHP packages to install @@ -318,14 +311,15 @@ ynh_install_php() { fi ynh_install_app_dependencies "$package" - ynh_app_setting_set --app=$app --key=phpversion --value=$specific_php_version } # Remove the specific version of PHP used by the app. # # [internal] # -# usage: ynh_install_php +# Legacy, to be remove on bullseye +# +# usage: ynh_remove_php # # Requires YunoHost version 3.8.1 or higher. ynh_remove_php () { From fcd2ef9d2086fe12c95a2ca958cd086c98a1eff3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 24 Oct 2021 18:16:21 +0200 Subject: [PATCH 0864/1155] [enh] helpers: allow to get/set/delete app settings without explicitly passing app id everytime... --- data/helpers.d/setting | 6 +++++ tests/test_helpers.d/ynhtest_settings.sh | 29 ++++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 tests/test_helpers.d/ynhtest_settings.sh diff --git a/data/helpers.d/setting b/data/helpers.d/setting index cd231c6ba..a2cf3a93d 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -8,6 +8,7 @@ # # Requires YunoHost version 2.2.4 or higher. ynh_app_setting_get() { + local _globalapp=${app-:} # Declare an array to define the options of this helper. local legacy_args=ak local -A args_array=([a]=app= [k]=key=) @@ -15,6 +16,7 @@ ynh_app_setting_get() { local key # Manage arguments with getopts ynh_handle_getopts_args "$@" + app="${app:-$_globalapp}" if [[ $key =~ (unprotected|protected|skipped)_ ]]; then yunohost app setting $app $key @@ -32,6 +34,7 @@ ynh_app_setting_get() { # # Requires YunoHost version 2.2.4 or higher. ynh_app_setting_set() { + local _globalapp=${app-:} # Declare an array to define the options of this helper. local legacy_args=akv local -A args_array=([a]=app= [k]=key= [v]=value=) @@ -40,6 +43,7 @@ ynh_app_setting_set() { local value # Manage arguments with getopts ynh_handle_getopts_args "$@" + app="${app:-$_globalapp}" if [[ $key =~ (unprotected|protected|skipped)_ ]]; then yunohost app setting $app $key -v $value @@ -56,6 +60,7 @@ ynh_app_setting_set() { # # Requires YunoHost version 2.2.4 or higher. ynh_app_setting_delete() { + local _globalapp=${app-:} # Declare an array to define the options of this helper. local legacy_args=ak local -A args_array=([a]=app= [k]=key=) @@ -63,6 +68,7 @@ ynh_app_setting_delete() { local key # Manage arguments with getopts ynh_handle_getopts_args "$@" + app="${app:-$_globalapp}" if [[ "$key" =~ (unprotected|skipped|protected)_ ]]; then yunohost app setting $app $key -d diff --git a/tests/test_helpers.d/ynhtest_settings.sh b/tests/test_helpers.d/ynhtest_settings.sh new file mode 100644 index 000000000..e916c146b --- /dev/null +++ b/tests/test_helpers.d/ynhtest_settings.sh @@ -0,0 +1,29 @@ +ynhtest_settings() { + + test -n "$app" + + mkdir -p "/etc/yunohost/apps/$app" + echo "label: $app" > "/etc/yunohost/apps/$app/settings.yml" + + test -z "$(ynh_app_setting_get --key="foo")" + test -z "$(ynh_app_setting_get --key="bar")" + test -z "$(ynh_app_setting_get --app="$app" --key="baz")" + + ynh_app_setting_set --key="foo" --value="foovalue" + ynh_app_setting_set --app="$app" --key="bar" --value="barvalue" + ynh_app_setting_set "$app" baz bazvalue + + test "$(ynh_app_setting_get --key="foo")" == "foovalue" + test "$(ynh_app_setting_get --key="bar")" == "barvalue" + test "$(ynh_app_setting_get --app="$app" --key="baz")" == "bazvalue" + + ynh_app_setting_delete --key="foo" + ynh_app_setting_delete --app="$app" --key="bar" + ynh_app_setting_delete "$app" baz + + test -z "$(ynh_app_setting_get --key="foo")" + test -z "$(ynh_app_setting_get --key="bar")" + test -z "$(ynh_app_setting_get --app="$app" --key="baz")" + + rm -rf "/etc/yunohost/apps/$app" +} From a8c6b5e6379727bd74c6dd9889f48de314de2e49 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 24 Oct 2021 18:17:02 +0200 Subject: [PATCH 0865/1155] [enh] helpers: add some tests for system user create that were lying in my uncommited files ? --- tests/test_helpers.d/ynhtest_user.sh | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 tests/test_helpers.d/ynhtest_user.sh diff --git a/tests/test_helpers.d/ynhtest_user.sh b/tests/test_helpers.d/ynhtest_user.sh new file mode 100644 index 000000000..46f2a0cd6 --- /dev/null +++ b/tests/test_helpers.d/ynhtest_user.sh @@ -0,0 +1,25 @@ + +ynhtest_system_user_create() { + username=$(head -c 12 /dev/urandom | md5sum | head -c 12) + + ! ynh_system_user_exists --username="$username" + + ynh_system_user_create --username="$username" + + ynh_system_user_exists --username="$username" + + ynh_system_user_delete --username="$username" + + ! ynh_system_user_exists --username="$username" +} + +ynhtest_system_user_with_group() { + username=$(head -c 12 /dev/urandom | md5sum | head -c 12) + + ynh_system_user_create --username="$username" --groups="ssl-cert,ssh.app" + + grep -q "^ssl-cert:.*$username" /etc/group + grep -q "^ssh.app:.*$username" /etc/group + + ynh_system_user_delete --username="$username" +} From c6dfe08973edd90ebc8c483d645b416172555e93 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 24 Oct 2021 22:16:07 +0200 Subject: [PATCH 0866/1155] Drop some dyndns yagni: we don't need to be able to specify a custom dyndns host... --- data/actionsmap/yunohost.yml | 6 -- src/yunohost/domain.py | 7 ++- src/yunohost/dyndns.py | 113 +++++++++++++---------------------- src/yunohost/tools.py | 44 ++++++-------- 4 files changed, 63 insertions(+), 107 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index b845ded21..b88f5260a 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1483,9 +1483,6 @@ dyndns: subscribe: action_help: Subscribe to a DynDNS service arguments: - --subscribe-host: - help: Dynette HTTP API to subscribe to - default: "dyndns.yunohost.org" -d: full: --domain help: Full domain to subscribe with @@ -1499,9 +1496,6 @@ dyndns: update: action_help: Update IP on DynDNS platform arguments: - --dyn-host: - help: Dynette DNS server to inform - default: "dyndns.yunohost.org" -d: full: --domain help: Full domain to update diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 8eb14a36f..0bd84ea82 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -167,15 +167,16 @@ def domain_add(operation_logger, domain, dyndns=False): # DynDNS domain if dyndns: - from yunohost.dyndns import _dyndns_provides, _guess_current_dyndns_domain + from yunohost.utils.dns import is_yunohost_dyndns_domain + from yunohost.dyndns import _guess_current_dyndns_domain # Do not allow to subscribe to multiple dyndns domains... - if _guess_current_dyndns_domain("dyndns.yunohost.org") != (None, None): + if _guess_current_dyndns_domain() != (None, None): 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): + if not is_yunohost_dyndns_domain(domain): raise YunohostValidationError("domain_dyndns_root_unknown") operation_logger.start() diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index cdc293421..1659ec6ea 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -39,7 +39,7 @@ from moulinette.utils.network import download_json from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.domain import _get_maindomain from yunohost.utils.network import get_public_ip -from yunohost.utils.dns import dig +from yunohost.utils.dns import dig, is_yunohost_dyndns_domain from yunohost.log import is_unit_operation from yunohost.regenconf import regen_conf @@ -53,66 +53,36 @@ RE_DYNDNS_PRIVATE_KEY_SHA512 = re.compile( r".*/K(?P[^\s\+]+)\.\+165.+\.private$" ) +DYNDNS_PROVIDER = "dyndns.yunohost.org" +DYNDNS_DNS_AUTH = ["ns0.yunohost.org", "ns1.yunohost.org"] -def _dyndns_provides(provider, domain): + +def _dyndns_available(domain): """ - Checks if a provider provide/manage a given domain. + Checks if a domain is available on dyndns.yunohost.org Keyword arguments: - provider -- The url of the provider, e.g. "dyndns.yunohost.org" - domain -- The full domain that you'd like.. e.g. "foo.nohost.me" - - Returns: - True if the provider provide/manages the domain. False otherwise. - """ - - logger.debug("Checking if %s is managed by %s ..." % (domain, provider)) - - try: - # Dyndomains will be a list of domains supported by the provider - # e.g. [ "nohost.me", "noho.st" ] - dyndomains = download_json("https://%s/domains" % provider, timeout=30) - except MoulinetteError as e: - logger.error(str(e)) - raise YunohostError( - "dyndns_could_not_check_provide", domain=domain, provider=provider - ) - - # Extract 'dyndomain' from 'domain', e.g. 'nohost.me' from 'foo.nohost.me' - dyndomain = ".".join(domain.split(".")[1:]) - - return dyndomain in dyndomains - - -def _dyndns_available(provider, domain): - """ - Checks if a domain is available from a given provider. - - Keyword arguments: - provider -- The url of the provider, e.g. "dyndns.yunohost.org" domain -- The full domain that you'd like.. e.g. "foo.nohost.me" Returns: True if the domain is available, False otherwise. """ - logger.debug("Checking if domain %s is available on %s ..." % (domain, provider)) + logger.debug(f"Checking if domain {domain} is available on {DYNDNS_PROVIDER} ...") try: - r = download_json( - "https://%s/test/%s" % (provider, domain), expected_status_code=None - ) + r = download_json(f"https://{DYNDNS_PROVIDER}/test/{domain}", expected_status_code=None) except MoulinetteError as e: logger.error(str(e)) raise YunohostError( - "dyndns_could_not_check_available", domain=domain, provider=provider + "dyndns_could_not_check_available", domain=domain, provider=DYNDNS_PROVIDER ) - return r == "Domain %s is available" % domain + return r == f"Domain {domain} is available" @is_unit_operation() def dyndns_subscribe( - operation_logger, subscribe_host="dyndns.yunohost.org", domain=None, key=None + operation_logger, domain=None, key=None ): """ Subscribe to a DynDNS service @@ -120,11 +90,9 @@ def dyndns_subscribe( Keyword argument: domain -- Full domain to subscribe with key -- Public DNS key - subscribe_host -- Dynette HTTP API to subscribe to - """ - if _guess_current_dyndns_domain(subscribe_host) != (None, None): + if _guess_current_dyndns_domain() != (None, None): raise YunohostValidationError("domain_dyndns_already_subscribed") if domain is None: @@ -132,13 +100,13 @@ def dyndns_subscribe( operation_logger.related_to.append(("domain", domain)) # Verify if domain is provided by subscribe_host - if not _dyndns_provides(subscribe_host, domain): + if not is_yunohost_dyndns_domain(domain): raise YunohostValidationError( - "dyndns_domain_not_provided", domain=domain, provider=subscribe_host + "dyndns_domain_not_provided", domain=domain, provider=DYNDNS_PROVIDER ) # Verify if domain is available - if not _dyndns_available(subscribe_host, domain): + if not _dyndns_available(domain): raise YunohostValidationError("dyndns_unavailable", domain=domain) operation_logger.start() @@ -167,9 +135,9 @@ def dyndns_subscribe( # Send subscription try: + b64encoded_key = base64.b64encode(key.encode()).decode() r = requests.post( - "https://%s/key/%s?key_algo=hmac-sha512" - % (subscribe_host, base64.b64encode(key.encode()).decode()), + f"https://{DYNDNS_PROVIDER}/key/{b64encoded_key}?key_algo=hmac-sha512", data={"subdomain": domain}, timeout=30, ) @@ -205,7 +173,6 @@ def dyndns_subscribe( @is_unit_operation() def dyndns_update( operation_logger, - dyn_host="dyndns.yunohost.org", domain=None, key=None, ipv4=None, @@ -218,7 +185,6 @@ def dyndns_update( Keyword argument: domain -- Full domain to update - dyn_host -- Dynette DNS server to inform key -- Public DNS key ipv4 -- IP address to send ipv6 -- IPv6 address to send @@ -229,7 +195,7 @@ 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) + (domain, key) = _guess_current_dyndns_domain() if domain is None: raise YunohostValidationError("dyndns_no_domain_registered") @@ -251,33 +217,32 @@ def dyndns_update( logger.debug("Building zone update file ...") lines = [ - "server %s" % dyn_host, - "zone %s" % host, + f"server {DYNDNS_PROVIDER}", + f"zone {host}", ] + auth_resolvers = [] + + for dns_auth in DYNDNS_DNS_AUTH: + for type_ in ["A", "AAAA"]: + + ok, result = dig(dns_auth, type_) + if ok == "ok" and len(result) and result[0]: + auth_resolvers.append(result[0]) + + if not auth_resolvers: + raise YunohostError( + f"Failed to resolve IPv4/IPv6 for {DYNDNS_DNS_AUTH} ?", raw_msg=True + ) + def resolve_domain(domain, rdtype): - ok, result = dig(dyn_host, "A") - dyn_host_ipv4 = result[0] if ok == "ok" and len(result) else None - if not dyn_host_ipv4: - raise YunohostError( - "Failed to resolve IPv4 for %s ?" % dyn_host, raw_msg=True - ) - - ok, result = dig(dyn_host, "AAAA") - dyn_host_ipv6 = result[0] if ok == "ok" and len(result) else None - if not dyn_host_ipv6: - raise YunohostError( - "Failed to resolve IPv6 for %s ?" % dyn_host, raw_msg=True - ) - - ok, result = dig(domain, rdtype, resolvers=[dyn_host_ipv4, dyn_host_ipv6]) + ok, result = dig(domain, rdtype, resolvers=auth_resolvers) if ok == "ok": return result[0] if len(result) else None elif result[0] == "Timeout": logger.debug( - "Timed-out while trying to resolve %s record for %s using %s" - % (rdtype, domain, dyn_host) + f"Timed-out while trying to resolve {rdtype} record for {domain}" ) else: return None @@ -388,19 +353,21 @@ def dyndns_update( ) +# Legacy 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'." ) +# Legacy 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'." ) -def _guess_current_dyndns_domain(dyn_host): +def _guess_current_dyndns_domain(): """ This function tries to guess which domain should be updated by "dyndns_update()" because there's not proper management of the current @@ -423,7 +390,7 @@ def _guess_current_dyndns_domain(dyn_host): # current domain beause that's not the one we want to update..) # If there's only 1 such key found, then avoid doing the request # for nothing (that's very probably the one we want to find ...) - if len(paths) > 1 and _dyndns_available(dyn_host, _domain): + if len(paths) > 1 and _dyndns_available(_domain): continue else: return (_domain, path) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index e89081abd..54d7ffc0e 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -45,7 +45,6 @@ from yunohost.app_catalog import ( _update_apps_catalog, ) from yunohost.domain import domain_add -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 @@ -205,12 +204,12 @@ def tools_postinstall( password -- YunoHost admin password """ + from yunohost.dyndns import _dyndns_available + from yunohost.utils.dns import is_yunohost_dyndns_domain from yunohost.utils.password import assert_password_is_strong_enough from yunohost.domain import domain_main_domain import psutil - dyndns_provider = "dyndns.yunohost.org" - # Do some checks at first if os.path.isfile("/etc/yunohost/installed"): raise YunohostValidationError("yunohost_already_installed") @@ -235,33 +234,28 @@ def tools_postinstall( if not force_password: assert_password_is_strong_enough("admin", password) - if not ignore_dyndns: - # Check if yunohost dyndns can handle the given domain - # (i.e. is it a .nohost.me ? a .noho.st ?) - try: - is_nohostme_or_nohost = _dyndns_provides(dyndns_provider, domain) - # If an exception is thrown, most likely we don't have internet - # connectivity or something. Assume that this domain isn't manageable - # and inform the user that we could not contact the dyndns host server. - except Exception: - logger.warning( - m18n.n("dyndns_provider_unreachable", provider=dyndns_provider) - ) - is_nohostme_or_nohost = False + # If this is a nohost.me/noho.st, actually check for availability + if not ignore_dyndns and is_yunohost_dyndns_domain(domain): + # (Except if the user explicitly said he/she doesn't care about dyndns) + if ignore_dyndns: + dyndns = False + # Check if the domain is available... + else: + try: + available = _dyndns_available(domain) + # If an exception is thrown, most likely we don't have internet + # connectivity or something. Assume that this domain isn't manageable + # and inform the user that we could not contact the dyndns host server. + except Exception: + logger.warning( + m18n.n("dyndns_provider_unreachable", provider="dyndns.yunohost.org") + ) - # If this is a nohost.me/noho.st, actually check for availability - if is_nohostme_or_nohost: - # (Except if the user explicitly said he/she doesn't care about dyndns) - if ignore_dyndns: - dyndns = False - # Check if the domain is available... - elif _dyndns_available(dyndns_provider, domain): + if available: dyndns = True # If not, abort the postinstall else: raise YunohostValidationError("dyndns_unavailable", domain=domain) - else: - dyndns = False else: dyndns = False From f7cea60a303e68ba69226107283860d2f5172ea2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 24 Oct 2021 22:27:11 +0200 Subject: [PATCH 0867/1155] Moar dyndns yagni: we don't need to be able to specify ipv4/ipv6/keyfile during dyndns update --- data/actionsmap/yunohost.yml | 9 --------- src/yunohost/dyndns.py | 30 +++++++++--------------------- 2 files changed, 9 insertions(+), 30 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index b88f5260a..16ea2c5d2 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1501,12 +1501,6 @@ dyndns: help: Full domain to update extra: pattern: *pattern_domain - -k: - full: --key - help: Public DNS key - -i: - full: --ipv4 - help: IP address to send -f: full: --force help: Force the update (for debugging only) @@ -1515,9 +1509,6 @@ dyndns: full: --dry-run help: Only display the generated zone action: store_true - -6: - full: --ipv6 - help: IPv6 address to send ### dyndns_installcron() installcron: diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 1659ec6ea..7027bebaa 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -174,9 +174,6 @@ def dyndns_subscribe( def dyndns_update( operation_logger, domain=None, - key=None, - ipv4=None, - ipv6=None, force=False, dry_run=False, ): @@ -185,15 +182,12 @@ def dyndns_update( Keyword argument: domain -- Full domain to update - key -- Public DNS key - ipv4 -- IP address to send - ipv6 -- IPv6 address to send - """ from yunohost.dns import _build_dns_conf # If domain is not given, try to guess it from keys available... + key = None if domain is None: (domain, key) = _guess_current_dyndns_domain() @@ -201,14 +195,13 @@ def dyndns_update( raise YunohostValidationError("dyndns_no_domain_registered") # If key is not given, pick the first file we find with the domain given - else: - if key is None: - keys = glob.glob("/etc/yunohost/dyndns/K{0}.+*.private".format(domain)) + elif key is None: + keys = glob.glob("/etc/yunohost/dyndns/K{0}.+*.private".format(domain)) - if not keys: - raise YunohostValidationError("dyndns_key_not_found") + if not keys: + raise YunohostValidationError("dyndns_key_not_found") - key = keys[0] + key = keys[0] # Extract 'host', e.g. 'nohost.me' from 'foo.nohost.me' host = domain.split(".")[1:] @@ -267,14 +260,8 @@ def dyndns_update( old_ipv6 = resolve_domain(domain, "AAAA") # Get current IPv4 and IPv6 - ipv4_ = get_public_ip() - ipv6_ = get_public_ip(6) - - if ipv4 is None: - ipv4 = ipv4_ - - if ipv6 is None: - ipv6 = ipv6_ + ipv4 = get_public_ip() + ipv6 = get_public_ip(6) logger.debug("Old IPv4/v6 are (%s, %s)" % (old_ipv4, old_ipv6)) logger.debug("Requested IPv4/v6 are (%s, %s)" % (ipv4, ipv6)) @@ -379,6 +366,7 @@ def _guess_current_dyndns_domain(): # Retrieve the first registered domain paths = list(glob.iglob("/etc/yunohost/dyndns/K*.private")) for path in paths: + # MD5 is legacy ugh match = RE_DYNDNS_PRIVATE_KEY_MD5.match(path) if not match: match = RE_DYNDNS_PRIVATE_KEY_SHA512.match(path) From e8e2cee030c871fa060b1cad5165c85c244215e5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 24 Oct 2021 22:30:10 +0200 Subject: [PATCH 0868/1155] dyndns: During dyndns update, first make sure we have an ipv4 or ipv6 to avoid the remaining code miserably crashing --- src/yunohost/dyndns.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 7027bebaa..80099d811 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -203,6 +203,16 @@ def dyndns_update( key = keys[0] + # Get current IPv4 and IPv6 + ipv4 = get_public_ip() + ipv6 = get_public_ip(6) + + 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 + # Extract 'host', e.g. 'nohost.me' from 'foo.nohost.me' host = domain.split(".")[1:] host = ".".join(host) @@ -259,19 +269,9 @@ def dyndns_update( old_ipv4 = resolve_domain(domain, "A") old_ipv6 = resolve_domain(domain, "AAAA") - # Get current IPv4 and IPv6 - ipv4 = get_public_ip() - ipv6 = get_public_ip(6) - 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 4162557aacc7888ba138d02fcf3553e688b96d69 Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 26 Oct 2021 12:37:08 +0200 Subject: [PATCH 0869/1155] [fix] Mypy CI --- src/yunohost/utils/config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 2e0fa5b81..5d1d1f9d2 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -191,7 +191,7 @@ def evaluate_simple_js_expression(expr, context={}): class ConfigPanel: entity_type = "config" - save_path_tpl = None + save_path_tpl: Union[str, None] = None config_path_tpl = "/usr/share/yunohost/other/config_{entity_type}.toml" save_mode = "full" @@ -705,7 +705,7 @@ class Question(object): # .value is the "proposed" value which we got from the user self.value = question.get("value") # Use to return several values in case answer is in mutipart - self.values = {} + self.values: Dict[str, Any] = {} # Empty value is parsed as empty string if self.default == "": @@ -1318,7 +1318,7 @@ ARGUMENTS_TYPE_PARSERS = { def ask_questions_and_parse_answers( raw_questions: Dict, prefilled_answers: Union[str, Mapping[str, Any]] = {}, - current_values: Union[str, Mapping[str, Any]] = {}, + current_values: Mapping[str, Any] = {}, hooks: Dict[str, Callable[[], None]] = {}, ) -> List[Question]: """Parse arguments store in either manifest.json or actions.json or from a From afbcc274439653538f69121484af6f84b69a342f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 1 Nov 2021 17:06:56 +0100 Subject: [PATCH 0870/1155] Unused i18n string --- locales/en.json | 1 - 1 file changed, 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 2c9652b9c..81e75eb32 100644 --- a/locales/en.json +++ b/locales/en.json @@ -349,7 +349,6 @@ "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_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", From 93f1ab2b34bf98ac5ebafe000fe7c04ea45b206c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 1 Nov 2021 18:19:23 +0100 Subject: [PATCH 0871/1155] Tweak debian control file to make php, mysql, metronome ~optional (but recommended) --- debian/control | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/debian/control b/debian/control index fe18b1de8..a1f84d579 100644 --- a/debian/control +++ b/debian/control @@ -16,8 +16,6 @@ Depends: ${python3:Depends}, ${misc:Depends} , python3-toml, python3-packaging, python3-publicsuffix, , 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 , openssh-server, iptables, fail2ban, dnsutils, bind9utils , openssl, ca-certificates, netcat-openbsd, iproute2 , slapd, ldap-utils, sudo-ldap, libnss-ldapd, unscd, libpam-ldapd @@ -26,17 +24,19 @@ Depends: ${python3:Depends}, ${misc:Depends} , dovecot-core, dovecot-ldap, dovecot-lmtpd, dovecot-managesieved, dovecot-antispam , rspamd, opendkim-tools, postsrsd, procmail, mailutils , redis-server - , metronome (>=3.14.0) , acl , git, curl, wget, cron, unzip, jq, bc, at , lsb-release, haveged, fake-hwclock, equivs, lsof, whois Recommends: yunohost-admin , ntp, inetutils-ping | iputils-ping , bash-completion, rsyslog + , php7.3-common, php7.3-fpm, php7.3-ldap, php7.3-intl + , mariadb-server, php7.3-mysql , php7.3-gd, php7.3-curl, php-gettext , python3-pip , unattended-upgrades , libdbd-ldap-perl, libnet-dns-perl + , metronome (>=3.14.0) Suggests: htop, vim, rsync, acpi-support-base, udisks2 Conflicts: iptables-persistent , apache2 From 00bd7f16a5f4838e653b9f0c5045c56d89a9ed2c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 1 Nov 2021 18:20:42 +0100 Subject: [PATCH 0872/1155] Tweak regenconf scripts for mysql, metronome, add one for postgresql --- data/helpers.d/postgresql | 31 +-------- data/hooks/conf_regen/12-metronome | 6 ++ data/hooks/conf_regen/34-mysql | 6 ++ data/hooks/conf_regen/35-postgresql | 66 ++++++++++++++++++++ data/hooks/conf_regen/{35-redis => 36-redis} | 0 5 files changed, 79 insertions(+), 30 deletions(-) create mode 100755 data/hooks/conf_regen/35-postgresql rename data/hooks/conf_regen/{35-redis => 36-redis} (100%) diff --git a/data/helpers.d/postgresql b/data/helpers.d/postgresql index 992474dd5..0e8d91936 100644 --- a/data/helpers.d/postgresql +++ b/data/helpers.d/postgresql @@ -292,34 +292,5 @@ ynh_psql_test_if_first_run() { # Make sure postgresql is indeed 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 --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 - # 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 --quiet - - # If this is the very first time, we define the root password - # and configure a few things - if [ ! -f "$PSQL_ROOT_PWD_FILE" ]; then - 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 + yunohost tools regen-conf postgresql } diff --git a/data/hooks/conf_regen/12-metronome b/data/hooks/conf_regen/12-metronome index 5dfa7b5dc..c3c5ff828 100755 --- a/data/hooks/conf_regen/12-metronome +++ b/data/hooks/conf_regen/12-metronome @@ -2,6 +2,12 @@ set -e +if ! dpkg --list | grep -q 'ii *metronome ' +then + echo 'metronome is not installed, skipping' + exit 0 +fi + do_pre_regen() { pending_dir=$1 diff --git a/data/hooks/conf_regen/34-mysql b/data/hooks/conf_regen/34-mysql index 8b4d59288..c9115017d 100755 --- a/data/hooks/conf_regen/34-mysql +++ b/data/hooks/conf_regen/34-mysql @@ -3,6 +3,12 @@ set -e . /usr/share/yunohost/helpers +if ! dpkg --list | grep -q 'ii *mariadb-server ' +then + echo 'mysql/mariadb is not installed, skipping' + exit 0 +fi + do_pre_regen() { pending_dir=$1 diff --git a/data/hooks/conf_regen/35-postgresql b/data/hooks/conf_regen/35-postgresql new file mode 100755 index 000000000..0da0767cc --- /dev/null +++ b/data/hooks/conf_regen/35-postgresql @@ -0,0 +1,66 @@ +#!/bin/bash + +set -e +. /usr/share/yunohost/helpers + +if ! dpkg --list | grep -q "ii *postgresql-$PSQL_VERSION " +then + echo 'postgresql is not installed, skipping' + exit 0 +fi + +if [ ! -e "/etc/postgresql/$PSQL_VERSION" ] +then + 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" +fi + + +do_pre_regen() { + return 0 +} + +do_post_regen() { + regen_conf_files=$1 + + # 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 --quiet + + # If this is the very first time, we define the root password + # and configure a few things + if [ ! -f "$PSQL_ROOT_PWD_FILE" ] || [ -z "$(cat $PSQL_ROOT_PWD_FILE)" ]; then + ynh_string_random >$PSQL_ROOT_PWD_FILE + fi + + sudo --login --user=postgres psql -c"ALTER user postgres WITH PASSWORD '$(cat $PSQL_ROOT_PWD_FILE)'" 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 + local pg_hba=/etc/postgresql/$PSQL_VERSION/main/pg_hba.conf + ynh_replace_string --match_string="local\(\s*\)all\(\s*\)all\(\s*\)peer" --replace_string="local\1all\2all\3md5" --target_file="$pg_hba" + + ynh_systemd_action --service_name=postgresql --action=reload +} + +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/35-redis b/data/hooks/conf_regen/36-redis similarity index 100% rename from data/hooks/conf_regen/35-redis rename to data/hooks/conf_regen/36-redis From 2282d0df61fa85e6f70fc57a425e3ba5cc0f2090 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 1 Nov 2021 18:35:36 +0100 Subject: [PATCH 0873/1155] Add sury by default in the core... --- data/helpers.d/apt | 33 --------------------------------- data/hooks/conf_regen/10-apt | 11 +++++++++++ 2 files changed, 11 insertions(+), 33 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index cea850f6e..3d06608ac 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -262,8 +262,6 @@ ynh_install_app_dependencies() { || ynh_die --message="Inconsistent php versions in dependencies ... found : $specific_php_version" dependencies+=", php${specific_php_version}, php${specific_php_version}-fpm, php${specific_php_version}-common" - - ynh_add_sury fi @@ -285,23 +283,6 @@ ynh_install_app_dependencies() { dependencies="$current_dependencies, $dependencies" 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 - # https://github.com/YunoHost/issues/issues/1407 - # - # If we require to install php dependency - if grep --quiet 'php' <<< "$dependencies"; 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 --quiet --invert-match "7.0.33-0+deb9"; then - # And sury ain't already in sources.lists - if ! grep --recursive --quiet "^ *deb.*sury" /etc/apt/sources.list*; then - # Re-add sury - ynh_add_sury - fi - fi - fi - cat >/tmp/${dep_app}-ynh-deps.control <>"${pending_dir}/etc/apt/preferences.d/ban_packages" + # Add sury + mkdir -p ${pending_dir}/etc/apt/sources.list.d/ + echo "deb https://packages.sury.org/php/ $(ynh_get_debian_release) main" > "${pending_dir}/etc/apt/sources.list.d/extra_php_version.list" + } do_post_regen() { regen_conf_files=$1 + # Add sury key + # We do this only at the post regen and if the key doesn't already exists, because we don't want the regenconf to fuck everything up if the regenconf runs while the network is down + if [[ ! -s /etc/apt/trusted.gpg.d/extra_php_version.gpg ]] + then + wget --timeout 900 --quiet "https://packages.sury.org/php/apt.gpg" --output-document=- | gpg --dearmor >"/etc/apt/trusted.gpg.d/extra_php_version.gpg" + fi + # Make sure php7.3 is the default version when using php in cli update-alternatives --set php /usr/bin/php7.3 } From 82f28efaafad73e0db72f1c7e8406f197d87af58 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 1 Nov 2021 18:44:12 +0100 Subject: [PATCH 0874/1155] Make php package always explicitly depend on the PHP version they're using, even if that's debian's default version --- data/helpers.d/apt | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index 3d06608ac..194cae40f 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -252,9 +252,6 @@ ynh_install_app_dependencies() { # The (?<=php) syntax corresponds to lookbehind ;) local specific_php_version=$(echo $dependencies | grep -oP '(?<=php)[0-9.]+(?=-|\>)' | sort -u) - # Ignore case where the php version found is the one available in debian vanilla - [[ "$specific_php_version" != "$YNH_DEFAULT_PHP_VERSION" ]] || specific_php_version="" - if [[ -n "$specific_php_version" ]] then # Cover a small edge case where a packager could have specified "php7.4-pwet php5-gni" which is confusing @@ -301,17 +298,10 @@ EOF if [[ -n "$specific_php_version" ]] then - # Set the default php version back as the default version for php-cli. - update-alternatives --set php /usr/bin/php$YNH_DEFAULT_PHP_VERSION - - # Store phpversion into the config of this app ynh_app_setting_set --app=$app --key=phpversion --value=$specific_php_version - # Integrate new php-fpm service in yunohost - yunohost service add php${specific_php_version}-fpm --log "/var/log/php${phpversion}-fpm.log" - elif grep --quiet 'php' <<< "$dependencies"; then - # Store phpversion into the config of this app - ynh_app_setting_set --app=$app --key=phpversion --value=$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 fi } From 4ccd718183e5646378c17d407d275e1502fc58bb Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 1 Nov 2021 19:59:35 +0100 Subject: [PATCH 0875/1155] Improve handling of optional services/packages + dynamically handle the list of php-fpm versions --- data/templates/yunohost/services.yml | 15 ++++++++++---- src/yunohost/service.py | 29 +++++++++++++++------------- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/data/templates/yunohost/services.yml b/data/templates/yunohost/services.yml index c781d54aa..967025d19 100644 --- a/data/templates/yunohost/services.yml +++ b/data/templates/yunohost/services.yml @@ -12,24 +12,31 @@ metronome: log: [/var/log/metronome/metronome.log,/var/log/metronome/metronome.err] needs_exposed_ports: [5222, 5269] category: xmpp + ignore_if_package_is_not_installed: metronome mysql: log: [/var/log/mysql.log,/var/log/mysql.err,/var/log/mysql/error.log] actual_systemd_service: mariadb category: database + ignore_if_package_is_not_installed: mariadb-server nginx: log: /var/log/nginx test_conf: nginx -t needs_exposed_ports: [80, 443] category: web -php7.3-fpm: - log: /var/log/php7.3-fpm.log - test_conf: php-fpm7.3 --test - category: web +# Yunohost will dynamically add installed php-fpm services (7.3, 7.4, 8.0, ...) in services.py +#php7.3-fpm: +# log: /var/log/php7.3-fpm.log +# test_conf: php-fpm7.3 --test +# category: web postfix: log: [/var/log/mail.log,/var/log/mail.err] actual_systemd_service: postfix@- needs_exposed_ports: [25, 587] category: email +postgresql: + actual_systemd_service: 'postgresql@11-main' + category: database + ignore_if_package_is_not_installed: postgresql-11 redis-server: log: /var/log/redis/redis-server.log category: database diff --git a/src/yunohost/service.py b/src/yunohost/service.py index f200d08c0..26d189d95 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -123,7 +123,7 @@ def service_add( # Try to get the description from systemd service _, systemd_info = _get_service_information_from_systemd(name) type_ = systemd_info.get("Type") if systemd_info is not None else "" - if type_ == "oneshot" and name != "postgresql": + 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." ) @@ -738,13 +738,20 @@ def _get_services(): 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) - if "postgresql" in services: - if "description" in services["postgresql"]: - del services["postgresql"]["description"] - services["postgresql"]["actual_systemd_service"] = "postgresql@11-main" + services_with_package_condition = [name for name, infos in services.items() if infos.get("ignore_if_package_is_not_installed")] + for name in services_with_package_condition: + package = services[name]["ignore_if_package_is_not_installed"] + if os.system(f"dpkg --list | grep -q 'ii *{package}'") != 0: + del services[name] + + php_fpm_versions = check_output(r"dpkg --list | grep -P 'ii php\d.\d-fpm' | awk '{print $2}' | grep -o -P '\d.\d'") + php_fpm_versions = [v for v in php_fpm_versions.split('\n') if v.strip()] + for version in php_fpm_versions: + services[f"php{version}-fpm"] = { + "log": f"/var/log/php{version}-fpm.log", + "test_conf": f"php-fpm{version} --test", # ofc the service is phpx.y-fpm but the program is php-fpmx.y because why not ... + "category": "web" + } # Remove legacy /var/log/daemon.log and /var/log/syslog from log entries # because they are too general. Instead, now the journalctl log is @@ -864,11 +871,7 @@ def _get_journalctl_logs(service, number="all"): services = _get_services() systemd_service = services.get(service, {}).get("actual_systemd_service", service) try: - return check_output( - "journalctl --no-hostname --no-pager -u {0} -n{1}".format( - systemd_service, number - ) - ) + return check_output(f"journalctl --no-hostname --no-pager -u {systemd_service} -n{number}") except Exception: import traceback From b46a94a41dd6725653b2206b6d3e4ae80d2aa02c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 1 Nov 2021 20:14:12 +0100 Subject: [PATCH 0876/1155] Automagically run ynh_psql_if_first_run / regenconf upon postgresl install --- data/helpers.d/apt | 9 +++++++++ data/helpers.d/postgresql | 2 ++ 2 files changed, 11 insertions(+) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index 194cae40f..ecad3b14a 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -261,6 +261,7 @@ ynh_install_app_dependencies() { dependencies+=", php${specific_php_version}, php${specific_php_version}-fpm, php${specific_php_version}-common" fi + local psql_installed2="$(dpkg --list | grep -q "ii *postgresql-$PSQL_VERSION" && echo yes || echo no)" # The first time we run ynh_install_app_dependencies, we will replace the # entire control file (This is in particular meant to cover the case of @@ -303,6 +304,14 @@ EOF # Set the default php version back as the default version for php-cli. update-alternatives --set php /usr/bin/php$YNH_DEFAULT_PHP_VERSION fi + + # Trigger postgresql regenconf if we may have just installed postgresql + local psql_installed2="$(dpkg --list | grep -q "ii *postgresql-$PSQL_VERSION" && echo yes || echo no)" + if [[ "$psql_installed" != "$psql_installed2" ]] + then + yunohost tools regen-conf postgresql + fi + } # Add dependencies to install with ynh_install_app_dependencies diff --git a/data/helpers.d/postgresql b/data/helpers.d/postgresql index 0e8d91936..563d3e7be 100644 --- a/data/helpers.d/postgresql +++ b/data/helpers.d/postgresql @@ -281,6 +281,8 @@ ynh_psql_remove_db() { # Create a master password and set up global settings # +# [internal] +# # usage: ynh_psql_test_if_first_run # # It also make sure that postgresql is installed and running From 56f7cd87ecacc3b73d3994b1d13f9022133d9c06 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 1 Nov 2021 21:00:35 +0100 Subject: [PATCH 0877/1155] regenconf apt: ynh_get_debian_release undefined + reorder for clarity --- data/hooks/conf_regen/10-apt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/data/hooks/conf_regen/10-apt b/data/hooks/conf_regen/10-apt index d67951823..06662375d 100755 --- a/data/hooks/conf_regen/10-apt +++ b/data/hooks/conf_regen/10-apt @@ -7,6 +7,11 @@ do_pre_regen() { mkdir --parents "${pending_dir}/etc/apt/preferences.d" + # Add sury + mkdir -p ${pending_dir}/etc/apt/sources.list.d/ + echo "deb https://packages.sury.org/php/ $(lsb_release --codename --short) main" > "${pending_dir}/etc/apt/sources.list.d/extra_php_version.list" + + # Ban some packages from sury packages_to_refuse_from_sury="php php-fpm php-mysql php-xml php-zip php-mbstring php-ldap php-gd php-curl php-bz2 php-json php-sqlite3 php-intl openssl libssl1.1 libssl-dev" for package in $packages_to_refuse_from_sury; do echo " @@ -15,6 +20,7 @@ Pin: origin \"packages.sury.org\" Pin-Priority: -1" >>"${pending_dir}/etc/apt/preferences.d/extra_php_version" done + # Ban some packages that users may inadvertendly try to install such as apache2 ... echo " # PLEASE READ THIS WARNING AND DON'T EDIT THIS FILE @@ -44,9 +50,6 @@ Pin: release * Pin-Priority: -1 " >>"${pending_dir}/etc/apt/preferences.d/ban_packages" - # Add sury - mkdir -p ${pending_dir}/etc/apt/sources.list.d/ - echo "deb https://packages.sury.org/php/ $(ynh_get_debian_release) main" > "${pending_dir}/etc/apt/sources.list.d/extra_php_version.list" } From 019d207c2f22616001dea719a0e4b0d18c647221 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 1 Nov 2021 21:09:29 +0100 Subject: [PATCH 0878/1155] helpers: Don't say the 'app was restored' when restore failed after failed upgrade --- data/helpers.d/backup | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/backup b/data/helpers.d/backup index 27ffa015c..98a61c16a 100644 --- a/data/helpers.d/backup +++ b/data/helpers.d/backup @@ -479,7 +479,12 @@ ynh_restore_upgradebackup() { yunohost app remove $app # Restore the backup 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." + if [[ -d /etc/yunohost/apps/$app ]] + then + ynh_die --message="The app was restored to the way it was before the failed upgrade." + elif + ynh_die --message="Uhoh ... Yunohost failed to restore the app to the way it was before the failed upgrade :|" + fi fi else ynh_print_warn --message="\$NO_BACKUP_UPGRADE is set, that means there's no backup to restore. You have to fix this upgrade by yourself !" From a97b282d97dd01b6fa784eba729edb4d9142cbdb Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Mon, 1 Nov 2021 20:34:04 +0000 Subject: [PATCH 0879/1155] [CI] Format code with Black --- src/yunohost/dyndns.py | 8 ++++---- src/yunohost/tools.py | 4 +++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 80099d811..4f4dc1d1f 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -70,7 +70,9 @@ def _dyndns_available(domain): logger.debug(f"Checking if domain {domain} is available on {DYNDNS_PROVIDER} ...") try: - r = download_json(f"https://{DYNDNS_PROVIDER}/test/{domain}", expected_status_code=None) + r = download_json( + f"https://{DYNDNS_PROVIDER}/test/{domain}", expected_status_code=None + ) except MoulinetteError as e: logger.error(str(e)) raise YunohostError( @@ -81,9 +83,7 @@ def _dyndns_available(domain): @is_unit_operation() -def dyndns_subscribe( - operation_logger, domain=None, key=None -): +def dyndns_subscribe(operation_logger, domain=None, key=None): """ Subscribe to a DynDNS service diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 54d7ffc0e..fb9839814 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -248,7 +248,9 @@ def tools_postinstall( # and inform the user that we could not contact the dyndns host server. except Exception: logger.warning( - m18n.n("dyndns_provider_unreachable", provider="dyndns.yunohost.org") + m18n.n( + "dyndns_provider_unreachable", provider="dyndns.yunohost.org" + ) ) if available: From 69ca64d333302e0ec61fddae16de8639efa7b827 Mon Sep 17 00:00:00 2001 From: punkrockgirl Date: Wed, 20 Oct 2021 16:00:17 +0000 Subject: [PATCH 0880/1155] Translated using Weblate (Basque) Currently translated at 66.1% (467 of 706 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eu/ --- locales/eu.json | 239 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 236 insertions(+), 3 deletions(-) diff --git a/locales/eu.json b/locales/eu.json index 99735e9ce..4dc280e43 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -35,7 +35,7 @@ "backup_no_uncompress_archive_dir": "Ez dago horrelako deskonprimatutako fitxategi katalogorik", "danger": "Arriskua:", "diagnosis_dns_discrepancy": "Ez dirudi ondorengo DNS balioak bat datozenik proposatutako konfigurazioarekin:
Mota: {type}
Izena: {name}
Oraingo balioa: {current}
Proposatutako balioa: {value}", - "diagnosis_dns_specialusedomain": "{domain} domeinua top-level domain (TLD) erabilera berezikoa da .local edo .test bezala eta horregatik ez du DNS erregistrorik erabiltzen.", + "diagnosis_dns_specialusedomain": "{domain} domeinua top-level domain (TLD) erabilera berezikoa da .local edo .test bezala eta horregatik ez du DNS erregistrorik erabiltzeko beharrik.", "diagnosis_http_bad_status_code": "Badirudi zerbitzaria ez den beste gailu batek erantzun diola eskaerari (agian routerrak).
1. Honen arrazoi ohikoena 80 (eta 443) ataka zerbitzarira ondo birbidaltzen ez dela da.
2. Konfigurazio konplexua badarabilzu, egiaztatu suebakiak edo reverse-proxyk oztopatzen ez dutela.", "diagnosis_http_timeout": "Denbora agortu da sare lokaletik kanpo zure zerbitzarira konexioa gauzatzeko ahaleginean. Eskuragarri ez dagoela dirudi.
1. 80 (eta 443) ataka zerbitzarira modu egokian birbidaltzen ez direla da ohiko arrazoia.
2. Badaezpada egiaztatu nginx martxan dagoela.
3. Konfigurazio konplexuetan, egiaztatu suebakiak edo reverse-proxyk konexioa oztopatzen ez dutela.", "app_sources_fetch_failed": "Ezin izan dira fitxategiak eskuratu, zuzena al da URLa?", @@ -143,7 +143,7 @@ "apps_catalog_init_success": "Abiarazi da aplikazioen katalogo sistema!", "apps_catalog_obsolete_cache": "Aplikazioen katalogoaren cachea hutsik edo zaharkituta dago.", "diagnosis_description_mail": "Posta elektronikoa", - "diagnosis_http_connection_error": "Arazoa konexioan: ezin izan da domeinu horretara konektatu, litekeena da eskuragaitza izatea.", + "diagnosis_http_connection_error": "Arazoa konexioan: ezin izan da domeinu horretara konektatu, litekeena da eskuragarri ez egotea.", "diagnosis_description_web": "Weba", "diagnosis_display_tip": "Aurkitu diren arazoak ikusteko joan Diagnosien atalera administrazio-webgunean, edo exekutatu 'yunohost diagnosis show --issues --human-readable' komandoak nahiago badituzu.", "diagnosis_dns_point_to_doc": "Mesedez, irakurri dokumentazioa DNS erregistroekin laguntza behar baduzu.", @@ -244,5 +244,238 @@ "diagnosis_apps_issue": "Arazo bat dago {app} aplikazioarekin", "diagnosis_apps_not_in_app_catalog": "Aplikazio hau ez da YunoHosten aplikazioen katalogokoa. Iraganean egon bazen eta ezabatu izan balitz, desinstalatzea litzateke onena, ez baitu eguneraketarik jasoko eta sistemaren integritate eta segurtasuna arriskuan jarri lezake.", "diagnosis_apps_outdated_ynh_requirement": "Instalatutako aplikazio honen bertsioak yunohost >= 2.x baino ez du behar, eta horrek egungo pakete-jardunbideekin bat ez datorrela iradokitzen du. Eguneratzen saiatu beharko zinateke.", - "diagnosis_description_apps": "Aplikazioak" + "diagnosis_description_apps": "Aplikazioak", + "domain_dns_conf_special_use_tld": "Domeinu hau top-level domain (TLD) erabilera bereziko motakoa da .local edo .test bezala eta ez du DNS ezarpenik behar.", + "log_permission_create": "Sortu '{}' baimena", + "log_user_delete": "Ezabatu '{}' erabiltzailea", + "log_app_install": "'{}' aplikazioa instalatu", + "done": "Egina", + "group_user_already_in_group": "{user} erabiltzailea {group} taldean dago dagoeneko", + "firewall_reloaded": "Suebakia birkargatu da", + "domain_unknown": "'{domain}' domeinua ezezaguna da", + "global_settings_cant_serialize_settings": "Ezin izan da konfikurazio-datuak serializatu, zergatia: {reason}", + "global_settings_setting_security_nginx_redirect_to_https": "Birbidali HTTP eskaerak HTTPSra (EZ ITZALI hau ez badakizu zertan ari zaren!)", + "group_deleted": "'{group}' taldea ezabatu da", + "invalid_password": "Pasahitza ez da zuzena", + "log_domain_main_domain": "Lehenetsi '{}' domeinua", + "log_user_group_update": "Aldatu '{}' taldea", + "dyndns_could_not_check_available": "Ezin izan da egiaztatu {domain} eskuragarri dagoenik {provider}(e)n.", + "diagnosis_rootfstotalspace_critical": "Sistemaren root memoriak {space} baino ez ditu erabilgarri, eta hori kezkagarria da! Litekeena da oso laster memoriarik gabe geratzea! Root partizioak gutxienez 16GB erabilgarri izatea da gomendioa.", + "disk_space_not_sufficient_install": "Ez dago aplikazio hau instalatzeko nahikoa espaziorik", + "domain_dns_conf_is_just_a_recommendation": "Komando honek *gomendatutako* konfigurazioa erakusten du. Ez du DNS konfigurazioa zugatik ezartzen. Zure ardura da DNS gunea zure erregistro-enpresaren gomendioen arabera ezartzea.", + "dyndns_ip_update_failed": "Ezin izan da IP helbidea DynDNSan eguneratu", + "dyndns_ip_updated": "IP helbidea DynDNS-n eguneratu da", + "dyndns_key_not_found": "Ez da domeinurako DNS gakorik aurkitu", + "dyndns_unavailable": "'{domain}' domeinua ez dago eskuragarri.", + "log_app_makedefault": "Lehenetsi '{}' aplikazioa", + "log_does_exists": "Ez dago '{log}' izena duen eragiketa-erregistrorik, erabili 'yunohost log list' eragiketa-erregistro guztiak ikusteko", + "log_user_group_delete": "Ezabatu '{}' taldea", + "log_user_import": "Inportatu erabiltzaileak", + "dyndns_key_generating": "DNS gakoa sortzen… litekeena da honek denbora behar izatea.", + "diagnosis_mail_fcrdns_ok": "Alderantzizko DNSa zuzen konfiguratuta dago!", + "diagnosis_mail_queue_unavailable_details": "Errorea: {error}", + "dyndns_provider_unreachable": "Ezin izan da DynDNS {provider} enpresarekin konexioa gauzatu: agian zure YunoHost zerbitzaria ez dago internetera konektatuta edo dynette zerbitzaria ez dago martxan.", + "dyndns_registered": "DynDNS domeinua erregistratu da", + "dyndns_registration_failed": "Ezin izan da DynDNS domeinua erregistratu: {error}", + "extracting": "Ateratzen…", + "diagnosis_ports_unreachable": "{port}. ataka ez dago eskuragarri kanpotik.", + "diagnosis_regenconf_manually_modified_details": "Ez dago arazorik zertan ari zaren baldin badakizu! YunoHostek fitxategi hau automatikoki eguneratzeari utziko dio… Baina kontutan izan YunoHosten eguneraketek aldaketa garrantzitsuak izan ditzaketela. Nahi izatekotan, desberdintasunak aztertu ditzakezu yunohost tools regen-conf {category} --dry-run --with-diff komandoa exekutatuz, eta gomendatutako konfiguraziora bueltatu yunohost tools regen-conf {category} --force erabiliz", + "experimental_feature": "Adi: Funtzio hau esperimentala eta ezegonkorra da, ez zenuke erabili beharko ez badakizu zertan ari zaren.", + "global_settings_cant_write_settings": "Ezin izan da konfigurazio fitxategia gorde, zergatia: {reason}", + "dyndns_domain_not_provided": "{provider} DynDNS enpresak ezin du {domain} domeinua eskaini.", + "firewall_reload_failed": "Ezin izan da suebakia birkargatu", + "global_settings_setting_security_password_admin_strength": "Administrazio-pasahitzaren segurtasuna", + "hook_name_unknown": "'{name}' \"hook\" izen ezezaguna", + "domain_deletion_failed": "Ezin izan da {domain} ezabatu: {error}", + "global_settings_setting_security_nginx_compatibility": "Bateragarritasun eta segurtasun arteko gatazka NGINX web zerbitzarirako. Zifraketari eragiten dio (eta segurtasunari lotutako beste kontu batzuei)", + "log_regen_conf": "Berregin '{}' sistemaren konfigurazioa", + "dpkg_lock_not_available": "Ezin da komando hau une honetan exekutatu beste aplikazio bat dpkg (sistemaren paketeen kudeatzailea) blokeatuta duelako, erabiltzen ari baita", + "group_created": "'{group}' taldea sortu da", + "global_settings_setting_security_password_user_strength": "Erabiltzaile-pasahitzaren segurtasuna", + "global_settings_setting_security_experimental_enabled": "Gaitu segurtasun funtzio esperimentlak (ez ezazu egin ez badakizu zertan ari zaren!)", + "good_practices_about_admin_password": "Administrazio-pasahitz berria ezartzear zaude. Pasahitzak zortzi karaktere izan beharko lituzke gutxienez, baina gomendagarria da pasahitz luzeagoa erabiltzea (esaldi bat, esaterako) edota karaktere desberdinak erabiltzea (larriak, txikiak, zenbakiak eta karaktere bereziak).", + "log_help_to_get_failed_log": "Ezin izan da '{desc}' eragiketa exekutatu. Mesedez, laguntza nahi baduzu partekatu eragiketa honen erregistro osoa 'yunohost log share {name}' komandoa erabiliz", + "global_settings_setting_security_webadmin_allowlist_enabled": "Baimendu IP zehatz batzuk bakarrik administrazio-webgunean.", + "group_unknown": "'{group}' taldea ezezaguna da", + "group_updated": "'{group}' taldea eguneratu da", + "group_update_failed": "Ezin izan da '{group}' taldea eguneratu: {error}", + "diagnosis_rootfstotalspace_warning": "Sistemaren root partizioak {space} baino ez ditu. Agian ez da arazorik egongo, baina kontuz ibili edo memoriarik gabe gera zaitezke laster… Root partizioak gutxienez 16GB erabilgarri izatea da gomendioa.", + "iptables_unavailable": "Ezin dituzu iptaulak hemen aldatu. Edukiontzi bat erabiltzen ari zara edo kernelak ez du aukera hau onartzen", + "log_permission_delete": "Ezabatu '{}' baimena", + "group_already_exist": "{group} taldea existitzen da dagoeneko", + "group_user_not_in_group": "{user} erabiltzailea ez dago {group} taldean", + "diagnosis_mail_fcrdns_nok_alternatives_6": "Operadore batzuek ez dute alderantzizko DNSa konfiguratzen uzten (edo funtzioa ez dabil…). IPv4rako alderantzizko DNSa zuzen konfiguratuta badago, IPv6 desgaitzen saia zaitezke posta elektronikoa bidaltzeko, yunohost settings set smtp.allow_ipv6 -v off exekutatuz. Adi: honek esan nahi du ez zarela gai izango IPv6 bakarrik darabilten zerbitzari apurren posta elektronikoa jasotzeko edo beraiei bidaltzeko.", + "diagnosis_sshd_config_inconsistent": "Dirudienez SSH ataka eskuz aldatu da /etc/ssh/sshd_config fitxategian. YunoHost 4.2tik aurrera 'security.ssh.port' izeneko ezarpen orokor bat dago konfigurazioa eskuz aldatzea ekiditeko.", + "diagnosis_sshd_config_inconsistent_details": "Mesedez, exekutatu yunohost settings set security.ssh.port -v YOUR_SSH_PORT SSH ataka zehazteko, egiaztatu yunohost tools regen-conf ssh --dry-run --with-diff erabiliz eta yunohost tools regen-conf ssh --force exekutatu gomendatutako konfiguraziora bueltatu nahi baduzu.", + "domain_dns_push_failed_to_authenticate": "Ezin izan da '{domain}' domeinurako erregistro-enpresan saioa hasi APIa erabiliz. Zuzenak al dira egiaztagiriak? (Errorea: {error})", + "domain_dns_pushing": "DNS ezarpenak bidaltzen…", + "diagnosis_sshd_config_insecure": "Badirudi SSH konfigurazioa eskuz aldatu dela eta ez da segurua ez duelako baldintzarik jartzen 'AllowGroups' edo AllowUsers' fitxategien sarbidea oztopatzeko.", + "disk_space_not_sufficient_update": "Ez dago aplikazio hau eguneratzeko nahikoa espaziorik", + "domain_cannot_add_xmpp_upload": "Ezin dira 'xmpp-upload.' hasiera duten domeinuak gehitu. Izen mota hau YunoHosten zati den XMPP igoeretarako erabiltzen da.", + "domain_cannot_remove_main_add_new_one": "Ezin duzu '{domain}' ezabatu domeinu nagusi eta bakarra delako. Beste domeinu bat gehitu 'yunohost domain add ' exekutatuz, gero erabili 'yunohost domain main-domain -n ' domeinu nagusi bilakatzeko, eta azkenik ezabatu {domain}' domeinua 'yunohost domain remove {domain}' komandoarekin.", + "domain_dns_push_record_failed": "Ezin izan da {type}/{name} ezarpenak {action}: {error}", + "domain_dns_push_success": "DNS ezarpenak eguneratu dira!", + "domain_dns_push_failed": "DNS ezarpenen eguneratzeak huts egin du.", + "domain_dns_push_partial_failure": "DNS ezarpenak hala-nola eguneratu dira: abisu/errore batzuk egon dira.", + "global_settings_setting_smtp_relay_host": "YunoHosten ordez posta elektronikoa bidaltzeko SMTP relay helbidea. Erabilgarri izan daiteke egoera hauetan: operadore edo VPS enpresak 25. ataka blokeatzen badute, DUHLn zure etxeko IPa ageri bada, ezin baduzu alderantzizko DNSa ezarri edo zerbitzari hau ez badago zuzenean internetera konektatuta baina posta elektronikoa bidali nahi baduzu.", + "group_deletion_failed": "Ezin izan da '{group}' taldea ezabatu: {error}", + "invalid_number_min": "{min} baino handiagoa izan behar da", + "invalid_number_max": "{max} baino txikiagoa izan behar da", + "diagnosis_services_bad_status": "{service} zerbitzua {status} dago :(", + "diagnosis_ports_needed_by": "{category} funtzioetarako ezinbestekoa da ataka hau eskuragarri egotea (zerbitzua {service})", + "diagnosis_package_installed_from_sury": "Sistemaren pakete batzuen lehenagoko bertsioa beharko litzateke", + "global_settings_setting_smtp_relay_password": "SMTP relay helbideko pasahitza", + "global_settings_setting_smtp_relay_port": "SMTP relay ataka", + "domain_deleted": "Domeinua ezabatu da", + "domain_dyndns_root_unknown": "Ez da ezagutzen DynDNSaren root domeinua", + "domain_exists": "Dagoeneko existitzen da domeinu hau", + "domain_registrar_is_not_configured": "Oraindik ez da {domain} domeinurako erregistro-enpresa ezarri.", + "domain_dns_push_not_applicable": "Ezin da {domain} domeinurako DNS konfigurazio automatiko funtzioa erabili. DNS erregistroak eskuz ezarri beharko zenituzke gidaorriei erreparatuz: https://yunohost.org/dns_config.", + "domain_dns_push_managed_in_parent_domain": "DNS ezarpenak automatikoki konfiguratzeko funtzioa {parent_domain} domeinu nagusian kudeatzen da.", + "domain_dns_registrar_managed_in_parent_domain": "Domeinu hau {parent_domain_link} (r)en azpidomeinua da. DNS ezarpenak {parent_domain}(r)en konfigurazio atalean kudeatu beharko lirateke.", + "domain_dns_registrar_yunohost": "Hau nohost.me / nohost.st / ynh.fr domeinu bat da eta, beraz, DNS ezarpenak automatikoki kudeatzen ditu YunoHostek, bestelako ezer konfiguratu beharrik gabe. (ikus 'yunohost dyndns update' komandoa)", + "domain_dns_registrar_not_supported": "YunoHostek ezin izan du automatikoki erregistro-enpresa antzeman domeinu honetarako. Eskuz konfiguratu beharko zenituzke DNS ezarpenak gidalerroei erreparatuz: https://yunohost.org/dns.", + "domain_dns_registrar_experimental": "Momentuz, YunoHosten kideek ez dute **{registrar}** erregistro-enpresaren APIa nahi beste probatu eta aztertu. Funtzioa **oso esperimentala** da - kontuz!", + "domain_config_mail_in": "Jasotako mezuak", + "domain_config_auth_token": "Token egiaztagiria", + "domain_config_auth_key": "Egiaztapen gakoa", + "domain_config_auth_secret": "Egiaztagiriaren \"secret\"a", + "domain_config_api_protocol": "API protokoloa", + "domain_config_auth_entrypoint": "APIaren sarrera", + "domain_config_auth_application_key": "Aplikazioaren gakoa", + "domain_config_auth_application_secret": "Aplikazioaren gako sekretua", + "domain_config_auth_consumer_key": "Erabiltzailearen gakoa", + "global_settings_setting_smtp_allow_ipv6": "Baimendu IPv6 posta elektronikoa jaso eta bidaltzeko", + "group_cannot_be_deleted": "{group} taldea ezin da eskuz ezabatu.", + "log_domain_config_set": "Aldatu '{}' domeinuko konfigurazioa", + "log_domain_dns_push": "Bidali '{}' domeinuaren DNS ezarpenak", + "log_tools_migrations_migrate_forward": "Exekutatu migrazioak", + "log_tools_postinstall": "Abiarazi YunoHost zerbitzariaren instalazio ondorengo prozesua", + "diagnosis_mail_fcrdns_nok_alternatives_4": "Operadore batzuek ez dute alderantzizko DNSa konfiguratzen uzten (edo funtzioa ez dabil…). Hau dela-eta arazoak badituzu, irtenbide batzuk eduki ditzakezu:
- Operadora batzuek relay posta zerbitzari bat eskaini dezakete, baina kasu horretan zure posta elektronikoa zelatatu dezakete.
- Pribatutasuna bermatzeko *IP publiko bat duen* VPN bat erabiltzea izan daiteke irtenbidea. Ikus https://yunohost.org/#/vpn_advantage
- Edo operadore desberdin batera aldatu", + "domain_dns_registrar_supported": "YunoHostek automatikoki antzeman du domeinu hau **{registrar}** erregistro-enpresak kudeatzen duela. Nahi baduzu YunoHostek automatikoki konfiguratu ditzake DNS ezarpenak, API egiaztagiri aproposak zehazten badituzu. API egiaztagiriak non lortzeko dokumentazioa orri honetan duzu: https://yunohost.org/registar_api_{registrar}. (Baduzu DNS erregistroak eskuz konfiguratzeko aukera ere, gidalerro hauetan ageri den bezala: https://yunohost.org/dns)", + "domain_dns_push_failed_to_list": "Ezin izan da APIa erabiliz oraingo erregistroak antzematea: {error}", + "domain_dns_push_already_up_to_date": "Ezarpenak egunean daude, ez dago zereginik.", + "domain_config_features_disclaimer": "Oraingoz, posta elektronikoa edo XMPP funtzioak gaitu/desgaitzeak, DNS ezarpenei soilik eragiten die, ez sistemaren konfigurazioari!", + "domain_config_mail_out": "Bidalitako mezuak", + "domain_config_xmpp": "Bat-bateko mezularitza (XMPP)", + "global_settings_bad_choice_for_enum": "{setting} ezarpenerako aukera okerra. '{choice}' ezarri da baina hauek dira aukerak: {available_choices}", + "global_settings_setting_security_postfix_compatibility": "Bateragarritasun eta segurtasun arteko gatazka Postfix zerbitzarirako. Zifraketari eragiten dio (eta segurtasunari lotutako beste kontu batzuei)", + "global_settings_setting_security_ssh_compatibility": "Bateragarritasun eta segurtasun arteko gatazka SSH zerbitzarirako. Zifraketari eragiten dio (eta segurtasunari lotutako beste kontu batzuei)", + "good_practices_about_user_password": "Erabiltzaile-pasahitz berria ezartzear zaude. Pasahitzak zortzi karaktere izan beharko lituzke gutxienez, baina gomendagarria da pasahitz luzeagoa erabiltzea (esaldi bat, esaterako) edota karaktere desberdinak erabiltzea (larriak, txikiak, zenbakiak eta karaktere bereziak).", + "group_cannot_edit_all_users": "'all_users' taldea ezin da eskuz aldatu. YunoHosten izena emanda dauden erabiltzaile guztiak barne dituen talde berezia da", + "invalid_number": "Zenbaki bat izan behar da", + "ldap_attribute_already_exists": "'{attribute}' LDAP funtzioa existitzen da dagoeneko eta '{value}' balioa dauka", + "log_app_change_url": "'{}' aplikazioaren URLa aldatu", + "log_app_config_set": "Ezarri '{}' aplikazioako konfigurazioa", + "downloading": "Deskargatzen…", + "dyndns_could_not_check_provide": "Ezin izan da egiaztatu {provider}(e)k {domain} eskaini dezakenik.", + "log_available_on_yunopaste": "Erregistroa {url} estekan ikus daiteke", + "log_dyndns_update": "Eguneratu YunoHosten '{}' domeinuarekin lotutako IP helbidea", + "log_letsencrypt_cert_install": "Instalatu Let's Encrypt ziurtagiria '{}' domeinurako", + "log_selfsigned_cert_install": "Instalatu '{}' domeinurako norberak sinatutako ziurtagiria", + "diagnosis_mail_ehlo_wrong": "Zurea ez den SMTP posta zerbitzari batek erantzun du IPv{ipversion}an. Litekeena da zure zerbitzariak posta elektronikorik jaso ezin izatea.", + "log_tools_upgrade": "Eguneratu sistemaren paketeak", + "log_tools_reboot": "Berrabiarazi zerbitzaria", + "diagnosis_mail_queue_unavailable": "Ezin da kontsultatu zenbat posta elektroniko dauden ilaran", + "log_user_create": "Gehitu '{}' erabiltzailea", + "group_cannot_edit_visitors": "'bisitariak' taldea ezin da eskuz aldatu. Saiorik hasi gabeko bisitariak barne hartzen dituen talde berezia da", + "diagnosis_ram_verylow": "Sistemak RAM memoriaren {available} ({available_percent}%) dauka bakarrik! ({total}(e)tik)", + "diagnosis_ram_low": "Sistemak {available} soilik du erabilgarri, ({available_percent}%), RAM memoriaren (out of {total}). Kontuz ibili", + "diagnosis_ram_ok": "Sistemak oraindik dauka RAM memoriaren {available} ({available_percent}%) erabilgarri, {total} orotara.", + "diagnosis_swap_none": "Sistemak ez du swap-ik. Gutxienez {recommended} gehitzen saiatu beharko zinateke, sistema memoriarik gabe gera ez dadin.", + "diagnosis_swap_ok": "Sistemak {total} swap dauka!", + "diagnosis_regenconf_allgood": "Konfigurazio fitxategi guztiak bat datoz gomendatutako ezarpenekin!", + "diagnosis_regenconf_manually_modified": "Dirudienez {file} konfigurazio fitxategia eskuz aldatu da.", + "diagnosis_security_vulnerable_to_meltdown": "Badirudi Meltdown izeneko segurtasun arazo larriak eragin diezazukela", + "diagnosis_ports_could_not_diagnose": "Ezin izan da egiaztatu atakak IPv{ipversion} erabiliz kanpotik eskuragarri daudenik.", + "diagnosis_ports_ok": "{port}. ataka eskuragarri dago kanpotik.", + "diagnosis_unknown_categories": "Honako kategoriak ez dira ezagutzen: {categories}", + "diagnosis_services_running": "{service} zerbitzua martxan dago!", + "log_app_action_run": "'{}' aplikazioaren eragiketa exekutatu", + "diagnosis_never_ran_yet": "Badirudi zerbitzari hau duela gutxi konfiguratu dela eta oraindik ez dago erakusteko diagnostikorik. Diagnostiko osoa abiarazi beharko zenuke, administrazio-webgunetik edo 'yunohost diagnosis run' komandoa exekutatuz.", + "diagnosis_mail_outgoing_port_25_blocked": "SMTP posta zerbitzariak ezin ditu posta elektronikoak bidali 25. ataka itxita dagoelako IPv{ipversion}n.", + "diagnosis_mail_outgoing_port_25_blocked_details": "Lehenik eta behin operadorearen routerreko aukeretan saiatu beharko zinateke 25. ataka desblokeatzen. (Hosting enpresaren arabera, beraiekin harremanetan jartzea beharrezkoa izango da).", + "diagnosis_mail_ehlo_wrong_details": "Kanpo-diagnostikatzaileak IPv{ipversion}an jaso duen EHLOa eta zure zerbitzariaren domeinukoa ez datoz bat.
Jasotako EHLOa: {wrong_ehlo}
Esperotakoa: {right_ehlo}
Arazo honen zergati ohikoena 25. ataka zuzen konfiguratuta ez egotea da. Edo agian suebaki edo reverse-proxya oztopo izan daiteke.", + "diagnosis_mail_fcrdns_different_from_ehlo_domain": "Alderantzizko DNSa ez dago zuzen konfiguratuta IPv{ipversion}an. Litekeena da posta elektroniko batzuk hartzaileak jaso ezin izatea edo spam modura etiketatuak izatea.", + "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Oraingo alderantzizko DNSa: {rdns_domain}
Esperotako balioa: {ehlo_domain}", + "diagnosis_mail_queue_too_big": "Mezu gehiegi posta elektronikoaren ilaran: ({nb_pending} mezu)", + "diagnosis_ports_could_not_diagnose_details": "Errorea: {error}", + "diagnosis_swap_tip": "Mesedez, kontutan hartu zerbitzari honen swap memoria SD edo SSD euskarri batean gordetzen dela, euskarri horien bizi-iraupena izugarri laburtu dezakeela.", + "invalid_regex": "\"Regexa\" ez da baliogarria: '{regex}'", + "group_creation_failed": "Ezin izan da '{group}' taldea sortu: {error}", + "log_user_permission_reset": "Berrezarri '{}' baimena", + "group_cannot_edit_primary_group": "'{group}' taldea ezin da eskuz aldatu. Erabiltzaile zehatz bakar bat duen talde nagusia da.", + "diagnosis_swap_notsomuch": "Sistemak {total} swap baino ez du. Gutxienez {recommended} gehitzen saiatu beharko zinateke sistema memoriarik gabe gera ez dadin.", + "diagnosis_security_vulnerable_to_meltdown_details": "Arazoa konpontzeko, sistema eguneratu eta berrabiarazi beharko zenuke linux-en kernel berriagoa erabiltzeko (edo zerbitzariaren arduradunarekin jarri harremanetan. Ikus https://meltdownattack.com/ informazio gehiagorako.", + "diagnosis_services_conf_broken": "{service} zerbitzuko konfigurazioa hondatuta dago!", + "diagnosis_services_bad_status_tip": "Zerbitzua berrabiarazten saia zaitezke eta nahikoa ez bada, aztertu zerbitzuaren erregistroa administrariaren webgunean. (komandoak nahiago badituzu yunohost service restart {service} eta yunohost service log {service} hurrenez hurren).", + "diagnosis_mail_ehlo_unreachable_details": "Ezin izan da zure zerbitzariko 25. atakari konektatu IPv{ipversion} erabiliz. Badirudi ez dagoela eskuragarri.
1. Arazo honen zergati ohikoena 25. ataka egoki birbidalita ez egotea da.
2. Egiaztatu postfix zerbitzua martxan dagoela.
3. Konfigurazio konplexuagoetan: egiaztatu suebaki edo reverse-proxyak konexioa oztopatzen ez dutela.", + "group_already_exist_on_system_but_removing_it": "{group} taldea existitzen da sistemaren taldeetan, baina YunoHostek ezabatuko du…", + "diagnosis_mail_fcrdns_nok_details": "Lehenik eta behin zure routerraren konfigurazio gunean edo hostingaren enprearen aukeretan alderantzizko DNSa konfiguratzen saiatu beharko zinateke {ehlo_domain} erabiliz. (Hosting enpresaren arabera, ezinbestekoa da beraiekin harremanetan jartzea).", + "diagnosis_mail_outgoing_port_25_ok": "SMTP posta zerbitzaria posta elektronikoa bidaltzeko gai da (25. atakaren irteera ez dago blokeatuta).", + "diagnosis_ports_partially_unreachable": "{port}. ataka ez dago eskuragarri kanpotik Pv{failed} erabiliz.", + "diagnosis_ports_forwarding_tip": "Arazoa konpontzeko, litekeena da operadorearen routerrean ataken birbidalketa konfiguratu behar izatea, https://yunohost.org/isp_box_config-n agertzen den bezala", + "domain_creation_failed": "Ezin izan da {domain} domeinua sortu: {error}", + "domains_available": "Erabilgarri dauden domeinuak:", + "global_settings_setting_pop3_enabled": "Gaitu POP3 protokoloa posta zerbitzarirako", + "global_settings_setting_security_ssh_port": "SSH ataka", + "global_settings_unknown_type": "Gertaera ezezaguna, {setting} ezarpenak {unknown_type} mota duela dirudi baina mota hori ez da sistemarekin bateragarria.", + "group_already_exist_on_system": "{group} taldea existitzen da sistemaren taldeetan dagoeneko", + "diagnosis_processes_killed_by_oom_reaper": "Memoria agortu eta sistemak prozesu batzuk amaituarazi behar izan ditu. Honek esan. nahi du sistemak ez duela memoria nahikoa edo prozesuren batek memoria gehiegi behar duela. Amaituarazi d(ir)en prozesua(k):\n{kills_summary}", + "hook_exec_not_terminated": "Aginduak ez du behar bezala amaitu: {path}", + "log_corrupted_md_file": "Erregistroei lotutako YAML metadatu fitxategia hondatuta dago: '{md_file}\nErrorea: {error}'", + "log_letsencrypt_cert_renew": "Berriztu '{}' Let's Encrypt ziurtagiria", + "log_remove_on_failed_restore": "Ezabatu '{}' babeskopia baten lehengoratzeak huts egin eta gero", + "diagnosis_package_installed_from_sury_details": "Sury izena duen kanpoko biltegi batetik instalatu dira pakete batzuk, nahi gabe. YunoHosten taldeak hobekuntzak egin ditu pakete hauek kudeatzeko, baina litekeena da PHP7.3 aplikazioak Stretch sistema eragilean instalatu zituzten kasu batzuetan arazoak sortzea. Egoera hau konpontzeko, honako komando hau exekutatu beharko zenuke: {cmd_to_fix}", + "log_help_to_get_log": "'{desc}' eragiketaren erregistroa ikusteko, exekutatu 'yunohost log show {name}'", + "dpkg_is_broken": "Ezin duzu une honetan agin dpkg/APT (sistemaren pakateen kudeatzaileak) hondatutako itxura dutelako… Arazoa konpontzeko SSH bidez konektatzen saia zaitezke eta ondoren exekutatu 'sudo apt install --fix-broken' edota 'sudo dpkg --configure -a'.", + "domain_cannot_remove_main": "Ezin duzu '{domain}' ezabatu domeinu nagusia delako. Beste domeinu bat ezarri beharko duzu nagusi bezala 'yunohost domain main-domain -n ' erabiliz; honako hauek dituzu aukeran: {other_domains}", + "domain_created": "Sortu da domeinua", + "domain_dyndns_already_subscribed": "Dagoeneko izena eman duzu DunDNS domeinu batean", + "domain_hostname_failed": "Ezin izan da hostname berria ezarri. Honek arazoak ekar litzake etorkizunean (litekeena da ondo egotea).", + "domain_uninstall_app_first": "Honako aplikazio hauek domeinuan instalatuta daude:\n{apps}\n\nMesedez, desinstalatu 'yunohost app remove the_app_id' ezekutatuz edo alda itzazu beste domeinu batera 'yunohost app change-url the_app_id' erabiliz domeinua ezabatu baino lehen", + "file_does_not_exist": "{path} fitxategia ez da existitzen.", + "firewall_rules_cmd_failed": "Suebakiko arau batzuen exekuzioak huts egin du. Informazio gehiago erregistroetan.", + "log_app_remove": "Ezabatu '{}' aplikazioa", + "global_settings_cant_open_settings": "Ezin izan da konfigurazio fitxategia ireki, zergatia: {reason}", + "global_settings_reset_success": "Lehengo ezarpenak {path}-n gorde dira", + "global_settings_unknown_setting_from_settings_file": "Gako ezezaguna ezarpenetan: '{setting_key}', baztertu eta gorde ezazu hemen: /etc/yunohost/settings-unknown.json", + "domain_remove_confirm_apps_removal": "Domeinu hau ezabatzean aplikazio hauek desinstalatuko dira:\n{apps}\n\nZiur al zaude? [{answers}]", + "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Baimendu DSA gakoa (zaharkitua) SSH zerbitzuaren konfiguraziorako", + "hook_list_by_invalid": "Aukera hau ezin da erabili \"hook\"ak zerrendatzeko", + "installation_complete": "Instalazioa amaitu da", + "hook_exec_failed": "Ezin izan da agindua exekutatu: {path}", + "hook_json_return_error": "Ezin izan da {path} aginduaren erantzuna irakurri. Errorea: {msg}. Jatorrizko edukia: {raw_content}", + "ip6tables_unavailable": "Ezin dituzu ip6taulak hemen aldatu. Edukiontzi bat erabiltzen ari zara edo kernelak ez du aukera hau onartzen", + "log_link_to_log": "Eragiketa honen erregistro osoa: '{desc}'", + "log_operation_unit_unclosed_properly": "Eragiketa ez da modu egokian itxi", + "log_backup_restore_app": "Lehengoratu '{}' babeskopia fitxategi bat erabiliz", + "log_remove_on_failed_install": "Ezabatu '{}' instalazioak huts egin ondoren", + "log_domain_add": "Gehitu '{}' domeinua sistemaren konfiguraziora", + "log_dyndns_subscribe": "Eman izena YunoHosten '{}' azpidomeinuan", + "diagnosis_no_cache": "Oraindik ez dago '{category}' kategoriarako diagnostikoaren cacherik", + "diagnosis_mail_queue_ok": "Posta elektronikoaren ilaran zain dauden mezuak: {nb_pending}", + "global_settings_setting_smtp_relay_user": "SMTP relay erabiltzailea", + "domain_cert_gen_failed": "Ezinezkoa izan da ziurtagiria sortzea", + "field_invalid": "'{}' ez da baliogarria", + "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Operadore batzuei bost axola zaie internetaren neutraltasuna (Net Neutrality) eta ez dute 25. ataka desblokeatzen uzten.
- Operadora batzuek relay posta zerbitzari bat eskaini dezakete, baina kasu horretan zure posta elektronikoa zelatatu dezakete.
- Pribatutasuna bermatzeko *IP publiko bat duen* VPN bat erabiltzea izan daiteke irtenbidea. Ikus https://yunohost.org/#/vpn_advantage
- Edo operadore desberdin batera aldatu", + "ldap_server_down": "Ezin izan da LDAP zerbitzarira konektatu", + "ldap_server_is_down_restart_it": "LDAP zerbitzaria ez dago martxan, saia zaitez berrabiarazten…", + "log_app_upgrade": "'{}' aplikazioa eguneratu", + "log_tools_shutdown": "Itzali zerbitzaria", + "log_user_permission_update": "Eguneratu '{}' baimenerako sarbideak", + "log_user_update": "Eguneratu '{}' erabiltzailearen informazioa", + "dyndns_no_domain_registered": "Ez dago DynDNSrekin izena emandako domeinurik", + "global_settings_bad_type_for_setting": "{setting} ezarpenerako mota okerra. {received_type} ezarri da, {expected_type} espero zen", + "diagnosis_mail_fcrdns_dns_missing": "Ez da alderantzizko DNSrik ezarri IPv{ipversion}rako. Litekeena da posta elektroniko batzuk hartzaileak jaso ezin izatea edo spam modura etiketatuak izatea.", + "log_backup_create": "Sortu babeskopia fitxategia", + "global_settings_setting_backup_compress_tar_archives": "Babeskopia berriak sortzean, konprimitu fitxategiak (.tar.gz) konprimitu gabeko fitxategien (.tar) ordez. Aukera hau gaitzean babeskopiek espazio gutxiago beharko dute, baina hasierako prozesua luzeagoa izango da eta CPUari lan handiagoa eragingo dio.", + "global_settings_setting_security_webadmin_allowlist": "Administrazio-webgunea bisita ditzaketen IP helbideak. Koma bidez bereiziak.", + "global_settings_key_doesnt_exists": "'{settings_key}' gakoa ez da existitzen konfigurazio orokorrean, erabilgarri dauden gakoak ikus ditzakezu 'yunohost settings list' exekutatuz", + "global_settings_setting_ssowat_panel_overlay_enabled": "Gaitu SSOwat paneleko \"overlay\"a", + "log_backup_restore_system": "Lehengoratu sistema babeskopia fitxategi batetik", + "log_domain_remove": "Ezabatu '{}' domeinua sistemaren konfiguraziotik", + "log_link_to_failed_log": "Ezin izan da '{desc}' eragiketa exekutatu. Mesedez, laguntza nahi izanez gero, elkarbanatu erakigeta honen erregistro osoa hemen sakatuz", + "log_permission_url": "Eguneratu '{}' baimenari lotutako URLa", + "log_user_group_create": "Sortu '{}' taldea" } From 1285bd50ab66cc2b27109b4073234caa3160de0a Mon Sep 17 00:00:00 2001 From: ppr Date: Wed, 20 Oct 2021 19:19:24 +0000 Subject: [PATCH 0881/1155] Translated using Weblate (French) Currently translated at 100.0% (706 of 706 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 cadc2e9dc..5b2ea6225 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -685,7 +685,7 @@ "domain_dns_registrar_managed_in_parent_domain": "Ce domaine est un sous-domaine de {parent_domain_link}. La configuration du registrar DNS doit être gérée dans le panneau de configuration de {parent_domain}.", "domain_dns_registrar_not_supported": "YunoHost n'a pas pu détecter automatiquement le bureau d'enregistrement gérant ce domaine. Vous devez configurer manuellement vos enregistrements DNS en suivant la documentation sur https://yunohost.org/dns.", "domain_dns_registrar_experimental": "Jusqu'à présent, l'interface avec l'API de **{registrar}** n'a pas été correctement testée et revue par la communauté YunoHost. L'assistance est **très expérimentale** - soyez prudent !", - "domain_dns_push_failed_to_authenticate": "Échec de l'authentification sur l'API du registrar gérant la réservation de votre nom de domaine internet pour '{domain}'. Il est très probable que les informations d'identification soient incorrectes ? (Erreur : {error})", + "domain_dns_push_failed_to_authenticate": "Échec de l'authentification sur l'API du registrar qui gère votre nom de domaine internet pour '{domain}'. Il est très probable que les informations d'identification soient incorrectes ? (Erreur : {error})", "domain_dns_push_failed_to_list": "Échec de la liste des enregistrements actuels à l'aide de l'API du registraire : {error}", "domain_dns_push_already_up_to_date": "Dossiers déjà à jour.", "domain_dns_pushing": "Transmission des enregistrements DNS...", From 6e3e3a847b3be538a97985a75f01948747ca55e8 Mon Sep 17 00:00:00 2001 From: punkrockgirl Date: Wed, 20 Oct 2021 21:56:34 +0000 Subject: [PATCH 0882/1155] Translated using Weblate (Basque) Currently translated at 96.8% (684 of 706 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eu/ --- locales/eu.json | 229 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 228 insertions(+), 1 deletion(-) diff --git a/locales/eu.json b/locales/eu.json index 4dc280e43..b4bd97f6c 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -477,5 +477,232 @@ "log_domain_remove": "Ezabatu '{}' domeinua sistemaren konfiguraziotik", "log_link_to_failed_log": "Ezin izan da '{desc}' eragiketa exekutatu. Mesedez, laguntza nahi izanez gero, elkarbanatu erakigeta honen erregistro osoa hemen sakatuz", "log_permission_url": "Eguneratu '{}' baimenari lotutako URLa", - "log_user_group_create": "Sortu '{}' taldea" + "log_user_group_create": "Sortu '{}' taldea", + "permission_creation_failed": "Ezin izan da '{permission}' baimena sortu: {error}", + "permission_not_found": "Ez da '{permission}' baimena aurkitu", + "pattern_lastname": "Abizen horrek ez du balio", + "permission_deleted": "'{permission}' baimena ezabatu da", + "service_disabled": "'{service}' zerbitzua ez da etorkizunean zerbitzaria abiaraztearekin batera exekutatuko.", + "tools_upgrade_regular_packages_failed": "Ezin izan dira paketeak eguneratu: {packages_list}", + "tools_upgrade_special_packages_completed": "YunoHosten paketeak eguneratu dira.\nSakatu [Enter] komando-lerrora bueltatzeko", + "unexpected_error": "Ezusteko zerbaitek huts egin du: {error}", + "updating_apt_cache": "Sistemaren paketeen eguneraketak eskuratzen…", + "mail_forward_remove_failed": "Ezin izan da '{mail}' posta elektronikoko birbidalketa ezabatu", + "migration_description_0020_ssh_sftp_permissions": "Gehitu SSH eta SFTP baimenen funtzioak", + "migration_ldap_migration_failed_trying_to_rollback": "Ezin izan da migratu… sistema lehengoratzen saiatzen.", + "migrations_exclusive_options": "'--auto', '--skip', and '--force-rerun' aukerek batak bestea baztertzen du.", + "migrations_running_forward": "{id} migrazioa exekutatzen…", + "regenconf_dry_pending_applying": "'{category}' atalari dagokion konfigurazioa egiaztatzen…", + "regenconf_file_backed_up": "'{conf} konfigurazio fitxategia '{backup}' babeskopian kopiatu da", + "regenconf_file_manually_modified": "'{conf}' konfigurazio fitxategia eskuz moldatu da eta ez da eguneratuko", + "regenconf_file_updated": "'{conf}' konfigurazio fitxategia eguneratu da", + "regenconf_updated": "'{category}' atalerako ezarpenak eguneratu dira", + "service_started": "'{service}' zerbitzua abiarazi da", + "show_tile_cant_be_enabled_for_regex": "Ezin duzu 'show_tile' gaitu une honetan, '{permission}' baimenerako URLa regez delako", + "unknown_main_domain_path": "{app} aplikaziorako domeinu edo bide ezezaguna. Domeinua eta bidea zehaztu behar dituzu baimena emateko URLa ahalbidetzeko.", + "user_import_partial_failed": "Erabiltzaileak inportatzeko eragiketak erdizka huts egin du", + "user_import_success": "Erabiltzaileak arazorik gabe inportatu dira", + "yunohost_already_installed": "YunoHost instalatuta dago dagoeneko", + "migration_0015_not_stretch": "Debianen oraingo bertsioa ez da Stretch!", + "migrations_success_forward": "{id} migrazioak amaitu du", + "migrations_to_be_ran_manually": "{id} migrazioa eskuz abiarazi behar da. Mesedez, joan Erramintak → Migrazioak atalera administratzeko webgunean edo bestela exekutatu 'yunohost tools migrations run'.", + "permission_currently_allowed_for_all_users": "Baimen hau erabiltzaile guztiei esleitzen zaie eta baita beste talde batzuei ere. Litekeena da 'all users' baimena edo eslaituta duten taldeei baimena kendu nahi izatea.", + "permission_require_account": "'{permission}' baimenak zentzua du zerbitzarian kontua duten erabiltzaileentzat eta beraz ezin da gaitu bisitarientzat.", + "postinstall_low_rootfsspace": "root direktorioak 10 GB edo espazio gutxiago dauka, kezkatzekoa dena! Litekeena da espaziorik gabe geratzea aurki! Gomendagarria da root direktorioan gutxienez 16 GB libre izatea. Abisu honen ondoren YunoHost instalatzen jarraitu nahi baduzu, berrabiarazi agindua '--force-diskspace' gehituz", + "this_action_broke_dpkg": "Eragiketa honek dpkg/APT (sistemaren pakete kudeatzaileak) apurtu ditu… Arazoa konpontzeko SSH bidez konektatu eta 'sudo apt install --fix-broken' edota 'sudo dpkg --configure -a' exekutatu dezakezu.", + "tools_upgrade_special_packages_explanation": "Eguneraketa bereziak atzeko planoan jarraituko du. Mesedez, ez abiarazi bestelako eragiketarik datozen ~10 minutuetan (zure hardwarearen abiaduraren arabera). Honen ondoren litekeena da saioa berriro hasi behar izatea. Eguneraketaren erregistroa Erramintak → Erregistroak (administrazio-webgunean) edo 'yunohost log list' komandoa erabiliz egongo da ikusgai.", + "user_import_bad_line": "{line} lerro okerra: {details}", + "restore_complete": "Lehengoratzea amaitu da", + "restore_extracting": "Behar diren fitxategiak ateratzen…", + "tools_upgrade_cant_unhold_critical_packages": "Ezin izan dira pakete kritikoak alde batera utzi…", + "tools_upgrade_regular_packages": "Orain pakete \"arruntak\" (YunoHostekin zerikusia ez dutenak) eguneratzen…", + "tools_upgrade_special_packages": "Orain pakete \"bereziak\" (YunoHostekin zerikusia dutenak) eguneratzen…", + "regenconf_would_be_updated": "'{category}' atalerako konfigurazioa eguneratu izango litzatekeen", + "migration_description_0018_xtable_to_nftable": "Migratu internet trafiko arau zaharrak nftaula sistema berrira", + "migrations_dependencies_not_satisfied": "Exekutatu honako migrazioak: '{dependencies_id}', {id} migratu baino lehen.", + "permission_created": "'{permission}' baimena sortu da", + "regenconf_now_managed_by_yunohost": "'{conf}' konfigurazio fitxategia YunoHostek kudeatzen du orain ({category} atala).", + "service_enabled": "'{service}' zerbitzua ez da automatikoki exekutatuko sistema abiaraztean.", + "service_removed": "'{service}' zerbitzua ezabatu da", + "service_restart_failed": "Ezin izan da '{service}' zerbitzua berrabiarazi\n\nZerbitzuen azken erregistroak: {logs}", + "service_restarted": "'{service}' zerbitzua berrabiarazi da", + "service_start_failed": "Ezin izan da '{service}' zerbitzua abiarazi\n\nZerbitzuen azken erregistroak: {logs}", + "ssowat_conf_updated": "SSOwat ezarpenak eguneratu dira", + "tools_upgrade_at_least_one": "Mesedez, zehaztu '--apps' edo '--system'", + "tools_upgrade_cant_both": "Ezin dira sistema eta aplikazioak une berean eguneratu", + "update_apt_cache_failed": "Ezin da APT Debian-en pakete kudeatzailearen cachea eguneratu. Hemen dituzu sources.list fitxategiaren lerroak, arazoa identifikatzeko baliagarria izan dezakezuna:\n{sourceslist}", + "update_apt_cache_warning": "Zerbaitek huts egin du APT Debian-en pakete kudeatzailearen cachea eguneratzean. Hemen dituzu sources.list fitxategiaren lerroak, arazoa identifikatzeko baliagarria izan dezakezuna:\n{sourceslist}", + "user_created": "Erabiltzailea sortu da", + "user_deletion_failed": "Ezin izan da '{user}' ezabatu: {error}", + "permission_updated": "'{permission}' baimena aldatu da", + "ssowat_conf_generated": "SSOwat ezarpenak berregin dira", + "system_upgraded": "Sistema eguneratu da", + "upnp_port_open_failed": "Ezin izan da UPnP bidez ataka zabaldu", + "user_creation_failed": "Ezin izan da '{user}' erabiltzailea sortu: {error}", + "user_deleted": "Erabiltzailea ezabatu da", + "main_domain_changed": "Domeinu nagusia aldatu da", + "migrations_already_ran": "Honako migrazio hauek amaitu dute dagoeneko: {ids}", + "yunohost_installing": "YunoHost instalatzen…", + "migrations_failed_to_load_migration": "Ezin izan da {id} migrazioa kargatu: {error}", + "migrations_must_provide_explicit_targets": "'--skip' edo '--force-rerun' aukerak erabiltzean jomuga zehatzak zehaztu behar dituzu", + "migrations_pending_cant_rerun": "Migrazio hauek exekutatzeke daude eta, beraz, ezin dira berriro abiarazi: {ids}", + "regenconf_file_kept_back": "'{conf}' konfigurazio fitxategia regen-conf-ek ({category} atala) ezabatzekoa zen baina mantendu egin da.", + "regenconf_file_removed": "'{conf}' konfigurazio fitxategia ezabatu da", + "tools_upgrade_cant_hold_critical_packages": "Ezin izan dira pakete kritikoak mantendu…", + "migrations_cant_reach_migration_file": "Ezin izan da '%s' migrazioen fitxategia eskuratu", + "permission_already_allowed": "'{group} taldeak badauka dagoeneko '{permission}' baimena", + "permission_cant_add_to_all_users": "{permission} baimena ezin da erabiltzaile guztiei ezarri.", + "mailbox_disabled": "Posta elektronikoa desgaituta dago {user} erabiltzailearentzat", + "operation_interrupted": "Eragiketa eskuz geldiarazi da?", + "permission_already_exist": "'{permission}' baimena existitzen da dagoeneko", + "regenconf_pending_applying": "'{category}' atalerako konfigurazioa ezartzen…", + "user_import_nothing_to_do": "Ez dago erabiltzaileak inportatu beharrik", + "mailbox_used_space_dovecot_down": "Dovecot mailbox zerbitzua martxan egon behar da postak erabilitako espazioa ezagutzeko", + "migration_0015_cleaning_up": "Beharrezkoak ez diren cache eta paketeak kentzen…", + "migration_0015_modified_files": "Mesedez, kontutan hartu ondorengo fitxategiak eskuz aldatu direla eta sistema eguneratzean euren gainean idatziko dela: {manually_modified_files}", + "migration_0015_not_enough_free_space": "/var/ direktorioan oso espazio gutxi geratzen da! Gutxienez GB bat izan behar da migrazioa abiarazteko.", + "migration_0015_weak_certs": "Sinadura-algoritmo ahulak darabiltzaten ziurtagiriak aurkitu dira eta eguneratu behar dira nginx-en hurrengo bertsioarekin bateragarriak izateko: {certs}", + "migration_description_0017_postgresql_9p6_to_11": "Migratu datubaseak PostgreSQL 9.6-tik 11-ra", + "other_available_options": "… eta erakusten ez diren beste {n} aukera daude", + "permission_cannot_remove_main": "Ezin da baimen nagusi bat kendu", + "service_not_reloading_because_conf_broken": "Ez da '{name}' zerbitzua birkargatu/berrabiarazi konfigurazioa hondatuta dagoelako: {errors}", + "service_reloaded": "'{service}' zerbitzua birkargatu da", + "service_reloaded_or_restarted": "'{service}' zerbitzua birkargatu edo berrabiarazi da", + "user_import_bad_file": "CSV fitxategiak ez du formatu egokia eta ekidingo da balizko datuen galera saihesteko", + "user_import_failed": "Erabiltzaileak inportatzeko eragiketak huts egin du", + "user_import_missing_columns": "Ondorengo zutabeak falta dira: {columns}", + "service_disable_failed": "Ezin izan da '{service}' zerbitzua geldiarazi zerbitzaria abiaraztean.\n\nZerbitzuen erregistro berriak: {logs}", + "migrations_skip_migration": "{id} migrazioa saihesten…", + "packages_upgrade_failed": "Ezin izan dira pakete guztiak eguneratu", + "upnp_disabled": "UPnP itzalita dago", + "main_domain_change_failed": "Ezin izan da domeinu nagusia aldatu", + "regenconf_failed": "Ezin izan da ondorengo atal(ar)en konfigurazioa berregin: {categories}", + "migration_0015_start": "Buster-erako migrazioa abiarazten", + "migration_0015_patching_sources_list": "sources.lists petatxatzen…", + "migration_0015_yunohost_upgrade": "YunoHosten muinaren eguneraketa abiarazten…", + "migration_0017_postgresql_96_not_installed": "Ez da PostgreSQL instalatu. Ez dago egitekorik.", + "pattern_email_forward": "Helbide elektroniko baliagarri bat izan behar da, '+' karakterea onartzen da (adibidez izena+urtea@domeinua.eus)", + "migrating_legacy_permission_settings": "Zaharkitutako baimenen ezarpenak migratzen…", + "migration_0019_add_new_attributes_in_ldap": "Gehitu LDAP datubasean baimenetarako atributu berriak", + "regenconf_file_manually_removed": "'{conf}' konfigurazio fitxategia eskuz ezabatu da eta ez da berriro sortuko", + "regenconf_up_to_date": "Konfigurazioa egunean dago dagoeneko '{category}' atalerako", + "service_regen_conf_is_deprecated": "'yunohost service regen-conf' zaharkitua dago! Mesedez, erabili 'yunohost tools regen-conf' haren ordez.", + "migrations_no_such_migration": "Ez dago '{id}' izeneko migraziorik", + "migrations_not_pending_cant_skip": "Migrazio hauek ez daude exekutatzeke eta beraz, ezin dira saihestu: {ids}", + "regex_with_only_domain": "Ezin duzu regex domeinuetarako erabili, bideetarako bakarrik", + "port_already_closed": "{port}. ataka itxita dago dagoeneko {ip_version} konexioetarako", + "regenconf_file_copy_failed": "Ezin izan da '{new}' konfigurazio fitxategi berria '{conf}'-(e)n kopiatu", + "regenconf_file_remove_failed": "Ezin izan da '{conf}' konfigurazio fitxategia ezabatu", + "server_shutdown_confirm": "Zerbitzaria berehala itzaliko da, ziur al zaude? [{answers}]", + "restore_already_installed_app": "'{app}' IDa duen aplikazioa dagoeneko instalatuta dago", + "service_description_postfix": "Posta elektronikoa bidali eta jasotzeko erabiltzen da", + "service_enable_failed": "Ezin izan da '{service}' zerbitzua sistema abiaraztearekin batera exekutatzea lortu.\n\nZerbitzuen erregistro berriak: {logs}", + "system_username_exists": "Erabiltzaile izena existitzen da dagoeneko sistemaren erabiltzaileen zerrendan", + "user_already_exists": "'{user}' erabiltzailea existitzen da dagoeneko", + "migration_0018_failed_to_migrate_iptables_rules": "Zaharkitutako iptaulak nftauletara eguneratzeak huts egin du: {error}", + "mail_domain_unknown": "Ezin da posta elektroniko hori erabili '{domain}' domeinurako. Mesedez, erabili zerbitzari honek kudeatzen duen domeinu bat.", + "migrations_list_conflict_pending_done": "Ezin dituzu '--previous' eta '--done' aldi berean erabili.", + "migrations_loading_migration": "{id} migrazioa kargatzen…", + "migrations_no_migrations_to_run": "Ez dago exekutatzeko migraziorik", + "password_listed": "Pasahitz hau munduan erabilienetarikoa da. Mesedez, aukeratu beste bat.", + "password_too_simple_2": "Pasahitzak zortzi karaktere izan behar ditu gutxienez eta zenbakiren bat, karaktere larriren bat eta txikiren bat izan behar ditu", + "pattern_firstname": "Izen horrek ez du balio", + "pattern_password": "Gutxienez hiru karaktere izan behar ditu", + "restore_failed": "Ezin izan da sistema lehengoratu", + "restore_removing_tmp_dir_failed": "Ezin izan da behin-behineko direktorio zaharra ezabatu", + "restore_running_app_script": "'{app}' aplikazioa lehengoratzen…", + "root_password_replaced_by_admin_password": "Administrazio pasahitzak root pasahitza ordezkatu du.", + "service_description_fail2ban": "Internetetik datozen bortxaz egindako saiakerak eta bestelako erasoak ekiditen ditu", + "service_description_ssh": "Zerbitzarira sare lokaletik kanpo konektatzea ahalbidetzen du (SSH protokoloa)", + "service_description_yunohost-firewall": "Zerbitzuen konexiorako atakak ireki eta ixteko kudeatzailea", + "service_remove_failed": "Ezin izan da '{service}' zerbitzua ezabatu", + "service_reload_failed": "Ezin izan da '{service}' zerbitzua birkargatu\n\nZerbitzuen azken erregistroak: {logs}", + "service_reload_or_restart_failed": "Ezin izan da '{service}' zerbitzua birkargatu edo berrabiarazi\n\nZerbitzuen azken erregistroak: {logs}", + "service_stopped": "'{service}' zerbitzua geldiarazi da", + "unbackup_app": "{app} aplikazioa ez da gordeko", + "unrestore_app": "{app} ez da lehengoratuko", + "upgrade_complete": "Eguneraketa amaitu da", + "upgrading_packages": "Paketeak eguneratzen…", + "upnp_dev_not_found": "Ez da UPnP gailurik aurkitu", + "user_update_failed": "Ezin izan da {user} erabiltzailea eguneratu: {error}", + "user_updated": "Erabiltzailearen informazioa aldatu da", + "yunohost_configured": "YunoHost konfiguratuta dago", + "migration_0015_system_not_fully_up_to_date": "Sistema ez dago guztiz eguneratuta. Mesedez, exekutatu eguneraketa orokorra Buster-erako migrazioa abiarazi baino lehen.", + "migration_description_0015_migrate_to_buster": "Eguneratu sistema Debian Buster eta YunoHost 4.x-ra", + "service_description_yunomdns": "Sare lokalean zerbitzarira 'yunohost.local' erabiliz konektatzea ahalbidetzen du", + "mail_alias_remove_failed": "Ezin izan da '{mail}' posta elektroniko ezizena ezabatu", + "mail_unavailable": "Helbide elektroniko hau lehenengo erabiltzailearentzat gorde da eta hari ezarri zaio automatikoki", + "migration_description_0016_php70_to_php73_pools": "Migratu php7.0-fpm 'pool' fitxategiak php7.3-ra", + "migration_description_0019_extend_permissions_features": "Hedatu aplikazioen baimenen kudeaketa sistema", + "migration_0015_main_upgrade": "Eguneraketa orokorra abiarazten…", + "migration_ldap_backup_before_migration": "Sortu LDAP datubase eta aplikazioen ezarpenen babeskopia migrazioa abiarazi baino lehen.", + "migration_ldap_can_not_backup_before_migration": "Sistemaren babeskopiak ez du amaitu migrazioak huts egin baino lehen. Errorea: {error}", + "migration_0015_problematic_apps_warning": "Mesedez, kontutan izan arazoak sor ditzaketen aplikazioak aurkitu direla. Badirudi ez zirela YunoHosten aplikazioen katalogotik instalatu, edo 'ez dabiltza' etiketa dute. Beraz, ezin da bermatu eguneratu eta gero funtzionatuko dutenik: {problematic_apps}", + "migration_0017_not_enough_space": "Espazio gehiago behar da {path}-n migrazioa exekutatzeko.", + "migration_0017_postgresql_11_not_installed": "PostgreSQL 9.6 instalatuta egon arren postgresql 11 ez‽ Zerbait arraroa gertatu da zure sisteman :(…", + "migration_0018_failed_to_reset_legacy_rules": "Ezin izan dira zaharkitutako iptaulak berrezarri: {error}", + "migrations_migration_has_failed": "{id} migrazioak ez du amaitu, geldiarazten. Errorea: {exception}", + "migrations_need_to_accept_disclaimer": "{id} migrazioa abiarazteko, ondorengo baldintzak onartu behar dituzu:\n---\n{disclaimer}\n---\nMigrazioa onartzen baduzu, mesedez berrabiarazi prozesua komandoan '--accept-disclaimer' aukera gehituz.", + "not_enough_disk_space": "Ez dago nahikoa espazio librerik '{path}'-n", + "password_too_simple_3": "Pasahitzak zortzi karaktere izan behar ditu gutxienez eta zenbakiren bat, karaktere larriren bat, txikiren bat eta bereziren bat izan behar ditu", + "pattern_backup_archive_name": "Fitxategiaren izenak 30 karaktere izan ditzake gehienez, alfanumerikoak eta ._- baino ez", + "pattern_domain": "Domeinu izen baliagarri bat izan behar da (adibidez nire-domeinua.eus)", + "pattern_mailbox_quota": "Tamainak b/k/M/G/T zehaztu behar du edo 0 mugarik ezarri nahi ez bada", + "pattern_password_app": "Barka, baina pasahitzek ezin dituzte ondorengo karaktereak izan: {forbidden_chars}", + "pattern_port_or_range": "Ataka zenbaki (0-65535) edo errenkada (100:200) baliagarri bat izan behar da", + "permission_already_disallowed": "'{group}' taldeak desgaituta dauka dagoeneko '{permission} baimena", + "permission_already_up_to_date": "Baimena ez da eguneratu egindako eskaria egungo egoerarekin bat datorrelako.", + "migration_0015_still_on_stretch_after_main_upgrade": "Zerbaitek huts egin du eguneraketa orokorrean, badirudi sistemak oraindik darabilela Debian Stretch", + "migration_update_LDAP_schema": "LDAP eskema eguneratzen…", + "permission_protected": "'{permission}' baimena babestuta dago. Ezin duzu bisitarien taldea baimen honetara gehitu / baimen honetatik kendu.", + "permission_update_failed": "Ezin izan da '{permission}' baimena aldatu: {error}", + "port_already_opened": "{port}. ataka dagoeneko irekita dago {ip_version} konexioetarako", + "user_home_creation_failed": "Ezin izan da erabiltzailearentzat '{home}' direktorioa sortu", + "user_unknown": "Erabiltzaile ezezaguna: {user}", + "yunohost_postinstall_end_tip": "Instalazio ondorengo prozesua amaitu da! Sistemaren konfigurazioa bukatzeko:\n- gehitu erabiltzaile bat administrazio-webguneko 'Erabiltzaileak' atalean (edo 'yunohost user create ' komandoa erabiliz);\n- erabili 'Diagnostikoa' atala ohiko arazoei aurre hartzeko. Administrazio-webgunean abiarazi edo 'yunohost diagnosis run' exekutatu;\n- irakurri 'Finalizing your setup' eta 'Getting to know YunoHost' atalak. Dokumentazioan aurki ditzakezu: https://yunohost.org/admindoc.", + "yunohost_not_installed": "YunoHost ez da zuzen instalatu. Mesedez, exekutatu 'yunohost tools postinstall'", + "migration_0019_slapd_config_will_be_overwritten": "Badirudi eskuz moldatu duzula slapd konfigurazioa. Migrazio garrantzitsu honetarako, YunoHostek slapd ezarpenak eguneratu behar ditu. Oraingo fitxategiak {conf_backup_folder}-n kopiatuko dira.", + "unlimit": "Mugarik ez", + "restore_already_installed_apps": "Ondorengo aplikazioak ezin dira lehengoratu dagoeneko instalatuta daudelako: {apps}", + "migration_0015_general_warning": "Mesedez, uler ezazu migrazio hau eragiketa zaila dela. YunoHosten kideek ahalik eta hoberen egin dute prozesua egiaztatzeko, baina hala ere sistemaren atalak edo aplikazioak honda litezke.\n\nHorregatik gomendagarria da:\n- Informazio edo aplikazio garrantzitsuen babeskopia egitea. Informazio gehiagorako: https://yunohost.org/backup;\n- Pazientzia izatea migrazioa abiarazterakoan: zure internet konexioaren eta hardwarearen arabera litekeena da ordu batzuk behar izatea eguneraketa amaitu arte.", + "migration_0015_specific_upgrade": "Aparte eguneratu behar diren sistemaren paketeen eguneraketa abiarazten…", + "password_too_simple_4": "Pasahitzak 12 karaktere izan behar ditu gutxienez eta zenbakiren bat, karaktere larriren bat, txikiren bat eta bereziren bat izan behar ditu", + "pattern_email": "Helbide elektroniko baliagarri bat izan behar da, '+' karaktererik gabe (adibidez izena@domeinua.eus)", + "pattern_username": "Txikiz idatzitako karaktere alfanumerikoak eta azpiko marra soilik eduki ditzake", + "permission_deletion_failed": "Ezin izan da '{permission}' baimena ezabatu: {error}", + "migration_ldap_rollback_success": "Sistema lehengoratu da.", + "regenconf_need_to_explicitly_specify_ssh": "SSH ezarpenak eskuz aldatu dira, baina aldaketak erabiltzeko '--force' zehaztu behar duzu 'ssh' atalean.", + "regex_incompatible_with_tile": "/!\\ Pakete-arduradunak! {permission}' baimenak show_tile aukera 'true' bezala dauka eta horregatik ezin duzu regex URLa URL nagusi bezala ezarri", + "root_password_desynchronized": "Administrariaren pasahitza aldatu da baina YunoHostek ezin izan du aldaketa root pasahitzera zabaldu!", + "server_shutdown": "Zerbitzaria itzaliko da", + "service_stop_failed": "Ezin izan da '{service}' zerbitzua geldiarazi\n\nZerbitzuen azken erregistroak: {logs}", + "service_unknown": "'{service}' zerbitzu ezezaguna", + "show_tile_cant_be_enabled_for_url_not_defined": "Ezin duzu 'show_tile' gaitu une honetan, '{permission}' baimenerako URL bat zehaztu behar duzulako", + "upnp_enabled": "UPnP piztuta dago", + "restore_nothings_done": "Ez da ezer lehengoratu", + "restore_backup_too_old": "Babeskopia fitxategi hau ezin da lehengoratu YunoHosten bertsio zaharregi batetik datorrelako.", + "restore_hook_unavailable": "'{part}'-(e)rako lehengoratze agindua ez dago erabilgarri ez sisteman ezta fitxategian ere", + "restore_cleaning_failed": "Ezin izan dira lehengoratu ondoren behin-behineko fitxategiak ezabatu", + "restore_confirm_yunohost_installed": "Ziur al zaude dagoeneko instalatuta dagoen sistema lehengoratzeaz? [{answers}]", + "restore_may_be_not_enough_disk_space": "Badirudi zure sistemak ez duela nahikoa espazio (erabilgarri: {free_space} B, beharrezkoa {needed_space} B, segurtasuneko tartea: {margin} B)", + "restore_not_enough_disk_space": "Ez dago nahikoa espazio (erabilgarri: {free_space} B, beharrezkoa {needed_space} B, segurtasuneko tartea: {margin} B)", + "restore_running_hooks": "Lehengoratzeko \"hook\"ak exekutatzen…", + "restore_system_part_failed": "Ezin izan da sistemaren '{part}' atala lehengoratu", + "server_reboot": "Zerbitzaria berrabiaraziko da", + "server_reboot_confirm": "Zerbitzaria berehala berrabiaraziko da, ziur al zaude? [{answers}]", + "service_add_failed": "Ezin izan da '{service}' zerbitzua gehitu", + "service_added": "'{service}' zerbitzua gehitu da", + "service_already_started": "'{service}' zerbitzua matxan dago dagoeneko", + "service_already_stopped": "'{service}' zerbitzua geldiarazi da dagoeneko", + "service_cmd_exec_failed": "Ezin izan da '{command}' komandoa exekutatu", + "service_description_dnsmasq": "DNSa \"domain name resolution\" kudeatzen du", + "service_description_dovecot": "Posta elektronikoko programei mezuak jasotzea ahalbidetzen die (IMAP eta POP3 bidez)", + "service_description_metronome": "Bat-bateko XMPP mezularitza kontuak kudeatzen ditu", + "service_description_mysql": "Aplikazioen datuak gordetzen ditu (SQL datubasea)", + "service_description_nginx": "Zerbitzariak ostatazen dituen webguneak ikusgai egiten ditu", + "service_description_php7.3-fpm": "PHP aplikazioak exekutatzen ditu NGINXi esker", + "service_description_redis-server": "Datuak bizkor atzitzeko, zereginak lerratzeko eta programen arteko komunikaziorako datubase berezi bat", + "service_description_rspamd": "Spama bahetzeko eta posta elektronikoarekin zerikusia duten bestelako ezaugarrietarako", + "service_description_slapd": "Erabiltzaileak, domeinuak eta hauei lotutako informazioa gordetzen du", + "service_description_yunohost-api": "YunoHosten web interfazearen eta sistemaren arteko hartuemana kudeatzen du" } From ee11e42b7b3c9dcddb0718e2bfdde1432d2cd3a1 Mon Sep 17 00:00:00 2001 From: punkrockgirl Date: Wed, 20 Oct 2021 23:57:14 +0000 Subject: [PATCH 0883/1155] Translated using Weblate (Basque) Currently translated at 100.0% (706 of 706 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eu/ --- locales/eu.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/locales/eu.json b/locales/eu.json index b4bd97f6c..6f62ebf53 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -1,5 +1,5 @@ { - "password_too_simple_1": "Pasahitzak gutxienez 8 karaktere izan behar ditu", + "password_too_simple_1": "Pasahitzak gutxienez zortzi karaktere izan behar ditu", "action_invalid": "'{action}' ekintza baliogabea", "aborting": "Bertan behera uzten.", "admin_password_changed": "Administrazio-pasahitza aldatu da", @@ -253,7 +253,7 @@ "group_user_already_in_group": "{user} erabiltzailea {group} taldean dago dagoeneko", "firewall_reloaded": "Suebakia birkargatu da", "domain_unknown": "'{domain}' domeinua ezezaguna da", - "global_settings_cant_serialize_settings": "Ezin izan da konfikurazio-datuak serializatu, zergatia: {reason}", + "global_settings_cant_serialize_settings": "Ezin izan dira konfikurazio-datuak serializatu, zergatia: {reason}", "global_settings_setting_security_nginx_redirect_to_https": "Birbidali HTTP eskaerak HTTPSra (EZ ITZALI hau ez badakizu zertan ari zaren!)", "group_deleted": "'{group}' taldea ezabatu da", "invalid_password": "Pasahitza ez da zuzena", @@ -307,9 +307,9 @@ "diagnosis_mail_fcrdns_nok_alternatives_6": "Operadore batzuek ez dute alderantzizko DNSa konfiguratzen uzten (edo funtzioa ez dabil…). IPv4rako alderantzizko DNSa zuzen konfiguratuta badago, IPv6 desgaitzen saia zaitezke posta elektronikoa bidaltzeko, yunohost settings set smtp.allow_ipv6 -v off exekutatuz. Adi: honek esan nahi du ez zarela gai izango IPv6 bakarrik darabilten zerbitzari apurren posta elektronikoa jasotzeko edo beraiei bidaltzeko.", "diagnosis_sshd_config_inconsistent": "Dirudienez SSH ataka eskuz aldatu da /etc/ssh/sshd_config fitxategian. YunoHost 4.2tik aurrera 'security.ssh.port' izeneko ezarpen orokor bat dago konfigurazioa eskuz aldatzea ekiditeko.", "diagnosis_sshd_config_inconsistent_details": "Mesedez, exekutatu yunohost settings set security.ssh.port -v YOUR_SSH_PORT SSH ataka zehazteko, egiaztatu yunohost tools regen-conf ssh --dry-run --with-diff erabiliz eta yunohost tools regen-conf ssh --force exekutatu gomendatutako konfiguraziora bueltatu nahi baduzu.", - "domain_dns_push_failed_to_authenticate": "Ezin izan da '{domain}' domeinurako erregistro-enpresan saioa hasi APIa erabiliz. Zuzenak al dira egiaztagiriak? (Errorea: {error})", + "domain_dns_push_failed_to_authenticate": "Ezin izan da '{domain}' domeinurako APIa erabiliz erregistro-enpresan saioa hastea. Zuzenak al dira datuak? (Errorea: {error})", "domain_dns_pushing": "DNS ezarpenak bidaltzen…", - "diagnosis_sshd_config_insecure": "Badirudi SSH konfigurazioa eskuz aldatu dela eta ez da segurua ez duelako baldintzarik jartzen 'AllowGroups' edo AllowUsers' fitxategien sarbidea oztopatzeko.", + "diagnosis_sshd_config_insecure": "Badirudi SSH konfigurazioa eskuz aldatu dela eta ez da segurua ez duelako 'AllowGroups' edo 'AllowUsers' baldintzarik jartzen fitxategien atzitzea oztopatzeko.", "disk_space_not_sufficient_update": "Ez dago aplikazio hau eguneratzeko nahikoa espaziorik", "domain_cannot_add_xmpp_upload": "Ezin dira 'xmpp-upload.' hasiera duten domeinuak gehitu. Izen mota hau YunoHosten zati den XMPP igoeretarako erabiltzen da.", "domain_cannot_remove_main_add_new_one": "Ezin duzu '{domain}' ezabatu domeinu nagusi eta bakarra delako. Beste domeinu bat gehitu 'yunohost domain add ' exekutatuz, gero erabili 'yunohost domain main-domain -n ' domeinu nagusi bilakatzeko, eta azkenik ezabatu {domain}' domeinua 'yunohost domain remove {domain}' komandoarekin.", @@ -323,7 +323,7 @@ "invalid_number_max": "{max} baino txikiagoa izan behar da", "diagnosis_services_bad_status": "{service} zerbitzua {status} dago :(", "diagnosis_ports_needed_by": "{category} funtzioetarako ezinbestekoa da ataka hau eskuragarri egotea (zerbitzua {service})", - "diagnosis_package_installed_from_sury": "Sistemaren pakete batzuen lehenagoko bertsioa beharko litzateke", + "diagnosis_package_installed_from_sury": "Sistemaren pakete batzuen lehenagoko bertsioak beharko lirateke", "global_settings_setting_smtp_relay_password": "SMTP relay helbideko pasahitza", "global_settings_setting_smtp_relay_port": "SMTP relay ataka", "domain_deleted": "Domeinua ezabatu da", @@ -379,8 +379,8 @@ "diagnosis_mail_queue_unavailable": "Ezin da kontsultatu zenbat posta elektroniko dauden ilaran", "log_user_create": "Gehitu '{}' erabiltzailea", "group_cannot_edit_visitors": "'bisitariak' taldea ezin da eskuz aldatu. Saiorik hasi gabeko bisitariak barne hartzen dituen talde berezia da", - "diagnosis_ram_verylow": "Sistemak RAM memoriaren {available} ({available_percent}%) dauka bakarrik! ({total}(e)tik)", - "diagnosis_ram_low": "Sistemak {available} soilik du erabilgarri, ({available_percent}%), RAM memoriaren (out of {total}). Kontuz ibili", + "diagnosis_ram_verylow": "Sistemak RAM memoriaren {available} ({available_percent}%) dauka bakarrik! ({total} orotara)", + "diagnosis_ram_low": "Sistemak {available} soilik du erabilgarri, RAM memoriaren ({available_percent}%) ({total} orotara). Kontuz ibili.", "diagnosis_ram_ok": "Sistemak oraindik dauka RAM memoriaren {available} ({available_percent}%) erabilgarri, {total} orotara.", "diagnosis_swap_none": "Sistemak ez du swap-ik. Gutxienez {recommended} gehitzen saiatu beharko zinateke, sistema memoriarik gabe gera ez dadin.", "diagnosis_swap_ok": "Sistemak {total} swap dauka!", @@ -488,7 +488,7 @@ "unexpected_error": "Ezusteko zerbaitek huts egin du: {error}", "updating_apt_cache": "Sistemaren paketeen eguneraketak eskuratzen…", "mail_forward_remove_failed": "Ezin izan da '{mail}' posta elektronikoko birbidalketa ezabatu", - "migration_description_0020_ssh_sftp_permissions": "Gehitu SSH eta SFTP baimenen funtzioak", + "migration_description_0020_ssh_sftp_permissions": "Gehitu SSH eta SFTP baimenak", "migration_ldap_migration_failed_trying_to_rollback": "Ezin izan da migratu… sistema lehengoratzen saiatzen.", "migrations_exclusive_options": "'--auto', '--skip', and '--force-rerun' aukerek batak bestea baztertzen du.", "migrations_running_forward": "{id} migrazioa exekutatzen…", @@ -514,7 +514,7 @@ "user_import_bad_line": "{line} lerro okerra: {details}", "restore_complete": "Lehengoratzea amaitu da", "restore_extracting": "Behar diren fitxategiak ateratzen…", - "tools_upgrade_cant_unhold_critical_packages": "Ezin izan dira pakete kritikoak alde batera utzi…", + "tools_upgrade_cant_unhold_critical_packages": "Ezin izan dira pakete kritikoak deuseztatu…", "tools_upgrade_regular_packages": "Orain pakete \"arruntak\" (YunoHostekin zerikusia ez dutenak) eguneratzen…", "tools_upgrade_special_packages": "Orain pakete \"bereziak\" (YunoHostekin zerikusia dutenak) eguneratzen…", "regenconf_would_be_updated": "'{category}' atalerako konfigurazioa eguneratu izango litzatekeen", @@ -695,7 +695,7 @@ "service_already_started": "'{service}' zerbitzua matxan dago dagoeneko", "service_already_stopped": "'{service}' zerbitzua geldiarazi da dagoeneko", "service_cmd_exec_failed": "Ezin izan da '{command}' komandoa exekutatu", - "service_description_dnsmasq": "DNSa \"domain name resolution\" kudeatzen du", + "service_description_dnsmasq": "Domeinuen izenen ebazpena (DNSa) kudeatzen du", "service_description_dovecot": "Posta elektronikoko programei mezuak jasotzea ahalbidetzen die (IMAP eta POP3 bidez)", "service_description_metronome": "Bat-bateko XMPP mezularitza kontuak kudeatzen ditu", "service_description_mysql": "Aplikazioen datuak gordetzen ditu (SQL datubasea)", From 5a213dae20157b607ec19f0ac0614a6d883c54b3 Mon Sep 17 00:00:00 2001 From: Page Asgardius Date: Sat, 23 Oct 2021 15:19:59 +0000 Subject: [PATCH 0884/1155] Translated using Weblate (Spanish) Currently translated at 78.0% (551 of 706 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/locales/es.json b/locales/es.json index 688db4546..dec90b42b 100644 --- a/locales/es.json +++ b/locales/es.json @@ -4,7 +4,7 @@ "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} ya está instalada", - "app_argument_choice_invalid": "Use una de estas opciones «{choices}» para el argumento «{name}»", + "app_argument_choice_invalid": "Elija un valor válido para el argumento '{name}': '{value}' no se encuentra entre las opciones disponibles ({choices})", "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", @@ -14,7 +14,7 @@ "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_removed": "{app} Desinstalado", "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?", @@ -520,7 +520,7 @@ "app_manifest_install_ask_is_public": "¿Debería exponerse esta aplicación a visitantes anónimos?", "app_manifest_install_ask_admin": "Elija un usuario administrativo para esta aplicación", "app_manifest_install_ask_password": "Elija una contraseña de administración para esta aplicación", - "app_manifest_install_ask_path": "Seleccione el path donde esta aplicación debería ser instalada", + "app_manifest_install_ask_path": "Seleccione la ruta de URL (después del dominio) donde esta aplicación debería ser instalada", "app_manifest_install_ask_domain": "Seleccione el dominio donde esta app debería ser instalada", "app_label_deprecated": "Este comando está depreciado! Favor usar el nuevo comando 'yunohost user permission update' para administrar la etiqueta de app.", "app_argument_password_no_default": "Error al interpretar argumento de contraseña'{name}': El argumento de contraseña no puede tener un valor por defecto por razón de seguridad", @@ -580,5 +580,11 @@ "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}» 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 + "additional_urls_already_added": "La URL adicional «{url}» ya se ha añadido para el permiso «{permission}»", + "config_apply_failed": "Falló la aplicación de la nueva configuración: {error}", + "app_restore_script_failed": "Ha ocurrido un error dentro del script de restauración de aplicaciones", + "app_config_unable_to_apply": "No se pudieron aplicar los valores del panel configuración.", + "app_config_unable_to_read": "No se pudieron leer los valores del panel configuración.", + "backup_create_size_estimation": "El archivo contendrá aproximadamente {size} de datos.", + "config_cant_set_value_on_section": "No puede establecer un único valor en una sección de configuración completa." +} From 5326f6d7a75f801bc690a63d2a5233d1b6a9061d Mon Sep 17 00:00:00 2001 From: punkrockgirl Date: Fri, 29 Oct 2021 23:22:14 +0000 Subject: [PATCH 0885/1155] Translated using Weblate (Basque) Currently translated at 100.0% (706 of 706 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eu/ --- locales/eu.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/eu.json b/locales/eu.json index 6f62ebf53..82063058a 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -1,6 +1,6 @@ { "password_too_simple_1": "Pasahitzak gutxienez zortzi karaktere izan behar ditu", - "action_invalid": "'{action}' ekintza baliogabea", + "action_invalid": "'{action}' eragiketa baliogabea", "aborting": "Bertan behera uzten.", "admin_password_changed": "Administrazio-pasahitza aldatu da", "admin_password_change_failed": "Ezinezkoa izan da pasahitza aldatzea", @@ -44,7 +44,7 @@ "diagnosis_ip_not_connected_at_all": "Badirudi zerbitzaria ez dagoela internetera konektatuta!?", "app_already_up_to_date": "{app} aplikazioa egunean da dagoeneko", "app_change_url_success": "{app} aplikazioaren URLa {domain}{path} da orain", - "admin_password_too_long": "Mesedez aukeratu 127 karaktere baino laburragoa den pasahitz bat", + "admin_password_too_long": "Mesedez, aukeratu 127 karaktere baino laburragoa den pasahitz bat", "app_action_broke_system": "Ekintza honek {services} zerbitzu garrantzitsuak hondatu dituela dirudi", "diagnosis_basesystem_hardware_model": "Zerbitzariaren modeloa {model} da", "already_up_to_date": "Ez dago egiteko ezer. Guztia dago egunean.", From 3a5765a5e464c9d6b49c08238bbc7f9a27a50b40 Mon Sep 17 00:00:00 2001 From: punkrockgirl Date: Sat, 30 Oct 2021 13:29:09 +0000 Subject: [PATCH 0886/1155] Translated using Weblate (Basque) Currently translated at 100.0% (706 of 706 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eu/ --- locales/eu.json | 408 ++++++++++++++++++++++++------------------------ 1 file changed, 204 insertions(+), 204 deletions(-) diff --git a/locales/eu.json b/locales/eu.json index 82063058a..bbe78029a 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -1,6 +1,6 @@ { "password_too_simple_1": "Pasahitzak gutxienez zortzi karaktere izan behar ditu", - "action_invalid": "'{action}' eragiketa baliogabea", + "action_invalid": "'{action}' eragiketa baliogabea da", "aborting": "Bertan behera uzten.", "admin_password_changed": "Administrazio-pasahitza aldatu da", "admin_password_change_failed": "Ezinezkoa izan da pasahitza aldatzea", @@ -8,44 +8,44 @@ "additional_urls_already_removed": "'{url}' URL gehigarriari '{permission}' baimena kendu zaio dagoeneko", "admin_password": "Administrazio-pasahitza", "diagnosis_ip_global": "IP orokorra: {global}", - "app_argument_password_no_default": "Errorea egon da '{name}' pasahitzaren argumentua ikuskatzean: pasahitzaren argumentuak ezin du balio hori izan segurtasun urria duela-eta", + "app_argument_password_no_default": "Errorea egon da '{name}' pasahitzaren argumentua ikuskatzean: pasahitzak ezin du balio hori izan segurtasuna dela-eta", "app_extraction_failed": "Ezinezkoa izan da instalazio fitxategiak ateratzea", "app_requirements_unmeet": "{app}(e)k behar dituen baldintzak ez dira betetzen, {pkgname} ({version}) paketea {spec} izan behar da", "backup_deleted": "Babeskopia ezabatuta", - "app_argument_required": "'{name}' argumentua beharrezkoa da", - "certmanager_acme_not_configured_for_domain": "Ezin da ACME azterketa {domain} domeinurako burutu une honetan nginx konfigurazioak ez duelako beharrezko kodea… Baieztatu nginx-en konfigurazioa egunean dagoela 'yunohost tools regen-conf nginx --dry-run --with-diff' komandoa exekutatuz.", + "app_argument_required": "'{name}' argumentua ezinbestekoa da", + "certmanager_acme_not_configured_for_domain": "Ezinezkoa da ACME azterketa {domain} domeinurako burutzea une honetan nginx ezarpenek ez dutelako beharrezko kodea… Egiaztatu nginx ezarpenak egunean daudela 'yunohost tools regen-conf nginx --dry-run --with-diff' komandoa exekutatuz.", "certmanager_domain_dns_ip_differs_from_public_ip": "'{domain}' domeinurako DNS balioak ez datoz bat zerbitzariaren IParekin. Mesedez, egiaztatu 'DNS balioak' (oinarrizkoa) kategoria diagnostikoen atalean. A balioak duela gutxi aldatu badituzu, itxaron hedatu daitezen (badaude DNSen hedapena ikusteko erramintak interneten). (Zertan ari zeren baldin badakizu, erabili '--no-checks' egiaztapen horiek desgaitzeko.)", - "confirm_app_install_thirdparty": "KONTUZ! Aplikazio hau ez da YunoHosten aplikazioen katalogokoa. Kanpoko aplikazioek sistemaren integritate eta segurtasuna arriskuan jarri dezakete. Ziur asko EZ zenuke instalatu beharko zertan ari zaren ez badakizu. Aplikazio hau ez badabil edo sistema hondatzen badu EZ DA LAGUNTZARIK EMANGO… aurrera jarraitu nahi duzu hala ere? Aukeratu '{answers}'", + "confirm_app_install_thirdparty": "KONTUZ! Aplikazio hau ez da YunoHosten aplikazioen katalogokoa. Kanpoko aplikazioek sistemaren integritate eta segurtasuna arriskuan jarri dezakete. Ziur asko EZ zenuke instalatu beharko zertan ari zaren ez badakizu. Aplikazio hau ez badabil edo sistema kaltetzen badu EZ DA LAGUNTZARIK EMANGO… aurrera jarraitu nahi duzu hala ere? Aukeratu '{answers}'", "app_start_remove": "{app} ezabatzen…", - "diagnosis_http_hairpinning_issue_details": "Litekeena da erantzulea zure kable-modem / routerra izatea. Honen eraginez, saretik kanpo daudenek zerbitzaria arazorik gabe erabili ahal izango dute, baina sarean bertan daudenek (ziur asko zure kasua) ezingo dute kanpoko IPa edo domeinu izena erabili zerbitzarira konektatzeko. Egoera hobetu edo guztiz konpontzeko, irakurri dokumentazioa. [Itzultzailearen oharra: SBC merke batean Pi-Hole instalatu eta bertako Local DNS > DNS records baliatu arazo hau ekiditeko]", + "diagnosis_http_hairpinning_issue_details": "Litekeena da erantzulea zure kable-modem / routerra izatea. Honen eraginez, saretik kanpo daudenek zerbitzaria arazorik gabe erabili ahal izango dute, baina sare lokalean bertan daudenek (ziur asko zure kasua) ezingo dute kanpoko IPa edo domeinu izena erabili zerbitzarira konektatzeko. Egoera hobetu edo guztiz konpontzeko, irakurri dokumentazioa", "diagnosis_http_special_use_tld": "{domain} domeinua top-level domain (TLD) motakoa da .local edo .test bezala eta ez du sare lokaletik kanpo eskuragarri zertan egon.", - "diagnosis_ip_weird_resolvconf_details": "/etc/resolv.conf fitxategia symlink bat izan beharko litzateke /etc/resolvconf/run/resolv.conf fitxategira 127.0.0.1ra adi dagoena (dnsmasq). DNS ebazleak eskuz konfiguratu nahi badituzu, mesedez aldatu /etc/resolv.dnsmasq.conf fitxategia.", + "diagnosis_ip_weird_resolvconf_details": "/etc/resolv.conf fitxategia symlink bat izan beharko litzateke 127.0.0.1ra adi dagoen /etc/resolvconf/run/resolv.conf fitxategira (dnsmasq). DNS ebazleak eskuz konfiguratu nahi badituzu, mesedez aldatu /etc/resolv.dnsmasq.conf fitxategia.", "diagnosis_ip_connected_ipv4": "Zerbitzaria IPv4 bidez dago internetera konektatuta!", "diagnosis_basesystem_ynh_inconsistent_versions": "YunoHost paketeen bertsioak ez datoz bat… ziur asko noizbait eguneraketa batek kale egin edo erabat amaitu ez zuelako.", "diagnosis_high_number_auth_failures": "Azken aldian kale egin duten saio-hasiera saiakera ugari egon dira. Egiaztatu fail2ban martxan dabilela eta egoki konfiguratuta dagoela, edo erabili beste ataka bat SSHrako dokumentazioan azaldu bezala.", - "diagnosis_mail_ehlo_could_not_diagnose": "Ezin izan da egiaztatu postfix posta zerbitzaria IPv{ipversion}az kanpo eskuragarri dagoenik.", + "diagnosis_mail_ehlo_could_not_diagnose": "Ezinezkoa izan da postfix posta zerbitzaria IPv{ipversion}az kanpo eskuragarri dagoen egiaztatzea.", "app_id_invalid": "Aplikazio ID okerra", - "app_install_files_invalid": "Fitxategi hauek ezin dira instalatu", + "app_install_files_invalid": "Ezin dira fitxategi hauek instalatu", "diagnosis_description_ip": "Internet konexioa", "diagnosis_description_dnsrecords": "DNS erregistroak", - "app_label_deprecated": "Komando hau zaharkitua dago! Mesedez erabili 'yunohost user permission update' komando berria aplikazioaren etiketa kudeatzeko.", - "confirm_app_install_danger": "KONTUZ! Aplikazio hau esperimentala da (edo ez dabil)! Ez zenuke instalatu beharko zertan ari zaren ez badakizu. Aplikazio hau ez badabil edo sistema hondatzen badu, EZ DA LAGUNTZARIK EMANGO… aurrera jarraitu nahi al duzu hala ere? Aukeratu '{answers}'", + "app_label_deprecated": "Komando hau zaharkitua dago! Mesedez, erabili 'yunohost user permission update' komando berria aplikazioaren etiketa kudeatzeko.", + "confirm_app_install_danger": "KONTUZ! Aplikazio hau esperimentala da (edo ez dabil)! Ez zenuke instalatu beharko zertan ari zaren ez badakizu. Aplikazio hau ez badabil edo sistema kaltetzen badu, EZ DA LAGUNTZARIK EMANGO… aurrera jarraitu nahi al duzu hala ere? Aukeratu '{answers}'", "diagnosis_description_systemresources": "Sistemaren baliabideak", - "backup_csv_addition_failed": "Ezin izan dira fitxategiak CSV fitxategira kopiatu", + "backup_csv_addition_failed": "Ezinezkoa izan da fitxategiak CSV fitxategira kopiatzea", "backup_no_uncompress_archive_dir": "Ez dago horrelako deskonprimatutako fitxategi katalogorik", "danger": "Arriskua:", "diagnosis_dns_discrepancy": "Ez dirudi ondorengo DNS balioak bat datozenik proposatutako konfigurazioarekin:
Mota: {type}
Izena: {name}
Oraingo balioa: {current}
Proposatutako balioa: {value}", "diagnosis_dns_specialusedomain": "{domain} domeinua top-level domain (TLD) erabilera berezikoa da .local edo .test bezala eta horregatik ez du DNS erregistrorik erabiltzeko beharrik.", - "diagnosis_http_bad_status_code": "Badirudi zerbitzaria ez den beste gailu batek erantzun diola eskaerari (agian routerrak).
1. Honen arrazoi ohikoena 80 (eta 443) ataka zerbitzarira ondo birbidaltzen ez dela da.
2. Konfigurazio konplexua badarabilzu, egiaztatu suebakiak edo reverse-proxyk oztopatzen ez dutela.", - "diagnosis_http_timeout": "Denbora agortu da sare lokaletik kanpo zure zerbitzarira konexioa gauzatzeko ahaleginean. Eskuragarri ez dagoela dirudi.
1. 80 (eta 443) ataka zerbitzarira modu egokian birbidaltzen ez direla da ohiko arrazoia.
2. Badaezpada egiaztatu nginx martxan dagoela.
3. Konfigurazio konplexuetan, egiaztatu suebakiak edo reverse-proxyk konexioa oztopatzen ez dutela.", - "app_sources_fetch_failed": "Ezin izan dira fitxategiak eskuratu, zuzena al da URLa?", - "app_make_default_location_already_used": "Ezinezkoa izan da '{app}' domeinuko aplikazio nagusi ezartzea, '{other_app}'(e)k dagoeneko '{domain}' erabiltzen duelako", - "app_already_installed_cant_change_url": "Aplikazio hau instalatuta dago dagoeneko. URLa ezin da aldatu aukera honekin. Markatu `app changeurl` markatzeko moduan badago.", + "diagnosis_http_bad_status_code": "Badirudi zerbitzari hau ez den beste gailu batek erantzun diola eskaerari (agian routerrak).
1. Honen arrazoi ohikoena 80 (eta 443) ataka zerbitzarira ondo birbidaltzen ez dela da.
2. Konfigurazio konplexua badarabilzu, egiaztatu suebakiak edo reverse-proxyk oztopatzen ez dutela.", + "diagnosis_http_timeout": "Denbora agortu da sare lokaletik kanpo zure zerbitzarira konektatzeko ahaleginean. Eskuragarri ez dagoela dirudi.
1. 80 (eta 443) ataka zerbitzarira modu egokian birzuzentzen ez direla da ohiko zergatia.
2. Badaezpada egiaztatu nginx martxan dagoela.
3. Konfigurazio konplexuetan, egiaztatu suebakiak edo reverse-proxyk konexioa oztopatzen ez dutela.", + "app_sources_fetch_failed": "Ezinezkoa izan da fitxategiak eskuratzea, zuzena al da URLa?", + "app_make_default_location_already_used": "Ezinezkoa izan da '{app}' '{domain}' domeinuan lehenestea, '{other_app}'(e)k dagoeneko '{domain}' erabiltzen duelako", + "app_already_installed_cant_change_url": "Aplikazio hau instalatuta dago dagoeneko. URLa ezin da aldatu aukera honekin. Markatu 'app changeurl' markatzeko moduan badago.", "diagnosis_ip_not_connected_at_all": "Badirudi zerbitzaria ez dagoela internetera konektatuta!?", - "app_already_up_to_date": "{app} aplikazioa egunean da dagoeneko", + "app_already_up_to_date": "{app} egunean da dagoeneko", "app_change_url_success": "{app} aplikazioaren URLa {domain}{path} da orain", "admin_password_too_long": "Mesedez, aukeratu 127 karaktere baino laburragoa den pasahitz bat", - "app_action_broke_system": "Ekintza honek {services} zerbitzu garrantzitsuak hondatu dituela dirudi", + "app_action_broke_system": "Eragiketa honek {services} zerbitzu garrantzitsua(k) hondatu d(it)uela dirudi", "diagnosis_basesystem_hardware_model": "Zerbitzariaren modeloa {model} da", "already_up_to_date": "Ez dago egiteko ezer. Guztia dago egunean.", "backup_permission": "{app}(r)entzat babeskopia baimena", @@ -53,35 +53,35 @@ "config_validate_email": "Benetazko posta elektronikoa izan behar da", "config_validate_time": "OO:MM formatua duen ordu bat izan behar da", "config_validate_url": "Benetazko URL bat izan behar da", - "config_version_not_supported": "Ezin da konfigurazio-panelaren '{version}' bertsioa erabili.", + "config_version_not_supported": "Ezinezkoa da konfigurazio-panelaren '{version}' bertsioa erabiltzea.", "app_restore_script_failed": "Errorea gertatu da aplikazioa lehengoratzeko aginduan", - "app_upgrade_some_app_failed": "Ezin izan dira aplikazio batzuk eguneratu", + "app_upgrade_some_app_failed": "Ezinezkoa izan da aplikazio batzuk eguneratzea", "app_install_failed": "Ezinezkoa izan da {app} instalatzea: {error}", "diagnosis_basesystem_kernel": "Zerbitzariak Linuxen {kernel_version} kernela darabil", - "app_argument_invalid": "Aukeratu balio onargarri bat {name}' argumenturako: {error}", + "app_argument_invalid": "Aukeratu balio egoki bat '{name}' argumenturako: {error}", "app_already_installed": "{app} instalatuta dago dagoeneko", "app_config_unable_to_apply": "Ezinezkoa izan da konfigurazio aukerak ezartzea.", "app_config_unable_to_read": "Ezinezkoa izan da konfigurazio aukerak irakurtzea.", "config_apply_failed": "Ezin izan da konfigurazio berria ezarri: {error}", - "config_cant_set_value_on_section": "Ezin da balio bakar bat ezarri konfigurazio atal oso batean.", + "config_cant_set_value_on_section": "Ezinezkoa da balio bakar bat ezartzea konfigurazio atal oso batean.", "config_no_panel": "Ez da konfigurazio-panelik aurkitu.", "diagnosis_found_errors_and_warnings": "{category} atalari dago(z)kion {errors} arazoa(k) (eta {warnings} abisua(k)) aurkitu d(ir)a!", "diagnosis_description_regenconf": "Sistemaren ezarpenak", "app_upgrade_script_failed": "Errore bat gertatu da aplikazioaren eguneratze aginduan", "diagnosis_basesystem_hardware": "Zerbitzariaren arkitektura {virt} {arch} da", - "diagnosis_mail_ehlo_ok": "SMTP posta zerbitzaria eskuragarri dago kanpoko saretik eta beraz, posta elektronikoa jasotzeko gai da!", + "diagnosis_mail_ehlo_ok": "SMTP posta zerbitzaria eskuragarri dago kanpoko saretik eta, beraz, posta elektronikoa jasotzeko gai da!", "app_unknown": "Aplikazio ezezaguna", "diagnosis_mail_ehlo_bad_answer": "SMTP ez den zerbitzu batek erantzun du IPv{ipversion}ko 25. atakan", "diagnosis_mail_ehlo_could_not_diagnose_details": "Errorea: {error}", - "diagnosis_mail_blacklist_ok": "Zerbitzari honek darabiltzan IPak eta domeinuak ez dirudi inolako zerrenda beltzetan daudenik", + "diagnosis_mail_blacklist_ok": "Ez dirudi zerbitzari honek darabiltzan IPak eta domeinuak inolako zerrenda beltzean daudenik", "diagnosis_domain_expiration_error": "Domeinu batzuk IRAUNGITZEAR daude!", "diagnosis_domain_expiration_success": "Domeinuak erregistratuta daude eta ez dira oraingoz iraungiko.", - "app_manifest_install_ask_is_public": "Saiorik hasi gabeko bisitarientzat ikusgai egon beharko litzateke aplikazio hau?", + "app_manifest_install_ask_is_public": "Saiorik hasi gabeko bisitarientzat ikusgai egon beharko luke aplikazioak?", "diagnosis_domain_expires_in": "{domain} {days} egun barru iraungiko da.", - "app_manifest_install_ask_domain": "Aukeratu zein domeinutan instalatu nahi duzun aplikazio hau", + "app_manifest_install_ask_domain": "Aukeratu zein domeinutan instalatu nahi duzun aplikazioa", "custom_app_url_required": "URL bat zehaztu behar duzu {app} eguneratzeko", "app_change_url_identical_domains": "Domeinu zahar eta berriaren bidea bera dira: ('{domain}{path}'), ez dago ezer egitekorik.", - "app_upgrade_failed": "Ezinezkoa {app} eguneratzea: {error}", + "app_upgrade_failed": "Ezinezkoa izan da {app} eguneratzea: {error}", "app_upgrade_app_name": "Orain {app} eguneratzen…", "app_upgraded": "{app} eguneratu da", "ask_firstname": "Izena", @@ -90,9 +90,9 @@ "config_forbidden_keyword": "'{keyword}' etiketa sistemak bakarrik erabil dezake; ezin da ID hau daukan baliorik sortu edo erabili.", "config_unknown_filter_key": "'{filter_key}' filtroaren kakoa ez da zuzena.", "config_validate_color": "RGB hamaseitar kolore bat izan behar da", - "diagnosis_cant_run_because_of_dep": "Ezin da diagnosia abiarazi {category} atalerako {dep}(r)i lotutako arazo garrantzitsuek dirauen artean.", + "diagnosis_cant_run_because_of_dep": "Ezinezkoa da diagnosia abiaraztea {category} atalerako {dep}(r)i lotutako arazo garrantzitsuek dirauen artean.", "diagnosis_dns_missing_record": "Proposatutako DNS konfigurazioaren arabera, ondorengo informazioa gehitu beharko zenuke DNS erregistroan:
Mota: {type}
Izena: {name}
Balioa: {value}", - "diagnosis_http_nginx_conf_not_up_to_date": "Domeinu honen nginx konfigurazioa eskuz moldatu dela dirudi eta YunoHostek ezin du egiaztatu HTTP bidez eskuragarri dagoen.", + "diagnosis_http_nginx_conf_not_up_to_date": "Domeinu honen nginx ezarpenak eskuz moldatu direla dirudi eta YunoHostek ezin du egiaztatu HTTP bidez eskuragarri dagoenik.", "ask_new_admin_password": "Administrazio-pasahitz berria", "ask_new_domain": "Domeinu berria", "ask_new_path": "Bide berria", @@ -103,23 +103,23 @@ "backup_archive_app_not_found": "Ezin izan da {app} aurkitu babeskopia fitxategian", "backup_applying_method_tar": "Babeskopiaren TAR fitxategia sortzen…", "backup_archive_broken_link": "Ezin izan da babeskopiaren fitxategia eskuratu ({path}ra esteka okerra)", - "backup_creation_failed": "Ezin izan da babeskopiaren fitxategia sortu", - "backup_csv_creation_failed": "Ezin izan da lehengoratzeko beharrezkoak diren CSV fitxategiak sortu", + "backup_creation_failed": "Ezinezkoa izan da babeskopiaren fitxategia sortzea", + "backup_csv_creation_failed": "Ezinezkoa izan da lehengoratzeko beharrezkoak diren CSV fitxategiak sortzea", "backup_custom_mount_error": "Neurrira egindako babeskopiak ezin izan du 'muntatu' urratsetik haratago egin", - "backup_delete_error": "Ezin izan da '{path}' ezabatu", + "backup_delete_error": "Ezinezkoa izan da '{path}' ezabatzea", "backup_method_copy_finished": "Babeskopiak amaitu du", - "backup_hook_unknown": "Babeskopiaren '{hook}' kakoa ez da ezagutzen", + "backup_hook_unknown": "Babeskopiaren '{hook}' kakoa ezezaguna da", "backup_method_custom_finished": "'{method}' neurrira egindako babeskopiak amaitu du", "backup_method_tar_finished": "TAR babeskopia artxiboa sortu da", - "backup_mount_archive_for_restore": "Lehengoratzeko fitxategoak prestatzen…", + "backup_mount_archive_for_restore": "Lehengoratzeko fitxategiak prestatzen…", "backup_nothings_done": "Ez dago gordetzeko ezer", "backup_output_directory_required": "Babeskopia non gorde nahi duzun zehaztu behar duzu", - "backup_system_part_failed": "Ezin izan da sistemaren '{part}' atalaren babeskopia egin", + "backup_system_part_failed": "Ezinezkoa izan da sistemaren '{part}' atalaren babeskopia egitea", "apps_catalog_updating": "Aplikazioen katalogoa eguneratzen…", - "certmanager_cert_signing_failed": "Ezin izan da ziurtagiri berria sinatu", + "certmanager_cert_signing_failed": "Ezinezkoa izan da ziurtagiri berria sinatzea", "certmanager_cert_renew_success": "Let's Encrypt ziurtagiria berriztu da '{domain}' domeinurako", "app_requirements_checking": "{app}(e)k behar dituen paketeak ikuskatzen…", - "certmanager_unable_to_parse_self_CA_name": "Ezin izan da norberak sinatutako ziurtagiriaren izena prozesatu (fitxategia: {file})", + "certmanager_unable_to_parse_self_CA_name": "Ezinezkoa izan da norberak sinatutako ziurtagiriaren izena prozesatzea (fitxategia: {file})", "app_remove_after_failed_install": "Aplikazioa ezabatzen instalatzerakoan errorea dela-eta…", "diagnosis_basesystem_ynh_single_version": "{package} bertsioa: {version} ({repo})", "diagnosis_failed_for_category": "'{category}' ataleko diagnostikoak kale egin du: {error}", @@ -127,17 +127,17 @@ "diagnosis_found_errors": "{category} atalari dago(z)kion {errors} arazoa(k) aurkitu d(ir)a!", "diagnosis_found_warnings": "{category} atalari dagokion eta hobetu daite(z)keen {warnings} abisua(k) aurkitu d(ir)a.", "diagnosis_ip_connected_ipv6": "Zerbitzaria IPv6 bidez dago internetera konektatuta!", - "diagnosis_everything_ok": "Itxura ona dauka {category} atalak!", + "diagnosis_everything_ok": "Badirudi guztia zuzen dagoela {category} atalean!", "diagnosis_ip_no_ipv4": "Zerbitzariak ez du dabilen IPv4rik.", "diagnosis_ip_no_ipv6": "Zerbitzariak ez du dabilen IPv6rik.", - "diagnosis_ip_broken_dnsresolution": "Domeinu izenaren ebazpena hondatuta dagoela dirudi… Suebakiren bat ote dago DNS eskaerak oztopatzen?", - "diagnosis_diskusage_low": "{mountpoint} euskarriak ({device} gailuan) edukieraren {free} ({free_percent}%) bakarrik du erabilgarri ({total} orotara). Kontuz ibili.", + "diagnosis_ip_broken_dnsresolution": "Domeinu izenaren ebazpena kaltetuta dagoela dirudi… Suebakiren bat ote dago DNS eskaerak oztopatzen?", + "diagnosis_diskusage_low": "{mountpoint} fitxategi-sistemak ({device} euskarrian) edukieraren {free} ({free_percent}%) bakarrik du erabilgarri ({total} orotara). Kontuz ibili.", "diagnosis_dns_good_conf": "DNS ezarpenak zuzen konfiguratuta daude {domain} domeinurako ({category} atala)", - "diagnosis_diskusage_verylow": "{mountpoint} euskarriak ({device} gailuan) edukieraren {free} ({free_percent}%) bakarrik du erabilgarri ({total} orotara). Zertxobait hustu beharko zenuke!", + "diagnosis_diskusage_verylow": "{mountpoint} fitxategi-sistemak ({device} euskarrian) edukieraren {free} ({free_percent}%) bakarrik du erabilgarri ({total} orotara). Zertxobait hustu beharko zenuke!", "diagnosis_description_basesystem": "Sistemaren oinarria", "diagnosis_description_services": "Zerbitzuen egoeraren egiaztapena", - "diagnosis_http_could_not_diagnose": "Ezin izan da egiaztatu domeinuak IPv{ipversion} kanpotik eskuragarri daudenik.", - "diagnosis_http_ok": "Ezin da {domain} domeinua HTTP bidez bisitatu sare lokaletik kanpo.", + "diagnosis_http_could_not_diagnose": "Ezinezkoa izan da domeinuak IPv{ipversion} kanpotik eskuragarri dauden egiaztatzea.", + "diagnosis_http_ok": "{domain} domeinua HTTP bidez bisitatu daiteke sare lokaletik kanpo.", "diagnosis_http_unreachable": "Badirudi {domain} domeinua ez dagoela eskuragarri HTTP bidez sare lokaletik kanpo.", "apps_catalog_failed_to_download": "Ezinezkoa izan da {apps_catalog} aplikazioen zerrenda eskuratzea: {error}", "apps_catalog_init_success": "Abiarazi da aplikazioen katalogo sistema!", @@ -147,47 +147,47 @@ "diagnosis_description_web": "Weba", "diagnosis_display_tip": "Aurkitu diren arazoak ikusteko joan Diagnosien atalera administrazio-webgunean, edo exekutatu 'yunohost diagnosis show --issues --human-readable' komandoak nahiago badituzu.", "diagnosis_dns_point_to_doc": "Mesedez, irakurri dokumentazioa DNS erregistroekin laguntza behar baduzu.", - "diagnosis_mail_ehlo_unreachable": "SMTP posta zerbitzaria ez dago eskuragarri IPv{ipversion}ko sare lokaletik kanpo eta beraz, ez da posta elektronikoa jasotzeko gai.", + "diagnosis_mail_ehlo_unreachable": "SMTP posta zerbitzaria ez dago eskuragarri IPv{ipversion}ko sare lokaletik kanpo eta, beraz, ez da posta elektronikoa jasotzeko gai.", "diagnosis_mail_ehlo_bad_answer_details": "Litekeena da zure zerbitzaria ez den beste gailu batek erantzun izana.", - "diagnosis_mail_blacklist_listed_by": "Zure IP edo {item} domeinua {blacklist_name} zerrenda beltzean ageri da", + "diagnosis_mail_blacklist_listed_by": "Zure {item} IPa edo domeinua {blacklist_name} zerrenda beltzean ageri da", "diagnosis_mail_blacklist_website": "Zerrenda beltzean zergatik zauden ulertu eta konpondu ondoren, {blacklist_website} webgunean zure IP edo domeinua bertatik atera dezatela eska dezakezu", "diagnosis_http_could_not_diagnose_details": "Errorea: {error}", "diagnosis_http_hairpinning_issue": "Dirudienez zure sareak ez du hairpinninga gaituta.", "diagnosis_http_partially_unreachable": "Badirudi {domain} domeinua ezin dela bisitatu HTTP bidez IPv{failed} sare lokaletik kanpo, bai ordea IPv{passed} erabiliz.", - "backup_archive_cant_retrieve_info_json": "Ezin izan da '{archive}' fitxategiko informazioa eskuratu… info.json ezin izan da eskuratu (edo ez da baliozko jsona).", - "diagnosis_domain_expiration_not_found": "Ezin izan da domeinu batzuen iraungitze data egiaztatu", - "diagnosis_domain_expiration_not_found_details": "Dirudienez {domain} domeinuari buruzko WHOIS informazioak ez du zehazten noiz iraungiko den.", + "backup_archive_cant_retrieve_info_json": "Ezinezkoa izan da '{archive}' fitxategiko informazioa eskuratzea… info.json ezin izan da eskuratu (edo ez da baliozko jsona).", + "diagnosis_domain_expiration_not_found": "Ezinezkoa izan da domeinu batzuen iraungitze data egiaztatzea", + "diagnosis_domain_expiration_not_found_details": "Badirudi {domain} domeinuari buruzko WHOIS informazioak ez duela zehazten noiz iraungiko den.", "certmanager_domain_not_diagnosed_yet": "Oraindik ez dago {domain} domeinurako diagnostikorik. Mesedez, berrabiarazi diagnostikoak 'DNS balioak' eta 'Web' ataletarako diagnostikoen gunean Let's Encrypt ziurtagirirako prest ote dagoen egiaztatzeko. (Edo zertan ari zaren baldin badakizu, erabili '--no-checks' egiaztatzea desgaitzeko.)", "diagnosis_domain_expiration_warning": "Domeinu batzuk iraungitzear daude!", "app_packaging_format_not_supported": "Aplikazio hau ezin da instalatu YunoHostek ez duelako paketea ezagutzen. Sistema eguneratzea hausnartu beharko zenuke ziur asko.", "diagnosis_dns_try_dyndns_update_force": "Domeinu honen DNS konfigurazioa YunoHostek kudeatu beharko luke automatikoki. Gertatuko ez balitz, eguneratzera behartu zenezake yunohost dyndns update --force erabiliz.", "app_manifest_install_ask_path": "Aukeratu aplikazio hau instalatzeko URLaren bidea (domeinuaren atzeko aldean)", - "app_manifest_install_ask_admin": "Aukeratu administrati bat aplikazio honetarako", + "app_manifest_install_ask_admin": "Aukeratu administrari bat aplikazio honetarako", "app_manifest_install_ask_password": "Aukeratu administrazio-pasahitz bat aplikazio honetarako", "ask_user_domain": "Erabiltzailearen posta elektroniko eta XMPP konturako erabiliko den domeinua", - "app_action_cannot_be_ran_because_required_services_down": "{services} zerbitzuak martxan egon beharko lirateke ekintza hau gauzatu ahal izateko. Saia zaitez zerbitzuok berrabiarazten (eta ikertu zergatik abiarazi ez diren).", + "app_action_cannot_be_ran_because_required_services_down": "{services} zerbitzuak martxan egon beharko lirateke eragiketa hau exekutatu ahal izateko. Saia zaitez zerbitzuok berrabiarazten (eta ikertu zergatik ez diren abiarazi).", "apps_already_up_to_date": "Egunean daude dagoeneko aplikazio guztiak", "app_full_domain_unavailable": "Aplikazio honek bere domeinu propioa behar du, baina beste aplikazio batzuk daude dagoeneko instalatuta '{domain}' domeinuan. Azpidomeinu bat erabil zenezake instalatu nahi duzun aplikaziorako.", "app_install_script_failed": "Errore bat gertatu da aplikazioaren instalatzailearen aginduetan", "diagnosis_basesystem_host": "Zerbitzariak Debian {debian_version} darabil", - "diagnosis_ignored_issues": "(kontuan hartu ez d(ir)en + {nb_ignored} arazoa(k))", + "diagnosis_ignored_issues": "(kontutan hartu ez d(ir)en + {nb_ignored} arazo)", "diagnosis_ip_dnsresolution_working": "Domeinu izenaren ebazpena badabil!", - "diagnosis_failed": "Ezin izan da '{category}' ataleko diagnostikoa lortu: {error}", + "diagnosis_failed": "Ezinezkoa izan da '{category}' ataleko diagnostikoa lortzea: {error}", "diagnosis_ip_weird_resolvconf": "DNS ebazpena badabilela dirudi, baina antza denez moldatutako /etc/resolv.conf fitxategia erabiltzen ari zara.", "diagnosis_dns_bad_conf": "DNS balio batzuk falta dira edo ez dira zuzenak {domain} domeinurako ({category} atala)", - "diagnosis_diskusage_ok": "{mountpoint} euskarriak ({device} gailuan) edukieraren {free} ({free_percent}%) du erabilgarri oraindik ({total} orotara)!", + "diagnosis_diskusage_ok": "{mountpoint} fitxategi-sistemak ({device} euskarrian) edukieraren {free} ({free_percent}%) du erabilgarri oraindik ({total} orotara)!", "apps_catalog_update_success": "Aplikazioen katalogoa eguneratu da!", "certmanager_warning_subdomain_dns_record": "'{subdomain}' azpidomeinuak ez dauka '{domain}'(e)k duen IP bera. Ezaugarri batzuk ez dira erabilgarri egongo hau zuzendu arte eta ziurtagiri bat birsortu arte.", - "app_argument_choice_invalid": "Aukeratu ({choices}) aukeretako bat '{name}' argumenturako: '{value}' ez dago aukera horien artean", + "app_argument_choice_invalid": "Hautatu ({choices}) aukeretako bat '{name}' argumenturako: '{value}' ez dago aukera horien artean", "backup_create_size_estimation": "Fitxategiak {size} datu inguru izango ditu.", "diagnosis_basesystem_ynh_main_version": "Zerbitzariak YunoHosten {main_version} ({repo}) darabil", "backup_custom_backup_error": "Neurrira egindako babeskopiak ezin izan du 'babeskopia egin' urratsetik haratago egin", - "diagnosis_ip_broken_resolvconf": "Zure zerbitzarian domeinu izenaren ebazpena hondatuta dagoela dirudi, antza denez /etc/resolv.conf fitxategia ez dago 127.0.0.1ra adi.", + "diagnosis_ip_broken_resolvconf": "Zure zerbitzarian domeinu izenaren ebazpena kaltetuta dagoela dirudi, antza denez /etc/resolv.conf fitxategia ez dago 127.0.0.1ra adi.", "diagnosis_ip_no_ipv6_tip": "Dabilen IPv6 izatea ez da derrigorrezkoa zerbitzariaren funtzionamendurako, baina egokiena da interneten osasunerako. IPv6 automatikoki konfiguratu beharko luke sistemak edo telefono-konpainiak. Bestela, eskuz konfiguratu beharko zenituzke hainbat gauza dokumentazioan azaltzen den bezala. Ezin baduzu edo IPv6 gaitzea zuretzat kontu teknikoegia baldin bada, ez duzu abisu hau zertan kontutan hartu.", "diagnosis_http_nginx_conf_not_up_to_date_details": "Egoera konpontzeko, ikuskatu desberdintasunak yunohost tools regen-conf nginx --dry-run --with-diff komandoren bidez eta, proposatutako aldaketak onartzen badituzu, ezarri itzazu yunohost tools regen-conf nginx --force erabiliz.", "diagnosis_domain_not_found_details": "{domain} domeinua ez da WHOISen datubasean existitzen edo iraungi da!", - "app_start_backup": "{app}(r)en babes-kopia egiteko fitxategiak hartzen…", - "app_change_url_no_script": "'{app_name}' aplikazioak ez du URLa moldatzerik onartzen momentuz. Agian eguneratu beharko zenuke.", + "app_start_backup": "{app}(r)en babeskopia egiteko fitxategiak eskuratzen…", + "app_change_url_no_script": "'{app_name}' aplikazioak oraingoz ez du URLa moldatzerik onartzen. Agian eguneratu beharko zenuke.", "app_location_unavailable": "URL hau ez dago erabilgarri edota dagoeneko instalatutako aplikazioren batekin talka egiten du:\n{apps}", "app_not_upgraded": "'{failed_app}' aplikazioa ezin izan da eguneratu, eta horregatik ondorengo aplikazioen eguneraketak bertan behera utzi dira: {apps}", "app_not_correctly_installed": "Ez dirudi {app} ondo instalatuta dagoenik", @@ -195,54 +195,54 @@ "app_not_properly_removed": "Ezinezkoa izan da {app} guztiz ezabatzea", "app_start_install": "{app} instalatzen…", "app_start_restore": "{app} lehengoratzen…", - "app_unsupported_remote_type": "Aplikazioak darabilen urruneko motak ez du babesik", + "app_unsupported_remote_type": "Aplikazioak darabilen urruneko motak ez du babesik (Unsupported remote type)", "app_upgrade_several_apps": "Ondorengo aplikazioak eguneratuko dira: {apps}", "backup_app_failed": "Ezinezkoa izan da {app}(r)en babeskopia egitea", "backup_actually_backuping": "Bildutako fitxategiekin babeskopia sortzen…", "backup_archive_name_exists": "Dagoeneko existitzen da izen bera duen babeskopia fitxategi bat.", "backup_archive_name_unknown": "Ez da '{name}' izeneko babeskopia ezagutzen", - "backup_archive_open_failed": "Ezin izan da babeskopien fitxategia ireki", + "backup_archive_open_failed": "Ezinezkoa izan da babeskopien fitxategia irekitzea", "backup_archive_system_part_not_available": "'{part}' sistemaren atala ez dago erabilgarri babeskopia honetan", - "backup_archive_writing_error": "Ezin izan da '{source}' ('{dest}' fitxategiak eskatu dituenak) fitxategia '{archive}' konprimatutako babeskopian sartu", - "backup_ask_for_copying_if_needed": "Denbora batez {size}MB erabili nahi dituzu babeskopia gauzatu ahal izateko? (Horrela egiten da fitxategi batzuk ezin direlako modu eraginkorragoan prestatu.)", - "backup_cant_mount_uncompress_archive": "Ezin izan da deskonprimatutako fitxategia muntatu idazketa-babesa duelako", - "backup_created": "Egin da babeskopia", + "backup_archive_writing_error": "Ezinezkoa izan da '{source}' ('{dest}' fitxategiak eskatu dituenak) fitxategia '{archive}' konprimatutako babeskopian sartzea", + "backup_ask_for_copying_if_needed": "Behin-behinean {size}MB erabili nahi dituzu babeskopia gauzatu ahal izateko? (Horrela egiten da fitxategi batzuk ezin direlako modu eraginkorragoan prestatu.)", + "backup_cant_mount_uncompress_archive": "Ezinezkoa izan da deskonprimatutako fitxategia muntatzea idazketa-babesa duelako", + "backup_created": "Babeskopia sortu da", "backup_copying_to_organize_the_archive": "{size}MB kopiatzen fitxategia antolatzeko", "backup_couldnt_bind": "Ezin izan da {src} {dest}-ra lotu.", - "backup_output_directory_forbidden": "Aukeratu beste katalogo bat emaitza gordetzeko. Babeskopiak ezin dira sortu /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var or /home/yunohost.backup/archives azpi-katalogoetan", + "backup_output_directory_forbidden": "Aukeratu beste katalogo bat emaitza gordetzeko. Babeskopiak ezin dira sortu /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var edo /home/yunohost.backup/archives azpi-katalogoetan", "backup_output_directory_not_empty": "Aukeratu hutsik dagoen katalogo bat", "backup_running_hooks": "Babeskopien kakoak exekutatzen…", - "backup_unable_to_organize_files": "Ezin izan da modu azkarra erabili fitxategiko artxiboak prestatzeko", + "backup_unable_to_organize_files": "Ezinezkoa izan da modu azkarra erabiltzea fitxategiko artxiboak prestatzeko", "backup_output_symlink_dir_broken": "'{path}' fitxategi-katalogoaren symlink-a ez dabil. Agian [ber]muntatzea ahaztu zaizu edo euskarria atakara konektatzea ahaztu duzu.", "backup_with_no_backup_script_for_app": "'{app}' aplikazioak ez du babeskopia egiteko agindurik. Ez da kontutan hartuko.", "backup_with_no_restore_script_for_app": "{app}(e)k ez du lehengoratzeko agindurik, ezingo duzu aplikazio hau automatikoki lehengoratu.", "certmanager_attempt_to_renew_nonLE_cert": "'{domain}' domeinurako ziurtagiria ez da Let's Encryptek jaulkitakoa. Ezin da automatikoki berriztu!", "certmanager_attempt_to_renew_valid_cert": "'{domain}' domeinurako ziurtagiria iraungitzear dago! (Zertan ari zaren baldin badakizu, --force erabil dezakezu)", - "certmanager_cannot_read_cert": "Arazoren bat egon da {domain} (fitxategia: {file}) domeinurako oraingo ziurtagiria irekitzen saiatzerakoan, arrazoia: {reason}", + "certmanager_cannot_read_cert": "Arazoren bat egon da {domain} (fitxategia: {file}) domeinurako oraingo ziurtagiria irekitzen saiatzerakoan, zergatia: {reason}", "certmanager_cert_install_success": "Let's Encrypt ziurtagiria instalatu da '{domain}' domeinurako", "certmanager_cert_install_success_selfsigned": "Norberak sinatutako ziurtagiria instalatu da '{domain}' domeinurako", - "certmanager_domain_cert_not_selfsigned": "{domain} domeinurako ziurtagiria ez da norberak sinatutakoa. Ziur al zaude ordezkatzeaz? (Erabili '--force' hori egiteko.)", + "certmanager_domain_cert_not_selfsigned": "{domain} domeinurako ziurtagiria ez da norberak sinatutakoa. Ziur al zaude ordezkatu nahi duzula? (Erabili '--force' hori egiteko.)", "certmanager_certificate_fetching_or_enabling_failed": "{domain} domeinurako ziurtagiri berriak kale egin du…", - "certmanager_domain_http_not_working": "Ez dirudi {domain} domeinua HTTP bidez ikusgai dagoenik. Mesedez, egiaztatu 'Web' kategoria diagnosien gunean informazio gehiagorako. (zertan ari zaren baldin badakizu, erabili '--no-checks' egiaztapen horiek desgaitzeko.)", - "certmanager_hit_rate_limit": "{domain} domeinu-multzurako ziurtagiri gehiegi jaulki dira dagoeneko. Mesedez, saia saitez geroago. Ikus https://letsencrypt.org/docs/rate-limits/ xehetasun gehiagorako", - "certmanager_no_cert_file": "Ezin izan da ziurtagiri fitxategia irakurri {domain} (fitxategia: {file}) domeinurako", - "certmanager_self_ca_conf_file_not_found": "Ezin izan da konfigurazio-fitxategia aurkitu norberak sinatutako ziurtagirirako (fitxategia: {file})", + "certmanager_domain_http_not_working": "Ez dirudi {domain} domeinua HTTP bidez ikusgai dagoenik. Mesedez, egiaztatu 'Weba' atala diagnosien gunean informazio gehiagorako. (Zertan ari zaren baldin badakizu, erabili '--no-checks' egiaztapen horiek desgaitzeko.)", + "certmanager_hit_rate_limit": "{domain} domeinu-multzorako ziurtagiri gehiegi jaulki dira dagoeneko. Mesedez, saia saitez geroago. Ikus https://letsencrypt.org/docs/rate-limits/ xehetasun gehiagorako", + "certmanager_no_cert_file": "Ezinezkoa izan da {domain} domeinurako ziurtagiri fitxategia irakurrtzea (fitxategia: {file})", + "certmanager_self_ca_conf_file_not_found": "Ezinezkoa izan da konfigurazio-fitxategia aurkitzea norberak sinatutako ziurtagirirako (fitxategia: {file})", "confirm_app_install_warning": "Adi: litekeena da aplikazio hau ibiltzea baina ez dago YunoHostera egina. Ezaugarri batzuk, SSO edo babeskopia/lehengoratzea esaterako, desgaituta egon daitezke. Instalatu hala ere? [{answers}] ", "diagnosis_description_ports": "Ataken irisgarritasuna", - "backup_archive_corrupted": "Badirudi '{archive}' babeskopia fitxategia hondatuta dagoela: {error}", + "backup_archive_corrupted": "Badirudi '{archive}' babeskopia fitxategia kaltetuta dagoela: {error}", "diagnosis_ip_local": "IP lokala: {local}", - "diagnosis_mail_blacklist_reason": "Zerrenda beltzean egotearen arrazoia zera da: {reason}", + "diagnosis_mail_blacklist_reason": "Zerrenda beltzean egotearen zergatia zera da: {reason}", "app_removed": "{app} desinstalatu da", - "backup_cleaning_failed": "Ezin izan da behin-behineko babeskopien karpeta hustu", + "backup_cleaning_failed": "Ezinezkoa izan da behin-behineko babeskopien karpeta hustea", "certmanager_attempt_to_replace_valid_cert": "{domain} domeinurako egokia eta baliogarria den ziurtagiri bat ordezkatzen saiatzen ari zara! (Erabili --force mezu hau deuseztatu eta ziurtagiria ordezkatzeko)", "diagnosis_backports_in_sources_list": "Dirudienez apt (pakete kudeatzailea) backports biltegia erabiltzeko konfiguratuta dago. Zertan ari zaren ez badakizu, ez zenuke backports biltegietako aplikaziorik instalatu beharko, ezegonkortasun eta gatazkak eragin ditzaketelako sistemarekin.", "app_restore_failed": "Ezinezkoa izan da {app} lehengoratzea: {error}", "diagnosis_apps_allgood": "Instalatutako aplikazioek oinarrizko pakete-jarraibideekin bat egiten dute", "diagnosis_apps_bad_quality": "Aplikazio hau hondatuta dagoela dio YunoHosten aplikazioen katalogoak. Agian behin-behineko kontua da arduradunak arazoa konpondu bitartean. Momentuz, ezin da aplikazioa eguneratu.", - "diagnosis_apps_broken": "Aplikazio hau hondatuta dagoela ageri da YunoHosten aplikazioen katalogoan. Agian, behin-behineko kontua da arduradunak konpondu bitartean. Momentuz, ezin da aplikazioa eguneratu.", - "diagnosis_apps_deprecated_practices": "Instalatutako aplikazio honen bertsioak oraindik darabil zaharkitutako pakete-jarraibideak. Eguneratzea hausnartu beharko zenuke.", + "diagnosis_apps_broken": "Aplikazio hau YunoHosten aplikazioen katalogoan hondatuta dagoela ageri da. Agian behin-behineko kontua da arduradunak konpondu bitartean. Momentuz, ezin da aplikazioa eguneratu.", + "diagnosis_apps_deprecated_practices": "Instalatutako aplikazio honen bertsioak oraindik darabiltza zaharkitutako pakete-jarraibideak. Eguneratzea hausnartu beharko zenuke.", "diagnosis_apps_issue": "Arazo bat dago {app} aplikazioarekin", - "diagnosis_apps_not_in_app_catalog": "Aplikazio hau ez da YunoHosten aplikazioen katalogokoa. Iraganean egon bazen eta ezabatu izan balitz, desinstalatzea litzateke onena, ez baitu eguneraketarik jasoko eta sistemaren integritate eta segurtasuna arriskuan jarri lezake.", + "diagnosis_apps_not_in_app_catalog": "Aplikazio hau ez da YunoHosten aplikazioen katalogokoa. Iraganean egon bazen eta ezabatu izan balitz, desinstalatzea litzateke onena, ez baitu eguneraketarik jasoko eta sistemaren integritate eta segurtasuna arriskuan jarri lezakeelako.", "diagnosis_apps_outdated_ynh_requirement": "Instalatutako aplikazio honen bertsioak yunohost >= 2.x baino ez du behar, eta horrek egungo pakete-jardunbideekin bat ez datorrela iradokitzen du. Eguneratzen saiatu beharko zinateke.", "diagnosis_description_apps": "Aplikazioak", "domain_dns_conf_special_use_tld": "Domeinu hau top-level domain (TLD) erabilera bereziko motakoa da .local edo .test bezala eta ez du DNS ezarpenik behar.", @@ -253,76 +253,76 @@ "group_user_already_in_group": "{user} erabiltzailea {group} taldean dago dagoeneko", "firewall_reloaded": "Suebakia birkargatu da", "domain_unknown": "'{domain}' domeinua ezezaguna da", - "global_settings_cant_serialize_settings": "Ezin izan dira konfikurazio-datuak serializatu, zergatia: {reason}", - "global_settings_setting_security_nginx_redirect_to_https": "Birbidali HTTP eskaerak HTTPSra (EZ ITZALI hau ez badakizu zertan ari zaren!)", + "global_settings_cant_serialize_settings": "Ezinezkoa izan da konfikurazio-datuak serializatzea, zergatia: {reason}", + "global_settings_setting_security_nginx_redirect_to_https": "Birbideratu HTTP eskaerak HTTPSra (EZ ITZALI hau ez badakizu zertan ari zaren!)", "group_deleted": "'{group}' taldea ezabatu da", "invalid_password": "Pasahitza ez da zuzena", "log_domain_main_domain": "Lehenetsi '{}' domeinua", - "log_user_group_update": "Aldatu '{}' taldea", - "dyndns_could_not_check_available": "Ezin izan da egiaztatu {domain} eskuragarri dagoenik {provider}(e)n.", - "diagnosis_rootfstotalspace_critical": "Sistemaren root memoriak {space} baino ez ditu erabilgarri, eta hori kezkagarria da! Litekeena da oso laster memoriarik gabe geratzea! Root partizioak gutxienez 16GB erabilgarri izatea da gomendioa.", + "log_user_group_update": "Moldatu '{}' taldea", + "dyndns_could_not_check_available": "Ezinezkoa izan da {domain} {provider}(e)n eskuragarri dagoen egiaztatzea.", + "diagnosis_rootfstotalspace_critical": "'root' fitxategi-sistemak {space} baino ez ditu erabilgarri, eta hori kezkagarria da! Litekeena da oso laster memoriarik gabe geratzea! 'root' fitxategi-sistemak gutxienez 16GB erabilgarri izatea da gomendioa.", "disk_space_not_sufficient_install": "Ez dago aplikazio hau instalatzeko nahikoa espaziorik", - "domain_dns_conf_is_just_a_recommendation": "Komando honek *gomendatutako* konfigurazioa erakusten du. Ez du DNS konfigurazioa zugatik ezartzen. Zure ardura da DNS gunea zure erregistro-enpresaren gomendioen arabera ezartzea.", + "domain_dns_conf_is_just_a_recommendation": "Komando honek *iradokitako* konfigurazioa erakusten du. Ez du DNS konfigurazioa zugatik ezartzen. Zure ardura da DNS gunea zure erregistro-enpresaren gomendioen arabera ezartzea.", "dyndns_ip_update_failed": "Ezin izan da IP helbidea DynDNSan eguneratu", "dyndns_ip_updated": "IP helbidea DynDNS-n eguneratu da", "dyndns_key_not_found": "Ez da domeinurako DNS gakorik aurkitu", "dyndns_unavailable": "'{domain}' domeinua ez dago eskuragarri.", "log_app_makedefault": "Lehenetsi '{}' aplikazioa", - "log_does_exists": "Ez dago '{log}' izena duen eragiketa-erregistrorik, erabili 'yunohost log list' eragiketa-erregistro guztiak ikusteko", + "log_does_exists": "Ez dago '{log}' izena duen eragiketa-erregistrorik; erabili 'yunohost log list' eragiketa-erregistro guztiak ikusteko", "log_user_group_delete": "Ezabatu '{}' taldea", "log_user_import": "Inportatu erabiltzaileak", "dyndns_key_generating": "DNS gakoa sortzen… litekeena da honek denbora behar izatea.", "diagnosis_mail_fcrdns_ok": "Alderantzizko DNSa zuzen konfiguratuta dago!", "diagnosis_mail_queue_unavailable_details": "Errorea: {error}", - "dyndns_provider_unreachable": "Ezin izan da DynDNS {provider} enpresarekin konexioa gauzatu: agian zure YunoHost zerbitzaria ez dago internetera konektatuta edo dynette zerbitzaria ez dago martxan.", + "dyndns_provider_unreachable": "Ezinezkoa izan da DynDNS {provider} enpresarekin konektatzea: agian zure YunoHost zerbitzaria ez dago internetera konektatuta edo dynette zerbitzaria ez dago martxan.", "dyndns_registered": "DynDNS domeinua erregistratu da", - "dyndns_registration_failed": "Ezin izan da DynDNS domeinua erregistratu: {error}", + "dyndns_registration_failed": "Ezinezkoa izan da DynDNS domeinua erregistratzea: {error}", "extracting": "Ateratzen…", "diagnosis_ports_unreachable": "{port}. ataka ez dago eskuragarri kanpotik.", "diagnosis_regenconf_manually_modified_details": "Ez dago arazorik zertan ari zaren baldin badakizu! YunoHostek fitxategi hau automatikoki eguneratzeari utziko dio… Baina kontutan izan YunoHosten eguneraketek aldaketa garrantzitsuak izan ditzaketela. Nahi izatekotan, desberdintasunak aztertu ditzakezu yunohost tools regen-conf {category} --dry-run --with-diff komandoa exekutatuz, eta gomendatutako konfiguraziora bueltatu yunohost tools regen-conf {category} --force erabiliz", "experimental_feature": "Adi: Funtzio hau esperimentala eta ezegonkorra da, ez zenuke erabili beharko ez badakizu zertan ari zaren.", - "global_settings_cant_write_settings": "Ezin izan da konfigurazio fitxategia gorde, zergatia: {reason}", + "global_settings_cant_write_settings": "Ezinezkoa izan da konfigurazio fitxategia gordetzea, zergatia: {reason}", "dyndns_domain_not_provided": "{provider} DynDNS enpresak ezin du {domain} domeinua eskaini.", - "firewall_reload_failed": "Ezin izan da suebakia birkargatu", + "firewall_reload_failed": "Ezinezkoa izan da suebakia birkargatzea", "global_settings_setting_security_password_admin_strength": "Administrazio-pasahitzaren segurtasuna", - "hook_name_unknown": "'{name}' \"hook\" izen ezezaguna", - "domain_deletion_failed": "Ezin izan da {domain} ezabatu: {error}", + "hook_name_unknown": "'{name}' 'hook' izen ezezaguna", + "domain_deletion_failed": "Ezinezkoa izan da {domain} ezabatzea: {error}", "global_settings_setting_security_nginx_compatibility": "Bateragarritasun eta segurtasun arteko gatazka NGINX web zerbitzarirako. Zifraketari eragiten dio (eta segurtasunari lotutako beste kontu batzuei)", "log_regen_conf": "Berregin '{}' sistemaren konfigurazioa", - "dpkg_lock_not_available": "Ezin da komando hau une honetan exekutatu beste aplikazio bat dpkg (sistemaren paketeen kudeatzailea) blokeatuta duelako, erabiltzen ari baita", + "dpkg_lock_not_available": "Ezin da komando hau une honetan exekutatu beste aplikazio batek dpkg (sistemaren paketeen kudeatzailea) blokeatuta duelako, erabiltzen ari baita", "group_created": "'{group}' taldea sortu da", "global_settings_setting_security_password_user_strength": "Erabiltzaile-pasahitzaren segurtasuna", - "global_settings_setting_security_experimental_enabled": "Gaitu segurtasun funtzio esperimentlak (ez ezazu egin ez badakizu zertan ari zaren!)", - "good_practices_about_admin_password": "Administrazio-pasahitz berria ezartzear zaude. Pasahitzak zortzi karaktere izan beharko lituzke gutxienez, baina gomendagarria da pasahitz luzeagoa erabiltzea (esaldi bat, esaterako) edota karaktere desberdinak erabiltzea (larriak, txikiak, zenbakiak eta karaktere bereziak).", + "global_settings_setting_security_experimental_enabled": "Gaitu segurtasun funtzio esperimentalak (ez ezazu egin ez badakizu zertan ari zaren!)", + "good_practices_about_admin_password": "Administrazio-pasahitz berria ezartzear zaude. Pasahitzak zortzi karaktere izan beharko lituzke gutxienez, baina gomendagarria da pasahitz luzeagoa erabiltzea (esaldi bat, esaterako) edota karaktere desberdinak erabiltzea (hizki larriak, txikiak, zenbakiak eta karaktere bereziak).", "log_help_to_get_failed_log": "Ezin izan da '{desc}' eragiketa exekutatu. Mesedez, laguntza nahi baduzu partekatu eragiketa honen erregistro osoa 'yunohost log share {name}' komandoa erabiliz", "global_settings_setting_security_webadmin_allowlist_enabled": "Baimendu IP zehatz batzuk bakarrik administrazio-webgunean.", "group_unknown": "'{group}' taldea ezezaguna da", "group_updated": "'{group}' taldea eguneratu da", - "group_update_failed": "Ezin izan da '{group}' taldea eguneratu: {error}", - "diagnosis_rootfstotalspace_warning": "Sistemaren root partizioak {space} baino ez ditu. Agian ez da arazorik egongo, baina kontuz ibili edo memoriarik gabe gera zaitezke laster… Root partizioak gutxienez 16GB erabilgarri izatea da gomendioa.", - "iptables_unavailable": "Ezin dituzu iptaulak hemen aldatu. Edukiontzi bat erabiltzen ari zara edo kernelak ez du aukera hau onartzen", + "group_update_failed": "Ezinezkoa izan da '{group}' taldea eguneratzea: {error}", + "diagnosis_rootfstotalspace_warning": "'root' fitxategi-sistemak {space} baino ez ditu. Agian ez da arazorik egongo, baina kontuz ibili edo memoriarik gabe gera zaitezke laster… 'root' fitxategi-sistemak gutxienez 16GB erabilgarri izatea da gomendioa.", + "iptables_unavailable": "Ezin dituzu iptaulak hemen moldatu; edukiontzi bat erabiltzen ari zara edo kernelak ez du aukera hau onartzen", "log_permission_delete": "Ezabatu '{}' baimena", "group_already_exist": "{group} taldea existitzen da dagoeneko", "group_user_not_in_group": "{user} erabiltzailea ez dago {group} taldean", "diagnosis_mail_fcrdns_nok_alternatives_6": "Operadore batzuek ez dute alderantzizko DNSa konfiguratzen uzten (edo funtzioa ez dabil…). IPv4rako alderantzizko DNSa zuzen konfiguratuta badago, IPv6 desgaitzen saia zaitezke posta elektronikoa bidaltzeko, yunohost settings set smtp.allow_ipv6 -v off exekutatuz. Adi: honek esan nahi du ez zarela gai izango IPv6 bakarrik darabilten zerbitzari apurren posta elektronikoa jasotzeko edo beraiei bidaltzeko.", "diagnosis_sshd_config_inconsistent": "Dirudienez SSH ataka eskuz aldatu da /etc/ssh/sshd_config fitxategian. YunoHost 4.2tik aurrera 'security.ssh.port' izeneko ezarpen orokor bat dago konfigurazioa eskuz aldatzea ekiditeko.", "diagnosis_sshd_config_inconsistent_details": "Mesedez, exekutatu yunohost settings set security.ssh.port -v YOUR_SSH_PORT SSH ataka zehazteko, egiaztatu yunohost tools regen-conf ssh --dry-run --with-diff erabiliz eta yunohost tools regen-conf ssh --force exekutatu gomendatutako konfiguraziora bueltatu nahi baduzu.", - "domain_dns_push_failed_to_authenticate": "Ezin izan da '{domain}' domeinurako APIa erabiliz erregistro-enpresan saioa hastea. Zuzenak al dira datuak? (Errorea: {error})", + "domain_dns_push_failed_to_authenticate": "Ezinezkoa izan da '{domain}' domeinurako APIa erabiliz erregistro-enpresan saioa hastea. Zuzenak al dira datuak? (Errorea: {error})", "domain_dns_pushing": "DNS ezarpenak bidaltzen…", "diagnosis_sshd_config_insecure": "Badirudi SSH konfigurazioa eskuz aldatu dela eta ez da segurua ez duelako 'AllowGroups' edo 'AllowUsers' baldintzarik jartzen fitxategien atzitzea oztopatzeko.", "disk_space_not_sufficient_update": "Ez dago aplikazio hau eguneratzeko nahikoa espaziorik", "domain_cannot_add_xmpp_upload": "Ezin dira 'xmpp-upload.' hasiera duten domeinuak gehitu. Izen mota hau YunoHosten zati den XMPP igoeretarako erabiltzen da.", "domain_cannot_remove_main_add_new_one": "Ezin duzu '{domain}' ezabatu domeinu nagusi eta bakarra delako. Beste domeinu bat gehitu 'yunohost domain add ' exekutatuz, gero erabili 'yunohost domain main-domain -n ' domeinu nagusi bilakatzeko, eta azkenik ezabatu {domain}' domeinua 'yunohost domain remove {domain}' komandoarekin.", - "domain_dns_push_record_failed": "Ezin izan da {type}/{name} ezarpenak {action}: {error}", + "domain_dns_push_record_failed": "Ezinezkoa izan da {type}/{name} ezarpenak {action}: {error}", "domain_dns_push_success": "DNS ezarpenak eguneratu dira!", - "domain_dns_push_failed": "DNS ezarpenen eguneratzeak huts egin du.", - "domain_dns_push_partial_failure": "DNS ezarpenak hala-nola eguneratu dira: abisu/errore batzuk egon dira.", - "global_settings_setting_smtp_relay_host": "YunoHosten ordez posta elektronikoa bidaltzeko SMTP relay helbidea. Erabilgarri izan daiteke egoera hauetan: operadore edo VPS enpresak 25. ataka blokeatzen badute, DUHLn zure etxeko IPa ageri bada, ezin baduzu alderantzizko DNSa ezarri edo zerbitzari hau ez badago zuzenean internetera konektatuta baina posta elektronikoa bidali nahi baduzu.", - "group_deletion_failed": "Ezin izan da '{group}' taldea ezabatu: {error}", + "domain_dns_push_failed": "DNS ezarpenen eguneratzeak kale egin du.", + "domain_dns_push_partial_failure": "DNS ezarpenak hala-nola eguneratu dira: jakinarazpen/errore batzuk egon dira.", + "global_settings_setting_smtp_relay_host": "YunoHosten ordez posta elektronikoa bidaltzeko SMTP relay helbidea. Erabilgarri izan daiteke egoera hauetan: operadore edo VPS enpresak 25. ataka blokeatzen badu, DUHLen zure etxeko IPa ageri bada, ezin baduzu alderantzizko DNSa ezarri edo zerbitzari hau ez badago zuzenean internetera konektatuta baina posta elektronikoa bidali nahi baduzu.", + "group_deletion_failed": "Ezinezkoa izan da '{group}' taldea ezabatzea: {error}", "invalid_number_min": "{min} baino handiagoa izan behar da", "invalid_number_max": "{max} baino txikiagoa izan behar da", "diagnosis_services_bad_status": "{service} zerbitzua {status} dago :(", - "diagnosis_ports_needed_by": "{category} funtzioetarako ezinbestekoa da ataka hau eskuragarri egotea (zerbitzua {service})", + "diagnosis_ports_needed_by": "{category} funtzioetarako ezinbestekoa da ataka hau eskuragarri egotea ({service} zerbitzua)", "diagnosis_package_installed_from_sury": "Sistemaren pakete batzuen lehenagoko bertsioak beharko lirateke", "global_settings_setting_smtp_relay_password": "SMTP relay helbideko pasahitza", "global_settings_setting_smtp_relay_port": "SMTP relay ataka", @@ -332,14 +332,14 @@ "domain_registrar_is_not_configured": "Oraindik ez da {domain} domeinurako erregistro-enpresa ezarri.", "domain_dns_push_not_applicable": "Ezin da {domain} domeinurako DNS konfigurazio automatiko funtzioa erabili. DNS erregistroak eskuz ezarri beharko zenituzke gidaorriei erreparatuz: https://yunohost.org/dns_config.", "domain_dns_push_managed_in_parent_domain": "DNS ezarpenak automatikoki konfiguratzeko funtzioa {parent_domain} domeinu nagusian kudeatzen da.", - "domain_dns_registrar_managed_in_parent_domain": "Domeinu hau {parent_domain_link} (r)en azpidomeinua da. DNS ezarpenak {parent_domain}(r)en konfigurazio atalean kudeatu beharko lirateke.", + "domain_dns_registrar_managed_in_parent_domain": "Domeinu hau {parent_domain_link} (r)en azpidomeinua da. DNS ezarpenak {parent_domain}(r)en konfigurazio atalean kudeatu behar dira.", "domain_dns_registrar_yunohost": "Hau nohost.me / nohost.st / ynh.fr domeinu bat da eta, beraz, DNS ezarpenak automatikoki kudeatzen ditu YunoHostek, bestelako ezer konfiguratu beharrik gabe. (ikus 'yunohost dyndns update' komandoa)", - "domain_dns_registrar_not_supported": "YunoHostek ezin izan du automatikoki erregistro-enpresa antzeman domeinu honetarako. Eskuz konfiguratu beharko zenituzke DNS ezarpenak gidalerroei erreparatuz: https://yunohost.org/dns.", - "domain_dns_registrar_experimental": "Momentuz, YunoHosten kideek ez dute **{registrar}** erregistro-enpresaren APIa nahi beste probatu eta aztertu. Funtzioa **oso esperimentala** da - kontuz!", + "domain_dns_registrar_not_supported": "YunoHostek ezin izan du domeinu honen erregistro-enpresa automatikoki antzeman. Eskuz konfiguratu beharko dituzu DNS ezarpenak gidalerroei erreparatuz: https://yunohost.org/dns.", + "domain_dns_registrar_experimental": "Oraingoz, YunoHosten kideek ez dute **{registrar}** erregistro-enpresaren APIa nahi beste probatu eta aztertu. Funtzioa **oso esperimentala** da — kontuz!", "domain_config_mail_in": "Jasotako mezuak", - "domain_config_auth_token": "Token egiaztagiria", - "domain_config_auth_key": "Egiaztapen gakoa", - "domain_config_auth_secret": "Egiaztagiriaren \"secret\"a", + "domain_config_auth_token": "Token autentifikazioa", + "domain_config_auth_key": "Autentifikazio gakoa", + "domain_config_auth_secret": "Autentifikazioaren \"secret\"a", "domain_config_api_protocol": "API protokoloa", "domain_config_auth_entrypoint": "APIaren sarrera", "domain_config_auth_application_key": "Aplikazioaren gakoa", @@ -347,39 +347,39 @@ "domain_config_auth_consumer_key": "Erabiltzailearen gakoa", "global_settings_setting_smtp_allow_ipv6": "Baimendu IPv6 posta elektronikoa jaso eta bidaltzeko", "group_cannot_be_deleted": "{group} taldea ezin da eskuz ezabatu.", - "log_domain_config_set": "Aldatu '{}' domeinuko konfigurazioa", + "log_domain_config_set": "Aldatu '{}' domeinuko ezarpenak", "log_domain_dns_push": "Bidali '{}' domeinuaren DNS ezarpenak", "log_tools_migrations_migrate_forward": "Exekutatu migrazioak", "log_tools_postinstall": "Abiarazi YunoHost zerbitzariaren instalazio ondorengo prozesua", - "diagnosis_mail_fcrdns_nok_alternatives_4": "Operadore batzuek ez dute alderantzizko DNSa konfiguratzen uzten (edo funtzioa ez dabil…). Hau dela-eta arazoak badituzu, irtenbide batzuk eduki ditzakezu:
- Operadora batzuek relay posta zerbitzari bat eskaini dezakete, baina kasu horretan zure posta elektronikoa zelatatu dezakete.
- Pribatutasuna bermatzeko *IP publiko bat duen* VPN bat erabiltzea izan daiteke irtenbidea. Ikus https://yunohost.org/#/vpn_advantage
- Edo operadore desberdin batera aldatu", - "domain_dns_registrar_supported": "YunoHostek automatikoki antzeman du domeinu hau **{registrar}** erregistro-enpresak kudeatzen duela. Nahi baduzu YunoHostek automatikoki konfiguratu ditzake DNS ezarpenak, API egiaztagiri aproposak zehazten badituzu. API egiaztagiriak non lortzeko dokumentazioa orri honetan duzu: https://yunohost.org/registar_api_{registrar}. (Baduzu DNS erregistroak eskuz konfiguratzeko aukera ere, gidalerro hauetan ageri den bezala: https://yunohost.org/dns)", - "domain_dns_push_failed_to_list": "Ezin izan da APIa erabiliz oraingo erregistroak antzematea: {error}", + "diagnosis_mail_fcrdns_nok_alternatives_4": "Operadore batzuek ez dute alderantzizko DNSa konfiguratzen uzten (edo funtzioa ez dabil…). Hau dela-eta arazoak badituzu, irtenbide batzuk eduki ditzakezu:
- Operadore batzuek relay posta zerbitzari bat eskaini dezakete, baina kasu horretan zure posta elektronikoa zelatatu dezakete.
- Pribatutasuna bermatzeko *IP publiko bat duen* VPN bat erabiltzea izan daiteke irtenbidea. Ikus https://yunohost.org/#/vpn_advantage
- Edo operadore desberdin batera aldatu", + "domain_dns_registrar_supported": "YunoHostek automatikoki antzeman du domeinu hau **{registrar}** erregistro-enpresak kudeatzen duela. Nahi baduzu YunoHostek automatikoki konfiguratu ditzake DNS ezarpenak, API egiaztagiri zuzenak zehazten badituzu. API egiaztagiriak non lortzeko dokumentazioa orri honetan duzu: https://yunohost.org/registar_api_{registrar}. (Baduzu DNS erregistroak eskuz konfiguratzeko aukera ere, gidalerro hauetan ageri den bezala: https://yunohost.org/dns)", + "domain_dns_push_failed_to_list": "Ezinezkoa izan da APIa erabiliz oraingo erregistroak antzematea: {error}", "domain_dns_push_already_up_to_date": "Ezarpenak egunean daude, ez dago zereginik.", - "domain_config_features_disclaimer": "Oraingoz, posta elektronikoa edo XMPP funtzioak gaitu/desgaitzeak, DNS ezarpenei soilik eragiten die, ez sistemaren konfigurazioari!", + "domain_config_features_disclaimer": "Oraingoz, posta elektronikoa edo XMPP funtzioak gaitu/desgaitzeak DNS ezarpenei soilik eragiten die, ez sistemaren konfigurazioari!", "domain_config_mail_out": "Bidalitako mezuak", "domain_config_xmpp": "Bat-bateko mezularitza (XMPP)", "global_settings_bad_choice_for_enum": "{setting} ezarpenerako aukera okerra. '{choice}' ezarri da baina hauek dira aukerak: {available_choices}", "global_settings_setting_security_postfix_compatibility": "Bateragarritasun eta segurtasun arteko gatazka Postfix zerbitzarirako. Zifraketari eragiten dio (eta segurtasunari lotutako beste kontu batzuei)", "global_settings_setting_security_ssh_compatibility": "Bateragarritasun eta segurtasun arteko gatazka SSH zerbitzarirako. Zifraketari eragiten dio (eta segurtasunari lotutako beste kontu batzuei)", - "good_practices_about_user_password": "Erabiltzaile-pasahitz berria ezartzear zaude. Pasahitzak zortzi karaktere izan beharko lituzke gutxienez, baina gomendagarria da pasahitz luzeagoa erabiltzea (esaldi bat, esaterako) edota karaktere desberdinak erabiltzea (larriak, txikiak, zenbakiak eta karaktere bereziak).", - "group_cannot_edit_all_users": "'all_users' taldea ezin da eskuz aldatu. YunoHosten izena emanda dauden erabiltzaile guztiak barne dituen talde berezia da", + "good_practices_about_user_password": "Erabiltzaile-pasahitz berria ezartzear zaude. Pasahitzak zortzi karaktere izan beharko lituzke gutxienez, baina gomendagarria da pasahitz luzeagoa erabiltzea (esaldi bat, esaterako) edota karaktere desberdinak erabiltzea (hizki larriak, txikiak, zenbakiak eta karaktere bereziak).", + "group_cannot_edit_all_users": "'all_users' taldea ezin da eskuz moldatu. YunoHosten izena emanda dauden erabiltzaile guztiak barne dituen talde berezia da", "invalid_number": "Zenbaki bat izan behar da", "ldap_attribute_already_exists": "'{attribute}' LDAP funtzioa existitzen da dagoeneko eta '{value}' balioa dauka", "log_app_change_url": "'{}' aplikazioaren URLa aldatu", - "log_app_config_set": "Ezarri '{}' aplikazioako konfigurazioa", + "log_app_config_set": "Ezarri '{}' aplikazioko konfigurazioa", "downloading": "Deskargatzen…", - "dyndns_could_not_check_provide": "Ezin izan da egiaztatu {provider}(e)k {domain} eskaini dezakenik.", + "dyndns_could_not_check_provide": "Ezinezkoa izan da {provider}(e)k {domain} eskaini dezakeen egiaztatzea.", "log_available_on_yunopaste": "Erregistroa {url} estekan ikus daiteke", - "log_dyndns_update": "Eguneratu YunoHosten '{}' domeinuarekin lotutako IP helbidea", + "log_dyndns_update": "Eguneratu YunoHosten '{}' domeinuari lotutako IP helbidea", "log_letsencrypt_cert_install": "Instalatu Let's Encrypt ziurtagiria '{}' domeinurako", "log_selfsigned_cert_install": "Instalatu '{}' domeinurako norberak sinatutako ziurtagiria", "diagnosis_mail_ehlo_wrong": "Zurea ez den SMTP posta zerbitzari batek erantzun du IPv{ipversion}an. Litekeena da zure zerbitzariak posta elektronikorik jaso ezin izatea.", "log_tools_upgrade": "Eguneratu sistemaren paketeak", "log_tools_reboot": "Berrabiarazi zerbitzaria", - "diagnosis_mail_queue_unavailable": "Ezin da kontsultatu zenbat posta elektroniko dauden ilaran", + "diagnosis_mail_queue_unavailable": "Ezinezkoa da ilaran zenbat posta elektroniko dauden kontsultatzea", "log_user_create": "Gehitu '{}' erabiltzailea", - "group_cannot_edit_visitors": "'bisitariak' taldea ezin da eskuz aldatu. Saiorik hasi gabeko bisitariak barne hartzen dituen talde berezia da", - "diagnosis_ram_verylow": "Sistemak RAM memoriaren {available} ({available_percent}%) dauka bakarrik! ({total} orotara)", + "group_cannot_edit_visitors": "'bisitariak' taldea ezin da eskuz moldatu. Saiorik hasi gabeko bisitariak barne hartzen dituen talde berezia da", + "diagnosis_ram_verylow": "Sistemak RAM memoriaren {available} ({available_percent}%) dauka bakarrik erabilgarri! ({total} orotara)", "diagnosis_ram_low": "Sistemak {available} soilik du erabilgarri, RAM memoriaren ({available_percent}%) ({total} orotara). Kontuz ibili.", "diagnosis_ram_ok": "Sistemak oraindik dauka RAM memoriaren {available} ({available_percent}%) erabilgarri, {total} orotara.", "diagnosis_swap_none": "Sistemak ez du swap-ik. Gutxienez {recommended} gehitzen saiatu beharko zinateke, sistema memoriarik gabe gera ez dadin.", @@ -387,9 +387,9 @@ "diagnosis_regenconf_allgood": "Konfigurazio fitxategi guztiak bat datoz gomendatutako ezarpenekin!", "diagnosis_regenconf_manually_modified": "Dirudienez {file} konfigurazio fitxategia eskuz aldatu da.", "diagnosis_security_vulnerable_to_meltdown": "Badirudi Meltdown izeneko segurtasun arazo larriak eragin diezazukela", - "diagnosis_ports_could_not_diagnose": "Ezin izan da egiaztatu atakak IPv{ipversion} erabiliz kanpotik eskuragarri daudenik.", + "diagnosis_ports_could_not_diagnose": "Ezinezkoa izan da atakak IPv{ipversion} erabiliz kanpotik eskuragarri dauden egiaztatzea.", "diagnosis_ports_ok": "{port}. ataka eskuragarri dago kanpotik.", - "diagnosis_unknown_categories": "Honako kategoriak ez dira ezagutzen: {categories}", + "diagnosis_unknown_categories": "Honako atalak ez dira ezagutzen: {categories}", "diagnosis_services_running": "{service} zerbitzua martxan dago!", "log_app_action_run": "'{}' aplikazioaren eragiketa exekutatu", "diagnosis_never_ran_yet": "Badirudi zerbitzari hau duela gutxi konfiguratu dela eta oraindik ez dago erakusteko diagnostikorik. Diagnostiko osoa abiarazi beharko zenuke, administrazio-webgunetik edo 'yunohost diagnosis run' komandoa exekutatuz.", @@ -400,65 +400,65 @@ "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Oraingo alderantzizko DNSa: {rdns_domain}
Esperotako balioa: {ehlo_domain}", "diagnosis_mail_queue_too_big": "Mezu gehiegi posta elektronikoaren ilaran: ({nb_pending} mezu)", "diagnosis_ports_could_not_diagnose_details": "Errorea: {error}", - "diagnosis_swap_tip": "Mesedez, kontutan hartu zerbitzari honen swap memoria SD edo SSD euskarri batean gordetzen dela, euskarri horien bizi-iraupena izugarri laburtu dezakeela.", - "invalid_regex": "\"Regexa\" ez da baliogarria: '{regex}'", - "group_creation_failed": "Ezin izan da '{group}' taldea sortu: {error}", + "diagnosis_swap_tip": "Mesedez, kontutan hartu zerbitzari honen swap memoria SD edo SSD euskarri batean gordetzen bada, euskarri horren bizi-iraupena izugarri laburtu dezakeela.", + "invalid_regex": "'Regexa' ez da zuzena: '{regex}'", + "group_creation_failed": "Ezinezkoa izan da '{group}' taldea sortzea: {error}", "log_user_permission_reset": "Berrezarri '{}' baimena", - "group_cannot_edit_primary_group": "'{group}' taldea ezin da eskuz aldatu. Erabiltzaile zehatz bakar bat duen talde nagusia da.", + "group_cannot_edit_primary_group": "'{group}' taldea ezin da eskuz moldatu. Erabiltzaile zehatz bakar bat duen talde nagusia da.", "diagnosis_swap_notsomuch": "Sistemak {total} swap baino ez du. Gutxienez {recommended} gehitzen saiatu beharko zinateke sistema memoriarik gabe gera ez dadin.", - "diagnosis_security_vulnerable_to_meltdown_details": "Arazoa konpontzeko, sistema eguneratu eta berrabiarazi beharko zenuke linux-en kernel berriagoa erabiltzeko (edo zerbitzariaren arduradunarekin jarri harremanetan. Ikus https://meltdownattack.com/ informazio gehiagorako.", + "diagnosis_security_vulnerable_to_meltdown_details": "Arazoa konpontzeko, sistema eguneratu eta berrabiarazi beharko zenuke linux-en kernel berriagoa erabiltzeko (edo zerbitzariaren arduradunarekin jarri harremanetan). Ikus https://meltdownattack.com/ argibide gehiagorako.", "diagnosis_services_conf_broken": "{service} zerbitzuko konfigurazioa hondatuta dago!", "diagnosis_services_bad_status_tip": "Zerbitzua berrabiarazten saia zaitezke eta nahikoa ez bada, aztertu zerbitzuaren erregistroa administrariaren webgunean. (komandoak nahiago badituzu yunohost service restart {service} eta yunohost service log {service} hurrenez hurren).", - "diagnosis_mail_ehlo_unreachable_details": "Ezin izan da zure zerbitzariko 25. atakari konektatu IPv{ipversion} erabiliz. Badirudi ez dagoela eskuragarri.
1. Arazo honen zergati ohikoena 25. ataka egoki birbidalita ez egotea da.
2. Egiaztatu postfix zerbitzua martxan dagoela.
3. Konfigurazio konplexuagoetan: egiaztatu suebaki edo reverse-proxyak konexioa oztopatzen ez dutela.", + "diagnosis_mail_ehlo_unreachable_details": "Ezinezkoa izan da zure zerbitzariko 25. atakari konektatzea IPv{ipversion} erabiliz. Badirudi ez dagoela eskuragarri.
1. Arazo honen zergati ohikoena 25. ataka egoki birbideratuta ez egotea da.
2. Egiaztatu postfix zerbitzua martxan dagoela.
3. Konfigurazio konplexuagoetan: egiaztatu suebaki edo reverse-proxyak konexioa oztopatzen ez dutela.", "group_already_exist_on_system_but_removing_it": "{group} taldea existitzen da sistemaren taldeetan, baina YunoHostek ezabatuko du…", "diagnosis_mail_fcrdns_nok_details": "Lehenik eta behin zure routerraren konfigurazio gunean edo hostingaren enprearen aukeretan alderantzizko DNSa konfiguratzen saiatu beharko zinateke {ehlo_domain} erabiliz. (Hosting enpresaren arabera, ezinbestekoa da beraiekin harremanetan jartzea).", "diagnosis_mail_outgoing_port_25_ok": "SMTP posta zerbitzaria posta elektronikoa bidaltzeko gai da (25. atakaren irteera ez dago blokeatuta).", "diagnosis_ports_partially_unreachable": "{port}. ataka ez dago eskuragarri kanpotik Pv{failed} erabiliz.", - "diagnosis_ports_forwarding_tip": "Arazoa konpontzeko, litekeena da operadorearen routerrean ataken birbidalketa konfiguratu behar izatea, https://yunohost.org/isp_box_config-n agertzen den bezala", - "domain_creation_failed": "Ezin izan da {domain} domeinua sortu: {error}", + "diagnosis_ports_forwarding_tip": "Arazoa konpontzeko, litekeena da operadorearen routerrean ataken birbideraketa konfiguratu behar izatea, https://yunohost.org/isp_box_config-n agertzen den bezala", + "domain_creation_failed": "Ezinezkoa izan da {domain} domeinua sortzea: {error}", "domains_available": "Erabilgarri dauden domeinuak:", "global_settings_setting_pop3_enabled": "Gaitu POP3 protokoloa posta zerbitzarirako", "global_settings_setting_security_ssh_port": "SSH ataka", "global_settings_unknown_type": "Gertaera ezezaguna, {setting} ezarpenak {unknown_type} mota duela dirudi baina mota hori ez da sistemarekin bateragarria.", - "group_already_exist_on_system": "{group} taldea existitzen da sistemaren taldeetan dagoeneko", - "diagnosis_processes_killed_by_oom_reaper": "Memoria agortu eta sistemak prozesu batzuk amaituarazi behar izan ditu. Honek esan. nahi du sistemak ez duela memoria nahikoa edo prozesuren batek memoria gehiegi behar duela. Amaituarazi d(ir)en prozesua(k):\n{kills_summary}", + "group_already_exist_on_system": "{group} taldea existitzen da dagoeneko sistemaren taldeetan", + "diagnosis_processes_killed_by_oom_reaper": "Memoria agortu eta sistemak prozesu batzuk amaituarazi behar izan ditu. Honek esan nahi du sistemak ez duela memoria nahikoa edo prozesuren batek memoria gehiegi behar duela. Amaituarazi d(ir)en prozesua(k):\n{kills_summary}", "hook_exec_not_terminated": "Aginduak ez du behar bezala amaitu: {path}", - "log_corrupted_md_file": "Erregistroei lotutako YAML metadatu fitxategia hondatuta dago: '{md_file}\nErrorea: {error}'", + "log_corrupted_md_file": "Erregistroei lotutako YAML metadatu fitxategia kaltetuta dago: '{md_file}\nErrorea: {error}'", "log_letsencrypt_cert_renew": "Berriztu '{}' Let's Encrypt ziurtagiria", "log_remove_on_failed_restore": "Ezabatu '{}' babeskopia baten lehengoratzeak huts egin eta gero", "diagnosis_package_installed_from_sury_details": "Sury izena duen kanpoko biltegi batetik instalatu dira pakete batzuk, nahi gabe. YunoHosten taldeak hobekuntzak egin ditu pakete hauek kudeatzeko, baina litekeena da PHP7.3 aplikazioak Stretch sistema eragilean instalatu zituzten kasu batzuetan arazoak sortzea. Egoera hau konpontzeko, honako komando hau exekutatu beharko zenuke: {cmd_to_fix}", "log_help_to_get_log": "'{desc}' eragiketaren erregistroa ikusteko, exekutatu 'yunohost log show {name}'", - "dpkg_is_broken": "Ezin duzu une honetan agin dpkg/APT (sistemaren pakateen kudeatzaileak) hondatutako itxura dutelako… Arazoa konpontzeko SSH bidez konektatzen saia zaitezke eta ondoren exekutatu 'sudo apt install --fix-broken' edota 'sudo dpkg --configure -a'.", + "dpkg_is_broken": "Ezin duzu une honetan egin dpkg/APT (sistemaren pakateen kudeatzaileak) hondatutako itxura dutelako… Arazoa konpontzeko SSH bidez konektatzen saia zaitezke eta ondoren exekutatu 'sudo apt install --fix-broken' edota 'sudo dpkg --configure -a'.", "domain_cannot_remove_main": "Ezin duzu '{domain}' ezabatu domeinu nagusia delako. Beste domeinu bat ezarri beharko duzu nagusi bezala 'yunohost domain main-domain -n ' erabiliz; honako hauek dituzu aukeran: {other_domains}", "domain_created": "Sortu da domeinua", - "domain_dyndns_already_subscribed": "Dagoeneko izena eman duzu DunDNS domeinu batean", - "domain_hostname_failed": "Ezin izan da hostname berria ezarri. Honek arazoak ekar litzake etorkizunean (litekeena da ondo egotea).", + "domain_dyndns_already_subscribed": "Dagoeneko izena eman duzu DynDNS domeinu batean", + "domain_hostname_failed": "Ezinezkoa izan da hostname berria ezartzea. Honek arazoak ekar litzake etorkizunean (litekeena da ondo egotea).", "domain_uninstall_app_first": "Honako aplikazio hauek domeinuan instalatuta daude:\n{apps}\n\nMesedez, desinstalatu 'yunohost app remove the_app_id' ezekutatuz edo alda itzazu beste domeinu batera 'yunohost app change-url the_app_id' erabiliz domeinua ezabatu baino lehen", "file_does_not_exist": "{path} fitxategia ez da existitzen.", "firewall_rules_cmd_failed": "Suebakiko arau batzuen exekuzioak huts egin du. Informazio gehiago erregistroetan.", "log_app_remove": "Ezabatu '{}' aplikazioa", - "global_settings_cant_open_settings": "Ezin izan da konfigurazio fitxategia ireki, zergatia: {reason}", + "global_settings_cant_open_settings": "Ezinezkoa izan da konfigurazio fitxategia irekitzea, zergatia: {reason}", "global_settings_reset_success": "Lehengo ezarpenak {path}-n gorde dira", "global_settings_unknown_setting_from_settings_file": "Gako ezezaguna ezarpenetan: '{setting_key}', baztertu eta gorde ezazu hemen: /etc/yunohost/settings-unknown.json", "domain_remove_confirm_apps_removal": "Domeinu hau ezabatzean aplikazio hauek desinstalatuko dira:\n{apps}\n\nZiur al zaude? [{answers}]", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Baimendu DSA gakoa (zaharkitua) SSH zerbitzuaren konfiguraziorako", - "hook_list_by_invalid": "Aukera hau ezin da erabili \"hook\"ak zerrendatzeko", + "hook_list_by_invalid": "Aukera hau ezin da 'hook'ak zerrendatzeko erabili", "installation_complete": "Instalazioa amaitu da", - "hook_exec_failed": "Ezin izan da agindua exekutatu: {path}", + "hook_exec_failed": "Ezinezkoa izan da agindua exekutatzea: {path}", "hook_json_return_error": "Ezin izan da {path} aginduaren erantzuna irakurri. Errorea: {msg}. Jatorrizko edukia: {raw_content}", - "ip6tables_unavailable": "Ezin dituzu ip6taulak hemen aldatu. Edukiontzi bat erabiltzen ari zara edo kernelak ez du aukera hau onartzen", + "ip6tables_unavailable": "Ezin dituzu ip6taulak hemen moldatu; edukiontzi bat erabiltzen ari zara edo kernelak ez du aukera hau onartzen", "log_link_to_log": "Eragiketa honen erregistro osoa: '{desc}'", "log_operation_unit_unclosed_properly": "Eragiketa ez da modu egokian itxi", "log_backup_restore_app": "Lehengoratu '{}' babeskopia fitxategi bat erabiliz", "log_remove_on_failed_install": "Ezabatu '{}' instalazioak huts egin ondoren", "log_domain_add": "Gehitu '{}' domeinua sistemaren konfiguraziora", "log_dyndns_subscribe": "Eman izena YunoHosten '{}' azpidomeinuan", - "diagnosis_no_cache": "Oraindik ez dago '{category}' kategoriarako diagnostikoaren cacherik", + "diagnosis_no_cache": "Oraindik ez dago '{category}' atalerako diagnostikoaren cacherik", "diagnosis_mail_queue_ok": "Posta elektronikoaren ilaran zain dauden mezuak: {nb_pending}", "global_settings_setting_smtp_relay_user": "SMTP relay erabiltzailea", "domain_cert_gen_failed": "Ezinezkoa izan da ziurtagiria sortzea", "field_invalid": "'{}' ez da baliogarria", - "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Operadore batzuei bost axola zaie internetaren neutraltasuna (Net Neutrality) eta ez dute 25. ataka desblokeatzen uzten.
- Operadora batzuek relay posta zerbitzari bat eskaini dezakete, baina kasu horretan zure posta elektronikoa zelatatu dezakete.
- Pribatutasuna bermatzeko *IP publiko bat duen* VPN bat erabiltzea izan daiteke irtenbidea. Ikus https://yunohost.org/#/vpn_advantage
- Edo operadore desberdin batera aldatu", + "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Operadore batzuei bost axola zaie internetaren neutraltasuna (Net Neutrality) eta ez dute 25. ataka desblokeatzen uzten.
- Operadore batzuek relay posta zerbitzari bat eskaini dezakete, baina kasu horretan zure posta elektronikoa zelatatu dezakete.
- Pribatutasuna bermatzeko *IP publiko bat duen* VPN bat erabiltzea izan daiteke irtenbidea. Ikus https://yunohost.org/#/vpn_advantage
- Edo operadore desberdin batera aldatu", "ldap_server_down": "Ezin izan da LDAP zerbitzarira konektatu", "ldap_server_is_down_restart_it": "LDAP zerbitzaria ez dago martxan, saia zaitez berrabiarazten…", "log_app_upgrade": "'{}' aplikazioa eguneratu", @@ -470,15 +470,15 @@ "diagnosis_mail_fcrdns_dns_missing": "Ez da alderantzizko DNSrik ezarri IPv{ipversion}rako. Litekeena da posta elektroniko batzuk hartzaileak jaso ezin izatea edo spam modura etiketatuak izatea.", "log_backup_create": "Sortu babeskopia fitxategia", "global_settings_setting_backup_compress_tar_archives": "Babeskopia berriak sortzean, konprimitu fitxategiak (.tar.gz) konprimitu gabeko fitxategien (.tar) ordez. Aukera hau gaitzean babeskopiek espazio gutxiago beharko dute, baina hasierako prozesua luzeagoa izango da eta CPUari lan handiagoa eragingo dio.", - "global_settings_setting_security_webadmin_allowlist": "Administrazio-webgunea bisita ditzaketen IP helbideak. Koma bidez bereiziak.", - "global_settings_key_doesnt_exists": "'{settings_key}' gakoa ez da existitzen konfigurazio orokorrean, erabilgarri dauden gakoak ikus ditzakezu 'yunohost settings list' exekutatuz", + "global_settings_setting_security_webadmin_allowlist": "Administrazio-webgunea bisita ditzaketen IP helbideak, koma bidez bereiziak.", + "global_settings_key_doesnt_exists": "'{settings_key}' gakoa ez da existitzen konfigurazio orokorrean; erabilgarri dauden gakoak ikus ditzakezu 'yunohost settings list' exekutatuz", "global_settings_setting_ssowat_panel_overlay_enabled": "Gaitu SSOwat paneleko \"overlay\"a", "log_backup_restore_system": "Lehengoratu sistema babeskopia fitxategi batetik", - "log_domain_remove": "Ezabatu '{}' domeinua sistemaren konfiguraziotik", - "log_link_to_failed_log": "Ezin izan da '{desc}' eragiketa exekutatu. Mesedez, laguntza nahi izanez gero, elkarbanatu erakigeta honen erregistro osoa hemen sakatuz", + "log_domain_remove": "Ezabatu '{}' domeinua sistemaren ezarpenetatik", + "log_link_to_failed_log": "Ezinezkoa izan da '{desc}' eragiketa exekutatzea. Mesedez, laguntza nahi izanez gero, partekatu erakigeta honen erregistro osoa hemen sakatuz", "log_permission_url": "Eguneratu '{}' baimenari lotutako URLa", "log_user_group_create": "Sortu '{}' taldea", - "permission_creation_failed": "Ezin izan da '{permission}' baimena sortu: {error}", + "permission_creation_failed": "Ezinezkoa izan da '{permission}' baimena sortzea: {error}", "permission_not_found": "Ez da '{permission}' baimena aurkitu", "pattern_lastname": "Abizen horrek ez du balio", "permission_deleted": "'{permission}' baimena ezabatu da", @@ -487,10 +487,10 @@ "tools_upgrade_special_packages_completed": "YunoHosten paketeak eguneratu dira.\nSakatu [Enter] komando-lerrora bueltatzeko", "unexpected_error": "Ezusteko zerbaitek huts egin du: {error}", "updating_apt_cache": "Sistemaren paketeen eguneraketak eskuratzen…", - "mail_forward_remove_failed": "Ezin izan da '{mail}' posta elektronikoko birbidalketa ezabatu", + "mail_forward_remove_failed": "Ezinezkoa izan da '{mail}' posta elektronikoko birbidalketa ezabatzea", "migration_description_0020_ssh_sftp_permissions": "Gehitu SSH eta SFTP baimenak", - "migration_ldap_migration_failed_trying_to_rollback": "Ezin izan da migratu… sistema lehengoratzen saiatzen.", - "migrations_exclusive_options": "'--auto', '--skip', and '--force-rerun' aukerek batak bestea baztertzen du.", + "migration_ldap_migration_failed_trying_to_rollback": "Ezinezkoa izan da migratzea… sistema lehengoratzen saiatzen.", + "migrations_exclusive_options": "'--auto', '--skip', eta '--force-rerun' aukerek batak bestea baztertzen du.", "migrations_running_forward": "{id} migrazioa exekutatzen…", "regenconf_dry_pending_applying": "'{category}' atalari dagokion konfigurazioa egiaztatzen…", "regenconf_file_backed_up": "'{conf} konfigurazio fitxategia '{backup}' babeskopian kopiatu da", @@ -498,18 +498,18 @@ "regenconf_file_updated": "'{conf}' konfigurazio fitxategia eguneratu da", "regenconf_updated": "'{category}' atalerako ezarpenak eguneratu dira", "service_started": "'{service}' zerbitzua abiarazi da", - "show_tile_cant_be_enabled_for_regex": "Ezin duzu 'show_tile' gaitu une honetan, '{permission}' baimenerako URLa regez delako", + "show_tile_cant_be_enabled_for_regex": "Ezin duzu 'show_tile' gaitu une honetan, '{permission}' baimenerako URLa regex delako", "unknown_main_domain_path": "{app} aplikaziorako domeinu edo bide ezezaguna. Domeinua eta bidea zehaztu behar dituzu baimena emateko URLa ahalbidetzeko.", "user_import_partial_failed": "Erabiltzaileak inportatzeko eragiketak erdizka huts egin du", "user_import_success": "Erabiltzaileak arazorik gabe inportatu dira", "yunohost_already_installed": "YunoHost instalatuta dago dagoeneko", "migration_0015_not_stretch": "Debianen oraingo bertsioa ez da Stretch!", "migrations_success_forward": "{id} migrazioak amaitu du", - "migrations_to_be_ran_manually": "{id} migrazioa eskuz abiarazi behar da. Mesedez, joan Erramintak → Migrazioak atalera administratzeko webgunean edo bestela exekutatu 'yunohost tools migrations run'.", - "permission_currently_allowed_for_all_users": "Baimen hau erabiltzaile guztiei esleitzen zaie eta baita beste talde batzuei ere. Litekeena da 'all users' baimena edo eslaituta duten taldeei baimena kendu nahi izatea.", - "permission_require_account": "'{permission}' baimenak zentzua du zerbitzarian kontua duten erabiltzaileentzat eta beraz ezin da gaitu bisitarientzat.", - "postinstall_low_rootfsspace": "root direktorioak 10 GB edo espazio gutxiago dauka, kezkatzekoa dena! Litekeena da espaziorik gabe geratzea aurki! Gomendagarria da root direktorioan gutxienez 16 GB libre izatea. Abisu honen ondoren YunoHost instalatzen jarraitu nahi baduzu, berrabiarazi agindua '--force-diskspace' gehituz", - "this_action_broke_dpkg": "Eragiketa honek dpkg/APT (sistemaren pakete kudeatzaileak) apurtu ditu… Arazoa konpontzeko SSH bidez konektatu eta 'sudo apt install --fix-broken' edota 'sudo dpkg --configure -a' exekutatu dezakezu.", + "migrations_to_be_ran_manually": "{id} migrazioa eskuz abiarazi behar da. Mesedez, joan Erramintak → Migrazioak atalera administrazio-webgunean edo bestela exekutatu 'yunohost tools migrations run'.", + "permission_currently_allowed_for_all_users": "Baimen hau erabiltzaile guztiei esleitzen zaio eta baita beste talde batzuei ere. Litekeena da 'all users' baimena edo esleituta duten taldeei baimena kendu nahi izatea.", + "permission_require_account": "'{permission}' baimena zerbitzarian kontua duten erabiltzaileentzat da eta, beraz, ezin da gaitu bisitarientzat.", + "postinstall_low_rootfsspace": "'root' fitxategi-sistemak 10 GB edo espazio gutxiago dauka, kezkatzekoa dena! Litekeena da espaziorik gabe geratzea aurki! Gomendagarria da 'root' fitxategi-sistemak gutxienez 16 GB libre izatea. Jakinarazpen honen ondoren YunoHost instalatzen jarraitu nahi baduzu, berrabiarazi agindua '--force-diskspace' gehituz", + "this_action_broke_dpkg": "Eragiketa honek dpkg/APT (sistemaren pakete kudeatzaileak) kaltetu ditu… Arazoa konpontzeko SSH bidez konektatu eta 'sudo apt install --fix-broken' edota 'sudo dpkg --configure -a' exekutatu dezakezu.", "tools_upgrade_special_packages_explanation": "Eguneraketa bereziak atzeko planoan jarraituko du. Mesedez, ez abiarazi bestelako eragiketarik datozen ~10 minutuetan (zure hardwarearen abiaduraren arabera). Honen ondoren litekeena da saioa berriro hasi behar izatea. Eguneraketaren erregistroa Erramintak → Erregistroak (administrazio-webgunean) edo 'yunohost log list' komandoa erabiliz egongo da ikusgai.", "user_import_bad_line": "{line} lerro okerra: {details}", "restore_complete": "Lehengoratzea amaitu da", @@ -534,7 +534,7 @@ "update_apt_cache_warning": "Zerbaitek huts egin du APT Debian-en pakete kudeatzailearen cachea eguneratzean. Hemen dituzu sources.list fitxategiaren lerroak, arazoa identifikatzeko baliagarria izan dezakezuna:\n{sourceslist}", "user_created": "Erabiltzailea sortu da", "user_deletion_failed": "Ezin izan da '{user}' ezabatu: {error}", - "permission_updated": "'{permission}' baimena aldatu da", + "permission_updated": "'{permission}' baimena moldatu da", "ssowat_conf_generated": "SSOwat ezarpenak berregin dira", "system_upgraded": "Sistema eguneratu da", "upnp_port_open_failed": "Ezin izan da UPnP bidez ataka zabaldu", @@ -543,13 +543,13 @@ "main_domain_changed": "Domeinu nagusia aldatu da", "migrations_already_ran": "Honako migrazio hauek amaitu dute dagoeneko: {ids}", "yunohost_installing": "YunoHost instalatzen…", - "migrations_failed_to_load_migration": "Ezin izan da {id} migrazioa kargatu: {error}", + "migrations_failed_to_load_migration": "Ezinezkoa izan da {id} migrazioa kargatzea: {error}", "migrations_must_provide_explicit_targets": "'--skip' edo '--force-rerun' aukerak erabiltzean jomuga zehatzak zehaztu behar dituzu", "migrations_pending_cant_rerun": "Migrazio hauek exekutatzeke daude eta, beraz, ezin dira berriro abiarazi: {ids}", "regenconf_file_kept_back": "'{conf}' konfigurazio fitxategia regen-conf-ek ({category} atala) ezabatzekoa zen baina mantendu egin da.", "regenconf_file_removed": "'{conf}' konfigurazio fitxategia ezabatu da", "tools_upgrade_cant_hold_critical_packages": "Ezin izan dira pakete kritikoak mantendu…", - "migrations_cant_reach_migration_file": "Ezin izan da '%s' migrazioen fitxategia eskuratu", + "migrations_cant_reach_migration_file": "Ezinezkoa izan da '%s' migrazioen fitxategia eskuratzea", "permission_already_allowed": "'{group} taldeak badauka dagoeneko '{permission}' baimena", "permission_cant_add_to_all_users": "{permission} baimena ezin da erabiltzaile guztiei ezarri.", "mailbox_disabled": "Posta elektronikoa desgaituta dago {user} erabiltzailearentzat", @@ -560,66 +560,66 @@ "mailbox_used_space_dovecot_down": "Dovecot mailbox zerbitzua martxan egon behar da postak erabilitako espazioa ezagutzeko", "migration_0015_cleaning_up": "Beharrezkoak ez diren cache eta paketeak kentzen…", "migration_0015_modified_files": "Mesedez, kontutan hartu ondorengo fitxategiak eskuz aldatu direla eta sistema eguneratzean euren gainean idatziko dela: {manually_modified_files}", - "migration_0015_not_enough_free_space": "/var/ direktorioan oso espazio gutxi geratzen da! Gutxienez GB bat izan behar da migrazioa abiarazteko.", + "migration_0015_not_enough_free_space": "/var/ fitxategi-sisteman oso espazio gutxi geratzen da! Gutxienez GB bat izan behar da migrazioa abiarazteko.", "migration_0015_weak_certs": "Sinadura-algoritmo ahulak darabiltzaten ziurtagiriak aurkitu dira eta eguneratu behar dira nginx-en hurrengo bertsioarekin bateragarriak izateko: {certs}", "migration_description_0017_postgresql_9p6_to_11": "Migratu datubaseak PostgreSQL 9.6-tik 11-ra", "other_available_options": "… eta erakusten ez diren beste {n} aukera daude", "permission_cannot_remove_main": "Ezin da baimen nagusi bat kendu", - "service_not_reloading_because_conf_broken": "Ez da '{name}' zerbitzua birkargatu/berrabiarazi konfigurazioa hondatuta dagoelako: {errors}", + "service_not_reloading_because_conf_broken": "Ez da '{name}' zerbitzua birkargatu/berrabiarazi konfigurazioa kaltetuta dagoelako: {errors}", "service_reloaded": "'{service}' zerbitzua birkargatu da", "service_reloaded_or_restarted": "'{service}' zerbitzua birkargatu edo berrabiarazi da", "user_import_bad_file": "CSV fitxategiak ez du formatu egokia eta ekidingo da balizko datuen galera saihesteko", "user_import_failed": "Erabiltzaileak inportatzeko eragiketak huts egin du", "user_import_missing_columns": "Ondorengo zutabeak falta dira: {columns}", - "service_disable_failed": "Ezin izan da '{service}' zerbitzua geldiarazi zerbitzaria abiaraztean.\n\nZerbitzuen erregistro berriak: {logs}", + "service_disable_failed": "Ezin izan da '{service}' zerbitzua geldiarazi zerbitzaria abiaraztean.\n\nZerbitzuen erregistro berrienak: {logs}", "migrations_skip_migration": "{id} migrazioa saihesten…", - "packages_upgrade_failed": "Ezin izan dira pakete guztiak eguneratu", + "packages_upgrade_failed": "Ezinezkoa izan da pakete guztiak eguneratzea", "upnp_disabled": "UPnP itzalita dago", - "main_domain_change_failed": "Ezin izan da domeinu nagusia aldatu", - "regenconf_failed": "Ezin izan da ondorengo atal(ar)en konfigurazioa berregin: {categories}", + "main_domain_change_failed": "Ezinezkoa izan da domeinu nagusia aldatzea", + "regenconf_failed": "Ezinezkoa izan da ondorengo atal(ar)en konfigurazioa berregitea: {categories}", "migration_0015_start": "Buster-erako migrazioa abiarazten", "migration_0015_patching_sources_list": "sources.lists petatxatzen…", "migration_0015_yunohost_upgrade": "YunoHosten muinaren eguneraketa abiarazten…", "migration_0017_postgresql_96_not_installed": "Ez da PostgreSQL instalatu. Ez dago egitekorik.", - "pattern_email_forward": "Helbide elektroniko baliagarri bat izan behar da, '+' karakterea onartzen da (adibidez izena+urtea@domeinua.eus)", + "pattern_email_forward": "Helbide elektroniko baliagarri bat izan behar da, '+' karakterea onartzen da (adibidez: izena+urtea@domeinua.eus)", "migrating_legacy_permission_settings": "Zaharkitutako baimenen ezarpenak migratzen…", "migration_0019_add_new_attributes_in_ldap": "Gehitu LDAP datubasean baimenetarako atributu berriak", "regenconf_file_manually_removed": "'{conf}' konfigurazio fitxategia eskuz ezabatu da eta ez da berriro sortuko", "regenconf_up_to_date": "Konfigurazioa egunean dago dagoeneko '{category}' atalerako", "service_regen_conf_is_deprecated": "'yunohost service regen-conf' zaharkitua dago! Mesedez, erabili 'yunohost tools regen-conf' haren ordez.", "migrations_no_such_migration": "Ez dago '{id}' izeneko migraziorik", - "migrations_not_pending_cant_skip": "Migrazio hauek ez daude exekutatzeke eta beraz, ezin dira saihestu: {ids}", - "regex_with_only_domain": "Ezin duzu regex domeinuetarako erabili, bideetarako bakarrik", + "migrations_not_pending_cant_skip": "Migrazio hauek ez daude exekutatzeke eta, beraz, ezin dira saihestu: {ids}", + "regex_with_only_domain": "Ezin duzu regex domeinuetarako erabili; bideetarako bakarrik", "port_already_closed": "{port}. ataka itxita dago dagoeneko {ip_version} konexioetarako", - "regenconf_file_copy_failed": "Ezin izan da '{new}' konfigurazio fitxategi berria '{conf}'-(e)n kopiatu", - "regenconf_file_remove_failed": "Ezin izan da '{conf}' konfigurazio fitxategia ezabatu", + "regenconf_file_copy_failed": "Ezinezkoa izan da '{new}' konfigurazio fitxategi berria '{conf}'-(e)n kopiatzea", + "regenconf_file_remove_failed": "Ezinezkoa izan da '{conf}' konfigurazio fitxategia ezabatzea", "server_shutdown_confirm": "Zerbitzaria berehala itzaliko da, ziur al zaude? [{answers}]", "restore_already_installed_app": "'{app}' IDa duen aplikazioa dagoeneko instalatuta dago", "service_description_postfix": "Posta elektronikoa bidali eta jasotzeko erabiltzen da", - "service_enable_failed": "Ezin izan da '{service}' zerbitzua sistema abiaraztearekin batera exekutatzea lortu.\n\nZerbitzuen erregistro berriak: {logs}", + "service_enable_failed": "Ezin izan da '{service}' zerbitzua sistema abiaraztearekin batera exekutatzea lortu.\n\nZerbitzuen erregistro berrienak: {logs}", "system_username_exists": "Erabiltzaile izena existitzen da dagoeneko sistemaren erabiltzaileen zerrendan", "user_already_exists": "'{user}' erabiltzailea existitzen da dagoeneko", "migration_0018_failed_to_migrate_iptables_rules": "Zaharkitutako iptaulak nftauletara eguneratzeak huts egin du: {error}", - "mail_domain_unknown": "Ezin da posta elektroniko hori erabili '{domain}' domeinurako. Mesedez, erabili zerbitzari honek kudeatzen duen domeinu bat.", + "mail_domain_unknown": "Ezinezkoa da posta elektroniko hori '{domain}' domeinurako erabiltzea. Mesedez, erabili zerbitzari honek kudeatzen duen domeinu bat.", "migrations_list_conflict_pending_done": "Ezin dituzu '--previous' eta '--done' aldi berean erabili.", "migrations_loading_migration": "{id} migrazioa kargatzen…", "migrations_no_migrations_to_run": "Ez dago exekutatzeko migraziorik", - "password_listed": "Pasahitz hau munduan erabilienetarikoa da. Mesedez, aukeratu beste bat.", - "password_too_simple_2": "Pasahitzak zortzi karaktere izan behar ditu gutxienez eta zenbakiren bat, karaktere larriren bat eta txikiren bat izan behar ditu", + "password_listed": "Pasahitz hau munduan erabilienetarikoa da. Mesedez, aukeratu bereziagoa den beste bat.", + "password_too_simple_2": "Pasahitzak zortzi karaktere izan behar ditu gutxienez eta zenbakiren bat, hizki larriren bat eta txikiren bat izan behar ditu", "pattern_firstname": "Izen horrek ez du balio", "pattern_password": "Gutxienez hiru karaktere izan behar ditu", "restore_failed": "Ezin izan da sistema lehengoratu", - "restore_removing_tmp_dir_failed": "Ezin izan da behin-behineko direktorio zaharra ezabatu", + "restore_removing_tmp_dir_failed": "Ezinezkoa izan da behin-behineko direktorio zaharra ezabatzea", "restore_running_app_script": "'{app}' aplikazioa lehengoratzen…", - "root_password_replaced_by_admin_password": "Administrazio pasahitzak root pasahitza ordezkatu du.", + "root_password_replaced_by_admin_password": "Administrazio-pasahitzak root pasahitza ordezkatu du.", "service_description_fail2ban": "Internetetik datozen bortxaz egindako saiakerak eta bestelako erasoak ekiditen ditu", "service_description_ssh": "Zerbitzarira sare lokaletik kanpo konektatzea ahalbidetzen du (SSH protokoloa)", - "service_description_yunohost-firewall": "Zerbitzuen konexiorako atakak ireki eta ixteko kudeatzailea", + "service_description_yunohost-firewall": "Zerbitzuen konexiorako atakak ireki eta ixteko kudeatzailea da", "service_remove_failed": "Ezin izan da '{service}' zerbitzua ezabatu", - "service_reload_failed": "Ezin izan da '{service}' zerbitzua birkargatu\n\nZerbitzuen azken erregistroak: {logs}", - "service_reload_or_restart_failed": "Ezin izan da '{service}' zerbitzua birkargatu edo berrabiarazi\n\nZerbitzuen azken erregistroak: {logs}", + "service_reload_failed": "Ezin izan da '{service}' zerbitzua birkargatu\n\nZerbitzuen erregistro berrienak: {logs}", + "service_reload_or_restart_failed": "Ezin izan da '{service}' zerbitzua birkargatu edo berrabiarazi\n\nZerbitzuen erregistro berrienak: {logs}", "service_stopped": "'{service}' zerbitzua geldiarazi da", - "unbackup_app": "{app} aplikazioa ez da gordeko", + "unbackup_app": "{app} ez da gordeko", "unrestore_app": "{app} ez da lehengoratuko", "upgrade_complete": "Eguneraketa amaitu da", "upgrading_packages": "Paketeak eguneratzen…", @@ -630,7 +630,7 @@ "migration_0015_system_not_fully_up_to_date": "Sistema ez dago guztiz eguneratuta. Mesedez, exekutatu eguneraketa orokorra Buster-erako migrazioa abiarazi baino lehen.", "migration_description_0015_migrate_to_buster": "Eguneratu sistema Debian Buster eta YunoHost 4.x-ra", "service_description_yunomdns": "Sare lokalean zerbitzarira 'yunohost.local' erabiliz konektatzea ahalbidetzen du", - "mail_alias_remove_failed": "Ezin izan da '{mail}' posta elektroniko ezizena ezabatu", + "mail_alias_remove_failed": "Ezin izan da '{mail}' e-mail ezizena ezabatu", "mail_unavailable": "Helbide elektroniko hau lehenengo erabiltzailearentzat gorde da eta hari ezarri zaio automatikoki", "migration_description_0016_php70_to_php73_pools": "Migratu php7.0-fpm 'pool' fitxategiak php7.3-ra", "migration_description_0019_extend_permissions_features": "Hedatu aplikazioen baimenen kudeaketa sistema", @@ -640,22 +640,22 @@ "migration_0015_problematic_apps_warning": "Mesedez, kontutan izan arazoak sor ditzaketen aplikazioak aurkitu direla. Badirudi ez zirela YunoHosten aplikazioen katalogotik instalatu, edo 'ez dabiltza' etiketa dute. Beraz, ezin da bermatu eguneratu eta gero funtzionatuko dutenik: {problematic_apps}", "migration_0017_not_enough_space": "Espazio gehiago behar da {path}-n migrazioa exekutatzeko.", "migration_0017_postgresql_11_not_installed": "PostgreSQL 9.6 instalatuta egon arren postgresql 11 ez‽ Zerbait arraroa gertatu da zure sisteman :(…", - "migration_0018_failed_to_reset_legacy_rules": "Ezin izan dira zaharkitutako iptaulak berrezarri: {error}", + "migration_0018_failed_to_reset_legacy_rules": "Ezinezkoa izan da zaharkitutako iptaulak berrezartzea: {error}", "migrations_migration_has_failed": "{id} migrazioak ez du amaitu, geldiarazten. Errorea: {exception}", "migrations_need_to_accept_disclaimer": "{id} migrazioa abiarazteko, ondorengo baldintzak onartu behar dituzu:\n---\n{disclaimer}\n---\nMigrazioa onartzen baduzu, mesedez berrabiarazi prozesua komandoan '--accept-disclaimer' aukera gehituz.", "not_enough_disk_space": "Ez dago nahikoa espazio librerik '{path}'-n", - "password_too_simple_3": "Pasahitzak zortzi karaktere izan behar ditu gutxienez eta zenbakiren bat, karaktere larriren bat, txikiren bat eta bereziren bat izan behar ditu", + "password_too_simple_3": "Pasahitzak zortzi karaktere izan behar ditu gutxienez eta zenbakiren bat, hizki larriren bat, txikiren bat eta karaktere bereziren bat izan behar ditu", "pattern_backup_archive_name": "Fitxategiaren izenak 30 karaktere izan ditzake gehienez, alfanumerikoak eta ._- baino ez", - "pattern_domain": "Domeinu izen baliagarri bat izan behar da (adibidez nire-domeinua.eus)", + "pattern_domain": "Domeinu izen baliagarri bat izan behar da (adibidez: nire-domeinua.eus)", "pattern_mailbox_quota": "Tamainak b/k/M/G/T zehaztu behar du edo 0 mugarik ezarri nahi ez bada", "pattern_password_app": "Barka, baina pasahitzek ezin dituzte ondorengo karaktereak izan: {forbidden_chars}", "pattern_port_or_range": "Ataka zenbaki (0-65535) edo errenkada (100:200) baliagarri bat izan behar da", "permission_already_disallowed": "'{group}' taldeak desgaituta dauka dagoeneko '{permission} baimena", "permission_already_up_to_date": "Baimena ez da eguneratu egindako eskaria egungo egoerarekin bat datorrelako.", - "migration_0015_still_on_stretch_after_main_upgrade": "Zerbaitek huts egin du eguneraketa orokorrean, badirudi sistemak oraindik darabilela Debian Stretch", + "migration_0015_still_on_stretch_after_main_upgrade": "Zerbaitek huts egin du eguneraketa orokorrean; badirudi sistemak oraindik darabilela Debian Stretch", "migration_update_LDAP_schema": "LDAP eskema eguneratzen…", "permission_protected": "'{permission}' baimena babestuta dago. Ezin duzu bisitarien taldea baimen honetara gehitu / baimen honetatik kendu.", - "permission_update_failed": "Ezin izan da '{permission}' baimena aldatu: {error}", + "permission_update_failed": "Ezinezkoa izan da '{permission}' baimena aldatzea: {error}", "port_already_opened": "{port}. ataka dagoeneko irekita dago {ip_version} konexioetarako", "user_home_creation_failed": "Ezin izan da erabiltzailearentzat '{home}' direktorioa sortu", "user_unknown": "Erabiltzaile ezezaguna: {user}", @@ -664,16 +664,16 @@ "migration_0019_slapd_config_will_be_overwritten": "Badirudi eskuz moldatu duzula slapd konfigurazioa. Migrazio garrantzitsu honetarako, YunoHostek slapd ezarpenak eguneratu behar ditu. Oraingo fitxategiak {conf_backup_folder}-n kopiatuko dira.", "unlimit": "Mugarik ez", "restore_already_installed_apps": "Ondorengo aplikazioak ezin dira lehengoratu dagoeneko instalatuta daudelako: {apps}", - "migration_0015_general_warning": "Mesedez, uler ezazu migrazio hau eragiketa zaila dela. YunoHosten kideek ahalik eta hoberen egin dute prozesua egiaztatzeko, baina hala ere sistemaren atalak edo aplikazioak honda litezke.\n\nHorregatik gomendagarria da:\n- Informazio edo aplikazio garrantzitsuen babeskopia egitea. Informazio gehiagorako: https://yunohost.org/backup;\n- Pazientzia izatea migrazioa abiarazterakoan: zure internet konexioaren eta hardwarearen arabera litekeena da ordu batzuk behar izatea eguneraketa amaitu arte.", + "migration_0015_general_warning": "Mesedez, uler ezazu migrazio hau eragiketa zaila dela. YunoHosten kideek ahalik eta hoberen egin dute prozesua egiaztatzeko, baina hala ere sistemaren atalak edo aplikazioak kaltetu litezke.\n\nHorregatik gomendagarria da:\n- Informazio edo aplikazio garrantzitsuen babeskopia egitea. Argibide gehiagorako: https://yunohost.org/backup;\n- Pazientzia izatea migrazioa abiarazterakoan: zure internet konexioaren eta hardwarearen arabera litekeena da ordu batzuk behar izatea eguneraketa amaitu arte.", "migration_0015_specific_upgrade": "Aparte eguneratu behar diren sistemaren paketeen eguneraketa abiarazten…", - "password_too_simple_4": "Pasahitzak 12 karaktere izan behar ditu gutxienez eta zenbakiren bat, karaktere larriren bat, txikiren bat eta bereziren bat izan behar ditu", - "pattern_email": "Helbide elektroniko baliagarri bat izan behar da, '+' karaktererik gabe (adibidez izena@domeinua.eus)", + "password_too_simple_4": "Pasahitzak 12 karaktere izan behar ditu gutxienez eta zenbakiren bat, hizki larriren bat, txikiren bat eta karaktere bereziren bat izan behar ditu", + "pattern_email": "Helbide elektroniko baliagarri bat izan behar da, '+' karaktererik gabe (adibidez: izena@domeinua.eus)", "pattern_username": "Txikiz idatzitako karaktere alfanumerikoak eta azpiko marra soilik eduki ditzake", - "permission_deletion_failed": "Ezin izan da '{permission}' baimena ezabatu: {error}", + "permission_deletion_failed": "Ezinezkoa izan da '{permission}' baimena ezabatzea: {error}", "migration_ldap_rollback_success": "Sistema lehengoratu da.", "regenconf_need_to_explicitly_specify_ssh": "SSH ezarpenak eskuz aldatu dira, baina aldaketak erabiltzeko '--force' zehaztu behar duzu 'ssh' atalean.", - "regex_incompatible_with_tile": "/!\\ Pakete-arduradunak! {permission}' baimenak show_tile aukera 'true' bezala dauka eta horregatik ezin duzu regex URLa URL nagusi bezala ezarri", - "root_password_desynchronized": "Administrariaren pasahitza aldatu da baina YunoHostek ezin izan du aldaketa root pasahitzera zabaldu!", + "regex_incompatible_with_tile": "/!\\ Pakete-arduradunak! {permission}' baimenak show_tile aukera 'true' bezala dauka eta horregatik ezin duzue regex URLa URL nagusi bezala ezarri", + "root_password_desynchronized": "Administrariaren pasahitza aldatu da baina YunoHostek ezin izan du aldaketa root pasahitzera hedatu!", "server_shutdown": "Zerbitzaria itzaliko da", "service_stop_failed": "Ezin izan da '{service}' zerbitzua geldiarazi\n\nZerbitzuen azken erregistroak: {logs}", "service_unknown": "'{service}' zerbitzu ezezaguna", @@ -682,27 +682,27 @@ "restore_nothings_done": "Ez da ezer lehengoratu", "restore_backup_too_old": "Babeskopia fitxategi hau ezin da lehengoratu YunoHosten bertsio zaharregi batetik datorrelako.", "restore_hook_unavailable": "'{part}'-(e)rako lehengoratze agindua ez dago erabilgarri ez sisteman ezta fitxategian ere", - "restore_cleaning_failed": "Ezin izan dira lehengoratu ondoren behin-behineko fitxategiak ezabatu", - "restore_confirm_yunohost_installed": "Ziur al zaude dagoeneko instalatuta dagoen sistema lehengoratzeaz? [{answers}]", + "restore_cleaning_failed": "Ezin izan dira lehengoratzeko behin-behineko fitxategiak ezabatu", + "restore_confirm_yunohost_installed": "Ziur al zaude dagoeneko instalatuta dagoen sistema lehengoratu nahi duzula? [{answers}]", "restore_may_be_not_enough_disk_space": "Badirudi zure sistemak ez duela nahikoa espazio (erabilgarri: {free_space} B, beharrezkoa {needed_space} B, segurtasuneko tartea: {margin} B)", "restore_not_enough_disk_space": "Ez dago nahikoa espazio (erabilgarri: {free_space} B, beharrezkoa {needed_space} B, segurtasuneko tartea: {margin} B)", - "restore_running_hooks": "Lehengoratzeko \"hook\"ak exekutatzen…", - "restore_system_part_failed": "Ezin izan da sistemaren '{part}' atala lehengoratu", + "restore_running_hooks": "Lehengoratzeko 'hook'ak exekutatzen…", + "restore_system_part_failed": "Ezinezkoa izan da sistemaren '{part}' atala lehengoratzea", "server_reboot": "Zerbitzaria berrabiaraziko da", "server_reboot_confirm": "Zerbitzaria berehala berrabiaraziko da, ziur al zaude? [{answers}]", - "service_add_failed": "Ezin izan da '{service}' zerbitzua gehitu", + "service_add_failed": "Ezinezkoa izan da '{service}' zerbitzua gehitzea", "service_added": "'{service}' zerbitzua gehitu da", "service_already_started": "'{service}' zerbitzua matxan dago dagoeneko", "service_already_stopped": "'{service}' zerbitzua geldiarazi da dagoeneko", "service_cmd_exec_failed": "Ezin izan da '{command}' komandoa exekutatu", "service_description_dnsmasq": "Domeinuen izenen ebazpena (DNSa) kudeatzen du", - "service_description_dovecot": "Posta elektronikoko programei mezuak jasotzea ahalbidetzen die (IMAP eta POP3 bidez)", + "service_description_dovecot": "Posta elektronikorako programei mezuak jasotzea ahalbidetzen die (IMAP eta POP3 bidez)", "service_description_metronome": "Bat-bateko XMPP mezularitza kontuak kudeatzen ditu", "service_description_mysql": "Aplikazioen datuak gordetzen ditu (SQL datubasea)", "service_description_nginx": "Zerbitzariak ostatazen dituen webguneak ikusgai egiten ditu", "service_description_php7.3-fpm": "PHP aplikazioak exekutatzen ditu NGINXi esker", - "service_description_redis-server": "Datuak bizkor atzitzeko, zereginak lerratzeko eta programen arteko komunikaziorako datubase berezi bat", - "service_description_rspamd": "Spama bahetzeko eta posta elektronikoarekin zerikusia duten bestelako ezaugarrietarako", + "service_description_redis-server": "Datuak bizkor atzitzeko, zereginak lerratzeko eta programen arteko komunikaziorako datubase berezi bat da", + "service_description_rspamd": "Spama bahetu eta posta elektronikoarekin zerikusia duten bestelako futzioen ardura dauka", "service_description_slapd": "Erabiltzaileak, domeinuak eta hauei lotutako informazioa gordetzen du", - "service_description_yunohost-api": "YunoHosten web interfazearen eta sistemaren arteko hartuemana kudeatzen du" + "service_description_yunohost-api": "YunoHosten web-interfazearen eta sistemaren arteko hartuemana kudeatzen du" } From edc5295d51585732249dcee862fe7ddd67d4194f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 2 Nov 2021 01:29:07 +0100 Subject: [PATCH 0887/1155] Add some debug to _collect_app_files exception ... --- src/yunohost/backup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index cce66597a..058813502 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -734,7 +734,8 @@ class BackupManager: this_app_permissions = {name: infos for name, infos in permissions.items()} write_to_yaml("%s/permissions.yml" % settings_dir, this_app_permissions) - except Exception: + except Exception as e: + logger.debug(e) abs_tmp_app_dir = os.path.join(self.work_dir, "apps/", app) shutil.rmtree(abs_tmp_app_dir, ignore_errors=True) logger.error(m18n.n("backup_app_failed", app=app)) From 85eb43a73045c1108e9bdba8caece8fdb2d0837f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 2 Nov 2021 01:38:11 +0100 Subject: [PATCH 0888/1155] apps: Add YNH_ARCH to app script env for easier debugging and arch check in script --- src/yunohost/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index a7a188452..649110267 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2390,6 +2390,7 @@ def _make_environment_for_app_script( "YNH_APP_INSTANCE_NAME": app, "YNH_APP_INSTANCE_NUMBER": str(app_instance_nb), "YNH_APP_MANIFEST_VERSION": manifest.get("version", "?"), + "YNH_ARCH": check_output("dpkg --print-architecture"), } if workdir: From 844346320b0d3b0684dcb975a610720c0206bd4a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 2 Nov 2021 01:38:33 +0100 Subject: [PATCH 0889/1155] Typo :| --- 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 98a61c16a..53efcc917 100644 --- a/data/helpers.d/backup +++ b/data/helpers.d/backup @@ -482,7 +482,7 @@ ynh_restore_upgradebackup() { if [[ -d /etc/yunohost/apps/$app ]] then ynh_die --message="The app was restored to the way it was before the failed upgrade." - elif + else ynh_die --message="Uhoh ... Yunohost failed to restore the app to the way it was before the failed upgrade :|" fi fi From b953ae9dd4f8886a9dcec93946cdfa10cee3de80 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 2 Nov 2021 01:43:31 +0100 Subject: [PATCH 0890/1155] Typo + used existing ynh_package_is_installed helper --- 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 ecad3b14a..d35328f6a 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -261,7 +261,7 @@ ynh_install_app_dependencies() { dependencies+=", php${specific_php_version}, php${specific_php_version}-fpm, php${specific_php_version}-common" fi - local psql_installed2="$(dpkg --list | grep -q "ii *postgresql-$PSQL_VERSION" && echo yes || echo no)" + local psql_installed="$(ynh_package_is_installed "postgresql-$PSQL_VERSION" && echo yes || echo no)" # The first time we run ynh_install_app_dependencies, we will replace the # entire control file (This is in particular meant to cover the case of @@ -306,7 +306,7 @@ EOF fi # Trigger postgresql regenconf if we may have just installed postgresql - local psql_installed2="$(dpkg --list | grep -q "ii *postgresql-$PSQL_VERSION" && echo yes || echo no)" + local psql_installed2="$(ynh_package_is_installed "postgresql-$PSQL_VERSION" && echo yes || echo no)" if [[ "$psql_installed" != "$psql_installed2" ]] then yunohost tools regen-conf postgresql From c571516c68a883069d13518b264ea582e2c546b7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 2 Nov 2021 01:44:48 +0100 Subject: [PATCH 0891/1155] i18n strings --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 81e75eb32..8590c5c62 100644 --- a/locales/en.json +++ b/locales/en.json @@ -624,8 +624,8 @@ "service_description_fail2ban": "Protects against brute-force and other kinds of attacks from the Internet", "service_description_metronome": "Manage XMPP instant messaging accounts", "service_description_mysql": "Stores app data (SQL database)", + "service_description_postgresql": "Stores app data (SQL database)", "service_description_nginx": "Serves or provides access to all the websites hosted on your server", - "service_description_php7.3-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_rspamd": "Filters spam, and other e-mail related features", From ba489bfc27445d9ed7e1dc65ad9405fccf161267 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 2 Nov 2021 02:02:29 +0100 Subject: [PATCH 0892/1155] helpers: We don't care about the apt_dependencies setting ... it's not used and the info is already stored in the $app-ynh-deps virtual package --- data/helpers.d/apt | 2 -- 1 file changed, 2 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index cea850f6e..2e56e3788 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -316,8 +316,6 @@ EOF || ynh_die --message="Unable to install dependencies" # Install the fake package and its dependencies rm /tmp/${dep_app}-ynh-deps.control - ynh_app_setting_set --app=$app --key=apt_dependencies --value="$dependencies" - if [[ -n "$specific_php_version" ]] then # Set the default php version back as the default version for php-cli. From 3eb69224aa8c29f290c68cca77116b03ae81b8cc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 2 Nov 2021 02:03:53 +0100 Subject: [PATCH 0893/1155] ci: Add test for apt helpers --- tests/test_helpers.d/ynhtest_apt.sh | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 tests/test_helpers.d/ynhtest_apt.sh diff --git a/tests/test_helpers.d/ynhtest_apt.sh b/tests/test_helpers.d/ynhtest_apt.sh new file mode 100644 index 000000000..074bc2e60 --- /dev/null +++ b/tests/test_helpers.d/ynhtest_apt.sh @@ -0,0 +1,22 @@ +ynhtest_apt_install_apt_deps_regular() { + + dpkg --list | grep -q "ii *$app-ynh-deps" && apt remove $app-ynh-deps --assume-yes || true + dpkg --list | grep -q 'ii *nyancat' && apt remove nyancat --assume-yes || true + dpkg --list | grep -q 'ii *sl' && apt remove sl --assume-yes || true + + ! ynh_package_is_installed "$app-ynh-deps" + ! ynh_package_is_installed "nyancat" + ! ynh_package_is_installed "sl" + + ynh_install_app_dependencies "nyancat sl" + + ynh_package_is_installed "$app-ynh-deps" + ynh_package_is_installed "nyancat" + ynh_package_is_installed "sl" + + ynh_remove_app_dependencies + + ! ynh_package_is_installed "$app-ynh-deps" + ! ynh_package_is_installed "nyancat" + ! ynh_package_is_installed "sl" +} From 99e46b3a61d73795267198bf99088dd8d00bcbc8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 2 Nov 2021 02:50:32 +0100 Subject: [PATCH 0894/1155] ynh_local_curl: temporarily auto-add visitors if needed --- data/helpers.d/utils | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 453a1ab94..8b7179289 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -290,8 +290,18 @@ ynh_local_curl() { chown root $cookiefile chmod 700 $cookiefile + # Temporarily enable visitors if needed... + local visitors_enabled=$(ynh_permission_has_user "main" "visitors" && echo yes || echo no) + if [[ $visitors_enabled == "no" ]]; then + ynh_permission_update --permission "main" --add "visitors" + fi + # Curl the URL 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 + + if [[ $visitors_enabled == "no" ]]; then + ynh_permission_update --permission "main" --remove "visitors" + fi } # Create a dedicated config file from a template From abaa60d7742f9b851266e4789a16c3f574c71000 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 2 Nov 2021 03:24:30 +0100 Subject: [PATCH 0895/1155] ynh_install_n / ux: remove useless info message / redundant because scripts already include a script_progression ... --- data/helpers.d/nodejs | 1 - 1 file changed, 1 deletion(-) diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index 6c23c0c99..69e9ce445 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -14,7 +14,6 @@ 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 echo "SOURCE_URL=https://github.com/tj/n/archive/v${n_version}.tar.gz SOURCE_SUM=d4da7ea91f680de0c9b5876e097e2a793e8234fcd0f7ca87a0599b925be087a3" >"$YNH_APP_BASEDIR/conf/n.src" From 0489b717290016c511e86939964492c435e68495 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 3 Nov 2021 18:42:02 +0100 Subject: [PATCH 0896/1155] Update changelog for 4.3.1.8 --- debian/changelog | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/debian/changelog b/debian/changelog index 651f20a75..a7d6c2445 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,18 @@ +yunohost (4.3.1.8) testing; urgency=low + + - [enh] dyndns: Drop some YAGNI + improve IPv6-only support + resilience w.r.t. ns0 / ns1 being down (a61d0231, [#1367](https://github.com/YunoHost/yunohost/pull/1367)) + - [fix] helpers: improve composer debug when it can't install dependencies (4ebcaf8d) + - [enh] helpers: allow to get/set/delete app settings without explicitly passing app id everytime... (fcd2ef9d) + - [fix] helpers: Don't say the 'app was restored' when restore failed after failed upgrade (019d207c) + - [enh] helpers: temporarily auto-add visitors during ynh_local_curl if needed ([#1370](https://github.com/YunoHost/yunohost/pull/1370)) + - [enh] apps: Add YNH_ARCH to app script env for easier debugging and arch check in script (85eb43a7) + - [mod] misc fixes/enh (2687121f, 146fba7d, 86a9cb37, 4e917b5e, 974ea71f, edc5295d, ba489bfc) + - [i18n] Translations updated for Basque, French, Spanish + + Thanks to all contributors <3 ! (ljf, Page Asgardius, ppr, punkrockgirl) + + -- Alexandre Aubin Wed, 03 Nov 2021 18:35:18 +0100 + yunohost (4.3.1.7) testing; urgency=low - [fix] configpanel: Misc technical fixes ... (341059d0, 9c22329e) From dc55a2d99e6b7f0d083c6781c0acb4941dbc1bb0 Mon Sep 17 00:00:00 2001 From: punkrockgirl Date: Thu, 4 Nov 2021 16:57:53 +0000 Subject: [PATCH 0897/1155] Translated using Weblate (Basque) Currently translated at 100.0% (705 of 705 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eu/ --- locales/eu.json | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/locales/eu.json b/locales/eu.json index bbe78029a..f8469c37e 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -65,7 +65,7 @@ "config_apply_failed": "Ezin izan da konfigurazio berria ezarri: {error}", "config_cant_set_value_on_section": "Ezinezkoa da balio bakar bat ezartzea konfigurazio atal oso batean.", "config_no_panel": "Ez da konfigurazio-panelik aurkitu.", - "diagnosis_found_errors_and_warnings": "{category} atalari dago(z)kion {errors} arazoa(k) (eta {warnings} abisua(k)) aurkitu d(ir)a!", + "diagnosis_found_errors_and_warnings": "{category} atalari dago(z)kion {errors} arazo (eta {warnings} abisu) aurkitu d(ir)a!", "diagnosis_description_regenconf": "Sistemaren ezarpenak", "app_upgrade_script_failed": "Errore bat gertatu da aplikazioaren eguneratze aginduan", "diagnosis_basesystem_hardware": "Zerbitzariaren arkitektura {virt} {arch} da", @@ -124,16 +124,16 @@ "diagnosis_basesystem_ynh_single_version": "{package} bertsioa: {version} ({repo})", "diagnosis_failed_for_category": "'{category}' ataleko diagnostikoak kale egin du: {error}", "diagnosis_cache_still_valid": "(Cachea oraindik baliogarria da {category} (ar)en diagnosirako. Ez da berrabiaraziko!)", - "diagnosis_found_errors": "{category} atalari dago(z)kion {errors} arazoa(k) aurkitu d(ir)a!", - "diagnosis_found_warnings": "{category} atalari dagokion eta hobetu daite(z)keen {warnings} abisua(k) aurkitu d(ir)a.", + "diagnosis_found_errors": "{category} atalari dago(z)kion {errors} arazo aurkitu d(ir)a!", + "diagnosis_found_warnings": "{category} atalari dagokion eta hobetu daite(z)keen {warnings} abisu aurkitu d(ir)a.", "diagnosis_ip_connected_ipv6": "Zerbitzaria IPv6 bidez dago internetera konektatuta!", "diagnosis_everything_ok": "Badirudi guztia zuzen dagoela {category} atalean!", "diagnosis_ip_no_ipv4": "Zerbitzariak ez du dabilen IPv4rik.", "diagnosis_ip_no_ipv6": "Zerbitzariak ez du dabilen IPv6rik.", "diagnosis_ip_broken_dnsresolution": "Domeinu izenaren ebazpena kaltetuta dagoela dirudi… Suebakiren bat ote dago DNS eskaerak oztopatzen?", - "diagnosis_diskusage_low": "{mountpoint} fitxategi-sistemak ({device} euskarrian) edukieraren {free} ({free_percent}%) bakarrik du erabilgarri ({total} orotara). Kontuz ibili.", + "diagnosis_diskusage_low": "{mountpoint} fitxategi-sistemak ({device} euskarrian) edukieraren {free} ({free_percent}%) bakarrik ditu erabilgarri ({total} orotara). Kontuz ibili.", "diagnosis_dns_good_conf": "DNS ezarpenak zuzen konfiguratuta daude {domain} domeinurako ({category} atala)", - "diagnosis_diskusage_verylow": "{mountpoint} fitxategi-sistemak ({device} euskarrian) edukieraren {free} ({free_percent}%) bakarrik du erabilgarri ({total} orotara). Zertxobait hustu beharko zenuke!", + "diagnosis_diskusage_verylow": "{mountpoint} fitxategi-sistemak ({device} euskarrian) edukieraren {free} ({free_percent}%) bakarrik ditu erabilgarri ({total} orotara). Zertxobait hustu beharko zenuke!", "diagnosis_description_basesystem": "Sistemaren oinarria", "diagnosis_description_services": "Zerbitzuen egoeraren egiaztapena", "diagnosis_http_could_not_diagnose": "Ezinezkoa izan da domeinuak IPv{ipversion} kanpotik eskuragarri dauden egiaztatzea.", @@ -170,12 +170,12 @@ "app_full_domain_unavailable": "Aplikazio honek bere domeinu propioa behar du, baina beste aplikazio batzuk daude dagoeneko instalatuta '{domain}' domeinuan. Azpidomeinu bat erabil zenezake instalatu nahi duzun aplikaziorako.", "app_install_script_failed": "Errore bat gertatu da aplikazioaren instalatzailearen aginduetan", "diagnosis_basesystem_host": "Zerbitzariak Debian {debian_version} darabil", - "diagnosis_ignored_issues": "(kontutan hartu ez d(ir)en + {nb_ignored} arazo)", + "diagnosis_ignored_issues": "(kontuan hartu ez d(ir)en + {nb_ignored} arazo)", "diagnosis_ip_dnsresolution_working": "Domeinu izenaren ebazpena badabil!", "diagnosis_failed": "Ezinezkoa izan da '{category}' ataleko diagnostikoa lortzea: {error}", "diagnosis_ip_weird_resolvconf": "DNS ebazpena badabilela dirudi, baina antza denez moldatutako /etc/resolv.conf fitxategia erabiltzen ari zara.", "diagnosis_dns_bad_conf": "DNS balio batzuk falta dira edo ez dira zuzenak {domain} domeinurako ({category} atala)", - "diagnosis_diskusage_ok": "{mountpoint} fitxategi-sistemak ({device} euskarrian) edukieraren {free} ({free_percent}%) du erabilgarri oraindik ({total} orotara)!", + "diagnosis_diskusage_ok": "{mountpoint} fitxategi-sistemak ({device} euskarrian) edukieraren {free} ({free_percent}%) ditu erabilgarri oraindik ({total} orotara)!", "apps_catalog_update_success": "Aplikazioen katalogoa eguneratu da!", "certmanager_warning_subdomain_dns_record": "'{subdomain}' azpidomeinuak ez dauka '{domain}'(e)k duen IP bera. Ezaugarri batzuk ez dira erabilgarri egongo hau zuzendu arte eta ziurtagiri bat birsortu arte.", "app_argument_choice_invalid": "Hautatu ({choices}) aukeretako bat '{name}' argumenturako: '{value}' ez dago aukera horien artean", @@ -183,7 +183,7 @@ "diagnosis_basesystem_ynh_main_version": "Zerbitzariak YunoHosten {main_version} ({repo}) darabil", "backup_custom_backup_error": "Neurrira egindako babeskopiak ezin izan du 'babeskopia egin' urratsetik haratago egin", "diagnosis_ip_broken_resolvconf": "Zure zerbitzarian domeinu izenaren ebazpena kaltetuta dagoela dirudi, antza denez /etc/resolv.conf fitxategia ez dago 127.0.0.1ra adi.", - "diagnosis_ip_no_ipv6_tip": "Dabilen IPv6 izatea ez da derrigorrezkoa zerbitzariaren funtzionamendurako, baina egokiena da interneten osasunerako. IPv6 automatikoki konfiguratu beharko luke sistemak edo telefono-konpainiak. Bestela, eskuz konfiguratu beharko zenituzke hainbat gauza dokumentazioan azaltzen den bezala. Ezin baduzu edo IPv6 gaitzea zuretzat kontu teknikoegia baldin bada, ez duzu abisu hau zertan kontutan hartu.", + "diagnosis_ip_no_ipv6_tip": "Dabilen IPv6 izatea ez da derrigorrezkoa zerbitzariaren funtzionamendurako, baina egokiena da interneten osasunerako. IPv6 automatikoki konfiguratu beharko luke sistemak edo operadoreak. Bestela, eskuz konfiguratu beharko zenituzke hainbat gauza dokumentazioan azaltzen den bezala. Ezin baduzu edo IPv6 gaitzea zuretzat kontu teknikoegia baldin bada, ez duzu abisu hau zertan kontuan hartu.", "diagnosis_http_nginx_conf_not_up_to_date_details": "Egoera konpontzeko, ikuskatu desberdintasunak yunohost tools regen-conf nginx --dry-run --with-diff komandoren bidez eta, proposatutako aldaketak onartzen badituzu, ezarri itzazu yunohost tools regen-conf nginx --force erabiliz.", "diagnosis_domain_not_found_details": "{domain} domeinua ez da WHOISen datubasean existitzen edo iraungi da!", "app_start_backup": "{app}(r)en babeskopia egiteko fitxategiak eskuratzen…", @@ -214,7 +214,7 @@ "backup_running_hooks": "Babeskopien kakoak exekutatzen…", "backup_unable_to_organize_files": "Ezinezkoa izan da modu azkarra erabiltzea fitxategiko artxiboak prestatzeko", "backup_output_symlink_dir_broken": "'{path}' fitxategi-katalogoaren symlink-a ez dabil. Agian [ber]muntatzea ahaztu zaizu edo euskarria atakara konektatzea ahaztu duzu.", - "backup_with_no_backup_script_for_app": "'{app}' aplikazioak ez du babeskopia egiteko agindurik. Ez da kontutan hartuko.", + "backup_with_no_backup_script_for_app": "'{app}' aplikazioak ez du babeskopia egiteko agindurik. Ez da kontuan hartuko.", "backup_with_no_restore_script_for_app": "{app}(e)k ez du lehengoratzeko agindurik, ezingo duzu aplikazio hau automatikoki lehengoratu.", "certmanager_attempt_to_renew_nonLE_cert": "'{domain}' domeinurako ziurtagiria ez da Let's Encryptek jaulkitakoa. Ezin da automatikoki berriztu!", "certmanager_attempt_to_renew_valid_cert": "'{domain}' domeinurako ziurtagiria iraungitzear dago! (Zertan ari zaren baldin badakizu, --force erabil dezakezu)", @@ -279,7 +279,7 @@ "dyndns_registration_failed": "Ezinezkoa izan da DynDNS domeinua erregistratzea: {error}", "extracting": "Ateratzen…", "diagnosis_ports_unreachable": "{port}. ataka ez dago eskuragarri kanpotik.", - "diagnosis_regenconf_manually_modified_details": "Ez dago arazorik zertan ari zaren baldin badakizu! YunoHostek fitxategi hau automatikoki eguneratzeari utziko dio… Baina kontutan izan YunoHosten eguneraketek aldaketa garrantzitsuak izan ditzaketela. Nahi izatekotan, desberdintasunak aztertu ditzakezu yunohost tools regen-conf {category} --dry-run --with-diff komandoa exekutatuz, eta gomendatutako konfiguraziora bueltatu yunohost tools regen-conf {category} --force erabiliz", + "diagnosis_regenconf_manually_modified_details": "Ez dago arazorik zertan ari zaren baldin badakizu! YunoHostek fitxategi hau automatikoki eguneratzeari utziko dio… Baina kontuan izan YunoHosten eguneraketek aldaketa garrantzitsuak izan ditzaketela. Nahi izatekotan, desberdintasunak aztertu ditzakezu yunohost tools regen-conf {category} --dry-run --with-diff komandoa exekutatuz, eta gomendatutako konfiguraziora bueltatu yunohost tools regen-conf {category} --force erabiliz", "experimental_feature": "Adi: Funtzio hau esperimentala eta ezegonkorra da, ez zenuke erabili beharko ez badakizu zertan ari zaren.", "global_settings_cant_write_settings": "Ezinezkoa izan da konfigurazio fitxategia gordetzea, zergatia: {reason}", "dyndns_domain_not_provided": "{provider} DynDNS enpresak ezin du {domain} domeinua eskaini.", @@ -351,7 +351,7 @@ "log_domain_dns_push": "Bidali '{}' domeinuaren DNS ezarpenak", "log_tools_migrations_migrate_forward": "Exekutatu migrazioak", "log_tools_postinstall": "Abiarazi YunoHost zerbitzariaren instalazio ondorengo prozesua", - "diagnosis_mail_fcrdns_nok_alternatives_4": "Operadore batzuek ez dute alderantzizko DNSa konfiguratzen uzten (edo funtzioa ez dabil…). Hau dela-eta arazoak badituzu, irtenbide batzuk eduki ditzakezu:
- Operadore batzuek relay posta zerbitzari bat eskaini dezakete, baina kasu horretan zure posta elektronikoa zelatatu dezakete.
- Pribatutasuna bermatzeko *IP publiko bat duen* VPN bat erabiltzea izan daiteke irtenbidea. Ikus https://yunohost.org/#/vpn_advantage
- Edo operadore desberdin batera aldatu", + "diagnosis_mail_fcrdns_nok_alternatives_4": "Operadore batzuek ez dute alderantzizko DNSa konfiguratzen uzten (edo funtzioa ez dabil…). Hau dela-eta arazoak badituzu, irtenbide batzuk eduki ditzakezu:
- Operadore batzuek relay posta zerbitzari bat eskaini dezakete, baina kasu horretan zure posta elektronikoa zelatatu dezakete.
- Pribatutasuna bermatzeko *IP publikoa* duen VPN bat erabiltzea izan daiteke irtenbidea. Ikus https://yunohost.org/#/vpn_advantage
- Edo operadore desberdin batera aldatu", "domain_dns_registrar_supported": "YunoHostek automatikoki antzeman du domeinu hau **{registrar}** erregistro-enpresak kudeatzen duela. Nahi baduzu YunoHostek automatikoki konfiguratu ditzake DNS ezarpenak, API egiaztagiri zuzenak zehazten badituzu. API egiaztagiriak non lortzeko dokumentazioa orri honetan duzu: https://yunohost.org/registar_api_{registrar}. (Baduzu DNS erregistroak eskuz konfiguratzeko aukera ere, gidalerro hauetan ageri den bezala: https://yunohost.org/dns)", "domain_dns_push_failed_to_list": "Ezinezkoa izan da APIa erabiliz oraingo erregistroak antzematea: {error}", "domain_dns_push_already_up_to_date": "Ezarpenak egunean daude, ez dago zereginik.", @@ -380,11 +380,11 @@ "log_user_create": "Gehitu '{}' erabiltzailea", "group_cannot_edit_visitors": "'bisitariak' taldea ezin da eskuz moldatu. Saiorik hasi gabeko bisitariak barne hartzen dituen talde berezia da", "diagnosis_ram_verylow": "Sistemak RAM memoriaren {available} ({available_percent}%) dauka bakarrik erabilgarri! ({total} orotara)", - "diagnosis_ram_low": "Sistemak {available} soilik du erabilgarri, RAM memoriaren ({available_percent}%) ({total} orotara). Kontuz ibili.", - "diagnosis_ram_ok": "Sistemak oraindik dauka RAM memoriaren {available} ({available_percent}%) erabilgarri, {total} orotara.", - "diagnosis_swap_none": "Sistemak ez du swap-ik. Gutxienez {recommended} gehitzen saiatu beharko zinateke, sistema memoriarik gabe gera ez dadin.", - "diagnosis_swap_ok": "Sistemak {total} swap dauka!", - "diagnosis_regenconf_allgood": "Konfigurazio fitxategi guztiak bat datoz gomendatutako ezarpenekin!", + "diagnosis_ram_low": "RAM memoriaren {available} ditu erabilgarri sistemak; memoria guztiaren {total} %{available_percent}a. Adi ibili.", + "diagnosis_ram_ok": "RAM memoriaren {available} ditu oraindik erabilgarri sistemak; memoria guztiaren {total} %{available_percent}a.", + "diagnosis_swap_none": "Sistemak ez du swap-ik. Gutxienez {recommended} izaten saiatu beharko zinateke, sistema memoriarik gabe gera ez dadin.", + "diagnosis_swap_ok": "Sistemak {total} swap dauzka!", + "diagnosis_regenconf_allgood": "Konfigurazio-fitxategi guztiak bat datoz gomendatutako ezarpenekin!", "diagnosis_regenconf_manually_modified": "Dirudienez {file} konfigurazio fitxategia eskuz aldatu da.", "diagnosis_security_vulnerable_to_meltdown": "Badirudi Meltdown izeneko segurtasun arazo larriak eragin diezazukela", "diagnosis_ports_could_not_diagnose": "Ezinezkoa izan da atakak IPv{ipversion} erabiliz kanpotik eskuragarri dauden egiaztatzea.", @@ -400,18 +400,18 @@ "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Oraingo alderantzizko DNSa: {rdns_domain}
Esperotako balioa: {ehlo_domain}", "diagnosis_mail_queue_too_big": "Mezu gehiegi posta elektronikoaren ilaran: ({nb_pending} mezu)", "diagnosis_ports_could_not_diagnose_details": "Errorea: {error}", - "diagnosis_swap_tip": "Mesedez, kontutan hartu zerbitzari honen swap memoria SD edo SSD euskarri batean gordetzen bada, euskarri horren bizi-iraupena izugarri laburtu dezakeela.", + "diagnosis_swap_tip": "Mesedez, kontuan hartu zerbitzari honen swap memoria SD edo SSD euskarri batean gordetzeak euskarri horren bizi-iraupena izugarri laburtu dezakeela.", "invalid_regex": "'Regexa' ez da zuzena: '{regex}'", "group_creation_failed": "Ezinezkoa izan da '{group}' taldea sortzea: {error}", "log_user_permission_reset": "Berrezarri '{}' baimena", "group_cannot_edit_primary_group": "'{group}' taldea ezin da eskuz moldatu. Erabiltzaile zehatz bakar bat duen talde nagusia da.", - "diagnosis_swap_notsomuch": "Sistemak {total} swap baino ez du. Gutxienez {recommended} gehitzen saiatu beharko zinateke sistema memoriarik gabe gera ez dadin.", + "diagnosis_swap_notsomuch": "Sistemak {total} swap baino ez ditu. Gutxienez {recommended} izaten saiatu beharko zinateke sistema memoriarik gabe gera ez dadin.", "diagnosis_security_vulnerable_to_meltdown_details": "Arazoa konpontzeko, sistema eguneratu eta berrabiarazi beharko zenuke linux-en kernel berriagoa erabiltzeko (edo zerbitzariaren arduradunarekin jarri harremanetan). Ikus https://meltdownattack.com/ argibide gehiagorako.", "diagnosis_services_conf_broken": "{service} zerbitzuko konfigurazioa hondatuta dago!", "diagnosis_services_bad_status_tip": "Zerbitzua berrabiarazten saia zaitezke eta nahikoa ez bada, aztertu zerbitzuaren erregistroa administrariaren webgunean. (komandoak nahiago badituzu yunohost service restart {service} eta yunohost service log {service} hurrenez hurren).", "diagnosis_mail_ehlo_unreachable_details": "Ezinezkoa izan da zure zerbitzariko 25. atakari konektatzea IPv{ipversion} erabiliz. Badirudi ez dagoela eskuragarri.
1. Arazo honen zergati ohikoena 25. ataka egoki birbideratuta ez egotea da.
2. Egiaztatu postfix zerbitzua martxan dagoela.
3. Konfigurazio konplexuagoetan: egiaztatu suebaki edo reverse-proxyak konexioa oztopatzen ez dutela.", "group_already_exist_on_system_but_removing_it": "{group} taldea existitzen da sistemaren taldeetan, baina YunoHostek ezabatuko du…", - "diagnosis_mail_fcrdns_nok_details": "Lehenik eta behin zure routerraren konfigurazio gunean edo hostingaren enprearen aukeretan alderantzizko DNSa konfiguratzen saiatu beharko zinateke {ehlo_domain} erabiliz. (Hosting enpresaren arabera, ezinbestekoa da beraiekin harremanetan jartzea).", + "diagnosis_mail_fcrdns_nok_details": "Lehenik eta behin zure routerraren konfigurazio gunean edo hostingaren enpresaren aukeretan alderantzizko DNSa konfiguratzen saiatu beharko zinateke {ehlo_domain} erabiliz. (Hosting enpresaren arabera, ezinbestekoa da beraiekin harremanetan jartzea).", "diagnosis_mail_outgoing_port_25_ok": "SMTP posta zerbitzaria posta elektronikoa bidaltzeko gai da (25. atakaren irteera ez dago blokeatuta).", "diagnosis_ports_partially_unreachable": "{port}. ataka ez dago eskuragarri kanpotik Pv{failed} erabiliz.", "diagnosis_ports_forwarding_tip": "Arazoa konpontzeko, litekeena da operadorearen routerrean ataken birbideraketa konfiguratu behar izatea, https://yunohost.org/isp_box_config-n agertzen den bezala", @@ -458,7 +458,7 @@ "global_settings_setting_smtp_relay_user": "SMTP relay erabiltzailea", "domain_cert_gen_failed": "Ezinezkoa izan da ziurtagiria sortzea", "field_invalid": "'{}' ez da baliogarria", - "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Operadore batzuei bost axola zaie internetaren neutraltasuna (Net Neutrality) eta ez dute 25. ataka desblokeatzen uzten.
- Operadore batzuek relay posta zerbitzari bat eskaini dezakete, baina kasu horretan zure posta elektronikoa zelatatu dezakete.
- Pribatutasuna bermatzeko *IP publiko bat duen* VPN bat erabiltzea izan daiteke irtenbidea. Ikus https://yunohost.org/#/vpn_advantage
- Edo operadore desberdin batera aldatu", + "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Operadore batzuei bost axola zaie internetaren neutraltasuna (Net Neutrality) eta ez dute 25. ataka desblokeatzen uzten.
- Operadore batzuek relay posta zerbitzari bat eskaini dezakete, baina kasu horretan zure posta elektronikoa zelatatu dezakete.
- Pribatutasuna bermatzeko *IP publikoa* duen VPN bat erabiltzea izan daiteke irtenbidea. Ikus https://yunohost.org/#/vpn_advantage
- Edo operadore desberdin batera aldatu", "ldap_server_down": "Ezin izan da LDAP zerbitzarira konektatu", "ldap_server_is_down_restart_it": "LDAP zerbitzaria ez dago martxan, saia zaitez berrabiarazten…", "log_app_upgrade": "'{}' aplikazioa eguneratu", @@ -506,7 +506,7 @@ "migration_0015_not_stretch": "Debianen oraingo bertsioa ez da Stretch!", "migrations_success_forward": "{id} migrazioak amaitu du", "migrations_to_be_ran_manually": "{id} migrazioa eskuz abiarazi behar da. Mesedez, joan Erramintak → Migrazioak atalera administrazio-webgunean edo bestela exekutatu 'yunohost tools migrations run'.", - "permission_currently_allowed_for_all_users": "Baimen hau erabiltzaile guztiei esleitzen zaio eta baita beste talde batzuei ere. Litekeena da 'all users' baimena edo esleituta duten taldeei baimena kendu nahi izatea.", + "permission_currently_allowed_for_all_users": "Baimen hau erabiltzaile guztiei esleitzen zaie eta baita beste talde batzuei ere. Litekeena da 'all users' baimena edo esleituta duten taldeei baimena kendu nahi izatea.", "permission_require_account": "'{permission}' baimena zerbitzarian kontua duten erabiltzaileentzat da eta, beraz, ezin da gaitu bisitarientzat.", "postinstall_low_rootfsspace": "'root' fitxategi-sistemak 10 GB edo espazio gutxiago dauka, kezkatzekoa dena! Litekeena da espaziorik gabe geratzea aurki! Gomendagarria da 'root' fitxategi-sistemak gutxienez 16 GB libre izatea. Jakinarazpen honen ondoren YunoHost instalatzen jarraitu nahi baduzu, berrabiarazi agindua '--force-diskspace' gehituz", "this_action_broke_dpkg": "Eragiketa honek dpkg/APT (sistemaren pakete kudeatzaileak) kaltetu ditu… Arazoa konpontzeko SSH bidez konektatu eta 'sudo apt install --fix-broken' edota 'sudo dpkg --configure -a' exekutatu dezakezu.", @@ -559,7 +559,7 @@ "user_import_nothing_to_do": "Ez dago erabiltzaileak inportatu beharrik", "mailbox_used_space_dovecot_down": "Dovecot mailbox zerbitzua martxan egon behar da postak erabilitako espazioa ezagutzeko", "migration_0015_cleaning_up": "Beharrezkoak ez diren cache eta paketeak kentzen…", - "migration_0015_modified_files": "Mesedez, kontutan hartu ondorengo fitxategiak eskuz aldatu direla eta sistema eguneratzean euren gainean idatziko dela: {manually_modified_files}", + "migration_0015_modified_files": "Mesedez, kontuan hartu ondorengo fitxategiak eskuz aldatu direla eta sistema eguneratzean euren gainean idatziko dela: {manually_modified_files}", "migration_0015_not_enough_free_space": "/var/ fitxategi-sisteman oso espazio gutxi geratzen da! Gutxienez GB bat izan behar da migrazioa abiarazteko.", "migration_0015_weak_certs": "Sinadura-algoritmo ahulak darabiltzaten ziurtagiriak aurkitu dira eta eguneratu behar dira nginx-en hurrengo bertsioarekin bateragarriak izateko: {certs}", "migration_description_0017_postgresql_9p6_to_11": "Migratu datubaseak PostgreSQL 9.6-tik 11-ra", @@ -637,7 +637,7 @@ "migration_0015_main_upgrade": "Eguneraketa orokorra abiarazten…", "migration_ldap_backup_before_migration": "Sortu LDAP datubase eta aplikazioen ezarpenen babeskopia migrazioa abiarazi baino lehen.", "migration_ldap_can_not_backup_before_migration": "Sistemaren babeskopiak ez du amaitu migrazioak huts egin baino lehen. Errorea: {error}", - "migration_0015_problematic_apps_warning": "Mesedez, kontutan izan arazoak sor ditzaketen aplikazioak aurkitu direla. Badirudi ez zirela YunoHosten aplikazioen katalogotik instalatu, edo 'ez dabiltza' etiketa dute. Beraz, ezin da bermatu eguneratu eta gero funtzionatuko dutenik: {problematic_apps}", + "migration_0015_problematic_apps_warning": "Mesedez, kontuan izan arazoak sor ditzaketen aplikazioak aurkitu direla. Badirudi ez zirela YunoHosten aplikazioen katalogotik instalatu, edo 'ez dabiltza' etiketa dute. Beraz, ezin da bermatu eguneratu eta gero funtzionatuko dutenik: {problematic_apps}", "migration_0017_not_enough_space": "Espazio gehiago behar da {path}-n migrazioa exekutatzeko.", "migration_0017_postgresql_11_not_installed": "PostgreSQL 9.6 instalatuta egon arren postgresql 11 ez‽ Zerbait arraroa gertatu da zure sisteman :(…", "migration_0018_failed_to_reset_legacy_rules": "Ezinezkoa izan da zaharkitutako iptaulak berrezartzea: {error}", @@ -704,5 +704,5 @@ "service_description_redis-server": "Datuak bizkor atzitzeko, zereginak lerratzeko eta programen arteko komunikaziorako datubase berezi bat da", "service_description_rspamd": "Spama bahetu eta posta elektronikoarekin zerikusia duten bestelako futzioen ardura dauka", "service_description_slapd": "Erabiltzaileak, domeinuak eta hauei lotutako informazioa gordetzen du", - "service_description_yunohost-api": "YunoHosten web-interfazearen eta sistemaren arteko hartuemana kudeatzen du" + "service_description_yunohost-api": "YunoHosten web-atariaren eta sistemaren arteko hartuemana kudeatzen du" } From 2d89e15671bf9312d411dfc153d551f7878714d8 Mon Sep 17 00:00:00 2001 From: punkrockgirl Date: Thu, 4 Nov 2021 17:37:09 +0000 Subject: [PATCH 0898/1155] Translated using Weblate (Basque) Currently translated at 100.0% (705 of 705 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eu/ --- locales/eu.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/locales/eu.json b/locales/eu.json index f8469c37e..b5f98dec5 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -36,7 +36,7 @@ "danger": "Arriskua:", "diagnosis_dns_discrepancy": "Ez dirudi ondorengo DNS balioak bat datozenik proposatutako konfigurazioarekin:
Mota: {type}
Izena: {name}
Oraingo balioa: {current}
Proposatutako balioa: {value}", "diagnosis_dns_specialusedomain": "{domain} domeinua top-level domain (TLD) erabilera berezikoa da .local edo .test bezala eta horregatik ez du DNS erregistrorik erabiltzeko beharrik.", - "diagnosis_http_bad_status_code": "Badirudi zerbitzari hau ez den beste gailu batek erantzun diola eskaerari (agian routerrak).
1. Honen arrazoi ohikoena 80 (eta 443) ataka zerbitzarira ondo birbidaltzen ez dela da.
2. Konfigurazio konplexua badarabilzu, egiaztatu suebakiak edo reverse-proxyk oztopatzen ez dutela.", + "diagnosis_http_bad_status_code": "Zerbitzari hau ez den beste gailu batek erantzun omen dio eskaerari (agian routerrak).
1. Honen arrazoi ohikoena 80 (eta 443) ataka zerbitzarira ondo birbidaltzen ez dela da.
2. Konfigurazio konplexua badarabilzu, egiaztatu suebakiak edo reverse-proxyk oztopatzen ez dutela.", "diagnosis_http_timeout": "Denbora agortu da sare lokaletik kanpo zure zerbitzarira konektatzeko ahaleginean. Eskuragarri ez dagoela dirudi.
1. 80 (eta 443) ataka zerbitzarira modu egokian birzuzentzen ez direla da ohiko zergatia.
2. Badaezpada egiaztatu nginx martxan dagoela.
3. Konfigurazio konplexuetan, egiaztatu suebakiak edo reverse-proxyk konexioa oztopatzen ez dutela.", "app_sources_fetch_failed": "Ezinezkoa izan da fitxategiak eskuratzea, zuzena al da URLa?", "app_make_default_location_already_used": "Ezinezkoa izan da '{app}' '{domain}' domeinuan lehenestea, '{other_app}'(e)k dagoeneko '{domain}' erabiltzen duelako", @@ -149,7 +149,7 @@ "diagnosis_dns_point_to_doc": "Mesedez, irakurri dokumentazioa DNS erregistroekin laguntza behar baduzu.", "diagnosis_mail_ehlo_unreachable": "SMTP posta zerbitzaria ez dago eskuragarri IPv{ipversion}ko sare lokaletik kanpo eta, beraz, ez da posta elektronikoa jasotzeko gai.", "diagnosis_mail_ehlo_bad_answer_details": "Litekeena da zure zerbitzaria ez den beste gailu batek erantzun izana.", - "diagnosis_mail_blacklist_listed_by": "Zure {item} IPa edo domeinua {blacklist_name} zerrenda beltzean ageri da", + "diagnosis_mail_blacklist_listed_by": "Zure domeinua edo {item} IPa {blacklist_name} zerrenda beltzean ageri da", "diagnosis_mail_blacklist_website": "Zerrenda beltzean zergatik zauden ulertu eta konpondu ondoren, {blacklist_website} webgunean zure IP edo domeinua bertatik atera dezatela eska dezakezu", "diagnosis_http_could_not_diagnose_details": "Errorea: {error}", "diagnosis_http_hairpinning_issue": "Dirudienez zure sareak ez du hairpinninga gaituta.", @@ -238,8 +238,8 @@ "diagnosis_backports_in_sources_list": "Dirudienez apt (pakete kudeatzailea) backports biltegia erabiltzeko konfiguratuta dago. Zertan ari zaren ez badakizu, ez zenuke backports biltegietako aplikaziorik instalatu beharko, ezegonkortasun eta gatazkak eragin ditzaketelako sistemarekin.", "app_restore_failed": "Ezinezkoa izan da {app} lehengoratzea: {error}", "diagnosis_apps_allgood": "Instalatutako aplikazioek oinarrizko pakete-jarraibideekin bat egiten dute", - "diagnosis_apps_bad_quality": "Aplikazio hau hondatuta dagoela dio YunoHosten aplikazioen katalogoak. Agian behin-behineko kontua da arduradunak arazoa konpondu bitartean. Momentuz, ezin da aplikazioa eguneratu.", - "diagnosis_apps_broken": "Aplikazio hau YunoHosten aplikazioen katalogoan hondatuta dagoela ageri da. Agian behin-behineko kontua da arduradunak konpondu bitartean. Momentuz, ezin da aplikazioa eguneratu.", + "diagnosis_apps_bad_quality": "Aplikazio hau hondatuta dagoela dio YunoHosten aplikazioen katalogoak. Agian behin-behineko kontua da arduradunak arazoa konpondu bitartean. Oraingoz, ezin da aplikazioa eguneratu.", + "diagnosis_apps_broken": "Aplikazio hau YunoHosten aplikazioen katalogoan hondatuta dagoela ageri da. Agian behin-behineko kontua da arduradunak konpondu bitartean. Oraingoz, ezin da aplikazioa eguneratu.", "diagnosis_apps_deprecated_practices": "Instalatutako aplikazio honen bertsioak oraindik darabiltza zaharkitutako pakete-jarraibideak. Eguneratzea hausnartu beharko zenuke.", "diagnosis_apps_issue": "Arazo bat dago {app} aplikazioarekin", "diagnosis_apps_not_in_app_catalog": "Aplikazio hau ez da YunoHosten aplikazioen katalogokoa. Iraganean egon bazen eta ezabatu izan balitz, desinstalatzea litzateke onena, ez baitu eguneraketarik jasoko eta sistemaren integritate eta segurtasuna arriskuan jarri lezakeelako.", @@ -316,7 +316,7 @@ "domain_dns_push_record_failed": "Ezinezkoa izan da {type}/{name} ezarpenak {action}: {error}", "domain_dns_push_success": "DNS ezarpenak eguneratu dira!", "domain_dns_push_failed": "DNS ezarpenen eguneratzeak kale egin du.", - "domain_dns_push_partial_failure": "DNS ezarpenak hala-nola eguneratu dira: jakinarazpen/errore batzuk egon dira.", + "domain_dns_push_partial_failure": "DNS ezarpenak erdipurdi eguneratu dira: jakinarazpen/errore batzuk egon dira.", "global_settings_setting_smtp_relay_host": "YunoHosten ordez posta elektronikoa bidaltzeko SMTP relay helbidea. Erabilgarri izan daiteke egoera hauetan: operadore edo VPS enpresak 25. ataka blokeatzen badu, DUHLen zure etxeko IPa ageri bada, ezin baduzu alderantzizko DNSa ezarri edo zerbitzari hau ez badago zuzenean internetera konektatuta baina posta elektronikoa bidali nahi baduzu.", "group_deletion_failed": "Ezinezkoa izan da '{group}' taldea ezabatzea: {error}", "invalid_number_min": "{min} baino handiagoa izan behar da", @@ -379,9 +379,9 @@ "diagnosis_mail_queue_unavailable": "Ezinezkoa da ilaran zenbat posta elektroniko dauden kontsultatzea", "log_user_create": "Gehitu '{}' erabiltzailea", "group_cannot_edit_visitors": "'bisitariak' taldea ezin da eskuz moldatu. Saiorik hasi gabeko bisitariak barne hartzen dituen talde berezia da", - "diagnosis_ram_verylow": "Sistemak RAM memoriaren {available} ({available_percent}%) dauka bakarrik erabilgarri! ({total} orotara)", - "diagnosis_ram_low": "RAM memoriaren {available} ditu erabilgarri sistemak; memoria guztiaren {total} %{available_percent}a. Adi ibili.", - "diagnosis_ram_ok": "RAM memoriaren {available} ditu oraindik erabilgarri sistemak; memoria guztiaren {total} %{available_percent}a.", + "diagnosis_ram_verylow": "RAM memoriaren {available} baino ez ditu erabilgarri sistemak; memoria guztiaren ({total}) %{available_percent}a bakarrik!", + "diagnosis_ram_low": "RAM memoriaren {available} ditu erabilgarri sistemak; memoria guztiaren ({total}) %{available_percent}a. Adi ibili.", + "diagnosis_ram_ok": "RAM memoriaren {available} ditu oraindik erabilgarri sistemak; memoria guztiaren ({total}) %{available_percent}a.", "diagnosis_swap_none": "Sistemak ez du swap-ik. Gutxienez {recommended} izaten saiatu beharko zinateke, sistema memoriarik gabe gera ez dadin.", "diagnosis_swap_ok": "Sistemak {total} swap dauzka!", "diagnosis_regenconf_allgood": "Konfigurazio-fitxategi guztiak bat datoz gomendatutako ezarpenekin!", @@ -396,7 +396,7 @@ "diagnosis_mail_outgoing_port_25_blocked": "SMTP posta zerbitzariak ezin ditu posta elektronikoak bidali 25. ataka itxita dagoelako IPv{ipversion}n.", "diagnosis_mail_outgoing_port_25_blocked_details": "Lehenik eta behin operadorearen routerreko aukeretan saiatu beharko zinateke 25. ataka desblokeatzen. (Hosting enpresaren arabera, beraiekin harremanetan jartzea beharrezkoa izango da).", "diagnosis_mail_ehlo_wrong_details": "Kanpo-diagnostikatzaileak IPv{ipversion}an jaso duen EHLOa eta zure zerbitzariaren domeinukoa ez datoz bat.
Jasotako EHLOa: {wrong_ehlo}
Esperotakoa: {right_ehlo}
Arazo honen zergati ohikoena 25. ataka zuzen konfiguratuta ez egotea da. Edo agian suebaki edo reverse-proxya oztopo izan daiteke.", - "diagnosis_mail_fcrdns_different_from_ehlo_domain": "Alderantzizko DNSa ez dago zuzen konfiguratuta IPv{ipversion}an. Litekeena da posta elektroniko batzuk hartzaileak jaso ezin izatea edo spam modura etiketatuak izatea.", + "diagnosis_mail_fcrdns_different_from_ehlo_domain": "Alderantzizko DNSa ez dago zuzen konfiguratuta IPv{ipversion}an. Litekeena da hartzaileak posta elektroniko batzuk jaso ezin izatea edo mezuok spam modura etiketatuak izatea.", "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Oraingo alderantzizko DNSa: {rdns_domain}
Esperotako balioa: {ehlo_domain}", "diagnosis_mail_queue_too_big": "Mezu gehiegi posta elektronikoaren ilaran: ({nb_pending} mezu)", "diagnosis_ports_could_not_diagnose_details": "Errorea: {error}", @@ -467,7 +467,7 @@ "log_user_update": "Eguneratu '{}' erabiltzailearen informazioa", "dyndns_no_domain_registered": "Ez dago DynDNSrekin izena emandako domeinurik", "global_settings_bad_type_for_setting": "{setting} ezarpenerako mota okerra. {received_type} ezarri da, {expected_type} espero zen", - "diagnosis_mail_fcrdns_dns_missing": "Ez da alderantzizko DNSrik ezarri IPv{ipversion}rako. Litekeena da posta elektroniko batzuk hartzaileak jaso ezin izatea edo spam modura etiketatuak izatea.", + "diagnosis_mail_fcrdns_dns_missing": "Ez da alderantzizko DNSrik ezarri IPv{ipversion}rako. Litekeena da hartzaileak posta elektroniko batzuk jaso ezin izatea edo mezuok spam modura etiketatuak izatea.", "log_backup_create": "Sortu babeskopia fitxategia", "global_settings_setting_backup_compress_tar_archives": "Babeskopia berriak sortzean, konprimitu fitxategiak (.tar.gz) konprimitu gabeko fitxategien (.tar) ordez. Aukera hau gaitzean babeskopiek espazio gutxiago beharko dute, baina hasierako prozesua luzeagoa izango da eta CPUari lan handiagoa eragingo dio.", "global_settings_setting_security_webadmin_allowlist": "Administrazio-webgunea bisita ditzaketen IP helbideak, koma bidez bereiziak.", From 6ec12e92a6889ea30bd5e97c053710a3936bebe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quent=C3=AD?= Date: Thu, 4 Nov 2021 20:13:54 +0000 Subject: [PATCH 0899/1155] Translated using Weblate (Occitan) Currently translated at 50.0% (353 of 705 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/oc/ --- locales/oc.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/locales/oc.json b/locales/oc.json index a2a5bfe31..a794f5e04 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -78,8 +78,8 @@ "upnp_enabled": "UPnP es activat", "upnp_port_open_failed": "Impossible de dobrir los pòrts amb UPnP", "yunohost_already_installed": "YunoHost es ja installat", - "yunohost_configured": "YunoHost es estat configurat", - "yunohost_installing": "Installacion de YunoHost…", + "yunohost_configured": "YunoHost es ara 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} » 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.", @@ -144,7 +144,7 @@ "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 »", + "yunohost_not_installed": "YunoHost es pas 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} ! (Utilizatz --force per cortcircuitar)", "certmanager_cert_renew_success": "Renovèlament capitat d’un certificat Let’s Encrypt pel domeni « {domain} »", @@ -513,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 +} From 875677a8632375647654194068ace6153e91c6fa Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 5 Nov 2021 02:33:48 +0100 Subject: [PATCH 0900/1155] Update changelog for 4.3.2 --- debian/changelog | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/debian/changelog b/debian/changelog index a7d6c2445..88d29d377 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,12 @@ +yunohost (4.3.2) stable; urgency=low + + - Release as stable + - [i18n] Translations updated for Basque, Occitan + + Thanks to all contributors <3 ! (punkrockgirl, Quentí) + + -- Alexandre Aubin Fri, 05 Nov 2021 02:32:56 +0100 + yunohost (4.3.1.8) testing; urgency=low - [enh] dyndns: Drop some YAGNI + improve IPv6-only support + resilience w.r.t. ns0 / ns1 being down (a61d0231, [#1367](https://github.com/YunoHost/yunohost/pull/1367)) From 09d5f6ea0029c73a49175dcb9675d522248d24fd Mon Sep 17 00:00:00 2001 From: ericgaspar Date: Fri, 5 Nov 2021 12:07:16 +0100 Subject: [PATCH 0901/1155] 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 69e9ce445..e7e61b0c6 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -1,6 +1,6 @@ #!/bin/bash -n_version=7.5.0 +n_version=8.0.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. @@ -16,7 +16,7 @@ export N_PREFIX="$n_install_dir" ynh_install_n() { # Build an app.src for n echo "SOURCE_URL=https://github.com/tj/n/archive/v${n_version}.tar.gz -SOURCE_SUM=d4da7ea91f680de0c9b5876e097e2a793e8234fcd0f7ca87a0599b925be087a3" >"$YNH_APP_BASEDIR/conf/n.src" +SOURCE_SUM=9e8879dc4f1c4c0fe4e08a108ed6c23046419b6865fe922ca5176ff7998ae6ff" >"$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 3da2df6e292cd3efd2d708b512fd55cf7b42b8c0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 5 Nov 2021 16:34:33 +0100 Subject: [PATCH 0902/1155] mdns: Add possibility to manually add .local aliases via /etc/yunohost/mdns.aliases (meant for internetcube) --- data/hooks/conf_regen/37-mdns | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data/hooks/conf_regen/37-mdns b/data/hooks/conf_regen/37-mdns index 52213c297..f45356db9 100755 --- a/data/hooks/conf_regen/37-mdns +++ b/data/hooks/conf_regen/37-mdns @@ -11,6 +11,10 @@ _generate_config() { [[ "$domain" =~ ^[^.]+\.local$ ]] || continue echo " - $domain" done + for localalias in $(cat /etc/yunohost/mdns.aliases | grep -v "^ *$") + do + echo " - $localalias.local" + done } do_init_regen() { From 69b8603f148aee400660ec19398780826f99db20 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 5 Nov 2021 16:41:33 +0100 Subject: [PATCH 0903/1155] mdns: Aaaaand we don't want to crash if mdns.aliases doesnt exists obviously --- data/hooks/conf_regen/37-mdns | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/data/hooks/conf_regen/37-mdns b/data/hooks/conf_regen/37-mdns index f45356db9..faa581efa 100755 --- a/data/hooks/conf_regen/37-mdns +++ b/data/hooks/conf_regen/37-mdns @@ -11,10 +11,13 @@ _generate_config() { [[ "$domain" =~ ^[^.]+\.local$ ]] || continue echo " - $domain" done - for localalias in $(cat /etc/yunohost/mdns.aliases | grep -v "^ *$") - do - echo " - $localalias.local" - done + if [[ -e /etc/yunohot/mdns.aliases ]] + then + for localalias in $(cat /etc/yunohost/mdns.aliases | grep -v "^ *$") + do + echo " - $localalias.local" + done + fi } do_init_regen() { From 6558b23dd6fb2402edf9578b7c8419c8aef41ca8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 6 Nov 2021 15:54:34 +0100 Subject: [PATCH 0904/1155] Fix conflict with redis-server --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index fe18b1de8..f99fe25a9 100644 --- a/debian/control +++ b/debian/control @@ -45,7 +45,7 @@ Conflicts: iptables-persistent , openssl (>= 1.1.1g) , slapd (>= 2.4.49) , dovecot-core (>= 1:2.3.7) - , redis-server (>= 5:5.0.7) + , redis-server (>= 5:5.1) , fail2ban (>= 0.11) , iptables (>= 1.8.3) Description: manageable and configured self-hosting server From 1cc3e44038746455dc5008ad2cae7f81abe9bc0e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 6 Nov 2021 16:45:25 +0100 Subject: [PATCH 0905/1155] nginx: Refine experimental CSP header (in the end still gotta enable unsafe-inline and unsafe-eval for a bunch of things, but better than no policy at all...) --- 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 bcb821770..8d261daeb 100644 --- a/data/templates/nginx/security.conf.inc +++ b/data/templates/nginx/security.conf.inc @@ -26,11 +26,11 @@ ssl_dhparam /usr/share/yunohost/other/ffdhe2048.pem; # 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:"; +more_set_headers "Content-Security-Policy : upgrade-insecure-requests; default-src https: data: blob: ; object-src https: data: 'unsafe-inline'; style-src https: data: 'unsafe-inline' ; script-src https: data: 'unsafe-inline' 'unsafe-eval'"; {% else %} more_set_headers "Content-Security-Policy : upgrade-insecure-requests"; +more_set_headers "Content-Security-Policy-Report-Only : default-src https: data: blob: ; object-src https: data: 'unsafe-inline'; style-src https: data: 'unsafe-inline' ; script-src https: data: 'unsafe-inline' 'unsafe-eval'"; {% 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"; more_set_headers "X-Download-Options : noopen"; From e6effd49d80916a272f0961fc6a9dfcb6444d5d5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 6 Nov 2021 16:59:10 +0100 Subject: [PATCH 0906/1155] Update changelog for 4.3.2.1 --- debian/changelog | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/debian/changelog b/debian/changelog index 88d29d377..732e4af7b 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +yunohost (4.3.2.1) stable; urgency=low + + - [enh] mdns: Add possibility to manually add .local aliases via /etc/yunohost/mdns.aliases (meant for internetcube) (3da2df6e) + - [fix] debian: Fix conflict with redis-server (6558b23d) + - [fix] nginx: Refine experimental CSP header (in the end still gotta enable unsafe-inline and unsafe-eval for a bunch of things, but better than no policy at all...) (1cc3e440) + + -- Alexandre Aubin Sat, 06 Nov 2021 16:58:07 +0100 + yunohost (4.3.2) stable; urgency=low - Release as stable From 74e2a51e4daa0e2eca5f3c984e7822358935a4a1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 6 Nov 2021 17:39:46 +0100 Subject: [PATCH 0907/1155] nginx: Try to fix again the webadmin cache hell --- data/templates/nginx/yunohost_admin.conf.inc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/data/templates/nginx/yunohost_admin.conf.inc b/data/templates/nginx/yunohost_admin.conf.inc index 150fce6f6..b5eff7a5e 100644 --- a/data/templates/nginx/yunohost_admin.conf.inc +++ b/data/templates/nginx/yunohost_admin.conf.inc @@ -13,6 +13,12 @@ location /yunohost/admin/ { deny all; {% endif %} + location = /yunohost/admin/index.html { + etag off; + expires off; + more_set_headers "Cache-Control: no-store, no-cache, must-revalidate"; + } + 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 5a08206837c6ed593bc2cee0514aaf2ee32ea1d8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 6 Nov 2021 17:40:31 +0100 Subject: [PATCH 0908/1155] Update changelog for 4.3.2.2 --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index 732e4af7b..7adbe59dc 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +yunohost (4.3.2.2) stable; urgency=low + + - [fix] nginx: Try to fix again the webadmin cache hell (74e2a51e) + + -- Alexandre Aubin Sat, 06 Nov 2021 17:39:58 +0100 + yunohost (4.3.2.1) stable; urgency=low - [enh] mdns: Add possibility to manually add .local aliases via /etc/yunohost/mdns.aliases (meant for internetcube) (3da2df6e) From fff1a8a4d682ca8177e49bcb66420dd5e516e811 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 6 Nov 2021 18:58:31 +0100 Subject: [PATCH 0909/1155] [enh] Smarter API self-upgrade mechanics --- debian/postinst | 11 +++ debian/rules | 2 +- src/yunohost/tools.py | 169 ++++++++++-------------------------------- 3 files changed, 51 insertions(+), 131 deletions(-) diff --git a/debian/postinst b/debian/postinst index 0dd1dedd0..b49260ae5 100644 --- a/debian/postinst +++ b/debian/postinst @@ -33,6 +33,17 @@ do_configure() { yunohost diagnosis run --force fi + # Trick to let yunohost handle the restart of the API, + # to prevent the webadmin from cutting the branch it's sitting on + if systemctl is-enabled yunohost-api --quiet + then + if [[ "${YUNOHOST_API_RESTART_WILL_BE_HANDLED_BY_YUNOHOST:-}" != "yes" ]]; + then + systemctl restart yunohost-api + else + echo "(Delaying the restart of yunohost-api, this should automatically happen after the end of this upgrade)" + fi + fi } # summary of how this script can be called: diff --git a/debian/rules b/debian/rules index 3790c0ef2..015143610 100755 --- a/debian/rules +++ b/debian/rules @@ -13,7 +13,7 @@ override_dh_auto_build: python3 doc/generate_manpages.py --gzip --output doc/yunohost.8.gz override_dh_installinit: - dh_installinit -pyunohost --name=yunohost-api --restart-after-upgrade + dh_installinit -pyunohost --name=yunohost-api --noscripts dh_installinit -pyunohost --name=yunohost-firewall --noscripts override_dh_systemd_enable: diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index fb9839814..ef84d98bd 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -531,19 +531,10 @@ def tools_upgrade( logger.info(m18n.n("upgrading_packages")) operation_logger.start() - # Critical packages are packages that we can't just upgrade - # randomly from yunohost itself... upgrading them is likely to - critical_packages = ["moulinette", "yunohost", "yunohost-admin", "ssowat"] - - 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" + if Moulinette.interface.type == "api": + dist_upgrade += "YUNOHOST_API_RESTART_WILL_BE_HANDLED_BY_YUNOHOST=yes" dist_upgrade += " APT_LISTCHANGES_FRONTEND=none" dist_upgrade += " apt-get" dist_upgrade += ( @@ -553,136 +544,54 @@ def tools_upgrade( dist_upgrade += ' -o Dpkg::Options::="--force-conf{}"'.format(conf_flag) dist_upgrade += " dist-upgrade" - # - # "Regular" packages upgrade - # - if noncritical_packages_upgradable: + logger.info(m18n.n("tools_upgrade")) - logger.info(m18n.n("tools_upgrade_regular_packages")) + logger.debug("Running apt command :\n{}".format(dist_upgrade)) - # Mark all critical packages as held - for package in critical_packages: - check_output("apt-mark hold %s" % package) + def is_relevant(line): + irrelevants = [ + "service sudo-ldap already provided", + "Reading database ...", + ] + return all(i not in line.rstrip() for i in irrelevants) - # Doublecheck with apt-mark showhold that packages are indeed held ... - held_packages = check_output("apt-mark showhold").split("\n") - if any(p not in held_packages for p in critical_packages): - logger.warning(m18n.n("tools_upgrade_cant_hold_critical_packages")) - operation_logger.error(m18n.n("packages_upgrade_failed")) - raise YunohostError(m18n.n("packages_upgrade_failed")) + callbacks = ( + lambda l: logger.info("+ " + l.rstrip() + "\r") + if is_relevant(l) + else logger.debug(l.rstrip() + "\r"), + lambda l: logger.warning(l.rstrip()) + if is_relevant(l) + else logger.debug(l.rstrip()), + ) + returncode = call_async_output(dist_upgrade, callbacks, shell=True) - logger.debug("Running apt command :\n{}".format(dist_upgrade)) + # If yunohost is being upgraded from the webadmin + if "yunohost" in upgradables and Moulinette.interface.type == "api": - def is_relevant(line): - irrelevants = [ - "service sudo-ldap already provided", - "Reading database ...", - ] - return all(i not in line.rstrip() for i in irrelevants) - - callbacks = ( - lambda l: logger.info("+ " + l.rstrip() + "\r") - if is_relevant(l) - else logger.debug(l.rstrip() + "\r"), - lambda l: logger.warning(l.rstrip()) - if is_relevant(l) - else logger.debug(l.rstrip()), + # Restart the API after 10 sec (at now doesn't support sub-minute times...) + # We do this so that the API / webadmin still gets the proper HTTP response + # It's then up to the webadmin to implement a proper UX process to wait 10 sec and then auto-fresh the webadmin + cmd = ( + "at -M now >/dev/null 2>&1 <<< \"sleep 10; systemctl restart yunohost-api\"" ) - 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")) - raise YunohostError(m18n.n("packages_upgrade_failed")) + # For some reason subprocess doesn't like the redirections so we have to use bash -c explicity... + subprocess.check_call(["bash", "-c", cmd]) - # - # Critical packages upgrade - # - if critical_packages_upgradable and allow_yunohost_upgrade: - - logger.info(m18n.n("tools_upgrade_special_packages")) - - # Mark all critical packages as unheld - for package in critical_packages: - check_output("apt-mark unhold %s" % package) - - # Doublecheck with apt-mark showhold that packages are indeed unheld ... - held_packages = check_output("apt-mark showhold").split("\n") - if any(p in held_packages for p in critical_packages): - logger.warning(m18n.n("tools_upgrade_cant_unhold_critical_packages")) - operation_logger.error(m18n.n("packages_upgrade_failed")) - raise YunohostError(m18n.n("packages_upgrade_failed")) - - # - # Here we use a dirty hack to run a command after the current - # "yunohost tools upgrade", because the upgrade of yunohost - # will also trigger other yunohost commands (e.g. "yunohost tools migrations run") - # (also the upgrade of the package, if executed from the webadmin, is - # likely to kill/restart the api which is in turn likely to kill this - # command before it ends...) - # - logfile = operation_logger.log_path - dist_upgrade = dist_upgrade + " 2>&1 | tee -a {}".format(logfile) - - MOULINETTE_LOCK = "/var/run/moulinette_yunohost.lock" - wait_until_end_of_yunohost_command = ( - "(while [ -f {} ]; do sleep 2; done)".format(MOULINETTE_LOCK) - ) - mark_success = ( - "(echo 'Done!' | tee -a {} && echo 'success: true' >> {})".format( - logfile, operation_logger.md_path + if returncode != 0: + upgradables = list(_list_upgradable_apt_packages()) + logger.warning( + m18n.n( + "tools_upgrade_failed", + packages_list=", ".join(upgradables), ) ) - mark_failure = ( - "(echo 'Failed :(' | tee -a {} && echo 'success: false' >> {})".format( - logfile, operation_logger.md_path - ) - ) - update_log_metadata = "sed -i \"s/ended_at: .*$/ended_at: $(date -u +'%Y-%m-%d %H:%M:%S.%N')/\" {}" - update_log_metadata = update_log_metadata.format(operation_logger.md_path) + operation_logger.error(m18n.n("packages_upgrade_failed")) + raise YunohostError(m18n.n("packages_upgrade_failed")) - # Dirty hack such that the operation_logger does not add ended_at - # and success keys in the log metadata. (c.f. the code of the - # is_unit_operation + operation_logger.close()) We take care of - # this ourselves (c.f. the mark_success and updated_log_metadata in - # the huge command launched by os.system) - operation_logger.ended_at = "notyet" + # FIXME : add a dpkg --audit / check dpkg is broken here ? - upgrade_completed = "\n" + m18n.n( - "tools_upgrade_special_packages_completed" - ) - command = "({wait} && {dist_upgrade}) && {mark_success} || {mark_failure}; {update_metadata}; echo '{done}'".format( - wait=wait_until_end_of_yunohost_command, - dist_upgrade=dist_upgrade, - mark_success=mark_success, - mark_failure=mark_failure, - update_metadata=update_log_metadata, - done=upgrade_completed, - ) - - logger.warning(m18n.n("tools_upgrade_special_packages_explanation")) - logger.debug("Running command :\n{}".format(command)) - open("/tmp/yunohost-selfupgrade", "w").write( - "rm /tmp/yunohost-selfupgrade; " + command - ) - # Using systemd-run --scope is like nohup/disown and &, but more robust somehow - # (despite using nohup/disown and &, the self-upgrade process was still getting killed...) - # ref: https://unix.stackexchange.com/questions/420594/why-process-killed-with-nohup - # (though I still don't understand it 100%...) - os.system("systemd-run --scope bash /tmp/yunohost-selfupgrade &") - return - - else: - logger.success(m18n.n("system_upgraded")) - operation_logger.success() + logger.success(m18n.n("system_upgraded")) + operation_logger.success() @is_unit_operation() From b166abab1db9157b2b730ec9291daa881d661888 Mon Sep 17 00:00:00 2001 From: Romain Thouvenin Date: Sun, 7 Nov 2021 21:13:34 +0000 Subject: [PATCH 0910/1155] Read DNS Blacklist answer and compare it against list of non-BL codes --- data/hooks/diagnosis/24-mail.py | 4 ++-- data/other/dnsbl_list.yml | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/data/hooks/diagnosis/24-mail.py b/data/hooks/diagnosis/24-mail.py index c5af4bbc6..7b7f5f502 100644 --- a/data/hooks/diagnosis/24-mail.py +++ b/data/hooks/diagnosis/24-mail.py @@ -209,8 +209,8 @@ class MailDiagnoser(Diagnoser): query = subdomain + "." + blacklist["dns_server"] # Do the DNS Query - status, _ = dig(query, "A") - if status != "ok": + status, answers = dig(query, "A") + if status != "ok" or (answers and set(answers) <= set(blacklist["non_blacklisted_return_code"])): continue # Try to get the reason diff --git a/data/other/dnsbl_list.yml b/data/other/dnsbl_list.yml index 1dc0175a3..ad86fd2a6 100644 --- a/data/other/dnsbl_list.yml +++ b/data/other/dnsbl_list.yml @@ -5,138 +5,161 @@ ipv4: true ipv6: true domain: false + non_blacklisted_return_code: [] - name: Barracuda Reputation Block List dns_server: b.barracudacentral.org website: https://barracudacentral.org/rbl/ ipv4: true ipv6: false domain: false + non_blacklisted_return_code: [] - name: Hostkarma dns_server: hostkarma.junkemailfilter.com website: https://ipadmin.junkemailfilter.com/remove.php ipv4: true ipv6: false domain: false + non_blacklisted_return_code: ['127.0.0.1', '127.0.0.5'] - name: ImproWare IP based spamlist dns_server: spamrbl.imp.ch website: https://antispam.imp.ch/ ipv4: true ipv6: false domain: false + non_blacklisted_return_code: [] - name: ImproWare IP based wormlist dns_server: wormrbl.imp.ch website: https://antispam.imp.ch/ ipv4: true ipv6: false domain: false + non_blacklisted_return_code: [] - name: Backscatterer.org dns_server: ips.backscatterer.org website: http://www.backscatterer.org/ ipv4: true ipv6: false domain: false + non_blacklisted_return_code: [] - name: inps.de dns_server: dnsbl.inps.de website: http://dnsbl.inps.de/ ipv4: true ipv6: false domain: false + non_blacklisted_return_code: [] - name: LASHBACK dns_server: ubl.unsubscore.com website: https://blacklist.lashback.com/ ipv4: true ipv6: false domain: false + non_blacklisted_return_code: [] - name: Mailspike.org dns_server: bl.mailspike.net website: http://www.mailspike.net/ ipv4: true ipv6: false domain: false + non_blacklisted_return_code: [] - name: NiX Spam dns_server: ix.dnsbl.manitu.net website: http://www.dnsbl.manitu.net/ ipv4: true ipv6: false domain: false + non_blacklisted_return_code: [] - name: REDHAWK dns_server: access.redhawk.org website: https://www.redhawk.org/SpamHawk/query.php ipv4: true ipv6: false domain: false + non_blacklisted_return_code: [] - name: SORBS Open SMTP relays dns_server: smtp.dnsbl.sorbs.net website: http://www.sorbs.net/ ipv4: true ipv6: false domain: false + non_blacklisted_return_code: [] - name: SORBS Spamhost (last 28 days) dns_server: recent.spam.dnsbl.sorbs.net website: http://www.sorbs.net/ ipv4: true ipv6: false domain: false + non_blacklisted_return_code: [] - name: SORBS Spamhost (last 48 hours) dns_server: new.spam.dnsbl.sorbs.net website: http://www.sorbs.net/ ipv4: true ipv6: false domain: false + non_blacklisted_return_code: [] - name: SpamCop Blocking List dns_server: bl.spamcop.net website: https://www.spamcop.net/bl.shtml ipv4: true ipv6: false domain: false + non_blacklisted_return_code: [] - name: Spam Eating Monkey SEM-BACKSCATTER dns_server: backscatter.spameatingmonkey.net website: https://spameatingmonkey.com/services ipv4: true ipv6: false domain: false + non_blacklisted_return_code: [] - name: Spam Eating Monkey SEM-BLACK dns_server: bl.spameatingmonkey.net website: https://spameatingmonkey.com/services ipv4: true ipv6: false domain: false + non_blacklisted_return_code: [] - name: Spam Eating Monkey SEM-IPV6BL dns_server: bl.ipv6.spameatingmonkey.net website: https://spameatingmonkey.com/services ipv4: false ipv6: true domain: false + non_blacklisted_return_code: [] - name: SpamRATS! all dns_server: all.spamrats.com website: http://www.spamrats.com/ ipv4: true ipv6: false domain: false + non_blacklisted_return_code: [] - name: PSBL (Passive Spam Block List) dns_server: psbl.surriel.com website: http://psbl.surriel.com/ ipv4: true ipv6: false domain: false + non_blacklisted_return_code: [] - name: SWINOG dns_server: dnsrbl.swinog.ch website: https://antispam.imp.ch/ ipv4: true ipv6: false domain: false + non_blacklisted_return_code: [] - name: GBUdb Truncate dns_server: truncate.gbudb.net website: http://www.gbudb.com/truncate/index.jsp ipv4: true ipv6: false domain: false + non_blacklisted_return_code: [] - name: Weighted Private Block List dns_server: db.wpbl.info website: http://www.wpbl.info/ ipv4: true ipv6: false domain: false + non_blacklisted_return_code: [] # Used by GAFAM - name: Composite Blocking List dns_server: cbl.abuseat.org @@ -144,6 +167,7 @@ ipv4: true ipv6: false domain: false + non_blacklisted_return_code: [] # Used by GAFAM - name: SenderScore Blacklist dns_server: bl.score.senderscore.com @@ -152,18 +176,21 @@ ipv6: false domain: false # Added cause it supports IPv6 + non_blacklisted_return_code: [] - name: AntiCaptcha.NET IPv6 dns_server: dnsbl6.anticaptcha.net website: http://anticaptcha.net/ ipv4: false ipv6: true domain: false + non_blacklisted_return_code: [] - name: Suomispam Blacklist dns_server: bl.suomispam.net website: http://suomispam.net/ ipv4: true ipv6: true domain: false + non_blacklisted_return_code: [] - name: NordSpam dns_server: bl.nordspam.com website: https://www.nordspam.com/ From 0a5bf3d021680def73a024a211501e07b4fc5c9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pi=C3=A9dallu?= Date: Mon, 8 Nov 2021 10:01:38 +0100 Subject: [PATCH 0911/1155] Fix dump_script_log_extract_for_debugging : We want to catch the last exit in the logs, not the first one. --- src/yunohost/log.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index d73a62cd0..b4f8e181b 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -753,8 +753,16 @@ class OperationLogger(object): filters = [re.compile(f_) for f_ in filters] lines_to_display = [] - for line in lines: + # The logs may contain multiple ynh_exit_properly (backup + upgrade e.g) + # We want to get the last one. + rev_lines = reversed(lines) + for line in rev_lines: + if line.endswith("+ ynh_exit_properly") or " + ynh_die " in line: + lines_to_display.append(line) + break + + for line in rev_lines: if ": " not in line.strip(): continue @@ -768,10 +776,10 @@ class OperationLogger(object): lines_to_display.append(line) - if line.endswith("+ ynh_exit_properly") or " + ynh_die " in line: + if len(lines_to_display) > 20: break - elif len(lines_to_display) > 20: - lines_to_display.pop(0) + + lines_to_display.reverse() logger.warning( "Here's an extract of the logs before the crash. It might help debugging the error:" From a9b0619f426724d3e3d43a7cbe11b73c41bfbb59 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 8 Nov 2021 12:57:28 +0100 Subject: [PATCH 0912/1155] log: Factorize boring/irrelevant log line filter definition --- src/yunohost/log.py | 65 +++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 38 deletions(-) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index b4f8e181b..e2f6a7510 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -42,12 +42,36 @@ from yunohost.utils.packages import get_ynh_package_version from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import read_file, read_yaml +logger = getActionLogger("yunohost.log") + CATEGORIES_PATH = "/var/log/yunohost/categories/" OPERATIONS_PATH = "/var/log/yunohost/categories/operation/" METADATA_FILE_EXT = ".yml" LOG_FILE_EXT = ".log" -logger = getActionLogger("yunohost.log") +BORING_LOG_LINES = [ + 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)$", +] def log_list(limit=None, with_details=False, with_suboperations=False): @@ -163,30 +187,7 @@ def log_show( if filter_irrelevant: 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] + filters = [re.compile(f) for f in BORING_LOG_LINES] return [ line for line in lines @@ -738,19 +739,7 @@ class OperationLogger(object): 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] + filters = [re.compile(f_) for f_ in BORING_LOG_LINES] lines_to_display = [] From 03065dda4ea699c63df4a938226907850322dc45 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 8 Nov 2021 13:36:06 +0100 Subject: [PATCH 0913/1155] log: Simplify code to fetch lines before ynh_exit_properly in dump_script_log_extract_for_debugging --- src/yunohost/log.py | 43 +++++++++++++++++++------------------------ 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index e2f6a7510..347fe2e65 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -739,36 +739,31 @@ class OperationLogger(object): with open(self.log_path, "r") as f: lines = f.readlines() + # 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 - " + lines = [line for line in lines if ":" in line.strip()] + lines = [line.strip().split(": ", 1)[1] for line in lines] + # And we ignore boring/irrelevant lines + # Annnnnnd we also ignore lines matching [number] + such as + # 72971 DEBUG 29739 + ynh_exit_properly + # which are lines from backup-before-upgrade or restore-after-failed-upgrade ... filters = [re.compile(f_) for f_ in BORING_LOG_LINES] + filters.append(re.compile(r'\d+ \+ ')) + lines = [line for line in lines if not any(filter_.search(line) for filter_ in filters)] lines_to_display = [] - # The logs may contain multiple ynh_exit_properly (backup + upgrade e.g) - # We want to get the last one. - rev_lines = reversed(lines) - for line in rev_lines: - if line.endswith("+ ynh_exit_properly") or " + ynh_die " in line: - lines_to_display.append(line) + # Get the 20 lines before the last 'ynh_exit_properly' + rev_lines = list(reversed(lines)) + for i, line in enumerate(rev_lines): + if line.endswith("+ ynh_exit_properly"): + lines_to_display = reversed(rev_lines[i:i + 20]) break - for line in rev_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 len(lines_to_display) > 20: - break - - lines_to_display.reverse() + # If didnt find anything, just get the last 20 lines + if not lines_to_display: + lines_to_display = lines[-20:] logger.warning( "Here's an extract of the logs before the crash. It might help debugging the error:" From 7c569d16b787c52f36f4d07b2d5b31c06d29b778 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 8 Nov 2021 18:13:08 +0100 Subject: [PATCH 0914/1155] certificate: fix stupid certificate/diagnosis issue with subdomains of ynh domains --- src/yunohost/certificate.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index fe350bf95..2679b2429 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -853,6 +853,7 @@ def _check_domain_is_ready_for_ACME(domain): from yunohost.domain import _get_parent_domain_of from yunohost.dns import _get_dns_zone_for_domain + from yunohost.utils.dns import is_yunohost_dyndns_domain httpreachable = ( Diagnoser.get_cached_report( @@ -876,6 +877,15 @@ def _check_domain_is_ready_for_ACME(domain): record_name = ( domain.replace(f".{base_dns_zone}", "") if domain != base_dns_zone else "@" ) + + # Stupid edge case for subdomains of ynh dyndns domains ... + # ... related to the fact that we don't actually check subdomains for + # dyndns domains because we assume that there's already the wildcard doing + # the job, hence no "A:foobar" ... Instead, just check that the parent domain + # is correctly configured. + if is_yunohost_dyndns_domain(parent_domain): + record_name = "@" + A_record_status = dnsrecords.get("data").get(f"A:{record_name}") AAAA_record_status = dnsrecords.get("data").get(f"AAAA:{record_name}") From 5de9e4fe6a2da619d1def5e91bd3ee30b53faddd Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 10 Nov 2021 17:56:02 +0100 Subject: [PATCH 0915/1155] Yolorework the repo dir structure --- {data/templates => conf}/dnsmasq/domain.tpl | 0 {data/templates => conf}/dnsmasq/plain/dnsmasq.conf | 0 {data/templates => conf}/dnsmasq/plain/etcdefault | 0 .../dnsmasq/plain/resolv.dnsmasq.conf | 0 {data/templates => conf}/dovecot/dovecot-ldap.conf | 0 {data/templates => conf}/dovecot/dovecot.conf | 0 {data/templates => conf}/dovecot/dovecot.sieve | 0 {data/templates => conf}/dovecot/post-ext.conf | 0 {data/templates => conf}/dovecot/pre-ext.conf | 0 {data/templates => conf}/fail2ban/jail.conf | 0 .../templates => conf}/fail2ban/yunohost-jails.conf | 0 {data/templates => conf}/fail2ban/yunohost.conf | 0 {data/templates => conf}/mdns/yunomdns.service | 0 .../templates => conf}/metronome/domain.tpl.cfg.lua | 0 .../templates => conf}/metronome/metronome.cfg.lua | 0 {lib => conf}/metronome/modules/ldap.lib.lua | 0 {lib => conf}/metronome/modules/mod_auth_ldap2.lua | 0 {lib => conf}/metronome/modules/mod_legacyauth.lua | 0 .../metronome/modules/mod_storage_ldap.lua | 0 {lib => conf}/metronome/modules/vcard.lib.lua | 0 {data/templates => conf}/nginx/autoconfig.tpl.xml | 0 .../nginx/plain/acme-challenge.conf.inc | 0 {data/templates => conf}/nginx/plain/global.conf | 0 {data/templates => conf}/nginx/plain/ssowat.conf | 0 .../nginx/plain/yunohost_panel.conf.inc | 0 .../nginx/plain/yunohost_sso.conf.inc | 0 .../templates => conf}/nginx/redirect_to_admin.conf | 0 {data/templates => conf}/nginx/security.conf.inc | 0 {data/templates => conf}/nginx/server.tpl.conf | 0 {data/templates => conf}/nginx/yunohost_admin.conf | 0 .../nginx/yunohost_admin.conf.inc | 0 .../templates => conf}/nginx/yunohost_api.conf.inc | 0 {data/templates => conf}/nslcd/nslcd.conf | 0 {data/templates => conf}/nsswitch/nsswitch.conf | 0 {data/templates => conf}/postfix/main.cf | 0 .../templates => conf}/postfix/plain/header_checks | 0 .../postfix/plain/ldap-accounts.cf | 0 .../postfix/plain/ldap-aliases.cf | 0 .../postfix/plain/ldap-domains.cf | 0 {data/templates => conf}/postfix/plain/master.cf | 0 .../postfix/plain/sender_canonical | 0 .../postfix/plain/smtp_reply_filter | 0 {data/templates => conf}/postfix/postsrsd | 0 {data/templates => conf}/rspamd/dkim_signing.conf | 0 {data/templates => conf}/rspamd/metrics.local.conf | 0 {data/templates => conf}/rspamd/milter_headers.conf | 0 {data/templates => conf}/rspamd/rspamd.sieve | 0 {data/templates => conf}/slapd/config.ldif | 0 {data/templates => conf}/slapd/db_init.ldif | 0 {data/templates => conf}/slapd/ldap.conf | 0 {data/templates => conf}/slapd/mailserver.ldif | 0 {data/templates => conf}/slapd/permission.ldif | 0 {data/templates => conf}/slapd/slapd.default | 0 {data/templates => conf}/slapd/sudo.ldif | 0 .../templates => conf}/slapd/systemd-override.conf | 0 {data/templates => conf}/ssh/sshd_config | 0 {data/templates => conf}/ssl/openssl.cnf | 0 {data/templates => conf}/yunohost/dpkg-origins | 0 {data/templates => conf}/yunohost/firewall.yml | 0 .../yunohost/proc-hidepid.service | 0 {data/templates => conf}/yunohost/services.yml | 0 .../templates => conf}/yunohost/yunoprompt.service | 0 .../generate_bash_completion.py | 0 {data/helpers.d => helpers}/apt | 0 {data/helpers.d => helpers}/backup | 0 {data/helpers.d => helpers}/config | 0 {data/helpers.d => helpers}/fail2ban | 0 {data/helpers.d => helpers}/getopts | 0 {data/helpers.d => helpers}/hardware | 0 {data/helpers.d => helpers}/logging | 0 {data/helpers.d => helpers}/logrotate | 0 {data/helpers.d => helpers}/multimedia | 0 {data/helpers.d => helpers}/mysql | 0 {data/helpers.d => helpers}/network | 0 {data/helpers.d => helpers}/nginx | 0 {data/helpers.d => helpers}/nodejs | 0 {data/helpers.d => helpers}/permission | 0 {data/helpers.d => helpers}/php | 0 {data/helpers.d => helpers}/postgresql | 0 {data/helpers.d => helpers}/setting | 0 {data/helpers.d => helpers}/string | 0 {data/helpers.d => helpers}/systemd | 0 {data/helpers.d => helpers}/user | 0 {data/helpers.d => helpers}/utils | 0 {data/hooks => hooks}/backup/05-conf_ldap | 0 {data/hooks => hooks}/backup/17-data_home | 0 {data/hooks => hooks}/backup/18-data_multimedia | 0 {data/hooks => hooks}/backup/20-conf_ynh_settings | 0 {data/hooks => hooks}/backup/21-conf_ynh_certs | 0 {data/hooks => hooks}/backup/23-data_mail | 0 {data/hooks => hooks}/backup/27-data_xmpp | 0 .../backup/50-conf_manually_modified_files | 0 {data/hooks => hooks}/conf_regen/01-yunohost | 0 {data/hooks => hooks}/conf_regen/02-ssl | 0 {data/hooks => hooks}/conf_regen/03-ssh | 0 {data/hooks => hooks}/conf_regen/06-slapd | 0 {data/hooks => hooks}/conf_regen/09-nslcd | 0 {data/hooks => hooks}/conf_regen/10-apt | 0 {data/hooks => hooks}/conf_regen/12-metronome | 0 {data/hooks => hooks}/conf_regen/15-nginx | 0 {data/hooks => hooks}/conf_regen/19-postfix | 0 {data/hooks => hooks}/conf_regen/25-dovecot | 0 {data/hooks => hooks}/conf_regen/31-rspamd | 0 {data/hooks => hooks}/conf_regen/34-mysql | 0 {data/hooks => hooks}/conf_regen/35-redis | 0 {data/hooks => hooks}/conf_regen/37-mdns | 0 {data/hooks => hooks}/conf_regen/43-dnsmasq | 0 {data/hooks => hooks}/conf_regen/46-nsswitch | 0 {data/hooks => hooks}/conf_regen/52-fail2ban | 0 .../hooks => hooks}/post_user_create/ynh_multimedia | 0 .../hooks => hooks}/post_user_delete/ynh_multimedia | 0 {data/hooks => hooks}/restore/05-conf_ldap | 0 {data/hooks => hooks}/restore/17-data_home | 0 {data/hooks => hooks}/restore/18-data_multimedia | 0 {data/hooks => hooks}/restore/20-conf_ynh_settings | 0 {data/hooks => hooks}/restore/21-conf_ynh_certs | 0 {data/hooks => hooks}/restore/23-data_mail | 0 {data/hooks => hooks}/restore/27-data_xmpp | 0 .../restore/50-conf_manually_modified_files | 0 .../100000-most-used-passwords.txt.gz | Bin .../actionsmap/yunohost.yml => share/actionsmap.yml | 0 {data/other => share}/config_domain.toml | 0 {data/other => share}/dnsbl_list.yml | 0 {data/other => share}/ffdhe2048.pem | 0 {data => share}/helpers | 0 {data/other => share}/registrar_list.toml | 0 src/{yunohost => }/__init__.py | 0 src/{yunohost => }/app.py | 0 src/{yunohost => }/app_catalog.py | 0 src/{yunohost => }/authenticators/ldap_admin.py | 0 src/{yunohost => }/backup.py | 0 src/{yunohost => }/certificate.py | 0 .../data_migrations/0021_migrate_to_bullseye.py | 0 .../data_migrations/0022_php73_to_php74_pools.py | 0 .../data_migrations/0023_postgresql_11_to_13.py | 0 src/{yunohost => }/data_migrations/__init__.py | 0 src/{yunohost => }/diagnosis.py | 0 {data/hooks => src}/diagnosis/00-basesystem.py | 0 {data/hooks => src}/diagnosis/10-ip.py | 0 {data/hooks => src}/diagnosis/12-dnsrecords.py | 0 {data/hooks => src}/diagnosis/14-ports.py | 0 {data/hooks => src}/diagnosis/21-web.py | 0 {data/hooks => src}/diagnosis/24-mail.py | 0 {data/hooks => src}/diagnosis/30-services.py | 0 {data/hooks => src}/diagnosis/50-systemresources.py | 0 {data/hooks => src}/diagnosis/70-regenconf.py | 0 {data/hooks => src}/diagnosis/80-apps.py | 0 src/{yunohost => }/dns.py | 0 src/{yunohost => }/domain.py | 0 src/{yunohost => }/dyndns.py | 0 src/{yunohost => }/firewall.py | 0 src/{yunohost => }/hook.py | 0 src/{yunohost => }/log.py | 0 src/{yunohost => }/permission.py | 0 src/{yunohost => }/regenconf.py | 0 src/{yunohost => }/service.py | 0 src/{yunohost => }/settings.py | 0 src/{yunohost => }/ssh.py | 0 src/{yunohost => }/tests/__init__.py | 0 src/{yunohost => }/tests/conftest.py | 0 src/{yunohost => }/tests/test_app_catalog.py | 0 src/{yunohost => }/tests/test_app_config.py | 0 src/{yunohost => }/tests/test_apps.py | 0 src/{yunohost => }/tests/test_appurl.py | 0 src/{yunohost => }/tests/test_backuprestore.py | 0 src/{yunohost => }/tests/test_changeurl.py | 0 src/{yunohost => }/tests/test_dns.py | 0 src/{yunohost => }/tests/test_domains.py | 0 src/{yunohost => }/tests/test_ldapauth.py | 0 src/{yunohost => }/tests/test_permission.py | 0 src/{yunohost => }/tests/test_questions.py | 0 src/{yunohost => }/tests/test_regenconf.py | 0 src/{yunohost => }/tests/test_service.py | 0 src/{yunohost => }/tests/test_settings.py | 0 src/{yunohost => }/tests/test_user-group.py | 0 src/{yunohost => }/tools.py | 0 src/{yunohost => }/user.py | 0 src/{yunohost => }/utils/__init__.py | 0 src/{yunohost => }/utils/config.py | 0 src/{yunohost => }/utils/dns.py | 0 src/{yunohost => }/utils/error.py | 0 src/{yunohost => }/utils/filesystem.py | 0 src/{yunohost => }/utils/i18n.py | 0 src/{yunohost => }/utils/ldap.py | 0 src/{yunohost => }/utils/legacy.py | 0 src/{yunohost => }/utils/network.py | 0 src/{yunohost => }/utils/packages.py | 0 src/{yunohost => }/utils/password.py | 0 src/{yunohost => }/utils/yunopaste.py | 0 src/{yunohost => }/vendor/__init__.py | 0 src/{yunohost => }/vendor/acme_tiny/__init__.py | 0 src/{yunohost => }/vendor/acme_tiny/acme_tiny.py | 0 .../vendor/spectre-meltdown-checker/Dockerfile | 0 .../vendor/spectre-meltdown-checker/LICENSE | 0 .../vendor/spectre-meltdown-checker/README.md | 0 .../spectre-meltdown-checker/docker-compose.yml | 0 .../spectre-meltdown-checker.sh | 0 197 files changed, 0 insertions(+), 0 deletions(-) rename {data/templates => conf}/dnsmasq/domain.tpl (100%) rename {data/templates => conf}/dnsmasq/plain/dnsmasq.conf (100%) rename {data/templates => conf}/dnsmasq/plain/etcdefault (100%) rename {data/templates => conf}/dnsmasq/plain/resolv.dnsmasq.conf (100%) rename {data/templates => conf}/dovecot/dovecot-ldap.conf (100%) rename {data/templates => conf}/dovecot/dovecot.conf (100%) rename {data/templates => conf}/dovecot/dovecot.sieve (100%) rename {data/templates => conf}/dovecot/post-ext.conf (100%) rename {data/templates => conf}/dovecot/pre-ext.conf (100%) rename {data/templates => conf}/fail2ban/jail.conf (100%) rename {data/templates => conf}/fail2ban/yunohost-jails.conf (100%) rename {data/templates => conf}/fail2ban/yunohost.conf (100%) rename {data/templates => conf}/mdns/yunomdns.service (100%) rename {data/templates => conf}/metronome/domain.tpl.cfg.lua (100%) rename {data/templates => conf}/metronome/metronome.cfg.lua (100%) rename {lib => conf}/metronome/modules/ldap.lib.lua (100%) rename {lib => conf}/metronome/modules/mod_auth_ldap2.lua (100%) rename {lib => conf}/metronome/modules/mod_legacyauth.lua (100%) rename {lib => conf}/metronome/modules/mod_storage_ldap.lua (100%) rename {lib => conf}/metronome/modules/vcard.lib.lua (100%) rename {data/templates => conf}/nginx/autoconfig.tpl.xml (100%) rename {data/templates => conf}/nginx/plain/acme-challenge.conf.inc (100%) rename {data/templates => conf}/nginx/plain/global.conf (100%) rename {data/templates => conf}/nginx/plain/ssowat.conf (100%) rename {data/templates => conf}/nginx/plain/yunohost_panel.conf.inc (100%) rename {data/templates => conf}/nginx/plain/yunohost_sso.conf.inc (100%) rename {data/templates => conf}/nginx/redirect_to_admin.conf (100%) rename {data/templates => conf}/nginx/security.conf.inc (100%) rename {data/templates => conf}/nginx/server.tpl.conf (100%) rename {data/templates => conf}/nginx/yunohost_admin.conf (100%) rename {data/templates => conf}/nginx/yunohost_admin.conf.inc (100%) rename {data/templates => conf}/nginx/yunohost_api.conf.inc (100%) rename {data/templates => conf}/nslcd/nslcd.conf (100%) rename {data/templates => conf}/nsswitch/nsswitch.conf (100%) rename {data/templates => conf}/postfix/main.cf (100%) rename {data/templates => conf}/postfix/plain/header_checks (100%) rename {data/templates => conf}/postfix/plain/ldap-accounts.cf (100%) rename {data/templates => conf}/postfix/plain/ldap-aliases.cf (100%) rename {data/templates => conf}/postfix/plain/ldap-domains.cf (100%) rename {data/templates => conf}/postfix/plain/master.cf (100%) rename {data/templates => conf}/postfix/plain/sender_canonical (100%) rename {data/templates => conf}/postfix/plain/smtp_reply_filter (100%) rename {data/templates => conf}/postfix/postsrsd (100%) rename {data/templates => conf}/rspamd/dkim_signing.conf (100%) rename {data/templates => conf}/rspamd/metrics.local.conf (100%) rename {data/templates => conf}/rspamd/milter_headers.conf (100%) rename {data/templates => conf}/rspamd/rspamd.sieve (100%) rename {data/templates => conf}/slapd/config.ldif (100%) rename {data/templates => conf}/slapd/db_init.ldif (100%) rename {data/templates => conf}/slapd/ldap.conf (100%) rename {data/templates => conf}/slapd/mailserver.ldif (100%) rename {data/templates => conf}/slapd/permission.ldif (100%) rename {data/templates => conf}/slapd/slapd.default (100%) rename {data/templates => conf}/slapd/sudo.ldif (100%) rename {data/templates => conf}/slapd/systemd-override.conf (100%) rename {data/templates => conf}/ssh/sshd_config (100%) rename {data/templates => conf}/ssl/openssl.cnf (100%) rename {data/templates => conf}/yunohost/dpkg-origins (100%) rename {data/templates => conf}/yunohost/firewall.yml (100%) rename {data/templates => conf}/yunohost/proc-hidepid.service (100%) rename {data/templates => conf}/yunohost/services.yml (100%) rename {data/templates => conf}/yunohost/yunoprompt.service (100%) rename data/actionsmap/yunohost_completion.py => doc/generate_bash_completion.py (100%) rename {data/helpers.d => helpers}/apt (100%) rename {data/helpers.d => helpers}/backup (100%) rename {data/helpers.d => helpers}/config (100%) rename {data/helpers.d => helpers}/fail2ban (100%) rename {data/helpers.d => helpers}/getopts (100%) rename {data/helpers.d => helpers}/hardware (100%) rename {data/helpers.d => helpers}/logging (100%) rename {data/helpers.d => helpers}/logrotate (100%) rename {data/helpers.d => helpers}/multimedia (100%) rename {data/helpers.d => helpers}/mysql (100%) rename {data/helpers.d => helpers}/network (100%) rename {data/helpers.d => helpers}/nginx (100%) rename {data/helpers.d => helpers}/nodejs (100%) rename {data/helpers.d => helpers}/permission (100%) rename {data/helpers.d => helpers}/php (100%) rename {data/helpers.d => helpers}/postgresql (100%) rename {data/helpers.d => helpers}/setting (100%) rename {data/helpers.d => helpers}/string (100%) rename {data/helpers.d => helpers}/systemd (100%) rename {data/helpers.d => helpers}/user (100%) rename {data/helpers.d => helpers}/utils (100%) rename {data/hooks => hooks}/backup/05-conf_ldap (100%) rename {data/hooks => hooks}/backup/17-data_home (100%) rename {data/hooks => hooks}/backup/18-data_multimedia (100%) rename {data/hooks => hooks}/backup/20-conf_ynh_settings (100%) rename {data/hooks => hooks}/backup/21-conf_ynh_certs (100%) rename {data/hooks => hooks}/backup/23-data_mail (100%) rename {data/hooks => hooks}/backup/27-data_xmpp (100%) rename {data/hooks => hooks}/backup/50-conf_manually_modified_files (100%) rename {data/hooks => hooks}/conf_regen/01-yunohost (100%) rename {data/hooks => hooks}/conf_regen/02-ssl (100%) rename {data/hooks => hooks}/conf_regen/03-ssh (100%) rename {data/hooks => hooks}/conf_regen/06-slapd (100%) rename {data/hooks => hooks}/conf_regen/09-nslcd (100%) rename {data/hooks => hooks}/conf_regen/10-apt (100%) rename {data/hooks => hooks}/conf_regen/12-metronome (100%) rename {data/hooks => hooks}/conf_regen/15-nginx (100%) rename {data/hooks => hooks}/conf_regen/19-postfix (100%) rename {data/hooks => hooks}/conf_regen/25-dovecot (100%) rename {data/hooks => hooks}/conf_regen/31-rspamd (100%) rename {data/hooks => hooks}/conf_regen/34-mysql (100%) rename {data/hooks => hooks}/conf_regen/35-redis (100%) rename {data/hooks => hooks}/conf_regen/37-mdns (100%) rename {data/hooks => hooks}/conf_regen/43-dnsmasq (100%) rename {data/hooks => hooks}/conf_regen/46-nsswitch (100%) rename {data/hooks => hooks}/conf_regen/52-fail2ban (100%) rename {data/hooks => hooks}/post_user_create/ynh_multimedia (100%) rename {data/hooks => hooks}/post_user_delete/ynh_multimedia (100%) rename {data/hooks => hooks}/restore/05-conf_ldap (100%) rename {data/hooks => hooks}/restore/17-data_home (100%) rename {data/hooks => hooks}/restore/18-data_multimedia (100%) rename {data/hooks => hooks}/restore/20-conf_ynh_settings (100%) rename {data/hooks => hooks}/restore/21-conf_ynh_certs (100%) rename {data/hooks => hooks}/restore/23-data_mail (100%) rename {data/hooks => hooks}/restore/27-data_xmpp (100%) rename {data/hooks => hooks}/restore/50-conf_manually_modified_files (100%) rename data/other/password/100000-most-used.txt.gz => share/100000-most-used-passwords.txt.gz (100%) rename data/actionsmap/yunohost.yml => share/actionsmap.yml (100%) rename {data/other => share}/config_domain.toml (100%) rename {data/other => share}/dnsbl_list.yml (100%) rename {data/other => share}/ffdhe2048.pem (100%) rename {data => share}/helpers (100%) rename {data/other => share}/registrar_list.toml (100%) rename src/{yunohost => }/__init__.py (100%) rename src/{yunohost => }/app.py (100%) rename src/{yunohost => }/app_catalog.py (100%) rename src/{yunohost => }/authenticators/ldap_admin.py (100%) rename src/{yunohost => }/backup.py (100%) rename src/{yunohost => }/certificate.py (100%) rename src/{yunohost => }/data_migrations/0021_migrate_to_bullseye.py (100%) rename src/{yunohost => }/data_migrations/0022_php73_to_php74_pools.py (100%) rename src/{yunohost => }/data_migrations/0023_postgresql_11_to_13.py (100%) rename src/{yunohost => }/data_migrations/__init__.py (100%) rename src/{yunohost => }/diagnosis.py (100%) rename {data/hooks => src}/diagnosis/00-basesystem.py (100%) rename {data/hooks => src}/diagnosis/10-ip.py (100%) rename {data/hooks => src}/diagnosis/12-dnsrecords.py (100%) rename {data/hooks => src}/diagnosis/14-ports.py (100%) rename {data/hooks => src}/diagnosis/21-web.py (100%) rename {data/hooks => src}/diagnosis/24-mail.py (100%) rename {data/hooks => src}/diagnosis/30-services.py (100%) rename {data/hooks => src}/diagnosis/50-systemresources.py (100%) rename {data/hooks => src}/diagnosis/70-regenconf.py (100%) rename {data/hooks => src}/diagnosis/80-apps.py (100%) rename src/{yunohost => }/dns.py (100%) rename src/{yunohost => }/domain.py (100%) rename src/{yunohost => }/dyndns.py (100%) rename src/{yunohost => }/firewall.py (100%) rename src/{yunohost => }/hook.py (100%) rename src/{yunohost => }/log.py (100%) rename src/{yunohost => }/permission.py (100%) rename src/{yunohost => }/regenconf.py (100%) rename src/{yunohost => }/service.py (100%) rename src/{yunohost => }/settings.py (100%) rename src/{yunohost => }/ssh.py (100%) rename src/{yunohost => }/tests/__init__.py (100%) rename src/{yunohost => }/tests/conftest.py (100%) rename src/{yunohost => }/tests/test_app_catalog.py (100%) rename src/{yunohost => }/tests/test_app_config.py (100%) rename src/{yunohost => }/tests/test_apps.py (100%) rename src/{yunohost => }/tests/test_appurl.py (100%) rename src/{yunohost => }/tests/test_backuprestore.py (100%) rename src/{yunohost => }/tests/test_changeurl.py (100%) rename src/{yunohost => }/tests/test_dns.py (100%) rename src/{yunohost => }/tests/test_domains.py (100%) rename src/{yunohost => }/tests/test_ldapauth.py (100%) rename src/{yunohost => }/tests/test_permission.py (100%) rename src/{yunohost => }/tests/test_questions.py (100%) rename src/{yunohost => }/tests/test_regenconf.py (100%) rename src/{yunohost => }/tests/test_service.py (100%) rename src/{yunohost => }/tests/test_settings.py (100%) rename src/{yunohost => }/tests/test_user-group.py (100%) rename src/{yunohost => }/tools.py (100%) rename src/{yunohost => }/user.py (100%) rename src/{yunohost => }/utils/__init__.py (100%) rename src/{yunohost => }/utils/config.py (100%) rename src/{yunohost => }/utils/dns.py (100%) rename src/{yunohost => }/utils/error.py (100%) rename src/{yunohost => }/utils/filesystem.py (100%) rename src/{yunohost => }/utils/i18n.py (100%) rename src/{yunohost => }/utils/ldap.py (100%) rename src/{yunohost => }/utils/legacy.py (100%) rename src/{yunohost => }/utils/network.py (100%) rename src/{yunohost => }/utils/packages.py (100%) rename src/{yunohost => }/utils/password.py (100%) rename src/{yunohost => }/utils/yunopaste.py (100%) rename src/{yunohost => }/vendor/__init__.py (100%) rename src/{yunohost => }/vendor/acme_tiny/__init__.py (100%) rename src/{yunohost => }/vendor/acme_tiny/acme_tiny.py (100%) rename src/{yunohost => }/vendor/spectre-meltdown-checker/Dockerfile (100%) rename src/{yunohost => }/vendor/spectre-meltdown-checker/LICENSE (100%) rename src/{yunohost => }/vendor/spectre-meltdown-checker/README.md (100%) rename src/{yunohost => }/vendor/spectre-meltdown-checker/docker-compose.yml (100%) rename src/{yunohost => }/vendor/spectre-meltdown-checker/spectre-meltdown-checker.sh (100%) diff --git a/data/templates/dnsmasq/domain.tpl b/conf/dnsmasq/domain.tpl similarity index 100% rename from data/templates/dnsmasq/domain.tpl rename to conf/dnsmasq/domain.tpl diff --git a/data/templates/dnsmasq/plain/dnsmasq.conf b/conf/dnsmasq/plain/dnsmasq.conf similarity index 100% rename from data/templates/dnsmasq/plain/dnsmasq.conf rename to conf/dnsmasq/plain/dnsmasq.conf diff --git a/data/templates/dnsmasq/plain/etcdefault b/conf/dnsmasq/plain/etcdefault similarity index 100% rename from data/templates/dnsmasq/plain/etcdefault rename to conf/dnsmasq/plain/etcdefault diff --git a/data/templates/dnsmasq/plain/resolv.dnsmasq.conf b/conf/dnsmasq/plain/resolv.dnsmasq.conf similarity index 100% rename from data/templates/dnsmasq/plain/resolv.dnsmasq.conf rename to conf/dnsmasq/plain/resolv.dnsmasq.conf diff --git a/data/templates/dovecot/dovecot-ldap.conf b/conf/dovecot/dovecot-ldap.conf similarity index 100% rename from data/templates/dovecot/dovecot-ldap.conf rename to conf/dovecot/dovecot-ldap.conf diff --git a/data/templates/dovecot/dovecot.conf b/conf/dovecot/dovecot.conf similarity index 100% rename from data/templates/dovecot/dovecot.conf rename to conf/dovecot/dovecot.conf diff --git a/data/templates/dovecot/dovecot.sieve b/conf/dovecot/dovecot.sieve similarity index 100% rename from data/templates/dovecot/dovecot.sieve rename to conf/dovecot/dovecot.sieve diff --git a/data/templates/dovecot/post-ext.conf b/conf/dovecot/post-ext.conf similarity index 100% rename from data/templates/dovecot/post-ext.conf rename to conf/dovecot/post-ext.conf diff --git a/data/templates/dovecot/pre-ext.conf b/conf/dovecot/pre-ext.conf similarity index 100% rename from data/templates/dovecot/pre-ext.conf rename to conf/dovecot/pre-ext.conf diff --git a/data/templates/fail2ban/jail.conf b/conf/fail2ban/jail.conf similarity index 100% rename from data/templates/fail2ban/jail.conf rename to conf/fail2ban/jail.conf diff --git a/data/templates/fail2ban/yunohost-jails.conf b/conf/fail2ban/yunohost-jails.conf similarity index 100% rename from data/templates/fail2ban/yunohost-jails.conf rename to conf/fail2ban/yunohost-jails.conf diff --git a/data/templates/fail2ban/yunohost.conf b/conf/fail2ban/yunohost.conf similarity index 100% rename from data/templates/fail2ban/yunohost.conf rename to conf/fail2ban/yunohost.conf diff --git a/data/templates/mdns/yunomdns.service b/conf/mdns/yunomdns.service similarity index 100% rename from data/templates/mdns/yunomdns.service rename to conf/mdns/yunomdns.service diff --git a/data/templates/metronome/domain.tpl.cfg.lua b/conf/metronome/domain.tpl.cfg.lua similarity index 100% rename from data/templates/metronome/domain.tpl.cfg.lua rename to conf/metronome/domain.tpl.cfg.lua diff --git a/data/templates/metronome/metronome.cfg.lua b/conf/metronome/metronome.cfg.lua similarity index 100% rename from data/templates/metronome/metronome.cfg.lua rename to conf/metronome/metronome.cfg.lua diff --git a/lib/metronome/modules/ldap.lib.lua b/conf/metronome/modules/ldap.lib.lua similarity index 100% rename from lib/metronome/modules/ldap.lib.lua rename to conf/metronome/modules/ldap.lib.lua diff --git a/lib/metronome/modules/mod_auth_ldap2.lua b/conf/metronome/modules/mod_auth_ldap2.lua similarity index 100% rename from lib/metronome/modules/mod_auth_ldap2.lua rename to conf/metronome/modules/mod_auth_ldap2.lua diff --git a/lib/metronome/modules/mod_legacyauth.lua b/conf/metronome/modules/mod_legacyauth.lua similarity index 100% rename from lib/metronome/modules/mod_legacyauth.lua rename to conf/metronome/modules/mod_legacyauth.lua diff --git a/lib/metronome/modules/mod_storage_ldap.lua b/conf/metronome/modules/mod_storage_ldap.lua similarity index 100% rename from lib/metronome/modules/mod_storage_ldap.lua rename to conf/metronome/modules/mod_storage_ldap.lua diff --git a/lib/metronome/modules/vcard.lib.lua b/conf/metronome/modules/vcard.lib.lua similarity index 100% rename from lib/metronome/modules/vcard.lib.lua rename to conf/metronome/modules/vcard.lib.lua diff --git a/data/templates/nginx/autoconfig.tpl.xml b/conf/nginx/autoconfig.tpl.xml similarity index 100% rename from data/templates/nginx/autoconfig.tpl.xml rename to conf/nginx/autoconfig.tpl.xml diff --git a/data/templates/nginx/plain/acme-challenge.conf.inc b/conf/nginx/plain/acme-challenge.conf.inc similarity index 100% rename from data/templates/nginx/plain/acme-challenge.conf.inc rename to conf/nginx/plain/acme-challenge.conf.inc diff --git a/data/templates/nginx/plain/global.conf b/conf/nginx/plain/global.conf similarity index 100% rename from data/templates/nginx/plain/global.conf rename to conf/nginx/plain/global.conf diff --git a/data/templates/nginx/plain/ssowat.conf b/conf/nginx/plain/ssowat.conf similarity index 100% rename from data/templates/nginx/plain/ssowat.conf rename to conf/nginx/plain/ssowat.conf diff --git a/data/templates/nginx/plain/yunohost_panel.conf.inc b/conf/nginx/plain/yunohost_panel.conf.inc similarity index 100% rename from data/templates/nginx/plain/yunohost_panel.conf.inc rename to conf/nginx/plain/yunohost_panel.conf.inc diff --git a/data/templates/nginx/plain/yunohost_sso.conf.inc b/conf/nginx/plain/yunohost_sso.conf.inc similarity index 100% rename from data/templates/nginx/plain/yunohost_sso.conf.inc rename to conf/nginx/plain/yunohost_sso.conf.inc diff --git a/data/templates/nginx/redirect_to_admin.conf b/conf/nginx/redirect_to_admin.conf similarity index 100% rename from data/templates/nginx/redirect_to_admin.conf rename to conf/nginx/redirect_to_admin.conf diff --git a/data/templates/nginx/security.conf.inc b/conf/nginx/security.conf.inc similarity index 100% rename from data/templates/nginx/security.conf.inc rename to conf/nginx/security.conf.inc diff --git a/data/templates/nginx/server.tpl.conf b/conf/nginx/server.tpl.conf similarity index 100% rename from data/templates/nginx/server.tpl.conf rename to conf/nginx/server.tpl.conf diff --git a/data/templates/nginx/yunohost_admin.conf b/conf/nginx/yunohost_admin.conf similarity index 100% rename from data/templates/nginx/yunohost_admin.conf rename to conf/nginx/yunohost_admin.conf diff --git a/data/templates/nginx/yunohost_admin.conf.inc b/conf/nginx/yunohost_admin.conf.inc similarity index 100% rename from data/templates/nginx/yunohost_admin.conf.inc rename to conf/nginx/yunohost_admin.conf.inc diff --git a/data/templates/nginx/yunohost_api.conf.inc b/conf/nginx/yunohost_api.conf.inc similarity index 100% rename from data/templates/nginx/yunohost_api.conf.inc rename to conf/nginx/yunohost_api.conf.inc diff --git a/data/templates/nslcd/nslcd.conf b/conf/nslcd/nslcd.conf similarity index 100% rename from data/templates/nslcd/nslcd.conf rename to conf/nslcd/nslcd.conf diff --git a/data/templates/nsswitch/nsswitch.conf b/conf/nsswitch/nsswitch.conf similarity index 100% rename from data/templates/nsswitch/nsswitch.conf rename to conf/nsswitch/nsswitch.conf diff --git a/data/templates/postfix/main.cf b/conf/postfix/main.cf similarity index 100% rename from data/templates/postfix/main.cf rename to conf/postfix/main.cf diff --git a/data/templates/postfix/plain/header_checks b/conf/postfix/plain/header_checks similarity index 100% rename from data/templates/postfix/plain/header_checks rename to conf/postfix/plain/header_checks diff --git a/data/templates/postfix/plain/ldap-accounts.cf b/conf/postfix/plain/ldap-accounts.cf similarity index 100% rename from data/templates/postfix/plain/ldap-accounts.cf rename to conf/postfix/plain/ldap-accounts.cf diff --git a/data/templates/postfix/plain/ldap-aliases.cf b/conf/postfix/plain/ldap-aliases.cf similarity index 100% rename from data/templates/postfix/plain/ldap-aliases.cf rename to conf/postfix/plain/ldap-aliases.cf diff --git a/data/templates/postfix/plain/ldap-domains.cf b/conf/postfix/plain/ldap-domains.cf similarity index 100% rename from data/templates/postfix/plain/ldap-domains.cf rename to conf/postfix/plain/ldap-domains.cf diff --git a/data/templates/postfix/plain/master.cf b/conf/postfix/plain/master.cf similarity index 100% rename from data/templates/postfix/plain/master.cf rename to conf/postfix/plain/master.cf diff --git a/data/templates/postfix/plain/sender_canonical b/conf/postfix/plain/sender_canonical similarity index 100% rename from data/templates/postfix/plain/sender_canonical rename to conf/postfix/plain/sender_canonical diff --git a/data/templates/postfix/plain/smtp_reply_filter b/conf/postfix/plain/smtp_reply_filter similarity index 100% rename from data/templates/postfix/plain/smtp_reply_filter rename to conf/postfix/plain/smtp_reply_filter diff --git a/data/templates/postfix/postsrsd b/conf/postfix/postsrsd similarity index 100% rename from data/templates/postfix/postsrsd rename to conf/postfix/postsrsd diff --git a/data/templates/rspamd/dkim_signing.conf b/conf/rspamd/dkim_signing.conf similarity index 100% rename from data/templates/rspamd/dkim_signing.conf rename to conf/rspamd/dkim_signing.conf diff --git a/data/templates/rspamd/metrics.local.conf b/conf/rspamd/metrics.local.conf similarity index 100% rename from data/templates/rspamd/metrics.local.conf rename to conf/rspamd/metrics.local.conf diff --git a/data/templates/rspamd/milter_headers.conf b/conf/rspamd/milter_headers.conf similarity index 100% rename from data/templates/rspamd/milter_headers.conf rename to conf/rspamd/milter_headers.conf diff --git a/data/templates/rspamd/rspamd.sieve b/conf/rspamd/rspamd.sieve similarity index 100% rename from data/templates/rspamd/rspamd.sieve rename to conf/rspamd/rspamd.sieve diff --git a/data/templates/slapd/config.ldif b/conf/slapd/config.ldif similarity index 100% rename from data/templates/slapd/config.ldif rename to conf/slapd/config.ldif diff --git a/data/templates/slapd/db_init.ldif b/conf/slapd/db_init.ldif similarity index 100% rename from data/templates/slapd/db_init.ldif rename to conf/slapd/db_init.ldif diff --git a/data/templates/slapd/ldap.conf b/conf/slapd/ldap.conf similarity index 100% rename from data/templates/slapd/ldap.conf rename to conf/slapd/ldap.conf diff --git a/data/templates/slapd/mailserver.ldif b/conf/slapd/mailserver.ldif similarity index 100% rename from data/templates/slapd/mailserver.ldif rename to conf/slapd/mailserver.ldif diff --git a/data/templates/slapd/permission.ldif b/conf/slapd/permission.ldif similarity index 100% rename from data/templates/slapd/permission.ldif rename to conf/slapd/permission.ldif diff --git a/data/templates/slapd/slapd.default b/conf/slapd/slapd.default similarity index 100% rename from data/templates/slapd/slapd.default rename to conf/slapd/slapd.default diff --git a/data/templates/slapd/sudo.ldif b/conf/slapd/sudo.ldif similarity index 100% rename from data/templates/slapd/sudo.ldif rename to conf/slapd/sudo.ldif diff --git a/data/templates/slapd/systemd-override.conf b/conf/slapd/systemd-override.conf similarity index 100% rename from data/templates/slapd/systemd-override.conf rename to conf/slapd/systemd-override.conf diff --git a/data/templates/ssh/sshd_config b/conf/ssh/sshd_config similarity index 100% rename from data/templates/ssh/sshd_config rename to conf/ssh/sshd_config diff --git a/data/templates/ssl/openssl.cnf b/conf/ssl/openssl.cnf similarity index 100% rename from data/templates/ssl/openssl.cnf rename to conf/ssl/openssl.cnf diff --git a/data/templates/yunohost/dpkg-origins b/conf/yunohost/dpkg-origins similarity index 100% rename from data/templates/yunohost/dpkg-origins rename to conf/yunohost/dpkg-origins diff --git a/data/templates/yunohost/firewall.yml b/conf/yunohost/firewall.yml similarity index 100% rename from data/templates/yunohost/firewall.yml rename to conf/yunohost/firewall.yml diff --git a/data/templates/yunohost/proc-hidepid.service b/conf/yunohost/proc-hidepid.service similarity index 100% rename from data/templates/yunohost/proc-hidepid.service rename to conf/yunohost/proc-hidepid.service diff --git a/data/templates/yunohost/services.yml b/conf/yunohost/services.yml similarity index 100% rename from data/templates/yunohost/services.yml rename to conf/yunohost/services.yml diff --git a/data/templates/yunohost/yunoprompt.service b/conf/yunohost/yunoprompt.service similarity index 100% rename from data/templates/yunohost/yunoprompt.service rename to conf/yunohost/yunoprompt.service diff --git a/data/actionsmap/yunohost_completion.py b/doc/generate_bash_completion.py similarity index 100% rename from data/actionsmap/yunohost_completion.py rename to doc/generate_bash_completion.py diff --git a/data/helpers.d/apt b/helpers/apt similarity index 100% rename from data/helpers.d/apt rename to helpers/apt diff --git a/data/helpers.d/backup b/helpers/backup similarity index 100% rename from data/helpers.d/backup rename to helpers/backup diff --git a/data/helpers.d/config b/helpers/config similarity index 100% rename from data/helpers.d/config rename to helpers/config diff --git a/data/helpers.d/fail2ban b/helpers/fail2ban similarity index 100% rename from data/helpers.d/fail2ban rename to helpers/fail2ban diff --git a/data/helpers.d/getopts b/helpers/getopts similarity index 100% rename from data/helpers.d/getopts rename to helpers/getopts diff --git a/data/helpers.d/hardware b/helpers/hardware similarity index 100% rename from data/helpers.d/hardware rename to helpers/hardware diff --git a/data/helpers.d/logging b/helpers/logging similarity index 100% rename from data/helpers.d/logging rename to helpers/logging diff --git a/data/helpers.d/logrotate b/helpers/logrotate similarity index 100% rename from data/helpers.d/logrotate rename to helpers/logrotate diff --git a/data/helpers.d/multimedia b/helpers/multimedia similarity index 100% rename from data/helpers.d/multimedia rename to helpers/multimedia diff --git a/data/helpers.d/mysql b/helpers/mysql similarity index 100% rename from data/helpers.d/mysql rename to helpers/mysql diff --git a/data/helpers.d/network b/helpers/network similarity index 100% rename from data/helpers.d/network rename to helpers/network diff --git a/data/helpers.d/nginx b/helpers/nginx similarity index 100% rename from data/helpers.d/nginx rename to helpers/nginx diff --git a/data/helpers.d/nodejs b/helpers/nodejs similarity index 100% rename from data/helpers.d/nodejs rename to helpers/nodejs diff --git a/data/helpers.d/permission b/helpers/permission similarity index 100% rename from data/helpers.d/permission rename to helpers/permission diff --git a/data/helpers.d/php b/helpers/php similarity index 100% rename from data/helpers.d/php rename to helpers/php diff --git a/data/helpers.d/postgresql b/helpers/postgresql similarity index 100% rename from data/helpers.d/postgresql rename to helpers/postgresql diff --git a/data/helpers.d/setting b/helpers/setting similarity index 100% rename from data/helpers.d/setting rename to helpers/setting diff --git a/data/helpers.d/string b/helpers/string similarity index 100% rename from data/helpers.d/string rename to helpers/string diff --git a/data/helpers.d/systemd b/helpers/systemd similarity index 100% rename from data/helpers.d/systemd rename to helpers/systemd diff --git a/data/helpers.d/user b/helpers/user similarity index 100% rename from data/helpers.d/user rename to helpers/user diff --git a/data/helpers.d/utils b/helpers/utils similarity index 100% rename from data/helpers.d/utils rename to helpers/utils diff --git a/data/hooks/backup/05-conf_ldap b/hooks/backup/05-conf_ldap similarity index 100% rename from data/hooks/backup/05-conf_ldap rename to hooks/backup/05-conf_ldap diff --git a/data/hooks/backup/17-data_home b/hooks/backup/17-data_home similarity index 100% rename from data/hooks/backup/17-data_home rename to hooks/backup/17-data_home diff --git a/data/hooks/backup/18-data_multimedia b/hooks/backup/18-data_multimedia similarity index 100% rename from data/hooks/backup/18-data_multimedia rename to hooks/backup/18-data_multimedia diff --git a/data/hooks/backup/20-conf_ynh_settings b/hooks/backup/20-conf_ynh_settings similarity index 100% rename from data/hooks/backup/20-conf_ynh_settings rename to hooks/backup/20-conf_ynh_settings diff --git a/data/hooks/backup/21-conf_ynh_certs b/hooks/backup/21-conf_ynh_certs similarity index 100% rename from data/hooks/backup/21-conf_ynh_certs rename to hooks/backup/21-conf_ynh_certs diff --git a/data/hooks/backup/23-data_mail b/hooks/backup/23-data_mail similarity index 100% rename from data/hooks/backup/23-data_mail rename to hooks/backup/23-data_mail diff --git a/data/hooks/backup/27-data_xmpp b/hooks/backup/27-data_xmpp similarity index 100% rename from data/hooks/backup/27-data_xmpp rename to hooks/backup/27-data_xmpp diff --git a/data/hooks/backup/50-conf_manually_modified_files b/hooks/backup/50-conf_manually_modified_files similarity index 100% rename from data/hooks/backup/50-conf_manually_modified_files rename to hooks/backup/50-conf_manually_modified_files diff --git a/data/hooks/conf_regen/01-yunohost b/hooks/conf_regen/01-yunohost similarity index 100% rename from data/hooks/conf_regen/01-yunohost rename to hooks/conf_regen/01-yunohost diff --git a/data/hooks/conf_regen/02-ssl b/hooks/conf_regen/02-ssl similarity index 100% rename from data/hooks/conf_regen/02-ssl rename to hooks/conf_regen/02-ssl diff --git a/data/hooks/conf_regen/03-ssh b/hooks/conf_regen/03-ssh similarity index 100% rename from data/hooks/conf_regen/03-ssh rename to hooks/conf_regen/03-ssh diff --git a/data/hooks/conf_regen/06-slapd b/hooks/conf_regen/06-slapd similarity index 100% rename from data/hooks/conf_regen/06-slapd rename to hooks/conf_regen/06-slapd diff --git a/data/hooks/conf_regen/09-nslcd b/hooks/conf_regen/09-nslcd similarity index 100% rename from data/hooks/conf_regen/09-nslcd rename to hooks/conf_regen/09-nslcd diff --git a/data/hooks/conf_regen/10-apt b/hooks/conf_regen/10-apt similarity index 100% rename from data/hooks/conf_regen/10-apt rename to hooks/conf_regen/10-apt diff --git a/data/hooks/conf_regen/12-metronome b/hooks/conf_regen/12-metronome similarity index 100% rename from data/hooks/conf_regen/12-metronome rename to hooks/conf_regen/12-metronome diff --git a/data/hooks/conf_regen/15-nginx b/hooks/conf_regen/15-nginx similarity index 100% rename from data/hooks/conf_regen/15-nginx rename to hooks/conf_regen/15-nginx diff --git a/data/hooks/conf_regen/19-postfix b/hooks/conf_regen/19-postfix similarity index 100% rename from data/hooks/conf_regen/19-postfix rename to hooks/conf_regen/19-postfix diff --git a/data/hooks/conf_regen/25-dovecot b/hooks/conf_regen/25-dovecot similarity index 100% rename from data/hooks/conf_regen/25-dovecot rename to hooks/conf_regen/25-dovecot diff --git a/data/hooks/conf_regen/31-rspamd b/hooks/conf_regen/31-rspamd similarity index 100% rename from data/hooks/conf_regen/31-rspamd rename to hooks/conf_regen/31-rspamd diff --git a/data/hooks/conf_regen/34-mysql b/hooks/conf_regen/34-mysql similarity index 100% rename from data/hooks/conf_regen/34-mysql rename to hooks/conf_regen/34-mysql diff --git a/data/hooks/conf_regen/35-redis b/hooks/conf_regen/35-redis similarity index 100% rename from data/hooks/conf_regen/35-redis rename to hooks/conf_regen/35-redis diff --git a/data/hooks/conf_regen/37-mdns b/hooks/conf_regen/37-mdns similarity index 100% rename from data/hooks/conf_regen/37-mdns rename to hooks/conf_regen/37-mdns diff --git a/data/hooks/conf_regen/43-dnsmasq b/hooks/conf_regen/43-dnsmasq similarity index 100% rename from data/hooks/conf_regen/43-dnsmasq rename to hooks/conf_regen/43-dnsmasq diff --git a/data/hooks/conf_regen/46-nsswitch b/hooks/conf_regen/46-nsswitch similarity index 100% rename from data/hooks/conf_regen/46-nsswitch rename to hooks/conf_regen/46-nsswitch diff --git a/data/hooks/conf_regen/52-fail2ban b/hooks/conf_regen/52-fail2ban similarity index 100% rename from data/hooks/conf_regen/52-fail2ban rename to hooks/conf_regen/52-fail2ban diff --git a/data/hooks/post_user_create/ynh_multimedia b/hooks/post_user_create/ynh_multimedia similarity index 100% rename from data/hooks/post_user_create/ynh_multimedia rename to hooks/post_user_create/ynh_multimedia diff --git a/data/hooks/post_user_delete/ynh_multimedia b/hooks/post_user_delete/ynh_multimedia similarity index 100% rename from data/hooks/post_user_delete/ynh_multimedia rename to hooks/post_user_delete/ynh_multimedia diff --git a/data/hooks/restore/05-conf_ldap b/hooks/restore/05-conf_ldap similarity index 100% rename from data/hooks/restore/05-conf_ldap rename to hooks/restore/05-conf_ldap diff --git a/data/hooks/restore/17-data_home b/hooks/restore/17-data_home similarity index 100% rename from data/hooks/restore/17-data_home rename to hooks/restore/17-data_home diff --git a/data/hooks/restore/18-data_multimedia b/hooks/restore/18-data_multimedia similarity index 100% rename from data/hooks/restore/18-data_multimedia rename to hooks/restore/18-data_multimedia diff --git a/data/hooks/restore/20-conf_ynh_settings b/hooks/restore/20-conf_ynh_settings similarity index 100% rename from data/hooks/restore/20-conf_ynh_settings rename to hooks/restore/20-conf_ynh_settings diff --git a/data/hooks/restore/21-conf_ynh_certs b/hooks/restore/21-conf_ynh_certs similarity index 100% rename from data/hooks/restore/21-conf_ynh_certs rename to hooks/restore/21-conf_ynh_certs diff --git a/data/hooks/restore/23-data_mail b/hooks/restore/23-data_mail similarity index 100% rename from data/hooks/restore/23-data_mail rename to hooks/restore/23-data_mail diff --git a/data/hooks/restore/27-data_xmpp b/hooks/restore/27-data_xmpp similarity index 100% rename from data/hooks/restore/27-data_xmpp rename to hooks/restore/27-data_xmpp diff --git a/data/hooks/restore/50-conf_manually_modified_files b/hooks/restore/50-conf_manually_modified_files similarity index 100% rename from data/hooks/restore/50-conf_manually_modified_files rename to hooks/restore/50-conf_manually_modified_files diff --git a/data/other/password/100000-most-used.txt.gz b/share/100000-most-used-passwords.txt.gz similarity index 100% rename from data/other/password/100000-most-used.txt.gz rename to share/100000-most-used-passwords.txt.gz diff --git a/data/actionsmap/yunohost.yml b/share/actionsmap.yml similarity index 100% rename from data/actionsmap/yunohost.yml rename to share/actionsmap.yml diff --git a/data/other/config_domain.toml b/share/config_domain.toml similarity index 100% rename from data/other/config_domain.toml rename to share/config_domain.toml diff --git a/data/other/dnsbl_list.yml b/share/dnsbl_list.yml similarity index 100% rename from data/other/dnsbl_list.yml rename to share/dnsbl_list.yml diff --git a/data/other/ffdhe2048.pem b/share/ffdhe2048.pem similarity index 100% rename from data/other/ffdhe2048.pem rename to share/ffdhe2048.pem diff --git a/data/helpers b/share/helpers similarity index 100% rename from data/helpers rename to share/helpers diff --git a/data/other/registrar_list.toml b/share/registrar_list.toml similarity index 100% rename from data/other/registrar_list.toml rename to share/registrar_list.toml diff --git a/src/yunohost/__init__.py b/src/__init__.py similarity index 100% rename from src/yunohost/__init__.py rename to src/__init__.py diff --git a/src/yunohost/app.py b/src/app.py similarity index 100% rename from src/yunohost/app.py rename to src/app.py diff --git a/src/yunohost/app_catalog.py b/src/app_catalog.py similarity index 100% rename from src/yunohost/app_catalog.py rename to src/app_catalog.py diff --git a/src/yunohost/authenticators/ldap_admin.py b/src/authenticators/ldap_admin.py similarity index 100% rename from src/yunohost/authenticators/ldap_admin.py rename to src/authenticators/ldap_admin.py diff --git a/src/yunohost/backup.py b/src/backup.py similarity index 100% rename from src/yunohost/backup.py rename to src/backup.py diff --git a/src/yunohost/certificate.py b/src/certificate.py similarity index 100% rename from src/yunohost/certificate.py rename to src/certificate.py diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/data_migrations/0021_migrate_to_bullseye.py similarity index 100% rename from src/yunohost/data_migrations/0021_migrate_to_bullseye.py rename to src/data_migrations/0021_migrate_to_bullseye.py diff --git a/src/yunohost/data_migrations/0022_php73_to_php74_pools.py b/src/data_migrations/0022_php73_to_php74_pools.py similarity index 100% rename from src/yunohost/data_migrations/0022_php73_to_php74_pools.py rename to src/data_migrations/0022_php73_to_php74_pools.py diff --git a/src/yunohost/data_migrations/0023_postgresql_11_to_13.py b/src/data_migrations/0023_postgresql_11_to_13.py similarity index 100% rename from src/yunohost/data_migrations/0023_postgresql_11_to_13.py rename to src/data_migrations/0023_postgresql_11_to_13.py diff --git a/src/yunohost/data_migrations/__init__.py b/src/data_migrations/__init__.py similarity index 100% rename from src/yunohost/data_migrations/__init__.py rename to src/data_migrations/__init__.py diff --git a/src/yunohost/diagnosis.py b/src/diagnosis.py similarity index 100% rename from src/yunohost/diagnosis.py rename to src/diagnosis.py diff --git a/data/hooks/diagnosis/00-basesystem.py b/src/diagnosis/00-basesystem.py similarity index 100% rename from data/hooks/diagnosis/00-basesystem.py rename to src/diagnosis/00-basesystem.py diff --git a/data/hooks/diagnosis/10-ip.py b/src/diagnosis/10-ip.py similarity index 100% rename from data/hooks/diagnosis/10-ip.py rename to src/diagnosis/10-ip.py diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/src/diagnosis/12-dnsrecords.py similarity index 100% rename from data/hooks/diagnosis/12-dnsrecords.py rename to src/diagnosis/12-dnsrecords.py diff --git a/data/hooks/diagnosis/14-ports.py b/src/diagnosis/14-ports.py similarity index 100% rename from data/hooks/diagnosis/14-ports.py rename to src/diagnosis/14-ports.py diff --git a/data/hooks/diagnosis/21-web.py b/src/diagnosis/21-web.py similarity index 100% rename from data/hooks/diagnosis/21-web.py rename to src/diagnosis/21-web.py diff --git a/data/hooks/diagnosis/24-mail.py b/src/diagnosis/24-mail.py similarity index 100% rename from data/hooks/diagnosis/24-mail.py rename to src/diagnosis/24-mail.py diff --git a/data/hooks/diagnosis/30-services.py b/src/diagnosis/30-services.py similarity index 100% rename from data/hooks/diagnosis/30-services.py rename to src/diagnosis/30-services.py diff --git a/data/hooks/diagnosis/50-systemresources.py b/src/diagnosis/50-systemresources.py similarity index 100% rename from data/hooks/diagnosis/50-systemresources.py rename to src/diagnosis/50-systemresources.py diff --git a/data/hooks/diagnosis/70-regenconf.py b/src/diagnosis/70-regenconf.py similarity index 100% rename from data/hooks/diagnosis/70-regenconf.py rename to src/diagnosis/70-regenconf.py diff --git a/data/hooks/diagnosis/80-apps.py b/src/diagnosis/80-apps.py similarity index 100% rename from data/hooks/diagnosis/80-apps.py rename to src/diagnosis/80-apps.py diff --git a/src/yunohost/dns.py b/src/dns.py similarity index 100% rename from src/yunohost/dns.py rename to src/dns.py diff --git a/src/yunohost/domain.py b/src/domain.py similarity index 100% rename from src/yunohost/domain.py rename to src/domain.py diff --git a/src/yunohost/dyndns.py b/src/dyndns.py similarity index 100% rename from src/yunohost/dyndns.py rename to src/dyndns.py diff --git a/src/yunohost/firewall.py b/src/firewall.py similarity index 100% rename from src/yunohost/firewall.py rename to src/firewall.py diff --git a/src/yunohost/hook.py b/src/hook.py similarity index 100% rename from src/yunohost/hook.py rename to src/hook.py diff --git a/src/yunohost/log.py b/src/log.py similarity index 100% rename from src/yunohost/log.py rename to src/log.py diff --git a/src/yunohost/permission.py b/src/permission.py similarity index 100% rename from src/yunohost/permission.py rename to src/permission.py diff --git a/src/yunohost/regenconf.py b/src/regenconf.py similarity index 100% rename from src/yunohost/regenconf.py rename to src/regenconf.py diff --git a/src/yunohost/service.py b/src/service.py similarity index 100% rename from src/yunohost/service.py rename to src/service.py diff --git a/src/yunohost/settings.py b/src/settings.py similarity index 100% rename from src/yunohost/settings.py rename to src/settings.py diff --git a/src/yunohost/ssh.py b/src/ssh.py similarity index 100% rename from src/yunohost/ssh.py rename to src/ssh.py diff --git a/src/yunohost/tests/__init__.py b/src/tests/__init__.py similarity index 100% rename from src/yunohost/tests/__init__.py rename to src/tests/__init__.py diff --git a/src/yunohost/tests/conftest.py b/src/tests/conftest.py similarity index 100% rename from src/yunohost/tests/conftest.py rename to src/tests/conftest.py diff --git a/src/yunohost/tests/test_app_catalog.py b/src/tests/test_app_catalog.py similarity index 100% rename from src/yunohost/tests/test_app_catalog.py rename to src/tests/test_app_catalog.py diff --git a/src/yunohost/tests/test_app_config.py b/src/tests/test_app_config.py similarity index 100% rename from src/yunohost/tests/test_app_config.py rename to src/tests/test_app_config.py diff --git a/src/yunohost/tests/test_apps.py b/src/tests/test_apps.py similarity index 100% rename from src/yunohost/tests/test_apps.py rename to src/tests/test_apps.py diff --git a/src/yunohost/tests/test_appurl.py b/src/tests/test_appurl.py similarity index 100% rename from src/yunohost/tests/test_appurl.py rename to src/tests/test_appurl.py diff --git a/src/yunohost/tests/test_backuprestore.py b/src/tests/test_backuprestore.py similarity index 100% rename from src/yunohost/tests/test_backuprestore.py rename to src/tests/test_backuprestore.py diff --git a/src/yunohost/tests/test_changeurl.py b/src/tests/test_changeurl.py similarity index 100% rename from src/yunohost/tests/test_changeurl.py rename to src/tests/test_changeurl.py diff --git a/src/yunohost/tests/test_dns.py b/src/tests/test_dns.py similarity index 100% rename from src/yunohost/tests/test_dns.py rename to src/tests/test_dns.py diff --git a/src/yunohost/tests/test_domains.py b/src/tests/test_domains.py similarity index 100% rename from src/yunohost/tests/test_domains.py rename to src/tests/test_domains.py diff --git a/src/yunohost/tests/test_ldapauth.py b/src/tests/test_ldapauth.py similarity index 100% rename from src/yunohost/tests/test_ldapauth.py rename to src/tests/test_ldapauth.py diff --git a/src/yunohost/tests/test_permission.py b/src/tests/test_permission.py similarity index 100% rename from src/yunohost/tests/test_permission.py rename to src/tests/test_permission.py diff --git a/src/yunohost/tests/test_questions.py b/src/tests/test_questions.py similarity index 100% rename from src/yunohost/tests/test_questions.py rename to src/tests/test_questions.py diff --git a/src/yunohost/tests/test_regenconf.py b/src/tests/test_regenconf.py similarity index 100% rename from src/yunohost/tests/test_regenconf.py rename to src/tests/test_regenconf.py diff --git a/src/yunohost/tests/test_service.py b/src/tests/test_service.py similarity index 100% rename from src/yunohost/tests/test_service.py rename to src/tests/test_service.py diff --git a/src/yunohost/tests/test_settings.py b/src/tests/test_settings.py similarity index 100% rename from src/yunohost/tests/test_settings.py rename to src/tests/test_settings.py diff --git a/src/yunohost/tests/test_user-group.py b/src/tests/test_user-group.py similarity index 100% rename from src/yunohost/tests/test_user-group.py rename to src/tests/test_user-group.py diff --git a/src/yunohost/tools.py b/src/tools.py similarity index 100% rename from src/yunohost/tools.py rename to src/tools.py diff --git a/src/yunohost/user.py b/src/user.py similarity index 100% rename from src/yunohost/user.py rename to src/user.py diff --git a/src/yunohost/utils/__init__.py b/src/utils/__init__.py similarity index 100% rename from src/yunohost/utils/__init__.py rename to src/utils/__init__.py diff --git a/src/yunohost/utils/config.py b/src/utils/config.py similarity index 100% rename from src/yunohost/utils/config.py rename to src/utils/config.py diff --git a/src/yunohost/utils/dns.py b/src/utils/dns.py similarity index 100% rename from src/yunohost/utils/dns.py rename to src/utils/dns.py diff --git a/src/yunohost/utils/error.py b/src/utils/error.py similarity index 100% rename from src/yunohost/utils/error.py rename to src/utils/error.py diff --git a/src/yunohost/utils/filesystem.py b/src/utils/filesystem.py similarity index 100% rename from src/yunohost/utils/filesystem.py rename to src/utils/filesystem.py diff --git a/src/yunohost/utils/i18n.py b/src/utils/i18n.py similarity index 100% rename from src/yunohost/utils/i18n.py rename to src/utils/i18n.py diff --git a/src/yunohost/utils/ldap.py b/src/utils/ldap.py similarity index 100% rename from src/yunohost/utils/ldap.py rename to src/utils/ldap.py diff --git a/src/yunohost/utils/legacy.py b/src/utils/legacy.py similarity index 100% rename from src/yunohost/utils/legacy.py rename to src/utils/legacy.py diff --git a/src/yunohost/utils/network.py b/src/utils/network.py similarity index 100% rename from src/yunohost/utils/network.py rename to src/utils/network.py diff --git a/src/yunohost/utils/packages.py b/src/utils/packages.py similarity index 100% rename from src/yunohost/utils/packages.py rename to src/utils/packages.py diff --git a/src/yunohost/utils/password.py b/src/utils/password.py similarity index 100% rename from src/yunohost/utils/password.py rename to src/utils/password.py diff --git a/src/yunohost/utils/yunopaste.py b/src/utils/yunopaste.py similarity index 100% rename from src/yunohost/utils/yunopaste.py rename to src/utils/yunopaste.py diff --git a/src/yunohost/vendor/__init__.py b/src/vendor/__init__.py similarity index 100% rename from src/yunohost/vendor/__init__.py rename to src/vendor/__init__.py diff --git a/src/yunohost/vendor/acme_tiny/__init__.py b/src/vendor/acme_tiny/__init__.py similarity index 100% rename from src/yunohost/vendor/acme_tiny/__init__.py rename to src/vendor/acme_tiny/__init__.py diff --git a/src/yunohost/vendor/acme_tiny/acme_tiny.py b/src/vendor/acme_tiny/acme_tiny.py similarity index 100% rename from src/yunohost/vendor/acme_tiny/acme_tiny.py rename to src/vendor/acme_tiny/acme_tiny.py diff --git a/src/yunohost/vendor/spectre-meltdown-checker/Dockerfile b/src/vendor/spectre-meltdown-checker/Dockerfile similarity index 100% rename from src/yunohost/vendor/spectre-meltdown-checker/Dockerfile rename to src/vendor/spectre-meltdown-checker/Dockerfile diff --git a/src/yunohost/vendor/spectre-meltdown-checker/LICENSE b/src/vendor/spectre-meltdown-checker/LICENSE similarity index 100% rename from src/yunohost/vendor/spectre-meltdown-checker/LICENSE rename to src/vendor/spectre-meltdown-checker/LICENSE diff --git a/src/yunohost/vendor/spectre-meltdown-checker/README.md b/src/vendor/spectre-meltdown-checker/README.md similarity index 100% rename from src/yunohost/vendor/spectre-meltdown-checker/README.md rename to src/vendor/spectre-meltdown-checker/README.md diff --git a/src/yunohost/vendor/spectre-meltdown-checker/docker-compose.yml b/src/vendor/spectre-meltdown-checker/docker-compose.yml similarity index 100% rename from src/yunohost/vendor/spectre-meltdown-checker/docker-compose.yml rename to src/vendor/spectre-meltdown-checker/docker-compose.yml diff --git a/src/yunohost/vendor/spectre-meltdown-checker/spectre-meltdown-checker.sh b/src/vendor/spectre-meltdown-checker/spectre-meltdown-checker.sh similarity index 100% rename from src/yunohost/vendor/spectre-meltdown-checker/spectre-meltdown-checker.sh rename to src/vendor/spectre-meltdown-checker/spectre-meltdown-checker.sh From 05f25fa85fa8495f97a81927991c1af9de6b1723 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 10 Nov 2021 18:25:37 +0100 Subject: [PATCH 0916/1155] Propagate dir structure change to scripts etc --- .coveragerc | 2 +- .gitignore | 4 +- .gitlab/ci/test.gitlab-ci.yml | 76 ++++++++++++++++----------------- conf/dovecot/dovecot.conf | 2 +- conf/nginx/security.conf.inc | 2 +- conf/postfix/main.cf | 2 +- debian/install | 9 ++-- debian/rules | 2 +- doc/generate_bash_completion.py | 5 +-- doc/generate_helper_doc.py | 2 +- doc/generate_manpages.py | 2 +- doc/helper_doc_template.md | 2 +- hooks/conf_regen/01-yunohost | 4 +- hooks/conf_regen/02-ssl | 8 ++-- hooks/conf_regen/03-ssh | 2 +- hooks/conf_regen/06-slapd | 6 +-- hooks/conf_regen/09-nslcd | 2 +- hooks/conf_regen/12-metronome | 2 +- hooks/conf_regen/15-nginx | 4 +- hooks/conf_regen/19-postfix | 2 +- hooks/conf_regen/25-dovecot | 2 +- hooks/conf_regen/31-rspamd | 2 +- hooks/conf_regen/34-mysql | 2 +- hooks/conf_regen/37-mdns | 2 +- hooks/conf_regen/43-dnsmasq | 2 +- hooks/conf_regen/46-nsswitch | 2 +- hooks/conf_regen/52-fail2ban | 2 +- src/diagnosis/10-ip.py | 2 +- src/diagnosis/24-mail.py | 2 +- src/dns.py | 2 +- src/domain.py | 2 +- src/service.py | 2 +- src/utils/config.py | 2 +- src/utils/password.py | 2 +- tests/add_missing_keys.py | 22 +++++----- tests/test_actionmap.py | 2 +- tests/test_i18n_keys.py | 26 +++++------ tox.ini | 6 +-- 38 files changed, 113 insertions(+), 111 deletions(-) diff --git a/.coveragerc b/.coveragerc index ed13dfa68..fe22c8381 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,2 +1,2 @@ [report] -omit=src/yunohost/tests/*,src/yunohost/vendor/*,/usr/lib/moulinette/yunohost/* +omit=src/tests/*,src/vendor/*,/usr/lib/moulinette/yunohost/* diff --git a/.gitignore b/.gitignore index 75f4ae6ea..eae46b4c5 100644 --- a/.gitignore +++ b/.gitignore @@ -31,7 +31,7 @@ pip-log.txt .mr.developer.cfg # moulinette lib -src/yunohost/locales +src/locales # Test -src/yunohost/tests/apps +src/tests/apps diff --git a/.gitlab/ci/test.gitlab-ci.yml b/.gitlab/ci/test.gitlab-ci.yml index 1aad46fbe..49c07e4b6 100644 --- a/.gitlab/ci/test.gitlab-ci.yml +++ b/.gitlab/ci/test.gitlab-ci.yml @@ -11,7 +11,7 @@ - *install_debs cache: paths: - - src/yunohost/tests/apps + - src/tests/apps key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG" needs: - job: build-yunohost @@ -36,7 +36,7 @@ full-tests: - *install_debs - yunohost tools postinstall -d domain.tld -p the_password --ignore-dyndns --force-diskspace script: - - python3 -m pytest --cov=yunohost tests/ src/yunohost/tests/ data/hooks/diagnosis/ --junitxml=report.xml + - python3 -m pytest --cov=yunohost tests/ src/tests/ src/diagnosis/ --junitxml=report.xml - cd tests - bash test_helpers.sh needs: @@ -57,8 +57,8 @@ test-i18n-keys: only: changes: - locales/en.json - - src/yunohost/*.py - - data/hooks/diagnosis/*.py + - src/*.py + - src/diagnosis/*.py test-translation-format-consistency: extends: .test-stage @@ -74,7 +74,7 @@ test-actionmap: - python3 -m pytest tests/test_actionmap.py only: changes: - - data/actionsmap/*.yml + - share/actionsmap.yml test-helpers: extends: .test-stage @@ -83,126 +83,126 @@ test-helpers: - bash test_helpers.sh only: changes: - - data/helpers.d/* + - helpers/* test-domains: extends: .test-stage script: - - python3 -m pytest src/yunohost/tests/test_domains.py + - python3 -m pytest src/tests/test_domains.py only: changes: - - src/yunohost/domain.py + - src/domain.py test-dns: extends: .test-stage script: - - python3 -m pytest src/yunohost/tests/test_dns.py + - python3 -m pytest src/tests/test_dns.py only: changes: - - src/yunohost/dns.py - - src/yunohost/utils/dns.py + - src/dns.py + - src/utils/dns.py test-apps: extends: .test-stage script: - - python3 -m pytest src/yunohost/tests/test_apps.py + - python3 -m pytest src/tests/test_apps.py only: changes: - - src/yunohost/app.py + - src/app.py test-appscatalog: extends: .test-stage script: - - python3 -m pytest src/yunohost/tests/test_app_catalog.py + - python3 -m pytest src/tests/test_app_catalog.py only: changes: - - src/yunohost/app_calalog.py + - src/app_calalog.py test-appurl: extends: .test-stage script: - - python3 -m pytest src/yunohost/tests/test_appurl.py + - python3 -m pytest src/tests/test_appurl.py only: changes: - - src/yunohost/app.py + - src/app.py test-questions: extends: .test-stage script: - - python3 -m pytest src/yunohost/tests/test_questions.py + - python3 -m pytest src/tests/test_questions.py only: changes: - - src/yunohost/utils/config.py + - src/utils/config.py test-app-config: extends: .test-stage script: - - python3 -m pytest src/yunohost/tests/test_app_config.py + - python3 -m pytest src/tests/test_app_config.py only: changes: - - src/yunohost/app.py - - src/yunohost/utils/config.py + - src/app.py + - src/utils/config.py test-changeurl: extends: .test-stage script: - - python3 -m pytest src/yunohost/tests/test_changeurl.py + - python3 -m pytest src/tests/test_changeurl.py only: changes: - - src/yunohost/app.py + - src/app.py test-backuprestore: extends: .test-stage script: - - python3 -m pytest src/yunohost/tests/test_backuprestore.py + - python3 -m pytest src/tests/test_backuprestore.py only: changes: - - src/yunohost/backup.py + - src/backup.py test-permission: extends: .test-stage script: - - python3 -m pytest src/yunohost/tests/test_permission.py + - python3 -m pytest src/tests/test_permission.py only: changes: - - src/yunohost/permission.py + - src/permission.py test-settings: extends: .test-stage script: - - python3 -m pytest src/yunohost/tests/test_settings.py + - python3 -m pytest src/tests/test_settings.py only: changes: - - src/yunohost/settings.py + - src/settings.py test-user-group: extends: .test-stage script: - - python3 -m pytest src/yunohost/tests/test_user-group.py + - python3 -m pytest src/tests/test_user-group.py only: changes: - - src/yunohost/user.py + - src/user.py test-regenconf: extends: .test-stage script: - - python3 -m pytest src/yunohost/tests/test_regenconf.py + - python3 -m pytest src/tests/test_regenconf.py only: changes: - - src/yunohost/regenconf.py + - src/regenconf.py test-service: extends: .test-stage script: - - python3 -m pytest src/yunohost/tests/test_service.py + - python3 -m pytest src/tests/test_service.py only: changes: - - src/yunohost/service.py + - src/service.py test-ldapauth: extends: .test-stage script: - - python3 -m pytest src/yunohost/tests/test_ldapauth.py + - python3 -m pytest src/tests/test_ldapauth.py only: changes: - - src/yunohost/authenticators/*.py + - src/authenticators/*.py diff --git a/conf/dovecot/dovecot.conf b/conf/dovecot/dovecot.conf index c7e937979..273bd45dc 100644 --- a/conf/dovecot/dovecot.conf +++ b/conf/dovecot/dovecot.conf @@ -23,7 +23,7 @@ ssl_cert = /path/to/dhparam -ssl_dh = /path/to/dhparam.pem # not actually 1024 bits, this applies to all DHE >= 1024 bits -smtpd_tls_dh1024_param_file = /usr/share/yunohost/other/ffdhe2048.pem +smtpd_tls_dh1024_param_file = /usr/share/yunohost/ffdhe2048.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 {% else %} diff --git a/debian/install b/debian/install index 4680bc6be..55c535ae8 100644 --- a/debian/install +++ b/debian/install @@ -1,7 +1,10 @@ bin/* /usr/bin/ -data/* /usr/share/yunohost/ -data/bash-completion.d/yunohost /etc/bash_completion.d/ +share/* /usr/share/yunohost/ +hooks/* /usr/share/yunohost/hooks/ +helpers/* /usr/share/yunohost/helpers.d/ +conf/* /usr/share/yunohost/conf/ doc/yunohost.8.gz /usr/share/man/man8/ +doc/bash-completion.sh /etc/bash_completion.d/yunohost lib/metronome/modules/* /usr/lib/metronome/modules/ locales/* /usr/lib/moulinette/yunohost/locales/ -src/yunohost /usr/lib/moulinette +src/ /usr/lib/moulinette/yunohost diff --git a/debian/rules b/debian/rules index 341ba2b01..d0cbd5138 100755 --- a/debian/rules +++ b/debian/rules @@ -6,7 +6,7 @@ override_dh_auto_build: # Generate bash completion file - python3 data/actionsmap/yunohost_completion.py + python3 doc/generate_bash_completion.py python3 doc/generate_manpages.py --gzip --output doc/yunohost.8.gz override_dh_installinit: diff --git a/doc/generate_bash_completion.py b/doc/generate_bash_completion.py index c801e2f3c..72a524210 100644 --- a/doc/generate_bash_completion.py +++ b/doc/generate_bash_completion.py @@ -12,9 +12,8 @@ import os 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" +ACTIONSMAP_FILE = THIS_SCRIPT_DIR + "/../share/actionsmap.yml" +BASH_COMPLETION_FILE = THIS_SCRIPT_DIR + "/bash-completion.sh" def get_dict_actions(OPTION_SUBTREE, category): diff --git a/doc/generate_helper_doc.py b/doc/generate_helper_doc.py index f2d5bf444..189a445d4 100644 --- a/doc/generate_helper_doc.py +++ b/doc/generate_helper_doc.py @@ -217,7 +217,7 @@ def malformed_error(line_number): def main(): - helper_files = sorted(glob.glob("../data/helpers.d/*")) + helper_files = sorted(glob.glob("../helpers/*")) helpers = [] for helper_file in helper_files: diff --git a/doc/generate_manpages.py b/doc/generate_manpages.py index fa042fb17..bdb1fcaee 100644 --- a/doc/generate_manpages.py +++ b/doc/generate_manpages.py @@ -22,7 +22,7 @@ template = Template(open(os.path.join(base_path, "manpage.template")).read()) THIS_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) -ACTIONSMAP_FILE = os.path.join(THIS_SCRIPT_DIR, "../data/actionsmap/yunohost.yml") +ACTIONSMAP_FILE = os.path.join(THIS_SCRIPT_DIR, "../share/actionsmap.yml") def ordered_yaml_load(stream): diff --git a/doc/helper_doc_template.md b/doc/helper_doc_template.md index d41c0b6e9..ac5d455fb 100644 --- a/doc/helper_doc_template.md +++ b/doc/helper_doc_template.md @@ -52,7 +52,7 @@ Doc auto-generated by [this script](https://github.com/YunoHost/yunohost/blob/{{ {{ h.details }} {%- endif %} -[Dude, show me the code!](https://github.com/YunoHost/yunohost/blob/{{ current_commit }}/data/helpers.d/{{ category }}#L{{ h.line + 1 }}) +[Dude, show me the code!](https://github.com/YunoHost/yunohost/blob/{{ current_commit }}/helpers/{{ category }}#L{{ h.line + 1 }}) [/details] ---------------- {% endfor %} diff --git a/hooks/conf_regen/01-yunohost b/hooks/conf_regen/01-yunohost index 963bd49d3..027be8020 100755 --- a/hooks/conf_regen/01-yunohost +++ b/hooks/conf_regen/01-yunohost @@ -8,7 +8,7 @@ do_init_regen() { exit 1 fi - cd /usr/share/yunohost/templates/yunohost + cd /usr/share/yunohost/conf/yunohost [[ -d /etc/yunohost ]] || mkdir -p /etc/yunohost @@ -71,7 +71,7 @@ do_init_regen() { do_pre_regen() { pending_dir=$1 - cd /usr/share/yunohost/templates/yunohost + cd /usr/share/yunohost/conf/yunohost mkdir -p $pending_dir/etc/systemd/system mkdir -p $pending_dir/etc/cron.d/ diff --git a/hooks/conf_regen/02-ssl b/hooks/conf_regen/02-ssl index 03478552c..f27a23cf8 100755 --- a/hooks/conf_regen/02-ssl +++ b/hooks/conf_regen/02-ssl @@ -6,7 +6,7 @@ ssl_dir="/usr/share/yunohost/yunohost-config/ssl/yunoCA" ynh_ca="/etc/yunohost/certs/yunohost.org/ca.pem" ynh_crt="/etc/yunohost/certs/yunohost.org/crt.pem" ynh_key="/etc/yunohost/certs/yunohost.org/key.pem" -openssl_conf="/usr/share/yunohost/templates/ssl/openssl.cnf" +openssl_conf="/usr/share/yunohost/conf/ssl/openssl.cnf" regen_local_ca() { @@ -26,7 +26,7 @@ regen_local_ca() { RANDFILE=.rnd openssl rand -hex 19 >serial rm -f index.txt touch index.txt - cp /usr/share/yunohost/templates/ssl/openssl.cnf openssl.ca.cnf + cp /usr/share/yunohost/conf/ssl/openssl.cnf openssl.ca.cnf sed -i "s/yunohost.org/${domain}/g" openssl.ca.cnf openssl req -x509 \ -new \ @@ -57,7 +57,7 @@ do_init_regen() { # Make sure this conf exists mkdir -p ${ssl_dir} - cp /usr/share/yunohost/templates/ssl/openssl.cnf ${ssl_dir}/openssl.ca.cnf + cp /usr/share/yunohost/conf/ssl/openssl.cnf ${ssl_dir}/openssl.ca.cnf # create default certificates if [[ ! -f "$ynh_ca" ]]; then @@ -99,7 +99,7 @@ do_init_regen() { do_pre_regen() { pending_dir=$1 - cd /usr/share/yunohost/templates/ssl + cd /usr/share/yunohost/conf/ssl install -D -m 644 openssl.cnf "${pending_dir}/${ssl_dir}/openssl.cnf" } diff --git a/hooks/conf_regen/03-ssh b/hooks/conf_regen/03-ssh index dd1589204..0c700c01f 100755 --- a/hooks/conf_regen/03-ssh +++ b/hooks/conf_regen/03-ssh @@ -7,7 +7,7 @@ set -e do_pre_regen() { pending_dir=$1 - cd /usr/share/yunohost/templates/ssh + cd /usr/share/yunohost/conf/ssh # do not listen to IPv6 if unavailable [[ -f /proc/net/if_inet6 ]] && ipv6_enabled=true || ipv6_enabled=false diff --git a/hooks/conf_regen/06-slapd b/hooks/conf_regen/06-slapd index 0c52e4998..616b383ec 100755 --- a/hooks/conf_regen/06-slapd +++ b/hooks/conf_regen/06-slapd @@ -4,8 +4,8 @@ set -e 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" +config="/usr/share/yunohost/conf/slapd/config.ldif" +db_init="/usr/share/yunohost/conf/slapd/db_init.ldif" do_init_regen() { if [[ $EUID -ne 0 ]]; then @@ -109,7 +109,7 @@ do_pre_regen() { schema_dir="${ldap_dir}/schema" mkdir -p "$ldap_dir" "$schema_dir" - cd /usr/share/yunohost/templates/slapd + cd /usr/share/yunohost/conf/slapd # copy configuration files cp -a ldap.conf "$ldap_dir" diff --git a/hooks/conf_regen/09-nslcd b/hooks/conf_regen/09-nslcd index ff1c05433..9d5e5e538 100755 --- a/hooks/conf_regen/09-nslcd +++ b/hooks/conf_regen/09-nslcd @@ -10,7 +10,7 @@ do_init_regen() { do_pre_regen() { pending_dir=$1 - cd /usr/share/yunohost/templates/nslcd + cd /usr/share/yunohost/conf/nslcd install -D -m 644 nslcd.conf "${pending_dir}/etc/nslcd.conf" } diff --git a/hooks/conf_regen/12-metronome b/hooks/conf_regen/12-metronome index 5dfa7b5dc..4802b750e 100755 --- a/hooks/conf_regen/12-metronome +++ b/hooks/conf_regen/12-metronome @@ -5,7 +5,7 @@ set -e do_pre_regen() { pending_dir=$1 - cd /usr/share/yunohost/templates/metronome + cd /usr/share/yunohost/conf/metronome # create directories for pending conf metronome_dir="${pending_dir}/etc/metronome" diff --git a/hooks/conf_regen/15-nginx b/hooks/conf_regen/15-nginx index b585e3cb5..c1d943681 100755 --- a/hooks/conf_regen/15-nginx +++ b/hooks/conf_regen/15-nginx @@ -10,7 +10,7 @@ do_init_regen() { exit 1 fi - cd /usr/share/yunohost/templates/nginx + cd /usr/share/yunohost/conf/nginx nginx_dir="/etc/nginx" nginx_conf_dir="${nginx_dir}/conf.d" @@ -47,7 +47,7 @@ do_init_regen() { do_pre_regen() { pending_dir=$1 - cd /usr/share/yunohost/templates/nginx + cd /usr/share/yunohost/conf/nginx nginx_dir="${pending_dir}/etc/nginx" nginx_conf_dir="${nginx_dir}/conf.d" diff --git a/hooks/conf_regen/19-postfix b/hooks/conf_regen/19-postfix index 7865cd312..4a7325c41 100755 --- a/hooks/conf_regen/19-postfix +++ b/hooks/conf_regen/19-postfix @@ -7,7 +7,7 @@ set -e do_pre_regen() { pending_dir=$1 - cd /usr/share/yunohost/templates/postfix + cd /usr/share/yunohost/conf/postfix postfix_dir="${pending_dir}/etc/postfix" mkdir -p "$postfix_dir" diff --git a/hooks/conf_regen/25-dovecot b/hooks/conf_regen/25-dovecot index e95816604..9a51d0363 100755 --- a/hooks/conf_regen/25-dovecot +++ b/hooks/conf_regen/25-dovecot @@ -7,7 +7,7 @@ set -e do_pre_regen() { pending_dir=$1 - cd /usr/share/yunohost/templates/dovecot + cd /usr/share/yunohost/conf/dovecot dovecot_dir="${pending_dir}/etc/dovecot" mkdir -p "${dovecot_dir}/global_script" diff --git a/hooks/conf_regen/31-rspamd b/hooks/conf_regen/31-rspamd index 72a35fdcc..536aec7c2 100755 --- a/hooks/conf_regen/31-rspamd +++ b/hooks/conf_regen/31-rspamd @@ -5,7 +5,7 @@ set -e do_pre_regen() { pending_dir=$1 - cd /usr/share/yunohost/templates/rspamd + cd /usr/share/yunohost/conf/rspamd install -D -m 644 metrics.local.conf \ "${pending_dir}/etc/rspamd/local.d/metrics.conf" diff --git a/hooks/conf_regen/34-mysql b/hooks/conf_regen/34-mysql index 13730e0bb..d7ddf1a90 100755 --- a/hooks/conf_regen/34-mysql +++ b/hooks/conf_regen/34-mysql @@ -6,7 +6,7 @@ set -e do_pre_regen() { pending_dir=$1 - cd /usr/share/yunohost/templates/mysql + cd /usr/share/yunohost/conf/mysql # Nothing to do } diff --git a/hooks/conf_regen/37-mdns b/hooks/conf_regen/37-mdns index faa581efa..ff674c391 100755 --- a/hooks/conf_regen/37-mdns +++ b/hooks/conf_regen/37-mdns @@ -29,7 +29,7 @@ do_init_regen() { do_pre_regen() { pending_dir="$1" - cd /usr/share/yunohost/templates/mdns + cd /usr/share/yunohost/conf/mdns mkdir -p ${pending_dir}/etc/systemd/system/ cp yunomdns.service ${pending_dir}/etc/systemd/system/ diff --git a/hooks/conf_regen/43-dnsmasq b/hooks/conf_regen/43-dnsmasq index 13c442158..406762ecb 100755 --- a/hooks/conf_regen/43-dnsmasq +++ b/hooks/conf_regen/43-dnsmasq @@ -6,7 +6,7 @@ set -e do_pre_regen() { pending_dir=$1 - cd /usr/share/yunohost/templates/dnsmasq + cd /usr/share/yunohost/conf/dnsmasq # create directory for pending conf dnsmasq_dir="${pending_dir}/etc/dnsmasq.d" diff --git a/hooks/conf_regen/46-nsswitch b/hooks/conf_regen/46-nsswitch index 2c984a905..cc34d0277 100755 --- a/hooks/conf_regen/46-nsswitch +++ b/hooks/conf_regen/46-nsswitch @@ -10,7 +10,7 @@ do_init_regen() { do_pre_regen() { pending_dir=$1 - cd /usr/share/yunohost/templates/nsswitch + cd /usr/share/yunohost/conf/nsswitch install -D -m 644 nsswitch.conf "${pending_dir}/etc/nsswitch.conf" } diff --git a/hooks/conf_regen/52-fail2ban b/hooks/conf_regen/52-fail2ban index 6cbebbfb1..8129e977d 100755 --- a/hooks/conf_regen/52-fail2ban +++ b/hooks/conf_regen/52-fail2ban @@ -7,7 +7,7 @@ set -e do_pre_regen() { pending_dir=$1 - cd /usr/share/yunohost/templates/fail2ban + cd /usr/share/yunohost/conf/fail2ban fail2ban_dir="${pending_dir}/etc/fail2ban" mkdir -p "${fail2ban_dir}/filter.d" diff --git a/src/diagnosis/10-ip.py b/src/diagnosis/10-ip.py index 408019668..bc00fe25c 100644 --- a/src/diagnosis/10-ip.py +++ b/src/diagnosis/10-ip.py @@ -152,7 +152,7 @@ class IPDiagnoser(Diagnoser): # 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" + "/usr/share/yunohost/conf/dnsmasq/plain/resolv.dnsmasq.conf" ) resolvers = [ r.split(" ")[1] diff --git a/src/diagnosis/24-mail.py b/src/diagnosis/24-mail.py index c5af4bbc6..f29b2786d 100644 --- a/src/diagnosis/24-mail.py +++ b/src/diagnosis/24-mail.py @@ -14,7 +14,7 @@ from yunohost.domain import _get_maindomain, domain_list from yunohost.settings import settings_get from yunohost.utils.dns import dig -DEFAULT_DNS_BLACKLIST = "/usr/share/yunohost/other/dnsbl_list.yml" +DEFAULT_DNS_BLACKLIST = "/usr/share/yunohost/dnsbl_list.yml" class MailDiagnoser(Diagnoser): diff --git a/src/dns.py b/src/dns.py index 534ade918..cad0ff2f5 100644 --- a/src/dns.py +++ b/src/dns.py @@ -50,7 +50,7 @@ from yunohost.hook import hook_callback logger = getActionLogger("yunohost.domain") -DOMAIN_REGISTRAR_LIST_PATH = "/usr/share/yunohost/other/registrar_list.toml" +DOMAIN_REGISTRAR_LIST_PATH = "/usr/share/yunohost/registrar_list.toml" def domain_dns_suggest(domain): diff --git a/src/domain.py b/src/domain.py index 0bd84ea82..3c244f9cf 100644 --- a/src/domain.py +++ b/src/domain.py @@ -44,7 +44,7 @@ from yunohost.log import is_unit_operation logger = getActionLogger("yunohost.domain") -DOMAIN_CONFIG_PATH = "/usr/share/yunohost/other/config_domain.toml" +DOMAIN_CONFIG_PATH = "/usr/share/yunohost/config_domain.toml" DOMAIN_SETTINGS_DIR = "/etc/yunohost/domains" # Lazy dev caching to avoid re-query ldap every time we need the domain list diff --git a/src/service.py b/src/service.py index 73534e2e3..527ca5813 100644 --- a/src/service.py +++ b/src/service.py @@ -48,7 +48,7 @@ from moulinette.utils.filesystem import ( MOULINETTE_LOCK = "/var/run/moulinette_yunohost.lock" SERVICES_CONF = "/etc/yunohost/services.yml" -SERVICES_CONF_BASE = "/usr/share/yunohost/templates/yunohost/services.yml" +SERVICES_CONF_BASE = "/usr/share/yunohost/conf/yunohost/services.yml" logger = getActionLogger("yunohost.service") diff --git a/src/utils/config.py b/src/utils/config.py index 5d1d1f9d2..352e25be6 100644 --- a/src/utils/config.py +++ b/src/utils/config.py @@ -192,7 +192,7 @@ def evaluate_simple_js_expression(expr, context={}): class ConfigPanel: entity_type = "config" save_path_tpl: Union[str, None] = None - config_path_tpl = "/usr/share/yunohost/other/config_{entity_type}.toml" + config_path_tpl = "/usr/share/yunohost/config_{entity_type}.toml" save_mode = "full" @classmethod diff --git a/src/utils/password.py b/src/utils/password.py index 188850183..04a1ed272 100644 --- a/src/utils/password.py +++ b/src/utils/password.py @@ -36,7 +36,7 @@ SMALL_PWD_LIST = [ "rpi", ] -MOST_USED_PASSWORDS = "/usr/share/yunohost/other/password/100000-most-used.txt" +MOST_USED_PASSWORDS = "/usr/share/yunohost/password/100000-most-used-passwords.txt" # Length, digits, lowers, uppers, others STRENGTH_LEVELS = [ diff --git a/tests/add_missing_keys.py b/tests/add_missing_keys.py index 30c6c6640..1bf335418 100644 --- a/tests/add_missing_keys.py +++ b/tests/add_missing_keys.py @@ -24,11 +24,11 @@ def find_expected_string_keys(): 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 = glob.glob("src/*.py") + python_files.extend(glob.glob("src/utils/*.py")) + python_files.extend(glob.glob("src/data_migrations/*.py")) + python_files.extend(glob.glob("src/authenticators/*.py")) + python_files.extend(glob.glob("src/diagnosis/*.py")) python_files.append("bin/yunohost") for python_file in python_files: @@ -51,7 +51,7 @@ def find_expected_string_keys(): # 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("src/diagnosis/*.py"): content = open(python_file).read() for m in p3.findall(content): if m.endswith("_"): @@ -63,14 +63,14 @@ def find_expected_string_keys(): ] # For each migration, expect to find "migration_description_" - for path in glob.glob("src/yunohost/data_migrations/*.py"): + for path in glob.glob("src/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") + open("conf/yunohost/services.yml") ).items(): if info is None: continue @@ -79,7 +79,7 @@ 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'" + cmd = "grep -hr '@is_unit_operation' src/ -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") ): @@ -94,14 +94,14 @@ def find_expected_string_keys(): # 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() + content = open("src/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(): + for category in yaml.safe_load(open("share/actionsmap.yml")).values(): if "actions" not in category.keys(): continue for action in category["actions"].values(): diff --git a/tests/test_actionmap.py b/tests/test_actionmap.py index 0b8abb152..e482bdfe1 100644 --- a/tests/test_actionmap.py +++ b/tests/test_actionmap.py @@ -2,4 +2,4 @@ import yaml def test_yaml_syntax(): - yaml.safe_load(open("data/actionsmap/yunohost.yml")) + yaml.safe_load(open("share/actionsmap.yml")) diff --git a/tests/test_i18n_keys.py b/tests/test_i18n_keys.py index 103241085..d0ae75bfc 100644 --- a/tests/test_i18n_keys.py +++ b/tests/test_i18n_keys.py @@ -25,11 +25,11 @@ def find_expected_string_keys(): 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 = glob.glob("src/*.py") + python_files.extend(glob.glob("src/utils/*.py")) + python_files.extend(glob.glob("src/data_migrations/*.py")) + python_files.extend(glob.glob("src/authenticators/*.py")) + python_files.extend(glob.glob("src/diagnosis/*.py")) python_files.append("bin/yunohost") for python_file in python_files: @@ -52,7 +52,7 @@ def find_expected_string_keys(): # 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("src/diagnosis/*.py"): content = open(python_file).read() for m in p3.findall(content): if m.endswith("_"): @@ -64,14 +64,14 @@ def find_expected_string_keys(): ] # For each migration, expect to find "migration_description_" - for path in glob.glob("src/yunohost/data_migrations/*.py"): + for path in glob.glob("src/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") + open("conf/yunohost/services.yml") ).items(): if info is None: continue @@ -80,7 +80,7 @@ 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'" + cmd = "grep -hr '@is_unit_operation' src/ -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") ): @@ -95,14 +95,14 @@ def find_expected_string_keys(): # 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() + content = open("src/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(): + for category in yaml.safe_load(open("src/actionsmap.yml")).values(): if "actions" not in category.keys(): continue for action in category["actions"].values(): @@ -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("share/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("share/config_domain.toml")) for panel in domain_config.values(): if not isinstance(panel, dict): continue diff --git a/tox.ini b/tox.ini index f00cc7fb8..e5cffb696 100644 --- a/tox.ini +++ b/tox.ini @@ -8,8 +8,8 @@ deps = py39-black-{run,check}: black py39-mypy: mypy >= 0.900 commands = - py39-lint: flake8 src doc data tests --ignore E402,E501,E203,W503 --exclude src/yunohost/vendor - py39-invalidcode: flake8 src data --exclude src/yunohost/tests,src/yunohost/vendor --select F,E722,W605 + py39-lint: flake8 src doc data tests --ignore E402,E501,E203,W503 --exclude src/vendor + py39-invalidcode: flake8 src data --exclude src/tests,src/vendor --select F,E722,W605 py39-black-check: black --check --diff src doc data tests py39-black-run: black src doc data tests - py39-mypy: mypy --ignore-missing-import --install-types --non-interactive --follow-imports silent src/yunohost/ --exclude (acme_tiny|data_migrations) + py39-mypy: mypy --ignore-missing-import --install-types --non-interactive --follow-imports silent src/ --exclude (acme_tiny|data_migrations) From ebf9d522e05f01038ffbe948e9c23e162c57baa9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 10 Nov 2021 19:51:11 +0100 Subject: [PATCH 0917/1155] Adapt diagnosers code to load them as python modules instead of hooks --- src/diagnosis.py | 68 ++++++++++++++++++----------- src/diagnosis/00-basesystem.py | 21 +++++---- src/diagnosis/10-ip.py | 13 +++--- src/diagnosis/12-dnsrecords.py | 12 +++-- src/diagnosis/14-ports.py | 6 +-- src/diagnosis/21-web.py | 6 +-- src/diagnosis/24-mail.py | 11 +++-- src/diagnosis/30-services.py | 6 +-- src/diagnosis/50-systemresources.py | 6 +-- src/diagnosis/70-regenconf.py | 6 +-- src/diagnosis/80-apps.py | 6 +-- 11 files changed, 74 insertions(+), 87 deletions(-) diff --git a/src/diagnosis.py b/src/diagnosis.py index 4ac5e2731..a7b6c725c 100644 --- a/src/diagnosis.py +++ b/src/diagnosis.py @@ -27,6 +27,8 @@ import re import os import time +import glob +from importlib import import_module from moulinette import m18n, Moulinette from moulinette.utils import log @@ -38,7 +40,6 @@ from moulinette.utils.filesystem import ( ) from yunohost.utils.error import YunohostError, YunohostValidationError -from yunohost.hook import hook_list, hook_exec logger = log.getActionLogger("yunohost.diagnosis") @@ -48,15 +49,13 @@ DIAGNOSIS_SERVER = "diagnosis.yunohost.org" def diagnosis_list(): - all_categories_names = [h for h, _ in _list_diagnosis_categories()] - return {"categories": all_categories_names} + return {"categories": _list_diagnosis_categories()} def diagnosis_get(category, item): # Get all the categories - all_categories = _list_diagnosis_categories() - all_categories_names = [c for c, _ in all_categories] + all_categories_names = _list_diagnosis_categories() if category not in all_categories_names: raise YunohostValidationError( @@ -84,8 +83,7 @@ def diagnosis_show( return # Get all the categories - all_categories = _list_diagnosis_categories() - all_categories_names = [category for category, _ in all_categories] + all_categories_names = _list_diagnosis_categories() # Check the requested category makes sense if categories == []: @@ -174,8 +172,7 @@ def diagnosis_run( return # Get all the categories - all_categories = _list_diagnosis_categories() - all_categories_names = [category for category, _ in all_categories] + all_categories_names = _list_diagnosis_categories() # Check the requested category makes sense if categories == []: @@ -192,10 +189,11 @@ def diagnosis_run( 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] + + diagnoser = _load_diagnoser(category) try: - code, report = hook_exec(path, args={"force": force}, env=None) + code, report = diagnoser.diagnose(force=force) except Exception: import traceback @@ -275,8 +273,7 @@ def _diagnosis_ignore(add_filter=None, remove_filter=None, list=False): def validate_filter_criterias(filter_): # Get all the categories - all_categories = _list_diagnosis_categories() - all_categories_names = [category for category, _ in all_categories] + all_categories_names = _list_diagnosis_categories() # Sanity checks for the provided arguments if len(filter_) == 0: @@ -404,12 +401,8 @@ def add_ignore_flag_to_issues(report): class Diagnoser: - def __init__(self, args, env, loggers): + def __init__(self): - # 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 {} self.cache_file = Diagnoser.cache_file(self.id_) self.description = Diagnoser.get_description(self.id_) @@ -424,10 +417,10 @@ class Diagnoser: os.makedirs(DIAGNOSIS_CACHE) return write_to_json(self.cache_file, report) - def diagnose(self): + def diagnose(self, force=False): if ( - not self.args.get("force", False) + not force and self.cached_time_ago() < self.cache_duration ): self.logger_debug("Cache still valid : %s" % self.cache_file) @@ -666,13 +659,36 @@ class Diagnoser: 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 + paths = glob.glob(os.path.dirname(__file__) + "/diagnosis/??-*.py") + names = sorted([os.path.basename(path)[: -len(".py")] for path in paths]) + + return names + + +def _load_diagnoser(diagnoser_name): + + logger.debug(f"Loading diagnoser {diagnoser_name}") + + paths = glob.glob(os.path.dirname(__file__) + f"/diagnosis/??-{diagnoser_name}.py") + + if len(paths) != 1: + raise YunohostError(f"Uhoh, found several matches (or none?) for diagnoser {diagnoser_name} : {paths}", raw_msg=True) + + module_id = os.path.basename(paths[0][: -len(".py")]) + + try: + # this is python builtin method to import a module using a name, we + # use that to import the migration as a python object so we'll be + # able to run it in the next loop + module = import_module("yunohost.diagnosis.{}".format(module_id)) + return module.MyDiagnoser() + except Exception as e: + import traceback + + traceback.print_exc() + + raise YunohostError(f"Failed to load diagnoser {diagnoser_name} : {e}", raw_msg=True) def _email_diagnosis_issues(): diff --git a/src/diagnosis/00-basesystem.py b/src/diagnosis/00-basesystem.py index b472a2d32..66a2bf47b 100644 --- a/src/diagnosis/00-basesystem.py +++ b/src/diagnosis/00-basesystem.py @@ -4,13 +4,16 @@ import os import json import subprocess +from moulinette.utils import log from moulinette.utils.process import check_output 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 +logger = log.getActionLogger("yunohost.diagnosis") -class BaseSystemDiagnoser(Diagnoser): + +class MyDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 600 @@ -172,7 +175,7 @@ class BaseSystemDiagnoser(Diagnoser): try: return int(n_failures) except Exception: - self.logger_warning( + logger.warning( "Failed to parse number of recent auth failures, expected an int, got '%s'" % n_failures ) @@ -196,7 +199,7 @@ class BaseSystemDiagnoser(Diagnoser): if not os.path.exists(dpkg_log) or os.path.getmtime( cache_file ) > os.path.getmtime(dpkg_log): - self.logger_debug( + logger.debug( "Using cached results for meltdown checker, from %s" % cache_file ) return read_json(cache_file)[0]["VULNERABLE"] @@ -209,7 +212,7 @@ class BaseSystemDiagnoser(Diagnoser): # 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") + logger.debug("Running meltdown vulnerability checker") call = subprocess.Popen( "bash %s --batch json --variant 3" % SCRIPT_PATH, shell=True, @@ -231,7 +234,7 @@ class BaseSystemDiagnoser(Diagnoser): # stuff which should be the last line output = output.strip() if "\n" in output: - self.logger_debug("Original meltdown checker output : %s" % output) + logger.debug("Original meltdown checker output : %s" % output) output = output.split("\n")[-1] CVEs = json.loads(output) @@ -241,18 +244,14 @@ class BaseSystemDiagnoser(Diagnoser): import traceback traceback.print_exc() - self.logger_warning( + 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( + 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/src/diagnosis/10-ip.py b/src/diagnosis/10-ip.py index bc00fe25c..4ad4cfbfc 100644 --- a/src/diagnosis/10-ip.py +++ b/src/diagnosis/10-ip.py @@ -4,6 +4,7 @@ import re import os import random +from moulinette.utils import log from moulinette.utils.network import download_text from moulinette.utils.process import check_output from moulinette.utils.filesystem import read_file @@ -11,8 +12,10 @@ from moulinette.utils.filesystem import read_file from yunohost.diagnosis import Diagnoser from yunohost.utils.network import get_network_interfaces +logger = log.getActionLogger("yunohost.diagnosis") -class IPDiagnoser(Diagnoser): + +class MyDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 600 @@ -144,7 +147,7 @@ class IPDiagnoser(Diagnoser): ) if not any(is_default_route(r) for r in routes): - self.logger_debug( + logger.debug( "No default route for IPv%s, so assuming there's no IP address for that version" % protocol ) @@ -220,11 +223,7 @@ class IPDiagnoser(Diagnoser): try: return download_text(url, timeout=30).strip() except Exception as e: - self.logger_debug( + 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).diagnose() diff --git a/src/diagnosis/12-dnsrecords.py b/src/diagnosis/12-dnsrecords.py index d45ebd9b8..305fda79b 100644 --- a/src/diagnosis/12-dnsrecords.py +++ b/src/diagnosis/12-dnsrecords.py @@ -6,6 +6,7 @@ import re from datetime import datetime, timedelta from publicsuffix2 import PublicSuffixList +from moulinette.utils import log from moulinette.utils.process import check_output from yunohost.utils.dns import ( @@ -18,8 +19,9 @@ from yunohost.diagnosis import Diagnoser from yunohost.domain import domain_list, _get_maindomain from yunohost.dns import _build_dns_conf, _get_dns_zone_for_domain +logger = log.getActionLogger("yunohost.diagnosis") -class DNSRecordsDiagnoser(Diagnoser): +class MyDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 600 @@ -31,7 +33,7 @@ class DNSRecordsDiagnoser(Diagnoser): major_domains = domain_list(exclude_subdomains=True)["domains"] for domain in major_domains: - self.logger_debug("Diagnosing DNS conf for %s" % domain) + logger.debug("Diagnosing DNS conf for %s" % domain) for report in self.check_domain( domain, @@ -223,7 +225,7 @@ class DNSRecordsDiagnoser(Diagnoser): ) ) else: - self.logger_debug("Dyndns domain: %s" % (domain)) + logger.debug("Dyndns domain: %s" % (domain)) continue expire_in = expire_date - datetime.now() @@ -298,7 +300,3 @@ class DNSRecordsDiagnoser(Diagnoser): return datetime.strptime(match.group(1), "%d-%b-%Y") return "expiration_not_found" - - -def main(args, env, loggers): - return DNSRecordsDiagnoser(args, env, loggers).diagnose() diff --git a/src/diagnosis/14-ports.py b/src/diagnosis/14-ports.py index 7581a1ac6..e339a946c 100644 --- a/src/diagnosis/14-ports.py +++ b/src/diagnosis/14-ports.py @@ -6,7 +6,7 @@ from yunohost.diagnosis import Diagnoser from yunohost.service import _get_services -class PortsDiagnoser(Diagnoser): +class MyDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 600 @@ -146,7 +146,3 @@ class PortsDiagnoser(Diagnoser): "diagnosis_ports_forwarding_tip", ], ) - - -def main(args, env, loggers): - return PortsDiagnoser(args, env, loggers).diagnose() diff --git a/src/diagnosis/21-web.py b/src/diagnosis/21-web.py index 450296e7e..3d9fb9f73 100644 --- a/src/diagnosis/21-web.py +++ b/src/diagnosis/21-web.py @@ -13,7 +13,7 @@ from yunohost.utils.dns import is_special_use_tld DIAGNOSIS_SERVER = "diagnosis.yunohost.org" -class WebDiagnoser(Diagnoser): +class MyDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 600 @@ -198,7 +198,3 @@ class WebDiagnoser(Diagnoser): summary="diagnosis_http_partially_unreachable", details=[detail.replace("error_http_check", "diagnosis_http")], ) - - -def main(args, env, loggers): - return WebDiagnoser(args, env, loggers).diagnose() diff --git a/src/diagnosis/24-mail.py b/src/diagnosis/24-mail.py index f29b2786d..af7af1a16 100644 --- a/src/diagnosis/24-mail.py +++ b/src/diagnosis/24-mail.py @@ -6,6 +6,7 @@ import re from subprocess import CalledProcessError +from moulinette.utils import log from moulinette.utils.process import check_output from moulinette.utils.filesystem import read_yaml @@ -16,8 +17,10 @@ from yunohost.utils.dns import dig DEFAULT_DNS_BLACKLIST = "/usr/share/yunohost/dnsbl_list.yml" +logger = log.getActionLogger("yunohost.diagnosis") -class MailDiagnoser(Diagnoser): + +class MyDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 600 @@ -42,7 +45,7 @@ class MailDiagnoser(Diagnoser): "check_queue", # i18n: diagnosis_mail_queue_ok ] for check in checks: - self.logger_debug("Running " + check) + logger.debug("Running " + check) reports = list(getattr(self, check)()) for report in reports: yield report @@ -292,7 +295,3 @@ class MailDiagnoser(Diagnoser): if 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/src/diagnosis/30-services.py b/src/diagnosis/30-services.py index adbcc73b9..daf86ab1e 100644 --- a/src/diagnosis/30-services.py +++ b/src/diagnosis/30-services.py @@ -6,7 +6,7 @@ from yunohost.diagnosis import Diagnoser from yunohost.service import service_status -class ServicesDiagnoser(Diagnoser): +class MyDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 300 @@ -41,7 +41,3 @@ class ServicesDiagnoser(Diagnoser): item["summary"] = "diagnosis_services_running" yield item - - -def main(args, env, loggers): - return ServicesDiagnoser(args, env, loggers).diagnose() diff --git a/src/diagnosis/50-systemresources.py b/src/diagnosis/50-systemresources.py index a662e392e..265e62acb 100644 --- a/src/diagnosis/50-systemresources.py +++ b/src/diagnosis/50-systemresources.py @@ -9,7 +9,7 @@ from moulinette.utils.process import check_output from yunohost.diagnosis import Diagnoser -class SystemResourcesDiagnoser(Diagnoser): +class MyDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 300 @@ -214,7 +214,3 @@ def round_(n): if n > 10: n = int(round(n)) return n - - -def main(args, env, loggers): - return SystemResourcesDiagnoser(args, env, loggers).diagnose() diff --git a/src/diagnosis/70-regenconf.py b/src/diagnosis/70-regenconf.py index 8ccbeed58..63f6f2b32 100644 --- a/src/diagnosis/70-regenconf.py +++ b/src/diagnosis/70-regenconf.py @@ -9,7 +9,7 @@ from yunohost.regenconf import _get_regenconf_infos, _calculate_hash from moulinette.utils.filesystem import read_file -class RegenconfDiagnoser(Diagnoser): +class MyDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 300 @@ -70,7 +70,3 @@ class RegenconfDiagnoser(Diagnoser): for path, hash_ in infos["conffiles"].items(): if hash_ != _calculate_hash(path): yield {"path": path, "category": category} - - -def main(args, env, loggers): - return RegenconfDiagnoser(args, env, loggers).diagnose() diff --git a/src/diagnosis/80-apps.py b/src/diagnosis/80-apps.py index 5aec48ed8..e62acaa1f 100644 --- a/src/diagnosis/80-apps.py +++ b/src/diagnosis/80-apps.py @@ -7,7 +7,7 @@ from yunohost.app import app_list from yunohost.diagnosis import Diagnoser -class AppDiagnoser(Diagnoser): +class MyDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 300 @@ -90,7 +90,3 @@ class AppDiagnoser(Diagnoser): == 0 ): yield ("error", "diagnosis_apps_deprecated_practices") - - -def main(args, env, loggers): - return AppDiagnoser(args, env, loggers).diagnose() From 248d7f5678437ef0f25b0783e90fda510eae213f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 10 Nov 2021 19:53:05 +0100 Subject: [PATCH 0918/1155] data_migrations -> migrations --- .../0021_migrate_to_bullseye.py | 0 .../0022_php73_to_php74_pools.py | 0 .../0023_postgresql_11_to_13.py | 0 src/{data_migrations => migrations}/__init__.py | 0 src/tools.py | 10 +++++----- tests/add_missing_keys.py | 4 ++-- tests/test_i18n_keys.py | 4 ++-- tox.ini | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) rename src/{data_migrations => migrations}/0021_migrate_to_bullseye.py (100%) rename src/{data_migrations => migrations}/0022_php73_to_php74_pools.py (100%) rename src/{data_migrations => migrations}/0023_postgresql_11_to_13.py (100%) rename src/{data_migrations => migrations}/__init__.py (100%) diff --git a/src/data_migrations/0021_migrate_to_bullseye.py b/src/migrations/0021_migrate_to_bullseye.py similarity index 100% rename from src/data_migrations/0021_migrate_to_bullseye.py rename to src/migrations/0021_migrate_to_bullseye.py diff --git a/src/data_migrations/0022_php73_to_php74_pools.py b/src/migrations/0022_php73_to_php74_pools.py similarity index 100% rename from src/data_migrations/0022_php73_to_php74_pools.py rename to src/migrations/0022_php73_to_php74_pools.py diff --git a/src/data_migrations/0023_postgresql_11_to_13.py b/src/migrations/0023_postgresql_11_to_13.py similarity index 100% rename from src/data_migrations/0023_postgresql_11_to_13.py rename to src/migrations/0023_postgresql_11_to_13.py diff --git a/src/data_migrations/__init__.py b/src/migrations/__init__.py similarity index 100% rename from src/data_migrations/__init__.py rename to src/migrations/__init__.py diff --git a/src/tools.py b/src/tools.py index a92e478de..f9de32cfe 100644 --- a/src/tools.py +++ b/src/tools.py @@ -933,12 +933,12 @@ def _get_migrations_list(): migrations = [] try: - from . import data_migrations + from . import migrations except ImportError: # not data migrations present, return empty list return migrations - migrations_path = data_migrations.__path__[0] + migrations_path = migrations.__path__[0] if not os.path.exists(migrations_path): logger.warn(m18n.n("migrations_cant_reach_migration_file", migrations_path)) @@ -973,11 +973,11 @@ def _get_migration_by_name(migration_name): """ try: - from . import data_migrations + from . import migrations except ImportError: raise AssertionError("Unable to find migration with name %s" % migration_name) - migrations_path = data_migrations.__path__[0] + migrations_path = migrations.__path__[0] migrations_found = [ x for x in os.listdir(migrations_path) @@ -1001,7 +1001,7 @@ def _load_migration(migration_file): # this is python builtin method to import a module using a name, we # use that to import the migration as a python object so we'll be # able to run it in the next loop - module = import_module("yunohost.data_migrations.{}".format(migration_id)) + module = import_module("yunohost.migrations.{}".format(migration_id)) return module.MyMigration(migration_id) except Exception as e: import traceback diff --git a/tests/add_missing_keys.py b/tests/add_missing_keys.py index 1bf335418..e1ecbf56e 100644 --- a/tests/add_missing_keys.py +++ b/tests/add_missing_keys.py @@ -26,7 +26,7 @@ def find_expected_string_keys(): python_files = glob.glob("src/*.py") python_files.extend(glob.glob("src/utils/*.py")) - python_files.extend(glob.glob("src/data_migrations/*.py")) + python_files.extend(glob.glob("src/migrations/*.py")) python_files.extend(glob.glob("src/authenticators/*.py")) python_files.extend(glob.glob("src/diagnosis/*.py")) python_files.append("bin/yunohost") @@ -63,7 +63,7 @@ def find_expected_string_keys(): ] # For each migration, expect to find "migration_description_" - for path in glob.glob("src/data_migrations/*.py"): + for path in glob.glob("src/migrations/*.py"): if "__init__" in path: continue yield "migration_description_" + os.path.basename(path)[:-3] diff --git a/tests/test_i18n_keys.py b/tests/test_i18n_keys.py index d0ae75bfc..b25d35923 100644 --- a/tests/test_i18n_keys.py +++ b/tests/test_i18n_keys.py @@ -27,7 +27,7 @@ def find_expected_string_keys(): python_files = glob.glob("src/*.py") python_files.extend(glob.glob("src/utils/*.py")) - python_files.extend(glob.glob("src/data_migrations/*.py")) + python_files.extend(glob.glob("src/migrations/*.py")) python_files.extend(glob.glob("src/authenticators/*.py")) python_files.extend(glob.glob("src/diagnosis/*.py")) python_files.append("bin/yunohost") @@ -64,7 +64,7 @@ def find_expected_string_keys(): ] # For each migration, expect to find "migration_description_" - for path in glob.glob("src/data_migrations/*.py"): + for path in glob.glob("src/migrations/*.py"): if "__init__" in path: continue yield "migration_description_" + os.path.basename(path)[:-3] diff --git a/tox.ini b/tox.ini index e5cffb696..3e3fcbf2e 100644 --- a/tox.ini +++ b/tox.ini @@ -12,4 +12,4 @@ commands = py39-invalidcode: flake8 src data --exclude src/tests,src/vendor --select F,E722,W605 py39-black-check: black --check --diff src doc data tests py39-black-run: black src doc data tests - py39-mypy: mypy --ignore-missing-import --install-types --non-interactive --follow-imports silent src/ --exclude (acme_tiny|data_migrations) + py39-mypy: mypy --ignore-missing-import --install-types --non-interactive --follow-imports silent src/ --exclude (acme_tiny|migrations) From e19330b0c2b13f32f47e1f0d51a4695893b6e210 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 10 Nov 2021 19:56:59 +0100 Subject: [PATCH 0919/1155] Aaaaand forgot to git add diagnosis/__init__.py --- src/diagnosis/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/diagnosis/__init__.py diff --git a/src/diagnosis/__init__.py b/src/diagnosis/__init__.py new file mode 100644 index 000000000..e69de29bb From 244287bdd0e1dd0cba83e50f6352dc467afe50a8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 12 Nov 2021 21:31:11 +0100 Subject: [PATCH 0920/1155] ci: Idk why dch ain't there by default anymore but meh, installing devscripts should solve the issue... --- .gitlab/ci/build.gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab/ci/build.gitlab-ci.yml b/.gitlab/ci/build.gitlab-ci.yml index 82def4eb3..db691b9d2 100644 --- a/.gitlab/ci/build.gitlab-ci.yml +++ b/.gitlab/ci/build.gitlab-ci.yml @@ -11,6 +11,7 @@ - $YNH_BUILD_DIR/*.deb .build_script: &build_script + - DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" install devscripts --no-install-recommends - cd $YNH_BUILD_DIR/$PACKAGE - VERSION=$(dpkg-parsechangelog -S Version 2>/dev/null) - VERSION_NIGHTLY="${VERSION}+$(date +%Y%m%d%H%M)" From 51d5dca03387d18842388379b5177b6e83833e4d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 13 Nov 2021 18:36:38 +0100 Subject: [PATCH 0921/1155] Make ynh_add_fpm_config more robust to some edge cases --- data/helpers.d/php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index b0e9fa59d..d6ccc52cd 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -90,9 +90,11 @@ ynh_add_fpm_config() { local old_php_fpm_config_dir=$(ynh_app_setting_get --app=$app --key=fpm_config_dir) local old_php_finalphpconf="$old_php_fpm_config_dir/pool.d/$app.conf" - ynh_backup_if_checksum_is_different --file="$old_php_finalphpconf" - - ynh_remove_fpm_config + if [[ -f "$old_php_finalphpconf" ]] + then + ynh_backup_if_checksum_is_different --file="$old_php_finalphpconf" + ynh_remove_fpm_config + fi fi # Legacy args (packager should just list their php dependency as regular apt dependencies... From 044038d1c7c254fbef4c5effe9bb3673df3d4c18 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 13 Nov 2021 20:12:52 +0100 Subject: [PATCH 0922/1155] ci: Attempt to ignore branches created by the CI .. --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d1cb36b73..ba0d9fd3e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -19,6 +19,8 @@ workflow: - 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-/ # Ignore branches names ci-* (created by those pipelines) + when: never - 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 8d5e69566b1b54926ca2557b4ae979ab3ae74a23 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 13 Nov 2021 20:14:25 +0100 Subject: [PATCH 0923/1155] Fix error during mysql regenconf, no templates folder anymoar --- data/hooks/conf_regen/34-mysql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/conf_regen/34-mysql b/data/hooks/conf_regen/34-mysql index 13730e0bb..27c956ced 100755 --- a/data/hooks/conf_regen/34-mysql +++ b/data/hooks/conf_regen/34-mysql @@ -6,7 +6,7 @@ set -e do_pre_regen() { pending_dir=$1 - cd /usr/share/yunohost/templates/mysql + #cd /usr/share/yunohost/templates/mysql # Nothing to do } From 38f5352fc0f80a6ef57006a72012652dcbe3af7c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 14 Nov 2021 16:06:04 +0100 Subject: [PATCH 0924/1155] Fix -conf_ynh_settings backup/restore when /etc/yunohost/domains doesn't exist --- data/hooks/backup/20-conf_ynh_settings | 2 +- data/hooks/restore/20-conf_ynh_settings | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/hooks/backup/20-conf_ynh_settings b/data/hooks/backup/20-conf_ynh_settings index 9b56f1579..76ab0aaca 100644 --- a/data/hooks/backup/20-conf_ynh_settings +++ b/data/hooks/backup/20-conf_ynh_settings @@ -12,7 +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" +[ ! -d "/etc/yunohost/domains" ] || 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 4c4c6ed5e..2d731bd54 100644 --- a/data/hooks/restore/20-conf_ynh_settings +++ b/data/hooks/restore/20-conf_ynh_settings @@ -2,7 +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 +[ ! -d "${backup_dir}/domains" ] || 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 02e397ccc8aa099f40f905abdf5bd9ebdf121260 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 14 Nov 2021 17:43:34 +0100 Subject: [PATCH 0925/1155] ci: Fix filter on ci branches ... my regex doesn't seem to be working :| --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ba0d9fd3e..8403cb8e8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -19,7 +19,7 @@ workflow: - 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-/ # Ignore branches names ci-* (created by those pipelines) + - if: $CI_COMMIT_REF_NAME == "ci-format-dev" # Ignore black formatting branch created by the CI when: never - 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 From 697dd8b9c2289b089c1093018e351a822a6f2195 Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Sun, 14 Nov 2021 16:52:57 +0000 Subject: [PATCH 0926/1155] [CI] Format code with Black --- data/hooks/diagnosis/24-mail.py | 5 ++++- src/yunohost/log.py | 10 +++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/data/hooks/diagnosis/24-mail.py b/data/hooks/diagnosis/24-mail.py index 7b7f5f502..99a4aef28 100644 --- a/data/hooks/diagnosis/24-mail.py +++ b/data/hooks/diagnosis/24-mail.py @@ -210,7 +210,10 @@ class MailDiagnoser(Diagnoser): # Do the DNS Query status, answers = dig(query, "A") - if status != "ok" or (answers and set(answers) <= set(blacklist["non_blacklisted_return_code"])): + if status != "ok" or ( + answers + and set(answers) <= set(blacklist["non_blacklisted_return_code"]) + ): continue # Try to get the reason diff --git a/src/yunohost/log.py b/src/yunohost/log.py index 347fe2e65..0e534f9f8 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -749,8 +749,12 @@ class OperationLogger(object): # 72971 DEBUG 29739 + ynh_exit_properly # which are lines from backup-before-upgrade or restore-after-failed-upgrade ... filters = [re.compile(f_) for f_ in BORING_LOG_LINES] - filters.append(re.compile(r'\d+ \+ ')) - lines = [line for line in lines if not any(filter_.search(line) for filter_ in filters)] + filters.append(re.compile(r"\d+ \+ ")) + lines = [ + line + for line in lines + if not any(filter_.search(line) for filter_ in filters) + ] lines_to_display = [] @@ -758,7 +762,7 @@ class OperationLogger(object): rev_lines = list(reversed(lines)) for i, line in enumerate(rev_lines): if line.endswith("+ ynh_exit_properly"): - lines_to_display = reversed(rev_lines[i:i + 20]) + lines_to_display = reversed(rev_lines[i : i + 20]) break # If didnt find anything, just get the last 20 lines From 094c5f0807037149d740f34b998ef93d57f7ae6d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 14 Nov 2021 21:10:44 +0100 Subject: [PATCH 0927/1155] Attempt to fix the CI again ... for some reason it complains that pytest ain't in the image despite being installed ... --- .gitlab/ci/test.gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab/ci/test.gitlab-ci.yml b/.gitlab/ci/test.gitlab-ci.yml index 1aad46fbe..776e3474c 100644 --- a/.gitlab/ci/test.gitlab-ci.yml +++ b/.gitlab/ci/test.gitlab-ci.yml @@ -1,6 +1,7 @@ .install_debs: &install_debs - apt-get update -o Acquire::Retries=3 - DEBIAN_FRONTEND=noninteractive SUDO_FORCE_REMOVE=yes apt --assume-yes -o Dpkg::Options::="--force-confold" --allow-downgrades install ./$YNH_BUILD_DIR/*.deb + - pip3 install -U mock pip pytest pytest-cov pytest-mock pytest-sugar requests-mock tox ansi2html black jinja2 .test-stage: stage: tests From 1d33a74b4c585d42f52c549207727d8b5995ecb9 Mon Sep 17 00:00:00 2001 From: punkrockgirl Date: Fri, 5 Nov 2021 15:39:30 +0000 Subject: [PATCH 0928/1155] Translated using Weblate (Basque) Currently translated at 100.0% (705 of 705 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eu/ --- locales/eu.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/locales/eu.json b/locales/eu.json index b5f98dec5..1f69866bb 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -131,9 +131,9 @@ "diagnosis_ip_no_ipv4": "Zerbitzariak ez du dabilen IPv4rik.", "diagnosis_ip_no_ipv6": "Zerbitzariak ez du dabilen IPv6rik.", "diagnosis_ip_broken_dnsresolution": "Domeinu izenaren ebazpena kaltetuta dagoela dirudi… Suebakiren bat ote dago DNS eskaerak oztopatzen?", - "diagnosis_diskusage_low": "{mountpoint} fitxategi-sistemak ({device} euskarrian) edukieraren {free} ({free_percent}%) bakarrik ditu erabilgarri ({total} orotara). Kontuz ibili.", + "diagnosis_diskusage_low": "{mountpoint} fitxategi-sistemak ({device} euskarrian) edukieraren {free} (%{free_percent}a) bakarrik ditu erabilgarri ({total} orotara). Kontuz ibili.", "diagnosis_dns_good_conf": "DNS ezarpenak zuzen konfiguratuta daude {domain} domeinurako ({category} atala)", - "diagnosis_diskusage_verylow": "{mountpoint} fitxategi-sistemak ({device} euskarrian) edukieraren {free} ({free_percent}%) bakarrik ditu erabilgarri ({total} orotara). Zertxobait hustu beharko zenuke!", + "diagnosis_diskusage_verylow": "{mountpoint} fitxategi-sistemak ({device} euskarrian) edukieraren {free} (%{free_percent}a) bakarrik ditu erabilgarri ({total} orotara). Zertxobait hustu beharko zenuke!", "diagnosis_description_basesystem": "Sistemaren oinarria", "diagnosis_description_services": "Zerbitzuen egoeraren egiaztapena", "diagnosis_http_could_not_diagnose": "Ezinezkoa izan da domeinuak IPv{ipversion} kanpotik eskuragarri dauden egiaztatzea.", @@ -145,7 +145,7 @@ "diagnosis_description_mail": "Posta elektronikoa", "diagnosis_http_connection_error": "Arazoa konexioan: ezin izan da domeinu horretara konektatu, litekeena da eskuragarri ez egotea.", "diagnosis_description_web": "Weba", - "diagnosis_display_tip": "Aurkitu diren arazoak ikusteko joan Diagnosien atalera administrazio-webgunean, edo exekutatu 'yunohost diagnosis show --issues --human-readable' komandoak nahiago badituzu.", + "diagnosis_display_tip": "Aurkitu diren arazoak ikusteko joan administrazio-atariko Diagnostikoak atalera, edo exekutatu 'yunohost diagnosis show --issues --human-readable' komandoak nahiago badituzu.", "diagnosis_dns_point_to_doc": "Mesedez, irakurri dokumentazioa DNS erregistroekin laguntza behar baduzu.", "diagnosis_mail_ehlo_unreachable": "SMTP posta zerbitzaria ez dago eskuragarri IPv{ipversion}ko sare lokaletik kanpo eta, beraz, ez da posta elektronikoa jasotzeko gai.", "diagnosis_mail_ehlo_bad_answer_details": "Litekeena da zure zerbitzaria ez den beste gailu batek erantzun izana.", @@ -175,7 +175,7 @@ "diagnosis_failed": "Ezinezkoa izan da '{category}' ataleko diagnostikoa lortzea: {error}", "diagnosis_ip_weird_resolvconf": "DNS ebazpena badabilela dirudi, baina antza denez moldatutako /etc/resolv.conf fitxategia erabiltzen ari zara.", "diagnosis_dns_bad_conf": "DNS balio batzuk falta dira edo ez dira zuzenak {domain} domeinurako ({category} atala)", - "diagnosis_diskusage_ok": "{mountpoint} fitxategi-sistemak ({device} euskarrian) edukieraren {free} ({free_percent}%) ditu erabilgarri oraindik ({total} orotara)!", + "diagnosis_diskusage_ok": "{mountpoint} fitxategi-sistemak ({device} euskarrian) edukieraren {free} (%{free_percent}a) ditu erabilgarri oraindik ({total} orotara)!", "apps_catalog_update_success": "Aplikazioen katalogoa eguneratu da!", "certmanager_warning_subdomain_dns_record": "'{subdomain}' azpidomeinuak ez dauka '{domain}'(e)k duen IP bera. Ezaugarri batzuk ez dira erabilgarri egongo hau zuzendu arte eta ziurtagiri bat birsortu arte.", "app_argument_choice_invalid": "Hautatu ({choices}) aukeretako bat '{name}' argumenturako: '{value}' ez dago aukera horien artean", @@ -295,7 +295,7 @@ "global_settings_setting_security_experimental_enabled": "Gaitu segurtasun funtzio esperimentalak (ez ezazu egin ez badakizu zertan ari zaren!)", "good_practices_about_admin_password": "Administrazio-pasahitz berria ezartzear zaude. Pasahitzak zortzi karaktere izan beharko lituzke gutxienez, baina gomendagarria da pasahitz luzeagoa erabiltzea (esaldi bat, esaterako) edota karaktere desberdinak erabiltzea (hizki larriak, txikiak, zenbakiak eta karaktere bereziak).", "log_help_to_get_failed_log": "Ezin izan da '{desc}' eragiketa exekutatu. Mesedez, laguntza nahi baduzu partekatu eragiketa honen erregistro osoa 'yunohost log share {name}' komandoa erabiliz", - "global_settings_setting_security_webadmin_allowlist_enabled": "Baimendu IP zehatz batzuk bakarrik administrazio-webgunean.", + "global_settings_setting_security_webadmin_allowlist_enabled": "Baimendu IP zehatz batzuk bakarrik administrazio-atarian.", "group_unknown": "'{group}' taldea ezezaguna da", "group_updated": "'{group}' taldea eguneratu da", "group_update_failed": "Ezinezkoa izan da '{group}' taldea eguneratzea: {error}", @@ -408,7 +408,7 @@ "diagnosis_swap_notsomuch": "Sistemak {total} swap baino ez ditu. Gutxienez {recommended} izaten saiatu beharko zinateke sistema memoriarik gabe gera ez dadin.", "diagnosis_security_vulnerable_to_meltdown_details": "Arazoa konpontzeko, sistema eguneratu eta berrabiarazi beharko zenuke linux-en kernel berriagoa erabiltzeko (edo zerbitzariaren arduradunarekin jarri harremanetan). Ikus https://meltdownattack.com/ argibide gehiagorako.", "diagnosis_services_conf_broken": "{service} zerbitzuko konfigurazioa hondatuta dago!", - "diagnosis_services_bad_status_tip": "Zerbitzua berrabiarazten saia zaitezke eta nahikoa ez bada, aztertu zerbitzuaren erregistroa administrariaren webgunean. (komandoak nahiago badituzu yunohost service restart {service} eta yunohost service log {service} hurrenez hurren).", + "diagnosis_services_bad_status_tip": "Zerbitzua berrabiarazten saia zaitezke eta nahikoa ez bada, aztertu zerbitzuaren erregistroa administrazio-atarian. (komandoak nahiago badituzu yunohost service restart {service} eta yunohost service log {service} hurrenez hurren).", "diagnosis_mail_ehlo_unreachable_details": "Ezinezkoa izan da zure zerbitzariko 25. atakari konektatzea IPv{ipversion} erabiliz. Badirudi ez dagoela eskuragarri.
1. Arazo honen zergati ohikoena 25. ataka egoki birbideratuta ez egotea da.
2. Egiaztatu postfix zerbitzua martxan dagoela.
3. Konfigurazio konplexuagoetan: egiaztatu suebaki edo reverse-proxyak konexioa oztopatzen ez dutela.", "group_already_exist_on_system_but_removing_it": "{group} taldea existitzen da sistemaren taldeetan, baina YunoHostek ezabatuko du…", "diagnosis_mail_fcrdns_nok_details": "Lehenik eta behin zure routerraren konfigurazio gunean edo hostingaren enpresaren aukeretan alderantzizko DNSa konfiguratzen saiatu beharko zinateke {ehlo_domain} erabiliz. (Hosting enpresaren arabera, ezinbestekoa da beraiekin harremanetan jartzea).", @@ -470,7 +470,7 @@ "diagnosis_mail_fcrdns_dns_missing": "Ez da alderantzizko DNSrik ezarri IPv{ipversion}rako. Litekeena da hartzaileak posta elektroniko batzuk jaso ezin izatea edo mezuok spam modura etiketatuak izatea.", "log_backup_create": "Sortu babeskopia fitxategia", "global_settings_setting_backup_compress_tar_archives": "Babeskopia berriak sortzean, konprimitu fitxategiak (.tar.gz) konprimitu gabeko fitxategien (.tar) ordez. Aukera hau gaitzean babeskopiek espazio gutxiago beharko dute, baina hasierako prozesua luzeagoa izango da eta CPUari lan handiagoa eragingo dio.", - "global_settings_setting_security_webadmin_allowlist": "Administrazio-webgunea bisita ditzaketen IP helbideak, koma bidez bereiziak.", + "global_settings_setting_security_webadmin_allowlist": "Administrazio-ataria bisita dezaketen IP helbideak, koma bidez bereiziak.", "global_settings_key_doesnt_exists": "'{settings_key}' gakoa ez da existitzen konfigurazio orokorrean; erabilgarri dauden gakoak ikus ditzakezu 'yunohost settings list' exekutatuz", "global_settings_setting_ssowat_panel_overlay_enabled": "Gaitu SSOwat paneleko \"overlay\"a", "log_backup_restore_system": "Lehengoratu sistema babeskopia fitxategi batetik", @@ -505,12 +505,12 @@ "yunohost_already_installed": "YunoHost instalatuta dago dagoeneko", "migration_0015_not_stretch": "Debianen oraingo bertsioa ez da Stretch!", "migrations_success_forward": "{id} migrazioak amaitu du", - "migrations_to_be_ran_manually": "{id} migrazioa eskuz abiarazi behar da. Mesedez, joan Erramintak → Migrazioak atalera administrazio-webgunean edo bestela exekutatu 'yunohost tools migrations run'.", + "migrations_to_be_ran_manually": "{id} migrazioa eskuz abiarazi behar da. Mesedez, joan Erramintak → Migrazioak atalera administrazio-atarian edo bestela exekutatu 'yunohost tools migrations run'.", "permission_currently_allowed_for_all_users": "Baimen hau erabiltzaile guztiei esleitzen zaie eta baita beste talde batzuei ere. Litekeena da 'all users' baimena edo esleituta duten taldeei baimena kendu nahi izatea.", "permission_require_account": "'{permission}' baimena zerbitzarian kontua duten erabiltzaileentzat da eta, beraz, ezin da gaitu bisitarientzat.", "postinstall_low_rootfsspace": "'root' fitxategi-sistemak 10 GB edo espazio gutxiago dauka, kezkatzekoa dena! Litekeena da espaziorik gabe geratzea aurki! Gomendagarria da 'root' fitxategi-sistemak gutxienez 16 GB libre izatea. Jakinarazpen honen ondoren YunoHost instalatzen jarraitu nahi baduzu, berrabiarazi agindua '--force-diskspace' gehituz", "this_action_broke_dpkg": "Eragiketa honek dpkg/APT (sistemaren pakete kudeatzaileak) kaltetu ditu… Arazoa konpontzeko SSH bidez konektatu eta 'sudo apt install --fix-broken' edota 'sudo dpkg --configure -a' exekutatu dezakezu.", - "tools_upgrade_special_packages_explanation": "Eguneraketa bereziak atzeko planoan jarraituko du. Mesedez, ez abiarazi bestelako eragiketarik datozen ~10 minutuetan (zure hardwarearen abiaduraren arabera). Honen ondoren litekeena da saioa berriro hasi behar izatea. Eguneraketaren erregistroa Erramintak → Erregistroak (administrazio-webgunean) edo 'yunohost log list' komandoa erabiliz egongo da ikusgai.", + "tools_upgrade_special_packages_explanation": "Eguneraketa bereziak atzeko planoan jarraituko du. Mesedez, ez abiarazi bestelako eragiketarik datozen ~10 minutuetan (zure hardwarearen abiaduraren arabera). Honen ondoren litekeena da saioa berriro hasi behar izatea. Eguneraketaren erregistroa Erramintak → Erregistroak atalean (administrazio-atarian) edo 'yunohost log list' komandoa erabiliz egongo da ikusgai.", "user_import_bad_line": "{line} lerro okerra: {details}", "restore_complete": "Lehengoratzea amaitu da", "restore_extracting": "Behar diren fitxategiak ateratzen…", @@ -659,7 +659,7 @@ "port_already_opened": "{port}. ataka dagoeneko irekita dago {ip_version} konexioetarako", "user_home_creation_failed": "Ezin izan da erabiltzailearentzat '{home}' direktorioa sortu", "user_unknown": "Erabiltzaile ezezaguna: {user}", - "yunohost_postinstall_end_tip": "Instalazio ondorengo prozesua amaitu da! Sistemaren konfigurazioa bukatzeko:\n- gehitu erabiltzaile bat administrazio-webguneko 'Erabiltzaileak' atalean (edo 'yunohost user create ' komandoa erabiliz);\n- erabili 'Diagnostikoa' atala ohiko arazoei aurre hartzeko. Administrazio-webgunean abiarazi edo 'yunohost diagnosis run' exekutatu;\n- irakurri 'Finalizing your setup' eta 'Getting to know YunoHost' atalak. Dokumentazioan aurki ditzakezu: https://yunohost.org/admindoc.", + "yunohost_postinstall_end_tip": "Instalazio ondorengo prozesua amaitu da! Sistemaren konfigurazioa bukatzeko:\n- gehitu erabiltzaile bat administrazio-atariko 'Erabiltzaileak' atalean (edo 'yunohost user create ' komandoa erabiliz);\n- erabili 'Diagnostikoak' atala ohiko arazoei aurre hartzeko. Administrazio-atarian abiarazi edo 'yunohost diagnosis run' exekutatu;\n- irakurri 'Finalizing your setup' eta 'Getting to know YunoHost' atalak. Dokumentazioan aurki ditzakezu: https://yunohost.org/admindoc.", "yunohost_not_installed": "YunoHost ez da zuzen instalatu. Mesedez, exekutatu 'yunohost tools postinstall'", "migration_0019_slapd_config_will_be_overwritten": "Badirudi eskuz moldatu duzula slapd konfigurazioa. Migrazio garrantzitsu honetarako, YunoHostek slapd ezarpenak eguneratu behar ditu. Oraingo fitxategiak {conf_backup_folder}-n kopiatuko dira.", "unlimit": "Mugarik ez", From 983c1a81ba54bb30c68ed6804e021634c96c3988 Mon Sep 17 00:00:00 2001 From: dagangtie Date: Fri, 5 Nov 2021 14:31:34 +0000 Subject: [PATCH 0929/1155] Translated using Weblate (Chinese (Simplified)) Currently translated at 89.2% (629 of 705 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/zh_Hans/ --- locales/zh_Hans.json | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/locales/zh_Hans.json b/locales/zh_Hans.json index 9176ebab9..fa3b7bb03 100644 --- a/locales/zh_Hans.json +++ b/locales/zh_Hans.json @@ -625,5 +625,30 @@ "log_user_group_delete": "删除组'{}'", "log_user_group_create": "创建组'{}'", "log_user_delete": "删除用户'{}'", - "log_user_create": "添加用户'{}'" -} \ No newline at end of file + "log_user_create": "添加用户'{}'", + "domain_registrar_is_not_configured": "尚未为域 {domain} 配置注册商。", + "domain_dns_push_not_applicable": "的自动DNS配置的特征是不适用域{域}。您应该按照 https://yunohost.org/dns_config 上的文档手动配置DNS 记录。", + "disk_space_not_sufficient_update": "没有足够的磁盘空间来更新此应用程序", + "diagnosis_high_number_auth_failures": "最近出现了大量可疑的失败身份验证。您的fail2ban正在运行且配置正确,或使用自定义端口的SSH作为https://yunohost.org/解释的安全性。", + "diagnosis_apps_not_in_app_catalog": "此应用程序不在 YunoHost 的应用程序目录中。如果它过去有被删除过,您应该考虑卸载此应用程,因为它不会更新,并且可能会损害您系统的完整和安全性。", + "app_config_unable_to_apply": "无法应用配置面板值。", + "app_config_unable_to_read": "无法读取配置面板值。", + "config_forbidden_keyword": "关键字“{keyword}”是保留的,您不能创建或使用带有此 ID 的问题的配置面板。", + "config_no_panel": "未找到配置面板。", + "config_unknown_filter_key": "该过滤器钥匙“{filter_key}”有误。", + "diagnosis_apps_outdated_ynh_requirement": "此应用程序的安装 版本只需要 yunohost >= 2.x,这往往表明它与推荐的打包实践和帮助程序不是最新的。你真的应该考虑更新它。", + "disk_space_not_sufficient_install": "没有足够的磁盘空间来安装此应用程序", + "config_apply_failed": "应用新配置 失败:{错误}", + "config_cant_set_value_on_section": "无法在整个配置部分设置单个值 。", + "config_validate_color": "是有效的 RGB 十六进制颜色", + "config_validate_date": "有效日期格式为YYYY-MM-DD", + "config_validate_email": "是有效的电子邮件", + "config_validate_time": "应该是像 HH:MM 这样的有效时间", + "config_validate_url": "应该是有效的URL", + "config_version_not_supported": "不支持配置面板版本“{ version }”。", + "danger": "警告:", + "diagnosis_apps_allgood": "所有已安装的应用程序都遵守基本的打包原则", + "diagnosis_apps_deprecated_practices": "此应用程序的安装 版本仍然使用一些超旧的弃用打包原则。推荐您升级它。", + "diagnosis_apps_issue": "发现应用{ app } 存在问题", + "diagnosis_description_apps": "应用" +} From d066aab364b4f1a382a96e5e917a718defd6bbdf Mon Sep 17 00:00:00 2001 From: liimee Date: Sat, 6 Nov 2021 00:46:35 +0000 Subject: [PATCH 0930/1155] Translated using Weblate (Indonesian) Currently translated at 6.2% (44 of 705 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/id/ --- locales/id.json | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/locales/id.json b/locales/id.json index 9e26dfeeb..1f28ae7e5 100644 --- a/locales/id.json +++ b/locales/id.json @@ -1 +1,46 @@ -{} \ No newline at end of file +{ + "admin_password": "Kata sandi administrasi", + "admin_password_change_failed": "Tidak dapat mengubah kata sandi", + "admin_password_changed": "Kata sandi administrasi diubah", + "admin_password_too_long": "Harap pilih kata sandi yang lebih pendek dari 127 karakter", + "already_up_to_date": "Tak ada yang harus dilakukan. Semuanya sudah mutakhir.", + "app_action_broke_system": "Tindakan ini sepertinya telah merusak layanan-layanan penting ini: {services}", + "app_already_installed": "{app} sudah terpasang", + "app_already_up_to_date": "{app} sudah dalam versi mutakhir", + "app_argument_required": "Argumen '{name}' dibutuhkan", + "app_change_url_identical_domains": "Domain)url_path yang lama dan baru identik ('{domain}{path}'), tak ada yang perlu dilakukan.", + "app_change_url_no_script": "Aplikasi '{app_name}' belum mendukung pengubahan URL. Mungkin Anda harus memperbaruinya.", + "app_change_url_success": "URL {app} sekarang adalah {domain}{path}", + "app_id_invalid": "ID aplikasi tidak valid", + "app_install_failed": "Tidak dapat memasang {app}: {error}", + "app_install_files_invalid": "Berkas-berkas ini tidak dapat dipasang", + "app_install_script_failed": "Sebuah kesalahan terjadi pada script pemasangan aplikasi", + "app_manifest_install_ask_admin": "Pilih seorang administrator untuk aplikasi ini", + "app_manifest_install_ask_domain": "Pilih di domain mana aplikasi ini harus dipasang", + "app_not_installed": "Tidak dapat menemukan {app} di daftar aplikasi yang terpasang: {all_apps}", + "app_not_properly_removed": "{app} belum dihapus dengan benar", + "app_remove_after_failed_install": "Menghapus aplikasi mengikuti kegagalan pemasangan...", + "app_removed": "{app} dihapus", + "app_restore_failed": "Tidak dapat memulihkan {app}: {error}", + "app_upgrade_some_app_failed": "Beberapa aplikasi tidak dapat diperbarui", + "app_upgraded": "{app} diperbarui", + "apps_already_up_to_date": "Semua aplikasi sudah pada versi mutakhir", + "apps_catalog_update_success": "Katalog aplikasi telah diperbarui!", + "apps_catalog_updating": "Memperbarui katalog aplikasi...", + "ask_firstname": "Nama depan", + "ask_lastname": "Nama belakang", + "ask_main_domain": "Domain utama", + "ask_new_domain": "Domain baru", + "ask_user_domain": "Domain yang digunakan untuk alamat surel dan akun XMPP pengguna", + "app_not_correctly_installed": "{app} kelihatannya terpasang dengan salah", + "app_start_restore": "Memulihkan {app}...", + "app_unknown": "Aplikasi tak dikenal", + "ask_new_admin_password": "Kata sandi administrasi baru", + "ask_password": "Kata sandi", + "app_upgrade_app_name": "Memperbarui {app}...", + "app_upgrade_failed": "Tidak dapat memperbarui {app}: {error}", + "app_start_install": "Memasang {app}...", + "app_start_remove": "Menghapus {app}...", + "app_manifest_install_ask_password": "Pilih kata sandi administrasi untuk aplikasi ini", + "app_upgrade_several_apps": "Aplikasi-aplikasi berikut akan diperbarui: {apps}" +} From 347e972693581a720324cb42160a4d3adb1604e2 Mon Sep 17 00:00:00 2001 From: Flavio Cristoforetti Date: Mon, 8 Nov 2021 11:56:54 +0000 Subject: [PATCH 0931/1155] Translated using Weblate (Italian) Currently translated at 88.6% (625 of 705 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/it/ --- locales/it.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/locales/it.json b/locales/it.json index 1332712ef..f47223a37 100644 --- a/locales/it.json +++ b/locales/it.json @@ -629,5 +629,7 @@ "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" -} \ No newline at end of file + "disk_space_not_sufficient_install": "Non c'è abbastanza spazio libero per installare questa applicazione", + "app_config_unable_to_apply": "Applicazione dei valori nel pannello di configurazione non riuscita.", + "app_config_unable_to_read": "Lettura dei valori nel pannello di configurazione non riuscita." +} From cfb05e0ca50f5e335195d37bdac1d01fb8bd5d2d Mon Sep 17 00:00:00 2001 From: Tymofii-Lytvynenko Date: Wed, 10 Nov 2021 11:56:01 +0000 Subject: [PATCH 0932/1155] Translated using Weblate (Ukrainian) Currently translated at 100.0% (705 of 705 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 648c97fca..4baf6d20f 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -247,8 +247,8 @@ "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 тут. Ви перебуваєте або в контейнері, або ваше ядро не підтримує його", - "ip6tables_unavailable": "Ви не можете грати з ip6tables тут. Ви перебуваєте або в контейнері, або ваше ядро не підтримує його", + "iptables_unavailable": "Ви не можете відтворювати з iptables тут. Ви перебуваєте або в контейнері, або ваше ядро не підтримує його", + "ip6tables_unavailable": "Ви не можете відтворювати з ip6tables тут. Ви перебуваєте або в контейнері, або ваше ядро не підтримує його", "invalid_regex": "Неприпустимий regex: '{regex}'", "installation_complete": "Установлення завершено", "hook_name_unknown": "Невідома назва хука '{name}'", From d9b53b08ea58140f546d3e794c791b2b0e66b2e8 Mon Sep 17 00:00:00 2001 From: Tommi Date: Sun, 14 Nov 2021 12:17:12 +0000 Subject: [PATCH 0933/1155] Translated using Weblate (Italian) Currently translated at 99.8% (704 of 705 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/it/ --- locales/it.json | 80 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 3 deletions(-) diff --git a/locales/it.json b/locales/it.json index f47223a37..7f3997839 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}' invece di '{value}'", + "app_argument_choice_invalid": "Scegli un opzione valida per il parametro '{name}': '{value}' non è fra le opzioni disponibili ('{choices}')", "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", @@ -301,7 +301,7 @@ "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", "app_manifest_install_ask_password": "Scegli una password di amministrazione per quest'applicazione", - "app_manifest_install_ask_path": "Scegli il percorso dove installare quest'applicazione", + "app_manifest_install_ask_path": "Scegli il percorso URL (dopo il dominio) 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}' è già utilizzato come URL aggiuntivo per il permesso '{permission}'", @@ -631,5 +631,79 @@ "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", "app_config_unable_to_apply": "Applicazione dei valori nel pannello di configurazione non riuscita.", - "app_config_unable_to_read": "Lettura dei valori nel pannello di configurazione non riuscita." + "app_config_unable_to_read": "Lettura dei valori nel pannello di configurazione non riuscita.", + "diagnosis_apps_issue": "È stato rilevato un errore per l’app {app}", + "global_settings_setting_security_nginx_redirect_to_https": "Reindirizza richieste HTTP a HTTPs di default (NON DISABILITARE a meno che tu non sappia veramente bene cosa stai facendo!)", + "diagnosis_http_special_use_tld": "Il dominio {domain} è basato su un dominio di primo livello (TLD) dall’uso speciale, come .local o .test, perciò non è previsto che sia esposto al di fuori della rete locale.", + "domain_dns_conf_special_use_tld": "Questo dominio è basato su un dominio di primo livello (TLD) dall’uso speciale, come .local o .test, perciò non è previsto abbia reali record DNS.", + "domain_dns_push_not_applicable": "La configurazione automatica del DNS non è applicabile al dominio {domain}. Dovresti configurare i tuoi record DNS manualmente, seguendo la documentazione su https://yunohost.org/dns_config.", + "domain_dns_registrar_not_supported": "YunoHost non è riuscito a riconoscere quale registrar sta gestendo questo dominio. Dovresti configurare i tuoi record DNS manualmente, seguendo la documentazione.", + "domain_dns_registrar_experimental": "Per ora, il collegamento con le API di **{registrar}** non è stata opportunamente testata e revisionata dalla comunità di YunoHost. Questa funzionalità è **altamente sperimentale**, fai attenzione!", + "domain_dns_push_failed_to_authenticate": "L’autenticazione sulle API del registrar per il dominio '{domain}' è fallita. Probabilmente le credenziali non sono corrette. (Error: {error})", + "domain_dns_push_failed_to_list": "Il reperimento dei record attuali usando le API del registrar è fallito: {error}", + "domain_dns_push_already_up_to_date": "I record sono aggiornati, nulla da fare.", + "domain_dns_pushing": "Sincronizzando i record DNS…", + "domain_config_mail_out": "Email in uscita", + "domain_config_xmpp": "Messaggistica (XMPP)", + "domain_config_auth_token": "Token di autenticazione", + "domain_config_auth_key": "Chiave di autenticazione", + "domain_config_auth_secret": "Autenticazione segreta", + "domain_config_api_protocol": "Protocollo API", + "domain_config_auth_entrypoint": "API entry point", + "other_available_options": "… e {n} altre opzioni di variabili non mostrate", + "service_description_yunomdns": "Ti permette di raggiungere il tuo server usando 'yunohost.local' all’interno della tua rete locale", + "user_import_nothing_to_do": "Nessun utente deve essere importato", + "user_import_partial_failed": "L’importazione degli utenti è parzialmente fallita", + "domain_unknown": "Il dominio '{domain}' è sconosciuto", + "log_user_import": "Importa utenti", + "invalid_password": "Password non valida", + "diagnosis_high_number_auth_failures": "Recentemente c’è stato un numero insolitamente alto di autenticazioni fallite. Potresti assicurarti che fail2ban stia funzionando e che sia configurato correttamente, oppure usare una differente porta SSH, come spiegato in https://yunohost.org/security.", + "diagnosis_apps_allgood": "Tutte le applicazioni installate rispettano le pratiche di packaging di base", + "config_apply_failed": "L’applicazione della nuova configurazione è fallita: {error}", + "diagnosis_apps_outdated_ynh_requirement": "La versione installata di quest’app richiede esclusivamente YunoHost >= 2.x, che tendenzialmente significa che non è aggiornata secondo le pratiche di packaging raccomandate. Dovresti proprio considerare di aggiornarla.", + "global_settings_setting_security_experimental_enabled": "Abilita funzionalità di sicurezza sperimentali (non abilitare se non sai cosa stai facendo!)", + "invalid_number_min": "Deve essere più grande di {min}", + "invalid_number_max": "Deve essere meno di {max}", + "log_app_config_set": "Applica la configurazione all’app '{}'", + "log_domain_dns_push": "Sincronizza i record DNS per il dominio '{}'", + "user_import_bad_file": "Il tuo file CSV non è formattato correttamente e sarà ignorato per evitare potenziali perdite di dati", + "user_import_failed": "L’operazione di importazione è completamente fallita", + "user_import_missing_columns": "Mancano le seguenti colonne: {columns}", + "user_import_success": "Utenti importati con successo", + "diagnosis_apps_bad_quality": "Sul catalogo delle applicazioni di YunoHost, questa applicazione è momentaneamente segnalata come non funzionante. Potrebbe trattarsi di un problema temporaneo, mentre i manutentori provano a risolverlo. Nel frattempo, l’aggiornamento di quest’app è disabilitato.", + "diagnosis_apps_broken": "Sul catalogo delle applicazioni di YunoHost, questa applicazione è momentaneamente segnalata come non funzionante. Potrebbe trattarsi di un problema temporaneo, mentre i manutentori provano a risolverlo. Nel frattempo, l’aggiornamento di quest’app è disabilitato.", + "diagnosis_apps_deprecated_practices": "La versione installata di questa app usa ancora delle pratiche di packaging super-vecchie oppure deprecate. Dovresti proprio considerare di aggiornarla.", + "diagnosis_apps_not_in_app_catalog": "Questa applicazione non è nel catalogo delle applicazioni di YunoHost. Se precedentemente lo era ed è stata rimossa, dovresti considerare di disinstallare l’app, dato che non riceverà aggiornamenti e potrebbe compromettere l’integrità e la sicurezza del tuo sistema.", + "diagnosis_dns_specialusedomain": "Il dominio {domain} è basato su un dominio di primo livello (TLD) dall’uso speciale, come .local o .test, perciò non è previsto abbia reali record DNS.", + "domain_dns_registrar_supported": "YunoHost ha automaticamente riconosciuto che questo dominio è gestito dal registrar **{registrar}**. Se vuoi e se fornirai le credenziali API appropriate, YunoHost può configurare automaticamente questa zona DNS. Puoi trovare la documentazione su come ottenere le tue credenziali API su questa pagina. (Puoi anche configurare i tuoi record DNS manualmente, seguendo la documentazione)", + "service_not_reloading_because_conf_broken": "Non sto ricaricando/riavviando il servizio '{name}' perché la sua configurazione è rotta: {errors}", + "config_cant_set_value_on_section": "Non puoi impostare un unico parametro in un’intera sezione della configurazione.", + "config_forbidden_keyword": "La parola chiave '{keyword}' è riservata, non puoi creare o utilizzare un pannello di configurazione con una domanda con questo id.", + "config_no_panel": "Nessun panello di configurazione trovato.", + "config_unknown_filter_key": "Il valore del filtro '{filter_key}' non è corretto.", + "config_validate_color": "È necessario inserire un codice colore in RGB esadecimale", + "config_validate_date": "È necessario inserire una data valida nel formato AAAA-MM-GG", + "config_validate_email": "È necessario inserire un’email valida", + "diagnosis_description_apps": "Applicazioni", + "domain_registrar_is_not_configured": "Il registrar non è ancora configurato per il dominio {domain}.", + "domain_dns_registrar_managed_in_parent_domain": "Questo dominio è un sotto-dominio di {parent_domain_link}. La configurazione del registrar DNS dovrebbe essere gestita dal pannello di configurazione di {parent_domain}.", + "domain_dns_registrar_yunohost": "Questo dominio è un nohost.me / nohost.st / ynh.fr, perciò la sua configurazione DNS è gestita automaticamente da YunoHost, senza alcuna ulteriore configurazione. (vedi il comando yunohost dyndns update)", + "domain_dns_push_success": "Record DNS aggiornati!", + "domain_dns_push_failed": "L’aggiornamento dei record DNS è miseramente fallito.", + "domain_dns_push_partial_failure": "Record DNS parzialmente aggiornati: alcuni segnali/errori sono stati riportati.", + "domain_config_features_disclaimer": "Per ora, abilitare/disabilitare le impostazioni di posta o XMPP impatta unicamente sulle configurazioni DNS raccomandate o ottimizzate, non cambia quelle di sistema!", + "domain_config_mail_in": "Email in arrivo", + "domain_config_auth_application_key": "Chiave applicazione", + "domain_config_auth_application_secret": "Chiave segreta applicazione", + "domain_config_auth_consumer_key": "Chiave consumatore", + "ldap_attribute_already_exists": "L’attributo LDAP '{attribute}' esiste già con il valore '{value}'", + "config_validate_time": "È necessario inserire un orario valido, come HH:MM", + "config_version_not_supported": "Le versioni '{version}' del pannello di configurazione non sono supportate.", + "danger": "Attenzione:", + "log_domain_config_set": "Aggiorna la configurazione per il dominio '{}'", + "domain_dns_push_managed_in_parent_domain": "La configurazione automatica del DNS è gestita nel dominio genitore {parent_domain}.", + "user_import_bad_line": "Linea errata {line}: {details}", + "config_validate_url": "È necessario inserire un URL web valido", + "ldap_server_down": "Impossibile raggiungere il server LDAP", + "ldap_server_is_down_restart_it": "Il servizio LDAP è down, prova a riavviarlo…" } From 4154ece1bbbe09eb9d668411114bfb06910c754a Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Sun, 14 Nov 2021 21:07:58 +0000 Subject: [PATCH 0934/1155] [CI] Reformat / remove stale translated strings --- locales/ca.json | 3 --- locales/de.json | 5 +---- locales/eo.json | 2 -- locales/es.json | 5 +---- locales/eu.json | 3 +-- locales/fa.json | 4 ---- locales/fr.json | 9 +-------- locales/gl.json | 9 +-------- locales/hi.json | 1 - locales/id.json | 2 +- locales/it.json | 5 +---- locales/nb_NO.json | 1 - locales/nl.json | 1 - locales/oc.json | 4 +--- locales/pt.json | 3 +-- locales/ru.json | 3 +-- locales/sl.json | 2 +- locales/uk.json | 9 +-------- locales/zh_Hans.json | 9 +++------ 19 files changed, 15 insertions(+), 65 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index b29a94fb6..bb656214f 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -17,7 +17,6 @@ "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}", - "app_manifest_invalid": "Hi ha algun error amb el manifest de l'aplicació: {error}", "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", @@ -132,7 +131,6 @@ "domains_available": "Dominis disponibles:", "done": "Fet", "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", "dyndns_ip_updated": "S'ha actualitzat l'adreça IP al DynDNS", @@ -590,7 +588,6 @@ "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", - "domain_name_unknown": "Domini «{domain}» desconegut", "diagnosis_processes_killed_by_oom_reaper": "El sistema ha matat alguns processos recentment perquè s'ha quedat sense memòria. Això acostuma a ser un símptoma de falta de memòria en el sistema o d'un procés que consumeix massa memòria. Llista dels processos que s'han matat:\n{kills_summary}", "diagnosis_package_installed_from_sury_details": "Alguns paquets s'han instal·lat per equivocació des d'un repositori de tercers anomenat Sury. L'equip de YunoHost a millorat l'estratègia per a gestionar aquests paquets, però s'espera que algunes configuracions que han instal·lat aplicacions PHP7.3 a Stretch puguin tenir algunes inconsistències. Per a resoldre aquesta situació, hauríeu d'intentar executar la següent ordre: {cmd_to_fix}", "diagnosis_package_installed_from_sury": "Alguns paquets del sistema s'han de tornar a versions anteriors", diff --git a/locales/de.json b/locales/de.json index 5dd8c059d..e491c5ac2 100644 --- a/locales/de.json +++ b/locales/de.json @@ -10,7 +10,6 @@ "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} 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?", @@ -185,7 +184,6 @@ "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} 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 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}'", @@ -435,7 +433,6 @@ "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}", - "domain_name_unknown": "Domäne '{domain}' unbekannt", "group_user_not_in_group": "Benutzer:in {user} ist nicht in der Gruppe {group}", "group_user_already_in_group": "Benutzer:in {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", @@ -642,4 +639,4 @@ "config_validate_time": "Sollte eine zulässige Zeit wie HH:MM sein", "config_validate_url": "Sollte eine zulässige web URL sein", "config_version_not_supported": "Konfigurationspanel Versionen '{version}' sind nicht unterstützt." -} +} \ No newline at end of file diff --git a/locales/eo.json b/locales/eo.json index 8973e6344..49040b768 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -51,7 +51,6 @@ "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}' estas nekonata", @@ -244,7 +243,6 @@ "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} 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}' ne haveblas.", diff --git a/locales/es.json b/locales/es.json index dec90b42b..d6382c262 100644 --- a/locales/es.json +++ b/locales/es.json @@ -10,7 +10,6 @@ "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} 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", @@ -194,7 +193,6 @@ "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).", @@ -544,7 +542,6 @@ "global_settings_setting_smtp_relay_port": "Puerto de envio / relay SMTP", "global_settings_setting_smtp_relay_host": "El servidor relay de SMTP para enviar correo en lugar de esta instalación YunoHost. Útil si estás en una de estas situaciones: tu puerto 25 esta bloqueado por tu ISP o VPS, si estás en usado una IP marcada como residencial o DUHL, si no puedes configurar un DNS inverso o si el servidor no está directamente expuesto a internet y quieres utilizar otro servidor para enviar correos.", "global_settings_setting_smtp_allow_ipv6": "Permitir el uso de IPv6 para enviar y recibir correo", - "domain_name_unknown": "Dominio «{domain}» desconocido", "diagnosis_processes_killed_by_oom_reaper": "Algunos procesos fueron terminados por el sistema recientemente porque se quedó sin memoria. Típicamente es sintoma de falta de memoria o de un proceso que se adjudicó demasiada memoria.
Resumen de los procesos terminados:
\n{kills_summary}", "diagnosis_http_nginx_conf_not_up_to_date_details": "Para arreglar este asunto, estudia las diferencias mediante el comando yunohost tools regen-conf nginx --dry-run --with-diff y si te parecen bien aplica los cambios mediante yunohost tools regen-conf nginx --force.", "diagnosis_http_nginx_conf_not_up_to_date": "Parece que la configuración nginx de este dominio haya sido modificada manualmente, esto no deja que YunoHost pueda diagnosticar si es accesible mediante HTTP.", @@ -587,4 +584,4 @@ "app_config_unable_to_read": "No se pudieron leer los valores del panel configuración.", "backup_create_size_estimation": "El archivo contendrá aproximadamente {size} de datos.", "config_cant_set_value_on_section": "No puede establecer un único valor en una sección de configuración completa." -} +} \ No newline at end of file diff --git a/locales/eu.json b/locales/eu.json index 1f69866bb..21c23260c 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -368,7 +368,6 @@ "log_app_change_url": "'{}' aplikazioaren URLa aldatu", "log_app_config_set": "Ezarri '{}' aplikazioko konfigurazioa", "downloading": "Deskargatzen…", - "dyndns_could_not_check_provide": "Ezinezkoa izan da {provider}(e)k {domain} eskaini dezakeen egiaztatzea.", "log_available_on_yunopaste": "Erregistroa {url} estekan ikus daiteke", "log_dyndns_update": "Eguneratu YunoHosten '{}' domeinuari lotutako IP helbidea", "log_letsencrypt_cert_install": "Instalatu Let's Encrypt ziurtagiria '{}' domeinurako", @@ -705,4 +704,4 @@ "service_description_rspamd": "Spama bahetu eta posta elektronikoarekin zerikusia duten bestelako futzioen ardura dauka", "service_description_slapd": "Erabiltzaileak, domeinuak eta hauei lotutako informazioa gordetzen du", "service_description_yunohost-api": "YunoHosten web-atariaren eta sistemaren arteko hartuemana kudeatzen du" -} +} \ No newline at end of file diff --git a/locales/fa.json b/locales/fa.json index f566fed90..e7ab1595f 100644 --- a/locales/fa.json +++ b/locales/fa.json @@ -183,7 +183,6 @@ "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}' استفاده می شود", @@ -199,7 +198,6 @@ "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", @@ -348,13 +346,11 @@ "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_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": "نام میزبان جدید قابل تنظیم نیست. این ممکن است بعداً مشکلی ایجاد کند (ممکن هم هست خوب باشد).", diff --git a/locales/fr.json b/locales/fr.json index 5b2ea6225..07bdcfad3 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -10,7 +10,6 @@ "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", @@ -223,7 +222,6 @@ "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", - "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}...", @@ -602,7 +600,6 @@ "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}'", - "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}' déjà supprimées pour la permission '{permission}'", "invalid_number": "Doit être un nombre", @@ -631,7 +628,6 @@ "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 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 comme .local ou .test 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...", @@ -669,13 +665,10 @@ "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'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", "domain_registrar_is_not_configured": "Le registrar n'est pas encore configuré pour le domaine {domain}.", "domain_dns_push_not_applicable": "La fonction de configuration DNS automatique n'est pas applicable au domaine {domain}. Vous devez configurer manuellement vos enregistrements DNS en suivant la documentation sur https://yunohost.org/dns_config.", "domain_dns_registrar_yunohost": "Ce domaine est de type nohost.me / nohost.st / ynh.fr et sa configuration DNS est donc automatiquement gérée par YunoHost sans qu'il n'y ait d'autre configuration à faire. (voir la commande 'yunohost dyndns update')", @@ -711,4 +704,4 @@ "other_available_options": "... et {n} autres options disponibles non affichées", "domain_config_auth_consumer_key": "Consumer key", "domain_unknown": "Domaine '{domain}' inconnu" -} +} \ No newline at end of file diff --git a/locales/gl.json b/locales/gl.json index 0d7d1afee..ac41200a6 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -76,7 +76,6 @@ "app_manifest_install_ask_password": "Elixe un contrasinal de administración para 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}", "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}'", @@ -288,13 +287,11 @@ "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} 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...", "done": "Feito", "domains_available": "Dominios dispoñibles:", - "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).", @@ -454,7 +451,6 @@ "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 esté exposto ao exterior da rede local.", "diagnosis_dns_specialusedomain": "O dominio {domain} baséase un dominio de nivel alto e uso especial (TLD) como .local ou .test polo que non é de agardar que realmente teña rexistros DNS.", "upnp_enabled": "UPnP activado", "upnp_disabled": "UPnP desactivado", @@ -655,13 +651,11 @@ "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.", - "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 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", "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.", @@ -673,7 +667,6 @@ "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}", "diagnosis_http_special_use_tld": "O dominio {domain} baséase nun dominio de alto-nivel (TLD) especial como .local ou .test e por isto non é de agardar que esté exposto fóra da rede local.", @@ -711,4 +704,4 @@ "ldap_attribute_already_exists": "Xa existe o atributo LDAP '{attribute}' con valor '{value}'", "log_domain_config_set": "Actualizar configuración para o dominio '{}'", "domain_unknown": "Dominio '{domain}' descoñecido" -} +} \ No newline at end of file diff --git a/locales/hi.json b/locales/hi.json index 5f521b1dc..1eed9faa4 100644 --- a/locales/hi.json +++ b/locales/hi.json @@ -10,7 +10,6 @@ "app_extraction_failed": "इन्सटाल्ड फ़ाइलों को निकालने में असमर्थ", "app_id_invalid": "अवैध एप्लिकेशन id", "app_install_files_invalid": "फाइलों की अमान्य स्थापना", - "app_manifest_invalid": "एप्लीकेशन का मैनिफेस्ट अमान्य", "app_not_correctly_installed": "{app} ठीक ढंग से इनस्टॉल नहीं हुई", "app_not_installed": "{app} इनस्टॉल नहीं हुई", "app_not_properly_removed": "{app} ठीक ढंग से नहीं अनइन्सटॉल की गई", diff --git a/locales/id.json b/locales/id.json index 1f28ae7e5..c9778dd5f 100644 --- a/locales/id.json +++ b/locales/id.json @@ -43,4 +43,4 @@ "app_start_remove": "Menghapus {app}...", "app_manifest_install_ask_password": "Pilih kata sandi administrasi untuk aplikasi ini", "app_upgrade_several_apps": "Aplikasi-aplikasi berikut akan diperbarui: {apps}" -} +} \ No newline at end of file diff --git a/locales/it.json b/locales/it.json index 7f3997839..742e38b0d 100644 --- a/locales/it.json +++ b/locales/it.json @@ -26,7 +26,6 @@ "admin_password_change_failed": "Impossibile cambiare la password", "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} sembra di non essere installata correttamente", "app_not_properly_removed": "{app} non è stata correttamente rimossa", "action_invalid": "L'azione '{action}' non è valida", @@ -223,7 +222,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 apt install --fix-broken` e/o `sudo dpkg --configure -a`.", "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} 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.", @@ -558,7 +556,6 @@ "global_settings_setting_pop3_enabled": "Abilita il protocollo POP3 per il server mail", "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}' 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}", @@ -706,4 +703,4 @@ "config_validate_url": "È necessario inserire un URL web valido", "ldap_server_down": "Impossibile raggiungere il server LDAP", "ldap_server_is_down_restart_it": "Il servizio LDAP è down, prova a riavviarlo…" -} +} \ No newline at end of file diff --git a/locales/nb_NO.json b/locales/nb_NO.json index dc217d74e..e81d3af05 100644 --- a/locales/nb_NO.json +++ b/locales/nb_NO.json @@ -30,7 +30,6 @@ "domains_available": "Tilgjengelige domener:", "done": "Ferdig", "downloading": "Laster ned…", - "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", diff --git a/locales/nl.json b/locales/nl.json index 5e612fc77..f5923fa9a 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -8,7 +8,6 @@ "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} is niet geïnstalleerd", "app_removed": "{app} succesvol verwijderd", "app_sources_fetch_failed": "Kan bronbestanden niet ophalen, klopt de URL?", diff --git a/locales/oc.json b/locales/oc.json index a794f5e04..9de7944c5 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -33,7 +33,6 @@ "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", @@ -110,7 +109,6 @@ "domains_available": "Domenis disponibles :", "done": "Acabat", "downloading": "Telecargament…", - "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.", @@ -513,4 +511,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 d285948be..154ab700f 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -7,7 +7,6 @@ "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": "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?", @@ -255,4 +254,4 @@ "diagnosis_backports_in_sources_list": "Parece que o apt (o gerenciador de pacotes) está configurado para usar o repositório backport. A não ser que você saiba o que você esteá fazendo, desencorajamos fortemente a instalação de pacotes de backports porque é provável que crie instabilidades ou conflitos no seu sistema.", "certmanager_cert_renew_success": "Certificado Let's Encrypt renovado para o domínio '{domain}'", "certmanager_warning_subdomain_dns_record": "O subdomínio '{subdomain}' não resolve para o mesmo IP que '{domain}'. Algumas funcionalidades não estarão disponíveis até que você conserte isto e regenere o certificado." -} +} \ No newline at end of file diff --git a/locales/ru.json b/locales/ru.json index 5c9a39322..58bd1846d 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -16,7 +16,6 @@ "app_id_invalid": "Неправильный ID приложения", "app_install_files_invalid": "Эти файлы не могут быть установлены", "app_location_unavailable": "Этот URL отсутствует или конфликтует с уже установленным приложением или приложениями:\n{apps}", - "app_manifest_invalid": "Недопустимый манифест приложения: {error}", "app_not_correctly_installed": "{app} , кажется, установлены неправильно", "app_not_installed": "{app} не найдено в списке установленных приложений: {all_apps}", "app_not_properly_removed": "{app} удалены неправильно", @@ -75,4 +74,4 @@ "yunohost_already_installed": "YunoHost уже установлен", "yunohost_configured": "Теперь YunoHost настроен", "upgrading_packages": "Обновление пакетов..." -} +} \ No newline at end of file diff --git a/locales/sl.json b/locales/sl.json index 0967ef424..9e26dfeeb 100644 --- a/locales/sl.json +++ b/locales/sl.json @@ -1 +1 @@ -{} +{} \ No newline at end of file diff --git a/locales/uk.json b/locales/uk.json index 4baf6d20f..ae59b0274 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -1,6 +1,5 @@ { "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}'", @@ -318,13 +317,11 @@ "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`.", "downloading": "Завантаження…", "done": "Готово", "domains_available": "Доступні домени:", - "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": "Неможливо встановити нову назву хоста. Це може викликати проблеми в подальшому (можливо, все буде в порядку).", @@ -356,7 +353,6 @@ "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 ", @@ -669,13 +665,10 @@ "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}", - "app_argument_password_help_optional": "Введіть один пробіл, щоб очистити пароль", - "app_argument_password_help_keep": "Натисніть Enter, щоб зберегти поточне значення", "domain_registrar_is_not_configured": "Реєстратор ще не конфігуровано для домену {domain}.", "domain_dns_push_not_applicable": "Функція автоматичної конфігурації DNS не застосовується до домену {domain}. Вам слід вручну конфігурувати записи DNS відповідно до документації за адресою https://yunohost.org/dns_config.", "domain_dns_registrar_not_supported": "YunoHost не зміг автоматично виявити реєстратора, який обробляє цей домен. Вам слід вручну конфігурувати записи DNS відповідно до документації за адресою https://yunohost.org/dns.", @@ -711,4 +704,4 @@ "ldap_attribute_already_exists": "Атрибут LDAP '{attribute}' вже існує зі значенням '{value}'", "domain_dns_push_already_up_to_date": "Записи вже оновлені, нічого не потрібно робити.", "domain_unknown": "Домен '{domain}' є невідомим" -} +} \ No newline at end of file diff --git a/locales/zh_Hans.json b/locales/zh_Hans.json index fa3b7bb03..c1cad8425 100644 --- a/locales/zh_Hans.json +++ b/locales/zh_Hans.json @@ -137,7 +137,6 @@ "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}", "app_label_deprecated": "不推荐使用此命令!请使用新命令 'yunohost user permission update'来管理应用标签。", "app_make_default_location_already_used": "无法将'{app}' 设置为域上的默认应用,'{other_app}'已在使用'{domain}'", @@ -353,13 +352,11 @@ "dyndns_ip_updated": "在DynDNS上更新了您的IP", "dyndns_ip_update_failed": "无法将IP地址更新到DynDNS", "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": "下载中…", "done": "完成", "domains_available": "可用域:", - "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": "无法设置新的主机名。稍后可能会引起问题(可能没问题)。", @@ -627,7 +624,7 @@ "log_user_delete": "删除用户'{}'", "log_user_create": "添加用户'{}'", "domain_registrar_is_not_configured": "尚未为域 {domain} 配置注册商。", - "domain_dns_push_not_applicable": "的自动DNS配置的特征是不适用域{域}。您应该按照 https://yunohost.org/dns_config 上的文档手动配置DNS 记录。", + "domain_dns_push_not_applicable": "的自动DNS配置的特征是不适用域{domain}。您应该按照 https://yunohost.org/dns_config 上的文档手动配置DNS 记录。", "disk_space_not_sufficient_update": "没有足够的磁盘空间来更新此应用程序", "diagnosis_high_number_auth_failures": "最近出现了大量可疑的失败身份验证。您的fail2ban正在运行且配置正确,或使用自定义端口的SSH作为https://yunohost.org/解释的安全性。", "diagnosis_apps_not_in_app_catalog": "此应用程序不在 YunoHost 的应用程序目录中。如果它过去有被删除过,您应该考虑卸载此应用程,因为它不会更新,并且可能会损害您系统的完整和安全性。", @@ -638,7 +635,7 @@ "config_unknown_filter_key": "该过滤器钥匙“{filter_key}”有误。", "diagnosis_apps_outdated_ynh_requirement": "此应用程序的安装 版本只需要 yunohost >= 2.x,这往往表明它与推荐的打包实践和帮助程序不是最新的。你真的应该考虑更新它。", "disk_space_not_sufficient_install": "没有足够的磁盘空间来安装此应用程序", - "config_apply_failed": "应用新配置 失败:{错误}", + "config_apply_failed": "应用新配置 失败:{error}", "config_cant_set_value_on_section": "无法在整个配置部分设置单个值 。", "config_validate_color": "是有效的 RGB 十六进制颜色", "config_validate_date": "有效日期格式为YYYY-MM-DD", @@ -651,4 +648,4 @@ "diagnosis_apps_deprecated_practices": "此应用程序的安装 版本仍然使用一些超旧的弃用打包原则。推荐您升级它。", "diagnosis_apps_issue": "发现应用{ app } 存在问题", "diagnosis_description_apps": "应用" -} +} \ No newline at end of file From 35850f75cd62831ed7af329f1b9dfae1f735a1b7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 14 Nov 2021 22:58:43 +0100 Subject: [PATCH 0935/1155] Update changelog for 4.3.3 --- debian/changelog | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/debian/changelog b/debian/changelog index 7adbe59dc..f2266e871 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,17 @@ +yunohost (4.3.3) stable; urgency=low + + - [fix] log: fix dump_script_log_extract_for_debugging displaying wrong log snippet during failed upgrade ([#1376](https://github.com/YunoHost/yunohost/pull/1376)) + - [fix] certificate: fix stupid certificate/diagnosis issue with subdomains of ynh domains (7c569d16) + - [fix] diagnosis: Read DNS Blacklist answer and compare it against list of non-BL codes ([#1375](https://github.com/YunoHost/yunohost/pull/1375)) + - [enh] helpers: Update n to 8.0.0 ([#1372](https://github.com/YunoHost/yunohost/pull/1372)) + - [fix] helpers: Make ynh_add_fpm_config more robust to some edge cases (51d5dca0) + - [fix] backup: conf_ynh_settings backup/restore hook, /etc/yunohost/domains may not exist (38f5352f) + - [i18n] Translations updated for Basque, Chinese (Simplified), Indonesian, Italian, Ukrainian + + Thanks to all contributors <3 ! (dagangtie, ericgaspar, Félix Piédallu, Flavio Cristoforetti, liimee, punkrockgirl, Romain Thouvenin, Tommi, Tymofii-Lytvynenko) + + -- Alexandre Aubin Sun, 14 Nov 2021 22:55:16 +0100 + yunohost (4.3.2.2) stable; urgency=low - [fix] nginx: Try to fix again the webadmin cache hell (74e2a51e) From 6b9ac717de86f8925d5e92b737e3126a1baaf290 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 14 Nov 2021 23:15:08 +0100 Subject: [PATCH 0936/1155] debian: fix metronome modules install --- debian/install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/install b/debian/install index 55c535ae8..0a23cbfc9 100644 --- a/debian/install +++ b/debian/install @@ -5,6 +5,6 @@ helpers/* /usr/share/yunohost/helpers.d/ conf/* /usr/share/yunohost/conf/ doc/yunohost.8.gz /usr/share/man/man8/ doc/bash-completion.sh /etc/bash_completion.d/yunohost -lib/metronome/modules/* /usr/lib/metronome/modules/ +conf/metronome/modules/* /usr/lib/metronome/modules/ locales/* /usr/lib/moulinette/yunohost/locales/ src/ /usr/lib/moulinette/yunohost From 42e86a80c5e46799839a5291756fc016eb42be0d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 15 Nov 2021 03:17:43 +0100 Subject: [PATCH 0937/1155] debian: bump ssowat and moulinette requirements --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index a06823a16..17eb56f55 100644 --- a/debian/control +++ b/debian/control @@ -10,7 +10,7 @@ Package: yunohost Essential: yes Architecture: all Depends: ${python3:Depends}, ${misc:Depends} - , moulinette (>= 4.3), ssowat (>= 4.3) + , moulinette (>= 11.0), ssowat (>= 11.0) , python3-psutil, python3-requests, python3-dnspython, python3-openssl , python3-miniupnpc, python3-dbus, python3-jinja2 , python3-toml, python3-packaging, python3-publicsuffix2 From 467c04f3820572ed82860c0ba296a2ab6b0541c6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 15 Nov 2021 04:00:47 +0100 Subject: [PATCH 0938/1155] codequality: Apply black on python script in bin/ --- bin/yunohost | 51 +++++++++++++++++++++++++++--------------------- bin/yunohost-api | 38 ++++++++++++++++++++++++------------ bin/yunomdns | 43 ++++++++++++++++++++++++++++------------ 3 files changed, 85 insertions(+), 47 deletions(-) diff --git a/bin/yunohost b/bin/yunohost index 0220c5f09..d3af76d92 100755 --- a/bin/yunohost +++ b/bin/yunohost @@ -12,37 +12,42 @@ import yunohost 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" + parser.add_argument( + "--output-as", + choices=["json", "plain", "none"], + default=None, + help="Output result in another format", ) - parser.add_argument('--debug', - action='store_true', default=False, - help="Log and print debug messages" + parser.add_argument( + "--debug", + action="store_true", + default=False, + help="Log and print debug messages", ) - parser.add_argument('--quiet', - action='store_true', default=False, - help="Don't produce any output" + parser.add_argument( + "--quiet", action="store_true", default=False, 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" + 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", ) # deprecated arguments - parser.add_argument('--plain', - action='store_true', default=False, help=argparse.SUPPRESS + parser.add_argument( + "--plain", action="store_true", default=False, help=argparse.SUPPRESS ) - parser.add_argument('--json', - action='store_true', default=False, help=argparse.SUPPRESS + parser.add_argument( + "--json", action="store_true", default=False, help=argparse.SUPPRESS ) opts, args = parser.parse_known_args() # output compatibility if opts.plain: - opts.output_as = 'plain' + opts.output_as = "plain" elif opts.json: - opts.output_as = 'json' + opts.output_as = "json" return (parser, opts, args) @@ -54,10 +59,12 @@ if os.environ["PATH"] != default_path: # Main action ---------------------------------------------------------- -if __name__ == '__main__': +if __name__ == "__main__": if os.geteuid() != 0: - sys.stderr.write("\033[1;31mError:\033[0m yunohost command must be " - "run as root or with sudo.\n") + 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() @@ -69,5 +76,5 @@ if __name__ == '__main__': output_as=opts.output_as, timeout=opts.timeout, args=args, - parser=parser + parser=parser, ) diff --git a/bin/yunohost-api b/bin/yunohost-api index b3ed3a817..6a809b198 100755 --- a/bin/yunohost-api +++ b/bin/yunohost-api @@ -8,37 +8,49 @@ sys.path.insert(0, "/usr/lib/moulinette/") import yunohost # Default server configuration -DEFAULT_HOST = 'localhost' +DEFAULT_HOST = "localhost" DEFAULT_PORT = 6787 def _parse_api_args(): """Parse main arguments for the api""" - parser = argparse.ArgumentParser(add_help=False, + parser = argparse.ArgumentParser( + add_help=False, description="Run the YunoHost API to manage your server.", ) - srv_group = parser.add_argument_group('server configuration') - srv_group.add_argument('-h', '--host', - action='store', default=DEFAULT_HOST, + srv_group = parser.add_argument_group("server configuration") + srv_group.add_argument( + "-h", + "--host", + action="store", + default=DEFAULT_HOST, help="Host to listen on (default: %s)" % DEFAULT_HOST, ) - srv_group.add_argument('-p', '--port', - action='store', default=DEFAULT_PORT, type=int, + srv_group.add_argument( + "-p", + "--port", + action="store", + default=DEFAULT_PORT, + type=int, help="Port to listen on (default: %d)" % DEFAULT_PORT, ) - glob_group = parser.add_argument_group('global arguments') - glob_group.add_argument('--debug', - action='store_true', default=False, + glob_group = parser.add_argument_group("global arguments") + glob_group.add_argument( + "--debug", + action="store_true", + default=False, help="Set log level to DEBUG", ) - glob_group.add_argument('--help', - action='help', help="Show this help message and exit", + glob_group.add_argument( + "--help", + action="help", + help="Show this help message and exit", ) return parser.parse_args() -if __name__ == '__main__': +if __name__ == "__main__": opts = _parse_api_args() # Run the server yunohost.api(debug=opts.debug, host=opts.host, port=opts.port) diff --git a/bin/yunomdns b/bin/yunomdns index 0aee28195..661c7c71f 100755 --- a/bin/yunomdns +++ b/bin/yunomdns @@ -21,8 +21,18 @@ def get_network_local_interfaces() -> Dict[str, Dict[str, List[str]]]: interfaces = { adapter.name: { - "ipv4": [ip.ip for ip in adapter.ips if ip.is_IPv4 and ip_address(ip.ip).is_private], - "ipv6": [ip.ip[0] for ip in adapter.ips if ip.is_IPv6 and ip_address(ip.ip[0]).is_private and not ip_address(ip.ip[0]).is_link_local], + "ipv4": [ + ip.ip + for ip in adapter.ips + if ip.is_IPv4 and ip_address(ip.ip).is_private + ], + "ipv6": [ + ip.ip[0] + for ip in adapter.ips + if ip.is_IPv6 + and ip_address(ip.ip[0]).is_private + and not ip_address(ip.ip[0]).is_link_local + ], } for adapter in ifaddr.get_adapters() if adapter.name != "lo" @@ -33,7 +43,6 @@ def get_network_local_interfaces() -> Dict[str, Dict[str, List[str]]]: # Listener class, to detect duplicates on the network # Stores the list of servers in its list property class Listener: - def __init__(self): self.list = [] @@ -66,14 +75,18 @@ def main() -> bool: return False if "interfaces" not in config: - config["interfaces"] = [interface - for interface, local_ips in interfaces.items() - if local_ips["ipv4"]] + config["interfaces"] = [ + interface + for interface, local_ips in interfaces.items() + if local_ips["ipv4"] + ] if "ban_interfaces" in config: - config["interfaces"] = [interface - for interface in config["interfaces"] - if interface not in config["ban_interfaces"]] + config["interfaces"] = [ + interface + for interface in config["interfaces"] + if interface not in config["ban_interfaces"] + ] # Let's discover currently published .local domains accross the network zc = Zeroconf() @@ -103,14 +116,18 @@ def main() -> bool: return domain_i - config['domains'] = [find_domain_not_already_published(domain) for domain in config['domains']] + config["domains"] = [ + find_domain_not_already_published(domain) for domain in config["domains"] + ] zcs: Dict[Zeroconf, List[ServiceInfo]] = {} for interface in config["interfaces"]: if interface not in interfaces: - print(f"Interface {interface} listed in config file is not present on system.") + print( + f"Interface {interface} listed in config file is not present on system." + ) continue # Only broadcast IPv4 because IPv6 is buggy ... because we ain't using python3-ifaddr >= 0.1.7 @@ -149,7 +166,9 @@ def main() -> bool: print("Registering...") for zc, infos in zcs.items(): for info in infos: - zc.register_service(info, allow_name_change=True, cooperating_responders=True) + zc.register_service( + info, allow_name_change=True, cooperating_responders=True + ) try: print("Registered. Press Ctrl+C or stop service to stop.") From 01ff6c932cff90b40b5e19ba5aeccd929e7ea50e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 15 Nov 2021 04:02:45 +0100 Subject: [PATCH 0939/1155] Attempt to fix debian install following repo structure change --- debian/install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/install b/debian/install index 0a23cbfc9..534dccebd 100644 --- a/debian/install +++ b/debian/install @@ -7,4 +7,4 @@ doc/yunohost.8.gz /usr/share/man/man8/ doc/bash-completion.sh /etc/bash_completion.d/yunohost conf/metronome/modules/* /usr/lib/metronome/modules/ locales/* /usr/lib/moulinette/yunohost/locales/ -src/ /usr/lib/moulinette/yunohost +src/* /usr/lib/moulinette/yunohost/ From 26e6b4a41450449f1c3cbad8548d6253e309e4fc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 15 Nov 2021 04:34:25 +0100 Subject: [PATCH 0940/1155] debian: another tweak to install actionsmap to the right place --- debian/install | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/install b/debian/install index 534dccebd..37840662b 100644 --- a/debian/install +++ b/debian/install @@ -1,5 +1,6 @@ bin/* /usr/bin/ share/* /usr/share/yunohost/ +share/actionsmap.yml /usr/share/moulinette/actionsmap/yunohost.yml hooks/* /usr/share/yunohost/hooks/ helpers/* /usr/share/yunohost/helpers.d/ conf/* /usr/share/yunohost/conf/ From 16dba79a3983b979a19de1701773a5440ae9766f Mon Sep 17 00:00:00 2001 From: Kay0u Date: Wed, 17 Nov 2021 00:44:58 +0100 Subject: [PATCH 0941/1155] fix backup_delete when compress_tar_archives is True --- 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 058813502..f68e59b13 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -2592,7 +2592,7 @@ def backup_delete(name): hook_callback("pre_backup_delete", args=[name]) archive_file = "%s/%s.tar" % (ARCHIVES_PATH, name) - if os.path.exists(archive_file + ".gz"): + if not os.path.exists(archive_file) and os.path.exists(archive_file + ".gz"): archive_file += ".gz" info_file = "%s/%s.info.json" % (ARCHIVES_PATH, name) From 7bfa15d4716daf25cf76462aa3aecf99596137a4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 17 Nov 2021 20:12:18 +0100 Subject: [PATCH 0942/1155] Propagate changes from moulinette --- bin/yunohost | 2 -- bin/yunohost-api | 3 --- debian/install | 5 ++--- share/actionsmap.yml | 3 ++- src/__init__.py | 13 +++++++++++-- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/bin/yunohost b/bin/yunohost index d3af76d92..8cebdee8e 100755 --- a/bin/yunohost +++ b/bin/yunohost @@ -4,8 +4,6 @@ import os import sys import argparse - -sys.path.insert(0, "/usr/lib/moulinette/") import yunohost diff --git a/bin/yunohost-api b/bin/yunohost-api index 6a809b198..8cf9d4f26 100755 --- a/bin/yunohost-api +++ b/bin/yunohost-api @@ -1,10 +1,7 @@ #! /usr/bin/python3 # -*- coding: utf-8 -*- -import sys import argparse - -sys.path.insert(0, "/usr/lib/moulinette/") import yunohost # Default server configuration diff --git a/debian/install b/debian/install index 37840662b..db88462ab 100644 --- a/debian/install +++ b/debian/install @@ -1,11 +1,10 @@ bin/* /usr/bin/ share/* /usr/share/yunohost/ -share/actionsmap.yml /usr/share/moulinette/actionsmap/yunohost.yml hooks/* /usr/share/yunohost/hooks/ helpers/* /usr/share/yunohost/helpers.d/ conf/* /usr/share/yunohost/conf/ +locales/* /usr/share/yunohost/locales/ doc/yunohost.8.gz /usr/share/man/man8/ doc/bash-completion.sh /etc/bash_completion.d/yunohost conf/metronome/modules/* /usr/lib/metronome/modules/ -locales/* /usr/lib/moulinette/yunohost/locales/ -src/* /usr/lib/moulinette/yunohost/ +src/* /usr/lib/python3/dist-packages/yunohost/ diff --git a/share/actionsmap.yml b/share/actionsmap.yml index 6ea82e8ab..cad0212b2 100644 --- a/share/actionsmap.yml +++ b/share/actionsmap.yml @@ -33,7 +33,8 @@ # Global parameters # ############################# _global: - name: yunohost.admin + namespace: yunohost + cookie_name: yunohost.admin authentication: api: ldap_admin cli: null diff --git a/src/__init__.py b/src/__init__.py index dad73e2a4..b9dcd93d9 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -22,7 +22,14 @@ def cli(debug, quiet, output_as, timeout, args, parser): if not is_installed(): check_command_is_valid_before_postinstall(args) - ret = moulinette.cli(args, output_as=output_as, timeout=timeout, top_parser=parser) + ret = moulinette.cli( + args, + actionsmap="/usr/share/yunohost/actionsmap.yml", + locales_dir="/usr/share/yunohost/locales/", + output_as=output_as, + timeout=timeout, + top_parser=parser + ) sys.exit(ret) @@ -39,6 +46,8 @@ def api(debug, host, port): ret = moulinette.api( host=host, port=port, + actionsmap="/usr/share/yunohost/actionsmap.yml", + locales_dir="/usr/share/yunohost/locales/", routes={("GET", "/installed"): is_installed_api}, ) sys.exit(ret) @@ -78,7 +87,7 @@ def init(interface="cli", debug=False, quiet=False, logdir="/var/log/yunohost"): 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_locales_dir("/usr/share/yunohost/locales/") m18n.set_locale(get_locale()) From bc9a9414a2e99ed5238ee8770380f854e93966a0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 18 Nov 2021 01:23:00 +0100 Subject: [PATCH 0943/1155] Fix diagnosis/ vs. diagnosis.py ambiguity, breaking python imports --- .gitlab/ci/test.gitlab-ci.yml | 4 ++-- src/{diagnosis => diagnosers}/00-basesystem.py | 0 src/{diagnosis => diagnosers}/10-ip.py | 0 src/{diagnosis => diagnosers}/12-dnsrecords.py | 0 src/{diagnosis => diagnosers}/14-ports.py | 0 src/{diagnosis => diagnosers}/21-web.py | 0 src/{diagnosis => diagnosers}/24-mail.py | 0 src/{diagnosis => diagnosers}/30-services.py | 0 src/{diagnosis => diagnosers}/50-systemresources.py | 0 src/{diagnosis => diagnosers}/70-regenconf.py | 0 src/{diagnosis => diagnosers}/80-apps.py | 0 src/{diagnosis => diagnosers}/__init__.py | 0 src/diagnosis.py | 6 +++--- tests/add_missing_keys.py | 4 ++-- tests/test_i18n_keys.py | 4 ++-- 15 files changed, 9 insertions(+), 9 deletions(-) rename src/{diagnosis => diagnosers}/00-basesystem.py (100%) rename src/{diagnosis => diagnosers}/10-ip.py (100%) rename src/{diagnosis => diagnosers}/12-dnsrecords.py (100%) rename src/{diagnosis => diagnosers}/14-ports.py (100%) rename src/{diagnosis => diagnosers}/21-web.py (100%) rename src/{diagnosis => diagnosers}/24-mail.py (100%) rename src/{diagnosis => diagnosers}/30-services.py (100%) rename src/{diagnosis => diagnosers}/50-systemresources.py (100%) rename src/{diagnosis => diagnosers}/70-regenconf.py (100%) rename src/{diagnosis => diagnosers}/80-apps.py (100%) rename src/{diagnosis => diagnosers}/__init__.py (100%) diff --git a/.gitlab/ci/test.gitlab-ci.yml b/.gitlab/ci/test.gitlab-ci.yml index e98927f38..1165e4826 100644 --- a/.gitlab/ci/test.gitlab-ci.yml +++ b/.gitlab/ci/test.gitlab-ci.yml @@ -37,7 +37,7 @@ full-tests: - *install_debs - yunohost tools postinstall -d domain.tld -p the_password --ignore-dyndns --force-diskspace script: - - python3 -m pytest --cov=yunohost tests/ src/tests/ src/diagnosis/ --junitxml=report.xml + - python3 -m pytest --cov=yunohost tests/ src/tests/ src/diagnosers/ --junitxml=report.xml - cd tests - bash test_helpers.sh needs: @@ -59,7 +59,7 @@ test-i18n-keys: changes: - locales/en.json - src/*.py - - src/diagnosis/*.py + - src/diagnosers/*.py test-translation-format-consistency: extends: .test-stage diff --git a/src/diagnosis/00-basesystem.py b/src/diagnosers/00-basesystem.py similarity index 100% rename from src/diagnosis/00-basesystem.py rename to src/diagnosers/00-basesystem.py diff --git a/src/diagnosis/10-ip.py b/src/diagnosers/10-ip.py similarity index 100% rename from src/diagnosis/10-ip.py rename to src/diagnosers/10-ip.py diff --git a/src/diagnosis/12-dnsrecords.py b/src/diagnosers/12-dnsrecords.py similarity index 100% rename from src/diagnosis/12-dnsrecords.py rename to src/diagnosers/12-dnsrecords.py diff --git a/src/diagnosis/14-ports.py b/src/diagnosers/14-ports.py similarity index 100% rename from src/diagnosis/14-ports.py rename to src/diagnosers/14-ports.py diff --git a/src/diagnosis/21-web.py b/src/diagnosers/21-web.py similarity index 100% rename from src/diagnosis/21-web.py rename to src/diagnosers/21-web.py diff --git a/src/diagnosis/24-mail.py b/src/diagnosers/24-mail.py similarity index 100% rename from src/diagnosis/24-mail.py rename to src/diagnosers/24-mail.py diff --git a/src/diagnosis/30-services.py b/src/diagnosers/30-services.py similarity index 100% rename from src/diagnosis/30-services.py rename to src/diagnosers/30-services.py diff --git a/src/diagnosis/50-systemresources.py b/src/diagnosers/50-systemresources.py similarity index 100% rename from src/diagnosis/50-systemresources.py rename to src/diagnosers/50-systemresources.py diff --git a/src/diagnosis/70-regenconf.py b/src/diagnosers/70-regenconf.py similarity index 100% rename from src/diagnosis/70-regenconf.py rename to src/diagnosers/70-regenconf.py diff --git a/src/diagnosis/80-apps.py b/src/diagnosers/80-apps.py similarity index 100% rename from src/diagnosis/80-apps.py rename to src/diagnosers/80-apps.py diff --git a/src/diagnosis/__init__.py b/src/diagnosers/__init__.py similarity index 100% rename from src/diagnosis/__init__.py rename to src/diagnosers/__init__.py diff --git a/src/diagnosis.py b/src/diagnosis.py index a7b6c725c..67128217c 100644 --- a/src/diagnosis.py +++ b/src/diagnosis.py @@ -660,7 +660,7 @@ class Diagnoser: def _list_diagnosis_categories(): - paths = glob.glob(os.path.dirname(__file__) + "/diagnosis/??-*.py") + paths = glob.glob(os.path.dirname(__file__) + "/diagnosers/??-*.py") names = sorted([os.path.basename(path)[: -len(".py")] for path in paths]) return names @@ -670,7 +670,7 @@ def _load_diagnoser(diagnoser_name): logger.debug(f"Loading diagnoser {diagnoser_name}") - paths = glob.glob(os.path.dirname(__file__) + f"/diagnosis/??-{diagnoser_name}.py") + paths = glob.glob(os.path.dirname(__file__) + f"/diagnosers/??-{diagnoser_name}.py") if len(paths) != 1: raise YunohostError(f"Uhoh, found several matches (or none?) for diagnoser {diagnoser_name} : {paths}", raw_msg=True) @@ -681,7 +681,7 @@ def _load_diagnoser(diagnoser_name): # this is python builtin method to import a module using a name, we # use that to import the migration as a python object so we'll be # able to run it in the next loop - module = import_module("yunohost.diagnosis.{}".format(module_id)) + module = import_module("yunohost.diagnosers.{}".format(module_id)) return module.MyDiagnoser() except Exception as e: import traceback diff --git a/tests/add_missing_keys.py b/tests/add_missing_keys.py index e1ecbf56e..ebee80a1b 100644 --- a/tests/add_missing_keys.py +++ b/tests/add_missing_keys.py @@ -28,7 +28,7 @@ def find_expected_string_keys(): python_files.extend(glob.glob("src/utils/*.py")) python_files.extend(glob.glob("src/migrations/*.py")) python_files.extend(glob.glob("src/authenticators/*.py")) - python_files.extend(glob.glob("src/diagnosis/*.py")) + python_files.extend(glob.glob("src/diagnosers/*.py")) python_files.append("bin/yunohost") for python_file in python_files: @@ -51,7 +51,7 @@ def find_expected_string_keys(): # 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("src/diagnosis/*.py"): + for python_file in glob.glob("src/diagnosers/*.py"): content = open(python_file).read() for m in p3.findall(content): if m.endswith("_"): diff --git a/tests/test_i18n_keys.py b/tests/test_i18n_keys.py index b25d35923..62cad7cbb 100644 --- a/tests/test_i18n_keys.py +++ b/tests/test_i18n_keys.py @@ -29,7 +29,7 @@ def find_expected_string_keys(): python_files.extend(glob.glob("src/utils/*.py")) python_files.extend(glob.glob("src/migrations/*.py")) python_files.extend(glob.glob("src/authenticators/*.py")) - python_files.extend(glob.glob("src/diagnosis/*.py")) + python_files.extend(glob.glob("src/diagnosers/*.py")) python_files.append("bin/yunohost") for python_file in python_files: @@ -52,7 +52,7 @@ def find_expected_string_keys(): # 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("src/diagnosis/*.py"): + for python_file in glob.glob("src/diagnosers/*.py"): content = open(python_file).read() for m in p3.findall(content): if m.endswith("_"): From 124dc158fc8aa888cddc42dd230e412343f68d8a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 18 Nov 2021 02:33:37 +0100 Subject: [PATCH 0944/1155] Fix redeclaration of 'migrations' var --- src/tools.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/tools.py b/src/tools.py index f9de32cfe..8962f41a3 100644 --- a/src/tools.py +++ b/src/tools.py @@ -930,19 +930,6 @@ def _write_migration_state(migration_id, state): def _get_migrations_list(): - migrations = [] - - try: - from . import migrations - except ImportError: - # not data migrations present, return empty list - return migrations - - migrations_path = migrations.__path__[0] - - if not os.path.exists(migrations_path): - 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: @@ -955,9 +942,11 @@ def _get_migrations_list(): # (in particular, pending migrations / not already ran are not listed states = tools_migrations_state()["migrations"] + migrations = [] + migrations_folder = os.path.dirname(__file__) + "/migrations/" for migration_file in [ x - for x in os.listdir(migrations_path) + for x in os.listdir(migrations_folder) if re.match(r"^\d+_[a-zA-Z0-9_]+\.py$", x) ]: m = _load_migration(migration_file) From 23b8418ee65230a4d8cf97dea30b0261a68caabe Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 18 Nov 2021 03:09:18 +0100 Subject: [PATCH 0945/1155] Fix diagnosis category listing --- src/diagnosis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diagnosis.py b/src/diagnosis.py index 67128217c..ea33b59f3 100644 --- a/src/diagnosis.py +++ b/src/diagnosis.py @@ -661,7 +661,7 @@ class Diagnoser: def _list_diagnosis_categories(): paths = glob.glob(os.path.dirname(__file__) + "/diagnosers/??-*.py") - names = sorted([os.path.basename(path)[: -len(".py")] for path in paths]) + names = sorted([os.path.basename(path)[: -len(".py")].split("-")[-1] for path in paths]) return names From 33d3db62b283b80d75973741f2c97d7f95449849 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 18 Nov 2021 03:12:08 +0100 Subject: [PATCH 0946/1155] Fix i18n key test --- 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 62cad7cbb..ec582ba72 100644 --- a/tests/test_i18n_keys.py +++ b/tests/test_i18n_keys.py @@ -102,7 +102,7 @@ def find_expected_string_keys(): yield m # Keys for the actionmap ... - for category in yaml.safe_load(open("src/actionsmap.yml")).values(): + for category in yaml.safe_load(open("share/actionsmap.yml")).values(): if "actions" not in category.keys(): continue for action in category["actions"].values(): From a792ed6e412d998c167945236be8005068e5e887 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 18 Nov 2021 03:54:25 +0100 Subject: [PATCH 0947/1155] Moar technical fixes --- src/diagnosis.py | 4 ++-- src/tests/conftest.py | 6 ------ 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/diagnosis.py b/src/diagnosis.py index ea33b59f3..cdb125eb1 100644 --- a/src/diagnosis.py +++ b/src/diagnosis.py @@ -423,7 +423,7 @@ class Diagnoser: not force and self.cached_time_ago() < self.cache_duration ): - self.logger_debug("Cache still valid : %s" % self.cache_file) + logger.debug("Cache still valid : %s" % self.cache_file) logger.info( m18n.n("diagnosis_cache_still_valid", category=self.description) ) @@ -457,7 +457,7 @@ class Diagnoser: new_report = {"id": self.id_, "cached_for": self.cache_duration, "items": items} - self.logger_debug("Updating cache %s" % self.cache_file) + logger.debug("Updating cache %s" % self.cache_file) self.write_cache(new_report) Diagnoser.i18n(new_report) add_ignore_flag_to_issues(new_report) diff --git a/src/tests/conftest.py b/src/tests/conftest.py index a07c44346..cd5cb307e 100644 --- a/src/tests/conftest.py +++ b/src/tests/conftest.py @@ -61,12 +61,6 @@ def new_translate(self, key, *args, **kwargs): moulinette.core.Translator.translate = new_translate -def new_m18nn(self, key, *args, **kwargs): - return self._namespaces[self._current_namespace].translate(key, *args, **kwargs) - - -moulinette.core.Moulinette18n.n = new_m18nn - # # Init the moulinette to have the cli loggers stuff # # From 799609c7a8e5946988d51779dce0eda4556b5fba Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 18 Nov 2021 13:53:00 +0100 Subject: [PATCH 0948/1155] Fix most used password file path --- src/utils/password.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/password.py b/src/utils/password.py index 04a1ed272..f0dacc8f3 100644 --- a/src/utils/password.py +++ b/src/utils/password.py @@ -36,7 +36,7 @@ SMALL_PWD_LIST = [ "rpi", ] -MOST_USED_PASSWORDS = "/usr/share/yunohost/password/100000-most-used-passwords.txt" +MOST_USED_PASSWORDS = "/usr/share/yunohost/100000-most-used-passwords.txt" # Length, digits, lowers, uppers, others STRENGTH_LEVELS = [ From 9c28c07af4b342d6db6a53818353bf19e0325ad1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 22 Nov 2021 08:10:14 +0100 Subject: [PATCH 0949/1155] Fix overly complex location for ssl workdir --- conf/ssl/openssl.cnf | 4 ++-- hooks/conf_regen/01-yunohost | 2 ++ hooks/conf_regen/02-ssl | 2 +- src/certificate.py | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/conf/ssl/openssl.cnf b/conf/ssl/openssl.cnf index 3ef7d80c3..a19a9c3df 100644 --- a/conf/ssl/openssl.cnf +++ b/conf/ssl/openssl.cnf @@ -5,7 +5,7 @@ # This definition stops the following lines choking if HOME isn't # defined. -HOME = /usr/share/yunohost/yunohost-config/ssl +HOME = /usr/share/yunohost/ssl RANDFILE = $ENV::HOME/.rnd # Extra OBJECT IDENTIFIER info: @@ -34,7 +34,7 @@ default_ca = Yunohost # The default ca section #################################################################### [ Yunohost ] -dir = /usr/share/yunohost/yunohost-config/ssl/yunoCA # Where everything is kept +dir = /usr/share/yunohost/ssl # Where everything is kept certs = $dir/certs # Where the issued certs are kept crl_dir = $dir/crl # Where the issued crl are kept database = $dir/index.txt # database index file. diff --git a/hooks/conf_regen/01-yunohost b/hooks/conf_regen/01-yunohost index 027be8020..7716f04c3 100755 --- a/hooks/conf_regen/01-yunohost +++ b/hooks/conf_regen/01-yunohost @@ -181,6 +181,8 @@ do_post_regen() { find /etc/cron.d/yunohost-* -type f -exec chmod 644 {} \; find /etc/cron.*/yunohost-* -type f -exec chown root:root {} \; + chmod 750 /usr/share/yunohost/ssl + chown root:root /var/cache/yunohost chmod 700 /var/cache/yunohost chown root:root /var/cache/moulinette diff --git a/hooks/conf_regen/02-ssl b/hooks/conf_regen/02-ssl index f27a23cf8..a824c91f8 100755 --- a/hooks/conf_regen/02-ssl +++ b/hooks/conf_regen/02-ssl @@ -2,7 +2,7 @@ set -e -ssl_dir="/usr/share/yunohost/yunohost-config/ssl/yunoCA" +ssl_dir="/usr/share/yunohost/ssl" ynh_ca="/etc/yunohost/certs/yunohost.org/ca.pem" ynh_crt="/etc/yunohost/certs/yunohost.org/crt.pem" ynh_key="/etc/yunohost/certs/yunohost.org/key.pem" diff --git a/src/certificate.py b/src/certificate.py index c697a487b..46aa9c818 100644 --- a/src/certificate.py +++ b/src/certificate.py @@ -54,7 +54,7 @@ WEBROOT_FOLDER = "/tmp/acme-challenge-public/" SELF_CA_FILE = "/etc/ssl/certs/ca-yunohost_crt.pem" ACCOUNT_KEY_FILE = "/etc/yunohost/letsencrypt_account.pem" -SSL_DIR = "/usr/share/yunohost/yunohost-config/ssl/yunoCA" +SSL_DIR = "/usr/share/yunohost/ssl" KEY_SIZE = 3072 From 10e00b3318a3350aff683a5d89c53fc9055fbd21 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 22 Nov 2021 08:13:14 +0100 Subject: [PATCH 0950/1155] Fix old moulinette stuff not relevant anymore --- debian/postinst | 4 ---- hooks/conf_regen/01-yunohost | 2 -- 2 files changed, 6 deletions(-) diff --git a/debian/postinst b/debian/postinst index 0dd1dedd0..aa2d17107 100644 --- a/debian/postinst +++ b/debian/postinst @@ -3,10 +3,6 @@ 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 diff --git a/hooks/conf_regen/01-yunohost b/hooks/conf_regen/01-yunohost index 7716f04c3..d73a899da 100755 --- a/hooks/conf_regen/01-yunohost +++ b/hooks/conf_regen/01-yunohost @@ -185,8 +185,6 @@ 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 From a399675f6057790e1c9e63eef851a649cd066bb8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 22 Nov 2021 08:14:45 +0100 Subject: [PATCH 0951/1155] Fix location for specte meltdown checker script --- src/diagnosers/00-basesystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diagnosers/00-basesystem.py b/src/diagnosers/00-basesystem.py index 66a2bf47b..0ef1a5197 100644 --- a/src/diagnosers/00-basesystem.py +++ b/src/diagnosers/00-basesystem.py @@ -206,7 +206,7 @@ class MyDiagnoser(Diagnoser): # 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" + SCRIPT_PATH = "/usr/lib/python3/dist-packages/yunohost/vendor/spectre-meltdown-checker/spectre-meltdown-checker.sh" # '--variant 3' corresponds to Meltdown # example output from the script: From dc4341f98cc75baeff03b32777e17045338ec144 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 22 Nov 2021 08:21:54 +0100 Subject: [PATCH 0952/1155] migrate_to_bullseye: /usr/share/yunohost/yunohost-config/ssl/yunoCA -> /usr/share/yunohost/ssl --- src/yunohost/data_migrations/0021_migrate_to_bullseye.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py index db10777bf..aee3bc2d6 100644 --- a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py +++ b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py @@ -77,6 +77,13 @@ class MyMigration(Migration): rm("/etc/mysql/my.cnf", force=True) self.apt_install("mariadb-common --reinstall -o Dpkg::Options::='--force-confmiss'") + # + # /usr/share/yunohost/yunohost-config/ssl/yunoCA -> /usr/share/yunohost/ssl + # + if os.path.exists("/usr/share/yunohost/yunohost-config/ssl/yunoCA"): + os.system("mv /usr/share/yunohost/yunohost-config/ssl/yunoCA /usr/share/yunohost/ssl") + rm("/usr/share/yunohost/yunohost-config", recursive=True, force=True) + # # Main upgrade # From 4f6d3c426ab5159230b017b1b23ea8e9bfe0a8f9 Mon Sep 17 00:00:00 2001 From: tituspijean Date: Tue, 23 Nov 2021 21:22:22 +0000 Subject: [PATCH 0953/1155] Allow tilde in username/organization for repo URLs --- 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 649110267..f6d940da8 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -79,7 +79,7 @@ re_app_instance_name = re.compile( ) APP_REPO_URL = re.compile( - r"^https://[a-zA-Z0-9-_.]+/[a-zA-Z0-9-_./]+/[a-zA-Z0-9-_.]+_ynh(/?(-/)?tree/[a-zA-Z0-9-_.]+)?(\.git)?/?$" + r"^https://[a-zA-Z0-9-_.]+/[a-zA-Z0-9-_./~]+/[a-zA-Z0-9-_.]+_ynh(/?(-/)?tree/[a-zA-Z0-9-_.]+)?(\.git)?/?$" ) APP_FILES_TO_COPY = [ From 6a8aa333eac7eada56f24f86771b30060f7fb66b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 23 Nov 2021 23:11:37 +0100 Subject: [PATCH 0954/1155] appurl: Add test for app url containing tilde --- src/yunohost/tests/test_appurl.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yunohost/tests/test_appurl.py b/src/yunohost/tests/test_appurl.py index 7b4c6e2e3..00bfe5c58 100644 --- a/src/yunohost/tests/test_appurl.py +++ b/src/yunohost/tests/test_appurl.py @@ -70,6 +70,7 @@ def test_repo_url_definition(): ) assert _is_app_repo_url("https://github.com/YunoHost-Apps/foobar_ynh/tree/1.23.4") assert _is_app_repo_url("git@github.com:YunoHost-Apps/foobar_ynh.git") + assert _is_app_repo_url("https://git.super.host/~max/foobar_ynh") assert not _is_app_repo_url("github.com/YunoHost-Apps/foobar_ynh") assert not _is_app_repo_url("http://github.com/YunoHost-Apps/foobar_ynh") From 6488b4f6493c65592c69a8f435b882f822449f7b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 25 Nov 2021 20:01:54 +0100 Subject: [PATCH 0955/1155] Fix /etc/yunohost permissions on some setups --- data/hooks/conf_regen/01-yunohost | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index 1703dccd1..e2ea1bbcb 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -179,6 +179,9 @@ do_post_regen() { chown admin:root /home/yunohost.backup chown admin:root /home/yunohost.backup/archives + # NB: x permission for 'others' is important for ssl-cert (and maybe mdns), otherwise slapd will fail to start because can't access the certs + chmod 755 /etc/yunohost + # Certs # We do this with find because there could be a lot of them... chown -R root:ssl-cert /etc/yunohost/certs From 7e7dbe41d711462d6db746e4d0dd4a44ad4c4b2e Mon Sep 17 00:00:00 2001 From: ljf Date: Fri, 26 Nov 2021 02:18:00 +0100 Subject: [PATCH 0956/1155] [enh] Hotspot and hairpining use cases --- data/hooks/conf_regen/43-dnsmasq | 17 ++++++++++++++--- data/templates/dnsmasq/domain.tpl | 6 ++++-- data/templates/dnsmasq/plain/dnsmasq.conf | 6 +++++- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/data/hooks/conf_regen/43-dnsmasq b/data/hooks/conf_regen/43-dnsmasq index 13c442158..0370d10b4 100755 --- a/data/hooks/conf_regen/43-dnsmasq +++ b/data/hooks/conf_regen/43-dnsmasq @@ -14,7 +14,7 @@ do_pre_regen() { etcdefault_dir="${pending_dir}/etc/default" mkdir -p "$etcdefault_dir" - # add general conf files + # add default conf files cp plain/etcdefault ${pending_dir}/etc/default/dnsmasq cp plain/dnsmasq.conf ${pending_dir}/etc/dnsmasq.conf @@ -26,11 +26,22 @@ 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='' + IFS=' ' read -a interfaces <<< "$(ls /sys/class/net)" + wireless_interfaces=() + for dev in "${interfaces[@]}"; do + if [ -d "/sys/class/net/$dev/wireless" ]; then + wireless_interfaces+=("$dev") + fi + done - export ipv4 - export ipv6 + # General configuration + export wireless_interfaces + ynh_render_template "dnsmasq.conf.tpl" "${pending_dir}/etc/dnsmasq.conf" # add domain conf files + export interfaces + export ipv4 + export ipv6 for domain in $YNH_DOMAINS; do [[ ! $domain =~ \.local$ ]] || continue export domain diff --git a/data/templates/dnsmasq/domain.tpl b/data/templates/dnsmasq/domain.tpl index c4bb56d1d..faa93954c 100644 --- a/data/templates/dnsmasq/domain.tpl +++ b/data/templates/dnsmasq/domain.tpl @@ -1,5 +1,7 @@ -host-record={{ domain }},{{ ipv4 }} -host-record=xmpp-upload.{{ domain }},{{ ipv4 }} +{% for interface in interfaces %} +interface-name={{ domain }},{{ interface }} +interface-name=xmpp-upload.{{ domain }},{{ interface }} +{% endfor %} {% if ipv6 %} host-record={{ domain }},{{ ipv6 }} host-record=xmpp-upload.{{ domain }},{{ ipv6 }} diff --git a/data/templates/dnsmasq/plain/dnsmasq.conf b/data/templates/dnsmasq/plain/dnsmasq.conf index 12a14048a..ac4d125de 100644 --- a/data/templates/dnsmasq/plain/dnsmasq.conf +++ b/data/templates/dnsmasq/plain/dnsmasq.conf @@ -1,6 +1,10 @@ domain-needed expand-hosts +localise-queries -listen-address=127.0.0.1 + +{% for interface in wireless_interfaces %} +interface={{ interface }} +{% endfor %} resolv-file=/etc/resolv.dnsmasq.conf cache-size=256 From c4f8c9e0221c5ba31cbc34b2a5b74583ca98c2ef Mon Sep 17 00:00:00 2001 From: ljf Date: Fri, 26 Nov 2021 02:18:33 +0100 Subject: [PATCH 0957/1155] [enh] Move dnsmasq conf in template dir --- data/templates/dnsmasq/{plain/dnsmasq.conf => dnsmasq.conf.tpl} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename data/templates/dnsmasq/{plain/dnsmasq.conf => dnsmasq.conf.tpl} (100%) diff --git a/data/templates/dnsmasq/plain/dnsmasq.conf b/data/templates/dnsmasq/dnsmasq.conf.tpl similarity index 100% rename from data/templates/dnsmasq/plain/dnsmasq.conf rename to data/templates/dnsmasq/dnsmasq.conf.tpl From c49628346f5c691639f93fb48b7b587d771c6aca Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 26 Nov 2021 18:20:40 +0100 Subject: [PATCH 0958/1155] mdns: Don't add yunohost.local in config if it's already among the yunohost domains --- data/hooks/conf_regen/37-mdns | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/data/hooks/conf_regen/37-mdns b/data/hooks/conf_regen/37-mdns index faa581efa..95c7a522b 100755 --- a/data/hooks/conf_regen/37-mdns +++ b/data/hooks/conf_regen/37-mdns @@ -4,7 +4,11 @@ set -e _generate_config() { echo "domains:" - echo " - yunohost.local" + # Add yunohost.local (only if yunohost.local ain't already in ynh_domains) + if ! echo "$YNH_DOMAINS" | tr ' ' '\n' | grep -q --line-regexp 'yunohost.local' + then + echo " - yunohost.local" + fi 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 From 321c8dd5ba94bf7b0eb999afd7fce532e7f67536 Mon Sep 17 00:00:00 2001 From: ljf Date: Fri, 26 Nov 2021 18:42:22 +0100 Subject: [PATCH 0959/1155] [fix] Bash array not supported in ynh_render_template --- data/hooks/conf_regen/43-dnsmasq | 6 +++--- data/templates/dnsmasq/dnsmasq.conf.tpl | 4 ++-- data/templates/dnsmasq/domain.tpl | 3 ++- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/data/hooks/conf_regen/43-dnsmasq b/data/hooks/conf_regen/43-dnsmasq index 0370d10b4..7f65b9494 100755 --- a/data/hooks/conf_regen/43-dnsmasq +++ b/data/hooks/conf_regen/43-dnsmasq @@ -26,11 +26,11 @@ 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='' - IFS=' ' read -a interfaces <<< "$(ls /sys/class/net)" - wireless_interfaces=() + interfaces="$(ls /sys/class/net)" + wireless_interfaces="" for dev in "${interfaces[@]}"; do if [ -d "/sys/class/net/$dev/wireless" ]; then - wireless_interfaces+=("$dev") + wireless_interfaces+=" $dev" fi done diff --git a/data/templates/dnsmasq/dnsmasq.conf.tpl b/data/templates/dnsmasq/dnsmasq.conf.tpl index ac4d125de..eece530dc 100644 --- a/data/templates/dnsmasq/dnsmasq.conf.tpl +++ b/data/templates/dnsmasq/dnsmasq.conf.tpl @@ -2,8 +2,8 @@ domain-needed expand-hosts localise-queries - -{% for interface in wireless_interfaces %} +{% set interfaces = wireless_interfaces.strip().split(' ') %} +{% for interface in interfaces %} interface={{ interface }} {% endfor %} resolv-file=/etc/resolv.dnsmasq.conf diff --git a/data/templates/dnsmasq/domain.tpl b/data/templates/dnsmasq/domain.tpl index faa93954c..50b946176 100644 --- a/data/templates/dnsmasq/domain.tpl +++ b/data/templates/dnsmasq/domain.tpl @@ -1,4 +1,5 @@ -{% for interface in interfaces %} +{% set interfaces_list = interfaces.split(' ') %} +{% for interface in interfaces_list %} interface-name={{ domain }},{{ interface }} interface-name=xmpp-upload.{{ domain }},{{ interface }} {% endfor %} From f34f9e5fc35cbe3f6c1986ad3f23695da637fab7 Mon Sep 17 00:00:00 2001 From: ljf Date: Fri, 26 Nov 2021 19:39:37 +0100 Subject: [PATCH 0960/1155] [fix] DNSmasq template generation --- data/hooks/conf_regen/43-dnsmasq | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/data/hooks/conf_regen/43-dnsmasq b/data/hooks/conf_regen/43-dnsmasq index 7f65b9494..8b5691dc6 100755 --- a/data/hooks/conf_regen/43-dnsmasq +++ b/data/hooks/conf_regen/43-dnsmasq @@ -16,7 +16,6 @@ do_pre_regen() { # add default conf files cp plain/etcdefault ${pending_dir}/etc/default/dnsmasq - cp plain/dnsmasq.conf ${pending_dir}/etc/dnsmasq.conf # add resolver file cat plain/resolv.dnsmasq.conf | grep "^nameserver" | shuf >${pending_dir}/etc/resolv.dnsmasq.conf @@ -26,9 +25,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='' - interfaces="$(ls /sys/class/net)" - wireless_interfaces="" - for dev in "${interfaces[@]}"; do + interfaces="$(ls -m /sys/class/net | sed s/,//g)" + wireless_interfaces="lo" + for dev in $(ls /sys/class/net); do if [ -d "/sys/class/net/$dev/wireless" ]; then wireless_interfaces+=" $dev" fi From a0344d44bd7e117dc2aec495408ec6aa135dc3ed Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 27 Nov 2021 00:56:03 +0100 Subject: [PATCH 0961/1155] Update changelog for 4.3.4 --- debian/changelog | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/debian/changelog b/debian/changelog index f2266e871..fbf9bcefd 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,14 @@ +yunohost (4.3.4) stable; urgency=low + + - [fix] apps: Allow tilde in username/organization for repo URLs ([#1382](https://github.com/YunoHost/yunohost/pull/1382)) + - [fix] misc: /etc/yunohost permissions broken on some setups (6488b4f6) + - [fix] mdns: Don't add yunohost.local in config if it's already among the yunohost domains (c4962834) + - [enh] dnsmasq: Tweak conf for better support of some stuff like the hotspot app ([#1383](https://github.com/YunoHost/yunohost/pull/1383)) + + Thanks to all contributors <3 ! (ljf, tituspijean) + + -- Alexandre Aubin Sat, 27 Nov 2021 00:53:16 +0100 + yunohost (4.3.3) stable; urgency=low - [fix] log: fix dump_script_log_extract_for_debugging displaying wrong log snippet during failed upgrade ([#1376](https://github.com/YunoHost/yunohost/pull/1376)) From 5881938c69b2eb2cd1a7dcf2d34aee77e6190556 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 27 Nov 2021 16:33:29 +0100 Subject: [PATCH 0962/1155] Force permission on /etc/resolv.dnsmasq.conf to fix an issue on some setup with umask=027 --- data/hooks/conf_regen/43-dnsmasq | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/hooks/conf_regen/43-dnsmasq b/data/hooks/conf_regen/43-dnsmasq index 8b5691dc6..7acc16e4b 100755 --- a/data/hooks/conf_regen/43-dnsmasq +++ b/data/hooks/conf_regen/43-dnsmasq @@ -61,6 +61,9 @@ do_pre_regen() { do_post_regen() { regen_conf_files=$1 + # Force permission (to cover some edge cases where root's umask is like 027 and then dnsmasq cant read this file) + chown 644 /etc/resolv.dnsmasq.conf + # 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 From 31dfb19a02b431ebf538099d3c19e66753ee5a65 Mon Sep 17 00:00:00 2001 From: ljf Date: Sat, 27 Nov 2021 19:29:20 +0100 Subject: [PATCH 0963/1155] [fix] Try to fix the return line bug in dnsmasq conf --- 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 8b5691dc6..a2aa4520b 100755 --- a/data/hooks/conf_regen/43-dnsmasq +++ b/data/hooks/conf_regen/43-dnsmasq @@ -25,7 +25,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='' - interfaces="$(ls -m /sys/class/net | sed s/,//g)" + interfaces="$(ip -j addr show | jq -r '[.[].ifname]|join(" ")')" wireless_interfaces="lo" for dev in $(ls /sys/class/net); do if [ -d "/sys/class/net/$dev/wireless" ]; then From b3df36dd168d52bfd81d4bc785bd42b074950920 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 27 Nov 2021 20:27:29 +0100 Subject: [PATCH 0964/1155] =?UTF-8?q?Typo=20=C3=A9=5F=C3=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/hooks/conf_regen/37-mdns | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/conf_regen/37-mdns b/data/hooks/conf_regen/37-mdns index 95c7a522b..bd11001e7 100755 --- a/data/hooks/conf_regen/37-mdns +++ b/data/hooks/conf_regen/37-mdns @@ -15,7 +15,7 @@ _generate_config() { [[ "$domain" =~ ^[^.]+\.local$ ]] || continue echo " - $domain" done - if [[ -e /etc/yunohot/mdns.aliases ]] + if [[ -e /etc/yunohost/mdns.aliases ]] then for localalias in $(cat /etc/yunohost/mdns.aliases | grep -v "^ *$") do From e3f33d6c203983820f4c9cd748cf5d63cede6af3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 27 Nov 2021 21:17:06 +0100 Subject: [PATCH 0965/1155] Update changelog for 4.3.4.1 --- debian/changelog | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/debian/changelog b/debian/changelog index fbf9bcefd..f53b259b6 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,13 @@ +yunohost (4.3.4.1) stable; urgency=low + + - [fix] regenconf: Force permission on /etc/resolv.dnsmasq.conf to fix an issue on some setup with umask=027 (5881938c) + - [fix] regenconf: Typo in custom mdns alias regen conf (b3df36dd) + - [fix] regenconf: Try to fix the return line bug in dnsmasq conf ([#1385](https://github.com/YunoHost/yunohost/pull/1385)) + + Thanks to all contributors <3 ! (ljf) + + -- Alexandre Aubin Sat, 27 Nov 2021 21:15:29 +0100 + yunohost (4.3.4) stable; urgency=low - [fix] apps: Allow tilde in username/organization for repo URLs ([#1382](https://github.com/YunoHost/yunohost/pull/1382)) From 6854f23cca74fad1b131f5bdec7d92c7cc62aec4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 28 Nov 2021 02:51:58 +0100 Subject: [PATCH 0966/1155] [fix] yunomdns: Ignore ipv4 link-local addresses --- bin/yunomdns | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/yunomdns b/bin/yunomdns index 0aee28195..11bed3215 100755 --- a/bin/yunomdns +++ b/bin/yunomdns @@ -21,7 +21,7 @@ def get_network_local_interfaces() -> Dict[str, Dict[str, List[str]]]: interfaces = { adapter.name: { - "ipv4": [ip.ip for ip in adapter.ips if ip.is_IPv4 and ip_address(ip.ip).is_private], + "ipv4": [ip.ip for ip in adapter.ips if ip.is_IPv4 and ip_address(ip.ip).is_private and not ip_address(ip.ip).is_link_local], "ipv6": [ip.ip[0] for ip in adapter.ips if ip.is_IPv6 and ip_address(ip.ip[0]).is_private and not ip_address(ip.ip[0]).is_link_local], } for adapter in ifaddr.get_adapters() From f43e567bf0930373e822d814ad21df3ec8b91bae Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 28 Nov 2021 16:12:24 +0100 Subject: [PATCH 0967/1155] apps: Remove the 'HOME' var from hook_exec env, because it's often defined in CLI but not in API because yunohost-api runs in a systemd service --- src/hook.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/hook.py b/src/hook.py index 98b624f12..fbf336ed7 100644 --- a/src/hook.py +++ b/src/hook.py @@ -439,6 +439,12 @@ def _hook_exec_bash(path, args, chdir, env, user, return_format, loggers): _env = os.environ.copy() _env.update(env) + # Remove the 'HOME' var which is causing some inconsistencies between + # cli and webapi (HOME ain't defined in yunohost-api because ran from systemd) + # Apps that need the HOME var should define it in the app scripts + if "HOME" in _env: + del _env["HOME"] + returncode = call_async_output(command, loggers, shell=False, cwd=chdir, env=_env) raw_content = None From bfa5092c103de871fb745d5a81a529c0e55e3d24 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 28 Nov 2021 13:39:16 +0100 Subject: [PATCH 0968/1155] ci: Yolorework i18n tests, add a maintenance/ folder --- .gitlab/ci/test.gitlab-ci.yml | 19 -- .gitlab/ci/translation.gitlab-ci.yml | 14 +- locales/ca.json | 34 --- locales/de.json | 38 +--- locales/en.json | 95 +++++---- locales/eo.json | 5 - locales/es.json | 21 +- locales/eu.json | 36 +--- locales/fa.json | 37 ---- locales/fr.json | 42 +--- locales/gl.json | 42 +--- locales/hi.json | 1 - locales/id.json | 2 +- locales/it.json | 38 +--- locales/nb_NO.json | 1 - locales/nl.json | 1 - locales/oc.json | 16 +- locales/pt.json | 3 +- locales/ru.json | 3 +- locales/sl.json | 2 +- locales/uk.json | 42 +--- locales/zh_Hans.json | 42 +--- maintenance/autofix_locale_format.py | 169 +++++++++++++++ maintenance/make_changelog.sh | 36 ++++ .../missing_i18n_keys.py | 95 +++++---- tests/add_missing_keys.py | 193 ------------------ tests/autofix_locale_format.py | 53 ----- tests/reformat_locales.py | 60 ------ tests/remove_stale_translated_strings.py | 25 --- tests/test_translation_format_consistency.py | 52 ----- 30 files changed, 334 insertions(+), 883 deletions(-) create mode 100644 maintenance/autofix_locale_format.py create mode 100644 maintenance/make_changelog.sh rename tests/test_i18n_keys.py => maintenance/missing_i18n_keys.py (71%) delete mode 100644 tests/add_missing_keys.py delete mode 100644 tests/autofix_locale_format.py delete mode 100644 tests/reformat_locales.py delete mode 100644 tests/remove_stale_translated_strings.py delete mode 100644 tests/test_translation_format_consistency.py diff --git a/.gitlab/ci/test.gitlab-ci.yml b/.gitlab/ci/test.gitlab-ci.yml index 1165e4826..27df0658d 100644 --- a/.gitlab/ci/test.gitlab-ci.yml +++ b/.gitlab/ci/test.gitlab-ci.yml @@ -23,7 +23,6 @@ artifacts: true - job: upgrade - ######################################## # TESTS ######################################## @@ -51,24 +50,6 @@ full-tests: reports: junit: report.xml -test-i18n-keys: - extends: .test-stage - script: - - python3 -m pytest tests/test_i18n_keys.py - only: - changes: - - locales/en.json - - src/*.py - - src/diagnosers/*.py - -test-translation-format-consistency: - extends: .test-stage - script: - - python3 -m pytest tests/test_translation_format_consistency.py - only: - changes: - - locales/* - test-actionmap: extends: .test-stage script: diff --git a/.gitlab/ci/translation.gitlab-ci.yml b/.gitlab/ci/translation.gitlab-ci.yml index 41e8c82d2..159dc7aa0 100644 --- a/.gitlab/ci/translation.gitlab-ci.yml +++ b/.gitlab/ci/translation.gitlab-ci.yml @@ -1,6 +1,14 @@ ######################################## # TRANSLATION ######################################## +test-i18n-keys: + script: + - python3 maintenance/missing_i18n_keys --check + only: + changes: + - locales/en.json + - src/*.py + - src/diagnosers/*.py autofix-translated-strings: stage: translation @@ -12,12 +20,10 @@ autofix-translated-strings: - 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 - git checkout -b "ci-autofix-translated-strings-${CI_COMMIT_REF_NAME}" --no-track - - python3 remove_stale_translated_strings.py - - python3 autofix_locale_format.py - - python3 reformat_locales.py + - python3 maintenance/missing_i18n_keys --fix + - python3 maintenanceautofix_locale_format.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 "HEAD":"ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}" diff --git a/locales/ca.json b/locales/ca.json index 00e3f65ad..4b1c51edd 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -17,7 +17,6 @@ "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}", - "app_manifest_invalid": "Hi ha algun error amb el manifest de l'aplicació: {error}", "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", @@ -132,7 +131,6 @@ "domains_available": "Dominis disponibles:", "done": "Fet", "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", "dyndns_ip_updated": "S'ha actualitzat l'adreça IP al DynDNS", @@ -297,7 +295,6 @@ "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}»", "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}", @@ -316,8 +313,6 @@ "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_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)...", @@ -547,31 +542,7 @@ "restore_already_installed_apps": "No s'han pogut restaurar les següents aplicacions perquè ja estan instal·lades: {apps}", "app_packaging_format_not_supported": "No es pot instal·lar aquesta aplicació ja que el format del paquet no és compatible amb la versió de YunoHost del sistema. Hauríeu de considerar actualitzar el sistema.", "diagnosis_dns_try_dyndns_update_force": "La configuració DNS d'aquest domini hauria de ser gestionada automàticament per YunoHost. Si aquest no és el cas, podeu intentar forçar-ne l'actualització utilitzant yunohost dyndns update --force.", - "migration_0015_cleaning_up": "Netejant la memòria cau i els paquets que ja no són necessaris...", - "migration_0015_specific_upgrade": "Començant l'actualització dels paquets del sistema que s'han d'actualitzar de forma independent...", - "migration_0015_modified_files": "Tingueu en compte que s'han trobat els següents fitxers que es van modificar manualment i podria ser que es sobreescriguin durant l'actualització: {manually_modified_files}", - "migration_0015_problematic_apps_warning": "Tingueu en compte que s'han trobat les següents aplicacions que podrien ser problemàtiques. Sembla que aquestes aplicacions no s'han instal·lat des del catàleg d'aplicacions de YunoHost, o no estan marcades com «funcionant». En conseqüència, no es pot garantir que segueixin funcionant després de l'actualització: {problematic_apps}", - "migration_0015_general_warning": "Tingueu en compte que aquesta migració és una operació delicada. L'equip de YunoHost ha fet tots els possibles per revisar i testejar, però tot i això podria ser que la migració trenqui alguna part del sistema o algunes aplicacions.\n\nPer tant, està recomana:\n - Fer una còpia de seguretat de totes les dades o aplicacions crítiques. Més informació a https://yunohost.org/backup;\n - Ser pacient un cop comenci la migració: en funció de la connexió Internet i del maquinari, podria estar unes hores per actualitzar-ho tot.", - "migration_0015_system_not_fully_up_to_date": "El sistema no està completament al dia. Heu de fer una actualització normal abans de fer la migració a Buster.", - "migration_0015_not_enough_free_space": "Hi ha poc espai lliure a /var/! HI hauria d'haver un mínim de 1GB lliure per poder fer aquesta migració.", - "migration_0015_not_stretch": "La distribució actual de Debian no és Stretch!", - "migration_0015_yunohost_upgrade": "Començant l'actualització del nucli de YunoHost...", - "migration_0015_still_on_stretch_after_main_upgrade": "Alguna cosa ha anat malament durant la actualització principal, sembla que el sistema encara està en Debian Stretch", - "migration_0015_main_upgrade": "Començant l'actualització principal...", - "migration_0015_patching_sources_list": "Apedaçament de source.lists...", - "migration_0015_start": "Començant la migració a Buster", - "migration_description_0015_migrate_to_buster": "Actualitza els sistema a Debian Buster i YunoHost 4.x", "regenconf_need_to_explicitly_specify_ssh": "La configuració ssh ha estat modificada manualment, però heu d'especificar explícitament la categoria «ssh» amb --force per fer realment els canvis.", - "migration_0015_weak_certs": "S'han trobat els següents certificats que encara utilitzen algoritmes de signatura febles i s'han d'actualitzar per a ser compatibles amb la propera versió de nginx: {certs}", - "service_description_php7.4-fpm": "Executa aplicacions escrites en PHP amb NGINX", - "migration_0018_failed_to_reset_legacy_rules": "No s'ha pogut restaurar les regles legacy iptables: {error}", - "migration_0018_failed_to_migrate_iptables_rules": "No s'ha pogut migrar les regles legacy iptables a nftables: {error}", - "migration_0017_not_enough_space": "Feu suficient espai disponible en {path} per a realitzar la migració.", - "migration_0017_postgresql_11_not_installed": "PostgreSQL 9.6 està instal·lat, però postgreSQL 11 no? Potser que hagi passat alguna cosa rara en aquest sistema :(...", - "migration_0017_postgresql_96_not_installed": "PostgreSQL no està instal·lat en aquest sistema. No s'ha de realitzar cap operació.", - "migration_description_0018_xtable_to_nftable": "Migrar les regles del trànsit de xarxa al nou sistema nftable", - "migration_description_0017_postgresql_9p6_to_11": "Migrar les bases de dades de PosrgreSQL 9.6 a 11", - "migration_description_0016_php70_to_php73_pools": "Migrar els fitxers de configuració «pool» php7.0-fpm a php7.3", "global_settings_setting_backup_compress_tar_archives": "Comprimir els arxius (.tar.gz) en lloc d'arxius no comprimits (.tar) al crear noves còpies de seguretat. N.B.: activar aquesta opció permet fer arxius de còpia de seguretat més lleugers, però el procés inicial de còpia de seguretat serà significativament més llarg i més exigent a nivell de CPU.", "global_settings_setting_smtp_relay_host": "L'amfitrió de tramesa SMTP que s'ha d'utilitzar per enviar correus electrònics en lloc d'aquesta instància de YunoHost. És útil si esteu en una de les següents situacions: el port 25 està bloquejat per el vostre proveïdor d'accés a internet o proveïdor de servidor privat virtual, si teniu una IP residencial llistada a DUHL, si no podeu configurar el DNS invers o si el servidor no està directament exposat a internet i voleu utilitzar-ne un altre per enviar correus electrònics.", "unknown_main_domain_path": "Domini o ruta desconeguda per a «{app}». Heu d'especificar un domini i una ruta per a poder especificar una URL per al permís.", @@ -582,15 +553,10 @@ "permission_protected": "El permís {permission} està protegit. No podeu afegir o eliminar el grup visitants a o d'aquest permís.", "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_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}»", "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", - "domain_name_unknown": "Domini «{domain}» desconegut", "diagnosis_processes_killed_by_oom_reaper": "El sistema ha matat alguns processos recentment perquè s'ha quedat sense memòria. Això acostuma a ser un símptoma de falta de memòria en el sistema o d'un procés que consumeix massa memòria. Llista dels processos que s'han matat:\n{kills_summary}", "diagnosis_package_installed_from_sury_details": "Alguns paquets s'han instal·lat per equivocació des d'un repositori de tercers anomenat Sury. L'equip de YunoHost a millorat l'estratègia per a gestionar aquests paquets, però s'espera que algunes configuracions que han instal·lat aplicacions PHP7.3 a Stretch puguin tenir algunes inconsistències. Per a resoldre aquesta situació, hauríeu d'intentar executar la següent ordre: {cmd_to_fix}", "diagnosis_package_installed_from_sury": "Alguns paquets del sistema s'han de tornar a versions anteriors", diff --git a/locales/de.json b/locales/de.json index 5dd8c059d..260ba3214 100644 --- a/locales/de.json +++ b/locales/de.json @@ -10,7 +10,6 @@ "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} 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?", @@ -185,7 +184,6 @@ "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} 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 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}'", @@ -308,9 +306,6 @@ "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 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", "mail_unavailable": "Diese E-Mail Adresse ist reserviert und wird dem/der ersten Benutzer:in automatisch zugewiesen", "diagnosis_services_conf_broken": "Die Konfiguration für den Dienst {service} ist fehlerhaft!", "diagnosis_services_running": "Dienst {service} läuft!", @@ -335,7 +330,6 @@ "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}' 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}\" 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})", @@ -358,7 +352,6 @@ "diagnosis_swap_none": "Das System hat gar keinen Swap. Sie sollten sich überlegen mindestens {recommended} an Swap einzurichten, um Situationen zu verhindern, in welchen der RAM des Systems knapp wird.", "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}' 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}", @@ -435,7 +428,6 @@ "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}", - "domain_name_unknown": "Domäne '{domain}' unbekannt", "group_user_not_in_group": "Benutzer:in {user} ist nicht in der Gruppe {group}", "group_user_already_in_group": "Benutzer:in {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", @@ -459,7 +451,6 @@ "log_available_on_yunopaste": "Das Protokoll ist nun via {url} verfügbar", "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", "mailbox_disabled": "E-Mail für Benutzer:in {user} deaktiviert", "log_tools_reboot": "Server neustarten", "log_tools_shutdown": "Server ausschalten", @@ -483,28 +474,9 @@ "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", - "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}]", - "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}", "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", "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_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}.", @@ -516,9 +488,6 @@ "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}", "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}...", @@ -572,7 +541,6 @@ "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!", "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 geändert und ist unsicher, weil sie keine 'AllowGroups'- oder 'AllowUsers' -Direktiven für die Beschränkung des Zugriffs durch autorisierte Benutzer enthält.", @@ -582,7 +550,6 @@ "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.", @@ -603,7 +570,6 @@ "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}]", "server_reboot": "Der Server wird neu gestartet", "server_shutdown_confirm": "Der Server wird sofort heruntergefahren, sind Sie sicher? [{answers}]", @@ -615,8 +581,6 @@ "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", @@ -642,4 +606,4 @@ "config_validate_time": "Sollte eine zulässige Zeit wie HH:MM sein", "config_validate_url": "Sollte eine zulässige web URL sein", "config_version_not_supported": "Konfigurationspanel Versionen '{version}' sind nicht unterstützt." -} +} \ No newline at end of file diff --git a/locales/en.json b/locales/en.json index 20667d076..f720af34e 100644 --- a/locales/en.json +++ b/locales/en.json @@ -213,11 +213,11 @@ "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_special_use_tld": "Domain {domain} is based on a special-use top-level domain (TLD) such as .local or .test and is therefore 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_special_use_tld": "Domain {domain} is based on a special-use top-level domain (TLD) such as .local or .test and is therefore not expected to be exposed 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_unreachable": "Domain {domain} appears unreachable through HTTP from outside the local network.", "diagnosis_ignored_issues": "(+ {nb_ignored} ignored issue(s))", @@ -302,47 +302,47 @@ "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_config_api_protocol": "API protocol", + "domain_config_auth_application_key": "Application key", + "domain_config_auth_application_secret": "Application secret key", + "domain_config_auth_consumer_key": "Consumer key", + "domain_config_auth_entrypoint": "API entry point", + "domain_config_auth_key": "Authentication key", + "domain_config_auth_secret": "Authentication secret", + "domain_config_auth_token": "Authentication token", + "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": "Instant messaging (XMPP)", "domain_created": "Domain created", "domain_creation_failed": "Unable to create domain {domain}: {error}", "domain_deleted": "Domain deleted", "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_dns_conf_special_use_tld": "This domain is based on a special-use top-level domain (TLD) such as .local or .test and is therefore not expected to have actual DNS records.", + "domain_dns_push_already_up_to_date": "Records already up to date, nothing to do.", + "domain_dns_push_failed": "Updating the DNS records failed miserably.", + "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_managed_in_parent_domain": "The automatic DNS configuration feature is managed in the parent domain {parent_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_partial_failure": "DNS records partially updated: some warnings/errors were reported.", + "domain_dns_push_record_failed": "Failed to {action} record {type}/{name} : {error}", + "domain_dns_push_success": "DNS records updated!", + "domain_dns_pushing": "Pushing DNS records...", + "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_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_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_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_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_hostname_failed": "Unable to set new hostname. This might cause an issue later (it might be fine).", - "domain_unknown": "Domain '{domain}' unknown", + "domain_registrar_is_not_configured": "The registrar is not yet configured for domain {domain}.", "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 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", - "domain_config_xmpp": "Instant messaging (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", - "domain_config_auth_application_secret": "Application secret key", - "domain_config_auth_consumer_key": "Consumer key", + "domain_unknown": "Domain '{domain}' unknown", "domains_available": "Available domains:", "done": "Done", "downloading": "Downloading...", @@ -419,15 +419,15 @@ "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_number_min": "Must be greater than {min}", "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_attribute_already_exists": "LDAP attribute '{attribute}' already exists with value '{value}'", "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", @@ -443,9 +443,9 @@ "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_dns_push": "Push DNS records 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", @@ -484,22 +484,22 @@ "mailbox_used_space_dovecot_down": "The Dovecot mailbox service needs to be up if you want to fetch used mailbox space", "main_domain_change_failed": "Unable to change the main domain", "main_domain_changed": "The main domain has been changed", - "migration_0021_start" : "Starting migration to Bullseye", - "migration_0021_patching_sources_list": "Patching the sources.lists...", - "migration_0021_main_upgrade": "Starting main upgrade...", - "migration_0021_still_on_buster_after_main_upgrade": "Something went wrong during the main upgrade, the system appears to still be on Debian Buster", - "migration_0021_yunohost_upgrade" : "Starting YunoHost core upgrade...", - "migration_0021_not_buster" : "The current Debian distribution is not Buster!", - "migration_0021_not_enough_free_space" : "Free space is pretty low in /var/! You should have at least 1GB free to run this migration.", - "migration_0021_system_not_fully_up_to_date": "Your system is not fully up-to-date. Please perform a regular upgrade before running the migration to Bullseye.", - "migration_0021_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_0021_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_0021_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_0021_cleaning_up": "Cleaning up cache and packages not useful anymore...", + "migration_0021_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_0021_main_upgrade": "Starting main upgrade...", + "migration_0021_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_0021_not_buster": "The current Debian distribution is not Buster!", + "migration_0021_not_enough_free_space": "Free space is pretty low in /var/! You should have at least 1GB free to run this migration.", "migration_0021_patch_yunohost_conflicts": "Applying patch to workaround conflict issue...", + "migration_0021_patching_sources_list": "Patching the sources.lists...", + "migration_0021_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_0021_start": "Starting migration to Bullseye", + "migration_0021_still_on_buster_after_main_upgrade": "Something went wrong during the main upgrade, the system appears to still be on Debian Buster", + "migration_0021_system_not_fully_up_to_date": "Your system is not fully up-to-date. Please perform a regular upgrade before running the migration to Bullseye.", + "migration_0021_yunohost_upgrade": "Starting YunoHost core upgrade...", + "migration_0023_not_enough_space": "Make sufficient space available in {path} to run the migration.", "migration_0023_postgresql_11_not_installed": "PostgreSQL was not installed on your system. Nothing to do.", "migration_0023_postgresql_13_not_installed": "PostgreSQL 11 is installed, but not postgresql 13!? Something weird might have happened on your system :(...", - "migration_0023_not_enough_space": "Make sufficient space available in {path} to run the migration.", "migration_description_0021_migrate_to_bullseye": "Upgrade the system to Debian Bullseye and YunoHost 11.x", "migration_description_0022_php73_to_php74_pools": "Migrate php7.3-fpm 'pool' conf files to php7.4", "migration_description_0023_postgresql_11_to_13": "Migrate databases from PostgreSQL 11 to 13", @@ -508,7 +508,6 @@ "migration_ldap_migration_failed_trying_to_rollback": "Could not migrate... trying to roll back the system.", "migration_ldap_rollback_success": "System rolled back.", "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_exclusive_options": "'--auto', '--skip', and '--force-rerun' are mutually exclusive options.", "migrations_failed_to_load_migration": "Could not load migration {id}: {error}", @@ -614,9 +613,9 @@ "service_description_fail2ban": "Protects against brute-force and other kinds of attacks from the Internet", "service_description_metronome": "Manage XMPP instant messaging accounts", "service_description_mysql": "Stores app data (SQL database)", - "service_description_postgresql": "Stores app data (SQL database)", "service_description_nginx": "Serves or provides access to all the websites hosted on your server", "service_description_postfix": "Used to send and receive e-mails", + "service_description_postgresql": "Stores app data (SQL database)", "service_description_redis-server": "A specialized database used for rapid data access, task queue, and communication between programs", "service_description_rspamd": "Filters spam, and other e-mail related features", "service_description_slapd": "Stores users, domains and related info", @@ -691,4 +690,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/locales/eo.json b/locales/eo.json index 8973e6344..1d98e8597 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -51,7 +51,6 @@ "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}' estas nekonata", @@ -219,7 +218,6 @@ "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}' 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}' 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} haveblas sur {provider}.", @@ -235,16 +233,13 @@ "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}' 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} 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}' ne haveblas.", diff --git a/locales/es.json b/locales/es.json index dec90b42b..4226b19e9 100644 --- a/locales/es.json +++ b/locales/es.json @@ -10,7 +10,6 @@ "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} 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", @@ -194,7 +193,6 @@ "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).", @@ -212,8 +210,6 @@ "tools_upgrade_regular_packages": "Actualizando ahora paquetes «normales» (no relacionados con YunoHost)…", "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»", "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}' fue recargado o reiniciado", "service_reload_or_restart_failed": "No se pudo recargar o reiniciar el servicio «{service}»\n\nRegistro de servicios recientes:{logs}", @@ -221,7 +217,6 @@ "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", "service_description_ssh": "Permite conectar a su servidor remotamente mediante un terminal (protocolo SSH)", @@ -524,19 +519,6 @@ "app_manifest_install_ask_domain": "Seleccione el dominio donde esta app debería ser instalada", "app_label_deprecated": "Este comando está depreciado! Favor usar el nuevo comando 'yunohost user permission update' para administrar la etiqueta de app.", "app_argument_password_no_default": "Error al interpretar argumento de contraseña'{name}': El argumento de contraseña no puede tener un valor por defecto por razón de seguridad", - "migration_0015_not_enough_free_space": "¡El espacio es muy bajo en `/var/`! Deberías tener almenos 1Gb de espacio libre para ejecutar la migración.", - "migration_0015_not_stretch": "¡La distribución actual de Debian no es Stretch!", - "migration_0015_yunohost_upgrade": "Iniciando la actualización del núcleo de YunoHost...", - "migration_0015_still_on_stretch_after_main_upgrade": "Algo fue mal durante la actualización principal, el sistema parece que está todavía en Debian Stretch", - "migration_0015_main_upgrade": "Comenzando la actualización principal...", - "migration_0015_patching_sources_list": "Adaptando las sources.lists...", - "migration_0015_start": "Comenzando la migración a Buster", - "migration_description_0019_extend_permissions_features": "Extiende/rehaz el sistema de gestión de permisos de la aplicación", - "migration_description_0018_xtable_to_nftable": "Migra las viejas reglas de tráfico de red al nuevo sistema nftable", - "migration_description_0017_postgresql_9p6_to_11": "Migra las bases de datos de PostgreSQL 9.6 a 11", - "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}»", "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", @@ -544,7 +526,6 @@ "global_settings_setting_smtp_relay_port": "Puerto de envio / relay SMTP", "global_settings_setting_smtp_relay_host": "El servidor relay de SMTP para enviar correo en lugar de esta instalación YunoHost. Útil si estás en una de estas situaciones: tu puerto 25 esta bloqueado por tu ISP o VPS, si estás en usado una IP marcada como residencial o DUHL, si no puedes configurar un DNS inverso o si el servidor no está directamente expuesto a internet y quieres utilizar otro servidor para enviar correos.", "global_settings_setting_smtp_allow_ipv6": "Permitir el uso de IPv6 para enviar y recibir correo", - "domain_name_unknown": "Dominio «{domain}» desconocido", "diagnosis_processes_killed_by_oom_reaper": "Algunos procesos fueron terminados por el sistema recientemente porque se quedó sin memoria. Típicamente es sintoma de falta de memoria o de un proceso que se adjudicó demasiada memoria.
Resumen de los procesos terminados:
\n{kills_summary}", "diagnosis_http_nginx_conf_not_up_to_date_details": "Para arreglar este asunto, estudia las diferencias mediante el comando yunohost tools regen-conf nginx --dry-run --with-diff y si te parecen bien aplica los cambios mediante yunohost tools regen-conf nginx --force.", "diagnosis_http_nginx_conf_not_up_to_date": "Parece que la configuración nginx de este dominio haya sido modificada manualmente, esto no deja que YunoHost pueda diagnosticar si es accesible mediante HTTP.", @@ -587,4 +568,4 @@ "app_config_unable_to_read": "No se pudieron leer los valores del panel configuración.", "backup_create_size_estimation": "El archivo contendrá aproximadamente {size} de datos.", "config_cant_set_value_on_section": "No puede establecer un único valor en una sección de configuración completa." -} +} \ No newline at end of file diff --git a/locales/eu.json b/locales/eu.json index 1f69866bb..91d744a7f 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -368,7 +368,6 @@ "log_app_change_url": "'{}' aplikazioaren URLa aldatu", "log_app_config_set": "Ezarri '{}' aplikazioko konfigurazioa", "downloading": "Deskargatzen…", - "dyndns_could_not_check_provide": "Ezinezkoa izan da {provider}(e)k {domain} eskaini dezakeen egiaztatzea.", "log_available_on_yunopaste": "Erregistroa {url} estekan ikus daiteke", "log_dyndns_update": "Eguneratu YunoHosten '{}' domeinuari lotutako IP helbidea", "log_letsencrypt_cert_install": "Instalatu Let's Encrypt ziurtagiria '{}' domeinurako", @@ -488,7 +487,6 @@ "unexpected_error": "Ezusteko zerbaitek huts egin du: {error}", "updating_apt_cache": "Sistemaren paketeen eguneraketak eskuratzen…", "mail_forward_remove_failed": "Ezinezkoa izan da '{mail}' posta elektronikoko birbidalketa ezabatzea", - "migration_description_0020_ssh_sftp_permissions": "Gehitu SSH eta SFTP baimenak", "migration_ldap_migration_failed_trying_to_rollback": "Ezinezkoa izan da migratzea… sistema lehengoratzen saiatzen.", "migrations_exclusive_options": "'--auto', '--skip', eta '--force-rerun' aukerek batak bestea baztertzen du.", "migrations_running_forward": "{id} migrazioa exekutatzen…", @@ -503,7 +501,6 @@ "user_import_partial_failed": "Erabiltzaileak inportatzeko eragiketak erdizka huts egin du", "user_import_success": "Erabiltzaileak arazorik gabe inportatu dira", "yunohost_already_installed": "YunoHost instalatuta dago dagoeneko", - "migration_0015_not_stretch": "Debianen oraingo bertsioa ez da Stretch!", "migrations_success_forward": "{id} migrazioak amaitu du", "migrations_to_be_ran_manually": "{id} migrazioa eskuz abiarazi behar da. Mesedez, joan Erramintak → Migrazioak atalera administrazio-atarian edo bestela exekutatu 'yunohost tools migrations run'.", "permission_currently_allowed_for_all_users": "Baimen hau erabiltzaile guztiei esleitzen zaie eta baita beste talde batzuei ere. Litekeena da 'all users' baimena edo esleituta duten taldeei baimena kendu nahi izatea.", @@ -518,7 +515,6 @@ "tools_upgrade_regular_packages": "Orain pakete \"arruntak\" (YunoHostekin zerikusia ez dutenak) eguneratzen…", "tools_upgrade_special_packages": "Orain pakete \"bereziak\" (YunoHostekin zerikusia dutenak) eguneratzen…", "regenconf_would_be_updated": "'{category}' atalerako konfigurazioa eguneratu izango litzatekeen", - "migration_description_0018_xtable_to_nftable": "Migratu internet trafiko arau zaharrak nftaula sistema berrira", "migrations_dependencies_not_satisfied": "Exekutatu honako migrazioak: '{dependencies_id}', {id} migratu baino lehen.", "permission_created": "'{permission}' baimena sortu da", "regenconf_now_managed_by_yunohost": "'{conf}' konfigurazio fitxategia YunoHostek kudeatzen du orain ({category} atala).", @@ -528,8 +524,6 @@ "service_restarted": "'{service}' zerbitzua berrabiarazi da", "service_start_failed": "Ezin izan da '{service}' zerbitzua abiarazi\n\nZerbitzuen azken erregistroak: {logs}", "ssowat_conf_updated": "SSOwat ezarpenak eguneratu dira", - "tools_upgrade_at_least_one": "Mesedez, zehaztu '--apps' edo '--system'", - "tools_upgrade_cant_both": "Ezin dira sistema eta aplikazioak une berean eguneratu", "update_apt_cache_failed": "Ezin da APT Debian-en pakete kudeatzailearen cachea eguneratu. Hemen dituzu sources.list fitxategiaren lerroak, arazoa identifikatzeko baliagarria izan dezakezuna:\n{sourceslist}", "update_apt_cache_warning": "Zerbaitek huts egin du APT Debian-en pakete kudeatzailearen cachea eguneratzean. Hemen dituzu sources.list fitxategiaren lerroak, arazoa identifikatzeko baliagarria izan dezakezuna:\n{sourceslist}", "user_created": "Erabiltzailea sortu da", @@ -558,11 +552,6 @@ "regenconf_pending_applying": "'{category}' atalerako konfigurazioa ezartzen…", "user_import_nothing_to_do": "Ez dago erabiltzaileak inportatu beharrik", "mailbox_used_space_dovecot_down": "Dovecot mailbox zerbitzua martxan egon behar da postak erabilitako espazioa ezagutzeko", - "migration_0015_cleaning_up": "Beharrezkoak ez diren cache eta paketeak kentzen…", - "migration_0015_modified_files": "Mesedez, kontuan hartu ondorengo fitxategiak eskuz aldatu direla eta sistema eguneratzean euren gainean idatziko dela: {manually_modified_files}", - "migration_0015_not_enough_free_space": "/var/ fitxategi-sisteman oso espazio gutxi geratzen da! Gutxienez GB bat izan behar da migrazioa abiarazteko.", - "migration_0015_weak_certs": "Sinadura-algoritmo ahulak darabiltzaten ziurtagiriak aurkitu dira eta eguneratu behar dira nginx-en hurrengo bertsioarekin bateragarriak izateko: {certs}", - "migration_description_0017_postgresql_9p6_to_11": "Migratu datubaseak PostgreSQL 9.6-tik 11-ra", "other_available_options": "… eta erakusten ez diren beste {n} aukera daude", "permission_cannot_remove_main": "Ezin da baimen nagusi bat kendu", "service_not_reloading_because_conf_broken": "Ez da '{name}' zerbitzua birkargatu/berrabiarazi konfigurazioa kaltetuta dagoelako: {errors}", @@ -577,16 +566,9 @@ "upnp_disabled": "UPnP itzalita dago", "main_domain_change_failed": "Ezinezkoa izan da domeinu nagusia aldatzea", "regenconf_failed": "Ezinezkoa izan da ondorengo atal(ar)en konfigurazioa berregitea: {categories}", - "migration_0015_start": "Buster-erako migrazioa abiarazten", - "migration_0015_patching_sources_list": "sources.lists petatxatzen…", - "migration_0015_yunohost_upgrade": "YunoHosten muinaren eguneraketa abiarazten…", - "migration_0017_postgresql_96_not_installed": "Ez da PostgreSQL instalatu. Ez dago egitekorik.", "pattern_email_forward": "Helbide elektroniko baliagarri bat izan behar da, '+' karakterea onartzen da (adibidez: izena+urtea@domeinua.eus)", - "migrating_legacy_permission_settings": "Zaharkitutako baimenen ezarpenak migratzen…", - "migration_0019_add_new_attributes_in_ldap": "Gehitu LDAP datubasean baimenetarako atributu berriak", "regenconf_file_manually_removed": "'{conf}' konfigurazio fitxategia eskuz ezabatu da eta ez da berriro sortuko", "regenconf_up_to_date": "Konfigurazioa egunean dago dagoeneko '{category}' atalerako", - "service_regen_conf_is_deprecated": "'yunohost service regen-conf' zaharkitua dago! Mesedez, erabili 'yunohost tools regen-conf' haren ordez.", "migrations_no_such_migration": "Ez dago '{id}' izeneko migraziorik", "migrations_not_pending_cant_skip": "Migrazio hauek ez daude exekutatzeke eta, beraz, ezin dira saihestu: {ids}", "regex_with_only_domain": "Ezin duzu regex domeinuetarako erabili; bideetarako bakarrik", @@ -599,7 +581,6 @@ "service_enable_failed": "Ezin izan da '{service}' zerbitzua sistema abiaraztearekin batera exekutatzea lortu.\n\nZerbitzuen erregistro berrienak: {logs}", "system_username_exists": "Erabiltzaile izena existitzen da dagoeneko sistemaren erabiltzaileen zerrendan", "user_already_exists": "'{user}' erabiltzailea existitzen da dagoeneko", - "migration_0018_failed_to_migrate_iptables_rules": "Zaharkitutako iptaulak nftauletara eguneratzeak huts egin du: {error}", "mail_domain_unknown": "Ezinezkoa da posta elektroniko hori '{domain}' domeinurako erabiltzea. Mesedez, erabili zerbitzari honek kudeatzen duen domeinu bat.", "migrations_list_conflict_pending_done": "Ezin dituzu '--previous' eta '--done' aldi berean erabili.", "migrations_loading_migration": "{id} migrazioa kargatzen…", @@ -627,20 +608,11 @@ "user_update_failed": "Ezin izan da {user} erabiltzailea eguneratu: {error}", "user_updated": "Erabiltzailearen informazioa aldatu da", "yunohost_configured": "YunoHost konfiguratuta dago", - "migration_0015_system_not_fully_up_to_date": "Sistema ez dago guztiz eguneratuta. Mesedez, exekutatu eguneraketa orokorra Buster-erako migrazioa abiarazi baino lehen.", - "migration_description_0015_migrate_to_buster": "Eguneratu sistema Debian Buster eta YunoHost 4.x-ra", "service_description_yunomdns": "Sare lokalean zerbitzarira 'yunohost.local' erabiliz konektatzea ahalbidetzen du", "mail_alias_remove_failed": "Ezin izan da '{mail}' e-mail ezizena ezabatu", "mail_unavailable": "Helbide elektroniko hau lehenengo erabiltzailearentzat gorde da eta hari ezarri zaio automatikoki", - "migration_description_0016_php70_to_php73_pools": "Migratu php7.0-fpm 'pool' fitxategiak php7.3-ra", - "migration_description_0019_extend_permissions_features": "Hedatu aplikazioen baimenen kudeaketa sistema", - "migration_0015_main_upgrade": "Eguneraketa orokorra abiarazten…", "migration_ldap_backup_before_migration": "Sortu LDAP datubase eta aplikazioen ezarpenen babeskopia migrazioa abiarazi baino lehen.", "migration_ldap_can_not_backup_before_migration": "Sistemaren babeskopiak ez du amaitu migrazioak huts egin baino lehen. Errorea: {error}", - "migration_0015_problematic_apps_warning": "Mesedez, kontuan izan arazoak sor ditzaketen aplikazioak aurkitu direla. Badirudi ez zirela YunoHosten aplikazioen katalogotik instalatu, edo 'ez dabiltza' etiketa dute. Beraz, ezin da bermatu eguneratu eta gero funtzionatuko dutenik: {problematic_apps}", - "migration_0017_not_enough_space": "Espazio gehiago behar da {path}-n migrazioa exekutatzeko.", - "migration_0017_postgresql_11_not_installed": "PostgreSQL 9.6 instalatuta egon arren postgresql 11 ez‽ Zerbait arraroa gertatu da zure sisteman :(…", - "migration_0018_failed_to_reset_legacy_rules": "Ezinezkoa izan da zaharkitutako iptaulak berrezartzea: {error}", "migrations_migration_has_failed": "{id} migrazioak ez du amaitu, geldiarazten. Errorea: {exception}", "migrations_need_to_accept_disclaimer": "{id} migrazioa abiarazteko, ondorengo baldintzak onartu behar dituzu:\n---\n{disclaimer}\n---\nMigrazioa onartzen baduzu, mesedez berrabiarazi prozesua komandoan '--accept-disclaimer' aukera gehituz.", "not_enough_disk_space": "Ez dago nahikoa espazio librerik '{path}'-n", @@ -652,8 +624,6 @@ "pattern_port_or_range": "Ataka zenbaki (0-65535) edo errenkada (100:200) baliagarri bat izan behar da", "permission_already_disallowed": "'{group}' taldeak desgaituta dauka dagoeneko '{permission} baimena", "permission_already_up_to_date": "Baimena ez da eguneratu egindako eskaria egungo egoerarekin bat datorrelako.", - "migration_0015_still_on_stretch_after_main_upgrade": "Zerbaitek huts egin du eguneraketa orokorrean; badirudi sistemak oraindik darabilela Debian Stretch", - "migration_update_LDAP_schema": "LDAP eskema eguneratzen…", "permission_protected": "'{permission}' baimena babestuta dago. Ezin duzu bisitarien taldea baimen honetara gehitu / baimen honetatik kendu.", "permission_update_failed": "Ezinezkoa izan da '{permission}' baimena aldatzea: {error}", "port_already_opened": "{port}. ataka dagoeneko irekita dago {ip_version} konexioetarako", @@ -661,11 +631,8 @@ "user_unknown": "Erabiltzaile ezezaguna: {user}", "yunohost_postinstall_end_tip": "Instalazio ondorengo prozesua amaitu da! Sistemaren konfigurazioa bukatzeko:\n- gehitu erabiltzaile bat administrazio-atariko 'Erabiltzaileak' atalean (edo 'yunohost user create ' komandoa erabiliz);\n- erabili 'Diagnostikoak' atala ohiko arazoei aurre hartzeko. Administrazio-atarian abiarazi edo 'yunohost diagnosis run' exekutatu;\n- irakurri 'Finalizing your setup' eta 'Getting to know YunoHost' atalak. Dokumentazioan aurki ditzakezu: https://yunohost.org/admindoc.", "yunohost_not_installed": "YunoHost ez da zuzen instalatu. Mesedez, exekutatu 'yunohost tools postinstall'", - "migration_0019_slapd_config_will_be_overwritten": "Badirudi eskuz moldatu duzula slapd konfigurazioa. Migrazio garrantzitsu honetarako, YunoHostek slapd ezarpenak eguneratu behar ditu. Oraingo fitxategiak {conf_backup_folder}-n kopiatuko dira.", "unlimit": "Mugarik ez", "restore_already_installed_apps": "Ondorengo aplikazioak ezin dira lehengoratu dagoeneko instalatuta daudelako: {apps}", - "migration_0015_general_warning": "Mesedez, uler ezazu migrazio hau eragiketa zaila dela. YunoHosten kideek ahalik eta hoberen egin dute prozesua egiaztatzeko, baina hala ere sistemaren atalak edo aplikazioak kaltetu litezke.\n\nHorregatik gomendagarria da:\n- Informazio edo aplikazio garrantzitsuen babeskopia egitea. Argibide gehiagorako: https://yunohost.org/backup;\n- Pazientzia izatea migrazioa abiarazterakoan: zure internet konexioaren eta hardwarearen arabera litekeena da ordu batzuk behar izatea eguneraketa amaitu arte.", - "migration_0015_specific_upgrade": "Aparte eguneratu behar diren sistemaren paketeen eguneraketa abiarazten…", "password_too_simple_4": "Pasahitzak 12 karaktere izan behar ditu gutxienez eta zenbakiren bat, hizki larriren bat, txikiren bat eta karaktere bereziren bat izan behar ditu", "pattern_email": "Helbide elektroniko baliagarri bat izan behar da, '+' karaktererik gabe (adibidez: izena@domeinua.eus)", "pattern_username": "Txikiz idatzitako karaktere alfanumerikoak eta azpiko marra soilik eduki ditzake", @@ -700,9 +667,8 @@ "service_description_metronome": "Bat-bateko XMPP mezularitza kontuak kudeatzen ditu", "service_description_mysql": "Aplikazioen datuak gordetzen ditu (SQL datubasea)", "service_description_nginx": "Zerbitzariak ostatazen dituen webguneak ikusgai egiten ditu", - "service_description_php7.3-fpm": "PHP aplikazioak exekutatzen ditu NGINXi esker", "service_description_redis-server": "Datuak bizkor atzitzeko, zereginak lerratzeko eta programen arteko komunikaziorako datubase berezi bat da", "service_description_rspamd": "Spama bahetu eta posta elektronikoarekin zerikusia duten bestelako futzioen ardura dauka", "service_description_slapd": "Erabiltzaileak, domeinuak eta hauei lotutako informazioa gordetzen du", "service_description_yunohost-api": "YunoHosten web-atariaren eta sistemaren arteko hartuemana kudeatzen du" -} +} \ No newline at end of file diff --git a/locales/fa.json b/locales/fa.json index f566fed90..ce2ba91bd 100644 --- a/locales/fa.json +++ b/locales/fa.json @@ -183,7 +183,6 @@ "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}' استفاده می شود", @@ -199,7 +198,6 @@ "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", @@ -348,13 +346,11 @@ "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_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": "نام میزبان جدید قابل تنظیم نیست. این ممکن است بعداً مشکلی ایجاد کند (ممکن هم هست خوب باشد).", @@ -413,39 +409,10 @@ "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": "این آدرس ایمیل محفوظ است و باید به طور خودکار به اولین کاربر اختصاص داده شود", @@ -538,8 +505,6 @@ "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": "سیستم ارتقا یافت", @@ -560,7 +525,6 @@ "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}' دیگر راه اندازی نمی شود.", @@ -572,7 +536,6 @@ "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", diff --git a/locales/fr.json b/locales/fr.json index 5b23b7a11..c9609a307 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -10,7 +10,6 @@ "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", @@ -223,7 +222,6 @@ "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", - "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}...", @@ -340,9 +338,6 @@ "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_regular_packages_failed": "Impossible de mettre à jour les paquets suivants : {packages_list}", @@ -549,33 +544,9 @@ "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_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_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_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": "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.4-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": "É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.", - "migration_description_0017_postgresql_9p6_to_11": "Migrer les bases de données de PostgreSQL 9.6 vers 11", - "migration_description_0016_php70_to_php73_pools": "Migrer les configurations php7.0 vers php7.3", "diagnosis_processes_killed_by_oom_reaper": "Certains processus ont été arrêtés récemment par le système car il manquait de mémoire. Cela apparaît généralement quand le système manque de mémoire ou qu'un processus consomme trop de mémoire. Liste des processus tués :\n{kills_summary}", "ask_user_domain": "Domaine à utiliser pour l'adresse email de l'utilisateur et le compte XMPP", "app_manifest_install_ask_is_public": "Cette application devrait-elle être visible par les visiteurs anonymes ?", @@ -598,15 +569,10 @@ "regex_with_only_domain": "Vous ne pouvez pas utiliser une expression régulière pour le domaine, uniquement pour le chemin", "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_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}'", - "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}' 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/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", @@ -615,7 +581,6 @@ "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...", "log_backup_create": "Créer une archive de sauvegarde", "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.", @@ -623,7 +588,6 @@ "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_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.", @@ -631,7 +595,6 @@ "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 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 comme .local ou .test 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...", @@ -669,13 +632,10 @@ "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'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", "domain_registrar_is_not_configured": "Le registrar n'est pas encore configuré pour le domaine {domain}.", "domain_dns_push_not_applicable": "La fonction de configuration DNS automatique n'est pas applicable au domaine {domain}. Vous devez configurer manuellement vos enregistrements DNS en suivant la documentation sur https://yunohost.org/dns_config.", "domain_dns_registrar_yunohost": "Ce domaine est de type nohost.me / nohost.st / ynh.fr et sa configuration DNS est donc automatiquement gérée par YunoHost sans qu'il n'y ait d'autre configuration à faire. (voir la commande 'yunohost dyndns update')", @@ -711,4 +671,4 @@ "other_available_options": "... et {n} autres options disponibles non affichées", "domain_config_auth_consumer_key": "Consumer key", "domain_unknown": "Domaine '{domain}' inconnu" -} +} \ No newline at end of file diff --git a/locales/gl.json b/locales/gl.json index 0d7d1afee..14351b388 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -76,7 +76,6 @@ "app_manifest_install_ask_password": "Elixe un contrasinal de administración para 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}", "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}'", @@ -288,13 +287,11 @@ "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} 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...", "done": "Feito", "domains_available": "Dominios dispoñibles:", - "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).", @@ -394,19 +391,10 @@ "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", - "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", @@ -433,28 +421,7 @@ "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 '{}'", - "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...", "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: {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.", - "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 esté exposto ao exterior da rede local.", "diagnosis_dns_specialusedomain": "O dominio {domain} baséase un dominio de nivel alto e uso especial (TLD) como .local ou .test polo que non é de agardar que realmente teña rexistros DNS.", "upnp_enabled": "UPnP activado", "upnp_disabled": "UPnP desactivado", @@ -549,7 +516,6 @@ "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", @@ -613,8 +579,6 @@ "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", @@ -635,7 +599,6 @@ "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.", "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.", @@ -655,13 +618,11 @@ "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.", - "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 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", "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.", @@ -673,7 +634,6 @@ "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}", "diagnosis_http_special_use_tld": "O dominio {domain} baséase nun dominio de alto-nivel (TLD) especial como .local ou .test e por isto non é de agardar que esté exposto fóra da rede local.", @@ -711,4 +671,4 @@ "ldap_attribute_already_exists": "Xa existe o atributo LDAP '{attribute}' con valor '{value}'", "log_domain_config_set": "Actualizar configuración para o dominio '{}'", "domain_unknown": "Dominio '{domain}' descoñecido" -} +} \ No newline at end of file diff --git a/locales/hi.json b/locales/hi.json index 5f521b1dc..1eed9faa4 100644 --- a/locales/hi.json +++ b/locales/hi.json @@ -10,7 +10,6 @@ "app_extraction_failed": "इन्सटाल्ड फ़ाइलों को निकालने में असमर्थ", "app_id_invalid": "अवैध एप्लिकेशन id", "app_install_files_invalid": "फाइलों की अमान्य स्थापना", - "app_manifest_invalid": "एप्लीकेशन का मैनिफेस्ट अमान्य", "app_not_correctly_installed": "{app} ठीक ढंग से इनस्टॉल नहीं हुई", "app_not_installed": "{app} इनस्टॉल नहीं हुई", "app_not_properly_removed": "{app} ठीक ढंग से नहीं अनइन्सटॉल की गई", diff --git a/locales/id.json b/locales/id.json index 1f28ae7e5..c9778dd5f 100644 --- a/locales/id.json +++ b/locales/id.json @@ -43,4 +43,4 @@ "app_start_remove": "Menghapus {app}...", "app_manifest_install_ask_password": "Pilih kata sandi administrasi untuk aplikasi ini", "app_upgrade_several_apps": "Aplikasi-aplikasi berikut akan diperbarui: {apps}" -} +} \ No newline at end of file diff --git a/locales/it.json b/locales/it.json index bd1dba8bd..bb2e7af6c 100644 --- a/locales/it.json +++ b/locales/it.json @@ -26,7 +26,6 @@ "admin_password_change_failed": "Impossibile cambiare la password", "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} sembra di non essere installata correttamente", "app_not_properly_removed": "{app} non è stata correttamente rimossa", "action_invalid": "L'azione '{action}' non è valida", @@ -223,7 +222,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 apt install --fix-broken` e/o `sudo dpkg --configure -a`.", "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} 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.", @@ -400,8 +398,6 @@ "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", "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}' è stato ricaricato o riavviato", @@ -410,7 +406,6 @@ "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", "service_description_ssh": "Ti consente di accedere da remoto al tuo server attraverso il terminale (protocollo SSH)", @@ -418,7 +413,6 @@ "service_description_rspamd": "Filtra SPAM, e altre funzionalità legate alle mail", "service_description_redis-server": "Un database specializzato usato per un veloce accesso ai dati, task queue, e comunicazioni tra programmi", "service_description_postfix": "Usato per inviare e ricevere email", - "service_description_php7.4-fpm": "Esegue app scritte in PHP con NGINX", "service_description_nginx": "Serve o permette l'accesso a tutti i siti pubblicati sul tuo server", "service_description_mysql": "Memorizza i dati delle app (database SQL)", "service_description_metronome": "Gestisce gli account di messaggistica instantanea XMPP", @@ -492,33 +486,6 @@ "migrations_dependencies_not_satisfied": "Esegui queste migrazioni: '{dependencies_id}', prima di {id}.", "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_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}", - "migration_0017_not_enough_space": "Libera abbastanza spazio in {path} per eseguire la migrazione.", - "migration_0017_postgresql_11_not_installed": "PostgreSQL 9.6 è installato, ma non PostgreSQL 11 ?! Qualcosa di strano potrebbe esser successo al tuo sistema :'( ...", - "migration_0017_postgresql_96_not_installed": "PostgreSQL non è stato installato sul tuo sistema. Nulla da fare.", - "migration_0015_weak_certs": "I seguenti certificati utilizzano ancora un algoritmo di firma debole e dovrebbero essere aggiornati per essere compatibili con la prossima versione di nginx: {certs}", - "migration_0015_cleaning_up": "Sto pulendo la cache e i pacchetti non più utili...", - "migration_0015_specific_upgrade": "Inizio l'aggiornamento dei pacchetti di sistema che necessitano di essere aggiornati da soli...", - "migration_0015_modified_files": "Attenzioni, i seguenti file sembrano esser stati modificati manualmente, e potrebbero essere sovrascritti dopo l'aggiornamento: {manually_modified_files}", - "migration_0015_problematic_apps_warning": "Alcune applicazioni potenzialmente problematiche sono state rilevate nel sistema. Sembra che non siano state installate attraverso il catalogo app YunoHost, o non erano flaggate come 'working'/'funzionanti'. Di conseguenza, non è possibile garantire che funzioneranno ancora dopo l'aggiornamento: {problematic_apps}", - "migration_0015_general_warning": "Attenzione, sappi che questa migrazione è un'operazione delicata. Il team YunoHost ha fatto del suo meglio nel controllarla e testarla, ma le probabilità che il sistema e/o qualche app si danneggi non sono nulle.\n\nPerciò, ti raccomandiamo di:\n\t- Effettuare un backup di tutti i dati e app importanti. Maggiori informazioni su https://yunohost.org/backup;\n\t- Sii paziente dopo aver lanciato l'operazione: in base alla tua connessione internet e al tuo hardware, potrebbero volerci alcune ore per aggiornare tutto.", - "migration_0015_system_not_fully_up_to_date": "Il tuo sistema non è completamente aggiornato. Esegui un aggiornamento classico prima di lanciare la migrazione a Buster.", - "migration_0015_not_enough_free_space": "Poco spazio libero disponibile in /var/! Dovresti avere almeno 1GB libero per effettuare questa migrazione.", - "migration_0015_not_stretch": "La distribuzione Debian corrente non è Stretch!", - "migration_0015_yunohost_upgrade": "Inizio l'aggiornamento del core di YunoHost...", - "migration_0015_still_on_stretch_after_main_upgrade": "Qualcosa è andato storto durante l'aggiornamento principale, il sistema sembra essere ancora su Debian Stretch", - "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_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", - "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}", "log_user_permission_reset": "Resetta il permesso '{}'", "log_user_permission_update": "Aggiorna gli accessi del permesso '{}'", @@ -558,7 +525,6 @@ "global_settings_setting_pop3_enabled": "Abilita il protocollo POP3 per il server mail", "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}' 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}", @@ -612,12 +578,10 @@ "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}", "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", @@ -706,4 +670,4 @@ "config_validate_url": "È necessario inserire un URL web valido", "ldap_server_down": "Impossibile raggiungere il server LDAP", "ldap_server_is_down_restart_it": "Il servizio LDAP è down, prova a riavviarlo…" -} +} \ No newline at end of file diff --git a/locales/nb_NO.json b/locales/nb_NO.json index dc217d74e..e81d3af05 100644 --- a/locales/nb_NO.json +++ b/locales/nb_NO.json @@ -30,7 +30,6 @@ "domains_available": "Tilgjengelige domener:", "done": "Ferdig", "downloading": "Laster ned…", - "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", diff --git a/locales/nl.json b/locales/nl.json index 5e612fc77..f5923fa9a 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -8,7 +8,6 @@ "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} is niet geïnstalleerd", "app_removed": "{app} succesvol verwijderd", "app_sources_fetch_failed": "Kan bronbestanden niet ophalen, klopt de URL?", diff --git a/locales/oc.json b/locales/oc.json index a794f5e04..d9ec19b09 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -33,7 +33,6 @@ "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", @@ -110,7 +109,6 @@ "domains_available": "Domenis disponibles :", "done": "Acabat", "downloading": "Telecargament…", - "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.", @@ -332,18 +330,15 @@ "regenconf_dry_pending_applying": "Verificacion de la configuracion que seriá estada aplicada a la categoria « {category} »…", "regenconf_failed": "Regeneracion impossibla de la configuracion per la(s) categoria(s) : {categories}", "regenconf_pending_applying": "Aplicacion de la configuracion en espèra per la categoria « {category} »…", - "tools_upgrade_cant_both": "Actualizacion impossibla del sistèma e de las aplicacions a l’encòp", "tools_upgrade_cant_hold_critical_packages": "Manteniment impossible dels paquets critiques…", "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)", - "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} »\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", "tools_upgrade_cant_unhold_critical_packages": "Se pòt pas quitar de manténer los paquets critics…", "tools_upgrade_regular_packages": "Actualizacion dels paquets « normals » (pas ligats a YunoHost)…", "tools_upgrade_special_packages": "Actualizacion dels paquets « especials » (ligats a YunoHost)…", @@ -480,7 +475,6 @@ "diagnosis_basesystem_hardware": "L’arquitectura del servidor es {virt} {arch}", "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…", "restore_already_installed_apps": "Restauracion impossibla de las aplicacions seguentas que son ja installadas : {apps}", "diagnosis_package_installed_from_sury": "D’unes paquets sistèma devon èsser meses a nivèl", "ask_user_domain": "Domeni d’utilizar per l’adreça de corrièl de l’utilizaire e lo compte XMPP", @@ -493,14 +487,6 @@ "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} 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...", - "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_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).", @@ -513,4 +499,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 d285948be..154ab700f 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -7,7 +7,6 @@ "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": "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?", @@ -255,4 +254,4 @@ "diagnosis_backports_in_sources_list": "Parece que o apt (o gerenciador de pacotes) está configurado para usar o repositório backport. A não ser que você saiba o que você esteá fazendo, desencorajamos fortemente a instalação de pacotes de backports porque é provável que crie instabilidades ou conflitos no seu sistema.", "certmanager_cert_renew_success": "Certificado Let's Encrypt renovado para o domínio '{domain}'", "certmanager_warning_subdomain_dns_record": "O subdomínio '{subdomain}' não resolve para o mesmo IP que '{domain}'. Algumas funcionalidades não estarão disponíveis até que você conserte isto e regenere o certificado." -} +} \ No newline at end of file diff --git a/locales/ru.json b/locales/ru.json index 5c9a39322..58bd1846d 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -16,7 +16,6 @@ "app_id_invalid": "Неправильный ID приложения", "app_install_files_invalid": "Эти файлы не могут быть установлены", "app_location_unavailable": "Этот URL отсутствует или конфликтует с уже установленным приложением или приложениями:\n{apps}", - "app_manifest_invalid": "Недопустимый манифест приложения: {error}", "app_not_correctly_installed": "{app} , кажется, установлены неправильно", "app_not_installed": "{app} не найдено в списке установленных приложений: {all_apps}", "app_not_properly_removed": "{app} удалены неправильно", @@ -75,4 +74,4 @@ "yunohost_already_installed": "YunoHost уже установлен", "yunohost_configured": "Теперь YunoHost настроен", "upgrading_packages": "Обновление пакетов..." -} +} \ No newline at end of file diff --git a/locales/sl.json b/locales/sl.json index 0967ef424..9e26dfeeb 100644 --- a/locales/sl.json +++ b/locales/sl.json @@ -1 +1 @@ -{} +{} \ No newline at end of file diff --git a/locales/uk.json b/locales/uk.json index 4baf6d20f..00df81891 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -1,6 +1,5 @@ { "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}'", @@ -40,7 +39,6 @@ "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}' більше не буде запускатися під час завантаження системи.", @@ -52,7 +50,6 @@ "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", @@ -162,39 +159,10 @@ "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 зробила все можливе, щоб перевірити і протестувати її, але міграція все ще може порушити частина системи або її застосунків.\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": "Поточний дистрибутив 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": "Ця е-пошта зарезервована і буде автоматично виділена найпершому користувачеві", @@ -318,13 +286,11 @@ "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`.", "downloading": "Завантаження…", "done": "Готово", "domains_available": "Доступні домени:", - "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": "Неможливо встановити нову назву хоста. Це може викликати проблеми в подальшому (можливо, все буде в порядку).", @@ -356,7 +322,6 @@ "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 ", @@ -446,8 +411,6 @@ "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": "Систему оновлено", @@ -669,13 +632,10 @@ "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}", - "app_argument_password_help_optional": "Введіть один пробіл, щоб очистити пароль", - "app_argument_password_help_keep": "Натисніть Enter, щоб зберегти поточне значення", "domain_registrar_is_not_configured": "Реєстратор ще не конфігуровано для домену {domain}.", "domain_dns_push_not_applicable": "Функція автоматичної конфігурації DNS не застосовується до домену {domain}. Вам слід вручну конфігурувати записи DNS відповідно до документації за адресою https://yunohost.org/dns_config.", "domain_dns_registrar_not_supported": "YunoHost не зміг автоматично виявити реєстратора, який обробляє цей домен. Вам слід вручну конфігурувати записи DNS відповідно до документації за адресою https://yunohost.org/dns.", @@ -711,4 +671,4 @@ "ldap_attribute_already_exists": "Атрибут LDAP '{attribute}' вже існує зі значенням '{value}'", "domain_dns_push_already_up_to_date": "Записи вже оновлені, нічого не потрібно робити.", "domain_unknown": "Домен '{domain}' є невідомим" -} +} \ No newline at end of file diff --git a/locales/zh_Hans.json b/locales/zh_Hans.json index fa3b7bb03..4b3067909 100644 --- a/locales/zh_Hans.json +++ b/locales/zh_Hans.json @@ -137,7 +137,6 @@ "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}", "app_label_deprecated": "不推荐使用此命令!请使用新命令 'yunohost user permission update'来管理应用标签。", "app_make_default_location_already_used": "无法将'{app}' 设置为域上的默认应用,'{other_app}'已在使用'{domain}'", @@ -218,7 +217,6 @@ "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即时消息传递帐户", @@ -235,7 +233,6 @@ "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}' 将不再启动。", @@ -243,8 +240,6 @@ "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": "系统升级", @@ -353,13 +348,11 @@ "dyndns_ip_updated": "在DynDNS上更新了您的IP", "dyndns_ip_update_failed": "无法将IP地址更新到DynDNS", "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": "下载中…", "done": "完成", "domains_available": "可用域:", - "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": "无法设置新的主机名。稍后可能会引起问题(可能没问题)。", @@ -571,39 +564,10 @@ "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}", "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": "该电子邮件地址是保留的,并且将自动分配给第一个用户", @@ -627,7 +591,7 @@ "log_user_delete": "删除用户'{}'", "log_user_create": "添加用户'{}'", "domain_registrar_is_not_configured": "尚未为域 {domain} 配置注册商。", - "domain_dns_push_not_applicable": "的自动DNS配置的特征是不适用域{域}。您应该按照 https://yunohost.org/dns_config 上的文档手动配置DNS 记录。", + "domain_dns_push_not_applicable": "的自动DNS配置的特征是不适用域{domain}。您应该按照 https://yunohost.org/dns_config 上的文档手动配置DNS 记录。", "disk_space_not_sufficient_update": "没有足够的磁盘空间来更新此应用程序", "diagnosis_high_number_auth_failures": "最近出现了大量可疑的失败身份验证。您的fail2ban正在运行且配置正确,或使用自定义端口的SSH作为https://yunohost.org/解释的安全性。", "diagnosis_apps_not_in_app_catalog": "此应用程序不在 YunoHost 的应用程序目录中。如果它过去有被删除过,您应该考虑卸载此应用程,因为它不会更新,并且可能会损害您系统的完整和安全性。", @@ -638,7 +602,7 @@ "config_unknown_filter_key": "该过滤器钥匙“{filter_key}”有误。", "diagnosis_apps_outdated_ynh_requirement": "此应用程序的安装 版本只需要 yunohost >= 2.x,这往往表明它与推荐的打包实践和帮助程序不是最新的。你真的应该考虑更新它。", "disk_space_not_sufficient_install": "没有足够的磁盘空间来安装此应用程序", - "config_apply_failed": "应用新配置 失败:{错误}", + "config_apply_failed": "应用新配置 失败:{error}", "config_cant_set_value_on_section": "无法在整个配置部分设置单个值 。", "config_validate_color": "是有效的 RGB 十六进制颜色", "config_validate_date": "有效日期格式为YYYY-MM-DD", @@ -651,4 +615,4 @@ "diagnosis_apps_deprecated_practices": "此应用程序的安装 版本仍然使用一些超旧的弃用打包原则。推荐您升级它。", "diagnosis_apps_issue": "发现应用{ app } 存在问题", "diagnosis_description_apps": "应用" -} +} \ No newline at end of file diff --git a/maintenance/autofix_locale_format.py b/maintenance/autofix_locale_format.py new file mode 100644 index 000000000..400704ddb --- /dev/null +++ b/maintenance/autofix_locale_format.py @@ -0,0 +1,169 @@ +import os +import re +import json +import glob +from collections import OrderedDict + +ROOT = os.path.dirname(__file__) + "/../" +LOCALE_FOLDER = ROOT + "/locales/" + +# List all locale files (except en.json being the ref) +TRANSLATION_FILES = glob.glob(LOCALE_FOLDER + "*.json") +TRANSLATION_FILES = [filename.split("/")[-1] for filename in TRANSLATION_FILES] +print(LOCALE_FOLDER) +TRANSLATION_FILES.remove("en.json") + +REFERENCE_FILE = LOCALE_FOLDER + "en.json" + + +def autofix_i18n_placeholders(): + def _autofix_i18n_placeholders(locale_file): + """ + This tries for magically fix mismatch between en.json format and other.json format + e.g. an i18n string with: + source: "Lorem ipsum {some_var}" + fr: "Lorem ipsum {une_variable}" + (ie the keyword in {} was translated but shouldnt have been) + """ + + this_locale = json.loads(open(LOCALE_FOLDER + locale_file).read()) + fixed_stuff = False + reference = json.loads(open(REFERENCE_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 = [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 + + # Validate that now it's okay ? + 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 any(k not in subkeys_in_ref for k in subkeys_in_this_locale): + raise Exception("""\n +========================== +Format inconsistency for string {key} in {locale_file}:" +en.json -> {string} +{locale_file} -> {translated_string} +Please fix it manually ! + """.format( + key=key, + string=string.encode("utf-8"), + locale_file=locale_file, + translated_string=this_locale[key].encode("utf-8"), + )) + + if fixed_stuff: + json.dump( + this_locale, + open(LOCALE_FOLDER + locale_file, "w"), + indent=4, + ensure_ascii=False, + ) + + for locale_file in TRANSLATION_FILES: + _autofix_i18n_placeholders(locale_file) + + +def autofix_orthotypography_and_standardized_words(): + + 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", + "e-mail": "email", + "Courriel": "Email", + "E-mail": "Email", + "« ": "'", + "«": "'", + " »": "'", + "»": "'", + "’": "'", + # r"$(\w{1,2})'|( \w{1,2})'": r"\1\2’", + } + ) + + reformat("fr", transformations) + + +def remove_stale_translated_strings(): + + reference = json.loads(open(LOCALE_FOLDER + "en.json").read()) + + for locale_file in TRANSLATION_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, + ) + + +autofix_orthotypography_and_standardized_words() +remove_stale_translated_strings() +autofix_i18n_placeholders() diff --git a/maintenance/make_changelog.sh b/maintenance/make_changelog.sh new file mode 100644 index 000000000..038d4741d --- /dev/null +++ b/maintenance/make_changelog.sh @@ -0,0 +1,36 @@ +VERSION="?" +RELEASE="testing" +REPO=$(basename $(git rev-parse --show-toplevel)) +REPO_URL=$(git remote get-url origin) +ME=$(git config --global --get user.name) +EMAIL=$(git config --global --get user.email) + +LAST_RELEASE=$(git tag --list | grep debian | tail -n 1) + +echo "$REPO ($VERSION) $RELEASE; urgency=low" +echo "" + +git log $LAST_RELEASE.. -n 10000 --first-parent --pretty=tformat:' - %b%s (%h)' \ +| sed -E "s@Merge .*#([0-9]+).*\$@ \([#\1]\($REPO_URL/pull/\1\)\)@g" \ +| grep -v "Update from Weblate" \ +| tac + +TRANSLATIONS=$(git log $LAST_RELEASE... -n 10000 --pretty=format:"%s" \ + | grep "Translated using Weblate" \ + | sed -E "s/Translated using Weblate \((.*)\)/\1/g" \ + | sort | uniq | tr '\n' ', ' | sed -e 's/,$//g' -e 's/,/, /g') +[[ -z "$TRANSLATIONS" ]] || echo " - [i18n] Translations updated for $TRANSLATIONS" + +echo "" +CONTRIBUTORS=$(git logc $LAST_RELEASE... -n 10000 --pretty=format:"%an" \ + | sort | uniq | grep -v "$ME" \ + | tr '\n' ', ' | sed -e 's/,$//g' -e 's/,/, /g') +[[ -z "$CONTRIBUTORS" ]] || echo " Thanks to all contributors <3 ! ($CONTRIBUTORS)" +echo "" +echo " -- $ME <$EMAIL> $(date -R)" +echo "" + + + +# PR links can be converted to regular texts using : sed -E 's@\[(#[0-9]*)\]\([^ )]*\)@\1@g' +# Or readded with sed -E 's@#([0-9]*)@[YunoHost#\1](https://github.com/yunohost/yunohost/pull/\1)@g' | sed -E 's@\((\w+)\)@([YunoHost/\1](https://github.com/yunohost/yunohost/commit/\1))@g' diff --git a/tests/test_i18n_keys.py b/maintenance/missing_i18n_keys.py similarity index 71% rename from tests/test_i18n_keys.py rename to maintenance/missing_i18n_keys.py index ec582ba72..3dbca8027 100644 --- a/tests/test_i18n_keys.py +++ b/maintenance/missing_i18n_keys.py @@ -1,12 +1,17 @@ # -*- coding: utf-8 -*- +import toml import os import re import glob import json import yaml import subprocess -import toml +import sys + +ROOT = os.path.dirname(__file__) + "/../" +LOCALE_FOLDER = ROOT + "/locales/" +REFERENCE_FILE = LOCALE_FOLDER + "en.json" ############################################################################### # Find used keys in python code # @@ -25,12 +30,12 @@ def find_expected_string_keys(): p3 = re.compile(r"YunohostValidationError\(\n*\s*[\'\"](\w+)[\'\"]") p4 = re.compile(r"# i18n: [\'\"]?(\w+)[\'\"]?") - python_files = glob.glob("src/*.py") - python_files.extend(glob.glob("src/utils/*.py")) - python_files.extend(glob.glob("src/migrations/*.py")) - python_files.extend(glob.glob("src/authenticators/*.py")) - python_files.extend(glob.glob("src/diagnosers/*.py")) - python_files.append("bin/yunohost") + python_files = glob.glob(ROOT + "src/*.py") + python_files.extend(glob.glob(ROOT + "src/utils/*.py")) + python_files.extend(glob.glob(ROOT + "src/migrations/*.py")) + python_files.extend(glob.glob(ROOT + "src/authenticators/*.py")) + python_files.extend(glob.glob(ROOT + "src/diagnosers/*.py")) + python_files.append(ROOT + "bin/yunohost") for python_file in python_files: content = open(python_file).read() @@ -52,7 +57,9 @@ def find_expected_string_keys(): # 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("src/diagnosers/*.py"): + for python_file in glob.glob(ROOT + "src/diagnosers/*.py"): + if "__init__.py" in python_file: + continue content = open(python_file).read() for m in p3.findall(content): if m.endswith("_"): @@ -64,14 +71,14 @@ def find_expected_string_keys(): ] # For each migration, expect to find "migration_description_" - for path in glob.glob("src/migrations/*.py"): + for path in glob.glob(ROOT + "src/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("conf/yunohost/services.yml") + open(ROOT + "conf/yunohost/services.yml") ).items(): if info is None: continue @@ -80,7 +87,7 @@ 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/ -A3 2>/dev/null | grep '^def' | sed -E 's@^def (\\w+)\\(.*@\\1@g'" + cmd = f"grep -hr '@is_unit_operation' {ROOT}/src/ -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") ): @@ -95,14 +102,14 @@ def find_expected_string_keys(): # 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/settings.py").read() + content = open(ROOT + "src/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("share/actionsmap.yml")).values(): + for category in yaml.safe_load(open(ROOT + "share/actionsmap.yml")).values(): if "actions" not in category.keys(): continue for action in category["actions"].values(): @@ -130,13 +137,13 @@ def find_expected_string_keys(): yield "backup_applying_method_%s" % method yield "backup_method_%s_finished" % method - registrars = toml.load(open("share/registrar_list.toml")) + registrars = toml.load(open(ROOT + "share/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("share/config_domain.toml")) + domain_config = toml.load(open(ROOT + "share/config_domain.toml")) for panel in domain_config.values(): if not isinstance(panel, dict): continue @@ -149,41 +156,53 @@ def find_expected_string_keys(): yield f"domain_config_{key}" -############################################################################### -# 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 # ############################################################################### +if len(sys.argv) <= 1 or sys.argv[1] not in ["--check", "--fix"]: + print("Please specify --check or --fix") + sys.exit(1) expected_string_keys = set(find_expected_string_keys()) -keys_defined = set(keys_defined_for_en()) +keys_defined_for_en = json.loads(open(REFERENCE_FILE).read()).keys() +keys_defined = set(keys_defined_for_en) +unused_keys = keys_defined.difference(expected_string_keys) +unused_keys = sorted(unused_keys) -def test_undefined_i18n_keys(): - undefined_keys = expected_string_keys.difference(keys_defined) - undefined_keys = sorted(undefined_keys) +undefined_keys = expected_string_keys.difference(keys_defined) +undefined_keys = sorted(undefined_keys) + +mode = sys.argv[1].strip("-") +if mode == "check": + + # Unused keys are not too problematic, will be automatically + # removed by the other autoreformat script, + # but still informative to display them + if unused_keys: + print( + "Those i18n keys appears unused:\n" " - " + "\n - ".join(unused_keys) + ) if undefined_keys: - raise Exception( + print( "Those i18n keys should be defined in en.json:\n" " - " + "\n - ".join(undefined_keys) ) + sys.exit(1) +elif mode == "fix": + j = json.loads(open(REFERENCE_FILE).read()) + for key in undefined_keys: + j[key] = "FIXME" + for key in unused_keys: + del j[key] -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) - ) + json.dump( + j, + open(REFERENCE_FILE, "w"), + indent=4, + ensure_ascii=False, + sort_keys=True, + ) diff --git a/tests/add_missing_keys.py b/tests/add_missing_keys.py deleted file mode 100644 index ebee80a1b..000000000 --- a/tests/add_missing_keys.py +++ /dev/null @@ -1,193 +0,0 @@ -# -*- 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/*.py") - python_files.extend(glob.glob("src/utils/*.py")) - python_files.extend(glob.glob("src/migrations/*.py")) - python_files.extend(glob.glob("src/authenticators/*.py")) - python_files.extend(glob.glob("src/diagnosers/*.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("src/diagnosers/*.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/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("conf/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/ -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/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("share/actionsmap.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, -) diff --git a/tests/autofix_locale_format.py b/tests/autofix_locale_format.py deleted file mode 100644 index f3825bd30..000000000 --- a/tests/autofix_locale_format.py +++ /dev/null @@ -1,53 +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()) - - -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) diff --git a/tests/reformat_locales.py b/tests/reformat_locales.py deleted file mode 100644 index 86c2664d7..000000000 --- a/tests/reformat_locales.py +++ /dev/null @@ -1,60 +0,0 @@ -import re - - -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", - "e-mail": "email", - "Courriel": "Email", - "E-mail": "Email", - "« ": "'", - "«": "'", - " »": "'", - "»": "'", - "’": "'", - # r"$(\w{1,2})'|( \w{1,2})'": r"\1\2’", - } -) - -reformat("fr", transformations) diff --git a/tests/remove_stale_translated_strings.py b/tests/remove_stale_translated_strings.py deleted file mode 100644 index 48f2180e4..000000000 --- a/tests/remove_stale_translated_strings.py +++ /dev/null @@ -1,25 +0,0 @@ -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, - ) diff --git a/tests/test_translation_format_consistency.py b/tests/test_translation_format_consistency.py deleted file mode 100644 index 86d1c3279..000000000 --- a/tests/test_translation_format_consistency.py +++ /dev/null @@ -1,52 +0,0 @@ -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 d831ccef4c5ea561d82cd09c19be183b080647de Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 28 Nov 2021 16:24:13 +0100 Subject: [PATCH 0969/1155] ci: Try to fix mypy --- src/diagnosers/00-basesystem.py | 3 ++- src/diagnosers/10-ip.py | 3 ++- src/diagnosers/12-dnsrecords.py | 5 +++-- src/diagnosers/14-ports.py | 3 ++- src/diagnosers/21-web.py | 3 ++- src/diagnosers/24-mail.py | 3 ++- src/diagnosers/30-services.py | 3 ++- src/diagnosers/50-systemresources.py | 3 ++- src/diagnosers/70-regenconf.py | 3 ++- src/diagnosers/80-apps.py | 3 ++- 10 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/diagnosers/00-basesystem.py b/src/diagnosers/00-basesystem.py index 0ef1a5197..104fa698e 100644 --- a/src/diagnosers/00-basesystem.py +++ b/src/diagnosers/00-basesystem.py @@ -3,6 +3,7 @@ import os import json import subprocess +from typing import List from moulinette.utils import log from moulinette.utils.process import check_output @@ -17,7 +18,7 @@ class MyDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 600 - dependencies = [] + dependencies: List[str] = [] def run(self): diff --git a/src/diagnosers/10-ip.py b/src/diagnosers/10-ip.py index 4ad4cfbfc..d495e3277 100644 --- a/src/diagnosers/10-ip.py +++ b/src/diagnosers/10-ip.py @@ -3,6 +3,7 @@ import re import os import random +from typing import List from moulinette.utils import log from moulinette.utils.network import download_text @@ -19,7 +20,7 @@ class MyDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 600 - dependencies = [] + dependencies: List[str] = [] def run(self): diff --git a/src/diagnosers/12-dnsrecords.py b/src/diagnosers/12-dnsrecords.py index 305fda79b..85f70ca44 100644 --- a/src/diagnosers/12-dnsrecords.py +++ b/src/diagnosers/12-dnsrecords.py @@ -2,7 +2,7 @@ import os import re - +from typing import List from datetime import datetime, timedelta from publicsuffix2 import PublicSuffixList @@ -21,11 +21,12 @@ from yunohost.dns import _build_dns_conf, _get_dns_zone_for_domain logger = log.getActionLogger("yunohost.diagnosis") + class MyDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 600 - dependencies = ["ip"] + dependencies: List[str] = ["ip"] def run(self): diff --git a/src/diagnosers/14-ports.py b/src/diagnosers/14-ports.py index e339a946c..be172e524 100644 --- a/src/diagnosers/14-ports.py +++ b/src/diagnosers/14-ports.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import os +from typing import List from yunohost.diagnosis import Diagnoser from yunohost.service import _get_services @@ -10,7 +11,7 @@ class MyDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 600 - dependencies = ["ip"] + dependencies: List[str] = ["ip"] def run(self): diff --git a/src/diagnosers/21-web.py b/src/diagnosers/21-web.py index 3d9fb9f73..584505ad1 100644 --- a/src/diagnosers/21-web.py +++ b/src/diagnosers/21-web.py @@ -3,6 +3,7 @@ import os import random import requests +from typing import List from moulinette.utils.filesystem import read_file @@ -17,7 +18,7 @@ class MyDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 600 - dependencies = ["ip"] + dependencies: List[str] = ["ip"] def run(self): diff --git a/src/diagnosers/24-mail.py b/src/diagnosers/24-mail.py index c4df3ca54..7fe7a08db 100644 --- a/src/diagnosers/24-mail.py +++ b/src/diagnosers/24-mail.py @@ -3,6 +3,7 @@ import os import dns.resolver import re +from typing import List from subprocess import CalledProcessError @@ -24,7 +25,7 @@ class MyDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 600 - dependencies = ["ip"] + dependencies: List[str] = ["ip"] def run(self): diff --git a/src/diagnosers/30-services.py b/src/diagnosers/30-services.py index daf86ab1e..f09688911 100644 --- a/src/diagnosers/30-services.py +++ b/src/diagnosers/30-services.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import os +from typing import List from yunohost.diagnosis import Diagnoser from yunohost.service import service_status @@ -10,7 +11,7 @@ class MyDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 300 - dependencies = [] + dependencies: List[str] = [] def run(self): diff --git a/src/diagnosers/50-systemresources.py b/src/diagnosers/50-systemresources.py index 265e62acb..d259e7713 100644 --- a/src/diagnosers/50-systemresources.py +++ b/src/diagnosers/50-systemresources.py @@ -3,6 +3,7 @@ import os import psutil import datetime import re +from typing import List from moulinette.utils.process import check_output @@ -13,7 +14,7 @@ class MyDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 300 - dependencies = [] + dependencies: List[str] = [] def run(self): diff --git a/src/diagnosers/70-regenconf.py b/src/diagnosers/70-regenconf.py index 63f6f2b32..591f883a4 100644 --- a/src/diagnosers/70-regenconf.py +++ b/src/diagnosers/70-regenconf.py @@ -2,6 +2,7 @@ import os import re +from typing import List from yunohost.settings import settings_get from yunohost.diagnosis import Diagnoser @@ -13,7 +14,7 @@ class MyDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 300 - dependencies = [] + dependencies: List[str] = [] def run(self): diff --git a/src/diagnosers/80-apps.py b/src/diagnosers/80-apps.py index e62acaa1f..56e45f831 100644 --- a/src/diagnosers/80-apps.py +++ b/src/diagnosers/80-apps.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import os +from typing import List from yunohost.app import app_list @@ -11,7 +12,7 @@ class MyDiagnoser(Diagnoser): id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] cache_duration = 300 - dependencies = [] + dependencies: List[str] = [] def run(self): From c6df391a9af7dcc7f79a6c44d7c6b1a001054e30 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 28 Nov 2021 16:29:45 +0100 Subject: [PATCH 0970/1155] ci: Oopsies --- .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 159dc7aa0..8daa4f473 100644 --- a/.gitlab/ci/translation.gitlab-ci.yml +++ b/.gitlab/ci/translation.gitlab-ci.yml @@ -2,6 +2,7 @@ # TRANSLATION ######################################## test-i18n-keys: + stage: translation script: - python3 maintenance/missing_i18n_keys --check only: From 2bd1df0659588e096a2936f429c53d8dce6e74a0 Mon Sep 17 00:00:00 2001 From: Kayou Date: Tue, 30 Nov 2021 13:10:20 +0100 Subject: [PATCH 0971/1155] [fix] hook restore multimedia --- data/hooks/restore/18-data_multimedia | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/hooks/restore/18-data_multimedia b/data/hooks/restore/18-data_multimedia index eb8ef2608..c3c349e7d 100644 --- a/data/hooks/restore/18-data_multimedia +++ b/data/hooks/restore/18-data_multimedia @@ -6,4 +6,6 @@ set -eu # Source YNH helpers source /usr/share/yunohost/helpers -ynh_restore_file --origin_path="/home/yunohost.multimedia" --not_mandatory +backup_dir="data/multimedia" + +ynh_restore_file --origin_path="${backup_dir}" --dest_path="/home/yunohost.multimedia" --not_mandatory From 73da37f152e545744fbb52ad7efb905833d05339 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 30 Nov 2021 20:46:43 +0100 Subject: [PATCH 0972/1155] Attempt to fix the weird selfsigned certificate generation error --- hooks/conf_regen/02-ssl | 17 ++++++----------- src/certificate.py | 2 +- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/hooks/conf_regen/02-ssl b/hooks/conf_regen/02-ssl index a824c91f8..eded92854 100755 --- a/hooks/conf_regen/02-ssl +++ b/hooks/conf_regen/02-ssl @@ -6,7 +6,7 @@ ssl_dir="/usr/share/yunohost/ssl" ynh_ca="/etc/yunohost/certs/yunohost.org/ca.pem" ynh_crt="/etc/yunohost/certs/yunohost.org/crt.pem" ynh_key="/etc/yunohost/certs/yunohost.org/key.pem" -openssl_conf="/usr/share/yunohost/conf/ssl/openssl.cnf" +template_dir="/usr/share/yunohost/conf/ssl/" regen_local_ca() { @@ -26,7 +26,7 @@ regen_local_ca() { RANDFILE=.rnd openssl rand -hex 19 >serial rm -f index.txt touch index.txt - cp /usr/share/yunohost/conf/ssl/openssl.cnf openssl.ca.cnf + cp ${template_dir}/openssl.cnf openssl.ca.cnf sed -i "s/yunohost.org/${domain}/g" openssl.ca.cnf openssl req -x509 \ -new \ @@ -57,7 +57,7 @@ do_init_regen() { # Make sure this conf exists mkdir -p ${ssl_dir} - cp /usr/share/yunohost/conf/ssl/openssl.cnf ${ssl_dir}/openssl.ca.cnf + install -D -m 644 ${template_dir}/openssl.cnf "${ssl_dir}/openssl.cnf" # create default certificates if [[ ! -f "$ynh_ca" ]]; then @@ -68,14 +68,13 @@ do_init_regen() { echo -e "\n# Creating initial key and certificate \n" >>$LOGFILE openssl req -new \ - -config "$openssl_conf" \ - -days 730 \ + -config "${ssl_dir}/openssl.cnf" \ -out "${ssl_dir}/certs/yunohost_csr.pem" \ -keyout "${ssl_dir}/certs/yunohost_key.pem" \ -nodes -batch &>>$LOGFILE openssl ca \ - -config "$openssl_conf" \ + -config "${ssl_dir}/openssl.cnf" \ -days 730 \ -in "${ssl_dir}/certs/yunohost_csr.pem" \ -out "${ssl_dir}/certs/yunohost_crt.pem" \ @@ -92,16 +91,12 @@ do_init_regen() { chown -R root:ssl-cert /etc/yunohost/certs/yunohost.org/ chmod o-rwx /etc/yunohost/certs/yunohost.org/ - - install -D -m 644 $openssl_conf "${ssl_dir}/openssl.cnf" } do_pre_regen() { pending_dir=$1 - cd /usr/share/yunohost/conf/ssl - - install -D -m 644 openssl.cnf "${pending_dir}/${ssl_dir}/openssl.cnf" + install -D -m 644 $template_dir/openssl.cnf "${pending_dir}/${ssl_dir}/openssl.cnf" } do_post_regen() { diff --git a/src/certificate.py b/src/certificate.py index 46aa9c818..724d9b62e 100644 --- a/src/certificate.py +++ b/src/certificate.py @@ -181,7 +181,7 @@ def _certificate_install_selfsigned(domain_list, force=False): # Use OpenSSL command line to create a certificate signing request, # and self-sign the cert commands = [ - "openssl req -new -config %s -days 3650 -out %s -keyout %s -nodes -batch" + "openssl req -new -config %s -out %s -keyout %s -nodes -batch" % (conf_file, csr_file, key_file), "openssl ca -config %s -days 3650 -in %s -out %s -batch" % (conf_file, csr_file, crt_file), From 76075909c9164e316a64bbd130223f78cb43520f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 30 Nov 2021 21:29:11 +0100 Subject: [PATCH 0973/1155] Moar attempt to fix ssl cert generation + automigrate legacy + enforce sensible permissions --- hooks/conf_regen/01-yunohost | 2 -- hooks/conf_regen/02-ssl | 22 ++++++++++++++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/hooks/conf_regen/01-yunohost b/hooks/conf_regen/01-yunohost index e2924bd6d..d30d77ace 100755 --- a/hooks/conf_regen/01-yunohost +++ b/hooks/conf_regen/01-yunohost @@ -184,8 +184,6 @@ do_post_regen() { find /etc/cron.d/yunohost-* -type f -exec chmod 644 {} \; find /etc/cron.*/yunohost-* -type f -exec chown root:root {} \; - chmod 750 /usr/share/yunohost/ssl - chown root:root /var/cache/yunohost chmod 700 /var/cache/yunohost diff --git a/hooks/conf_regen/02-ssl b/hooks/conf_regen/02-ssl index eded92854..a50e51187 100755 --- a/hooks/conf_regen/02-ssl +++ b/hooks/conf_regen/02-ssl @@ -3,10 +3,10 @@ set -e ssl_dir="/usr/share/yunohost/ssl" +template_dir="/usr/share/yunohost/conf/ssl/" ynh_ca="/etc/yunohost/certs/yunohost.org/ca.pem" ynh_crt="/etc/yunohost/certs/yunohost.org/crt.pem" ynh_key="/etc/yunohost/certs/yunohost.org/key.pem" -template_dir="/usr/share/yunohost/conf/ssl/" regen_local_ca() { @@ -56,7 +56,7 @@ do_init_regen() { chmod 640 $LOGFILE # Make sure this conf exists - mkdir -p ${ssl_dir} + mkdir -p ${ssl_dir}/{ca,certs,crl,newcerts} install -D -m 644 ${template_dir}/openssl.cnf "${ssl_dir}/openssl.cnf" # create default certificates @@ -104,12 +104,26 @@ do_post_regen() { current_local_ca_domain=$(openssl x509 -in $ynh_ca -text | tr ',' '\n' | grep Issuer | awk '{print $4}') main_domain=$(cat /etc/yunohost/current_host) + + # Automigrate legacy folder + if [ -e /usr/share/yunohost/yunohost-config/ssl/yunoCA ] + then + mv /usr/share/yunohost/yunohost-config/ssl/yunoCA/* ${ssl_dir} + rm -rf /usr/share/yunohost/yunohost-config + fi + + mkdir -p ${ssl_dir}/{ca,certs,crl,newcerts} + chown root:root ${ssl_dir} + chmod 750 ${ssl_dir} + chmod -R o-rwx ${ssl_dir} + chmod o+x ${ssl_dir}/certs + chmod o+r ${ssl_dir}/certs/yunohost_crt.pem if [[ "$current_local_ca_domain" != "$main_domain" ]]; then regen_local_ca $main_domain # Idk how useful this is, but this was in the previous python code (domain.main_domain()) - ln -sf /etc/yunohost/certs/$domain/crt.pem /etc/ssl/certs/yunohost_crt.pem - ln -sf /etc/yunohost/certs/$domain/key.pem /etc/ssl/private/yunohost_key.pem + ln -sf /etc/yunohost/certs/$main_domain/crt.pem /etc/ssl/certs/yunohost_crt.pem + ln -sf /etc/yunohost/certs/$main_domain/key.pem /etc/ssl/private/yunohost_key.pem fi } From 34b7e667a774376a888f57867b20c2ee84ad596c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 30 Nov 2021 23:25:25 +0100 Subject: [PATCH 0974/1155] Aaaaand try again to fix certificate generation --- hooks/conf_regen/02-ssl | 3 +++ src/certificate.py | 12 +++--------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/hooks/conf_regen/02-ssl b/hooks/conf_regen/02-ssl index a50e51187..e98c70c6e 100755 --- a/hooks/conf_regen/02-ssl +++ b/hooks/conf_regen/02-ssl @@ -110,6 +110,9 @@ do_post_regen() { then mv /usr/share/yunohost/yunohost-config/ssl/yunoCA/* ${ssl_dir} rm -rf /usr/share/yunohost/yunohost-config + # Overwrite openssl.cnf because it may still contain references to the old yunoCA dir + rm -f ${ssl_dir}/openssl.ca.cnf + install -D -m 644 ${template_dir}/openssl.cnf "${ssl_dir}/openssl.cnf" fi mkdir -p ${ssl_dir}/{ca,certs,crl,newcerts} diff --git a/src/certificate.py b/src/certificate.py index 724d9b62e..86a63a996 100644 --- a/src/certificate.py +++ b/src/certificate.py @@ -143,11 +143,7 @@ def _certificate_install_selfsigned(domain_list, force=False): # Paths of files and folder we'll need date_tag = datetime.utcnow().strftime("%Y%m%d.%H%M%S") - new_cert_folder = "%s/%s-history/%s-selfsigned" % ( - CERT_FOLDER, - domain, - date_tag, - ) + new_cert_folder = f"{CERT_FOLDER}/{domain}-history/{date_tag}-selfsigned" conf_template = os.path.join(SSL_DIR, "openssl.cnf") @@ -181,10 +177,8 @@ def _certificate_install_selfsigned(domain_list, force=False): # Use OpenSSL command line to create a certificate signing request, # and self-sign the cert commands = [ - "openssl req -new -config %s -out %s -keyout %s -nodes -batch" - % (conf_file, csr_file, key_file), - "openssl ca -config %s -days 3650 -in %s -out %s -batch" - % (conf_file, csr_file, crt_file), + f"openssl req -new -config {conf_file} -out {csr_file} -keyout {key_file} -nodes -batch", + f"openssl ca -config {conf_file} -days 3650 -in {csr_file} -out {crt_file} -batch", ] for command in commands: From 9488b419c3facda0dddd9c00a6d6960420a7fa90 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 1 Dec 2021 00:24:31 +0100 Subject: [PATCH 0975/1155] Having an openssl.ca.cnf is needed to create domains --- hooks/conf_regen/02-ssl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hooks/conf_regen/02-ssl b/hooks/conf_regen/02-ssl index e98c70c6e..1aaab59d1 100755 --- a/hooks/conf_regen/02-ssl +++ b/hooks/conf_regen/02-ssl @@ -111,8 +111,9 @@ do_post_regen() { mv /usr/share/yunohost/yunohost-config/ssl/yunoCA/* ${ssl_dir} rm -rf /usr/share/yunohost/yunohost-config # Overwrite openssl.cnf because it may still contain references to the old yunoCA dir - rm -f ${ssl_dir}/openssl.ca.cnf install -D -m 644 ${template_dir}/openssl.cnf "${ssl_dir}/openssl.cnf" + install -D -m 644 ${template_dir}/openssl.cnf "${ssl_dir}/openssl.ca.cnf" + sed -i "s/yunohost.org/${main_domain}/g" openssl.ca.cnf fi mkdir -p ${ssl_dir}/{ca,certs,crl,newcerts} From 8feb63be0c93ffd881d9863764d7cee417792672 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 1 Dec 2021 18:37:59 +0100 Subject: [PATCH 0976/1155] Tweak definition of self-signed cert to cover cases where issuer is not the current main domain anymore --- src/certificate.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/certificate.py b/src/certificate.py index 86a63a996..c89d2e312 100644 --- a/src/certificate.py +++ b/src/certificate.py @@ -664,6 +664,8 @@ def _prepare_certificate_signing_request(domain, key_file, output_folder): def _get_status(domain): + import yunohost.domain + cert_file = os.path.join(CERT_FOLDER, domain, "crt.pem") if not os.path.isfile(cert_file): @@ -692,7 +694,7 @@ def _get_status(domain): ) days_remaining = (valid_up_to - datetime.utcnow()).days - if cert_issuer == "yunohost.org" or cert_issuer == _name_self_CA(): + if cert_issuer in ["yunohost.org"] + yunohost.domain.domain_list()["domains"]: CA_type = { "code": "self-signed", "verbose": "Self-signed", From 6622a9f2647ec9f3a5f30a2fc2cbb1cd3769d3a4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 1 Dec 2021 19:08:16 +0100 Subject: [PATCH 0977/1155] Typo in conf/yunohost/services.yml ? --- conf/yunohost/services.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/yunohost/services.yml b/conf/yunohost/services.yml index ed92ac47c..45621876e 100644 --- a/conf/yunohost/services.yml +++ b/conf/yunohost/services.yml @@ -75,6 +75,6 @@ spamassassin: null rmilter: null php5-fpm: null php7.0-fpm: null -php7.3-fpm: +php7.3-fpm: null nslcd: null avahi-daemon: null From 55937a933e0854f043527c7b35b28a3004525ae8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 1 Dec 2021 19:44:42 +0100 Subject: [PATCH 0978/1155] 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 ef84d98bd..32e02413b 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -534,7 +534,7 @@ def tools_upgrade( # Prepare dist-upgrade command dist_upgrade = "DEBIAN_FRONTEND=noninteractive" if Moulinette.interface.type == "api": - dist_upgrade += "YUNOHOST_API_RESTART_WILL_BE_HANDLED_BY_YUNOHOST=yes" + dist_upgrade += " YUNOHOST_API_RESTART_WILL_BE_HANDLED_BY_YUNOHOST=yes" dist_upgrade += " APT_LISTCHANGES_FRONTEND=none" dist_upgrade += " apt-get" dist_upgrade += ( From 94ac15e4de00aa9450e4cab08c9fc1d68adf304c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 1 Dec 2021 19:53:28 +0100 Subject: [PATCH 0979/1155] Update changelog for 11.0.1~alpha / bullseye alpha --- debian/changelog | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index b90838102..78023c71e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,6 +1,26 @@ -yunohost (11.0.0~alpha) unstable; urgency=low +yunohost (11.0.1~alpha) unstable; urgency=low - - Placeholder for 11.0 + - [mod] Various tweaks for Python 3.9, PHP 7.4, and other changes related to Buster->Bullseye ecosystem + - [mod] quality: Rework repository code architecture ([#1377](https://github.com/YunoHost/yunohost/pull/1377)) + - [mod] quality: Rework where yunohost files are deployed (yunohost now a proper python lib with files in /usr/lib/python3/dist-packages/yunohost/, and other files are in /usr/share/yunohost) ([#1377](https://github.com/YunoHost/yunohost/pull/1377)) + - [mod] debian: Moved mysql, php, and metronome from Depends to Recommends ([#1369](https://github.com/YunoHost/yunohost/pull/1369)) + - [mod] apt: Add sury by default ([#1369](https://github.com/YunoHost/yunohost/pull/1369)) + - [enh] regenconf/helpers: Better integration for postgresql ([#1369](https://github.com/YunoHost/yunohost/pull/1369)) + - [enh] mysql: Drop super old mysql config, now rely on Debian default's one ([44c972f...144126f](https://github.com/YunoHost/yunohost/compare/44c972f2dd65...144126f56a3d)) + - [enh] upgrade: Try to implement a smarter self-upgrade mechanism to prevent/limit API downtime and related UX issues ([#1374](https://github.com/YunoHost/yunohost/pull/1374)) + - [mod] app: During app scripts (and all stuff run in hook_exec), do not inject the HOME variable if it exists. This aims to prevent inconsistencies between CLI (where HOME usually is defined) and API (where HOME doesnt exists) (f43e567b) + - [mod] quality: Cleanup legacy stuff + - Drop `--other_vars` options in ynh_add_fail2ban_config and systemd_config helpers + - Drop deprecated/superold `ynh_bind_or_cp`, `ynh_mkdir_tmp`, `ynh_get_plain_key` helpers + - Drop obsolete `yunohost-reset-ldap-password` command + - Drop obsolete `yunohost dyndns installcron` and `removecron` commands + - Drop deprecated `yunohost service regen-conf` command (see `tools regen-conf` instead) + - Drop deprecated `yunohost app fetchlist` command + - Drop obsolete `yunohost app add/remove/clearaccess` commands + - Drop depcreated `--list` and `--filter` options in `yunohost app list` + - Drop deprecated `--apps` and `--system` options in `yunohost tools update/upgrade` (no double dashes anymore) + - Drop deprecated `--status` and `--log_type` options in `yunohost service add` + - Drop deprecated `--mail` option in `yunohost user create` -- Alexandre Aubin Fri, 05 Feb 2021 00:02:38 +0100 From bc71e18cba713164dc6b2b6c59033f21cb2ecda1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 1 Dec 2021 20:33:48 +0100 Subject: [PATCH 0980/1155] Aleks broke everything again /o\ --- debian/rules | 6 ------ 1 file changed, 6 deletions(-) diff --git a/debian/rules b/debian/rules index 8c9bc5976..5527d8703 100755 --- a/debian/rules +++ b/debian/rules @@ -12,9 +12,3 @@ override_dh_auto_build: override_dh_installinit: dh_installinit -pyunohost --name=yunohost-api --noscripts dh_installinit -pyunohost --name=yunohost-firewall --noscripts - -override_dh_systemd_enable: - dh_systemd_enable --name=yunohost-api \ - yunohost-api.service - dh_systemd_enable --name=yunohost-firewall --no-enable \ - yunohost-firewall.service From 613fc67bd1a1d1feb2133cd172c2df41590f7836 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 1 Dec 2021 20:35:51 +0100 Subject: [PATCH 0981/1155] Fix i18n strings --- locales/en.json | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/locales/en.json b/locales/en.json index f720af34e..fc4e4283a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -648,13 +648,8 @@ "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_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_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).", + "tools_upgrade": "Upgrading system packages", + "tools_upgrade_failed": "Could not upgrade packages: {packages_list}", "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.", @@ -690,4 +685,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 8eaa7012309d4a9a58bbadf17274b0dca5ce050f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 1 Dec 2021 21:06:53 +0100 Subject: [PATCH 0982/1155] Fuckit I don't understand how to properly manage systemctl service with debian files while having flexibility on when the api restart happens, let's manage everything with the regen conf --- {debian => conf/yunohost}/yunohost-api.service | 4 +--- .../yunohost}/yunohost-firewall.service | 0 debian/rules | 4 ---- debian/yunohost-api.default | 4 ---- hooks/conf_regen/01-yunohost | 14 ++++++++++++++ 5 files changed, 15 insertions(+), 11 deletions(-) rename {debian => conf/yunohost}/yunohost-api.service (59%) rename {debian => conf/yunohost}/yunohost-firewall.service (100%) delete mode 100644 debian/yunohost-api.default diff --git a/debian/yunohost-api.service b/conf/yunohost/yunohost-api.service similarity index 59% rename from debian/yunohost-api.service rename to conf/yunohost/yunohost-api.service index 850255127..aa429ec7a 100644 --- a/debian/yunohost-api.service +++ b/conf/yunohost/yunohost-api.service @@ -4,9 +4,7 @@ After=network.target [Service] Type=simple -Environment=DAEMON_OPTS= -EnvironmentFile=-/etc/default/yunohost-api -ExecStart=/usr/bin/yunohost-api $DAEMON_OPTS +ExecStart=/usr/bin/yunohost-api Restart=always RestartSec=5 TimeoutStopSec=30 diff --git a/debian/yunohost-firewall.service b/conf/yunohost/yunohost-firewall.service similarity index 100% rename from debian/yunohost-firewall.service rename to conf/yunohost/yunohost-firewall.service diff --git a/debian/rules b/debian/rules index 5527d8703..5cf1d9bee 100755 --- a/debian/rules +++ b/debian/rules @@ -8,7 +8,3 @@ override_dh_auto_build: # Generate bash completion file python3 doc/generate_bash_completion.py python3 doc/generate_manpages.py --gzip --output doc/yunohost.8.gz - -override_dh_installinit: - dh_installinit -pyunohost --name=yunohost-api --noscripts - dh_installinit -pyunohost --name=yunohost-firewall --noscripts diff --git a/debian/yunohost-api.default b/debian/yunohost-api.default deleted file mode 100644 index b6a9e5a99..000000000 --- a/debian/yunohost-api.default +++ /dev/null @@ -1,4 +0,0 @@ -# Override yunohost-api options. -# Example to log debug: DAEMON_OPTS="--debug" -# -#DAEMON_OPTS="" diff --git a/hooks/conf_regen/01-yunohost b/hooks/conf_regen/01-yunohost index d30d77ace..22929db33 100755 --- a/hooks/conf_regen/01-yunohost +++ b/hooks/conf_regen/01-yunohost @@ -56,7 +56,16 @@ do_init_regen() { chown root:root /var/cache/yunohost chmod 700 /var/cache/yunohost + cp yunohost-api.service /etc/systemd/system/yunohost-api.service + cp yunohost-firewall.service /etc/systemd/system/yunohost-firewall.service cp yunoprompt.service /etc/systemd/system/yunoprompt.service + + systemctl daemon-reload + + systemctl enable yunohost-api.service + systemctl start yunohost-api.service + # Yunohost-firewall is enabled only during postinstall, not init, not 100% sure why + cp dpkg-origins /etc/dpkg/origins/yunohost # Change dpkg vendor @@ -142,6 +151,8 @@ HandleLidSwitchDocked=ignore HandleLidSwitchExternalPower=ignore EOF + cp yunohost-api.service ${pending_dir}/etc/systemd/system/yunohost-api.service + cp yunohost-firewall.service ${pending_dir}/etc/systemd/system/yunohost-firewall.service cp yunoprompt.service ${pending_dir}/etc/systemd/system/yunoprompt.service if [[ "$(yunohost settings get 'security.experimental.enabled')" == "True" ]]; then @@ -219,6 +230,9 @@ do_post_regen() { } [[ ! "$regen_conf_files" =~ "nftables.service.d/ynh-override.conf" ]] || systemctl daemon-reload [[ ! "$regen_conf_files" =~ "login.conf.d/ynh-override.conf" ]] || systemctl daemon-reload + [[ ! "$regen_conf_files" =~ "yunohost-firewall.service" ]] || systemctl daemon-reload + [[ ! "$regen_conf_files" =~ "yunohost-api.service" ]] || systemctl daemon-reload + if [[ "$regen_conf_files" =~ "yunoprompt.service" ]]; then systemctl daemon-reload action=$([[ -e /etc/systemd/system/yunoprompt.service ]] && echo 'enable' || echo 'disable') From 47f3c00d0c1ef3ce65cd7768b38526cb2b02a9b6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 3 Dec 2021 01:00:19 +0100 Subject: [PATCH 0983/1155] helpers apt/php: fix typo spotted by tituspijean --- 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 2e56e3788..281e17f70 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -325,7 +325,7 @@ EOF ynh_app_setting_set --app=$app --key=phpversion --value=$specific_php_version # Integrate new php-fpm service in yunohost - yunohost service add php${specific_php_version}-fpm --log "/var/log/php${phpversion}-fpm.log" + yunohost service add php${specific_php_version}-fpm --log "/var/log/php${specific_php_version}-fpm.log" elif grep --quiet 'php' <<< "$dependencies"; then # Store phpversion into the config of this app ynh_app_setting_set --app=$app --key=phpversion --value=$YNH_DEFAULT_PHP_VERSION From 00d535a620ac8c4e5a7dbd7bfbcdb4f7338318cd Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 6 Dec 2021 18:38:49 +0100 Subject: [PATCH 0984/1155] Stop using /home/yunohost.conf for regenconf / confbackup, /var/cache is fine, prevent confusing /home/yunohost.* folder --- helpers/backup | 6 +++--- hooks/conf_regen/01-yunohost | 7 ++----- src/regenconf.py | 2 +- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/helpers/backup b/helpers/backup index e4828a10d..01b51d5a1 100644 --- a/helpers/backup +++ b/helpers/backup @@ -226,7 +226,7 @@ with open(sys.argv[1], 'r') as backup_file: # 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. +# `/var/cache/yunohost/appconfbackup/`. Otherwise, the existing file is removed. # # if `apps/$app/etc/nginx/conf.d/$domain.d/$app.conf` exists, restore it into # `/etc/nginx/conf.d/$domain.d/$app.conf` @@ -263,7 +263,7 @@ ynh_restore_file() { if [[ -e "${dest_path}" ]]; then # Check if the file/dir size is less than 500 Mo 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')" + local backup_file="/var/cache/yunohost/appconfbackup/${dest_path}.backup.$(date '+%Y%m%d.%H%M%S')" mkdir --parents "$(dirname "$backup_file")" mv "${dest_path}" "$backup_file" # Move the current file or directory else @@ -353,7 +353,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 [ -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')" + backup_file_checksum="/var/cache/yunohost/appconfbackup/$file.backup.$(date '+%Y%m%d.%H%M%S')" 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" diff --git a/hooks/conf_regen/01-yunohost b/hooks/conf_regen/01-yunohost index 22929db33..14840e2f1 100755 --- a/hooks/conf_regen/01-yunohost +++ b/hooks/conf_regen/01-yunohost @@ -174,12 +174,12 @@ do_post_regen() { ###################### chmod 750 /home/admin - chmod 750 /home/yunohost.conf chmod 750 /home/yunohost.backup chmod 750 /home/yunohost.backup/archives - chown root:root /home/yunohost.conf + chmod 700 /var/cache/yunohost chown admin:root /home/yunohost.backup chown admin:root /home/yunohost.backup/archives + chown root:root /var/cache/yunohost # NB: x permission for 'others' is important for ssl-cert (and maybe mdns), otherwise slapd will fail to start because can't access the certs chmod 755 /etc/yunohost @@ -195,9 +195,6 @@ do_post_regen() { find /etc/cron.d/yunohost-* -type f -exec chmod 644 {} \; find /etc/cron.*/yunohost-* -type f -exec chown root:root {} \; - chown root:root /var/cache/yunohost - chmod 700 /var/cache/yunohost - setfacl -m g:all_users:--- /var/www setfacl -m g:all_users:--- /var/log/nginx setfacl -m g:all_users:--- /etc/yunohost diff --git a/src/regenconf.py b/src/regenconf.py index afcfb4360..1be62a96f 100644 --- a/src/regenconf.py +++ b/src/regenconf.py @@ -35,7 +35,7 @@ from yunohost.utils.error import YunohostError from yunohost.log import is_unit_operation from yunohost.hook import hook_callback, hook_list -BASE_CONF_PATH = "/home/yunohost.conf" +BASE_CONF_PATH = "/var/cache/yunohost/regenconf" BACKUP_CONF_DIR = os.path.join(BASE_CONF_PATH, "backup") PENDING_CONF_DIR = os.path.join(BASE_CONF_PATH, "pending") REGEN_CONF_FILE = "/etc/yunohost/regenconf.yml" From 4106ed669e5a53652b46d9e7804dbdfa14d699fc Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 6 Dec 2021 23:01:06 +0100 Subject: [PATCH 0985/1155] [enh] Manage SSH PasswordAuthentication setting --- data/hooks/conf_regen/03-ssh | 1 + data/templates/ssh/sshd_config | 10 ++++++++-- src/yunohost/settings.py | 5 +++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/data/hooks/conf_regen/03-ssh b/data/hooks/conf_regen/03-ssh index f10dbb653..3f04acd0c 100755 --- a/data/hooks/conf_regen/03-ssh +++ b/data/hooks/conf_regen/03-ssh @@ -26,6 +26,7 @@ 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 password_authentication="$(yunohost settings get 'security.ssh.password_authentication')" 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 1c2854f73..22f0697d9 100644 --- a/data/templates/ssh/sshd_config +++ b/data/templates/ssh/sshd_config @@ -2,6 +2,8 @@ # by YunoHost Protocol 2 +# PLEASE: to change ssh port properly in YunoHost, use this command +# yunohost settings set security.ssh.port -v Port {{ port }} {% if ipv6_enabled == "true" %}ListenAddress ::{% endif %} @@ -53,9 +55,13 @@ PermitEmptyPasswords no ChallengeResponseAuthentication no UsePAM yes -# Change to no to disable tunnelled clear text passwords -# (i.e. everybody will need to authenticate using ssh keys) +# PLEASE: to force everybody to authenticate using ssh keys, run this command: +# yunohost settings set security.ssh.password_authentication -v no +{% if password_authentication == "True" %} #PasswordAuthentication yes +{% else %} +PasswordAuthentication no +{% endif %} # Post-login stuff Banner /etc/issue.net diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index d59b41a58..26b8c48b2 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -81,6 +81,10 @@ DEFAULTS = OrderedDict( "security.ssh.port", {"type": "int", "default": 22}, ), + ( + "security.ssh.password_authentication", + {"type": "bool", "default": True}, + ), ( "security.nginx.redirect_to_https", { @@ -420,6 +424,7 @@ def reconfigure_nginx_and_yunohost(setting_name, old_value, new_value): @post_change_hook("security.ssh.compatibility") +@post_change_hook("security.ssh.password_authentication") def reconfigure_ssh(setting_name, old_value, new_value): if old_value != new_value: regen_conf(names=["ssh"]) From 1b198e12f69badf99791cd88a037d96dca9ff039 Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 7 Dec 2021 00:14:15 +0100 Subject: [PATCH 0986/1155] [fix] Missing locale key --- locales/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/locales/en.json b/locales/en.json index 81e75eb32..66f42df58 100644 --- a/locales/en.json +++ b/locales/en.json @@ -382,6 +382,7 @@ "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)", "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_password_authentication": "Password authentication allowed", "global_settings_setting_security_ssh_port": "SSH port", "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.", From d1ab1f674eb25f0970ded4698a89aee18f202cec Mon Sep 17 00:00:00 2001 From: ericgaspar Date: Tue, 7 Dec 2021 12:23:22 +0100 Subject: [PATCH 0987/1155] Update n to 8.0.1 --- 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 e7e61b0c6..962ac2a70 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -1,6 +1,6 @@ #!/bin/bash -n_version=8.0.0 +n_version=8.0.1 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. @@ -16,7 +16,7 @@ export N_PREFIX="$n_install_dir" ynh_install_n() { # Build an app.src for n echo "SOURCE_URL=https://github.com/tj/n/archive/v${n_version}.tar.gz -SOURCE_SUM=9e8879dc4f1c4c0fe4e08a108ed6c23046419b6865fe922ca5176ff7998ae6ff" >"$YNH_APP_BASEDIR/conf/n.src" +SOURCE_SUM=8703ae88fd06ce7f2d0f4018d68bfbab7b26859ed86a86ce4b8f25d2110aee2f" >"$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 71a08c09ea5099d7951d8d7badf0ed3bc0a02cf2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 8 Dec 2021 22:06:46 +0100 Subject: [PATCH 0988/1155] Update changelog for 4.3.4.2 --- debian/changelog | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/debian/changelog b/debian/changelog index f53b259b6..001ab678a 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,14 @@ +yunohost (4.3.4.2) stable; urgency=low + + - [fix] yunomdns: Ignore ipv4 link-local addresses (6854f23c) + - [fix] backup: Fix path for multimedia restore ([#1386](https://github.com/YunoHost/yunohost/pull/1386)) + - [fix] helpers apt/php: typo in extra php-fpm yunohost service integration (47f3c00d) + - [enh] helpers: Update n to 8.0.1 (d1ab1f67) + + Thanks to all contributors <3 ! (ericgaspar, Kayou) + + -- Alexandre Aubin Wed, 08 Dec 2021 22:04:04 +0100 + yunohost (4.3.4.1) stable; urgency=low - [fix] regenconf: Force permission on /etc/resolv.dnsmasq.conf to fix an issue on some setup with umask=027 (5881938c) From b617c799d05fa95e8fb3af85442b59053412f03f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 8 Dec 2021 22:11:02 +0100 Subject: [PATCH 0989/1155] migrate_to_bullseye: move /home/yunohost.conf to /var/cache/yunohost/regenconf --- src/yunohost/data_migrations/0021_migrate_to_bullseye.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py index aee3bc2d6..b39ef09e8 100644 --- a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py +++ b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py @@ -84,6 +84,13 @@ class MyMigration(Migration): os.system("mv /usr/share/yunohost/yunohost-config/ssl/yunoCA /usr/share/yunohost/ssl") rm("/usr/share/yunohost/yunohost-config", recursive=True, force=True) + # + # /home/yunohost.conf -> /var/cache/yunohost/regenconf + # + if os.path.exists("/home/yunohost.conf"): + os.system("mv /home/yunohost.conf /var/cache/yunohost/regenconf") + rm("/home/yunohost.conf", recursive=True, force=True) + # # Main upgrade # From 83f7721fdd515bd5d3acdd9f5f584c3ee0891a8e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 9 Dec 2021 16:24:12 +0100 Subject: [PATCH 0990/1155] [fix] services: small issue when parsing installed php-fpm versions during buster->bullseye migration (or more generally, there could be no php-fpm installed) --- src/service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/service.py b/src/service.py index b723df407..fb1e15c5f 100644 --- a/src/service.py +++ b/src/service.py @@ -705,7 +705,7 @@ def _get_services(): if os.system(f"dpkg --list | grep -q 'ii *{package}'") != 0: del services[name] - php_fpm_versions = check_output(r"dpkg --list | grep -P 'ii php\d.\d-fpm' | awk '{print $2}' | grep -o -P '\d.\d'") + php_fpm_versions = check_output(r"dpkg --list | grep -P 'ii php\d.\d-fpm' | awk '{print $2}' | grep -o -P '\d.\d' || true") php_fpm_versions = [v for v in php_fpm_versions.split('\n') if v.strip()] for version in php_fpm_versions: services[f"php{version}-fpm"] = { From 107bd854eb338617741847e07a085981140249b7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 9 Dec 2021 16:47:57 +0100 Subject: [PATCH 0991/1155] migrate_to_bullseye: fix PHP dependencies issues --- .../data_migrations/0021_migrate_to_bullseye.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py index b39ef09e8..6ce963625 100644 --- a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py +++ b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py @@ -108,6 +108,22 @@ class MyMigration(Migration): os.system("apt autoremove --assume-yes") os.system("apt clean --assume-yes") + # Force add sury if it's not there yet + # This is to solve some weird issue with php-common breaking php7.3-common, + # hence breaking many php7.3-deps + # hence triggering some dependency conflict (or foobar-ynh-deps uninstall) + # Adding it there shouldnt be a big deal - Yunohost 11.x does add it + # through its regen conf anyway. + if not os.path.exists("/etc/apt/sources.list.d/extra_php_version.list"): + open("/etc/apt/sources.list.d/extra_php_version.list", "w").write("deb https://packages.sury.org/php/ bullseye main") + os.system('wget --timeout 900 --quiet "https://packages.sury.org/php/apt.gpg" --output-document=- | gpg --dearmor >"/etc/apt/trusted.gpg.d/extra_php_version.gpg"') + + os.system("apt update") + + # Force explicit install of php7.4-fpm to make sure it's ll be there + # during 0022_php73_to_php74_pools migration + self.apt_install("php7.4-fpm -o Dpkg::Options::='--force-confmiss'") + # # Yunohost upgrade # From c4732b776e7420c5b19658d61dc36c787f5cf6c5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 9 Dec 2021 17:13:06 +0100 Subject: [PATCH 0992/1155] migrate_to_bullseye: Remove legacy postgresql service record added by helpers, will now be dynamically handled by the core in bullseye --- src/yunohost/data_migrations/0021_migrate_to_bullseye.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py index 6ce963625..de53ba8be 100644 --- a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py +++ b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py @@ -15,6 +15,7 @@ from yunohost.utils.packages import ( get_ynh_package_version, _list_upgradable_apt_packages, ) +from yunohost.services import _get_services, _save_services logger = getActionLogger("yunohost.migration") @@ -124,6 +125,13 @@ class MyMigration(Migration): # during 0022_php73_to_php74_pools migration self.apt_install("php7.4-fpm -o Dpkg::Options::='--force-confmiss'") + # Remove legacy postgresql service record added by helpers, + # will now be dynamically handled by the core in bullseye + services = _get_services() + if "postgresql" in services: + del services["postgresql"] + _save_services(services) + # # Yunohost upgrade # From 8abd843fdde7fcec9cc2ae79fbe2f9417ec30725 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 9 Dec 2021 17:31:07 +0100 Subject: [PATCH 0993/1155] Aaaand typo T_T --- src/yunohost/data_migrations/0021_migrate_to_bullseye.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py index de53ba8be..36e816db1 100644 --- a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py +++ b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py @@ -15,7 +15,7 @@ from yunohost.utils.packages import ( get_ynh_package_version, _list_upgradable_apt_packages, ) -from yunohost.services import _get_services, _save_services +from yunohost.service import _get_services, _save_services logger = getActionLogger("yunohost.migration") From 6cc7978b4a285496371e47d9e0cff8d6ed0b7600 Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Thu, 9 Dec 2021 19:18:07 +0000 Subject: [PATCH 0994/1155] [CI] Format code with Black --- .../0021_migrate_to_bullseye.py | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py index 36e816db1..f97ab16da 100644 --- a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py +++ b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py @@ -25,6 +25,7 @@ N_CURRENT_YUNOHOST = 4 N_NEXT_DEBAN = 11 N_NEXT_YUNOHOST = 11 + class MyMigration(Migration): "Upgrade the system to Debian Bullseye and Yunohost 11.x" @@ -76,13 +77,17 @@ class MyMigration(Migration): _force_clear_hashes(["/etc/mysql/my.cnf"]) rm("/etc/mysql/mariadb.cnf", force=True) rm("/etc/mysql/my.cnf", force=True) - self.apt_install("mariadb-common --reinstall -o Dpkg::Options::='--force-confmiss'") + self.apt_install( + "mariadb-common --reinstall -o Dpkg::Options::='--force-confmiss'" + ) # # /usr/share/yunohost/yunohost-config/ssl/yunoCA -> /usr/share/yunohost/ssl # if os.path.exists("/usr/share/yunohost/yunohost-config/ssl/yunoCA"): - os.system("mv /usr/share/yunohost/yunohost-config/ssl/yunoCA /usr/share/yunohost/ssl") + os.system( + "mv /usr/share/yunohost/yunohost-config/ssl/yunoCA /usr/share/yunohost/ssl" + ) rm("/usr/share/yunohost/yunohost-config", recursive=True, force=True) # @@ -116,8 +121,12 @@ class MyMigration(Migration): # Adding it there shouldnt be a big deal - Yunohost 11.x does add it # through its regen conf anyway. if not os.path.exists("/etc/apt/sources.list.d/extra_php_version.list"): - open("/etc/apt/sources.list.d/extra_php_version.list", "w").write("deb https://packages.sury.org/php/ bullseye main") - os.system('wget --timeout 900 --quiet "https://packages.sury.org/php/apt.gpg" --output-document=- | gpg --dearmor >"/etc/apt/trusted.gpg.d/extra_php_version.gpg"') + open("/etc/apt/sources.list.d/extra_php_version.list", "w").write( + "deb https://packages.sury.org/php/ bullseye main" + ) + os.system( + 'wget --timeout 900 --quiet "https://packages.sury.org/php/apt.gpg" --output-document=- | gpg --dearmor >"/etc/apt/trusted.gpg.d/extra_php_version.gpg"' + ) os.system("apt update") @@ -205,10 +214,10 @@ class MyMigration(Migration): message = m18n.n("migration_0021_general_warning") # FIXME: re-enable this message with updated topic link once we release the migration as stable - #message = ( + # message = ( # "N.B.: This migration has been tested by the community over the last few months but has only been declared stable recently. If your server hosts critical services and if you are not too confident with debugging possible issues, we recommend you to wait a little bit more while we gather more feedback and polish things up. If on the other hand you are relatively confident with debugging small issues that may arise, you are encouraged to run this migration ;)! You can read about remaining known issues and feedback from the community here: https://forum.yunohost.org/t/12195\n\n" # + message - #) + # ) if problematic_apps: message += "\n\n" + m18n.n( @@ -285,7 +294,6 @@ class MyMigration(Migration): call_async_output(cmd, callbacks, shell=True) - def patch_yunohost_conflicts(self): # # This is a super dirty hack to remove the conflicts from yunohost's debian/control file @@ -305,6 +313,8 @@ class MyMigration(Migration): # We want to keep conflicting with apache/bind9 tho new_conflicts = "Conflicts: apache2, bind9" - command = f"sed -i /var/lib/dpkg/status -e 's@{conflicts}@{new_conflicts}@g'" + command = ( + f"sed -i /var/lib/dpkg/status -e 's@{conflicts}@{new_conflicts}@g'" + ) logger.debug(f"Running: {command}") os.system(command) From 0fc209acfbc6dbcb4d56ceb7618153ee34f1aac6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 13 Dec 2021 18:19:32 +0100 Subject: [PATCH 0995/1155] [fix] helpers logrorate: remove permission tweak .. code was not working as expected. To be re-addressed some day ... --- data/helpers.d/logrotate | 5 ----- 1 file changed, 5 deletions(-) diff --git a/data/helpers.d/logrotate b/data/helpers.d/logrotate index 80b761711..6f9726beb 100644 --- a/data/helpers.d/logrotate +++ b/data/helpers.d/logrotate @@ -90,11 +90,6 @@ $logfile { EOF 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) - - if ynh_user_exists --username="$app"; then - chown $app:$app "$logfile" - chmod o-rwx "$logfile" - fi } # Remove the app's logrotate config. From cc1230c5de25aa3c70cdb364bdad2278c2b6ef74 Mon Sep 17 00:00:00 2001 From: liimee Date: Mon, 15 Nov 2021 10:40:19 +0000 Subject: [PATCH 0996/1155] Translated using Weblate (Indonesian) Currently translated at 7.9% (56 of 705 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/id/ --- locales/id.json | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/locales/id.json b/locales/id.json index c9778dd5f..08e09fe8b 100644 --- a/locales/id.json +++ b/locales/id.json @@ -42,5 +42,17 @@ "app_start_install": "Memasang {app}...", "app_start_remove": "Menghapus {app}...", "app_manifest_install_ask_password": "Pilih kata sandi administrasi untuk aplikasi ini", - "app_upgrade_several_apps": "Aplikasi-aplikasi berikut akan diperbarui: {apps}" -} \ No newline at end of file + "app_upgrade_several_apps": "Aplikasi-aplikasi berikut akan diperbarui: {apps}", + "backup_app_failed": "Tidak dapat mencadangkan {app}", + "backup_archive_name_exists": "Arsip cadangan dengan nama ini sudah ada.", + "backup_created": "Cadangan dibuat", + "backup_creation_failed": "Tidak dapat membuat arsip cadangan", + "backup_delete_error": "Tidak dapat menghapus '{path}'", + "backup_deleted": "Cadangan dihapus", + "diagnosis_apps_issue": "Sebuah masalah ditemukan pada aplikasi {app}", + "backup_applying_method_tar": "Membuat arsip TAR cadangan...", + "backup_method_tar_finished": "Arsip TAR cadanagan dibuat", + "backup_nothings_done": "Tak ada yang harus disimpan", + "certmanager_cert_install_success": "Sertifikat Let's Encrypt sekarang sudah terpasang pada domain '{domain}'", + "backup_mount_archive_for_restore": "Menyiapkan arsip untuk pemulihan..." +} From fbf85fa0ba486262e107ac982684b1a7d3e4afba Mon Sep 17 00:00:00 2001 From: Ilya Date: Wed, 17 Nov 2021 20:03:22 +0000 Subject: [PATCH 0997/1155] Translated using Weblate (Russian) Currently translated at 14.4% (102 of 705 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ru/ --- locales/ru.json | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/locales/ru.json b/locales/ru.json index 58bd1846d..f38f703fc 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -73,5 +73,32 @@ "user_unknown": "Неизвестный пользователь: {user}", "yunohost_already_installed": "YunoHost уже установлен", "yunohost_configured": "Теперь YunoHost настроен", - "upgrading_packages": "Обновление пакетов..." -} \ No newline at end of file + "upgrading_packages": "Обновление пакетов...", + "app_requirements_unmeet": "Необходимые требования для {app} не выполнены, пакет {pkgname} ({version}) должен быть {spec}", + "app_make_default_location_already_used": "Невозможно сделать '{app}' приложением по умолчанию на домене, '{domain}' уже используется '{other_app}'", + "app_config_unable_to_apply": "Не удалось применить значения панели конфигурации.", + "app_config_unable_to_read": "Не удалось прочитать значения панели конфигурации.", + "app_install_failed": "Невозможно установить {app}: {error}", + "apps_catalog_init_success": "Система каталога приложений инициализирована!", + "backup_abstract_method": "Этот метод резервного копирования еще не реализован", + "backup_actually_backuping": "Создание резервного архива из собранных файлов...", + "backup_applying_method_custom": "Вызов пользовательского метода резервного копирования {method}'...", + "backup_archive_app_not_found": "Не удалось найти {app} в резервной копии", + "backup_applying_method_tar": "Создание резервной копии в TAR-архиве...", + "backup_archive_broken_link": "Не удалось получить доступ к резервной копии (неправильная ссылка {path})", + "apps_catalog_failed_to_download": "Невозможно загрузить каталог приложений {apps_catalog}: {error}", + "apps_catalog_obsolete_cache": "Кэш каталога приложений пуст или устарел.", + "backup_archive_cant_retrieve_info_json": "Не удалось загрузить информацию об архиве '{archive}'... info.json не может быть получен (или не является корректным json).", + "app_packaging_format_not_supported": "Это приложение не может быть установлено, поскольку его формат не поддерживается вашей версией YunoHost. Возможно, вам следует обновить систему.", + "app_restore_failed": "Не удалось восстановить {app}: {error}", + "app_restore_script_failed": "Произошла ошибка внутри сценария восстановления приложения", + "ask_user_domain": "Домен, используемый для адреса электронной почты пользователя и учетной записи XMPP", + "app_not_upgraded": "Не удалось обновить приложение '{failed_app}', и, как следствие, обновление следующих приложений было отменено: {apps}", + "app_start_backup": "Сбор файлов для резервного копирования {app}...", + "app_start_install": "Устанавливается {app}...", + "backup_app_failed": "Не удалось создать резервную копию {app}", + "backup_archive_name_exists": "Резервная копия с таким именем уже существует.", + "backup_archive_name_unknown": "Неизвестный локальный архив резервного копирования с именем '{name}'", + "backup_archive_open_failed": "Не удалось открыть архив резервной копии", + "backup_archive_corrupted": "Похоже, что архив резервной копии '{archive}' поврежден : {error}" +} From 59c27886211229f39d7991ef7e551a40cf0982e5 Mon Sep 17 00:00:00 2001 From: Ilya Date: Thu, 18 Nov 2021 19:32:14 +0000 Subject: [PATCH 0998/1155] Translated using Weblate (Russian) Currently translated at 24.1% (170 of 705 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ru/ --- locales/ru.json | 74 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 3 deletions(-) diff --git a/locales/ru.json b/locales/ru.json index f38f703fc..deb64c70a 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -35,7 +35,7 @@ "domain_dns_conf_is_just_a_recommendation": "Эта страница показывает вам *рекомендуемую* конфигурацию. Она *не* создаёт для вас конфигурацию DNS. Вы должны сами конфигурировать зону вашего DNS у вашего регистратора в соответствии с этой рекомендацией.", "good_practices_about_user_password": "Выберите пароль пользователя длиной не менее 8 символов, хотя рекомендуется использовать более длинные (например, парольную фразу) и / или использовать символы различного типа (прописные, строчные буквы, цифры и специальные символы).", "password_too_simple_3": "Пароль должен содержать не менее 8 символов и содержать цифры, заглавные и строчные буквы и специальные символы", - "upnp_enabled": "UPnP включён", + "upnp_enabled": "UPnP включен", "user_deleted": "Пользователь удалён", "ask_lastname": "Фамилия", "app_action_broke_system": "Это действие, по-видимому, нарушило эти важные службы: {services}", @@ -51,7 +51,7 @@ "ask_password": "Пароль", "app_remove_after_failed_install": "Удаление приложения после сбоя установки...", "app_upgrade_script_failed": "Внутри скрипта обновления приложения произошла ошибка", - "upnp_disabled": "UPnP отключён", + "upnp_disabled": "UPnP отключен", "app_manifest_install_ask_domain": "Выберите домен, в котором должно быть установлено это приложение", "app_manifest_install_ask_path": "Выберите URL путь (часть после домена), по которому должно быть установлено это приложение", "app_manifest_install_ask_admin": "Выберите пользователя администратора для этого приложения", @@ -100,5 +100,73 @@ "backup_archive_name_exists": "Резервная копия с таким именем уже существует.", "backup_archive_name_unknown": "Неизвестный локальный архив резервного копирования с именем '{name}'", "backup_archive_open_failed": "Не удалось открыть архив резервной копии", - "backup_archive_corrupted": "Похоже, что архив резервной копии '{archive}' поврежден : {error}" + "backup_archive_corrupted": "Похоже, что архив резервной копии '{archive}' поврежден : {error}", + "certmanager_cert_install_success_selfsigned": "Самоподписанный сертификат для домена '{domain}' установлен", + "backup_created": "Создана резервная копия", + "config_unknown_filter_key": "Ключ фильтра '{filter_key}' неверен.", + "config_validate_date": "Должна быть правильная дата в формате YYYY-MM-DD", + "config_validate_email": "Должен быть правильный email", + "config_validate_time": "Должно быть правильное время формата HH:MM", + "backup_ask_for_copying_if_needed": "Хотите ли вы временно выполнить резервное копирование с использованием {size}MB? (Этот способ используется, поскольку некоторые файлы не могут быть подготовлены более эффективным методом.)", + "backup_permission": "Разрешить резервное копирование для {app}", + "certmanager_domain_dns_ip_differs_from_public_ip": "DNS-записи для домена '{domain}' отличаются от IP этого сервера. Пожалуйста, проверьте категорию 'DNS-записи' (основные) в диагностике для получения дополнительной информации. Если вы недавно изменили свою A-запись, пожалуйста, подождите, пока она распространится (некоторые программы проверки распространения DNS доступны в интернете). (Если вы знаете, что делаете, используйте '--no-checks', чтобы отключить эти проверки.)", + "certmanager_domain_not_diagnosed_yet": "Для домена {domain} еще нет результатов диагностики. Пожалуйста, перезапустите диагностику для категорий 'DNS-записи' и 'Web', чтобы проверить, готов ли домен к Let's Encrypt. (Или, если вы знаете, что делаете, используйте '--no-checks', чтобы отключить эти проверки.)", + "config_validate_url": "Должна быть правильная ссылка", + "config_version_not_supported": "Версии конфигурационной панели '{version}' не поддерживаются.", + "confirm_app_install_danger": "ОПАСНО! Это приложение все еще является экспериментальным (если не сказать, что оно явно не работает)! Вам не следует устанавливать его, если вы не знаете, что делаете. Если это приложение не будет работать или сломает вашу систему, мы не будем оказывать техническую поддержку... Если вы все равно готовы рискнуть, введите '{answers}'", + "confirm_app_install_thirdparty": "ВАЖНО! Это приложение не входит в каталог приложений YunoHost. Установка сторонних приложений может нарушить целостность и безопасность вашей системы. Вам не следует устанавливать его, если вы не знаете, что делаете. ТЕХНИЧЕСКОЙ ПОДДЕРЖКИ НЕ БУДЕТ, если это приложение не будет работать или сломает вашу систему... Если вы все равно готовы рискнуть, введите '{answers}'", + "config_apply_failed": "Не удалось применить новую конфигурацию: {error}", + "config_cant_set_value_on_section": "Вы не можете установить одно значение на весь раздел конфигурации.", + "config_forbidden_keyword": "Ключевое слово '{keyword}' зарезервировано, вы не можете создать или использовать панель конфигурации с вопросом с таким id.", + "config_no_panel": "Панель конфигурации не найдена.", + "danger": "Опасно:", + "certmanager_warning_subdomain_dns_record": "Субдомен '{subdomain}' не соответствует IP-адресу основного домена '{domain}'. Некоторые функции будут недоступны, пока вы не исправите это и не перегенерируете сертификат.", + "app_argument_password_no_default": "Ошибка при парсинге аргумента пароля '{name}': аргумент пароля не может иметь значение по умолчанию по причинам безопасности", + "custom_app_url_required": "Вы должны указать URL для обновления вашего пользовательского приложения {app}", + "backup_creation_failed": "Не удалось создать резервную копию", + "backup_csv_addition_failed": "Не удалось добавить файлы для резервного копирования в CSV-файл", + "backup_csv_creation_failed": "Не удалось создать CSV-файл, необходимый для восстановления", + "backup_deleted": "Резервная копия удалена", + "backup_delete_error": "Не удалось удалить '{path}'", + "backup_method_copy_finished": "Создание копии бэкапа завершено", + "backup_method_tar_finished": "Создан резервный TAR-архив", + "backup_mount_archive_for_restore": "Подготовка архива для восстановления...", + "backup_method_custom_finished": "Пользовательский метод резервного копирования '{method}' завершен", + "backup_nothings_done": "Нечего сохранять", + "backup_output_directory_required": "Вы должны выбрать каталог для сохранения резервной копии", + "backup_system_part_failed": "Не удалось создать резервную копию системной части '{part}'", + "certmanager_cert_renew_success": "Обновлен сертификат Let's Encrypt для домена '{domain}'", + "certmanager_cert_signing_failed": "Не удалось подписать новый сертификат", + "diagnosis_apps_bad_quality": "В настоящее время это приложение отмечено как неработающее в каталоге приложений YunoHost. Это может быть временной проблемой, пока мэинтейнеры пытаются исправить проблему. Пока что обновление этого приложения отключено.", + "diagnosis_apps_broken": "В настоящее время это приложение отмечено как неработающее в каталоге приложений YunoHost. Это может быть временной проблемой, пока мэинтейнеры пытаются исправить проблему. Пока что обновления для этого приложения отключены.", + "diagnosis_apps_allgood": "Все установленные приложения соблюдают основные правила упаковки", + "diagnosis_apps_issue": "Обнаружена проблема для приложения {app}", + "diagnosis_apps_not_in_app_catalog": "Этого приложения нет в каталоге приложений YunoHost. Если оно было там раньше, а теперь удалено, вам стоит подумать об удалении этого приложения, так как оно больше не получит обновлений и может нарушить целостность и безопасность вашей системы.", + "diagnosis_apps_deprecated_practices": "Установленная версия этого приложения все еще использует некоторые устаревшие пакеты. Вам стоит подумать об обновлении.", + "additional_urls_already_added": "Этот URL '{url}' уже добавлен в дополнительный URL для разрешения '{permission}'", + "additional_urls_already_removed": "Этот URL '{url}' уже удален из дополнительных URL для разрешения '{permission}'", + "app_action_cannot_be_ran_because_required_services_down": "Для выполнения этого действия должны быть запущены следующие службы: {services}. Попробуйте перезапустить их, чтобы продолжить (и, возможно, выяснить, почему они не работают).", + "app_unsupported_remote_type": "Неподдерживаемый удаленный тип, используемый для приложения", + "backup_archive_system_part_not_available": "Системная часть '{часть}' недоступна в этой резервной копии", + "backup_output_directory_not_empty": "Вы должны выбрать пустой каталог для сохранения", + "backup_archive_writing_error": "Не удалось добавить файлы '{source}' (названные в архиве '{dest}') для резервного копирования в архив '{archive}'", + "backup_cant_mount_uncompress_archive": "Не удалось смонтировать несжатый архив как защищенный от записи", + "backup_copying_to_organize_the_archive": "Копирование {size}MB для создания архива", + "backup_couldnt_bind": "Не удалось связать {src} с {dest}.", + "backup_output_directory_forbidden": "Выберите другой каталог для сохранения. Резервные копии не могут быть созданы в подкаталогах /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var или /home/yunohost.backup/archives", + "backup_with_no_backup_script_for_app": "Приложение '{app}' не имеет сценария резервного копирования. Оно будет проигнорировано.", + "certmanager_attempt_to_renew_nonLE_cert": "Сертификат для домена '{domain}' не выпущен Let's Encrypt. Невозможно продлить его автоматически!", + "certmanager_attempt_to_renew_valid_cert": "Срок действия сертификата для домена '{domain}' НЕ истекает! (Вы можете использовать --force, если знаете, что делаете)", + "certmanager_cannot_read_cert": "При попытке открыть текущий сертификат для домена {домен} произошло что-то неправильное (файл: {file}), причина: {reason}", + "certmanager_cert_install_success": "Сертификат Let's Encrypt для домена '{domain}' установлен", + "certmanager_domain_cert_not_selfsigned": "Сертификат для домена {domain} не самоподписанный. Вы уверены, что хотите заменить его? (Для этого используйте '--force'.)", + "certmanager_certificate_fetching_or_enabling_failed": "Попытка использовать новый сертификат для {domain} не сработала...", + "certmanager_domain_http_not_working": "Похоже, домен {domain} не доступен через HTTP. Пожалуйста, проверьте категорию 'Web' в диагностике для получения дополнительной информации. (Если вы знаете, что делаете, используйте '--no-checks', чтобы отключить эти проверки.)", + "certmanager_hit_rate_limit": "Для этого набора доменов {domain} в последнее время было выпущено слишком много сертификатов. Пожалуйста, повторите попытку позже. См. https://letsencrypt.org/docs/rate-limits/ для получения более подробной информации", + "certmanager_no_cert_file": "Не удалось прочитать файл сертификата для домена {domain} (файл: {file})", + "confirm_app_install_warning": "Предупреждение: Это приложение может работать, но пока еще недостаточно интегрировано в YunoHost. Некоторые функции, такие как единая регистрация и резервное копирование/восстановление, могут быть недоступны. Все равно устанавливать? [{answers}] ", + "yunohost_not_installed": "YunoHost установлен неправильно. Пожалуйста, запустите 'yunohost tools postinstall'", + "backup_cleaning_failed": "Не удалось очистить временную папку резервного копирования", + "certmanager_attempt_to_replace_valid_cert": "Вы пытаетесь перезаписать хороший и действительный сертификат для домена {domain}! (Используйте --force для обхода)", + "backup_create_size_estimation": "Архив будет содержать около {размер} данных." } From c27f13230c7e340f1db238e5784506694f3c4658 Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Fri, 19 Nov 2021 13:08:44 +0000 Subject: [PATCH 0999/1155] Translated using Weblate (German) Currently translated at 91.0% (642 of 705 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/locales/de.json b/locales/de.json index e491c5ac2..4129e7e72 100644 --- a/locales/de.json +++ b/locales/de.json @@ -638,5 +638,16 @@ "diagnosis_apps_issue": "Ein Problem für die App {app} ist aufgetreten", "config_validate_time": "Sollte eine zulässige Zeit wie HH:MM sein", "config_validate_url": "Sollte eine zulässige web URL sein", - "config_version_not_supported": "Konfigurationspanel Versionen '{version}' sind nicht unterstützt." -} \ No newline at end of file + "config_version_not_supported": "Konfigurationspanel Versionen '{version}' sind nicht unterstützt.", + "diagnosis_apps_allgood": "Alle installierten Apps berücksichtigen die grundlegenden Paketierungspraktiken", + "diagnosis_apps_broken": "Diese App ist im YunoHost-Applikationskatalog momentan als defekt gekennzeichnet. Es könnte sich dabei um einen vorübergehendes Problem handeln. Während der Betreuer versucht das Problem zu beheben, ist die Upgrade-Funktion für diese App gesperrt.", + "diagnosis_apps_not_in_app_catalog": "Diese App fehlt im Applikationskatalog von YunoHost oder wird in diesem nicht mehr angezeigt. Sie müssen im Betracht ziehen, sie zu deinstallieren, weil sie keine Aktualisierungen mehr erhält und die Integrität und die Sicherheit Ihres Systems kompromittieren könnte.", + "diagnosis_apps_outdated_ynh_requirement": "Die installierte Version dieser App erfordert nur YunoHost >=2.x, was darauf hinweist, dass die App nicht mehr auf dem Stand der guten Paketierungspraktiken und der empfohlenen Helper ist. Sie sollten wirklich in Betracht ziehen, sie zu aktualisieren.", + "diagnosis_description_apps": "Applikationen", + "config_cant_set_value_on_section": "Sie können nicht einen einzelnen Wert auf einen gesamten Konfigurationsbereich anwenden.", + "diagnosis_apps_deprecated_practices": "Die installierte Version dieser App verwendet immer noch gewisse veraltete Paketierungspraktiken. Sie sollten die App wirklich aktualisieren.", + "app_config_unable_to_apply": "Konnte die Werte des Konfigurations-Panels nicht anwenden.", + "app_config_unable_to_read": "Konnte die Werte des Konfigurations-Panels nicht auslesen.", + "config_unknown_filter_key": "Der Filterschlüssel '{filter_key}' ist inkorrekt.", + "diagnosis_dns_specialusedomain": "Die Domäne {domain} basiert auf einer Top-Level-Domain (TLD) für spezielle Zwecke wie .local oder .test und deshalb wird von ihr nicht erwartet, dass sie echte DNS-Einträge besitzt." +} From da1b0aa041de4c5d43fc280dbf0c594902df4bb8 Mon Sep 17 00:00:00 2001 From: Ilya Date: Fri, 19 Nov 2021 19:47:40 +0000 Subject: [PATCH 1000/1155] Translated using Weblate (Russian) Currently translated at 26.9% (190 of 705 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ru/ --- locales/ru.json | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/locales/ru.json b/locales/ru.json index deb64c70a..8fbc3f91b 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -168,5 +168,25 @@ "yunohost_not_installed": "YunoHost установлен неправильно. Пожалуйста, запустите 'yunohost tools postinstall'", "backup_cleaning_failed": "Не удалось очистить временную папку резервного копирования", "certmanager_attempt_to_replace_valid_cert": "Вы пытаетесь перезаписать хороший и действительный сертификат для домена {domain}! (Используйте --force для обхода)", - "backup_create_size_estimation": "Архив будет содержать около {размер} данных." + "backup_create_size_estimation": "Архив будет содержать около {размер} данных.", + "diagnosis_description_regenconf": "Конфигурации системы", + "diagnosis_description_services": "Проверка статусов сервисов", + "config_validate_color": "Должен быть правильный hex цвета RGB", + "diagnosis_basesystem_hardware": "Аппаратная архитектура сервера – {virt} {arch}", + "certmanager_acme_not_configured_for_domain": "Задача ACME не может быть запущена для {domain} прямо сейчас, потому что в его nginx conf отсутствует соответствующий фрагмент кода... Пожалуйста, убедитесь, что конфигурация вашего nginx обновлена, используя `yunohost tools regen-conf nginx --dry-run --with-diff`.", + "diagnosis_basesystem_ynh_single_version": "{package} версия: {version} ({repo})", + "diagnosis_description_mail": "Email", + "diagnosis_basesystem_kernel": "Версия ядра Linux на сервере {kernel_version}", + "diagnosis_description_apps": "Приложения", + "diagnosis_diskusage_low": "В хранилище {mountpoint} (на устройстве {device}) осталось {free} ({free_percent}%) места (из {total}). Будьте осторожны.", + "diagnosis_description_dnsrecords": "DNS записи", + "diagnosis_description_ip": "Интернет-соединение", + "diagnosis_description_basesystem": "Основная система", + "diagnosis_description_web": "Web", + "diagnosis_basesystem_host": "На сервере запущен Debian {debian_version}", + "diagnosis_dns_bad_conf": "Некоторые записи DNS для домена {domain} (категория {category}) отсутствуют или неверны", + "diagnosis_description_systemresources": "Системные ресурсы", + "backup_with_no_restore_script_for_app": "{app} не имеет сценария восстановления, вы не сможете автоматически восстановить это приложение из резервной копии.", + "diagnosis_description_ports": "Открытые порты", + "diagnosis_basesystem_hardware_model": "Модель сервера {model}" } From 10699e5de04a1ef1dde71939a8f105421474e69e Mon Sep 17 00:00:00 2001 From: Boudewijn Date: Wed, 24 Nov 2021 12:48:07 +0000 Subject: [PATCH 1001/1155] Translated using Weblate (Dutch) Currently translated at 11.4% (81 of 705 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/nl/ --- locales/nl.json | 48 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/locales/nl.json b/locales/nl.json index f5923fa9a..038d18283 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -1,19 +1,19 @@ { "action_invalid": "Ongeldige actie '{action}'", "admin_password": "Administrator wachtwoord", - "admin_password_changed": "Het administratie wachtwoord werd gewijzigd", + "admin_password_changed": "Het administratie wachtwoord is gewijzigd", "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_extraction_failed": "Het lukt niet om de installatiebestanden uit te pakken", "app_id_invalid": "Ongeldige app-id", "app_install_files_invalid": "Deze bestanden kunnen niet worden geïnstalleerd", - "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_not_installed": "Het lukte niet om {app} te vinden in de lijst met geïnstalleerde apps: {all_apps}", + "app_removed": "{app} is verwijderd", + "app_sources_fetch_failed": "Het is niet gelukt bronbestanden op te halen, klopt de URL?", "app_unknown": "Onbekende app", - "app_upgrade_failed": "Kan app {app} niet updaten", - "app_upgraded": "{app} succesvol geüpgraded", + "app_upgrade_failed": "Het is niet gelukt app {app} bij te werken: {error}", + "app_upgraded": "{app} is bijgewerkt", "ask_firstname": "Voornaam", "ask_lastname": "Achternaam", "ask_new_admin_password": "Nieuw administratorwachtwoord", @@ -69,8 +69,8 @@ "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}'. Het moet een van de volgende keuzes zijn {choices}", + "admin_password_change_failed": "Wachtwoord wijzigen is niet gelukt", + "app_argument_choice_invalid": "Kiel een geldige waarde voor argument '{name}'; {value}' komt niet voor in de keuzelijst {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...", @@ -98,18 +98,36 @@ "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_path": "Kies het URL-pad (achter het domein) waar deze app geïnstalleerd moet worden", "app_manifest_install_ask_admin": "Kies een administrator voor deze app", "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_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}'. Een mogelijke oplossing is om een nieuw subdomein toe te voegen, speciaal voor deze app.", "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}", "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_install": "Bezig met installeren van {app}...", + "app_start_remove": "Bezig met verwijderen van {app}...", "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 + "app_upgrade_several_apps": "De volgende apps zullen worden geüpgraded: {apps}", + "app_upgrade_script_failed": "Er is een fout opgetreden in het upgradescript van de app", + "apps_already_up_to_date": "Alle apps zijn al bijgewerkt met de nieuwste versie", + "app_restore_script_failed": "Er ging iets mis in het helstelscript van de app", + "app_change_url_identical_domains": "De oude en nieuwe domeinnaam/url_path zijn identiek ('{domain}{path}'), er is niets te doen.", + "app_already_up_to_date": "{app} is al de meest actuele versie", + "app_action_broke_system": "Deze actie lijkt de volgende belangrijke services te hebben kapotgemaakt: {services}", + "app_config_unable_to_apply": "De waarden in het configuratiescherm konden niet toegepast worden.", + "app_config_unable_to_read": "Het is niet gelukt de waarden van het configuratiescherm te lezen.", + "app_argument_password_no_default": "Foutmelding tijdens het lezen van wachtwoordargument '{name}': het wachtwoordargument mag om veiligheidsredenen geen standaardwaarde hebben.", + "app_already_installed_cant_change_url": "Deze app is al geïnstalleerd. De URL kan niet veranderd worden met deze functie. Probeer of dat lukt via `app changeurl`.", + "apps_catalog_init_success": "De app-catalogus is succesvol geinitieerd!", + "apps_catalog_failed_to_download": "Het is niet gelukt de {apps_catalog} app-catalogus te downloaden: {error}", + "app_packaging_format_not_supported": "Deze app kon niet geinstalleerd worden, omdat het pakketformaat niet ondersteund wordt door je Yunohost. Probeer of je Yunohost bijgewerkt kan worden.", + "additional_urls_already_added": "Extra URL '{url:s}' is al toegevoegd in de extra URL voor privilege '{permission:s}'", + "additional_urls_already_removed": "Extra URL '{url}' is al verwijderd in de extra URL voor privilege '{permission}'", + "app_label_deprecated": "Dit commando is vervallen. Gebruik alsjeblieft het nieuwe commando 'yunohost user permission update' om het label van de app te beheren.", + "app_change_url_no_script": "De app '{app_name}' ondersteunt nog geen URL-aanpassingen. Misschien wel na een upgrade.", + "app_upgrade_some_app_failed": "Sommige apps konden niet worden bijgewerkt" +} From 4d8ca7aaad63520ed604d0bd4889287945a6fb20 Mon Sep 17 00:00:00 2001 From: Tymofii Lytvynenko Date: Thu, 25 Nov 2021 00:51:47 +0000 Subject: [PATCH 1002/1155] Translated using Weblate (Ukrainian) Currently translated at 100.0% (705 of 705 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 ae59b0274..f45ad8c66 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -376,7 +376,7 @@ "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})", @@ -704,4 +704,4 @@ "ldap_attribute_already_exists": "Атрибут LDAP '{attribute}' вже існує зі значенням '{value}'", "domain_dns_push_already_up_to_date": "Записи вже оновлені, нічого не потрібно робити.", "domain_unknown": "Домен '{domain}' є невідомим" -} \ No newline at end of file +} From 77d74eba06f1f0f65c3321db2dbd5d2238600569 Mon Sep 17 00:00:00 2001 From: Valentin von Guttenberg Date: Mon, 29 Nov 2021 15:02:06 +0000 Subject: [PATCH 1003/1155] Translated using Weblate (German) Currently translated at 100.0% (705 of 705 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 82 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 68 insertions(+), 14 deletions(-) diff --git a/locales/de.json b/locales/de.json index 4129e7e72..d94161151 100644 --- a/locales/de.json +++ b/locales/de.json @@ -4,7 +4,7 @@ "admin_password_change_failed": "Ändern des Passworts nicht möglich", "admin_password_changed": "Das Administrator-Kennwort wurde geändert", "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_choice_invalid": "Wähle einen gültigen Wert für das Argument '{name}': '{value}' ist nicht unter den verfügbaren Auswahlmöglichkeiten ({choices})", "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", @@ -127,7 +127,7 @@ "user_creation_failed": "Benutzer:in konnte nicht erstellt werden {user}: {error}", "user_deleted": "Benutzer:in gelöscht", "user_deletion_failed": "Benutzer:in konnte nicht gelöscht werden {user}: {error}", - "user_home_creation_failed": "Persönlicher Ordner des Benutzers konnte nicht erstellt werden", + "user_home_creation_failed": "Persönlicher Ordner '{home}' des/der Benutzers:in konnte nicht erstellt werden", "user_unknown": "Unbekannte:r Benutzer:in : {user}", "user_update_failed": "Benutzer:in konnte nicht aktualisiert werden {user}: {error}", "user_updated": "Benutzerinformationen wurden aktualisiert", @@ -279,7 +279,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_everything_ok": "Alles schaut gut aus für {category}!", + "diagnosis_everything_ok": "Alles sieht OK 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}'", @@ -288,7 +288,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": "(Der Cache für die Diagnose {category} ist immer noch gültig. Es wird momentan keine neue Diagnose durchgeführt!)", + "diagnosis_cache_still_valid": "(Cache noch gültig für {category} Diagnose. Es wird 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?", @@ -317,7 +317,7 @@ "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 der Domäne sollte von Yunohost kontrolliert werden. Andernfalls, kannst du mit yunohost dyndns update --force ein Update erzwingen.", + "diagnosis_dns_try_dyndns_update_force": "Die DNS-Konfiguration dieser Domäne sollte automatisch von YunoHost verwaltet werden. Andernfalls könntest Du mittels yunohost dyndns update --force ein Update erzwingen.", "diagnosis_dns_point_to_doc": "Bitte schaue in die Dokumentation unter https://yunohost.org/dns_config wenn du hilfe bei der Konfiguration der DNS Einträge brauchst.", "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}", @@ -349,7 +349,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}' 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} scheinen keine Informationen über das Ablaufdatum zu enthalten.", + "diagnosis_domain_expiration_not_found_details": "Die WHOIS-Informationen für die Domäne {domain} scheinen keine Informationen über das Ablaufdatum zu enthalten. Stimmt das?", "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}.", @@ -375,7 +375,7 @@ "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: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", + "app_manifest_install_ask_path": "Wählen Sie den URL-Pfad (nach der Domäne), unter dem die Applikation installiert werden soll", "diagnosis_mail_blacklist_listed_by": "Ihre IP-Adresse oder Domäne {item} ist auf der Blacklist auf {blacklist_name}", "diagnosis_mail_blacklist_ok": "Die IP-Adressen und die Domänen, welche von diesem Server verwendet werden, scheinen nicht auf einer Blacklist zu sein", "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Aktueller Reverse-DNS-Eintrag: {rdns_domain}
Erwarteter Wert: {ehlo_domain}", @@ -428,7 +428,7 @@ "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_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_http_bad_status_code": "Es sieht so aus als ob ein anderes Gerät (vielleicht dein Router/Modem) anstelle deines Servers antwortet.
1. Der häufigste Grund hierfür ist, dass Port 80 (und 443) nicht korrekt zu deinem Server weiterleiten.
2. Bei komplexeren Setups: prüfe ob deine Firewall oder Reverse-Proxy die Verbindung stören.", "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.", @@ -606,7 +606,7 @@ "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", + "show_tile_cant_be_enabled_for_regex": "Du kannst 'show_tile' momentan 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...", @@ -628,11 +628,11 @@ "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:", - "diagnosis_apps_bad_quality": "Diese App ist im YunoHost App Katalog momentan als kaputt gekennzeichnet. Dies mag ein temporäres Problem darstellen, das von den Maintainern versucht wird zu beheben. In der Zwischenzeit ist das Upgrade dieser App nicht möglich.", - "config_apply_failed": "Die neue Konfiguration umzusetzen ist fehlgeschlagen: {error}", + "diagnosis_apps_bad_quality": "Diese App ist im YunoHost-Applikationskatalog momentan als defekt gekennzeichnet. Es könnte sich dabei um einen vorübergehendes Problem handeln. Während der/die Betreuer:in versucht das Problem zu beheben, ist die Upgrade-Funktion für diese App gesperrt.", + "config_apply_failed": "Anwenden der neuen Konfiguration fehlgeschlagen: {error}", "config_validate_date": "Sollte ein zulässiges Datum in folgendem Format sein: YYYY-MM-DD", "config_validate_email": "Sollte eine zulässige eMail sein", - "config_forbidden_keyword": "Das Keyword '{keyword}' ist reserviert. Mit dieser id kannst du keine Konfigurationspanel erstellen", + "config_forbidden_keyword": "Das Schlüsselwort '{keyword}' ist reserviert. Du kannst kein Konfigurationspanel mit einer Frage erstellen, die diese ID verwendet.", "config_no_panel": "Kein Konfigurationspanel gefunden.", "config_validate_color": "Sollte eine zulässige RGB hexadezimal Farbe sein", "diagnosis_apps_issue": "Ein Problem für die App {app} ist aufgetreten", @@ -640,7 +640,7 @@ "config_validate_url": "Sollte eine zulässige web URL sein", "config_version_not_supported": "Konfigurationspanel Versionen '{version}' sind nicht unterstützt.", "diagnosis_apps_allgood": "Alle installierten Apps berücksichtigen die grundlegenden Paketierungspraktiken", - "diagnosis_apps_broken": "Diese App ist im YunoHost-Applikationskatalog momentan als defekt gekennzeichnet. Es könnte sich dabei um einen vorübergehendes Problem handeln. Während der Betreuer versucht das Problem zu beheben, ist die Upgrade-Funktion für diese App gesperrt.", + "diagnosis_apps_broken": "Diese App ist im YunoHost-Applikationskatalog momentan als defekt gekennzeichnet. Es könnte sich dabei um einen vorübergehendes Problem handeln. Während der/die Betreuer:in versucht das Problem zu beheben, ist die Upgrade-Funktion für diese App gesperrt.", "diagnosis_apps_not_in_app_catalog": "Diese App fehlt im Applikationskatalog von YunoHost oder wird in diesem nicht mehr angezeigt. Sie müssen im Betracht ziehen, sie zu deinstallieren, weil sie keine Aktualisierungen mehr erhält und die Integrität und die Sicherheit Ihres Systems kompromittieren könnte.", "diagnosis_apps_outdated_ynh_requirement": "Die installierte Version dieser App erfordert nur YunoHost >=2.x, was darauf hinweist, dass die App nicht mehr auf dem Stand der guten Paketierungspraktiken und der empfohlenen Helper ist. Sie sollten wirklich in Betracht ziehen, sie zu aktualisieren.", "diagnosis_description_apps": "Applikationen", @@ -649,5 +649,59 @@ "app_config_unable_to_apply": "Konnte die Werte des Konfigurations-Panels nicht anwenden.", "app_config_unable_to_read": "Konnte die Werte des Konfigurations-Panels nicht auslesen.", "config_unknown_filter_key": "Der Filterschlüssel '{filter_key}' ist inkorrekt.", - "diagnosis_dns_specialusedomain": "Die Domäne {domain} basiert auf einer Top-Level-Domain (TLD) für spezielle Zwecke wie .local oder .test und deshalb wird von ihr nicht erwartet, dass sie echte DNS-Einträge besitzt." + "diagnosis_dns_specialusedomain": "Die Domäne {domain} basiert auf einer Top-Level-Domain (TLD) für spezielle Zwecke wie .local oder .test und deshalb wird von ihr nicht erwartet, dass sie echte DNS-Einträge besitzt.", + "ldap_server_down": "LDAP-Server kann nicht erreicht werden", + "diagnosis_http_special_use_tld": "Die Domäne {domain} basiert auf einer Top-Level-Domäne (TLD) für besondere Zwecke wie .local oder .test und wird daher voraussichtlich nicht außerhalb des lokalen Netzwerks zugänglich sein.", + "domain_dns_push_managed_in_parent_domain": "Die automatische DNS-Konfiguration wird von der übergeordneten Domäne {parent_domain} verwaltet.", + "domain_dns_push_already_up_to_date": "Die Einträge sind auf dem neuesten Stand, es gibt nichts zu tun.", + "domain_config_auth_token": "Authentifizierungstoken", + "domain_config_auth_key": "Authentifizierungsschlüssel", + "domain_config_auth_secret": "Authentifizierungsgeheimnis", + "domain_config_api_protocol": "API-Protokoll", + "domain_unknown": "Domäne '{domain}' unbekannt", + "ldap_server_is_down_restart_it": "Der LDAP-Dienst ist nicht erreichbar, versuche ihn neu zu starten...", + "user_import_bad_file": "Deine CSV-Datei ist nicht korrekt formatiert und wird daher ignoriert, um einen möglichen Datenverlust zu vermeiden", + "global_settings_setting_security_experimental_enabled": "Aktiviere experimentelle Sicherheitsfunktionen (nur aktivieren, wenn Du weißt was Du tust!)", + "global_settings_setting_security_nginx_redirect_to_https": "HTTP-Anfragen standardmäßig auf HTTPs umleiten (NICHT AUSSCHALTEN, sofern Du nicht weißt was Du tust!)", + "user_import_missing_columns": "Die folgenden Spalten fehlen: {columns}", + "user_import_nothing_to_do": "Es muss kein:e Benutzer:in importiert werden", + "user_import_partial_failed": "Der Import von Benutzer:innen ist teilweise fehlgeschlagen", + "user_import_bad_line": "Ungültige Zeile {line}: {details}", + "other_available_options": "… und {n} weitere verfügbare Optionen, die nicht angezeigt werden", + "domain_dns_conf_special_use_tld": "Diese Domäne basiert auf einer Top-Level-Domäne (TLD) für besondere Zwecke wie .local oder .test und wird daher vermutlich keine eigenen DNS-Einträge haben.", + "domain_dns_registrar_managed_in_parent_domain": "Diese Domäne ist eine Unterdomäne von {parent_domain_link}. Die Konfiguration des DNS-Registrars sollte auf der Konfigurationsseite von {parent_domain} verwaltet werden.", + "domain_dns_registrar_not_supported": "YunoHost konnte den Registrar, der diese Domäne verwaltet, nicht automatisch erkennen. Du solltest die DNS-Einträge, wie unter https://yunohost.org/dns beschrieben, manuell konfigurieren.", + "domain_dns_registrar_supported": "YunoHost hat automatisch erkannt, dass diese Domäne von dem Registrar **{registrar}** verwaltet wird. Wenn Du möchtest, konfiguriert YunoHost diese DNS-Zone automatisch, wenn Du die entsprechenden API-Zugangsdaten zur Verfügung stellst. Auf dieser Seite erfährst Du, wie Du deine API-Anmeldeinformationen erhältst: https://yunohost.org/registar_api_{registrar}. (Du kannst deine DNS-Einträge auch, wie unter https://yunohost.org/dns beschrieben, manuell konfigurieren)", + "service_not_reloading_because_conf_broken": "Der Dienst '{Name}' wird nicht neu geladen/gestartet, da seine Konfiguration fehlerhaft ist: {Fehler}", + "user_import_failed": "Der Import von Benutzer:innen ist komplett fehlgeschlagen", + "domain_dns_push_failed_to_list": "Auflistung der aktuellen Einträge über die API des Registrars fehlgeschlagen: {error}", + "domain_dns_pushing": "DNS-Einträge übertragen…", + "domain_dns_push_record_failed": "{action} für Eintrag {type}/{name} fehlgeschlagen: {error}", + "domain_dns_push_success": "DNS-Einträge aktualisiert!", + "domain_dns_push_failed": "Die Aktualisierung der DNS-Einträge ist leider gescheitert.", + "domain_dns_push_partial_failure": "DNS-Einträge teilweise aktualisiert: einige Warnungen/Fehler wurden gemeldet.", + "domain_config_features_disclaimer": "Bisher hat das Aktivieren/Deaktivieren von Mail- oder XMPP-Funktionen nur Auswirkungen auf die empfohlene und automatische DNS-Konfiguration, nicht auf die Systemkonfigurationen!", + "domain_config_mail_in": "Eingehende E-Mails", + "domain_config_mail_out": "Ausgehende E-Mails", + "domain_config_xmpp": "Instant Messaging (XMPP)", + "log_app_config_set": "Konfiguration auf die Applikation '{}' anwenden", + "log_user_import": "Benutzer:innen importieren", + "diagnosis_high_number_auth_failures": "In letzter Zeit gab es eine verdächtig hohe Anzahl von Authentifizierungsfehlern. Stelle sicher, dass fail2ban läuft und korrekt konfiguriert ist, oder verwende einen benutzerdefinierten Port für SSH, wie unter https://yunohost.org/security beschrieben.", + "domain_dns_registrar_yunohost": "Dies ist eine nohost.me / nohost.st / ynh.fr Domäne, ihre DNS-Konfiguration wird daher automatisch von YunoHost ohne weitere Konfiguration übernommen. (siehe Befehl 'yunohost dyndns update')", + "domain_config_auth_entrypoint": "API-Einstiegspunkt", + "domain_config_auth_application_key": "Anwendungsschlüssel", + "domain_config_auth_application_secret": "Geheimer Anwendungsschlüssel", + "domain_config_auth_consumer_key": "Consumer-Schlüssel", + "invalid_number_min": "Muss größer sein als {min}", + "invalid_number_max": "Muss kleiner sein als {max}", + "invalid_password": "Ungültiges Passwort", + "ldap_attribute_already_exists": "LDAP-Attribut '{attribute}' existiert bereits mit dem Wert '{value}'", + "user_import_success": "Benutzer:innen erfolgreich importiert", + "domain_registrar_is_not_configured": "Der DNS-Registrar ist noch nicht für die Domäne '{domain}' konfiguriert.", + "domain_dns_push_not_applicable": "Die automatische DNS-Konfiguration ist nicht auf die Domäne {domain} anwendbar. Konfiguriere die DNS-Einträge manuell, wie unter https://yunohost.org/dns_config beschrieben.", + "domain_dns_registrar_experimental": "Bislang wurde die Schnittstelle zur API von **{registrar}** noch nicht außreichend von der YunoHost-Community getestet und geprüft. Der Support ist **sehr experimentell** – sei vorsichtig!", + "domain_dns_push_failed_to_authenticate": "Die Authentifizierung bei der API des Registrars für die Domäne '{domain}' ist fehlgeschlagen. Wahrscheinlich sind die Anmeldedaten falsch? (Fehler: {error})", + "log_domain_config_set": "Konfiguration für die Domäne '{}' aktualisieren", + "log_domain_dns_push": "DNS-Einträge für die Domäne '{}' übertragen", + "service_description_yunomdns": "Ermöglicht es dir, deinen Server über 'yunohost.local' in deinem lokalen Netzwerk zu erreichen" } From b6250c0094aba4aac8f1a51a517f9314b6cd8519 Mon Sep 17 00:00:00 2001 From: Colin Wawrik Date: Mon, 29 Nov 2021 18:25:18 +0000 Subject: [PATCH 1004/1155] Translated using Weblate (German) Currently translated at 100.0% (705 of 705 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 d94161151..f1b7ec2cc 100644 --- a/locales/de.json +++ b/locales/de.json @@ -427,7 +427,7 @@ "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": "Die Konfiguration von Nginx scheint manuell geändert worden zu sein. Dies hindert YunoHost daran festzustellen, ob es über HTTP erreichbar ist.", "diagnosis_http_bad_status_code": "Es sieht so aus als ob ein anderes Gerät (vielleicht dein Router/Modem) anstelle deines Servers antwortet.
1. Der häufigste Grund hierfür ist, dass Port 80 (und 443) nicht korrekt zu deinem Server weiterleiten.
2. Bei komplexeren Setups: prüfe ob deine Firewall oder Reverse-Proxy die Verbindung stören.", "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.", From c3ef8ad3c7b6aab121b80a5726c643ea6b9c2532 Mon Sep 17 00:00:00 2001 From: Valentin von Guttenberg Date: Mon, 29 Nov 2021 18:26:05 +0000 Subject: [PATCH 1005/1155] Translated using Weblate (German) Currently translated at 100.0% (705 of 705 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 f1b7ec2cc..aa0413ba8 100644 --- a/locales/de.json +++ b/locales/de.json @@ -427,7 +427,7 @@ "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": "Die Konfiguration von Nginx scheint manuell geändert worden zu sein. Dies hindert YunoHost daran festzustellen, ob es über HTTP erreichbar ist.", + "diagnosis_http_nginx_conf_not_up_to_date": "Die Konfiguration von Nginx scheint für diese Domäne manuell geändert worden zu sein. Dies hindert YunoHost daran festzustellen, ob es über HTTP erreichbar ist.", "diagnosis_http_bad_status_code": "Es sieht so aus als ob ein anderes Gerät (vielleicht dein Router/Modem) anstelle deines Servers antwortet.
1. Der häufigste Grund hierfür ist, dass Port 80 (und 443) nicht korrekt zu deinem Server weiterleiten.
2. Bei komplexeren Setups: prüfe ob deine Firewall oder Reverse-Proxy die Verbindung stören.", "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.", From 7c8ded3275e4702d97f2c30f78c47eddfb11d050 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Fri, 10 Dec 2021 11:16:05 +0000 Subject: [PATCH 1006/1155] Translated using Weblate (French) Currently translated at 100.0% (719 of 719 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 07bdcfad3..e6827df56 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -416,7 +416,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
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}%) d'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", @@ -442,9 +442,9 @@ "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_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_diskusage_verylow": "L'espace de stockage {mountpoint} (sur l'appareil {device}) ne dispose que de {free} ({free_percent}%) d'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}%) d'espace restant (sur {total}). Faites attention.", + "diagnosis_ram_verylow": "Le système ne dispose plus que de {available} ({available_percent}%) de RAM ! (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.", @@ -547,7 +547,7 @@ "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_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}", @@ -703,5 +703,19 @@ "domain_dns_conf_special_use_tld": "Ce domaine est basé sur un domaine de premier niveau (TLD) à usage spécial tel que .local ou .test et ne devrait donc pas avoir d'enregistrements DNS réels.", "other_available_options": "... et {n} autres options disponibles non affichées", "domain_config_auth_consumer_key": "Consumer key", - "domain_unknown": "Domaine '{domain}' inconnu" -} \ No newline at end of file + "domain_unknown": "Domaine '{domain}' inconnu", + "migration_0021_start": "Démarrage de la migration vers Bullseye", + "migration_0021_patching_sources_list": "Mise à jour du fichier sources.lists...", + "migration_0021_main_upgrade": "Démarrage de la mise à niveau générale...", + "migration_0021_still_on_buster_after_main_upgrade": "Quelque chose s'est mal passé lors de la mise à niveau, le système semble toujours être sous Debian Buster", + "migration_0021_yunohost_upgrade": "Démarrage de la mise à jour du noyau YunoHost...", + "migration_0021_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_0021_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 Bullseye.", + "migration_0021_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_0021_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_0021_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_0021_cleaning_up": "Nettoyage du cache et des paquets qui ne sont plus nécessaires...", + "migration_0021_patch_yunohost_conflicts": "Application du correctif pour contourner le problème de conflit...", + "migration_0021_not_buster": "La distribution Debian actuelle n'est pas Buster !", + "migration_description_0021_migrate_to_bullseye": "Mise à niveau du système vers Debian Bullseye et YunoHost 11.x" +} From 05c69ed9fac93d285e8313c83b89347dae4b90bc Mon Sep 17 00:00:00 2001 From: Tymofii-Lytvynenko Date: Fri, 10 Dec 2021 23:55:58 +0000 Subject: [PATCH 1007/1155] Translated using Weblate (Ukrainian) Currently translated at 100.0% (719 of 719 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/uk/ --- locales/uk.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/locales/uk.json b/locales/uk.json index f45ad8c66..25fa1d551 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -703,5 +703,19 @@ "domain_dns_pushing": "Передання записів DNS...", "ldap_attribute_already_exists": "Атрибут LDAP '{attribute}' вже існує зі значенням '{value}'", "domain_dns_push_already_up_to_date": "Записи вже оновлені, нічого не потрібно робити.", - "domain_unknown": "Домен '{domain}' є невідомим" + "domain_unknown": "Домен '{domain}' є невідомим", + "migration_0021_start": "Початок міграції на Bullseye", + "migration_0021_patching_sources_list": "Виправлення sources.lists...", + "migration_0021_main_upgrade": "Початок основного оновлення...", + "migration_0021_yunohost_upgrade": "Початок оновлення ядра YunoHost...", + "migration_0021_not_buster": "Поточний дистрибутив Debian не є Buster!", + "migration_0021_problematic_apps_warning": "Зверніть увагу, що були виявлені наступні, ймовірно проблемні встановлені застосунки. Схоже, що вони не були встановлені з каталогу застосунків YunoHost або не зазначені як «робочі». Отже, не можна гарантувати, що вони будуть працювати після оновлення: {problematic_apps}", + "migration_0021_modified_files": "Зверніть увагу, що такі файли були змінені вручну і можуть бути перезаписані після оновлення: {manually_modified_files}", + "migration_0021_cleaning_up": "Очищення кеш-пам'яті і пакетів, які більше не потрібні...", + "migration_0021_patch_yunohost_conflicts": "Застосування виправлення для вирішення проблеми конфлікту...", + "migration_0021_still_on_buster_after_main_upgrade": "Щось пішло не так під час основного оновлення, здається, що система все ще працює на Debian Buster", + "migration_0021_not_enough_free_space": "Вільного місця в /var/ досить мало! У вас повинно бути не менше 1 ГБ вільного місця, щоб запустити цю міграцію.", + "migration_0021_system_not_fully_up_to_date": "Ваша система не повністю оновлена. Будь ласка, виконайте регулярне оновлення перед запуском міграції на Bullseye.", + "migration_0021_general_warning": "Будь ласка, зверніть увагу, що ця міграція є делікатною операцією. Команда YunoHost зробила все можливе, щоб перевірити і протестувати її, але міграція все ще може порушити частину системи або її застосунків.\n\nТому рекомендовано:\n - Виконати резервне копіювання всіх важливих даних або застосунків. Подробиці на сайті https://yunohost.org/backup; \n - Наберіться терпіння після запуску міграції: В залежності від вашого з'єднання з Інтернетом і апаратного забезпечення, оновлення може зайняти до декількох годин.", + "migration_description_0021_migrate_to_bullseye": "Оновлення системи до Debian Bullseye і YunoHost 11.x" } From e84bd3a1c11d2cca3ba3d65f3580d25a83d6a29e Mon Sep 17 00:00:00 2001 From: Moutonjr Geoff Date: Fri, 17 Dec 2021 11:23:00 +0100 Subject: [PATCH 1008/1155] Message stays compliant in each locale with --apps deprecation --- locales/de.json | 2 +- locales/eo.json | 2 +- locales/es.json | 2 +- locales/eu.json | 2 +- locales/fr.json | 4 ++-- locales/oc.json | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/locales/de.json b/locales/de.json index e491c5ac2..49e312a08 100644 --- a/locales/de.json +++ b/locales/de.json @@ -613,7 +613,7 @@ "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", + "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", diff --git a/locales/eo.json b/locales/eo.json index 49040b768..5139ec98d 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -234,7 +234,7 @@ "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'", + "tools_upgrade_at_least_one": "Bonvolu specifi 'apps' aŭ 'system'", "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…", diff --git a/locales/es.json b/locales/es.json index d6382c262..8e64fbcec 100644 --- a/locales/es.json +++ b/locales/es.json @@ -211,7 +211,7 @@ "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_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}' fue recargado o reiniciado", "service_reload_or_restart_failed": "No se pudo recargar o reiniciar el servicio «{service}»\n\nRegistro de servicios recientes:{logs}", diff --git a/locales/eu.json b/locales/eu.json index 21c23260c..b5e880ce4 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -527,7 +527,7 @@ "service_restarted": "'{service}' zerbitzua berrabiarazi da", "service_start_failed": "Ezin izan da '{service}' zerbitzua abiarazi\n\nZerbitzuen azken erregistroak: {logs}", "ssowat_conf_updated": "SSOwat ezarpenak eguneratu dira", - "tools_upgrade_at_least_one": "Mesedez, zehaztu '--apps' edo '--system'", + "tools_upgrade_at_least_one": "Mesedez, zehaztu 'apps' edo 'system'", "tools_upgrade_cant_both": "Ezin dira sistema eta aplikazioak une berean eguneratu", "update_apt_cache_failed": "Ezin da APT Debian-en pakete kudeatzailearen cachea eguneratu. Hemen dituzu sources.list fitxategiaren lerroak, arazoa identifikatzeko baliagarria izan dezakezuna:\n{sourceslist}", "update_apt_cache_warning": "Zerbaitek huts egin du APT Debian-en pakete kudeatzailearen cachea eguneratzean. Hemen dituzu sources.list fitxategiaren lerroak, arazoa identifikatzeko baliagarria izan dezakezuna:\n{sourceslist}", diff --git a/locales/fr.json b/locales/fr.json index 07bdcfad3..c3cae1342 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -339,7 +339,7 @@ "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é 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)...", @@ -704,4 +704,4 @@ "other_available_options": "... et {n} autres options disponibles non affichées", "domain_config_auth_consumer_key": "Consumer key", "domain_unknown": "Domaine '{domain}' inconnu" -} \ No newline at end of file +} diff --git a/locales/oc.json b/locales/oc.json index 9de7944c5..a90298f18 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -341,7 +341,7 @@ "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", + "tools_upgrade_at_least_one": "Especificatz apps O system", "tools_upgrade_cant_unhold_critical_packages": "Se pòt pas quitar de manténer los paquets critics…", "tools_upgrade_regular_packages": "Actualizacion dels paquets « normals » (pas ligats a YunoHost)…", "tools_upgrade_special_packages": "Actualizacion dels paquets « especials » (ligats a YunoHost)…", From 4315045e4aae87966d6dd64343ca47e98b257361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Fri, 17 Dec 2021 13:41:53 +0000 Subject: [PATCH 1009/1155] Translated using Weblate (Galician) Currently translated at 100.0% (719 of 719 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/locales/gl.json b/locales/gl.json index ac41200a6..308cba961 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -703,5 +703,19 @@ "domain_dns_push_managed_in_parent_domain": "A función de rexistro DNS automático está xestionada polo dominio nai {parent_domain}.", "ldap_attribute_already_exists": "Xa existe o atributo LDAP '{attribute}' con valor '{value}'", "log_domain_config_set": "Actualizar configuración para o dominio '{}'", - "domain_unknown": "Dominio '{domain}' descoñecido" -} \ No newline at end of file + "domain_unknown": "Dominio '{domain}' descoñecido", + "migration_0021_start": "Comezando a migración a Bullseye", + "migration_0021_patching_sources_list": "Actualizando sources.list...", + "migration_0021_main_upgrade": "Iniciando a actualización principal...", + "migration_0021_still_on_buster_after_main_upgrade": "Algo fallou durante a actualización principal, o sistema semlla que aínda está en Debian Buster", + "migration_0021_yunohost_upgrade": "Iniciando actualización compoñente core de YunoHost...", + "migration_0021_not_buster": "A distribución Debian actual non é Buster!", + "migration_0021_not_enough_free_space": "Queda pouco espazo en /var/! Deberías ter polo menos 1GB libre para facer a migración.", + "migration_0021_problematic_apps_warning": "Detectamos que están instaladas estas app que poderían ser problemáticas. Semella que non foron instaladas desde o catálogo YunoHost, ou non están marcadas como que 'funcionan'. Así, non podemos garantir que seguiran funcionando ben tras a migración: {problematic_apps}", + "migration_0021_modified_files": "Ten en conta que os seguintes ficheiros semella que foron editados manualmente e poderían ser sobrescritos durante a migración: {manually_modified_files}", + "migration_0021_cleaning_up": "Limpando a caché e os paquetes que xa non son precisos...", + "migration_0021_patch_yunohost_conflicts": "Solucionando os problemas e conflitos...", + "migration_description_0021_migrate_to_bullseye": "Actualizar o sistema a Debian Bullseye e YunoHost 11.x", + "migration_0021_system_not_fully_up_to_date": "O teu sistema non está completamente actualizado. Fai unha actualización normal antes de executar a migración a Bullseye.", + "migration_0021_general_warning": "Ten en conta que a migración é unha operación delicada. O equipo de YunoHost fixo todo o que puido para revisalo e probalo, pero aínda así poderían acontecer fallos no sistema ou apps.\n\nAsí as cousas, é recomendable:\n - Facer unha copia de apoio dos datos e apps importantes. Máis info en https://yunohost.org/backup;\n - Ter paciencia unha vez inicias a migración: dependendo da túa conexión a internet e hardware, podería levarlle varias horas completar o proceso." +} From 4decdfc07c64818519df8228ad7b578f9e2d636d Mon Sep 17 00:00:00 2001 From: tituspijean Date: Tue, 21 Dec 2021 10:32:19 +0100 Subject: [PATCH 1010/1155] Fix typo in deleting superfluous question keys Co-authored-by: jacen05 --- 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 f6d940da8..ece44f172 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1997,7 +1997,7 @@ def _set_default_ask_questions(arguments): if "example" in arg: del arg["example"] if "default" in arg: - del arg["domain"] + del arg["default"] return arguments From f49f03d11e4e297920e084bb00f3c7934fdce6ad Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 22 Dec 2021 19:07:19 +0100 Subject: [PATCH 1011/1155] api: Move cookie session management logic to the authenticator for more flexibility --- share/actionsmap.yml | 1 - src/authenticators/ldap_admin.py | 60 +++++++++++++++++++++++++++++++- src/utils/error.py | 8 ++++- 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/share/actionsmap.yml b/share/actionsmap.yml index cad0212b2..9eee48716 100644 --- a/share/actionsmap.yml +++ b/share/actionsmap.yml @@ -34,7 +34,6 @@ ############################# _global: namespace: yunohost - cookie_name: yunohost.admin authentication: api: ldap_admin cli: null diff --git a/src/authenticators/ldap_admin.py b/src/authenticators/ldap_admin.py index 94d68a8db..26843c2c1 100644 --- a/src/authenticators/ldap_admin.py +++ b/src/authenticators/ldap_admin.py @@ -8,10 +8,14 @@ import time from moulinette import m18n from moulinette.authentication import BaseAuthenticator -from yunohost.utils.error import YunohostError +from moulinette.utils.text import random_ascii + +from yunohost.utils.error import YunohostError, YunohostAuthenticationError logger = logging.getLogger("yunohost.authenticators.ldap_admin") +session_secret = random_ascii() + class Authenticator(BaseAuthenticator): @@ -66,3 +70,57 @@ class Authenticator(BaseAuthenticator): # 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() + + def set_session_cookie(self, infos): + + from bottle import response + + assert isinstance(infos, dict) + + # This allows to generate a new session id or keep the existing one + current_infos = self.get_session_cookie(raise_if_no_session_exists=False) + new_infos = {"id": current_infos["id"]} + new_infos.update(infos) + + response.set_cookie( + "yunohost.admin", + new_infos, + secure=True, + secret=session_secret, + httponly=True, + # samesite="strict", # Bottle 0.12 doesn't support samesite, to be added in next versions + ) + + def get_session_cookie(self, raise_if_no_session_exists=True): + + from bottle import request + + try: + # N.B. : here we implicitly reauthenticate the cookie + # because it's signed via the session_secret + # If no session exists (or if session is invalid?) + # it's gonna return the default empty dict, + # which we interpret as an authentication failure + infos = request.get_cookie( + "yunohost.admin", secret=session_secret, default={} + ) + except Exception: + if not raise_if_no_session_exists: + return {"id": random_ascii()} + raise YunohostAuthenticationError("unable_authenticate") + + if "id" not in infos: + infos["id"] = random_ascii() + + # FIXME: Here, maybe we want to re-authenticate the session via the authenticator + # For example to check that the username authenticated is still in the admin group... + + return infos + + @staticmethod + def delete_session_cookie(self): + + from bottle import response + + response.set_cookie("yunohost.admin", "", max_age=-1) + response.delete_cookie("yunohost.admin") diff --git a/src/utils/error.py b/src/utils/error.py index 8405830e7..aa76ba67e 100644 --- a/src/utils/error.py +++ b/src/utils/error.py @@ -19,7 +19,7 @@ """ -from moulinette.core import MoulinetteError +from moulinette.core import MoulinetteError, MoulinetteAuthenticationError from moulinette import m18n @@ -60,3 +60,9 @@ class YunohostValidationError(YunohostError): def content(self): return {"error": self.strerror, "error_key": self.key, **self.kwargs} + + +class YunohostAuthenticationError(MoulinetteAuthenticationError): + + pass + From 4fc6db6317a6126d51f8eb34a706f538a6195c1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Alberto=20Gonz=C3=A1lez?= Date: Wed, 22 Dec 2021 19:52:53 +0000 Subject: [PATCH 1012/1155] Translated using Weblate (Spanish) Currently translated at 83.0% (597 of 719 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 92 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 62 insertions(+), 30 deletions(-) diff --git a/locales/es.json b/locales/es.json index 8e64fbcec..4afa56996 100644 --- a/locales/es.json +++ b/locales/es.json @@ -51,7 +51,7 @@ "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", - "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_uninstall_app_first": "Estas aplicaciones siguen instaladas en tu dominio:\n{apps}\n\nPor favor desinstálalas con el comando 'yunohost app remove the_app_id' o cámbialas a otro dominio usando /etc/resolv.conf
no apunta 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} ({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_diskusage_low": "El almacenamiento {mountpoint} (en el dispositivo {device}) solo tiene {free} ({free_percent}%) de espacio disponible (de {total}). Ten cuidado.", + "diagnosis_services_bad_status_tip": "Puedes intentar reiniciar el servicio, y si no funciona, echar un vistazo a los logs del serviciode la administración web (desde la línea de comandos puedes hacerlo con yunohost service restart {service} y yunohost service log {service}).", "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!", @@ -441,8 +441,8 @@ "diagnosis_dns_bad_conf": "Algunos registros DNS faltan o están mal cofigurados para el dominio {domain} (categoría {category})", "diagnosis_dns_discrepancy": "El siguiente registro DNS parace que no sigue la configuración recomendada
Tipo: {type}
Nombre: {name}
Valor Actual: {current}
Valor 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} ({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_diskusage_verylow": "El almacenamiento {mountpoint}(en el dispositivo {device}) sólo tiene {free} ({free_percent}%) de espacio disponible(de {total}). ¡Deberías limpiar algo de espacio!", + "diagnosis_diskusage_ok": "¡El almacenamiento {mountpoint} (en el dispositivo {device}) todavía tiene {free} ({free_percent}%) de espacio libre (de {total})!", "diagnosis_services_conf_broken": "¡Mala configuración para el servicio {service}!", "diagnosis_services_running": "¡El servicio {service} está en ejecución!", "diagnosis_failed": "Error al obtener el resultado del diagnóstico para la categoría '{category}': {error}", @@ -455,7 +455,7 @@ "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_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": "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_security_vulnerable_to_meltdown": "Pareces vulnerable a el colapso de vulnerabilidad critica de seguridad", "diagnosis_description_basesystem": "Sistema de base", @@ -504,11 +504,11 @@ "diagnosis_domain_expiration_not_found_details": "¿Parece que la información de WHOIS para el dominio {domain} no contiene información sobre la fecha de expiración?", "diagnosis_domain_not_found_details": "¡El dominio {domain} no existe en la base de datos WHOIS o ha expirado!", "diagnosis_domain_expiration_not_found": "No se pudo revisar la fecha de expiración para algunos dominios", - "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_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, puedes intentar forzar una actualización mediante 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 --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_details": "Algunos paquetes fueron accidentalmente instalados de un repositorio de terceros llamado Sury. El equipo YunoHost ha mejorado la estrategia para manejar estos paquetes, pero es posible que algunas configuraciones que han instalado aplicaciones PHP7.3 al tiempo que presentes en Stretch tienen algunas inconsistencias. Para solucionar esta situación, deberías 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.)", "backup_archive_corrupted": "Parece que el archivo de respaldo '{archive}' está corrupto : {error}", @@ -571,7 +571,7 @@ "diagnosis_mail_ehlo_bad_answer_details": "Podría ser debido a otra máquina en lugar de tu servidor.", "diagnosis_mail_ehlo_bad_answer": "Un servicio que no es SMTP respondió en el puerto 25 mediante IPv{ipversion}", "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_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_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.", @@ -583,5 +583,37 @@ "app_config_unable_to_apply": "No se pudieron aplicar los valores del panel configuración.", "app_config_unable_to_read": "No se pudieron leer los valores del panel configuración.", "backup_create_size_estimation": "El archivo contendrá aproximadamente {size} de datos.", - "config_cant_set_value_on_section": "No puede establecer un único valor en una sección de configuración completa." -} \ No newline at end of file + "config_cant_set_value_on_section": "No puede establecer un único valor en una sección de configuración completa.", + "diagnosis_http_special_use_tld": "Le dominio {domain} está basado en un dominio de primer nivel (TLD) de uso especial, como un .local o .test y no debería estar expuesto fuera de la red local.", + "domain_dns_push_failed": "La actualización de las entradas DNS ha fallado.", + "domain_dns_push_partial_failure": "Entradas DNS actualizadas parcialmente: algunas advertencias/errores reportados.", + "domain_unknown": "Dominio '{domain}' desconocido", + "diagnosis_high_number_auth_failures": "Ultimamente ha habido un gran número de errores de autenticación. Asegúrate de que Fail2Ban está ejecutándose y correctamente configurado, o usa un puerto SSH personalizado como se explica en https://yunohost.org/security.", + "diagnosis_sshd_config_inconsistent": "Parece que el puerto SSH ha sido modificado manualmente en /etc/ssh/sshd_config. Desde YunoHost 4.2, hay un nuevo parámetro global 'security.ssh.port' disponible para evitar modificar manualmente la configuración.", + "diagnosis_sshd_config_inconsistent_details": "Por favor ejecuta yunohost settings set security.ssh.port -v TU_PUERTO_SSH para definir el puerto SSH, y comprueba yunohost tools regen-conf ssh --dry-run --with-diff y yunohost tools regen-conf ssh --force para resetear tu configuración a las recomendaciones de YunoHost.", + "config_forbidden_keyword": "'{keyword}' es una palabra reservada, no puedes crear ni usar un panel de configuración con una pregunta que use esta id.", + "config_no_panel": "No se ha encontrado ningún panel de configuración.", + "config_unknown_filter_key": "La clave de filtrado '{filter_key}' es incorrecta.", + "config_validate_color": "Debe ser un valor hexadecimal RGB correcto", + "danger": "Peligro:", + "diagnosis_apps_issue": "Se ha detectado un problema con la aplicación {app}", + "diagnosis_description_apps": "Aplicaciones", + "diagnosis_rootfstotalspace_critical": "¡El sistema de ficheros raíz solo tiene un total de {space}! ¡Vas a quedarte sin espacio rápidamente! Se recomienda tener al menos 16GB para ese sistema de ficheros.", + "diagnosis_rootfstotalspace_warning": "¡El sistema de ficheros raíz solo tiene un total de {space}! Podría ser suficiente, pero cuidado, puedes rellenarlo rápidamente… Se recomienda tener al menos 16GB para el sistema de ficheros raíz.", + "diagnosis_apps_allgood": "Todas las aplicaciones instaladas respetan las prácticas básicas de empaquetado", + "config_validate_date": "Debe ser una fecha válida en formato AAAA-MM-DD", + "config_validate_email": "Debe ser una dirección de correo correcta", + "config_validate_time": "Debe ser una hora valida en formato HH:MM", + "config_validate_url": "Debe ser una URL válida", + "config_version_not_supported": "Las versiones del panel de configuración '{version}' no están soportadas.", + "domain_remove_confirm_apps_removal": "La supresión de este dominio también eliminará las siguientes aplicaciones:\n{apps}\n\n¿Seguro? [{answers}]", + "domain_registrar_is_not_configured": "El registrador aún no ha configurado el dominio {domain}.", + "diagnosis_apps_not_in_app_catalog": "Esta aplicación se encuentra ausente o ya no figura en el catálogo de aplicaciones de YunoHost. Deberías considerar desinstalarla ya que no recibirá actualizaciones y podría comprometer la integridad y seguridad de tu sistema.", + "disk_space_not_sufficient_install": "No hay espacio libre suficiente para instalar esta aplicación", + "disk_space_not_sufficient_update": "No hay espacio libre suficiente para actualizar esta aplicación", + "diagnosis_dns_specialusedomain": "El dominio {domain} se basa en un dominio de primer nivel (TLD) de usos especiales como .local o .test y no debería tener entradas DNS reales.", + "diagnosis_apps_bad_quality": "Esta aplicación está etiquetada como defectuosa en el catálogo de aplicaciones YunoHost. Podría ser un problema temporal mientras las personas responsables corrigen el asunto. Mientras tanto, la actualización de esta aplicación está desactivada.", + "diagnosis_apps_broken": "Esta aplicación está etiquetada como defectuosa en el catálogo de aplicaciones YunoHost. Podría ser un problema temporal mientras las personas responsables corrigen el asunto. Mientras tanto, la actualización de esta aplicación está desactivada.", + "diagnosis_apps_deprecated_practices": "La versión instalada de esta aplicación usa aún prácticas de empaquetado obsoletas. Deberías actualizarla.", + "diagnosis_apps_outdated_ynh_requirement": "La versión instalada de esta aplicación solo necesita YunoHost >= 2.x, lo que indica que no está al día con la buena praxis de ayudas y empaquetado recomendadas. Deberías actualizarla." +} From 6aaf47493d0cc26e617fe3fc6f9fe589a98666ad Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Dec 2021 16:55:12 +0100 Subject: [PATCH 1013/1155] Not sure when that started to happen, but bottle will return an empty dict if no valid session cookie found, this shall trigger an exception --- src/authenticators/ldap_admin.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/authenticators/ldap_admin.py b/src/authenticators/ldap_admin.py index 26843c2c1..7f96165cb 100644 --- a/src/authenticators/ldap_admin.py +++ b/src/authenticators/ldap_admin.py @@ -109,6 +109,9 @@ class Authenticator(BaseAuthenticator): return {"id": random_ascii()} raise YunohostAuthenticationError("unable_authenticate") + if not infos and raise_if_no_session_exists: + raise YunohostAuthenticationError("unable_authenticate") + if "id" not in infos: infos["id"] = random_ascii() From ad56275801bccbfa0c0b5acf2e47df999ff798b9 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Fri, 24 Dec 2021 01:16:41 +0100 Subject: [PATCH 1014/1155] [mod] run pyupgrade on source code --- data/hooks/diagnosis/00-basesystem.py | 4 +-- data/hooks/diagnosis/10-ip.py | 4 +-- data/hooks/diagnosis/50-systemresources.py | 8 ++--- doc/generate_helper_doc.py | 2 +- src/yunohost/app.py | 10 +++--- src/yunohost/app_catalog.py | 2 +- src/yunohost/backup.py | 14 ++++---- src/yunohost/certificate.py | 34 +++++++++--------- .../data_migrations/0015_migrate_to_buster.py | 8 ++--- src/yunohost/diagnosis.py | 16 ++++----- src/yunohost/dns.py | 2 +- src/yunohost/dyndns.py | 10 +++--- src/yunohost/hook.py | 6 ++-- src/yunohost/log.py | 2 +- src/yunohost/permission.py | 8 ++--- src/yunohost/regenconf.py | 2 +- src/yunohost/service.py | 8 ++--- src/yunohost/settings.py | 2 +- src/yunohost/tests/test_app_catalog.py | 2 +- src/yunohost/tests/test_app_config.py | 2 +- src/yunohost/tests/test_apps.py | 8 ++--- src/yunohost/tests/test_appurl.py | 8 ++--- src/yunohost/tests/test_backuprestore.py | 4 +-- src/yunohost/tests/test_changeurl.py | 2 +- src/yunohost/tests/test_permission.py | 20 +++++------ src/yunohost/tests/test_questions.py | 2 +- src/yunohost/tests/test_user-group.py | 2 +- src/yunohost/tools.py | 2 +- src/yunohost/user.py | 12 +++---- src/yunohost/utils/network.py | 4 +-- src/yunohost/utils/yunopaste.py | 2 +- src/yunohost/vendor/acme_tiny/acme_tiny.py | 36 +++++++++---------- tests/test_translation_format_consistency.py | 6 ++-- 33 files changed, 126 insertions(+), 128 deletions(-) diff --git a/data/hooks/diagnosis/00-basesystem.py b/data/hooks/diagnosis/00-basesystem.py index b472a2d32..8b7b888e3 100644 --- a/data/hooks/diagnosis/00-basesystem.py +++ b/data/hooks/diagnosis/00-basesystem.py @@ -42,7 +42,7 @@ class BaseSystemDiagnoser(Diagnoser): elif os.path.exists("/sys/devices/virtual/dmi/id/sys_vendor"): model = read_file("/sys/devices/virtual/dmi/id/sys_vendor").strip() if os.path.exists("/sys/devices/virtual/dmi/id/product_name"): - model = "%s %s" % ( + model = "{} {}".format( model, read_file("/sys/devices/virtual/dmi/id/product_name").strip(), ) @@ -116,7 +116,7 @@ class BaseSystemDiagnoser(Diagnoser): bad_sury_packages = list(self.bad_sury_packages()) if bad_sury_packages: cmd_to_fix = "apt install --allow-downgrades " + " ".join( - ["%s=%s" % (package, version) for package, version in bad_sury_packages] + ["{}={}".format(package, version) for package, version in bad_sury_packages] ) yield dict( meta={"test": "packages_from_sury"}, diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index 408019668..486e37e3e 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -167,7 +167,7 @@ class IPDiagnoser(Diagnoser): assert ( resolvers != [] - ), "Uhoh, need at least one IPv%s DNS resolver in %s ..." % ( + ), "Uhoh, need at least one IPv{} DNS resolver in {} ...".format( protocol, resolver_file, ) @@ -221,7 +221,7 @@ class IPDiagnoser(Diagnoser): 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)) + "Could not get public IPv{} : {}".format(str(protocol), str(e)) ) return None diff --git a/data/hooks/diagnosis/50-systemresources.py b/data/hooks/diagnosis/50-systemresources.py index a662e392e..4deb607f4 100644 --- a/data/hooks/diagnosis/50-systemresources.py +++ b/data/hooks/diagnosis/50-systemresources.py @@ -132,7 +132,7 @@ class SystemResourcesDiagnoser(Diagnoser): d for d in disk_partitions if d.mountpoint in ["/", "/var"] ] main_space = sum( - [psutil.disk_usage(d.mountpoint).total for d in main_disk_partitions] + psutil.disk_usage(d.mountpoint).total for d in main_disk_partitions ) if main_space < 10 * GB: yield dict( @@ -156,7 +156,7 @@ class SystemResourcesDiagnoser(Diagnoser): kills_count = self.recent_kills_by_oom_reaper() if kills_count: kills_summary = "\n".join( - ["%s (x%s)" % (proc, count) for proc, count in kills_count] + ["{} (x{})".format(proc, count) for proc, count in kills_count] ) yield dict( @@ -202,9 +202,9 @@ 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) + return "{} {}B".format(round_(bytes_), unit) bytes_ /= 1024.0 - return "%s %sB" % (round_(bytes_), "Yi") + return "{} {}B".format(round_(bytes_), "Yi") def round_(n): diff --git a/doc/generate_helper_doc.py b/doc/generate_helper_doc.py index f2d5bf444..689904a8a 100644 --- a/doc/generate_helper_doc.py +++ b/doc/generate_helper_doc.py @@ -107,7 +107,7 @@ class Parser: else: # We're getting out of a comment bloc, we should find # the name of the function - assert len(line.split()) >= 1, "Malformed line %s in %s" % ( + assert len(line.split()) >= 1, "Malformed line {} in {}".format( i, self.filename, ) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index ece44f172..f47efd9d7 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -122,7 +122,7 @@ def app_list(full=False, installed=False, filter=None): 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)) + logger.error("Failed to read info for {} : {}".format(app_id, e)) continue app_info_dict["id"] = app_id out.append(app_info_dict) @@ -1219,7 +1219,7 @@ def app_setting(app, key, value=None, delete=False): ) permissions = user_permission_list(full=True, apps=[app])["permissions"] - permission_name = "%s.legacy_%s_uris" % (app, key.split("_")[0]) + permission_name = "{}.legacy_{}_uris".format(app, key.split("_")[0]) permission = permissions.get(permission_name) # GET @@ -1562,7 +1562,7 @@ def app_action_run(operation_logger, app, action, args=None): 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" % ( + msg = "Error while executing action '{}' of app '{}': return code {}".format( action, app, retcode, @@ -1989,7 +1989,7 @@ def _set_default_ask_questions(arguments): 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"]) + key = "app_manifest_{}_ask_{}".format(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... @@ -2397,7 +2397,7 @@ def _make_environment_for_app_script( env_dict["YNH_APP_BASEDIR"] = workdir for arg_name, arg_value in args.items(): - env_dict["YNH_%s%s" % (args_prefix, arg_name.upper())] = str(arg_value) + env_dict["YNH_{}{}".format(args_prefix, arg_name.upper())] = str(arg_value) return env_dict diff --git a/src/yunohost/app_catalog.py b/src/yunohost/app_catalog.py index e4ffa1db6..96a006704 100644 --- a/src/yunohost/app_catalog.py +++ b/src/yunohost/app_catalog.py @@ -217,7 +217,7 @@ def _load_apps_catalog(): ) except Exception as e: raise YunohostError( - "Unable to read cache for apps_catalog %s : %s" % (cache_file, e), + "Unable to read cache for apps_catalog {} : {}".format(cache_file, e), raw_msg=True, ) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index f68e59b13..03721be0c 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -2380,7 +2380,7 @@ def backup_list(with_info=False, human_readable=False): # Get local archives sorted according to last modification time # (we do a realpath() to resolve symlinks) archives = glob("%s/*.tar.gz" % ARCHIVES_PATH) + glob("%s/*.tar" % ARCHIVES_PATH) - archives = set([os.path.realpath(archive) for archive in archives]) + archives = {os.path.realpath(archive) for archive in archives} archives = sorted(archives, key=lambda x: os.path.getctime(x)) # Extract only filename without the extension @@ -2420,7 +2420,7 @@ def backup_download(name): ) return - archive_file = "%s/%s.tar" % (ARCHIVES_PATH, name) + archive_file = "{}/{}.tar".format(ARCHIVES_PATH, name) # Check file exist (even if it's a broken symlink) if not os.path.lexists(archive_file): @@ -2462,7 +2462,7 @@ def backup_info(name, with_details=False, human_readable=False): elif name.endswith(".tar"): name = name[: -len(".tar")] - archive_file = "%s/%s.tar" % (ARCHIVES_PATH, name) + archive_file = "{}/{}.tar".format(ARCHIVES_PATH, name) # Check file exist (even if it's a broken symlink) if not os.path.lexists(archive_file): @@ -2480,7 +2480,7 @@ def backup_info(name, with_details=False, human_readable=False): "backup_archive_broken_link", path=archive_file ) - info_file = "%s/%s.info.json" % (ARCHIVES_PATH, name) + info_file = "{}/{}.info.json".format(ARCHIVES_PATH, name) if not os.path.exists(info_file): tar = tarfile.open( @@ -2591,10 +2591,10 @@ def backup_delete(name): hook_callback("pre_backup_delete", args=[name]) - archive_file = "%s/%s.tar" % (ARCHIVES_PATH, name) + archive_file = "{}/{}.tar".format(ARCHIVES_PATH, name) if not os.path.exists(archive_file) and os.path.exists(archive_file + ".gz"): archive_file += ".gz" - info_file = "%s/%s.info.json" % (ARCHIVES_PATH, name) + info_file = "{}/{}.info.json".format(ARCHIVES_PATH, name) files_to_delete = [archive_file, info_file] @@ -2693,5 +2693,5 @@ def binary_to_human(n, customary=False): for s in reversed(symbols): if n >= prefix[s]: value = float(n) / prefix[s] - return "%.1f%s" % (value, s) + return "{:.1f}{}".format(value, s) return "%s" % n diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 2679b2429..e4dc7d350 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -143,7 +143,7 @@ def _certificate_install_selfsigned(domain_list, force=False): # Paths of files and folder we'll need date_tag = datetime.utcnow().strftime("%Y%m%d.%H%M%S") - new_cert_folder = "%s/%s-history/%s-selfsigned" % ( + new_cert_folder = "{}/{}-history/{}-selfsigned".format( CERT_FOLDER, domain, date_tag, @@ -300,7 +300,7 @@ def _certificate_install_letsencrypt( try: _fetch_and_enable_new_certificate(domain, staging, no_checks=no_checks) except Exception as e: - msg = "Certificate installation for %s failed !\nException: %s" % ( + msg = "Certificate installation for {} failed !\nException: {}".format( domain, e, ) @@ -457,20 +457,20 @@ def _email_renewing_failed(domain, exception_message, stack=""): logs = _tail(50, "/var/log/yunohost/yunohost-cli.log") text = """ -An attempt for renewing the certificate for domain %s failed with the following +An attempt for renewing the certificate for domain {} failed with the following error : -%s -%s +{} +{} Here's the tail of /var/log/yunohost/yunohost-cli.log, which might help to investigate : -%s +{} -- Certificate Manager -""" % ( +""".format( domain, exception_message, stack, @@ -478,12 +478,12 @@ investigate : ) message = """\ -From: %s -To: %s -Subject: %s +From: {} +To: {} +Subject: {} -%s -""" % ( +{} +""".format( from_, to_, subject_, @@ -532,7 +532,7 @@ def _fetch_and_enable_new_certificate(domain, staging=False, no_checks=False): # Prepare certificate signing request logger.debug("Prepare key and certificate signing request (CSR) for %s...", domain) - domain_key_file = "%s/%s.pem" % (TMP_FOLDER, domain) + domain_key_file = "{}/{}.pem".format(TMP_FOLDER, domain) _generate_key(domain_key_file) _set_permissions(domain_key_file, "root", "ssl-cert", 0o640) @@ -541,7 +541,7 @@ def _fetch_and_enable_new_certificate(domain, staging=False, no_checks=False): # Sign the certificate logger.debug("Now using ACME Tiny to sign the certificate...") - domain_csr_file = "%s/%s.csr" % (TMP_FOLDER, domain) + domain_csr_file = "{}/{}.csr".format(TMP_FOLDER, domain) if staging: certification_authority = STAGING_CERTIFICATION_AUTHORITY @@ -580,7 +580,7 @@ def _fetch_and_enable_new_certificate(domain, staging=False, no_checks=False): else: folder_flag = "letsencrypt" - new_cert_folder = "%s/%s-history/%s-%s" % ( + new_cert_folder = "{}/{}-history/{}-{}".format( CERT_FOLDER, domain, date_tag, @@ -642,7 +642,7 @@ def _prepare_certificate_signing_request(domain, key_file, output_folder): csr.add_extensions( [ crypto.X509Extension( - "subjectAltName".encode("utf8"), + b"subjectAltName", False, ("DNS:" + subdomain).encode("utf8"), ) @@ -844,7 +844,7 @@ def _backup_current_cert(domain): cert_folder_domain = os.path.join(CERT_FOLDER, domain) date_tag = datetime.utcnow().strftime("%Y%m%d.%H%M%S") - backup_folder = "%s-backups/%s" % (cert_folder_domain, date_tag) + backup_folder = "{}-backups/{}".format(cert_folder_domain, date_tag) shutil.copytree(cert_folder_domain, backup_folder) diff --git a/src/yunohost/data_migrations/0015_migrate_to_buster.py b/src/yunohost/data_migrations/0015_migrate_to_buster.py index 4f2d4caf8..626dd682a 100644 --- a/src/yunohost/data_migrations/0015_migrate_to_buster.py +++ b/src/yunohost/data_migrations/0015_migrate_to_buster.py @@ -269,14 +269,14 @@ class MyMigration(Migration): % default_crt ) - os.system("mv %s %s.old" % (default_crt, default_crt)) - os.system("mv %s %s.old" % (default_key, default_key)) + os.system("mv {} {}.old".format(default_crt, default_crt)) + os.system("mv {} {}.old".format(default_key, default_key)) ret = os.system("/usr/share/yunohost/hooks/conf_regen/02-ssl init") if ret != 0 or not os.path.exists(default_crt): logger.error("Upgrading the certificate failed ... reverting") - os.system("mv %s.old %s" % (default_crt, default_crt)) - os.system("mv %s.old %s" % (default_key, default_key)) + os.system("mv {}.old {}".format(default_crt, default_crt)) + os.system("mv {}.old {}".format(default_key, default_key)) signatures = {cert: check_output(cmd % cert) for cert in active_certs} diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 4ac5e2731..dfe58685e 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -640,7 +640,7 @@ class Diagnoser: elif ipversion == 6: socket.getaddrinfo = getaddrinfo_ipv6_only - url = "https://%s/%s" % (DIAGNOSIS_SERVER, uri) + url = "https://{}/{}".format(DIAGNOSIS_SERVER, uri) try: r = requests.post(url, json=data, timeout=timeout) finally: @@ -679,7 +679,7 @@ def _email_diagnosis_issues(): from yunohost.domain import _get_maindomain maindomain = _get_maindomain() - from_ = "diagnosis@%s (Automatic diagnosis on %s)" % (maindomain, maindomain) + from_ = "diagnosis@{} (Automatic diagnosis on {})".format(maindomain, maindomain) to_ = "root" subject_ = "Issues found by automatic diagnosis on %s" % maindomain @@ -692,16 +692,16 @@ def _email_diagnosis_issues(): content = _dump_human_readable_reports(issues) message = """\ -From: %s -To: %s -Subject: %s +From: {} +To: {} +Subject: {} -%s +{} --- -%s -""" % ( +{} +""".format( from_, to_, subject_, diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 534ade918..23c641a35 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -762,7 +762,7 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= changes = {"delete": [], "update": [], "create": [], "unchanged": []} type_and_names = sorted( - set([(r["type"], r["name"]) for r in current_records + wanted_records]) + {(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 diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 4f4dc1d1f..63bb3180f 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -151,7 +151,7 @@ def dyndns_subscribe(operation_logger, domain=None, key=None): try: error = json.loads(r.text)["error"] except Exception: - error = 'Server error, code: %s. (Message: "%s")' % (r.status_code, r.text) + error = 'Server error, code: {}. (Message: "{}")'.format(r.status_code, r.text) raise YunohostError("dyndns_registration_failed", error=error) # Yunohost regen conf will add the dyndns cron job if a private key exists @@ -196,7 +196,7 @@ def dyndns_update( # If key is not given, pick the first file we find with the domain given elif key is None: - keys = glob.glob("/etc/yunohost/dyndns/K{0}.+*.private".format(domain)) + keys = glob.glob("/etc/yunohost/dyndns/K{}.+*.private".format(domain)) if not keys: raise YunohostValidationError("dyndns_key_not_found") @@ -263,14 +263,14 @@ def dyndns_update( return None raise YunohostError( - "Failed to resolve %s for %s" % (rdtype, domain), raw_msg=True + "Failed to resolve {} for {}".format(rdtype, domain), raw_msg=True ) old_ipv4 = resolve_domain(domain, "A") old_ipv6 = resolve_domain(domain, "AAAA") - logger.debug("Old IPv4/v6 are (%s, %s)" % (old_ipv4, old_ipv6)) - logger.debug("Requested IPv4/v6 are (%s, %s)" % (ipv4, ipv6)) + logger.debug("Old IPv4/v6 are ({}, {})".format(old_ipv4, old_ipv6)) + logger.debug("Requested IPv4/v6 are ({}, {})".format(ipv4, ipv6)) # no need to update if (not force and not dry_run) and (old_ipv4 == ipv4 and old_ipv6 == ipv6): diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index 98b624f12..c5ff8b0cd 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -156,7 +156,7 @@ def hook_list(action, list_by="name", show_info=False): try: d[priority].add(name) except KeyError: - d[priority] = set([name]) + d[priority] = {name} elif list_by == "name" or list_by == "folder": if show_info: @@ -197,7 +197,7 @@ def hook_list(action, list_by="name", show_info=False): or (f.startswith("__") and f.endswith("__")) ): continue - path = "%s%s/%s" % (folder, action, f) + path = "{}{}/{}".format(folder, action, f) priority, name = _extract_filename_parts(f) _append_hook(d, priority, name, path) @@ -407,7 +407,7 @@ def _hook_exec_bash(path, args, chdir, env, user, return_format, loggers): if not chdir: # use the script directory as current one chdir, cmd_script = os.path.split(path) - cmd_script = "./{0}".format(cmd_script) + cmd_script = "./{}".format(cmd_script) else: cmd_script = path diff --git a/src/yunohost/log.py b/src/yunohost/log.py index 0e534f9f8..cb11c7112 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -544,7 +544,7 @@ class OperationLogger(object): # 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], + (f.path for f in proc.open_files() if f.path in recent_operation_logs), key=os.path.getctime, reverse=True, ) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 1856046d6..04c170a32 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -139,7 +139,7 @@ def user_permission_list( continue main_perm_label = permissions[main_perm_name]["label"] infos["sublabel"] = infos["label"] - infos["label"] = "%s (%s)" % (main_perm_label, infos["label"]) + infos["label"] = "{} ({})".format(main_perm_label, infos["label"]) if short: permissions = list(permissions.keys()) @@ -664,13 +664,11 @@ def permission_sync_to_user(): currently_allowed_users = set(permission_infos["corresponding_users"]) # 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( - [ + should_be_allowed_users = { user for group in permission_infos["allowed"] for user in groups[group]["members"] - ] - ) + } # 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 diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py index 1beef8a44..224fb8bc5 100644 --- a/src/yunohost/regenconf.py +++ b/src/yunohost/regenconf.py @@ -640,7 +640,7 @@ def _process_regen_conf(system_conf, new_conf=None, save=True): if save: backup_path = os.path.join( BACKUP_CONF_DIR, - "{0}-{1}".format( + "{}-{}".format( system_conf.lstrip("/"), datetime.utcnow().strftime("%Y%m%d.%H%M%S") ), ) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index f200d08c0..f47f67a3c 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -625,7 +625,7 @@ def _run_service_command(action, service): % (action, ", ".join(possible_actions)) ) - cmd = "systemctl %s %s" % (action, service) + cmd = "systemctl {} {}".format(action, service) need_lock = services[service].get("need_lock", False) and action in [ "start", @@ -673,7 +673,7 @@ def _give_lock(action, service, p): else: systemctl_PID_name = "ControlPID" - cmd_get_son_PID = "systemctl show %s -p %s" % (service, systemctl_PID_name) + cmd_get_son_PID = "systemctl show {} -p {}".format(service, systemctl_PID_name) son_PID = 0 # As long as we did not found the PID and that the command is still running while son_PID == 0 and p.poll() is None: @@ -687,7 +687,7 @@ def _give_lock(action, service, p): if son_PID != 0: # Append the PID to the lock file logger.debug( - "Giving a lock to PID %s for service %s !" % (str(son_PID), service) + "Giving a lock to PID {} for service {} !".format(str(son_PID), service) ) append_to_file(MOULINETTE_LOCK, "\n%s" % str(son_PID)) @@ -865,7 +865,7 @@ def _get_journalctl_logs(service, number="all"): systemd_service = services.get(service, {}).get("actual_systemd_service", service) try: return check_output( - "journalctl --no-hostname --no-pager -u {0} -n{1}".format( + "journalctl --no-hostname --no-pager -u {} -n{}".format( systemd_service, number ) ) diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index d59b41a58..77d0d2705 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -224,7 +224,7 @@ def settings_set(key, value): try: trigger_post_change_hook(key, old_value, value) except Exception as e: - logger.error("Post-change hook for setting %s failed : %s" % (key, e)) + logger.error("Post-change hook for setting {} failed : {}".format(key, e)) raise diff --git a/src/yunohost/tests/test_app_catalog.py b/src/yunohost/tests/test_app_catalog.py index 8423b868e..e9ecb1c12 100644 --- a/src/yunohost/tests/test_app_catalog.py +++ b/src/yunohost/tests/test_app_catalog.py @@ -132,7 +132,7 @@ def test_apps_catalog_update_nominal(mocker): catalog = app_catalog(with_categories=True) assert "apps" in catalog - assert set(catalog["apps"].keys()) == set(["foo", "bar"]) + assert set(catalog["apps"].keys()) == {"foo", "bar"} assert "categories" in catalog assert [c["id"] for c in catalog["categories"]] == ["yolo", "swag"] diff --git a/src/yunohost/tests/test_app_config.py b/src/yunohost/tests/test_app_config.py index 0eb813672..8de03bfd5 100644 --- a/src/yunohost/tests/test_app_config.py +++ b/src/yunohost/tests/test_app_config.py @@ -70,7 +70,7 @@ def legacy_app(request): app_install( os.path.join(get_test_apps_dir(), "legacy_app_ynh"), - args="domain=%s&path=%s&is_public=%s" % (main_domain, "/", 1), + args="domain={}&path={}&is_public={}".format(main_domain, "/", 1), force=True, ) diff --git a/src/yunohost/tests/test_apps.py b/src/yunohost/tests/test_apps.py index 22e18ec9a..2a808b5bd 100644 --- a/src/yunohost/tests/test_apps.py +++ b/src/yunohost/tests/test_apps.py @@ -111,7 +111,7 @@ def secondary_domain(request): def app_expected_files(domain, app): - yield "/etc/nginx/conf.d/%s.d/%s.conf" % (domain, app) + yield "/etc/nginx/conf.d/{}.d/{}.conf".format(domain, app) if app.startswith("legacy_app"): yield "/var/www/%s/index.html" % app yield "/etc/yunohost/apps/%s/settings.yml" % app @@ -152,7 +152,7 @@ def install_legacy_app(domain, path, public=True): app_install( os.path.join(get_test_apps_dir(), "legacy_app_ynh"), - args="domain=%s&path=%s&is_public=%s" % (domain, path, 1 if public else 0), + args="domain={}&path={}&is_public={}".format(domain, path, 1 if public else 0), force=True, ) @@ -170,7 +170,7 @@ def install_break_yo_system(domain, breakwhat): app_install( os.path.join(get_test_apps_dir(), "break_yo_system_ynh"), - args="domain=%s&breakwhat=%s" % (domain, breakwhat), + args="domain={}&breakwhat={}".format(domain, breakwhat), force=True, ) @@ -338,7 +338,7 @@ def test_legacy_app_failed_remove(mocker, secondary_domain): # 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")) + os.remove("/etc/nginx/conf.d/{}.d/{}.conf".format(secondary_domain, "legacy_app")) # TODO / FIXME : can't easily validate that 'app_not_properly_removed' # is triggered for weird reasons ... diff --git a/src/yunohost/tests/test_appurl.py b/src/yunohost/tests/test_appurl.py index 00bfe5c58..c036ae28a 100644 --- a/src/yunohost/tests/test_appurl.py +++ b/src/yunohost/tests/test_appurl.py @@ -99,7 +99,7 @@ def test_registerurl(): app_install( os.path.join(get_test_apps_dir(), "register_url_app_ynh"), - args="domain=%s&path=%s" % (maindomain, "/urlregisterapp"), + args="domain={}&path={}".format(maindomain, "/urlregisterapp"), force=True, ) @@ -109,7 +109,7 @@ def test_registerurl(): with pytest.raises(YunohostError): app_install( os.path.join(get_test_apps_dir(), "register_url_app_ynh"), - args="domain=%s&path=%s" % (maindomain, "/urlregisterapp"), + args="domain={}&path={}".format(maindomain, "/urlregisterapp"), force=True, ) @@ -119,7 +119,7 @@ def test_registerurl_baddomain(): with pytest.raises(YunohostError): app_install( os.path.join(get_test_apps_dir(), "register_url_app_ynh"), - args="domain=%s&path=%s" % ("yolo.swag", "/urlregisterapp"), + args="domain={}&path={}".format("yolo.swag", "/urlregisterapp"), force=True, ) @@ -234,7 +234,7 @@ def test_normalize_permission_path_with_unknown_domain(): def test_normalize_permission_path_conflicting_path(): app_install( os.path.join(get_test_apps_dir(), "register_url_app_ynh"), - args="domain=%s&path=%s" % (maindomain, "/url/registerapp"), + args="domain={}&path={}".format(maindomain, "/url/registerapp"), force=True, ) diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index 6e2c3b514..bfed5082a 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -139,7 +139,7 @@ def app_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)) + app_files.append("/etc/nginx/conf.d/{}.d/{}.conf".format(maindomain, app)) app_files.append("/var/www/%s/index.html" % app) app_files.append("/etc/importantfile") @@ -214,7 +214,7 @@ def install_app(app, path, additionnal_args=""): app_install( os.path.join(get_test_apps_dir(), app), - args="domain=%s&path=%s%s" % (maindomain, path, additionnal_args), + args="domain={}&path={}{}".format(maindomain, path, additionnal_args), force=True, ) diff --git a/src/yunohost/tests/test_changeurl.py b/src/yunohost/tests/test_changeurl.py index e375bd9f0..04cb4a1a9 100644 --- a/src/yunohost/tests/test_changeurl.py +++ b/src/yunohost/tests/test_changeurl.py @@ -26,7 +26,7 @@ def teardown_function(function): def install_changeurl_app(path): app_install( os.path.join(get_test_apps_dir(), "change_url_app_ynh"), - args="domain=%s&path=%s" % (maindomain, path), + args="domain={}&path={}".format(maindomain, path), force=True, ) diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 00799d0fd..01b62f2a8 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -347,7 +347,7 @@ def check_permission_for_apps(): # {"bar", "foo"} # and compare this to the list of installed apps ... - app_perms_prefix = set(p.split(".")[0] for p in app_perms) + app_perms_prefix = {p.split(".")[0] for p in app_perms} assert set(_installed_apps()) == app_perms_prefix @@ -398,7 +398,7 @@ def test_permission_list(): 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 set(res["wiki.main"]["corresponding_users"]) == {"alice", "bob"} assert res["blog.main"]["corresponding_users"] == ["alice"] assert res["blog.api"]["corresponding_users"] == [] assert res["wiki.main"]["url"] == "/" @@ -442,7 +442,7 @@ def test_permission_create_main(mocker): 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"]) + assert set(res["site.main"]["corresponding_users"]) == {"alice", "bob"} assert res["site.main"]["protected"] is False @@ -630,8 +630,8 @@ def test_permission_add_group(mocker): 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"]) + assert set(res["wiki.main"]["allowed"]) == {"all_users", "alice"} + assert set(res["wiki.main"]["corresponding_users"]) == {"alice", "bob"} def test_permission_remove_group(mocker): @@ -680,7 +680,7 @@ def test_permission_reset(mocker): 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 set(res["blog.main"]["corresponding_users"]) == {"alice", "bob"} def test_permission_reset_idempotency(): @@ -690,7 +690,7 @@ def test_permission_reset_idempotency(): 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 set(res["blog.main"]["corresponding_users"]) == {"alice", "bob"} def test_permission_change_label(mocker): @@ -1013,9 +1013,9 @@ def test_permission_app_install(): 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"] - ) + assert set(res["permissions_app.main"]["corresponding_users"]) == { + "alice", "bob" + } assert res["permissions_app.admin"]["allowed"] == ["alice"] assert res["permissions_app.admin"]["corresponding_users"] == ["alice"] diff --git a/src/yunohost/tests/test_questions.py b/src/yunohost/tests/test_questions.py index c21ff8c40..5917d32d4 100644 --- a/src/yunohost/tests/test_questions.py +++ b/src/yunohost/tests/test_questions.py @@ -1977,7 +1977,7 @@ def test_question_file_from_api(): from base64 import b64encode - b64content = b64encode("helloworld".encode()) + b64content = b64encode(b"helloworld") questions = [ { "name": "some_file", diff --git a/src/yunohost/tests/test_user-group.py b/src/yunohost/tests/test_user-group.py index d65366a9a..e561118e0 100644 --- a/src/yunohost/tests/test_user-group.py +++ b/src/yunohost/tests/test_user-group.py @@ -281,7 +281,7 @@ def test_update_group_add_user(mocker): user_group_update("dev", add=["bob"]) group_res = user_group_list()["groups"] - assert set(group_res["dev"]["members"]) == set(["alice", "bob"]) + assert set(group_res["dev"]["members"]) == {"alice", "bob"} def test_update_group_add_user_already_in(mocker): diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index fb9839814..87c962499 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -224,7 +224,7 @@ def tools_postinstall( disk_partitions = sorted(psutil.disk_partitions(), key=lambda k: k.mountpoint) main_disk_partitions = [d for d in disk_partitions if d.mountpoint in ["/", "/var"]] main_space = sum( - [psutil.disk_usage(d.mountpoint).total for d in main_disk_partitions] + psutil.disk_usage(d.mountpoint).total for d in main_disk_partitions ) GB = 1024 ** 3 if not force_diskspace and main_space < 10 * GB: diff --git a/src/yunohost/user.py b/src/yunohost/user.py index c9f70e152..6a924add3 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -97,7 +97,7 @@ def user_list(fields=None): and values[0].strip() == "/bin/false", } - attrs = set(["uid"]) + attrs = {"uid"} users = {} if not fields: @@ -215,7 +215,7 @@ def user_create( uid_guid_found = uid not in all_uid and uid not in all_gid # Adapt values for LDAP - fullname = "%s %s" % (firstname, lastname) + fullname = "{} {}".format(firstname, lastname) attr_dict = { "objectClass": [ @@ -333,8 +333,8 @@ 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/{}".format(username)]) + subprocess.call(["rm", "-rf", "/var/mail/{}".format(username)]) hook_callback("post_user_delete", args=[username, purge]) @@ -1334,9 +1334,9 @@ def user_ssh_remove_key(username, key): def _convertSize(num, suffix=""): for unit in ["K", "M", "G", "T", "P", "E", "Z"]: if abs(num) < 1024.0: - return "%3.1f%s%s" % (num, unit, suffix) + return "{:3.1f}{}{}".format(num, unit, suffix) num /= 1024.0 - return "%.1f%s%s" % (num, "Yi", suffix) + return "{:.1f}{}{}".format(num, "Yi", suffix) def _hash_user_password(password): diff --git a/src/yunohost/utils/network.py b/src/yunohost/utils/network.py index 4474af14f..fd70e4d4b 100644 --- a/src/yunohost/utils/network.py +++ b/src/yunohost/utils/network.py @@ -44,7 +44,7 @@ def get_public_ip(protocol=4): ): 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)) + logger.debug("Reusing IPv{} from cache: {}".format(protocol, ip)) else: ip = get_public_ip_from_remote_server(protocol) logger.debug("IP fetched: %s" % ip) @@ -87,7 +87,7 @@ def get_public_ip_from_remote_server(protocol=4): try: return download_text(url, timeout=30).strip() except Exception as e: - logger.debug("Could not get public IPv%s : %s" % (str(protocol), str(e))) + logger.debug("Could not get public IPv{} : {}".format(str(protocol), str(e))) return None diff --git a/src/yunohost/utils/yunopaste.py b/src/yunohost/utils/yunopaste.py index 0c3e3c998..35e829991 100644 --- a/src/yunohost/utils/yunopaste.py +++ b/src/yunohost/utils/yunopaste.py @@ -49,7 +49,7 @@ def yunopaste(data): raw_msg=True, ) - return "%s/raw/%s" % (paste_server, url) + return "{}/raw/{}".format(paste_server, url) def anonymize(data): diff --git a/src/yunohost/vendor/acme_tiny/acme_tiny.py b/src/yunohost/vendor/acme_tiny/acme_tiny.py index 3c13d13ec..0d2534df9 100644 --- a/src/yunohost/vendor/acme_tiny/acme_tiny.py +++ b/src/yunohost/vendor/acme_tiny/acme_tiny.py @@ -38,7 +38,7 @@ def get_crt( ) out, err = proc.communicate(cmd_input) if proc.returncode != 0: - raise IOError("{0}\n{1}".format(err_msg, err)) + raise IOError("{}\n{}".format(err_msg, err)) return out # helper function - make request and automatically parse json response @@ -74,7 +74,7 @@ def get_crt( raise IndexError(resp_data) # allow 100 retrys for bad nonces if code not in [200, 201, 204]: raise ValueError( - "{0}:\nUrl: {1}\nData: {2}\nResponse Code: {3}\nResponse: {4}".format( + "{}:\nUrl: {}\nData: {}\nResponse Code: {}\nResponse: {}".format( err_msg, url, data, code, resp_data ) ) @@ -89,7 +89,7 @@ def get_crt( {"jwk": jwk} if acct_headers is None else {"kid": acct_headers["Location"]} ) protected64 = _b64(json.dumps(protected).encode("utf8")) - protected_input = "{0}.{1}".format(protected64, payload64).encode("utf8") + protected_input = "{}.{}".format(protected64, payload64).encode("utf8") out = _cmd( ["openssl", "dgst", "-sha256", "-sign", account_key], stdin=subprocess.PIPE, @@ -125,8 +125,8 @@ def get_crt( pub_hex, pub_exp = re.search( pub_pattern, out.decode("utf8"), re.MULTILINE | re.DOTALL ).groups() - pub_exp = "{0:x}".format(int(pub_exp)) - pub_exp = "0{0}".format(pub_exp) if len(pub_exp) % 2 else pub_exp + pub_exp = "{:x}".format(int(pub_exp)) + pub_exp = "0{}".format(pub_exp) if len(pub_exp) % 2 else pub_exp alg = "RS256" jwk = { "e": _b64(binascii.unhexlify(pub_exp.encode("utf-8"))), @@ -140,9 +140,9 @@ def get_crt( log.info("Parsing CSR...") out = _cmd( ["openssl", "req", "-in", csr, "-noout", "-text"], - err_msg="Error loading {0}".format(csr), + err_msg="Error loading {}".format(csr), ) - domains = set([]) + domains = set() common_name = re.search(r"Subject:.*? CN\s?=\s?([^\s,;/]+)", out.decode("utf8")) if common_name is not None: domains.add(common_name.group(1)) @@ -155,7 +155,7 @@ def get_crt( for san in subject_alt_names.group(1).split(", "): if san.startswith("DNS:"): domains.add(san[4:]) - log.info("Found domains: {0}".format(", ".join(domains))) + log.info("Found domains: {}".format(", ".join(domains))) # get the ACME directory of urls log.info("Getting directory...") @@ -178,7 +178,7 @@ def get_crt( {"contact": contact}, "Error updating contact details", ) - log.info("Updated contact details:\n{0}".format("\n".join(account["contact"]))) + log.info("Updated contact details:\n{}".format("\n".join(account["contact"]))) # create a new order log.info("Creating new order...") @@ -194,46 +194,46 @@ def get_crt( auth_url, None, "Error getting challenges" ) domain = authorization["identifier"]["value"] - log.info("Verifying {0}...".format(domain)) + log.info("Verifying {}...".format(domain)) # find the http-01 challenge and write the challenge file challenge = [c for c in authorization["challenges"] if c["type"] == "http-01"][ 0 ] token = re.sub(r"[^A-Za-z0-9_\-]", "_", challenge["token"]) - keyauthorization = "{0}.{1}".format(token, thumbprint) + keyauthorization = "{}.{}".format(token, thumbprint) wellknown_path = os.path.join(acme_dir, token) with open(wellknown_path, "w") as wellknown_file: wellknown_file.write(keyauthorization) # check that the file is in place try: - wellknown_url = "http://{0}/.well-known/acme-challenge/{1}".format( + wellknown_url = "http://{}/.well-known/acme-challenge/{}".format( domain, token ) assert disable_check or _do_request(wellknown_url)[0] == keyauthorization except (AssertionError, ValueError) as e: raise ValueError( - "Wrote file to {0}, but couldn't download {1}: {2}".format( + "Wrote file to {}, but couldn't download {}: {}".format( wellknown_path, wellknown_url, e ) ) # say the challenge is done _send_signed_request( - challenge["url"], {}, "Error submitting challenges: {0}".format(domain) + challenge["url"], {}, "Error submitting challenges: {}".format(domain) ) authorization = _poll_until_not( auth_url, ["pending"], - "Error checking challenge status for {0}".format(domain), + "Error checking challenge status for {}".format(domain), ) if authorization["status"] != "valid": raise ValueError( - "Challenge did not pass for {0}: {1}".format(domain, authorization) + "Challenge did not pass for {}: {}".format(domain, authorization) ) os.remove(wellknown_path) - log.info("{0} verified!".format(domain)) + log.info("{} verified!".format(domain)) # finalize the order with the csr log.info("Signing certificate...") @@ -251,7 +251,7 @@ def get_crt( "Error checking order status", ) if order["status"] != "valid": - raise ValueError("Order failed: {0}".format(order)) + raise ValueError("Order failed: {}".format(order)) # download the certificate certificate_pem, _, _ = _send_signed_request( diff --git a/tests/test_translation_format_consistency.py b/tests/test_translation_format_consistency.py index 86d1c3279..bfd0e3ae4 100644 --- a/tests/test_translation_format_consistency.py +++ b/tests/test_translation_format_consistency.py @@ -26,10 +26,10 @@ def find_inconsistencies(locale_file): # 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( + 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 any(k not in subkeys_in_ref for k in subkeys_in_this_locale): yield """\n From 77058ab356316809b0474ee64cff189fe2b64583 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Fri, 24 Dec 2021 01:16:52 +0100 Subject: [PATCH 1015/1155] [mod] stop using old style class --- src/yunohost/backup.py | 4 ++-- src/yunohost/log.py | 2 +- src/yunohost/tools.py | 2 +- src/yunohost/utils/config.py | 2 +- src/yunohost/utils/password.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 03721be0c..458d7655f 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -80,7 +80,7 @@ MB_ALLOWED_TO_ORGANIZE = 10 logger = getActionLogger("yunohost.backup") -class BackupRestoreTargetsManager(object): +class BackupRestoreTargetsManager: """ BackupRestoreTargetsManager manage the targets @@ -1570,7 +1570,7 @@ class RestoreManager: # # Backup methods # # -class BackupMethod(object): +class BackupMethod: """ BackupMethod is an abstract class that represents a way to backup and diff --git a/src/yunohost/log.py b/src/yunohost/log.py index cb11c7112..d28a35e18 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -469,7 +469,7 @@ class RedactingFormatter(Formatter): ) -class OperationLogger(object): +class OperationLogger: """ Instances of this class represents unit operation done on the ynh instance. diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 87c962499..679b4d16e 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -1107,7 +1107,7 @@ def _tools_migrations_run_before_app_restore(backup_version, app_id): raise -class Migration(object): +class Migration: # Those are to be implemented by daughter classes diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index 5d1d1f9d2..e3b09f870 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -678,7 +678,7 @@ class ConfigPanel: yield (panel, section, option) -class Question(object): +class Question: hide_user_input_in_prompt = False pattern: Optional[Dict] = None diff --git a/src/yunohost/utils/password.py b/src/yunohost/utils/password.py index 188850183..9fff5ece7 100644 --- a/src/yunohost/utils/password.py +++ b/src/yunohost/utils/password.py @@ -51,7 +51,7 @@ def assert_password_is_strong_enough(profile, password): PasswordValidator(profile).validate(password) -class PasswordValidator(object): +class PasswordValidator: def __init__(self, profile): """ Initialize a password validator. From a615528c7f9f60ee7b4d2838242389455ace0b9b Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sat, 25 Dec 2021 17:32:50 +0100 Subject: [PATCH 1016/1155] remove double quote around a variable --- 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 7e681484d..cedd3891e 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -136,7 +136,7 @@ class DNSRecordsDiagnoser(Diagnoser): # If status is okay and there's actually no expected records # (e.g. XMPP disabled) # then let's not yield any diagnosis line - if not records and "status" == "SUCCESS": + if not records and status == "SUCCESS": continue output = dict( From c4b83459b81c0bfc67882155e967ab9c645436e3 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sat, 25 Dec 2021 18:20:12 +0100 Subject: [PATCH 1017/1155] add lgtm/code quality badge --- .lgtm.yml | 4 ++++ README.md | 1 + 2 files changed, 5 insertions(+) create mode 100644 .lgtm.yml diff --git a/.lgtm.yml b/.lgtm.yml new file mode 100644 index 000000000..8fd57e49e --- /dev/null +++ b/.lgtm.yml @@ -0,0 +1,4 @@ +extraction: + python: + python_setup: + version: "3" \ No newline at end of file diff --git a/README.md b/README.md index df3a4bb9f..969651eee 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ ![Version](https://img.shields.io/github/v/tag/yunohost/yunohost?label=version&sort=semver) [![Build status](https://shields.io/gitlab/pipeline/yunohost/yunohost/dev)](https://gitlab.com/yunohost/yunohost/-/pipelines) ![Test coverage](https://img.shields.io/gitlab/coverage/yunohost/yunohost/dev) +[![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/YunoHost/yunohost.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/YunoHost/yunohost/context:python) [![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 7112deb16738dd11f5b9fa134dde5dbc178fc18e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 27 Dec 2021 14:59:08 +0100 Subject: [PATCH 1018/1155] f-string all the things! --- data/hooks/diagnosis/00-basesystem.py | 8 ++- data/hooks/diagnosis/10-ip.py | 11 ++-- data/hooks/diagnosis/50-systemresources.py | 8 +-- src/yunohost/app.py | 17 +++--- src/yunohost/app_catalog.py | 2 +- src/yunohost/backup.py | 10 ++-- src/yunohost/certificate.py | 60 ++++++---------------- src/yunohost/dyndns.py | 10 ++-- src/yunohost/hook.py | 4 +- src/yunohost/permission.py | 3 +- src/yunohost/regenconf.py | 9 ++-- src/yunohost/service.py | 12 ++--- src/yunohost/settings.py | 2 +- src/yunohost/user.py | 8 +-- src/yunohost/utils/network.py | 4 +- 15 files changed, 66 insertions(+), 102 deletions(-) diff --git a/data/hooks/diagnosis/00-basesystem.py b/data/hooks/diagnosis/00-basesystem.py index 8b7b888e3..99241c55f 100644 --- a/data/hooks/diagnosis/00-basesystem.py +++ b/data/hooks/diagnosis/00-basesystem.py @@ -42,10 +42,8 @@ class BaseSystemDiagnoser(Diagnoser): elif os.path.exists("/sys/devices/virtual/dmi/id/sys_vendor"): model = read_file("/sys/devices/virtual/dmi/id/sys_vendor").strip() if os.path.exists("/sys/devices/virtual/dmi/id/product_name"): - model = "{} {}".format( - model, - read_file("/sys/devices/virtual/dmi/id/product_name").strip(), - ) + product_name = read_file("/sys/devices/virtual/dmi/id/product_name").strip() + model = f"{model} {product_name}" hardware["data"]["model"] = model hardware["details"] = ["diagnosis_basesystem_hardware_model"] @@ -116,7 +114,7 @@ class BaseSystemDiagnoser(Diagnoser): bad_sury_packages = list(self.bad_sury_packages()) if bad_sury_packages: cmd_to_fix = "apt install --allow-downgrades " + " ".join( - ["{}={}".format(package, version) for package, version in bad_sury_packages] + [f"{package}={version}" for package, version in bad_sury_packages] ) yield dict( meta={"test": "packages_from_sury"}, diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py index 486e37e3e..1c6c5a4b3 100644 --- a/data/hooks/diagnosis/10-ip.py +++ b/data/hooks/diagnosis/10-ip.py @@ -167,10 +167,7 @@ class IPDiagnoser(Diagnoser): assert ( resolvers != [] - ), "Uhoh, need at least one IPv{} DNS resolver in {} ...".format( - protocol, - resolver_file, - ) + ), f"Uhoh, need at least one IPv{protocol} DNS resolver in {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 @@ -220,9 +217,9 @@ class IPDiagnoser(Diagnoser): try: return download_text(url, timeout=30).strip() except Exception as e: - self.logger_debug( - "Could not get public IPv{} : {}".format(str(protocol), str(e)) - ) + protocol = str(protocol) + e = str(e) + self.logger_debug(f"Could not get public IPv{protocol} : {e}") return None diff --git a/data/hooks/diagnosis/50-systemresources.py b/data/hooks/diagnosis/50-systemresources.py index 4deb607f4..bdea0ea16 100644 --- a/data/hooks/diagnosis/50-systemresources.py +++ b/data/hooks/diagnosis/50-systemresources.py @@ -156,7 +156,7 @@ class SystemResourcesDiagnoser(Diagnoser): kills_count = self.recent_kills_by_oom_reaper() if kills_count: kills_summary = "\n".join( - ["{} (x{})".format(proc, count) for proc, count in kills_count] + [f"{proc} (x{count})" for proc, count in kills_count] ) yield dict( @@ -202,9 +202,11 @@ 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 "{} {}B".format(round_(bytes_), unit) + bytes_ = round_(bytes_) + return f"{bytes_} {unit}B" bytes_ /= 1024.0 - return "{} {}B".format(round_(bytes_), "Yi") + bytes_ = round_(bytes_) + return f"{bytes_} YiB" def round_(n): diff --git a/src/yunohost/app.py b/src/yunohost/app.py index f47efd9d7..d5e57d6cf 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -122,7 +122,7 @@ def app_list(full=False, installed=False, filter=None): try: app_info_dict = app_info(app_id, full=full) except Exception as e: - logger.error("Failed to read info for {} : {}".format(app_id, e)) + logger.error(f"Failed to read info for {app_id} : {e}") continue app_info_dict["id"] = app_id out.append(app_info_dict) @@ -1219,7 +1219,8 @@ def app_setting(app, key, value=None, delete=False): ) permissions = user_permission_list(full=True, apps=[app])["permissions"] - permission_name = "{}.legacy_{}_uris".format(app, key.split("_")[0]) + key_ = key.split("_")[0] + permission_name = f"{app}.legacy_{key_}_uris" permission = permissions.get(permission_name) # GET @@ -1562,11 +1563,7 @@ def app_action_run(operation_logger, app, action, args=None): shutil.rmtree(tmp_workdir_for_app) if retcode not in action_declaration.get("accepted_return_codes", [0]): - msg = "Error while executing action '{}' of app '{}': return code {}".format( - action, - app, - retcode, - ) + msg = f"Error while executing action '{action}' of app '{app}': return code {retcode}" operation_logger.error(msg) raise YunohostError(msg, raw_msg=True) @@ -1989,7 +1986,8 @@ def _set_default_ask_questions(arguments): for question in questions_with_default ): # The key is for example "app_manifest_install_ask_domain" - key = "app_manifest_{}_ask_{}".format(script_name, arg["name"]) + arg_name = arg["name"] + key = f"app_manifest_{script_name}_ask_{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... @@ -2397,7 +2395,8 @@ def _make_environment_for_app_script( env_dict["YNH_APP_BASEDIR"] = workdir for arg_name, arg_value in args.items(): - env_dict["YNH_{}{}".format(args_prefix, arg_name.upper())] = str(arg_value) + arg_name_upper = arg_name.upper() + env_dict[f"YNH_{args_prefix}{arg_name_upper}"] = str(arg_value) return env_dict diff --git a/src/yunohost/app_catalog.py b/src/yunohost/app_catalog.py index 96a006704..0f0e1ae46 100644 --- a/src/yunohost/app_catalog.py +++ b/src/yunohost/app_catalog.py @@ -217,7 +217,7 @@ def _load_apps_catalog(): ) except Exception as e: raise YunohostError( - "Unable to read cache for apps_catalog {} : {}".format(cache_file, e), + f"Unable to read cache for apps_catalog {cache_file} : {e}", raw_msg=True, ) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 458d7655f..ebfb23ab0 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -2420,7 +2420,7 @@ def backup_download(name): ) return - archive_file = "{}/{}.tar".format(ARCHIVES_PATH, name) + archive_file = f"{ARCHIVES_PATH}/{name}.tar" # Check file exist (even if it's a broken symlink) if not os.path.lexists(archive_file): @@ -2462,7 +2462,7 @@ def backup_info(name, with_details=False, human_readable=False): elif name.endswith(".tar"): name = name[: -len(".tar")] - archive_file = "{}/{}.tar".format(ARCHIVES_PATH, name) + archive_file = f"{ARCHIVES_PATH}/{name}.tar" # Check file exist (even if it's a broken symlink) if not os.path.lexists(archive_file): @@ -2480,7 +2480,7 @@ def backup_info(name, with_details=False, human_readable=False): "backup_archive_broken_link", path=archive_file ) - info_file = "{}/{}.info.json".format(ARCHIVES_PATH, name) + info_file = f"{ARCHIVES_PATH}/{name}.info.json" if not os.path.exists(info_file): tar = tarfile.open( @@ -2591,10 +2591,10 @@ def backup_delete(name): hook_callback("pre_backup_delete", args=[name]) - archive_file = "{}/{}.tar".format(ARCHIVES_PATH, name) + archive_file = f"{ARCHIVES_PATH}/{name}.tar" if not os.path.exists(archive_file) and os.path.exists(archive_file + ".gz"): archive_file += ".gz" - info_file = "{}/{}.info.json".format(ARCHIVES_PATH, name) + info_file = f"{ARCHIVES_PATH}/{name}.info.json" files_to_delete = [archive_file, info_file] diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index e4dc7d350..79e3ae092 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -143,11 +143,7 @@ def _certificate_install_selfsigned(domain_list, force=False): # Paths of files and folder we'll need date_tag = datetime.utcnow().strftime("%Y%m%d.%H%M%S") - new_cert_folder = "{}/{}-history/{}-selfsigned".format( - CERT_FOLDER, - domain, - date_tag, - ) + new_cert_folder = f"{CERT_FOLDER}/{domain}-history/{date_tag}-selfsigned" conf_template = os.path.join(SSL_DIR, "openssl.cnf") @@ -300,10 +296,7 @@ def _certificate_install_letsencrypt( try: _fetch_and_enable_new_certificate(domain, staging, no_checks=no_checks) except Exception as e: - msg = "Certificate installation for {} failed !\nException: {}".format( - domain, - e, - ) + msg = f"Certificate installation for {domain} failed !\nException: {e}" logger.error(msg) operation_logger.error(msg) if no_checks: @@ -456,39 +449,25 @@ def _email_renewing_failed(domain, exception_message, stack=""): subject_ = "Certificate renewing attempt for %s failed!" % domain logs = _tail(50, "/var/log/yunohost/yunohost-cli.log") - text = """ -An attempt for renewing the certificate for domain {} failed with the following + message = f"""\ +From: {from_} +To: {to_} +Subject: {subject_} + + +An attempt for renewing the certificate for domain {domain} failed with the following error : -{} -{} +{exception_message} +{stack} Here's the tail of /var/log/yunohost/yunohost-cli.log, which might help to investigate : -{} +{logs} -- Certificate Manager - -""".format( - domain, - exception_message, - stack, - logs, - ) - - message = """\ -From: {} -To: {} -Subject: {} - -{} -""".format( - from_, - to_, - subject_, - text, - ) +""" import smtplib @@ -532,7 +511,7 @@ def _fetch_and_enable_new_certificate(domain, staging=False, no_checks=False): # Prepare certificate signing request logger.debug("Prepare key and certificate signing request (CSR) for %s...", domain) - domain_key_file = "{}/{}.pem".format(TMP_FOLDER, domain) + domain_key_file = f"{TMP_FOLDER}/{domain}.pem" _generate_key(domain_key_file) _set_permissions(domain_key_file, "root", "ssl-cert", 0o640) @@ -541,7 +520,7 @@ def _fetch_and_enable_new_certificate(domain, staging=False, no_checks=False): # Sign the certificate logger.debug("Now using ACME Tiny to sign the certificate...") - domain_csr_file = "{}/{}.csr".format(TMP_FOLDER, domain) + domain_csr_file = f"{TMP_FOLDER}/{domain}.csr" if staging: certification_authority = STAGING_CERTIFICATION_AUTHORITY @@ -580,12 +559,7 @@ def _fetch_and_enable_new_certificate(domain, staging=False, no_checks=False): else: folder_flag = "letsencrypt" - new_cert_folder = "{}/{}-history/{}-{}".format( - CERT_FOLDER, - domain, - date_tag, - folder_flag, - ) + new_cert_folder = f"{CERT_FOLDER}/{domain}-history/{date_tag}-{folder_flag}" os.makedirs(new_cert_folder) @@ -844,7 +818,7 @@ def _backup_current_cert(domain): cert_folder_domain = os.path.join(CERT_FOLDER, domain) date_tag = datetime.utcnow().strftime("%Y%m%d.%H%M%S") - backup_folder = "{}-backups/{}".format(cert_folder_domain, date_tag) + backup_folder = f"{cert_folder_domain}-backups/{date_tag}" shutil.copytree(cert_folder_domain, backup_folder) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 63bb3180f..8748c1c00 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -151,7 +151,7 @@ def dyndns_subscribe(operation_logger, domain=None, key=None): try: error = json.loads(r.text)["error"] except Exception: - error = 'Server error, code: {}. (Message: "{}")'.format(r.status_code, r.text) + error = f'Server error, code: {r.status_code}. (Message: "{r.text}")' raise YunohostError("dyndns_registration_failed", error=error) # Yunohost regen conf will add the dyndns cron job if a private key exists @@ -196,7 +196,7 @@ def dyndns_update( # If key is not given, pick the first file we find with the domain given elif key is None: - keys = glob.glob("/etc/yunohost/dyndns/K{}.+*.private".format(domain)) + keys = glob.glob(f"/etc/yunohost/dyndns/K{domain}.+*.private") if not keys: raise YunohostValidationError("dyndns_key_not_found") @@ -263,14 +263,14 @@ def dyndns_update( return None raise YunohostError( - "Failed to resolve {} for {}".format(rdtype, domain), raw_msg=True + f"Failed to resolve {rdtype} for {domain}", raw_msg=True ) old_ipv4 = resolve_domain(domain, "A") old_ipv6 = resolve_domain(domain, "AAAA") - logger.debug("Old IPv4/v6 are ({}, {})".format(old_ipv4, old_ipv6)) - logger.debug("Requested IPv4/v6 are ({}, {})".format(ipv4, ipv6)) + logger.debug(f"Old IPv4/v6 are ({old_ipv4}, {old_ipv6})") + logger.debug(f"Requested IPv4/v6 are ({ipv4}, {ipv6})") # no need to update if (not force and not dry_run) and (old_ipv4 == ipv4 and old_ipv6 == ipv6): diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index c5ff8b0cd..7b03b2e9b 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -197,7 +197,7 @@ def hook_list(action, list_by="name", show_info=False): or (f.startswith("__") and f.endswith("__")) ): continue - path = "{}{}/{}".format(folder, action, f) + path = f"{folder}{action}/{f}" priority, name = _extract_filename_parts(f) _append_hook(d, priority, name, path) @@ -407,7 +407,7 @@ def _hook_exec_bash(path, args, chdir, env, user, return_format, loggers): if not chdir: # use the script directory as current one chdir, cmd_script = os.path.split(path) - cmd_script = "./{}".format(cmd_script) + cmd_script = f"./{cmd_script}" else: cmd_script = path diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 04c170a32..9a46cad27 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -139,7 +139,8 @@ def user_permission_list( continue main_perm_label = permissions[main_perm_name]["label"] infos["sublabel"] = infos["label"] - infos["label"] = "{} ({})".format(main_perm_label, infos["label"]) + label_ = infos["label"] + infos["label"] = f"{main_perm_label} ({label_})" if short: permissions = list(permissions.keys()) diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py index 224fb8bc5..adec5508d 100644 --- a/src/yunohost/regenconf.py +++ b/src/yunohost/regenconf.py @@ -638,12 +638,9 @@ def _process_regen_conf(system_conf, new_conf=None, save=True): """ if save: - backup_path = os.path.join( - BACKUP_CONF_DIR, - "{}-{}".format( - system_conf.lstrip("/"), datetime.utcnow().strftime("%Y%m%d.%H%M%S") - ), - ) + system_conf_ = system_conf.lstrip("/") + now_ = datetime.utcnow().strftime("%Y%m%d.%H%M%S") + backup_path = os.path.join(BACKUP_CONF_DIR, f"{system_conf_}-{now_}") backup_dir = os.path.dirname(backup_path) if not os.path.isdir(backup_dir): diff --git a/src/yunohost/service.py b/src/yunohost/service.py index f47f67a3c..4f453dfe9 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -625,7 +625,7 @@ def _run_service_command(action, service): % (action, ", ".join(possible_actions)) ) - cmd = "systemctl {} {}".format(action, service) + cmd = f"systemctl {action} {service}" need_lock = services[service].get("need_lock", False) and action in [ "start", @@ -673,7 +673,7 @@ def _give_lock(action, service, p): else: systemctl_PID_name = "ControlPID" - cmd_get_son_PID = "systemctl show {} -p {}".format(service, systemctl_PID_name) + cmd_get_son_PID = f"systemctl show {service} -p {systemctl_PID_name}" son_PID = 0 # As long as we did not found the PID and that the command is still running while son_PID == 0 and p.poll() is None: @@ -686,9 +686,7 @@ def _give_lock(action, service, p): # If we found a PID if son_PID != 0: # Append the PID to the lock file - logger.debug( - "Giving a lock to PID {} for service {} !".format(str(son_PID), service) - ) + logger.debug(f"Giving a lock to PID {son_PID} for service {service} !") append_to_file(MOULINETTE_LOCK, "\n%s" % str(son_PID)) return son_PID @@ -865,9 +863,7 @@ def _get_journalctl_logs(service, number="all"): systemd_service = services.get(service, {}).get("actual_systemd_service", service) try: return check_output( - "journalctl --no-hostname --no-pager -u {} -n{}".format( - systemd_service, number - ) + f"journalctl --no-hostname --no-pager -u {systemd_service} -n{number}" ) except Exception: import traceback diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index 77d0d2705..eddb30764 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -224,7 +224,7 @@ def settings_set(key, value): try: trigger_post_change_hook(key, old_value, value) except Exception as e: - logger.error("Post-change hook for setting {} failed : {}".format(key, e)) + logger.error(f"Post-change hook for setting {key} failed : {e}") raise diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 6a924add3..78fda8d09 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -166,7 +166,7 @@ def user_create( # On affiche les differents domaines possibles Moulinette.display(m18n.n("domains_available")) for domain in domain_list()["domains"]: - Moulinette.display("- {}".format(domain)) + Moulinette.display(f"- {domain}") maindomain = _get_maindomain() domain = Moulinette.prompt( @@ -215,7 +215,7 @@ def user_create( uid_guid_found = uid not in all_uid and uid not in all_gid # Adapt values for LDAP - fullname = "{} {}".format(firstname, lastname) + fullname = f"{firstname} {lastname}" attr_dict = { "objectClass": [ @@ -333,8 +333,8 @@ def user_delete(operation_logger, username, purge=False, from_import=False): subprocess.call(["nscd", "-i", "passwd"]) if purge: - subprocess.call(["rm", "-rf", "/home/{}".format(username)]) - subprocess.call(["rm", "-rf", "/var/mail/{}".format(username)]) + subprocess.call(["rm", "-rf", f"/home/{username}"]) + subprocess.call(["rm", "-rf", f"/var/mail/{username}"]) hook_callback("post_user_delete", args=[username, purge]) diff --git a/src/yunohost/utils/network.py b/src/yunohost/utils/network.py index fd70e4d4b..c4dc8723e 100644 --- a/src/yunohost/utils/network.py +++ b/src/yunohost/utils/network.py @@ -44,7 +44,7 @@ def get_public_ip(protocol=4): ): 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{} from cache: {}".format(protocol, ip)) + logger.debug(f"Reusing IPv{protocol} from cache: {ip}") else: ip = get_public_ip_from_remote_server(protocol) logger.debug("IP fetched: %s" % ip) @@ -87,7 +87,7 @@ def get_public_ip_from_remote_server(protocol=4): try: return download_text(url, timeout=30).strip() except Exception as e: - logger.debug("Could not get public IPv{} : {}".format(str(protocol), str(e))) + logger.debug(f"Could not get public IPv{protocol} : {e}")) return None From 377ccd8acf73d2a01757f66fdbedb5a97a15cbee Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 27 Dec 2021 15:19:48 +0100 Subject: [PATCH 1019/1155] Typo /o\ --- 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 c4dc8723e..28dcb204c 100644 --- a/src/yunohost/utils/network.py +++ b/src/yunohost/utils/network.py @@ -87,7 +87,7 @@ def get_public_ip_from_remote_server(protocol=4): try: return download_text(url, timeout=30).strip() except Exception as e: - logger.debug(f"Could not get public IPv{protocol} : {e}")) + logger.debug(f"Could not get public IPv{protocol} : {e}") return None From a063b63d1c86a1f30dac670cea17f2f24e9c944e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 27 Dec 2021 15:35:02 +0100 Subject: [PATCH 1020/1155] ssh config : Invert the password_authentication value check to be more resilient in case something goes wrong while fetching / parsing the value --- data/templates/ssh/sshd_config | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/data/templates/ssh/sshd_config b/data/templates/ssh/sshd_config index 22f0697d9..b6d4111ee 100644 --- a/data/templates/ssh/sshd_config +++ b/data/templates/ssh/sshd_config @@ -2,7 +2,7 @@ # by YunoHost Protocol 2 -# PLEASE: to change ssh port properly in YunoHost, use this command +# PLEASE: if you wish to change the ssh port properly in YunoHost, use this command: # yunohost settings set security.ssh.port -v Port {{ port }} @@ -55,12 +55,12 @@ PermitEmptyPasswords no ChallengeResponseAuthentication no UsePAM yes -# PLEASE: to force everybody to authenticate using ssh keys, run this command: +# PLEASE: if you wish to force everybody to authenticate using ssh keys, run this command: # yunohost settings set security.ssh.password_authentication -v no -{% if password_authentication == "True" %} -#PasswordAuthentication yes -{% else %} +{% if password_authentication == "False" %} PasswordAuthentication no +{% else %} +#PasswordAuthentication yes {% endif %} # Post-login stuff From dce411e0e62193094f6613dfdca081a7ca46a04c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 27 Dec 2021 15:36:38 +0100 Subject: [PATCH 1021/1155] Improve setting description --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 66f42df58..bfd410740 100644 --- a/locales/en.json +++ b/locales/en.json @@ -382,7 +382,7 @@ "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)", "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_password_authentication": "Password authentication allowed", + "global_settings_setting_security_ssh_password_authentication": "Allow password authentication for SSH", "global_settings_setting_security_ssh_port": "SSH port", "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.", From bd04779e402e63cce2ffb819aee96fb8a8436bc6 Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Mon, 27 Dec 2021 15:31:00 +0000 Subject: [PATCH 1022/1155] [CI] Reformat / remove stale translated strings --- locales/de.json | 4 ++-- locales/es.json | 2 +- locales/fr.json | 2 +- locales/gl.json | 2 +- locales/id.json | 2 +- locales/nl.json | 2 +- locales/ru.json | 8 ++++---- locales/uk.json | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/locales/de.json b/locales/de.json index 85d566266..537621f99 100644 --- a/locales/de.json +++ b/locales/de.json @@ -672,7 +672,7 @@ "domain_dns_registrar_managed_in_parent_domain": "Diese Domäne ist eine Unterdomäne von {parent_domain_link}. Die Konfiguration des DNS-Registrars sollte auf der Konfigurationsseite von {parent_domain} verwaltet werden.", "domain_dns_registrar_not_supported": "YunoHost konnte den Registrar, der diese Domäne verwaltet, nicht automatisch erkennen. Du solltest die DNS-Einträge, wie unter https://yunohost.org/dns beschrieben, manuell konfigurieren.", "domain_dns_registrar_supported": "YunoHost hat automatisch erkannt, dass diese Domäne von dem Registrar **{registrar}** verwaltet wird. Wenn Du möchtest, konfiguriert YunoHost diese DNS-Zone automatisch, wenn Du die entsprechenden API-Zugangsdaten zur Verfügung stellst. Auf dieser Seite erfährst Du, wie Du deine API-Anmeldeinformationen erhältst: https://yunohost.org/registar_api_{registrar}. (Du kannst deine DNS-Einträge auch, wie unter https://yunohost.org/dns beschrieben, manuell konfigurieren)", - "service_not_reloading_because_conf_broken": "Der Dienst '{Name}' wird nicht neu geladen/gestartet, da seine Konfiguration fehlerhaft ist: {Fehler}", + "service_not_reloading_because_conf_broken": "Der Dienst '{name}' wird nicht neu geladen/gestartet, da seine Konfiguration fehlerhaft ist: {errors}", "user_import_failed": "Der Import von Benutzer:innen ist komplett fehlgeschlagen", "domain_dns_push_failed_to_list": "Auflistung der aktuellen Einträge über die API des Registrars fehlgeschlagen: {error}", "domain_dns_pushing": "DNS-Einträge übertragen…", @@ -704,4 +704,4 @@ "log_domain_config_set": "Konfiguration für die Domäne '{}' aktualisieren", "log_domain_dns_push": "DNS-Einträge für die Domäne '{}' übertragen", "service_description_yunomdns": "Ermöglicht es dir, deinen Server über 'yunohost.local' in deinem lokalen Netzwerk zu erreichen" -} +} \ No newline at end of file diff --git a/locales/es.json b/locales/es.json index 4afa56996..22493ec11 100644 --- a/locales/es.json +++ b/locales/es.json @@ -616,4 +616,4 @@ "diagnosis_apps_broken": "Esta aplicación está etiquetada como defectuosa en el catálogo de aplicaciones YunoHost. Podría ser un problema temporal mientras las personas responsables corrigen el asunto. Mientras tanto, la actualización de esta aplicación está desactivada.", "diagnosis_apps_deprecated_practices": "La versión instalada de esta aplicación usa aún prácticas de empaquetado obsoletas. Deberías actualizarla.", "diagnosis_apps_outdated_ynh_requirement": "La versión instalada de esta aplicación solo necesita YunoHost >= 2.x, lo que indica que no está al día con la buena praxis de ayudas y empaquetado recomendadas. Deberías actualizarla." -} +} \ No newline at end of file diff --git a/locales/fr.json b/locales/fr.json index 2d70f2cab..cc7088c19 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -718,4 +718,4 @@ "migration_0021_patch_yunohost_conflicts": "Application du correctif pour contourner le problème de conflit...", "migration_0021_not_buster": "La distribution Debian actuelle n'est pas Buster !", "migration_description_0021_migrate_to_bullseye": "Mise à niveau du système vers Debian Bullseye et YunoHost 11.x" -} +} \ No newline at end of file diff --git a/locales/gl.json b/locales/gl.json index 308cba961..3c577b935 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -718,4 +718,4 @@ "migration_description_0021_migrate_to_bullseye": "Actualizar o sistema a Debian Bullseye e YunoHost 11.x", "migration_0021_system_not_fully_up_to_date": "O teu sistema non está completamente actualizado. Fai unha actualización normal antes de executar a migración a Bullseye.", "migration_0021_general_warning": "Ten en conta que a migración é unha operación delicada. O equipo de YunoHost fixo todo o que puido para revisalo e probalo, pero aínda así poderían acontecer fallos no sistema ou apps.\n\nAsí as cousas, é recomendable:\n - Facer unha copia de apoio dos datos e apps importantes. Máis info en https://yunohost.org/backup;\n - Ter paciencia unha vez inicias a migración: dependendo da túa conexión a internet e hardware, podería levarlle varias horas completar o proceso." -} +} \ No newline at end of file diff --git a/locales/id.json b/locales/id.json index 08e09fe8b..47aff8b2e 100644 --- a/locales/id.json +++ b/locales/id.json @@ -55,4 +55,4 @@ "backup_nothings_done": "Tak ada yang harus disimpan", "certmanager_cert_install_success": "Sertifikat Let's Encrypt sekarang sudah terpasang pada domain '{domain}'", "backup_mount_archive_for_restore": "Menyiapkan arsip untuk pemulihan..." -} +} \ No newline at end of file diff --git a/locales/nl.json b/locales/nl.json index 038d18283..1c3e2083d 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -130,4 +130,4 @@ "app_label_deprecated": "Dit commando is vervallen. Gebruik alsjeblieft het nieuwe commando 'yunohost user permission update' om het label van de app te beheren.", "app_change_url_no_script": "De app '{app_name}' ondersteunt nog geen URL-aanpassingen. Misschien wel na een upgrade.", "app_upgrade_some_app_failed": "Sommige apps konden niet worden bijgewerkt" -} +} \ No newline at end of file diff --git a/locales/ru.json b/locales/ru.json index 8fbc3f91b..9c857f7a6 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -147,7 +147,7 @@ "additional_urls_already_removed": "Этот URL '{url}' уже удален из дополнительных URL для разрешения '{permission}'", "app_action_cannot_be_ran_because_required_services_down": "Для выполнения этого действия должны быть запущены следующие службы: {services}. Попробуйте перезапустить их, чтобы продолжить (и, возможно, выяснить, почему они не работают).", "app_unsupported_remote_type": "Неподдерживаемый удаленный тип, используемый для приложения", - "backup_archive_system_part_not_available": "Системная часть '{часть}' недоступна в этой резервной копии", + "backup_archive_system_part_not_available": "Системная часть '{part}' недоступна в этой резервной копии", "backup_output_directory_not_empty": "Вы должны выбрать пустой каталог для сохранения", "backup_archive_writing_error": "Не удалось добавить файлы '{source}' (названные в архиве '{dest}') для резервного копирования в архив '{archive}'", "backup_cant_mount_uncompress_archive": "Не удалось смонтировать несжатый архив как защищенный от записи", @@ -157,7 +157,7 @@ "backup_with_no_backup_script_for_app": "Приложение '{app}' не имеет сценария резервного копирования. Оно будет проигнорировано.", "certmanager_attempt_to_renew_nonLE_cert": "Сертификат для домена '{domain}' не выпущен Let's Encrypt. Невозможно продлить его автоматически!", "certmanager_attempt_to_renew_valid_cert": "Срок действия сертификата для домена '{domain}' НЕ истекает! (Вы можете использовать --force, если знаете, что делаете)", - "certmanager_cannot_read_cert": "При попытке открыть текущий сертификат для домена {домен} произошло что-то неправильное (файл: {file}), причина: {reason}", + "certmanager_cannot_read_cert": "При попытке открыть текущий сертификат для домена {domain} произошло что-то неправильное (файл: {file}), причина: {reason}", "certmanager_cert_install_success": "Сертификат Let's Encrypt для домена '{domain}' установлен", "certmanager_domain_cert_not_selfsigned": "Сертификат для домена {domain} не самоподписанный. Вы уверены, что хотите заменить его? (Для этого используйте '--force'.)", "certmanager_certificate_fetching_or_enabling_failed": "Попытка использовать новый сертификат для {domain} не сработала...", @@ -168,7 +168,7 @@ "yunohost_not_installed": "YunoHost установлен неправильно. Пожалуйста, запустите 'yunohost tools postinstall'", "backup_cleaning_failed": "Не удалось очистить временную папку резервного копирования", "certmanager_attempt_to_replace_valid_cert": "Вы пытаетесь перезаписать хороший и действительный сертификат для домена {domain}! (Используйте --force для обхода)", - "backup_create_size_estimation": "Архив будет содержать около {размер} данных.", + "backup_create_size_estimation": "Архив будет содержать около {size} данных.", "diagnosis_description_regenconf": "Конфигурации системы", "diagnosis_description_services": "Проверка статусов сервисов", "config_validate_color": "Должен быть правильный hex цвета RGB", @@ -189,4 +189,4 @@ "backup_with_no_restore_script_for_app": "{app} не имеет сценария восстановления, вы не сможете автоматически восстановить это приложение из резервной копии.", "diagnosis_description_ports": "Открытые порты", "diagnosis_basesystem_hardware_model": "Модель сервера {model}" -} +} \ No newline at end of file diff --git a/locales/uk.json b/locales/uk.json index 25fa1d551..e997d6bf4 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -718,4 +718,4 @@ "migration_0021_system_not_fully_up_to_date": "Ваша система не повністю оновлена. Будь ласка, виконайте регулярне оновлення перед запуском міграції на Bullseye.", "migration_0021_general_warning": "Будь ласка, зверніть увагу, що ця міграція є делікатною операцією. Команда YunoHost зробила все можливе, щоб перевірити і протестувати її, але міграція все ще може порушити частину системи або її застосунків.\n\nТому рекомендовано:\n - Виконати резервне копіювання всіх важливих даних або застосунків. Подробиці на сайті https://yunohost.org/backup; \n - Наберіться терпіння після запуску міграції: В залежності від вашого з'єднання з Інтернетом і апаратного забезпечення, оновлення може зайняти до декількох годин.", "migration_description_0021_migrate_to_bullseye": "Оновлення системи до Debian Bullseye і YunoHost 11.x" -} +} \ No newline at end of file From 76abbf03d75c20705f85157b86ea18b45dda10f7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Dec 2021 00:35:42 +0100 Subject: [PATCH 1023/1155] diagnosis: bump treshold for suspiciously high number of auth failure because too many people getting report about it idk --- 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 b472a2d32..d003c2670 100644 --- a/data/hooks/diagnosis/00-basesystem.py +++ b/data/hooks/diagnosis/00-basesystem.py @@ -133,7 +133,7 @@ class BaseSystemDiagnoser(Diagnoser): summary="diagnosis_backports_in_sources_list", ) - if self.number_of_recent_auth_failure() > 500: + if self.number_of_recent_auth_failure() > 750: yield dict( meta={"test": "high_number_auth_failure"}, status="WARNING", From 76b9838bfd2c1e2871d74fa1bdcb307c79928c6f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Dec 2021 01:06:45 +0100 Subject: [PATCH 1024/1155] Tmp removal of bullseye migration for small release --- .../0021_migrate_to_bullseye.py | 320 ------------------ 1 file changed, 320 deletions(-) delete mode 100644 src/yunohost/data_migrations/0021_migrate_to_bullseye.py diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py deleted file mode 100644 index f97ab16da..000000000 --- a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py +++ /dev/null @@ -1,320 +0,0 @@ -import glob -import os - -from moulinette import m18n -from yunohost.utils.error import YunohostError -from moulinette.utils.log import getActionLogger -from moulinette.utils.process import check_output, call_async_output -from moulinette.utils.filesystem import read_file, rm - -from yunohost.tools import Migration, tools_update, tools_upgrade -from yunohost.app import unstable_apps -from yunohost.regenconf import manually_modified_files, _force_clear_hashes -from yunohost.utils.filesystem import free_space_in_directory -from yunohost.utils.packages import ( - get_ynh_package_version, - _list_upgradable_apt_packages, -) -from yunohost.service import _get_services, _save_services - -logger = getActionLogger("yunohost.migration") - -N_CURRENT_DEBIAN = 10 -N_CURRENT_YUNOHOST = 4 - -N_NEXT_DEBAN = 11 -N_NEXT_YUNOHOST = 11 - - -class MyMigration(Migration): - - "Upgrade the system to Debian Bullseye and Yunohost 11.x" - - mode = "manual" - - def run(self): - - self.check_assertions() - - logger.info(m18n.n("migration_0021_start")) - - # - # Add new apt .deb signing key - # - - new_apt_key = "https://forge.yunohost.org/yunohost_bullseye.asc" - check_output(f"wget -O- {new_apt_key} -q | apt-key add -qq -") - - # - # Patch sources.list - # - logger.info(m18n.n("migration_0021_patching_sources_list")) - self.patch_apt_sources_list() - tools_update(target="system") - - # Tell libc6 it's okay to restart system stuff during the upgrade - os.system( - "echo 'libc6 libraries/restart-without-asking boolean true' | debconf-set-selections" - ) - - # Don't send an email to root about the postgresql migration. It should be handled automatically after. - os.system( - "echo 'postgresql-common postgresql-common/obsolete-major seen true' | debconf-set-selections" - ) - - # - # Patch yunohost conflicts - # - logger.info(m18n.n("migration_0021_patch_yunohost_conflicts")) - - self.patch_yunohost_conflicts() - - # - # Specific tweaking to get rid of custom my.cnf and use debian's default one - # (my.cnf is actually a symlink to mariadb.cnf) - # - - _force_clear_hashes(["/etc/mysql/my.cnf"]) - rm("/etc/mysql/mariadb.cnf", force=True) - rm("/etc/mysql/my.cnf", force=True) - self.apt_install( - "mariadb-common --reinstall -o Dpkg::Options::='--force-confmiss'" - ) - - # - # /usr/share/yunohost/yunohost-config/ssl/yunoCA -> /usr/share/yunohost/ssl - # - if os.path.exists("/usr/share/yunohost/yunohost-config/ssl/yunoCA"): - os.system( - "mv /usr/share/yunohost/yunohost-config/ssl/yunoCA /usr/share/yunohost/ssl" - ) - rm("/usr/share/yunohost/yunohost-config", recursive=True, force=True) - - # - # /home/yunohost.conf -> /var/cache/yunohost/regenconf - # - if os.path.exists("/home/yunohost.conf"): - os.system("mv /home/yunohost.conf /var/cache/yunohost/regenconf") - rm("/home/yunohost.conf", recursive=True, force=True) - - # - # Main upgrade - # - logger.info(m18n.n("migration_0021_main_upgrade")) - - apps_packages = self.get_apps_equivs_packages() - self.hold(apps_packages) - tools_upgrade(target="system", allow_yunohost_upgrade=False) - - if self.debian_major_version() == N_CURRENT_DEBIAN: - raise YunohostError("migration_0021_still_on_buster_after_main_upgrade") - - # Clean the mess - logger.info(m18n.n("migration_0021_cleaning_up")) - os.system("apt autoremove --assume-yes") - os.system("apt clean --assume-yes") - - # Force add sury if it's not there yet - # This is to solve some weird issue with php-common breaking php7.3-common, - # hence breaking many php7.3-deps - # hence triggering some dependency conflict (or foobar-ynh-deps uninstall) - # Adding it there shouldnt be a big deal - Yunohost 11.x does add it - # through its regen conf anyway. - if not os.path.exists("/etc/apt/sources.list.d/extra_php_version.list"): - open("/etc/apt/sources.list.d/extra_php_version.list", "w").write( - "deb https://packages.sury.org/php/ bullseye main" - ) - os.system( - 'wget --timeout 900 --quiet "https://packages.sury.org/php/apt.gpg" --output-document=- | gpg --dearmor >"/etc/apt/trusted.gpg.d/extra_php_version.gpg"' - ) - - os.system("apt update") - - # Force explicit install of php7.4-fpm to make sure it's ll be there - # during 0022_php73_to_php74_pools migration - self.apt_install("php7.4-fpm -o Dpkg::Options::='--force-confmiss'") - - # Remove legacy postgresql service record added by helpers, - # will now be dynamically handled by the core in bullseye - services = _get_services() - if "postgresql" in services: - del services["postgresql"] - _save_services(services) - - # - # Yunohost upgrade - # - logger.info(m18n.n("migration_0021_yunohost_upgrade")) - self.unhold(apps_packages) - tools_upgrade(target="system") - - def debian_major_version(self): - # The python module "platform" and lsb_release are not reliable because - # on some setup, they may still return Release=9 even after upgrading to - # buster ... (Apparently this is related to OVH overriding some stuff - # with /etc/lsb-release for instance -_-) - # Instead, we rely on /etc/os-release which should be the raw info from - # the distribution... - 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_ynh_package_version("yunohost")["version"].split(".")[0]) - - def check_assertions(self): - - # Be on buster (10.x) and yunohost 4.x - # NB : we do both check to cover situations where the upgrade crashed - # in the middle and debian version could be > 9.x but yunohost package - # would still be in 3.x... - if ( - not self.debian_major_version() == N_CURRENT_DEBIAN - and not self.yunohost_major_version() == N_CURRENT_YUNOHOST - ): - raise YunohostError("migration_0021_not_buster") - - # Have > 1 Go free space on /var/ ? - if free_space_in_directory("/var/") / (1024 ** 3) < 1.0: - raise YunohostError("migration_0021_not_enough_free_space") - - # Check system is up to date - # (but we don't if 'bullseye' is already in the sources.list ... - # which means maybe a previous upgrade crashed and we're re-running it) - if " bullseye " not in read_file("/etc/apt/sources.list"): - tools_update(target="system") - upgradable_system_packages = list(_list_upgradable_apt_packages()) - if upgradable_system_packages: - raise YunohostError("migration_0021_system_not_fully_up_to_date") - - @property - def disclaimer(self): - - # Avoid having a super long disclaimer + uncessary check if we ain't - # on buster / yunohost 4.x anymore - # NB : we do both check to cover situations where the upgrade crashed - # in the middle and debian version could be >= 10.x but yunohost package - # would still be in 4.x... - if ( - not self.debian_major_version() == N_CURRENT_DEBIAN - and not self.yunohost_major_version() == N_CURRENT_YUNOHOST - ): - return None - - # Get list of problematic apps ? I.e. not official or community+working - problematic_apps = unstable_apps() - problematic_apps = "".join(["\n - " + app for app in problematic_apps]) - - # Manually modified files ? (c.f. yunohost service regen-conf) - modified_files = manually_modified_files() - modified_files = "".join(["\n - " + f for f in modified_files]) - - message = m18n.n("migration_0021_general_warning") - - # FIXME: re-enable this message with updated topic link once we release the migration as stable - # message = ( - # "N.B.: This migration has been tested by the community over the last few months but has only been declared stable recently. If your server hosts critical services and if you are not too confident with debugging possible issues, we recommend you to wait a little bit more while we gather more feedback and polish things up. If on the other hand you are relatively confident with debugging small issues that may arise, you are encouraged to run this migration ;)! You can read about remaining known issues and feedback from the community here: https://forum.yunohost.org/t/12195\n\n" - # + message - # ) - - if problematic_apps: - message += "\n\n" + m18n.n( - "migration_0021_problematic_apps_warning", - problematic_apps=problematic_apps, - ) - - if modified_files: - message += "\n\n" + m18n.n( - "migration_0021_modified_files", manually_modified_files=modified_files - ) - - return message - - def patch_apt_sources_list(self): - - sources_list = glob.glob("/etc/apt/sources.list.d/*.list") - sources_list.append("/etc/apt/sources.list") - - # This : - # - replace single 'buster' occurence by 'bulleye' - # - comments lines containing "backports" - # - replace 'buster/updates' by 'bullseye/updates' (or same with -) - # Special note about the security suite: - # https://www.debian.org/releases/bullseye/amd64/release-notes/ch-information.en.html#security-archive - for f in sources_list: - command = ( - f"sed -i {f} " - "-e 's@ buster @ bullseye @g' " - "-e '/backports/ s@^#*@#@' " - "-e 's@ buster/updates @ bullseye-security @g' " - "-e 's@ buster-@ bullseye-@g' " - ) - os.system(command) - - def get_apps_equivs_packages(self): - - command = ( - "dpkg --get-selections" - " | grep -v deinstall" - " | awk '{print $1}'" - " | { grep 'ynh-deps$' || true; }" - ) - - output = check_output(command) - - return output.split("\n") if output else [] - - def hold(self, packages): - for package in packages: - os.system("apt-mark hold {}".format(package)) - - def unhold(self, packages): - for package in packages: - os.system("apt-mark unhold {}".format(package)) - - def apt_install(self, cmd): - def is_relevant(line): - return "Reading database ..." not in line.rstrip() - - callbacks = ( - lambda l: logger.info("+ " + l.rstrip() + "\r") - if is_relevant(l) - else logger.debug(l.rstrip() + "\r"), - lambda l: logger.warning(l.rstrip()), - ) - - cmd = ( - "LC_ALL=C DEBIAN_FRONTEND=noninteractive APT_LISTCHANGES_FRONTEND=none apt install --quiet -o=Dpkg::Use-Pty=0 --fix-broken --assume-yes " - + cmd - ) - - logger.debug("Running: %s" % cmd) - - call_async_output(cmd, callbacks, shell=True) - - def patch_yunohost_conflicts(self): - # - # This is a super dirty hack to remove the conflicts from yunohost's debian/control file - # Those conflicts are there to prevent mistakenly upgrading critical packages - # such as dovecot, postfix, nginx, openssl, etc... usually related to mistakenly - # using backports etc. - # - # The hack consists in savagely removing the conflicts directly in /var/lib/dpkg/status - # - - # We only patch the conflict if we're on yunohost 4.x - if self.yunohost_major_version() != N_CURRENT_YUNOHOST: - return - - conflicts = check_output("dpkg-query -s yunohost | grep '^Conflicts:'").strip() - if conflicts: - # We want to keep conflicting with apache/bind9 tho - new_conflicts = "Conflicts: apache2, bind9" - - command = ( - f"sed -i /var/lib/dpkg/status -e 's@{conflicts}@{new_conflicts}@g'" - ) - logger.debug(f"Running: {command}") - os.system(command) From 509ba1e8a28e0be598aa0617eda06669b7b0f1d8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Dec 2021 01:07:09 +0100 Subject: [PATCH 1025/1155] Update changelog for 4.3.5 --- debian/changelog | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/debian/changelog b/debian/changelog index 001ab678a..49227f946 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,19 @@ +yunohost (4.3.5) stable; urgency=low + + - [fix] backup: bug in backup_delete when compress_tar_archives is True ([#1381](https://github.com/YunoHost/yunohost/pull/1381)) + - [fix] helpers logrorate: remove permission tweak .. code was not working as expected. To be re-addressed some day ... (0fc209ac) + - [fix] i18n: consistency for deprecation for --apps in 'yunohost tools update/upgrade' ([#1392](https://github.com/YunoHost/yunohost/pull/1392)) + - [fix] apps: typo when deleting superfluous question keys ([#1393](https://github.com/YunoHost/yunohost/pull/1393)) + - [fix] diagnosis: typo in dns record diagnoser (a615528c) + - [fix] diagnosis: tweak treshold for suspiciously high number of auth failure because too many people getting report about it idk (76abbf03) + - [enh] quality: apply pyupgrade ([#1395](https://github.com/YunoHost/yunohost/pull/1395)) + - [enh] quality: add lgtm/code quality badge ([#1396](https://github.com/YunoHost/yunohost/pull/1396)) + - [i18n] Translations updated for Dutch, French, Galician, German, Indonesian, Russian, Spanish, Ukrainian + + Thanks to all contributors <3 ! (Boudewijn, Bram, Christian Wehrli, Colin Wawrik, Éric Gaspar, Ilya, José M, Juan Alberto González, Kay0u, liimee, Moutonjr Geoff, tituspijean, Tymofii Lytvynenko, Valentin von Guttenberg) + + -- Alexandre Aubin Wed, 29 Dec 2021 01:01:33 +0100 + yunohost (4.3.4.2) stable; urgency=low - [fix] yunomdns: Ignore ipv4 link-local addresses (6854f23c) From 2f38639a2ed61c7b1195fc3f9cf7a1dfee84c60c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Dec 2021 01:07:34 +0100 Subject: [PATCH 1026/1155] Revert "Tmp removal of bullseye migration for small release" This reverts commit 76b9838bfd2c1e2871d74fa1bdcb307c79928c6f. --- .../0021_migrate_to_bullseye.py | 320 ++++++++++++++++++ 1 file changed, 320 insertions(+) create mode 100644 src/yunohost/data_migrations/0021_migrate_to_bullseye.py diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py new file mode 100644 index 000000000..f97ab16da --- /dev/null +++ b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py @@ -0,0 +1,320 @@ +import glob +import os + +from moulinette import m18n +from yunohost.utils.error import YunohostError +from moulinette.utils.log import getActionLogger +from moulinette.utils.process import check_output, call_async_output +from moulinette.utils.filesystem import read_file, rm + +from yunohost.tools import Migration, tools_update, tools_upgrade +from yunohost.app import unstable_apps +from yunohost.regenconf import manually_modified_files, _force_clear_hashes +from yunohost.utils.filesystem import free_space_in_directory +from yunohost.utils.packages import ( + get_ynh_package_version, + _list_upgradable_apt_packages, +) +from yunohost.service import _get_services, _save_services + +logger = getActionLogger("yunohost.migration") + +N_CURRENT_DEBIAN = 10 +N_CURRENT_YUNOHOST = 4 + +N_NEXT_DEBAN = 11 +N_NEXT_YUNOHOST = 11 + + +class MyMigration(Migration): + + "Upgrade the system to Debian Bullseye and Yunohost 11.x" + + mode = "manual" + + def run(self): + + self.check_assertions() + + logger.info(m18n.n("migration_0021_start")) + + # + # Add new apt .deb signing key + # + + new_apt_key = "https://forge.yunohost.org/yunohost_bullseye.asc" + check_output(f"wget -O- {new_apt_key} -q | apt-key add -qq -") + + # + # Patch sources.list + # + logger.info(m18n.n("migration_0021_patching_sources_list")) + self.patch_apt_sources_list() + tools_update(target="system") + + # Tell libc6 it's okay to restart system stuff during the upgrade + os.system( + "echo 'libc6 libraries/restart-without-asking boolean true' | debconf-set-selections" + ) + + # Don't send an email to root about the postgresql migration. It should be handled automatically after. + os.system( + "echo 'postgresql-common postgresql-common/obsolete-major seen true' | debconf-set-selections" + ) + + # + # Patch yunohost conflicts + # + logger.info(m18n.n("migration_0021_patch_yunohost_conflicts")) + + self.patch_yunohost_conflicts() + + # + # Specific tweaking to get rid of custom my.cnf and use debian's default one + # (my.cnf is actually a symlink to mariadb.cnf) + # + + _force_clear_hashes(["/etc/mysql/my.cnf"]) + rm("/etc/mysql/mariadb.cnf", force=True) + rm("/etc/mysql/my.cnf", force=True) + self.apt_install( + "mariadb-common --reinstall -o Dpkg::Options::='--force-confmiss'" + ) + + # + # /usr/share/yunohost/yunohost-config/ssl/yunoCA -> /usr/share/yunohost/ssl + # + if os.path.exists("/usr/share/yunohost/yunohost-config/ssl/yunoCA"): + os.system( + "mv /usr/share/yunohost/yunohost-config/ssl/yunoCA /usr/share/yunohost/ssl" + ) + rm("/usr/share/yunohost/yunohost-config", recursive=True, force=True) + + # + # /home/yunohost.conf -> /var/cache/yunohost/regenconf + # + if os.path.exists("/home/yunohost.conf"): + os.system("mv /home/yunohost.conf /var/cache/yunohost/regenconf") + rm("/home/yunohost.conf", recursive=True, force=True) + + # + # Main upgrade + # + logger.info(m18n.n("migration_0021_main_upgrade")) + + apps_packages = self.get_apps_equivs_packages() + self.hold(apps_packages) + tools_upgrade(target="system", allow_yunohost_upgrade=False) + + if self.debian_major_version() == N_CURRENT_DEBIAN: + raise YunohostError("migration_0021_still_on_buster_after_main_upgrade") + + # Clean the mess + logger.info(m18n.n("migration_0021_cleaning_up")) + os.system("apt autoremove --assume-yes") + os.system("apt clean --assume-yes") + + # Force add sury if it's not there yet + # This is to solve some weird issue with php-common breaking php7.3-common, + # hence breaking many php7.3-deps + # hence triggering some dependency conflict (or foobar-ynh-deps uninstall) + # Adding it there shouldnt be a big deal - Yunohost 11.x does add it + # through its regen conf anyway. + if not os.path.exists("/etc/apt/sources.list.d/extra_php_version.list"): + open("/etc/apt/sources.list.d/extra_php_version.list", "w").write( + "deb https://packages.sury.org/php/ bullseye main" + ) + os.system( + 'wget --timeout 900 --quiet "https://packages.sury.org/php/apt.gpg" --output-document=- | gpg --dearmor >"/etc/apt/trusted.gpg.d/extra_php_version.gpg"' + ) + + os.system("apt update") + + # Force explicit install of php7.4-fpm to make sure it's ll be there + # during 0022_php73_to_php74_pools migration + self.apt_install("php7.4-fpm -o Dpkg::Options::='--force-confmiss'") + + # Remove legacy postgresql service record added by helpers, + # will now be dynamically handled by the core in bullseye + services = _get_services() + if "postgresql" in services: + del services["postgresql"] + _save_services(services) + + # + # Yunohost upgrade + # + logger.info(m18n.n("migration_0021_yunohost_upgrade")) + self.unhold(apps_packages) + tools_upgrade(target="system") + + def debian_major_version(self): + # The python module "platform" and lsb_release are not reliable because + # on some setup, they may still return Release=9 even after upgrading to + # buster ... (Apparently this is related to OVH overriding some stuff + # with /etc/lsb-release for instance -_-) + # Instead, we rely on /etc/os-release which should be the raw info from + # the distribution... + 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_ynh_package_version("yunohost")["version"].split(".")[0]) + + def check_assertions(self): + + # Be on buster (10.x) and yunohost 4.x + # NB : we do both check to cover situations where the upgrade crashed + # in the middle and debian version could be > 9.x but yunohost package + # would still be in 3.x... + if ( + not self.debian_major_version() == N_CURRENT_DEBIAN + and not self.yunohost_major_version() == N_CURRENT_YUNOHOST + ): + raise YunohostError("migration_0021_not_buster") + + # Have > 1 Go free space on /var/ ? + if free_space_in_directory("/var/") / (1024 ** 3) < 1.0: + raise YunohostError("migration_0021_not_enough_free_space") + + # Check system is up to date + # (but we don't if 'bullseye' is already in the sources.list ... + # which means maybe a previous upgrade crashed and we're re-running it) + if " bullseye " not in read_file("/etc/apt/sources.list"): + tools_update(target="system") + upgradable_system_packages = list(_list_upgradable_apt_packages()) + if upgradable_system_packages: + raise YunohostError("migration_0021_system_not_fully_up_to_date") + + @property + def disclaimer(self): + + # Avoid having a super long disclaimer + uncessary check if we ain't + # on buster / yunohost 4.x anymore + # NB : we do both check to cover situations where the upgrade crashed + # in the middle and debian version could be >= 10.x but yunohost package + # would still be in 4.x... + if ( + not self.debian_major_version() == N_CURRENT_DEBIAN + and not self.yunohost_major_version() == N_CURRENT_YUNOHOST + ): + return None + + # Get list of problematic apps ? I.e. not official or community+working + problematic_apps = unstable_apps() + problematic_apps = "".join(["\n - " + app for app in problematic_apps]) + + # Manually modified files ? (c.f. yunohost service regen-conf) + modified_files = manually_modified_files() + modified_files = "".join(["\n - " + f for f in modified_files]) + + message = m18n.n("migration_0021_general_warning") + + # FIXME: re-enable this message with updated topic link once we release the migration as stable + # message = ( + # "N.B.: This migration has been tested by the community over the last few months but has only been declared stable recently. If your server hosts critical services and if you are not too confident with debugging possible issues, we recommend you to wait a little bit more while we gather more feedback and polish things up. If on the other hand you are relatively confident with debugging small issues that may arise, you are encouraged to run this migration ;)! You can read about remaining known issues and feedback from the community here: https://forum.yunohost.org/t/12195\n\n" + # + message + # ) + + if problematic_apps: + message += "\n\n" + m18n.n( + "migration_0021_problematic_apps_warning", + problematic_apps=problematic_apps, + ) + + if modified_files: + message += "\n\n" + m18n.n( + "migration_0021_modified_files", manually_modified_files=modified_files + ) + + return message + + def patch_apt_sources_list(self): + + sources_list = glob.glob("/etc/apt/sources.list.d/*.list") + sources_list.append("/etc/apt/sources.list") + + # This : + # - replace single 'buster' occurence by 'bulleye' + # - comments lines containing "backports" + # - replace 'buster/updates' by 'bullseye/updates' (or same with -) + # Special note about the security suite: + # https://www.debian.org/releases/bullseye/amd64/release-notes/ch-information.en.html#security-archive + for f in sources_list: + command = ( + f"sed -i {f} " + "-e 's@ buster @ bullseye @g' " + "-e '/backports/ s@^#*@#@' " + "-e 's@ buster/updates @ bullseye-security @g' " + "-e 's@ buster-@ bullseye-@g' " + ) + os.system(command) + + def get_apps_equivs_packages(self): + + command = ( + "dpkg --get-selections" + " | grep -v deinstall" + " | awk '{print $1}'" + " | { grep 'ynh-deps$' || true; }" + ) + + output = check_output(command) + + return output.split("\n") if output else [] + + def hold(self, packages): + for package in packages: + os.system("apt-mark hold {}".format(package)) + + def unhold(self, packages): + for package in packages: + os.system("apt-mark unhold {}".format(package)) + + def apt_install(self, cmd): + def is_relevant(line): + return "Reading database ..." not in line.rstrip() + + callbacks = ( + lambda l: logger.info("+ " + l.rstrip() + "\r") + if is_relevant(l) + else logger.debug(l.rstrip() + "\r"), + lambda l: logger.warning(l.rstrip()), + ) + + cmd = ( + "LC_ALL=C DEBIAN_FRONTEND=noninteractive APT_LISTCHANGES_FRONTEND=none apt install --quiet -o=Dpkg::Use-Pty=0 --fix-broken --assume-yes " + + cmd + ) + + logger.debug("Running: %s" % cmd) + + call_async_output(cmd, callbacks, shell=True) + + def patch_yunohost_conflicts(self): + # + # This is a super dirty hack to remove the conflicts from yunohost's debian/control file + # Those conflicts are there to prevent mistakenly upgrading critical packages + # such as dovecot, postfix, nginx, openssl, etc... usually related to mistakenly + # using backports etc. + # + # The hack consists in savagely removing the conflicts directly in /var/lib/dpkg/status + # + + # We only patch the conflict if we're on yunohost 4.x + if self.yunohost_major_version() != N_CURRENT_YUNOHOST: + return + + conflicts = check_output("dpkg-query -s yunohost | grep '^Conflicts:'").strip() + if conflicts: + # We want to keep conflicting with apache/bind9 tho + new_conflicts = "Conflicts: apache2, bind9" + + command = ( + f"sed -i /var/lib/dpkg/status -e 's@{conflicts}@{new_conflicts}@g'" + ) + logger.debug(f"Running: {command}") + os.system(command) From 94bf107838ee97fd2a544a1036c6410daa6a89e4 Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Wed, 29 Dec 2021 00:44:22 +0000 Subject: [PATCH 1027/1155] [CI] Format code with Black --- data/hooks/diagnosis/00-basesystem.py | 4 +++- src/yunohost/dyndns.py | 4 +--- src/yunohost/permission.py | 6 +++--- src/yunohost/tests/test_permission.py | 4 +--- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/data/hooks/diagnosis/00-basesystem.py b/data/hooks/diagnosis/00-basesystem.py index a60070b5a..e30efc7fc 100644 --- a/data/hooks/diagnosis/00-basesystem.py +++ b/data/hooks/diagnosis/00-basesystem.py @@ -42,7 +42,9 @@ class BaseSystemDiagnoser(Diagnoser): elif os.path.exists("/sys/devices/virtual/dmi/id/sys_vendor"): model = read_file("/sys/devices/virtual/dmi/id/sys_vendor").strip() if os.path.exists("/sys/devices/virtual/dmi/id/product_name"): - product_name = read_file("/sys/devices/virtual/dmi/id/product_name").strip() + product_name = read_file( + "/sys/devices/virtual/dmi/id/product_name" + ).strip() model = f"{model} {product_name}" hardware["data"]["model"] = model hardware["details"] = ["diagnosis_basesystem_hardware_model"] diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 8748c1c00..ebf04c008 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -262,9 +262,7 @@ def dyndns_update( else: return None - raise YunohostError( - f"Failed to resolve {rdtype} for {domain}", raw_msg=True - ) + raise YunohostError(f"Failed to resolve {rdtype} for {domain}", raw_msg=True) old_ipv4 = resolve_domain(domain, "A") old_ipv6 = resolve_domain(domain, "AAAA") diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 9a46cad27..e87715e63 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -666,9 +666,9 @@ def permission_sync_to_user(): # 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 = { - user - for group in permission_infos["allowed"] - for user in groups[group]["members"] + user + for group in permission_infos["allowed"] + for user in groups[group]["members"] } # Note that a LDAP operation with the same value that is in LDAP crash SLAP. diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index 01b62f2a8..9c059f0e4 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -1013,9 +1013,7 @@ def test_permission_app_install(): assert res["permissions_app.dev"]["url"] == "/dev" assert res["permissions_app.main"]["allowed"] == ["all_users"] - assert set(res["permissions_app.main"]["corresponding_users"]) == { - "alice", "bob" - } + assert set(res["permissions_app.main"]["corresponding_users"]) == {"alice", "bob"} assert res["permissions_app.admin"]["allowed"] == ["alice"] assert res["permissions_app.admin"]["corresponding_users"] == ["alice"] From de149975d2c235fe29beba1eee4108d5c9f5532d Mon Sep 17 00:00:00 2001 From: Kay0u Date: Wed, 29 Dec 2021 22:40:54 +0100 Subject: [PATCH 1028/1155] Remove unused variable --- 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 cedd3891e..554576f79 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -56,7 +56,6 @@ class DNSRecordsDiagnoser(Diagnoser): def check_domain(self, domain, is_main_domain): if is_special_use_tld(domain): - categories = [] yield dict( meta={"domain": domain}, data={}, From f92c88aff7abdd9812a32592e0dd7fa052937b58 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Wed, 29 Dec 2021 22:41:57 +0100 Subject: [PATCH 1029/1155] remove unused lines --- src/yunohost/app.py | 1 - src/yunohost/dns.py | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index d5e57d6cf..bf41f163d 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1033,7 +1033,6 @@ def app_remove(operation_logger, app, purge=False): remove_script = f"{tmp_workdir_for_app}/scripts/remove" env_dict = {} - app_id, app_instance_nb = _parse_app_instance_name(app) env_dict = _make_environment_for_app_script(app, workdir=tmp_workdir_for_app) env_dict["YNH_APP_PURGE"] = str(1 if purge else 0) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 23c641a35..492b285c0 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -857,7 +857,6 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= 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": @@ -867,7 +866,7 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= f"{name:>20} [{t:^5}] {old_content:^30} -> {new_content:^30} {ignored}" ) elif action == "unchanged": - old_content = new_content = record.get("content", "(None)")[:30] + old_content = record.get("content", "(None)")[:30] return f"{name:>20} [{t:^5}] {old_content:^30}" else: old_content = record.get("content", "(None)")[:30] From 2cc09aca91c97ae2bfbda9d224a93cd422b206ae Mon Sep 17 00:00:00 2001 From: Kay0u Date: Wed, 29 Dec 2021 22:45:25 +0100 Subject: [PATCH 1030/1155] some improvements --- .../0016_php70_to_php73_pools.py | 12 +++---- src/yunohost/service.py | 2 +- src/yunohost/tools.py | 34 ++++++++----------- 3 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/yunohost/data_migrations/0016_php70_to_php73_pools.py b/src/yunohost/data_migrations/0016_php70_to_php73_pools.py index fed96c9c8..570e63ea4 100644 --- a/src/yunohost/data_migrations/0016_php70_to_php73_pools.py +++ b/src/yunohost/data_migrations/0016_php70_to_php73_pools.py @@ -38,11 +38,11 @@ class MyMigration(Migration): # Ignore the "www.conf" (default stuff, probably don't want to touch it ?) php70_pool_files = [f for f in php70_pool_files if f != "www.conf"] - for f in php70_pool_files: + for pf in php70_pool_files: # Copy the files to the php7.3 pool - src = "{}/{}".format(PHP70_POOLS, f) - dest = "{}/{}".format(PHP73_POOLS, f) + src = "{}/{}".format(PHP70_POOLS, pf) + dest = "{}/{}".format(PHP73_POOLS, pf) copy2(src, dest) # Replace the socket prefix if it's found @@ -56,17 +56,17 @@ class MyMigration(Migration): c = "sed -i '1i {}' {}".format(MIGRATION_COMMENT, dest) os.system(c) - app_id = os.path.basename(f)[: -len(".conf")] + app_id = os.path.basename(pf)[: -len(".conf")] if _is_installed(app_id): _patch_legacy_php_versions_in_settings( "/etc/yunohost/apps/%s/" % app_id ) nginx_conf_files = glob.glob("/etc/nginx/conf.d/*.d/%s.conf" % app_id) - for f in nginx_conf_files: + for nf in nginx_conf_files: # Replace the socket prefix if it's found c = "sed -i -e 's@{}@{}@g' {}".format( - PHP70_SOCKETS_PREFIX, PHP73_SOCKETS_PREFIX, f + PHP70_SOCKETS_PREFIX, PHP73_SOCKETS_PREFIX, nf ) os.system(c) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 4f453dfe9..8a7b146b1 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -587,7 +587,7 @@ def service_regen_conf( if name not in services.keys(): raise YunohostValidationError("service_unknown", service=name) - if names is []: + if names == []: names = list(services.keys()) logger.warning(m18n.n("service_regen_conf_is_deprecated")) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 679b4d16e..a0411c69e 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -236,28 +236,24 @@ def tools_postinstall( # If this is a nohost.me/noho.st, actually check for availability if not ignore_dyndns and is_yunohost_dyndns_domain(domain): - # (Except if the user explicitly said he/she doesn't care about dyndns) - if ignore_dyndns: - dyndns = False # Check if the domain is available... - else: - try: - available = _dyndns_available(domain) - # If an exception is thrown, most likely we don't have internet - # connectivity or something. Assume that this domain isn't manageable - # and inform the user that we could not contact the dyndns host server. - except Exception: - logger.warning( - m18n.n( - "dyndns_provider_unreachable", provider="dyndns.yunohost.org" - ) + try: + available = _dyndns_available(domain) + # If an exception is thrown, most likely we don't have internet + # connectivity or something. Assume that this domain isn't manageable + # and inform the user that we could not contact the dyndns host server. + except Exception: + logger.warning( + m18n.n( + "dyndns_provider_unreachable", provider="dyndns.yunohost.org" ) + ) - if available: - dyndns = True - # If not, abort the postinstall - else: - raise YunohostValidationError("dyndns_unavailable", domain=domain) + if available: + dyndns = True + # If not, abort the postinstall + else: + raise YunohostValidationError("dyndns_unavailable", domain=domain) else: dyndns = False From bbe0486935615779925b4a11ba239395a095f33d Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 30 Dec 2021 00:08:49 +0100 Subject: [PATCH 1031/1155] Trying to fix some lgtm warnings --- src/yunohost/app.py | 7 ++- src/yunohost/certificate.py | 52 +++++++++---------- .../data_migrations/0018_xtable_to_nftable.py | 6 ++- src/yunohost/domain.py | 20 +++---- src/yunohost/regenconf.py | 5 +- src/yunohost/tools.py | 2 +- src/yunohost/user.py | 24 ++++----- src/yunohost/utils/config.py | 5 +- 8 files changed, 66 insertions(+), 55 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index bf41f163d..ca56be232 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -627,7 +627,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( @@ -1626,9 +1626,12 @@ class AppConfigPanel(ConfigPanel): error=message, ) - def _call_config_script(self, action, env={}): + def _call_config_script(self, action, env=None): from yunohost.hook import hook_exec + if env is None: + env = {} + # Add default config script if needed config_script = os.path.join( APPS_SETTING_PATH, self.entity, "scripts", "config" diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 79e3ae092..2f3676202 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -70,28 +70,28 @@ PRODUCTION_CERTIFICATION_AUTHORITY = "https://acme-v02.api.letsencrypt.org" # -def certificate_status(domain_list, full=False): +def certificate_status(domains, full=False): """ Print the status of certificate for given domains (all by default) Keyword argument: - domain_list -- Domains to be checked + domains -- Domains to be checked full -- Display more info about the certificates """ - import yunohost.domain + from yunohost.domain import domain_list, _assert_domain_exists # If no domains given, consider all yunohost domains - if domain_list == []: - domain_list = yunohost.domain.domain_list()["domains"] + if domains == []: + domains = domain_list()["domains"] # Else, validate that yunohost knows the domains given else: - for domain in domain_list: - yunohost.domain._assert_domain_exists(domain) + for domain in domains: + _assert_domain_exists(domain) certificates = {} - for domain in domain_list: + for domain in domains: status = _get_status(domain) if not full: @@ -239,28 +239,28 @@ def _certificate_install_selfsigned(domain_list, force=False): def _certificate_install_letsencrypt( - domain_list, force=False, no_checks=False, staging=False + domains, force=False, no_checks=False, staging=False ): - import yunohost.domain + from yunohost.domain import domain_list, _assert_domain_exists if not os.path.exists(ACCOUNT_KEY_FILE): _generate_account_key() # If no domains given, consider all yunohost domains with self-signed # certificates - if domain_list == []: - for domain in yunohost.domain.domain_list()["domains"]: + if domains == []: + for domain in domain_list()["domains"]: status = _get_status(domain) if status["CA_type"]["code"] != "self-signed": continue - domain_list.append(domain) + domains.append(domain) # Else, validate that yunohost knows the domains given else: - for domain in domain_list: - yunohost.domain._assert_domain_exists(domain) + for domain in domains: + _assert_domain_exists(domain) # Is it self-signed? status = _get_status(domain) @@ -275,7 +275,7 @@ def _certificate_install_letsencrypt( ) # Actual install steps - for domain in domain_list: + for domain in domains: if not no_checks: try: @@ -311,25 +311,25 @@ def _certificate_install_letsencrypt( def certificate_renew( - domain_list, force=False, no_checks=False, email=False, staging=False + domains, force=False, no_checks=False, email=False, staging=False ): """ Renew Let's Encrypt certificate for given domains (all by default) Keyword argument: - domain_list -- Domains for which to renew the certificates + domains -- Domains for which to renew the certificates force -- Ignore the validity threshold (15 days) no-check -- Disable some checks about the reachability of web server before attempting the renewing email -- Emails root if some renewing failed """ - import yunohost.domain + from yunohost.domain import domain_list, _assert_domain_exists # If no domains given, consider all yunohost domains with Let's Encrypt # certificates - if domain_list == []: - for domain in yunohost.domain.domain_list()["domains"]: + if domains == []: + for domain in domain_list()["domains"]: # Does it have a Let's Encrypt cert? status = _get_status(domain) @@ -347,17 +347,17 @@ def certificate_renew( ) continue - domain_list.append(domain) + domains.append(domain) - if len(domain_list) == 0 and not email: + if len(domains) == 0 and not email: logger.info("No certificate needs to be renewed.") # Else, validate the domain list given else: - for domain in domain_list: + for domain in domains: # Is it in Yunohost domain list? - yunohost.domain._assert_domain_exists(domain) + _assert_domain_exists(domain) status = _get_status(domain) @@ -385,7 +385,7 @@ def certificate_renew( ) # Actual renew steps - for domain in domain_list: + for domain in domains: if not no_checks: try: diff --git a/src/yunohost/data_migrations/0018_xtable_to_nftable.py b/src/yunohost/data_migrations/0018_xtable_to_nftable.py index 94b47d944..374620f2f 100644 --- a/src/yunohost/data_migrations/0018_xtable_to_nftable.py +++ b/src/yunohost/data_migrations/0018_xtable_to_nftable.py @@ -41,7 +41,8 @@ class MyMigration(Migration): ) # For some reason if we don't do this, iptables-legacy-save is empty ? self.runcmd("iptables-legacy-save > %s" % self.backup_rules_ipv4) assert ( - open(self.backup_rules_ipv4).read().strip() + os.path.exists(self.backup_rules_ipv4) and + os.stat(self.backup_rules_ipv4).st_size > 0 ), "Uhoh backup of legacy ipv4 rules is empty !?" if self.do_ipv6 and not os.path.exists(self.backup_rules_ipv6): self.runcmd( @@ -49,7 +50,8 @@ class MyMigration(Migration): ) # For some reason if we don't do this, iptables-legacy-save is empty ? self.runcmd("ip6tables-legacy-save > %s" % self.backup_rules_ipv6) assert ( - open(self.backup_rules_ipv6).read().strip() + os.path.exists(self.backup_rules_ipv6) and + os.stat(self.backup_rules_ipv6).st_size > 0 ), "Uhoh backup of legacy ipv6 rules is empty !?" # We inject the legacy rules (iptables-legacy) into the new iptable (just "iptables") diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 0bd84ea82..d1ea45a08 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -507,17 +507,17 @@ def _set_domain_settings(domain: str, settings: dict) -> None: def domain_cert_status(domain_list, full=False): - import yunohost.certificate + from yunohost.certificate import certificate_status - return yunohost.certificate.certificate_status(domain_list, full) + return certificate_status(domain_list, full) def domain_cert_install( domain_list, force=False, no_checks=False, self_signed=False, staging=False ): - import yunohost.certificate + from yunohost.certificate import certificate_install - return yunohost.certificate.certificate_install( + return certificate_install( domain_list, force, no_checks, self_signed, staging ) @@ -525,9 +525,9 @@ def domain_cert_install( def domain_cert_renew( domain_list, force=False, no_checks=False, email=False, staging=False ): - import yunohost.certificate + from yunohost.certificate import certificate_renew - return yunohost.certificate.certificate_renew( + return certificate_renew( domain_list, force, no_checks, email, staging ) @@ -537,12 +537,12 @@ def domain_dns_conf(domain): def domain_dns_suggest(domain): - import yunohost.dns + from yunohost.dns import domain_dns_suggest - return yunohost.dns.domain_dns_suggest(domain) + return domain_dns_suggest(domain) def domain_dns_push(domain, dry_run, force, purge): - import yunohost.dns + from yunohost.dns import domain_dns_push - return yunohost.dns.domain_dns_push(domain, dry_run, force, purge) + return domain_dns_push(domain, dry_run, force, purge) diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py index adec5508d..4b697cd84 100644 --- a/src/yunohost/regenconf.py +++ b/src/yunohost/regenconf.py @@ -48,7 +48,7 @@ logger = log.getActionLogger("yunohost.regenconf") @is_unit_operation([("names", "configuration")]) def regen_conf( operation_logger, - names=[], + names=None, with_diff=False, force=False, dry_run=False, @@ -66,6 +66,9 @@ def regen_conf( """ + if names is None: + names = [] + result = {} # Return the list of pending conf diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index a0411c69e..6dd4032e9 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -1130,7 +1130,7 @@ class Migration: def description(self): return m18n.n("migration_description_%s" % self.id) - def ldap_migration(run): + def ldap_migration(self, run): def func(self): # Backup LDAP before the migration diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 78fda8d09..a27fffbee 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -1263,25 +1263,25 @@ def user_group_remove(groupname, usernames, force=False, sync_perm=True): def user_permission_list(short=False, full=False, apps=[]): - import yunohost.permission + from yunohost.permission import user_permission_list - return yunohost.permission.user_permission_list( + return user_permission_list( short, full, absolute_urls=True, apps=apps ) def user_permission_update(permission, label=None, show_tile=None, sync_perm=True): - import yunohost.permission + from yunohost.permission import user_permission_update - return yunohost.permission.user_permission_update( + return 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 + from yunohost.permission import user_permission_update - return yunohost.permission.user_permission_update( + return user_permission_update( permission, add=names, protected=protected, force=force, sync_perm=sync_perm ) @@ -1289,23 +1289,23 @@ def user_permission_add(permission, names, protected=None, force=False, sync_per def user_permission_remove( permission, names, protected=None, force=False, sync_perm=True ): - import yunohost.permission + from yunohost.permission import user_permission_update - return yunohost.permission.user_permission_update( + return user_permission_update( permission, remove=names, protected=protected, force=force, sync_perm=sync_perm ) def user_permission_reset(permission, sync_perm=True): - import yunohost.permission + from yunohost.permission import user_permission_reset - return yunohost.permission.user_permission_reset(permission, sync_perm=sync_perm) + return user_permission_reset(permission, sync_perm=sync_perm) def user_permission_info(permission): - import yunohost.permission + from yunohost.permission import user_permission_info - return yunohost.permission.user_permission_info(permission) + return user_permission_info(permission) # diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index e3b09f870..5b286a92b 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -52,7 +52,10 @@ CONFIG_PANEL_VERSION_SUPPORTED = 1.0 # Those js-like evaluate functions are used to eval safely visible attributes # The goal is to evaluate in the same way than js simple-evaluate # https://github.com/shepherdwind/simple-evaluate -def evaluate_simple_ast(node, context={}): +def evaluate_simple_ast(node, context=None): + if context is None: + context = {} + operators = { ast.Not: op.not_, ast.Mult: op.mul, From 7cb96c6652eeb6eb1e6a2e37fda26e9d396abfd9 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 30 Dec 2021 00:25:54 +0100 Subject: [PATCH 1032/1155] fix ldap_migration decorator --- 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 6dd4032e9..79c558b2d 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -1130,7 +1130,8 @@ class Migration: def description(self): return m18n.n("migration_description_%s" % self.id) - def ldap_migration(self, run): + @staticmethod + def ldap_migration(run): def func(self): # Backup LDAP before the migration From c177a6797e5fa09f7b6c4db1b056e56eadad19c5 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Wed, 29 Dec 2021 22:40:54 +0100 Subject: [PATCH 1033/1155] Remove unused variable --- 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 cedd3891e..554576f79 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -56,7 +56,6 @@ class DNSRecordsDiagnoser(Diagnoser): def check_domain(self, domain, is_main_domain): if is_special_use_tld(domain): - categories = [] yield dict( meta={"domain": domain}, data={}, From acd96f1382b68fe23dc3ea72a807257ddecdf058 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Wed, 29 Dec 2021 22:41:57 +0100 Subject: [PATCH 1034/1155] remove unused lines --- src/yunohost/app.py | 1 - src/yunohost/dns.py | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index d5e57d6cf..bf41f163d 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1033,7 +1033,6 @@ def app_remove(operation_logger, app, purge=False): remove_script = f"{tmp_workdir_for_app}/scripts/remove" env_dict = {} - app_id, app_instance_nb = _parse_app_instance_name(app) env_dict = _make_environment_for_app_script(app, workdir=tmp_workdir_for_app) env_dict["YNH_APP_PURGE"] = str(1 if purge else 0) diff --git a/src/yunohost/dns.py b/src/yunohost/dns.py index 23c641a35..492b285c0 100644 --- a/src/yunohost/dns.py +++ b/src/yunohost/dns.py @@ -857,7 +857,6 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= 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": @@ -867,7 +866,7 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge= f"{name:>20} [{t:^5}] {old_content:^30} -> {new_content:^30} {ignored}" ) elif action == "unchanged": - old_content = new_content = record.get("content", "(None)")[:30] + old_content = record.get("content", "(None)")[:30] return f"{name:>20} [{t:^5}] {old_content:^30}" else: old_content = record.get("content", "(None)")[:30] From ef44abae6e7e557855f0ece47e5fa4725ed97c14 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Wed, 29 Dec 2021 22:45:25 +0100 Subject: [PATCH 1035/1155] some improvements --- .../0016_php70_to_php73_pools.py | 12 +++---- src/yunohost/service.py | 2 +- src/yunohost/tools.py | 34 ++++++++----------- 3 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/yunohost/data_migrations/0016_php70_to_php73_pools.py b/src/yunohost/data_migrations/0016_php70_to_php73_pools.py index fed96c9c8..570e63ea4 100644 --- a/src/yunohost/data_migrations/0016_php70_to_php73_pools.py +++ b/src/yunohost/data_migrations/0016_php70_to_php73_pools.py @@ -38,11 +38,11 @@ class MyMigration(Migration): # Ignore the "www.conf" (default stuff, probably don't want to touch it ?) php70_pool_files = [f for f in php70_pool_files if f != "www.conf"] - for f in php70_pool_files: + for pf in php70_pool_files: # Copy the files to the php7.3 pool - src = "{}/{}".format(PHP70_POOLS, f) - dest = "{}/{}".format(PHP73_POOLS, f) + src = "{}/{}".format(PHP70_POOLS, pf) + dest = "{}/{}".format(PHP73_POOLS, pf) copy2(src, dest) # Replace the socket prefix if it's found @@ -56,17 +56,17 @@ class MyMigration(Migration): c = "sed -i '1i {}' {}".format(MIGRATION_COMMENT, dest) os.system(c) - app_id = os.path.basename(f)[: -len(".conf")] + app_id = os.path.basename(pf)[: -len(".conf")] if _is_installed(app_id): _patch_legacy_php_versions_in_settings( "/etc/yunohost/apps/%s/" % app_id ) nginx_conf_files = glob.glob("/etc/nginx/conf.d/*.d/%s.conf" % app_id) - for f in nginx_conf_files: + for nf in nginx_conf_files: # Replace the socket prefix if it's found c = "sed -i -e 's@{}@{}@g' {}".format( - PHP70_SOCKETS_PREFIX, PHP73_SOCKETS_PREFIX, f + PHP70_SOCKETS_PREFIX, PHP73_SOCKETS_PREFIX, nf ) os.system(c) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 4f453dfe9..8a7b146b1 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -587,7 +587,7 @@ def service_regen_conf( if name not in services.keys(): raise YunohostValidationError("service_unknown", service=name) - if names is []: + if names == []: names = list(services.keys()) logger.warning(m18n.n("service_regen_conf_is_deprecated")) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 679b4d16e..a0411c69e 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -236,28 +236,24 @@ def tools_postinstall( # If this is a nohost.me/noho.st, actually check for availability if not ignore_dyndns and is_yunohost_dyndns_domain(domain): - # (Except if the user explicitly said he/she doesn't care about dyndns) - if ignore_dyndns: - dyndns = False # Check if the domain is available... - else: - try: - available = _dyndns_available(domain) - # If an exception is thrown, most likely we don't have internet - # connectivity or something. Assume that this domain isn't manageable - # and inform the user that we could not contact the dyndns host server. - except Exception: - logger.warning( - m18n.n( - "dyndns_provider_unreachable", provider="dyndns.yunohost.org" - ) + try: + available = _dyndns_available(domain) + # If an exception is thrown, most likely we don't have internet + # connectivity or something. Assume that this domain isn't manageable + # and inform the user that we could not contact the dyndns host server. + except Exception: + logger.warning( + m18n.n( + "dyndns_provider_unreachable", provider="dyndns.yunohost.org" ) + ) - if available: - dyndns = True - # If not, abort the postinstall - else: - raise YunohostValidationError("dyndns_unavailable", domain=domain) + if available: + dyndns = True + # If not, abort the postinstall + else: + raise YunohostValidationError("dyndns_unavailable", domain=domain) else: dyndns = False From 270c2eb9c0a7154680b3672689416613ed158a7f Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 30 Dec 2021 00:08:49 +0100 Subject: [PATCH 1036/1155] Trying to fix some lgtm warnings --- src/yunohost/app.py | 7 ++- src/yunohost/certificate.py | 52 +++++++++---------- .../data_migrations/0018_xtable_to_nftable.py | 6 ++- src/yunohost/domain.py | 20 +++---- src/yunohost/regenconf.py | 5 +- src/yunohost/tools.py | 2 +- src/yunohost/user.py | 24 ++++----- src/yunohost/utils/config.py | 5 +- 8 files changed, 66 insertions(+), 55 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index bf41f163d..ca56be232 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -627,7 +627,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( @@ -1626,9 +1626,12 @@ class AppConfigPanel(ConfigPanel): error=message, ) - def _call_config_script(self, action, env={}): + def _call_config_script(self, action, env=None): from yunohost.hook import hook_exec + if env is None: + env = {} + # Add default config script if needed config_script = os.path.join( APPS_SETTING_PATH, self.entity, "scripts", "config" diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 79e3ae092..2f3676202 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -70,28 +70,28 @@ PRODUCTION_CERTIFICATION_AUTHORITY = "https://acme-v02.api.letsencrypt.org" # -def certificate_status(domain_list, full=False): +def certificate_status(domains, full=False): """ Print the status of certificate for given domains (all by default) Keyword argument: - domain_list -- Domains to be checked + domains -- Domains to be checked full -- Display more info about the certificates """ - import yunohost.domain + from yunohost.domain import domain_list, _assert_domain_exists # If no domains given, consider all yunohost domains - if domain_list == []: - domain_list = yunohost.domain.domain_list()["domains"] + if domains == []: + domains = domain_list()["domains"] # Else, validate that yunohost knows the domains given else: - for domain in domain_list: - yunohost.domain._assert_domain_exists(domain) + for domain in domains: + _assert_domain_exists(domain) certificates = {} - for domain in domain_list: + for domain in domains: status = _get_status(domain) if not full: @@ -239,28 +239,28 @@ def _certificate_install_selfsigned(domain_list, force=False): def _certificate_install_letsencrypt( - domain_list, force=False, no_checks=False, staging=False + domains, force=False, no_checks=False, staging=False ): - import yunohost.domain + from yunohost.domain import domain_list, _assert_domain_exists if not os.path.exists(ACCOUNT_KEY_FILE): _generate_account_key() # If no domains given, consider all yunohost domains with self-signed # certificates - if domain_list == []: - for domain in yunohost.domain.domain_list()["domains"]: + if domains == []: + for domain in domain_list()["domains"]: status = _get_status(domain) if status["CA_type"]["code"] != "self-signed": continue - domain_list.append(domain) + domains.append(domain) # Else, validate that yunohost knows the domains given else: - for domain in domain_list: - yunohost.domain._assert_domain_exists(domain) + for domain in domains: + _assert_domain_exists(domain) # Is it self-signed? status = _get_status(domain) @@ -275,7 +275,7 @@ def _certificate_install_letsencrypt( ) # Actual install steps - for domain in domain_list: + for domain in domains: if not no_checks: try: @@ -311,25 +311,25 @@ def _certificate_install_letsencrypt( def certificate_renew( - domain_list, force=False, no_checks=False, email=False, staging=False + domains, force=False, no_checks=False, email=False, staging=False ): """ Renew Let's Encrypt certificate for given domains (all by default) Keyword argument: - domain_list -- Domains for which to renew the certificates + domains -- Domains for which to renew the certificates force -- Ignore the validity threshold (15 days) no-check -- Disable some checks about the reachability of web server before attempting the renewing email -- Emails root if some renewing failed """ - import yunohost.domain + from yunohost.domain import domain_list, _assert_domain_exists # If no domains given, consider all yunohost domains with Let's Encrypt # certificates - if domain_list == []: - for domain in yunohost.domain.domain_list()["domains"]: + if domains == []: + for domain in domain_list()["domains"]: # Does it have a Let's Encrypt cert? status = _get_status(domain) @@ -347,17 +347,17 @@ def certificate_renew( ) continue - domain_list.append(domain) + domains.append(domain) - if len(domain_list) == 0 and not email: + if len(domains) == 0 and not email: logger.info("No certificate needs to be renewed.") # Else, validate the domain list given else: - for domain in domain_list: + for domain in domains: # Is it in Yunohost domain list? - yunohost.domain._assert_domain_exists(domain) + _assert_domain_exists(domain) status = _get_status(domain) @@ -385,7 +385,7 @@ def certificate_renew( ) # Actual renew steps - for domain in domain_list: + for domain in domains: if not no_checks: try: diff --git a/src/yunohost/data_migrations/0018_xtable_to_nftable.py b/src/yunohost/data_migrations/0018_xtable_to_nftable.py index 94b47d944..374620f2f 100644 --- a/src/yunohost/data_migrations/0018_xtable_to_nftable.py +++ b/src/yunohost/data_migrations/0018_xtable_to_nftable.py @@ -41,7 +41,8 @@ class MyMigration(Migration): ) # For some reason if we don't do this, iptables-legacy-save is empty ? self.runcmd("iptables-legacy-save > %s" % self.backup_rules_ipv4) assert ( - open(self.backup_rules_ipv4).read().strip() + os.path.exists(self.backup_rules_ipv4) and + os.stat(self.backup_rules_ipv4).st_size > 0 ), "Uhoh backup of legacy ipv4 rules is empty !?" if self.do_ipv6 and not os.path.exists(self.backup_rules_ipv6): self.runcmd( @@ -49,7 +50,8 @@ class MyMigration(Migration): ) # For some reason if we don't do this, iptables-legacy-save is empty ? self.runcmd("ip6tables-legacy-save > %s" % self.backup_rules_ipv6) assert ( - open(self.backup_rules_ipv6).read().strip() + os.path.exists(self.backup_rules_ipv6) and + os.stat(self.backup_rules_ipv6).st_size > 0 ), "Uhoh backup of legacy ipv6 rules is empty !?" # We inject the legacy rules (iptables-legacy) into the new iptable (just "iptables") diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 0bd84ea82..d1ea45a08 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -507,17 +507,17 @@ def _set_domain_settings(domain: str, settings: dict) -> None: def domain_cert_status(domain_list, full=False): - import yunohost.certificate + from yunohost.certificate import certificate_status - return yunohost.certificate.certificate_status(domain_list, full) + return certificate_status(domain_list, full) def domain_cert_install( domain_list, force=False, no_checks=False, self_signed=False, staging=False ): - import yunohost.certificate + from yunohost.certificate import certificate_install - return yunohost.certificate.certificate_install( + return certificate_install( domain_list, force, no_checks, self_signed, staging ) @@ -525,9 +525,9 @@ def domain_cert_install( def domain_cert_renew( domain_list, force=False, no_checks=False, email=False, staging=False ): - import yunohost.certificate + from yunohost.certificate import certificate_renew - return yunohost.certificate.certificate_renew( + return certificate_renew( domain_list, force, no_checks, email, staging ) @@ -537,12 +537,12 @@ def domain_dns_conf(domain): def domain_dns_suggest(domain): - import yunohost.dns + from yunohost.dns import domain_dns_suggest - return yunohost.dns.domain_dns_suggest(domain) + return domain_dns_suggest(domain) def domain_dns_push(domain, dry_run, force, purge): - import yunohost.dns + from yunohost.dns import domain_dns_push - return yunohost.dns.domain_dns_push(domain, dry_run, force, purge) + return domain_dns_push(domain, dry_run, force, purge) diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py index adec5508d..4b697cd84 100644 --- a/src/yunohost/regenconf.py +++ b/src/yunohost/regenconf.py @@ -48,7 +48,7 @@ logger = log.getActionLogger("yunohost.regenconf") @is_unit_operation([("names", "configuration")]) def regen_conf( operation_logger, - names=[], + names=None, with_diff=False, force=False, dry_run=False, @@ -66,6 +66,9 @@ def regen_conf( """ + if names is None: + names = [] + result = {} # Return the list of pending conf diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index a0411c69e..6dd4032e9 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -1130,7 +1130,7 @@ class Migration: def description(self): return m18n.n("migration_description_%s" % self.id) - def ldap_migration(run): + def ldap_migration(self, run): def func(self): # Backup LDAP before the migration diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 78fda8d09..a27fffbee 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -1263,25 +1263,25 @@ def user_group_remove(groupname, usernames, force=False, sync_perm=True): def user_permission_list(short=False, full=False, apps=[]): - import yunohost.permission + from yunohost.permission import user_permission_list - return yunohost.permission.user_permission_list( + return user_permission_list( short, full, absolute_urls=True, apps=apps ) def user_permission_update(permission, label=None, show_tile=None, sync_perm=True): - import yunohost.permission + from yunohost.permission import user_permission_update - return yunohost.permission.user_permission_update( + return 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 + from yunohost.permission import user_permission_update - return yunohost.permission.user_permission_update( + return user_permission_update( permission, add=names, protected=protected, force=force, sync_perm=sync_perm ) @@ -1289,23 +1289,23 @@ def user_permission_add(permission, names, protected=None, force=False, sync_per def user_permission_remove( permission, names, protected=None, force=False, sync_perm=True ): - import yunohost.permission + from yunohost.permission import user_permission_update - return yunohost.permission.user_permission_update( + return user_permission_update( permission, remove=names, protected=protected, force=force, sync_perm=sync_perm ) def user_permission_reset(permission, sync_perm=True): - import yunohost.permission + from yunohost.permission import user_permission_reset - return yunohost.permission.user_permission_reset(permission, sync_perm=sync_perm) + return user_permission_reset(permission, sync_perm=sync_perm) def user_permission_info(permission): - import yunohost.permission + from yunohost.permission import user_permission_info - return yunohost.permission.user_permission_info(permission) + return user_permission_info(permission) # diff --git a/src/yunohost/utils/config.py b/src/yunohost/utils/config.py index e3b09f870..5b286a92b 100644 --- a/src/yunohost/utils/config.py +++ b/src/yunohost/utils/config.py @@ -52,7 +52,10 @@ CONFIG_PANEL_VERSION_SUPPORTED = 1.0 # Those js-like evaluate functions are used to eval safely visible attributes # The goal is to evaluate in the same way than js simple-evaluate # https://github.com/shepherdwind/simple-evaluate -def evaluate_simple_ast(node, context={}): +def evaluate_simple_ast(node, context=None): + if context is None: + context = {} + operators = { ast.Not: op.not_, ast.Mult: op.mul, From fa9365d5fa50aff9b94b3356492679fa19ad8dca Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 3 Jan 2022 16:28:35 +0100 Subject: [PATCH 1037/1155] Yolotweaking for the bullseye migration mecanism to also install, for example, php7.4-zip if an app is gonna need it --- .../0021_migrate_to_bullseye.py | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py index f97ab16da..10f385c23 100644 --- a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py +++ b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py @@ -130,9 +130,37 @@ class MyMigration(Migration): os.system("apt update") - # Force explicit install of php7.4-fpm to make sure it's ll be there - # during 0022_php73_to_php74_pools migration - self.apt_install("php7.4-fpm -o Dpkg::Options::='--force-confmiss'") + # Force explicit install of php7.4-fpm and other old 'default' dependencies + # that are now only in Recommends + # + # Also, we need to install php7.4 equivalents of other php7.3 dependencies. + # For example, Nextcloud may depend on php7.3-zip, and after the php pool migration + # to autoupgrade Nextcloud to 7.4, it will need the php7.4-zip to work. + # The following list is based on an ad-hoc analysis of php deps found in the + # app ecosystem, with a known equivalent on php7.4. + # + # This is kinda a dirty hack as it doesnt properly update the *-ynh-deps virtual packages + # with the proper list of dependencies, and the dependencies install this way + # will get flagged as 'manually installed'. + # + # We'll probably want to do something during the Bullseye->Bookworm migration to re-flag + # these as 'auto' so they get autoremoved if not needed anymore. + # Also hopefully by then we'll have manifestv2 (maybe) and will be able to use + # the apt resource mecanism to regenerate the *-ynh-deps virtual packages ;) + + php73packages_suffixes = ['apcu', 'bcmath', 'bz2', 'dom', 'gmp', 'igbinary', 'imagick', 'imap', 'mbstring', 'memcached', 'mysqli', 'mysqlnd', 'pgsql', 'redis', 'simplexml', 'soap', 'sqlite3', 'ssh2', 'tidy', 'xml', 'xmlrpc', 'xsl', 'zip'] + + cmd = f""" + apt show '*-ynh-deps' 2>/dev/null + | grep Depends + | grep -o -E "php7.3-({'|'.join(php73packages_suffixes)})"; + | sort | uniq + | sed 's/php7.3/php7.4/g' + """ + php74packages_to_install = ["php7.4-fpm", "php7.4-common", "php7.4-ldap", "php7.4-intl", "php7.4-mysql", "php7.4-gd", "php7.4-curl", "php-php-gettext"] + php74packages_to_install += [f.strip() for f in check_output(cmd).split("\n") if f.strip()] + + self.apt_install("{' '.join(php74packages_to_install)} -o Dpkg::Options::='--force-confmiss'") # Remove legacy postgresql service record added by helpers, # will now be dynamically handled by the core in bullseye From e98ba46e4b740ee383a24653ed6db08b92eff104 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 3 Jan 2022 16:32:34 +0100 Subject: [PATCH 1038/1155] postinstall: don't skip migrate_to_bullseye migration when installing a fresh buster --- src/yunohost/tools.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 79c558b2d..0ed88d871 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -1045,6 +1045,10 @@ def _skip_all_migrations(): all_migrations = _get_migrations_list() new_states = {"migrations": {}} for migration in all_migrations: + # Don't skip bullseye migration while we're + # still on buster + if "migrate_to_bullseye" in migration.id: + continue new_states["migrations"][migration.id] = "skipped" write_to_yaml(MIGRATIONS_STATE_PATH, new_states) From 05b93aaaf1a9be736eae843cb60ca41258bb07af Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Mon, 3 Jan 2022 16:01:49 +0000 Subject: [PATCH 1039/1155] [CI] Format code with Black --- src/yunohost/app.py | 2 +- .../data_migrations/0018_xtable_to_nftable.py | 8 ++-- .../0021_migrate_to_bullseye.py | 45 +++++++++++++++++-- src/yunohost/domain.py | 8 +--- src/yunohost/tools.py | 4 +- src/yunohost/user.py | 4 +- 6 files changed, 50 insertions(+), 21 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index ca56be232..d6bb5d92f 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -627,7 +627,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( diff --git a/src/yunohost/data_migrations/0018_xtable_to_nftable.py b/src/yunohost/data_migrations/0018_xtable_to_nftable.py index 374620f2f..ae20354d7 100644 --- a/src/yunohost/data_migrations/0018_xtable_to_nftable.py +++ b/src/yunohost/data_migrations/0018_xtable_to_nftable.py @@ -41,8 +41,8 @@ class MyMigration(Migration): ) # For some reason if we don't do this, iptables-legacy-save is empty ? self.runcmd("iptables-legacy-save > %s" % self.backup_rules_ipv4) assert ( - os.path.exists(self.backup_rules_ipv4) and - os.stat(self.backup_rules_ipv4).st_size > 0 + os.path.exists(self.backup_rules_ipv4) + and os.stat(self.backup_rules_ipv4).st_size > 0 ), "Uhoh backup of legacy ipv4 rules is empty !?" if self.do_ipv6 and not os.path.exists(self.backup_rules_ipv6): self.runcmd( @@ -50,8 +50,8 @@ class MyMigration(Migration): ) # For some reason if we don't do this, iptables-legacy-save is empty ? self.runcmd("ip6tables-legacy-save > %s" % self.backup_rules_ipv6) assert ( - os.path.exists(self.backup_rules_ipv6) and - os.stat(self.backup_rules_ipv6).st_size > 0 + os.path.exists(self.backup_rules_ipv6) + and os.stat(self.backup_rules_ipv6).st_size > 0 ), "Uhoh backup of legacy ipv6 rules is empty !?" # We inject the legacy rules (iptables-legacy) into the new iptable (just "iptables") diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py index 10f385c23..717be4e15 100644 --- a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py +++ b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py @@ -148,7 +148,31 @@ class MyMigration(Migration): # Also hopefully by then we'll have manifestv2 (maybe) and will be able to use # the apt resource mecanism to regenerate the *-ynh-deps virtual packages ;) - php73packages_suffixes = ['apcu', 'bcmath', 'bz2', 'dom', 'gmp', 'igbinary', 'imagick', 'imap', 'mbstring', 'memcached', 'mysqli', 'mysqlnd', 'pgsql', 'redis', 'simplexml', 'soap', 'sqlite3', 'ssh2', 'tidy', 'xml', 'xmlrpc', 'xsl', 'zip'] + php73packages_suffixes = [ + "apcu", + "bcmath", + "bz2", + "dom", + "gmp", + "igbinary", + "imagick", + "imap", + "mbstring", + "memcached", + "mysqli", + "mysqlnd", + "pgsql", + "redis", + "simplexml", + "soap", + "sqlite3", + "ssh2", + "tidy", + "xml", + "xmlrpc", + "xsl", + "zip", + ] cmd = f""" apt show '*-ynh-deps' 2>/dev/null @@ -157,10 +181,23 @@ class MyMigration(Migration): | sort | uniq | sed 's/php7.3/php7.4/g' """ - php74packages_to_install = ["php7.4-fpm", "php7.4-common", "php7.4-ldap", "php7.4-intl", "php7.4-mysql", "php7.4-gd", "php7.4-curl", "php-php-gettext"] - php74packages_to_install += [f.strip() for f in check_output(cmd).split("\n") if f.strip()] + php74packages_to_install = [ + "php7.4-fpm", + "php7.4-common", + "php7.4-ldap", + "php7.4-intl", + "php7.4-mysql", + "php7.4-gd", + "php7.4-curl", + "php-php-gettext", + ] + php74packages_to_install += [ + f.strip() for f in check_output(cmd).split("\n") if f.strip() + ] - self.apt_install("{' '.join(php74packages_to_install)} -o Dpkg::Options::='--force-confmiss'") + self.apt_install( + "{' '.join(php74packages_to_install)} -o Dpkg::Options::='--force-confmiss'" + ) # Remove legacy postgresql service record added by helpers, # will now be dynamically handled by the core in bullseye diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index d1ea45a08..7c512106a 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -517,9 +517,7 @@ def domain_cert_install( ): from yunohost.certificate import certificate_install - return certificate_install( - domain_list, force, no_checks, self_signed, staging - ) + return certificate_install(domain_list, force, no_checks, self_signed, staging) def domain_cert_renew( @@ -527,9 +525,7 @@ def domain_cert_renew( ): from yunohost.certificate import certificate_renew - return certificate_renew( - domain_list, force, no_checks, email, staging - ) + return certificate_renew(domain_list, force, no_checks, email, staging) def domain_dns_conf(domain): diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 0ed88d871..c2014b466 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -244,9 +244,7 @@ def tools_postinstall( # and inform the user that we could not contact the dyndns host server. except Exception: logger.warning( - m18n.n( - "dyndns_provider_unreachable", provider="dyndns.yunohost.org" - ) + m18n.n("dyndns_provider_unreachable", provider="dyndns.yunohost.org") ) if available: diff --git a/src/yunohost/user.py b/src/yunohost/user.py index a27fffbee..64249e9d0 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -1265,9 +1265,7 @@ def user_group_remove(groupname, usernames, force=False, sync_perm=True): def user_permission_list(short=False, full=False, apps=[]): from yunohost.permission import user_permission_list - return user_permission_list( - short, full, absolute_urls=True, apps=apps - ) + return user_permission_list(short, full, absolute_urls=True, apps=apps) def user_permission_update(permission, label=None, show_tile=None, sync_perm=True): From 63a84f53981a379a7cbecc706fc703fa88c40484 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 3 Jan 2022 20:02:56 +0100 Subject: [PATCH 1040/1155] dyndns: replace dnssec-keygen and nsupdate with python code, drop legacy md5 stuff, drop unecessary dyndns 'private' key --- debian/control | 2 +- hooks/conf_regen/01-yunohost | 2 +- src/dyndns.py | 101 +++++++++++++++++------------------ 3 files changed, 51 insertions(+), 54 deletions(-) diff --git a/debian/control b/debian/control index 31204a180..65bb0b529 100644 --- a/debian/control +++ b/debian/control @@ -18,7 +18,7 @@ Depends: ${python3:Depends}, ${misc:Depends} , python-is-python3 , nginx, nginx-extras (>=1.18) , apt, apt-transport-https, apt-utils, dirmngr - , openssh-server, iptables, fail2ban, dnsutils, bind9utils + , openssh-server, iptables, fail2ban, bind9-dnsutils , openssl, ca-certificates, netcat-openbsd, iproute2 , slapd, ldap-utils, sudo-ldap, libnss-ldapd, unscd, libpam-ldapd , dnsmasq, resolvconf, libnss-myhostname diff --git a/hooks/conf_regen/01-yunohost b/hooks/conf_regen/01-yunohost index 14840e2f1..1f6c143a6 100755 --- a/hooks/conf_regen/01-yunohost +++ b/hooks/conf_regen/01-yunohost @@ -109,7 +109,7 @@ 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 + if ls -l /etc/yunohost/dyndns/K*.key 2>/dev/null; then cat >$pending_dir/etc/cron.d/yunohost-dyndns <[^\s\+]+)\.\+157.+\.private$") - -RE_DYNDNS_PRIVATE_KEY_SHA512 = re.compile( - r".*/K(?P[^\s\+]+)\.\+165.+\.private$" -) - DYNDNS_PROVIDER = "dyndns.yunohost.org" DYNDNS_DNS_AUTH = ["ns0.yunohost.org", "ns1.yunohost.org"] @@ -111,6 +103,10 @@ def dyndns_subscribe(operation_logger, domain=None, key=None): operation_logger.start() + # '165' is the convention identifier for hmac-sha512 algorithm + # '1234' is idk? doesnt matter, but the old format contained a number here... + key_file = f"/etc/yunohost/dyndns/K{domain}.+165+1234.key" + if key is None: if len(glob.glob("/etc/yunohost/dyndns/*.key")) == 0: if not os.path.exists("/etc/yunohost/dyndns"): @@ -118,35 +114,39 @@ def dyndns_subscribe(operation_logger, domain=None, key=None): logger.debug(m18n.n("dyndns_key_generating")) - os.system( - "cd /etc/yunohost/dyndns && " - f"dnssec-keygen -a hmac-sha512 -b 512 -r /dev/urandom -n USER {domain}" - ) + # Here, we emulate the behavior of the old 'dnssec-keygen' utility + # which since bullseye was replaced by ddns-keygen which is now + # in the bind9 package ... but installing bind9 will conflict with dnsmasq + # and is just madness just to have access to a tsig keygen utility -.- + + # Use 512 // 8 = 64 bytes for hmac-sha512 (c.f. https://git.hactrn.net/sra/tsig-keygen/src/master/tsig-keygen.py) + secret = base64.b64encode(os.urandom(512 // 8)).decode("ascii") + + # Idk why but the secret is split in two parts, with the first one + # being 57-long char ... probably some DNS format + secret = f"{secret[:56]} {secret[56:]}" + + key_content = f"{domain}. IN KEY 0 3 165 {secret}" + write_to_file(key_file, key_content) chmod("/etc/yunohost/dyndns", 0o600, recursive=True) chown("/etc/yunohost/dyndns", "root", recursive=True) - private_file = glob.glob("/etc/yunohost/dyndns/*%s*.private" % domain)[0] - key_file = glob.glob("/etc/yunohost/dyndns/*%s*.key" % domain)[0] - with open(key_file) as f: - key = f.readline().strip().split(" ", 6)[-1] - import requests # lazy loading this module for performance reasons # Send subscription try: - b64encoded_key = base64.b64encode(key.encode()).decode() + # Yeah the secret is already a base64-encoded but we double-bas64-encode it, whatever... + b64encoded_key = base64.b64encode(secret.encode()).decode() r = requests.post( f"https://{DYNDNS_PROVIDER}/key/{b64encoded_key}?key_algo=hmac-sha512", data={"subdomain": domain}, timeout=30, ) except Exception as e: - rm(private_file, force=True) rm(key_file, force=True) raise YunohostError("dyndns_registration_failed", error=str(e)) if r.status_code != 201: - rm(private_file, force=True) rm(key_file, force=True) try: error = json.loads(r.text)["error"] @@ -154,7 +154,7 @@ def dyndns_subscribe(operation_logger, domain=None, key=None): error = f'Server error, code: {r.status_code}. (Message: "{r.text}")' raise YunohostError("dyndns_registration_failed", error=error) - # Yunohost regen conf will add the dyndns cron job if a private key exists + # Yunohost regen conf will add the dyndns cron job if a key exists # in /etc/yunohost/dyndns regen_conf(["yunohost"]) @@ -185,6 +185,11 @@ def dyndns_update( """ from yunohost.dns import _build_dns_conf + import dns.query + import dns.tsig + import dns.tsigkeyring + import dns.update + # If domain is not given, try to guess it from keys available... key = None @@ -196,7 +201,7 @@ def dyndns_update( # If key is not given, pick the first file we find with the domain given elif key is None: - keys = glob.glob(f"/etc/yunohost/dyndns/K{domain}.+*.private") + keys = glob.glob(f"/etc/yunohost/dyndns/K{domain}.+*.key") if not keys: raise YunohostValidationError("dyndns_key_not_found") @@ -217,12 +222,14 @@ def dyndns_update( host = domain.split(".")[1:] host = ".".join(host) - logger.debug("Building zone update file ...") + logger.debug("Building zone update ...") - lines = [ - f"server {DYNDNS_PROVIDER}", - f"zone {host}", - ] + with open(key) as f: + key = f.readline().strip().split(" ", 6)[-1] + + keyring = dns.tsigkeyring.from_text({f'{domain}.': key}) + # Python's dns.update is similar to the old nsupdate cli tool + update = dns.update.Update(domain, keyring=keyring, keyalgorithm=dns.tsig.HMAC_SHA512) auth_resolvers = [] @@ -293,9 +300,8 @@ def dyndns_update( # [{"name": "...", "ttl": "...", "type": "...", "value": "..."}] for records in dns_conf.values(): for record in records: - action = "update delete {name}.{domain}.".format(domain=domain, **record) - action = action.replace(" @.", " ") - lines.append(action) + name = f"{record['name']}.{domain}." if record['name'] != "@" else f"{domain}." + update.delete(name) # Add the new records for all domain/subdomains @@ -307,32 +313,22 @@ def dyndns_update( if record["value"] == "@": record["value"] = domain record["value"] = record["value"].replace(";", r"\;") + name = f"{record['name']}.{domain}." if record['name'] != "@" else f"{domain}." - action = "update add {name}.{domain}. {ttl} {type} {value}".format( - domain=domain, **record - ) - action = action.replace(" @.", " ") - lines.append(action) - - lines += ["show", "send"] - - # Write the actions to do to update to a file, to be able to pass it - # to nsupdate as argument - write_to_file(DYNDNS_ZONE, "\n".join(lines)) + update.add(name, record['ttl'], record['type'], record['value']) logger.debug("Now pushing new conf to DynDNS host...") + logger.debug(update) if not dry_run: try: - command = ["/usr/bin/nsupdate", "-k", key, DYNDNS_ZONE] - subprocess.check_call(command) - except subprocess.CalledProcessError: + dns.query.tcp(update, auth_resolvers[0]) + except Exception as e: + logger.error(e) raise YunohostError("dyndns_ip_update_failed") 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" ) @@ -347,15 +343,16 @@ def _guess_current_dyndns_domain(): dynette...) """ + DYNDNS_KEY_REGEX = re.compile( + r".*/K(?P[^\s\+]+)\.\+165.+\.key$" + ) + # Retrieve the first registered domain - paths = list(glob.iglob("/etc/yunohost/dyndns/K*.private")) + paths = list(glob.iglob("/etc/yunohost/dyndns/K*.key")) for path in paths: - # MD5 is legacy ugh - match = RE_DYNDNS_PRIVATE_KEY_MD5.match(path) + match = DYNDNS_KEY_REGEX.match(path) if not match: - match = RE_DYNDNS_PRIVATE_KEY_SHA512.match(path) - if not match: - continue + continue _domain = match.group("domain") # Verify if domain is registered (i.e., if it's available, skip From e12a0153bbc01be4c9098b5211e4262ca0e19baa Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 3 Jan 2022 20:07:32 +0100 Subject: [PATCH 1041/1155] Unused imports --- src/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools.py b/src/tools.py index a1e90daa9..259426a20 100644 --- a/src/tools.py +++ b/src/tools.py @@ -33,7 +33,7 @@ from typing import List from moulinette import Moulinette, m18n from moulinette.utils.log import getActionLogger -from moulinette.utils.process import check_output, call_async_output +from moulinette.utils.process import call_async_output from moulinette.utils.filesystem import read_yaml, write_to_yaml, cp, mkdir, rm from yunohost.app import ( From 620da53fe79442d0ef8bf7264ba2fba9c6be6bca Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 3 Jan 2022 20:14:36 +0100 Subject: [PATCH 1042/1155] postfix: move postmap to post regenconf, because the postfix user must be able to access the file, and cant anymore during pre because we moved the pending regenconf folder elsewhere --- hooks/conf_regen/19-postfix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/conf_regen/19-postfix b/hooks/conf_regen/19-postfix index 4a7325c41..e18243508 100755 --- a/hooks/conf_regen/19-postfix +++ b/hooks/conf_regen/19-postfix @@ -42,7 +42,6 @@ do_pre_regen() { chown postfix ${pending_dir}/etc/postfix/sasl_passwd 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" @@ -71,6 +70,7 @@ do_post_regen() { if [ -e /etc/postfix/sasl_passwd ]; then chmod 750 /etc/postfix/sasl_passwd* chown postfix:root /etc/postfix/sasl_passwd* + postmap /etc/postfix/sasl_passwd fi [[ -z "$regen_conf_files" ]] \ From 017de77405b0c4c93d263410c6ee14397349d92f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 3 Jan 2022 20:20:27 +0100 Subject: [PATCH 1043/1155] debian/mdns: require python3-zeroconf >= 0.36 --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 65bb0b529..6e059231a 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-publicsuffix2 - , python3-ldap, python3-zeroconf, python3-lexicon, + , python3-ldap, python3-zeroconf (>= 0.36), python3-lexicon, , python-is-python3 , nginx, nginx-extras (>=1.18) , apt, apt-transport-https, apt-utils, dirmngr From 8321642a34445b35bf19dcf729b0e22e48fc1511 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 4 Jan 2022 17:41:06 +0100 Subject: [PATCH 1044/1155] Add codequality --- .gitlab-ci.yml | 14 +++++++++++++- .gitlab/ci/test.gitlab-ci.yml | 4 ++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8403cb8e8..43afb7d4e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,7 +2,7 @@ stages: - build - install - - tests + - test - lint - doc - translation @@ -13,6 +13,17 @@ default: # All jobs are interruptible by default interruptible: true +code_quality: + tags: + - docker + +code_quality_html: + extends: code_quality + variables: + REPORT_FORMAT: html + artifacts: + paths: [gl-code-quality-report.html] + # see: https://docs.gitlab.com/ee/ci/yaml/#switch-between-branch-pipelines-and-merge-request-pipelines workflow: rules: @@ -29,4 +40,5 @@ variables: YNH_BUILD_DIR: "ynh-build" include: + - template: Code-Quality.gitlab-ci.yml - local: .gitlab/ci/*.gitlab-ci.yml diff --git a/.gitlab/ci/test.gitlab-ci.yml b/.gitlab/ci/test.gitlab-ci.yml index 1aad46fbe..64ca65d13 100644 --- a/.gitlab/ci/test.gitlab-ci.yml +++ b/.gitlab/ci/test.gitlab-ci.yml @@ -3,7 +3,7 @@ - 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 + stage: test image: "after-install" variables: PYTEST_ADDOPTS: "--color=yes" @@ -28,7 +28,7 @@ ######################################## full-tests: - stage: tests + stage: test image: "before-install" variables: PYTEST_ADDOPTS: "--color=yes" From 816c503d3af888cdb59988d64181a2d1a57a9307 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 4 Jan 2022 17:59:37 +0100 Subject: [PATCH 1045/1155] add codeclimate file --- .codeclimate.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .codeclimate.yml diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 000000000..73707650c --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,22 @@ +--- +version: "2" +plugins: + duplication: + enabled: true + config: + languages: + python: + python_version: 3 + shellcheck: + enabled: true + pep8: + enabled: true + sonar-python: + enabled: true + config: + tests_patterns: + - bin/* + - data/** + - doc/* + - src/** + - tests/** \ No newline at end of file From 4a377bbf23ee65fd31d2cd2f6bbb375438056b4c Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 4 Jan 2022 18:05:24 +0100 Subject: [PATCH 1046/1155] add fixme plugin --- .codeclimate.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.codeclimate.yml b/.codeclimate.yml index 73707650c..453396f07 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -11,6 +11,8 @@ plugins: enabled: true pep8: enabled: true + fixme: + enabled: true sonar-python: enabled: true config: From 0973301b0f5d072657969befbd4744e11c89c773 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 4 Jan 2022 20:09:48 +0100 Subject: [PATCH 1047/1155] ynh_add_config: crons should be owned by root, otherwise they probably don't run? --- data/helpers.d/utils | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 8b7179289..6929e3f95 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -967,4 +967,11 @@ _ynh_apply_default_permissions() { chown $app:$app $target fi fi + + # Crons should be owned by root otherwise they probably don't run + if echo "$target" | grep -q '^/etc/cron' + then + chmod 400 $target + chown root:root $target + fi } From 3cc1a0a59df905cab69444f4635d0bf875c64bd2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 5 Jan 2022 17:44:15 +0100 Subject: [PATCH 1048/1155] tools_upgrade: filter more boring apt messages --- src/yunohost/tools.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index c2014b466..c825ca2ef 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -571,8 +571,18 @@ def tools_upgrade( irrelevants = [ "service sudo-ldap already provided", "Reading database ...", + "Preparing to unpack", + "Selecting previously unselected package", + "Created symlink /etc/systemd", + "Replacing config file", + "Creating config file", + "Installing new version of config file", + "Installing new config file as you requested", + ", does not exist on system.", + "unable to delete old directory", + "update-alternatives:", ] - return all(i not in line.rstrip() for i in irrelevants) + return line.rstrip() and all(i not in line.rstrip() for i in irrelevants) callbacks = ( lambda l: logger.info("+ " + l.rstrip() + "\r") From 17ad3c8581e3d0612132eddf541b1fab3c046510 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 5 Jan 2022 17:45:23 +0100 Subject: [PATCH 1049/1155] migrate_to_bullseye: fix typo + reorganize a couple steps --- .../0021_migrate_to_bullseye.py | 63 +++++++++---------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py index 717be4e15..d26eede88 100644 --- a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py +++ b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py @@ -97,23 +97,6 @@ class MyMigration(Migration): os.system("mv /home/yunohost.conf /var/cache/yunohost/regenconf") rm("/home/yunohost.conf", recursive=True, force=True) - # - # Main upgrade - # - logger.info(m18n.n("migration_0021_main_upgrade")) - - apps_packages = self.get_apps_equivs_packages() - self.hold(apps_packages) - tools_upgrade(target="system", allow_yunohost_upgrade=False) - - if self.debian_major_version() == N_CURRENT_DEBIAN: - raise YunohostError("migration_0021_still_on_buster_after_main_upgrade") - - # Clean the mess - logger.info(m18n.n("migration_0021_cleaning_up")) - os.system("apt autoremove --assume-yes") - os.system("apt clean --assume-yes") - # Force add sury if it's not there yet # This is to solve some weird issue with php-common breaking php7.3-common, # hence breaking many php7.3-deps @@ -128,7 +111,24 @@ class MyMigration(Migration): 'wget --timeout 900 --quiet "https://packages.sury.org/php/apt.gpg" --output-document=- | gpg --dearmor >"/etc/apt/trusted.gpg.d/extra_php_version.gpg"' ) - os.system("apt update") + # Remove legacy postgresql service record added by helpers, + # will now be dynamically handled by the core in bullseye + services = _get_services() + if "postgresql" in services: + del services["postgresql"] + _save_services(services) + + # + # Main upgrade + # + logger.info(m18n.n("migration_0021_main_upgrade")) + + apps_packages = self.get_apps_equivs_packages() + self.hold(apps_packages) + tools_upgrade(target="system", allow_yunohost_upgrade=False) + + if self.debian_major_version() == N_CURRENT_DEBIAN: + raise YunohostError("migration_0021_still_on_buster_after_main_upgrade") # Force explicit install of php7.4-fpm and other old 'default' dependencies # that are now only in Recommends @@ -174,13 +174,13 @@ class MyMigration(Migration): "zip", ] - cmd = f""" - apt show '*-ynh-deps' 2>/dev/null - | grep Depends - | grep -o -E "php7.3-({'|'.join(php73packages_suffixes)})"; - | sort | uniq - | sed 's/php7.3/php7.4/g' - """ + cmd = "apt show '*-ynh-deps' 2>/dev/null" \ + " | grep Depends" \ + f" | grep -o -E \"php7.3-({'|'.join(php73packages_suffixes)})\"" \ + " | sort | uniq" \ + " | sed 's/php7.3/php7.4/g'" \ + " || true" + php74packages_to_install = [ "php7.4-fpm", "php7.4-common", @@ -191,20 +191,19 @@ class MyMigration(Migration): "php7.4-curl", "php-php-gettext", ] + php74packages_to_install += [ f.strip() for f in check_output(cmd).split("\n") if f.strip() ] self.apt_install( - "{' '.join(php74packages_to_install)} -o Dpkg::Options::='--force-confmiss'" + f"{' '.join(php74packages_to_install)} -o Dpkg::Options::='--force-confmiss'" ) - # Remove legacy postgresql service record added by helpers, - # will now be dynamically handled by the core in bullseye - services = _get_services() - if "postgresql" in services: - del services["postgresql"] - _save_services(services) + # Clean the mess + logger.info(m18n.n("migration_0021_cleaning_up")) + os.system("apt autoremove --assume-yes") + os.system("apt clean --assume-yes") # # Yunohost upgrade From fa163cbc2e44a5c54ac7d48adff7c573da6c2b30 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 5 Jan 2022 19:50:50 +0100 Subject: [PATCH 1050/1155] migrate_to_bullseye: zblerg, more constrains to not yoloremove app-ynh-deps ... --- .../0021_migrate_to_bullseye.py | 34 +++++++++++++--- src/yunohost/tools.py | 39 ++++++++++--------- 2 files changed, 48 insertions(+), 25 deletions(-) diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py index d26eede88..153081916 100644 --- a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py +++ b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py @@ -7,7 +7,7 @@ from moulinette.utils.log import getActionLogger from moulinette.utils.process import check_output, call_async_output from moulinette.utils.filesystem import read_file, rm -from yunohost.tools import Migration, tools_update, tools_upgrade +from yunohost.tools import Migration, tools_update, tools_upgrade, _apt_log_line_is_relevant from yunohost.app import unstable_apps from yunohost.regenconf import manually_modified_files, _force_clear_hashes from yunohost.utils.filesystem import free_space_in_directory @@ -77,9 +77,12 @@ class MyMigration(Migration): _force_clear_hashes(["/etc/mysql/my.cnf"]) rm("/etc/mysql/mariadb.cnf", force=True) rm("/etc/mysql/my.cnf", force=True) - self.apt_install( + ret = self.apt_install( "mariadb-common --reinstall -o Dpkg::Options::='--force-confmiss'" ) + if ret != 0: + # FIXME: i18n once this is stable? + raise YunohostError("Failed to reinstall mariadb-common ?", raw_msg=True) # # /usr/share/yunohost/yunohost-config/ssl/yunoCA -> /usr/share/yunohost/ssl @@ -196,9 +199,12 @@ class MyMigration(Migration): f.strip() for f in check_output(cmd).split("\n") if f.strip() ] - self.apt_install( + ret = self.apt_install( f"{' '.join(php74packages_to_install)} -o Dpkg::Options::='--force-confmiss'" ) + if ret != 0: + # FIXME: i18n once this is stable? + raise YunohostError("Failed to force the install of php dependencies ?", raw_msg=True) # Clean the mess logger.info(m18n.n("migration_0021_cleaning_up")) @@ -209,7 +215,21 @@ class MyMigration(Migration): # Yunohost upgrade # logger.info(m18n.n("migration_0021_yunohost_upgrade")) + self.unhold(apps_packages) + + cmd = "LC_ALL=C" + cmd += " DEBIAN_FRONTEND=noninteractive" + cmd += " APT_LISTCHANGES_FRONTEND=none" + cmd += " apt dist-upgrade " + cmd += " --quiet -o=Dpkg::Use-Pty=0 --fix-broken --dry-run" + cmd += " | grep -q '-ynh-deps'" + + logger.info("Simulating upgrade...") + if os.system(cmd) == 0: + # FIXME: i18n once this is stable? + raise YunohostError("The upgrade cannot be completed, because some app dependencies would need to be removed?", raw_msg=True) + tools_upgrade(target="system") def debian_major_version(self): @@ -344,9 +364,11 @@ class MyMigration(Migration): callbacks = ( lambda l: logger.info("+ " + l.rstrip() + "\r") - if is_relevant(l) + if _apt_log_line_is_relevant(l) else logger.debug(l.rstrip() + "\r"), - lambda l: logger.warning(l.rstrip()), + lambda l: logger.warning(l.rstrip()) + if _apt_log_line_is_relevant(l) + else logger.debug(l.rstrip()), ) cmd = ( @@ -356,7 +378,7 @@ class MyMigration(Migration): logger.debug("Running: %s" % cmd) - call_async_output(cmd, callbacks, shell=True) + return call_async_output(cmd, callbacks, shell=True) def patch_yunohost_conflicts(self): # diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index c825ca2ef..021a8f3a1 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -567,29 +567,12 @@ def tools_upgrade( logger.debug("Running apt command :\n{}".format(dist_upgrade)) - def is_relevant(line): - irrelevants = [ - "service sudo-ldap already provided", - "Reading database ...", - "Preparing to unpack", - "Selecting previously unselected package", - "Created symlink /etc/systemd", - "Replacing config file", - "Creating config file", - "Installing new version of config file", - "Installing new config file as you requested", - ", does not exist on system.", - "unable to delete old directory", - "update-alternatives:", - ] - return line.rstrip() and all(i not in line.rstrip() for i in irrelevants) - callbacks = ( lambda l: logger.info("+ " + l.rstrip() + "\r") - if is_relevant(l) + if _apt_log_line_is_relevant(l) else logger.debug(l.rstrip() + "\r"), lambda l: logger.warning(l.rstrip()) - if is_relevant(l) + if _apt_log_line_is_relevant(l) else logger.debug(l.rstrip()), ) returncode = call_async_output(dist_upgrade, callbacks, shell=True) @@ -689,6 +672,24 @@ def tools_upgrade( operation_logger.success() +def _apt_log_line_is_relevant(line): + irrelevants = [ + "service sudo-ldap already provided", + "Reading database ...", + "Preparing to unpack", + "Selecting previously unselected package", + "Created symlink /etc/systemd", + "Replacing config file", + "Creating config file", + "Installing new version of config file", + "Installing new config file as you requested", + ", does not exist on system.", + "unable to delete old directory", + "update-alternatives:", + ] + return line.rstrip() and all(i not in line.rstrip() for i in irrelevants) + + @is_unit_operation() def tools_shutdown(operation_logger, force=False): shutdown = force From b5e04df39800a1c4754766439d0ebfa7a641ee36 Mon Sep 17 00:00:00 2001 From: Tagada <36127788+Tagadda@users.noreply.github.com> Date: Thu, 6 Jan 2022 11:50:10 +0100 Subject: [PATCH 1051/1155] [fix] Force install certificate on yunohost domain add --- 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 7c512106a..21787ea36 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -187,7 +187,7 @@ def domain_add(operation_logger, domain, dyndns=False): # Actually subscribe dyndns_subscribe(domain=domain) - _certificate_install_selfsigned([domain], False) + _certificate_install_selfsigned([domain], True) try: attr_dict = { From 2b3138ef8027ed13db301e88d3341fc162d7d80c Mon Sep 17 00:00:00 2001 From: Kayou Date: Thu, 6 Jan 2022 17:37:05 +0100 Subject: [PATCH 1052/1155] remove args for metadata (#1405) * remove args for metadata * Add explanation for removing 'args' from log metadata Co-authored-by: Alexandre Aubin --- src/yunohost/log.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index d28a35e18..9f9e0b753 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -659,6 +659,11 @@ class OperationLogger: data["error"] = self._error # TODO: detect if 'extra' erase some key of 'data' data.update(self.extra) + # Remove the 'args' arg from args (yodawg). It corresponds to url-encoded args for app install, config panel set, etc + # Because the data are url encoded, it's hell to properly redact secrets inside it, + # and the useful info is usually already available in `env` too + if "args" in data and isinstance(data["args"], dict) and "args" in data["args"]: + data["args"].pop("args") return data def success(self): From 607fad210593e756f37de8ed053f4f4d65080485 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 7 Jan 2022 00:54:57 +0100 Subject: [PATCH 1053/1155] Sury has a crazy new amount of php-* packages that depend on specific versions of php, we shall ban them or it creates a frakin mess --- data/hooks/conf_regen/10-apt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/conf_regen/10-apt b/data/hooks/conf_regen/10-apt index da0620e59..2b3ae006f 100755 --- a/data/hooks/conf_regen/10-apt +++ b/data/hooks/conf_regen/10-apt @@ -7,7 +7,7 @@ do_pre_regen() { mkdir --parents "${pending_dir}/etc/apt/preferences.d" - packages_to_refuse_from_sury="php php-fpm php-mysql php-xml php-zip php-mbstring php-ldap php-gd php-curl php-bz2 php-json php-sqlite3 php-intl openssl libssl1.1 libssl-dev" + packages_to_refuse_from_sury="php php-* openssl libssl1.1 libssl-dev" for package in $packages_to_refuse_from_sury; do echo " Package: $package From 79c70b76298688a404a3f89555a97abe5a36c04c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 7 Jan 2022 01:01:19 +0100 Subject: [PATCH 1054/1155] migrate_to_bullseye: add the list of app-ynh-deps in the php7.4 install trick, because that seem to solve some stupid dependency blocage issues... --- src/yunohost/data_migrations/0021_migrate_to_bullseye.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py index 153081916..9ec8dc17d 100644 --- a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py +++ b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py @@ -200,7 +200,9 @@ class MyMigration(Migration): ] ret = self.apt_install( - f"{' '.join(php74packages_to_install)} -o Dpkg::Options::='--force-confmiss'" + f"{' '.join(php74packages_to_install)} " + "$(dpkg --list | grep ynh-deps | awk '{print $2}') " + "-o Dpkg::Options::='--force-confmiss'" ) if ret != 0: # FIXME: i18n once this is stable? From 9cfbbd122b341eb53dde43f90c2e877024fa3287 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 7 Jan 2022 02:26:18 +0100 Subject: [PATCH 1055/1155] migrate_to_bullseye: add sury before running apt update otherwise we end up in an inconsistent state --- .../0021_migrate_to_bullseye.py | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py index 9ec8dc17d..8f15019e9 100644 --- a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py +++ b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py @@ -50,6 +50,25 @@ class MyMigration(Migration): # logger.info(m18n.n("migration_0021_patching_sources_list")) self.patch_apt_sources_list() + + # Force add sury if it's not there yet + # This is to solve some weird issue with php-common breaking php7.3-common, + # hence breaking many php7.3-deps + # hence triggering some dependency conflict (or foobar-ynh-deps uninstall) + # Adding it there shouldnt be a big deal - Yunohost 11.x does add it + # through its regen conf anyway. + if not os.path.exists("/etc/apt/sources.list.d/extra_php_version.list"): + open("/etc/apt/sources.list.d/extra_php_version.list", "w").write( + "deb https://packages.sury.org/php/ bullseye main" + ) + os.system( + 'wget --timeout 900 --quiet "https://packages.sury.org/php/apt.gpg" --output-document=- | gpg --dearmor >"/etc/apt/trusted.gpg.d/extra_php_version.gpg"' + ) + + # + # Run apt update + # + tools_update(target="system") # Tell libc6 it's okay to restart system stuff during the upgrade @@ -100,20 +119,6 @@ class MyMigration(Migration): os.system("mv /home/yunohost.conf /var/cache/yunohost/regenconf") rm("/home/yunohost.conf", recursive=True, force=True) - # Force add sury if it's not there yet - # This is to solve some weird issue with php-common breaking php7.3-common, - # hence breaking many php7.3-deps - # hence triggering some dependency conflict (or foobar-ynh-deps uninstall) - # Adding it there shouldnt be a big deal - Yunohost 11.x does add it - # through its regen conf anyway. - if not os.path.exists("/etc/apt/sources.list.d/extra_php_version.list"): - open("/etc/apt/sources.list.d/extra_php_version.list", "w").write( - "deb https://packages.sury.org/php/ bullseye main" - ) - os.system( - 'wget --timeout 900 --quiet "https://packages.sury.org/php/apt.gpg" --output-document=- | gpg --dearmor >"/etc/apt/trusted.gpg.d/extra_php_version.gpg"' - ) - # Remove legacy postgresql service record added by helpers, # will now be dynamically handled by the core in bullseye services = _get_services() From b7121502d646796622fb4b1ade5859e9c6c18d78 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 7 Jan 2022 02:48:02 +0100 Subject: [PATCH 1056/1155] More sury/php madness ... forbidding php-common doesnt seem to be good idea so let's have it --- data/hooks/conf_regen/10-apt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/data/hooks/conf_regen/10-apt b/data/hooks/conf_regen/10-apt index 2b3ae006f..9eadea90a 100755 --- a/data/hooks/conf_regen/10-apt +++ b/data/hooks/conf_regen/10-apt @@ -15,6 +15,11 @@ Pin: origin \"packages.sury.org\" Pin-Priority: -1" >>"${pending_dir}/etc/apt/preferences.d/extra_php_version" done + echo " +Package: php-common +Pin: origin \"packages.sury.org\" +Pin-Priority: 500" >>"${pending_dir}/etc/apt/preferences.d/extra_php_version" + echo " # PLEASE READ THIS WARNING AND DON'T EDIT THIS FILE From beadea5305e7b9ad1211d03b820f3e02abfa1514 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Fri, 7 Jan 2022 10:47:03 +0100 Subject: [PATCH 1057/1155] remove unused import --- src/dyndns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dyndns.py b/src/dyndns.py index 521b16218..69f1989f6 100644 --- a/src/dyndns.py +++ b/src/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, read_file, rm, chown, chmod +from moulinette.utils.filesystem import write_to_file, rm, chown, chmod from moulinette.utils.network import download_json from yunohost.utils.error import YunohostError, YunohostValidationError From b0f756b1a8a15d217df18a6b4b9ae23a2b9f773a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 7 Jan 2022 14:58:50 +0100 Subject: [PATCH 1058/1155] Aaaand order matters in apt preferences --- data/hooks/conf_regen/10-apt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/data/hooks/conf_regen/10-apt b/data/hooks/conf_regen/10-apt index 9eadea90a..8e005cbec 100755 --- a/data/hooks/conf_regen/10-apt +++ b/data/hooks/conf_regen/10-apt @@ -7,6 +7,11 @@ do_pre_regen() { mkdir --parents "${pending_dir}/etc/apt/preferences.d" + echo " +Package: php-common +Pin: origin \"packages.sury.org\" +Pin-Priority: 500" >>"${pending_dir}/etc/apt/preferences.d/extra_php_version" + packages_to_refuse_from_sury="php php-* openssl libssl1.1 libssl-dev" for package in $packages_to_refuse_from_sury; do echo " @@ -15,11 +20,6 @@ Pin: origin \"packages.sury.org\" Pin-Priority: -1" >>"${pending_dir}/etc/apt/preferences.d/extra_php_version" done - echo " -Package: php-common -Pin: origin \"packages.sury.org\" -Pin-Priority: 500" >>"${pending_dir}/etc/apt/preferences.d/extra_php_version" - echo " # PLEASE READ THIS WARNING AND DON'T EDIT THIS FILE From 7aa840a9569cc6d137d5e58c8df316812824945f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 7 Jan 2022 22:46:15 +0100 Subject: [PATCH 1059/1155] Typo: grep doesn't like args starting with dashes --- src/yunohost/data_migrations/0021_migrate_to_bullseye.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py index 8f15019e9..b046b4b66 100644 --- a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py +++ b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py @@ -230,7 +230,7 @@ class MyMigration(Migration): cmd += " APT_LISTCHANGES_FRONTEND=none" cmd += " apt dist-upgrade " cmd += " --quiet -o=Dpkg::Use-Pty=0 --fix-broken --dry-run" - cmd += " | grep -q '-ynh-deps'" + cmd += " | grep -q 'ynh-deps'" logger.info("Simulating upgrade...") if os.system(cmd) == 0: From bbcbc411ea56eaeb4c78cfa529ce49e29028a1c9 Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Fri, 7 Jan 2022 22:02:58 +0000 Subject: [PATCH 1060/1155] [CI] Format code with Black --- .../0021_migrate_to_bullseye.py | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py index b046b4b66..9a45cfb95 100644 --- a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py +++ b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py @@ -7,7 +7,12 @@ from moulinette.utils.log import getActionLogger from moulinette.utils.process import check_output, call_async_output from moulinette.utils.filesystem import read_file, rm -from yunohost.tools import Migration, tools_update, tools_upgrade, _apt_log_line_is_relevant +from yunohost.tools import ( + Migration, + tools_update, + tools_upgrade, + _apt_log_line_is_relevant, +) from yunohost.app import unstable_apps from yunohost.regenconf import manually_modified_files, _force_clear_hashes from yunohost.utils.filesystem import free_space_in_directory @@ -182,12 +187,14 @@ class MyMigration(Migration): "zip", ] - cmd = "apt show '*-ynh-deps' 2>/dev/null" \ - " | grep Depends" \ - f" | grep -o -E \"php7.3-({'|'.join(php73packages_suffixes)})\"" \ - " | sort | uniq" \ - " | sed 's/php7.3/php7.4/g'" \ - " || true" + cmd = ( + "apt show '*-ynh-deps' 2>/dev/null" + " | grep Depends" + f" | grep -o -E \"php7.3-({'|'.join(php73packages_suffixes)})\"" + " | sort | uniq" + " | sed 's/php7.3/php7.4/g'" + " || true" + ) php74packages_to_install = [ "php7.4-fpm", @@ -211,7 +218,9 @@ class MyMigration(Migration): ) if ret != 0: # FIXME: i18n once this is stable? - raise YunohostError("Failed to force the install of php dependencies ?", raw_msg=True) + raise YunohostError( + "Failed to force the install of php dependencies ?", raw_msg=True + ) # Clean the mess logger.info(m18n.n("migration_0021_cleaning_up")) @@ -235,7 +244,10 @@ class MyMigration(Migration): logger.info("Simulating upgrade...") if os.system(cmd) == 0: # FIXME: i18n once this is stable? - raise YunohostError("The upgrade cannot be completed, because some app dependencies would need to be removed?", raw_msg=True) + raise YunohostError( + "The upgrade cannot be completed, because some app dependencies would need to be removed?", + raw_msg=True, + ) tools_upgrade(target="system") From b59348fffe7003756331e070fee56dc97ff6740e Mon Sep 17 00:00:00 2001 From: ljf Date: Sat, 8 Jan 2022 17:33:58 +0100 Subject: [PATCH 1061/1155] [fix] Force dependencies upgrade even if previous version is accepted --- data/helpers.d/apt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index 281e17f70..0b75138aa 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -425,6 +425,12 @@ ynh_install_extra_app_dependencies() { # Install requested dependencies from this extra repository. ynh_install_app_dependencies "$package" + # Force to upgrade to the last version... + # Without doing apt install, an already installed dep is not upgraded + local apps_auto_installed="$(apt-mark showauto $package)" + ynh_package_install "$package" + apt-mark auto $apps_auto_installed + # Remove this extra repository after packages are installed ynh_remove_extra_repo --name=$app } From b63136da5264384b28ca00432dc57196971c7f03 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 9 Jan 2022 18:40:53 +0100 Subject: [PATCH 1062/1155] certificate: don't attempt to restart metronome if not installed --- src/certificate.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/certificate.py b/src/certificate.py index 0955b9f7c..adf707000 100644 --- a/src/certificate.py +++ b/src/certificate.py @@ -792,6 +792,9 @@ def _enable_certificate(domain, new_cert_folder): logger.debug("Restarting services...") for service in ("postfix", "dovecot", "metronome"): + # Ugly trick to not restart metronome if it's not installed + if service == "metronome" and os.system("dpkg --list | grep -q 'ii *metronome'") != 0: + continue _run_service_command("restart", service) if os.path.isfile("/etc/yunohost/installed"): From ca71d374477c9b7a337ce1f514c0d9dab6a9923d Mon Sep 17 00:00:00 2001 From: Germain Edy Date: Wed, 29 Dec 2021 11:48:02 +0000 Subject: [PATCH 1063/1155] Translated using Weblate (Spanish) Currently translated at 83.5% (601 of 719 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/locales/es.json b/locales/es.json index 22493ec11..06f9385cf 100644 --- a/locales/es.json +++ b/locales/es.json @@ -615,5 +615,9 @@ "diagnosis_apps_bad_quality": "Esta aplicación está etiquetada como defectuosa en el catálogo de aplicaciones YunoHost. Podría ser un problema temporal mientras las personas responsables corrigen el asunto. Mientras tanto, la actualización de esta aplicación está desactivada.", "diagnosis_apps_broken": "Esta aplicación está etiquetada como defectuosa en el catálogo de aplicaciones YunoHost. Podría ser un problema temporal mientras las personas responsables corrigen el asunto. Mientras tanto, la actualización de esta aplicación está desactivada.", "diagnosis_apps_deprecated_practices": "La versión instalada de esta aplicación usa aún prácticas de empaquetado obsoletas. Deberías actualizarla.", - "diagnosis_apps_outdated_ynh_requirement": "La versión instalada de esta aplicación solo necesita YunoHost >= 2.x, lo que indica que no está al día con la buena praxis de ayudas y empaquetado recomendadas. Deberías actualizarla." -} \ No newline at end of file + "diagnosis_apps_outdated_ynh_requirement": "La versión instalada de esta aplicación solo necesita YunoHost >= 2.x, lo que indica que no está al día con la buena praxis de ayudas y empaquetado recomendadas. Deberías actualizarla.", + "domain_dns_conf_special_use_tld": "Este dominio se basa en un dominio de primer nivel (TLD) de usos especiales como .local o .test y no debería tener entradas DNS reales.", + "diagnosis_sshd_config_insecure": "Parece que la configuración SSH ha sido modificada manualmente, y es insegura porque no tiene ninguna instrucción 'AllowGroups' o 'AllowUsers' para limitar el acceso a los usuarios autorizados.", + "domain_dns_push_not_applicable": "La configuración automática de los registros DNS no puede realizarse en el dominio {domain}. Deberìas configurar manualmente los registros DNS siguiendo la documentación.", + "domain_dns_push_managed_in_parent_domain": "La configuración automática de los registros DNS es administrada desde el dominio superior {parent_domain}." +} From fb85030bc30ae9831029dcd61bd618cf6c67b400 Mon Sep 17 00:00:00 2001 From: Germain Edy Date: Wed, 29 Dec 2021 10:07:04 +0000 Subject: [PATCH 1064/1155] Translated using Weblate (French) Currently translated at 100.0% (719 of 719 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 cc7088c19..cb2547d9e 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -216,7 +216,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}]", "server_reboot": "Le serveur va redémarrer", @@ -337,7 +337,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", @@ -376,7 +376,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à", @@ -483,7 +483,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 est 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.", @@ -547,7 +547,7 @@ "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_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_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}", @@ -611,7 +611,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 de YunoHost.", + "restore_backup_too_old": "Cette sauvegarde ne peut pas être restaurée car elle provient d'une version YunoHost trop ancienne.", "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", @@ -718,4 +718,4 @@ "migration_0021_patch_yunohost_conflicts": "Application du correctif pour contourner le problème de conflit...", "migration_0021_not_buster": "La distribution Debian actuelle n'est pas Buster !", "migration_description_0021_migrate_to_bullseye": "Mise à niveau du système vers Debian Bullseye et YunoHost 11.x" -} \ No newline at end of file +} From 39d28af963c6ab5b57ef0af3f4fc38e6e4648d23 Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Thu, 30 Dec 2021 09:16:46 +0000 Subject: [PATCH 1065/1155] Translated using Weblate (German) Currently translated at 98.0% (705 of 719 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 537621f99..c5de78e7a 100644 --- a/locales/de.json +++ b/locales/de.json @@ -285,7 +285,7 @@ "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_no_ipv6": "Der Server verfügt nicht über eine funktionierende IPv6-Adresse.", "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. Es wird keine neue Diagnose durchgeführt!)", @@ -318,10 +318,10 @@ "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önntest Du mittels yunohost dyndns update --force ein Update erzwingen.", - "diagnosis_dns_point_to_doc": "Bitte schaue in die Dokumentation unter https://yunohost.org/dns_config wenn du hilfe bei der Konfiguration der DNS Einträge brauchst.", + "diagnosis_dns_point_to_doc": "Bitte schauen Sie in der Dokumentation unter https://yunohost.org/dns_config nach, 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}", - "diagnosis_dns_bad_conf": "Einige DNS Einträge für die Domäne {domain} fehlen oder sind nicht korrekt (Kategorie {category})", + "diagnosis_dns_bad_conf": "Einige DNS-Einträge für die Domäne {domain} fehlen oder sind nicht korrekt (Kategorie {category})", "diagnosis_ip_local": "Lokale IP: {local}", "diagnosis_ip_global": "Globale IP: {global}", "diagnosis_ip_no_ipv6_tip": "Die Verwendung von IPv6 ist nicht Voraussetzung für das Funktionieren Ihres Servers, trägt aber zur Gesundheit des Internet als Ganzes bei. IPv6 sollte normalerweise automatisch von Ihrem Server oder Ihrem Provider konfiguriert werden, sofern verfügbar. Andernfalls müßen Sie einige Dinge manuell konfigurieren. Weitere Informationen finden Sie hier: https://yunohost.org/#/ipv6. Wenn Sie IPv6 nicht aktivieren können oder Ihnen das zu technisch ist, können Sie diese Warnung gefahrlos ignorieren.", @@ -704,4 +704,4 @@ "log_domain_config_set": "Konfiguration für die Domäne '{}' aktualisieren", "log_domain_dns_push": "DNS-Einträge für die Domäne '{}' übertragen", "service_description_yunomdns": "Ermöglicht es dir, deinen Server über 'yunohost.local' in deinem lokalen Netzwerk zu erreichen" -} \ No newline at end of file +} From e0e93dd29ed2e366377b8e3a41b00ea706457a13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Mon, 3 Jan 2022 16:44:54 +0000 Subject: [PATCH 1066/1155] Translated using Weblate (French) Currently translated at 100.0% (720 of 720 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 cb2547d9e..2242a48ab 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -717,5 +717,6 @@ "migration_0021_cleaning_up": "Nettoyage du cache et des paquets qui ne sont plus nécessaires...", "migration_0021_patch_yunohost_conflicts": "Application du correctif pour contourner le problème de conflit...", "migration_0021_not_buster": "La distribution Debian actuelle n'est pas Buster !", - "migration_description_0021_migrate_to_bullseye": "Mise à niveau du système vers Debian Bullseye et YunoHost 11.x" + "migration_description_0021_migrate_to_bullseye": "Mise à niveau du système vers Debian Bullseye et YunoHost 11.x", + "global_settings_setting_security_ssh_password_authentication": "Autoriser l'authentification par mot de passe pour SSH" } From 80805b3bc6b602e446837c2f2a8fcc1cd49ffde4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Tue, 4 Jan 2022 04:51:36 +0000 Subject: [PATCH 1067/1155] Translated using Weblate (Galician) Currently translated at 100.0% (720 of 720 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/locales/gl.json b/locales/gl.json index 3c577b935..8820987e0 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -717,5 +717,6 @@ "migration_0021_patch_yunohost_conflicts": "Solucionando os problemas e conflitos...", "migration_description_0021_migrate_to_bullseye": "Actualizar o sistema a Debian Bullseye e YunoHost 11.x", "migration_0021_system_not_fully_up_to_date": "O teu sistema non está completamente actualizado. Fai unha actualización normal antes de executar a migración a Bullseye.", - "migration_0021_general_warning": "Ten en conta que a migración é unha operación delicada. O equipo de YunoHost fixo todo o que puido para revisalo e probalo, pero aínda así poderían acontecer fallos no sistema ou apps.\n\nAsí as cousas, é recomendable:\n - Facer unha copia de apoio dos datos e apps importantes. Máis info en https://yunohost.org/backup;\n - Ter paciencia unha vez inicias a migración: dependendo da túa conexión a internet e hardware, podería levarlle varias horas completar o proceso." -} \ No newline at end of file + "migration_0021_general_warning": "Ten en conta que a migración é unha operación delicada. O equipo de YunoHost fixo todo o que puido para revisalo e probalo, pero aínda así poderían acontecer fallos no sistema ou apps.\n\nAsí as cousas, é recomendable:\n - Facer unha copia de apoio dos datos e apps importantes. Máis info en https://yunohost.org/backup;\n - Ter paciencia unha vez inicias a migración: dependendo da túa conexión a internet e hardware, podería levarlle varias horas completar o proceso.", + "global_settings_setting_security_ssh_password_authentication": "Permitir autenticación con contrasinal para SSH" +} From dbc8852730723d808a257e4dd8c460d6608ac41e Mon Sep 17 00:00:00 2001 From: Christian Wehrli Date: Thu, 6 Jan 2022 07:48:48 +0000 Subject: [PATCH 1068/1155] Translated using Weblate (German) Currently translated at 97.9% (705 of 720 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 c5de78e7a..3aa07475e 100644 --- a/locales/de.json +++ b/locales/de.json @@ -590,7 +590,7 @@ "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)", "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:innen, Domains und verbundene Informationen", + "service_description_slapd": "Speichert Benutzer:innen, Domänen 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", From aba7e3fba38ba9f47cfae703426669994ef26b17 Mon Sep 17 00:00:00 2001 From: Tymofii-Lytvynenko Date: Sun, 9 Jan 2022 02:45:22 +0000 Subject: [PATCH 1069/1155] Translated using Weblate (Ukrainian) Currently translated at 100.0% (720 of 720 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/uk/ --- locales/uk.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/locales/uk.json b/locales/uk.json index e997d6bf4..c43a6e490 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -717,5 +717,6 @@ "migration_0021_not_enough_free_space": "Вільного місця в /var/ досить мало! У вас повинно бути не менше 1 ГБ вільного місця, щоб запустити цю міграцію.", "migration_0021_system_not_fully_up_to_date": "Ваша система не повністю оновлена. Будь ласка, виконайте регулярне оновлення перед запуском міграції на Bullseye.", "migration_0021_general_warning": "Будь ласка, зверніть увагу, що ця міграція є делікатною операцією. Команда YunoHost зробила все можливе, щоб перевірити і протестувати її, але міграція все ще може порушити частину системи або її застосунків.\n\nТому рекомендовано:\n - Виконати резервне копіювання всіх важливих даних або застосунків. Подробиці на сайті https://yunohost.org/backup; \n - Наберіться терпіння після запуску міграції: В залежності від вашого з'єднання з Інтернетом і апаратного забезпечення, оновлення може зайняти до декількох годин.", - "migration_description_0021_migrate_to_bullseye": "Оновлення системи до Debian Bullseye і YunoHost 11.x" -} \ No newline at end of file + "migration_description_0021_migrate_to_bullseye": "Оновлення системи до Debian Bullseye і YunoHost 11.x", + "global_settings_setting_security_ssh_password_authentication": "Дозволити автентифікацію паролем для SSH" +} From cc7fe09efae657f14111d5137e630729025bb8c1 Mon Sep 17 00:00:00 2001 From: Boudewijn Date: Sun, 9 Jan 2022 11:08:58 +0000 Subject: [PATCH 1070/1155] Translated using Weblate (Dutch) Currently translated at 11.3% (82 of 720 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/nl/ --- locales/nl.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/nl.json b/locales/nl.json index 1c3e2083d..038d18283 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -130,4 +130,4 @@ "app_label_deprecated": "Dit commando is vervallen. Gebruik alsjeblieft het nieuwe commando 'yunohost user permission update' om het label van de app te beheren.", "app_change_url_no_script": "De app '{app_name}' ondersteunt nog geen URL-aanpassingen. Misschien wel na een upgrade.", "app_upgrade_some_app_failed": "Sommige apps konden niet worden bijgewerkt" -} \ No newline at end of file +} From af1937c596f3c1f5c35ffe689cfccccdcf33dd11 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 11 Jan 2022 12:57:53 +0100 Subject: [PATCH 1071/1155] Typo --- src/authenticators/ldap_admin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/authenticators/ldap_admin.py b/src/authenticators/ldap_admin.py index 7f96165cb..872dd3c8d 100644 --- a/src/authenticators/ldap_admin.py +++ b/src/authenticators/ldap_admin.py @@ -120,7 +120,6 @@ class Authenticator(BaseAuthenticator): return infos - @staticmethod def delete_session_cookie(self): from bottle import response From fedcf6dabbabc565812c91d09b85f2b325504c99 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 11 Jan 2022 16:04:25 +0100 Subject: [PATCH 1072/1155] migrate_to_bullseye: mark the base php7.4 packages as auto --- .../data_migrations/0021_migrate_to_bullseye.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py index 9a45cfb95..e47087976 100644 --- a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py +++ b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py @@ -196,7 +196,7 @@ class MyMigration(Migration): " || true" ) - php74packages_to_install = [ + basephp74packages_to_install = [ "php7.4-fpm", "php7.4-common", "php7.4-ldap", @@ -207,7 +207,7 @@ class MyMigration(Migration): "php-php-gettext", ] - php74packages_to_install += [ + php74packages_to_install = basephp74packages_to_install + [ f.strip() for f in check_output(cmd).split("\n") if f.strip() ] @@ -222,6 +222,8 @@ class MyMigration(Migration): "Failed to force the install of php dependencies ?", raw_msg=True ) + os.system(f"apt-mark auto {' '.join(basephp74packages_to_install)}") + # Clean the mess logger.info(m18n.n("migration_0021_cleaning_up")) os.system("apt autoremove --assume-yes") @@ -371,11 +373,11 @@ class MyMigration(Migration): def hold(self, packages): for package in packages: - os.system("apt-mark hold {}".format(package)) + os.system(f"apt-mark hold {package}") def unhold(self, packages): for package in packages: - os.system("apt-mark unhold {}".format(package)) + os.system(f"apt-mark unhold {package}") def apt_install(self, cmd): def is_relevant(line): From 23d58c121793ed28a64dd8185d4976cf14f4b529 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 11 Jan 2022 16:06:32 +0100 Subject: [PATCH 1073/1155] Reformat fr locale --- locales/fr.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 2242a48ab..92dc4b68a 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -216,7 +216,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}]", "server_reboot": "Le serveur va redémarrer", @@ -337,7 +337,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", @@ -376,7 +376,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à", @@ -547,7 +547,7 @@ "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_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_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}", From a118a5a1322c3d15b3ed45e41ecd71d3be2a6f13 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 11 Jan 2022 16:14:20 +0100 Subject: [PATCH 1074/1155] ci: typo --- .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 8daa4f473..fc7ade862 100644 --- a/.gitlab/ci/translation.gitlab-ci.yml +++ b/.gitlab/ci/translation.gitlab-ci.yml @@ -23,7 +23,7 @@ autofix-translated-strings: script: # create a local branch that will overwrite distant one - git checkout -b "ci-autofix-translated-strings-${CI_COMMIT_REF_NAME}" --no-track - - python3 maintenance/missing_i18n_keys --fix + - python3 maintenance/missing_i18n_keys.py --fix - python3 maintenanceautofix_locale_format.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 From ea6500ebfd08473ce9a7eaeca9853795f1c0f327 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 11 Jan 2022 17:10:07 +0100 Subject: [PATCH 1075/1155] ldap: having to repeat the base dn everytime we call search() is boring and inconsistent with other methods, let's use relative dns instead --- src/domain.py | 2 +- src/permission.py | 4 ++-- src/ssh.py | 2 +- src/tests/test_permission.py | 6 +++--- src/user.py | 14 +++++++------- src/utils/ldap.py | 4 +++- 6 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/domain.py b/src/domain.py index 2560a42f2..57c990762 100644 --- a/src/domain.py +++ b/src/domain.py @@ -69,7 +69,7 @@ def domain_list(exclude_subdomains=False): result = [ entry["virtualdomain"][0] for entry in ldap.search( - "ou=domains,dc=yunohost,dc=org", "virtualdomain=*", ["virtualdomain"] + "ou=domains", "virtualdomain=*", ["virtualdomain"] ) ] diff --git a/src/permission.py b/src/permission.py index e87715e63..493f17278 100644 --- a/src/permission.py +++ b/src/permission.py @@ -58,7 +58,7 @@ def user_permission_list( ldap = _get_ldap_interface() permissions_infos = ldap.search( - "ou=permission,dc=yunohost,dc=org", + "ou=permission", "(objectclass=permissionYnh)", [ "cn", @@ -408,7 +408,7 @@ def permission_create( # Validate uniqueness of permission in LDAP if ldap.get_conflict( - {"cn": permission}, base_dn="ou=permission,dc=yunohost,dc=org" + {"cn": permission}, base_dn="ou=permission" ): raise YunohostValidationError("permission_already_exist", permission=permission) diff --git a/src/ssh.py b/src/ssh.py index ecee39f4a..98fa8fb3c 100644 --- a/src/ssh.py +++ b/src/ssh.py @@ -172,7 +172,7 @@ def _get_user_for_ssh(username, attrs=None): ldap = _get_ldap_interface() user = ldap.search( - "ou=users,dc=yunohost,dc=org", + "ou=users", "(&(objectclass=person)(uid=%s))" % username, attrs, ) diff --git a/src/tests/test_permission.py b/src/tests/test_permission.py index 9c059f0e4..4e7f9f53d 100644 --- a/src/tests/test_permission.py +++ b/src/tests/test_permission.py @@ -236,17 +236,17 @@ def check_LDAP_db_integrity(): ldap = _get_ldap_interface() user_search = ldap.search( - "ou=users,dc=yunohost,dc=org", + "ou=users", "(&(objectclass=person)(!(uid=root))(!(uid=nobody)))", ["uid", "memberOf", "permission"], ) group_search = ldap.search( - "ou=groups,dc=yunohost,dc=org", + "ou=groups", "(objectclass=groupOfNamesYnh)", ["cn", "member", "memberUid", "permission"], ) permission_search = ldap.search( - "ou=permission,dc=yunohost,dc=org", + "ou=permission", "(objectclass=permissionYnh)", ["cn", "groupPermission", "inheritPermission", "memberUid"], ) diff --git a/src/user.py b/src/user.py index be9b74641..6f99321bb 100644 --- a/src/user.py +++ b/src/user.py @@ -111,7 +111,7 @@ def user_list(fields=None): ldap = _get_ldap_interface() result = ldap.search( - "ou=users,dc=yunohost,dc=org", + "ou=users", "(&(objectclass=person)(!(uid=root))(!(uid=nobody)))", attrs, ) @@ -233,7 +233,7 @@ def user_create( } # If it is the first user, add some aliases - if not ldap.search(base="ou=users,dc=yunohost,dc=org", filter="uid=*"): + if not ldap.search(base="ou=users", filter="uid=*"): attr_dict["mail"] = [attr_dict["mail"]] + aliases try: @@ -377,7 +377,7 @@ def user_update( ldap = _get_ldap_interface() attrs_to_fetch = ["givenName", "sn", "mail", "maildrop"] result = ldap.search( - base="ou=users,dc=yunohost,dc=org", + base="ou=users", filter="uid=" + username, attrs=attrs_to_fetch, ) @@ -538,7 +538,7 @@ def user_info(username): else: filter = "uid=" + username - result = ldap.search("ou=users,dc=yunohost,dc=org", filter, user_attrs) + result = ldap.search("ou=users", filter, user_attrs) if result: user = result[0] @@ -938,7 +938,7 @@ def user_group_list(short=False, full=False, include_primary_groups=True): ldap = _get_ldap_interface() groups_infos = ldap.search( - "ou=groups,dc=yunohost,dc=org", + "ou=groups", "(objectclass=groupOfNamesYnh)", ["cn", "member", "permission"], ) @@ -989,7 +989,7 @@ def user_group_create( # Validate uniqueness of groupname in LDAP conflict = ldap.get_conflict( - {"cn": groupname}, base_dn="ou=groups,dc=yunohost,dc=org" + {"cn": groupname}, base_dn="ou=groups" ) if conflict: raise YunohostValidationError("group_already_exist", group=groupname) @@ -1204,7 +1204,7 @@ def user_group_info(groupname): # Fetch info for this group result = ldap.search( - "ou=groups,dc=yunohost,dc=org", + "ou=groups", "cn=" + groupname, ["cn", "member", "permission"], ) diff --git a/src/utils/ldap.py b/src/utils/ldap.py index 651d09f75..98c0fecf7 100644 --- a/src/utils/ldap.py +++ b/src/utils/ldap.py @@ -140,6 +140,8 @@ class LDAPInterface: """ if not base: base = self.basedn + else: + base = base + "," + self.basedn try: result = self.con.search_s(base, ldap.SCOPE_SUBTREE, filter, attrs) @@ -241,7 +243,7 @@ class LDAPInterface: """ dn = rdn + "," + self.basedn - actual_entry = self.search(base=dn, attrs=None) + actual_entry = self.search(rdn, attrs=None) ldif = modlist.modifyModlist(actual_entry[0], attr_dict, ignore_oldexistent=1) if ldif == []: From 3fe44ee73bbe7c96dfb5b5ecc19db1efe37f5d43 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 11 Jan 2022 19:50:43 +0100 Subject: [PATCH 1076/1155] ci: moar typoz, wtf @_@ --- .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 fc7ade862..0d73852c7 100644 --- a/.gitlab/ci/translation.gitlab-ci.yml +++ b/.gitlab/ci/translation.gitlab-ci.yml @@ -4,7 +4,7 @@ test-i18n-keys: stage: translation script: - - python3 maintenance/missing_i18n_keys --check + - python3 maintenance/missing_i18n_keys.py --check only: changes: - locales/en.json @@ -24,7 +24,7 @@ autofix-translated-strings: # create a local branch that will overwrite distant one - git checkout -b "ci-autofix-translated-strings-${CI_COMMIT_REF_NAME}" --no-track - python3 maintenance/missing_i18n_keys.py --fix - - python3 maintenanceautofix_locale_format.py + - python3 maintenance/autofix_locale_format.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 "HEAD":"ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}" From d81b85a49abebdbf38caf07b30219f2964c4af9d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 11 Jan 2022 20:54:55 +0100 Subject: [PATCH 1077/1155] diagnosis: incorrect dns check (relative vs absolute) for CNAME on subdomain --- 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 554576f79..21b3a21a1 100644 --- a/data/hooks/diagnosis/12-dnsrecords.py +++ b/data/hooks/diagnosis/12-dnsrecords.py @@ -97,6 +97,8 @@ class DNSRecordsDiagnoser(Diagnoser): r["current"] = self.get_current_record(fqdn, r["type"]) if r["value"] == "@": r["value"] = domain + "." + elif r["type"] == "CNAME": + r["value"] = r["value"] + f".{base_dns_zone}." if self.current_record_match_expected(r): results[id_] = "OK" From b1fe61ed68be0f80b3ffcce09d63395e5a95f1c8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 11 Jan 2022 21:37:11 +0100 Subject: [PATCH 1078/1155] codequality: fstring all the things! (well, not all but a lot :P) --- src/app.py | 32 +++++++++++-------------------- src/app_catalog.py | 29 ++++++++-------------------- src/backup.py | 37 +++++++++++++++++------------------- src/certificate.py | 19 +++++++------------ src/diagnosis.py | 46 +++++++++++++++++++-------------------------- src/dns.py | 2 +- src/domain.py | 15 +++++++-------- src/hook.py | 7 +++---- src/permission.py | 15 +++++++-------- src/regenconf.py | 17 +++++++---------- src/service.py | 21 +++++++++------------ src/settings.py | 13 ++++++------- src/ssh.py | 4 ++-- src/tools.py | 14 +++++++------- src/user.py | 32 +++++++++++++++---------------- src/utils/legacy.py | 2 +- 16 files changed, 127 insertions(+), 178 deletions(-) diff --git a/src/app.py b/src/app.py index 0ba8cf04d..76c86e326 100644 --- a/src/app.py +++ b/src/app.py @@ -170,7 +170,7 @@ def app_info(app, full=False): ret["label"] = permissions.get(app + ".main", {}).get("label") if not ret["label"]: - logger.warning("Failed to get label for app %s ?" % app) + logger.warning(f"Failed to get label for app {app} ?") return ret @@ -285,8 +285,7 @@ def app_map(app=None, raw=False, user=None): 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 + f"Uhoh, no main permission was found for app {app_id} ... sounds like an app was only partially removed due to another bug :/" ) continue main_perm = permissions[app_id + ".main"] @@ -406,7 +405,7 @@ def app_change_url(operation_logger, app, domain, path): # Execute App change_url script ret = hook_exec(change_url_script, env=env_dict)[0] if ret != 0: - msg = "Failed to change '%s' url." % app + msg = f"Failed to change '{app}' url." logger.error(msg) operation_logger.error(msg) @@ -845,7 +844,7 @@ def app_install( 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()] + del env_dict_for_logging[f"YNH_APP_ARG_{question.name.upper()}"] operation_logger.extra.update({"env": env_dict_for_logging}) @@ -892,8 +891,7 @@ def app_install( # 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, + f"The installation of {app_id} failed, but was not cleaned up as requested by --no-remove-on-failure.", raw_msg=True, ) else: @@ -1427,9 +1425,9 @@ def app_action_run(operation_logger, app, action, args=None): actions = {x["id"]: x for x in actions} if action not in actions: + available_actions = ", ".join(actions.keys()), raise YunohostValidationError( - "action '%s' not available for app '%s', available actions are: %s" - % (action, app, ", ".join(actions.keys())), + f"action '{action}' not available for app '{app}', available actions are: {available_actions}", raw_msg=True, ) @@ -1852,8 +1850,7 @@ def _get_manifest_of_app(path): 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, + f"There doesn't seem to be any manifest file in {path} ... It looks like an app was not correctly installed/removed.", raw_msg=True, ) @@ -2093,7 +2090,7 @@ def _extract_app_from_gitrepo( cmd = f"git ls-remote --exit-code {url} {branch} | awk '{{print $1}}'" manifest["remote"]["revision"] = check_output(cmd) except Exception as e: - logger.warning("cannot get last commit hash because: %s ", e) + logger.warning(f"cannot get last commit hash because: {e}") else: manifest["remote"]["revision"] = revision manifest["lastUpdate"] = app_info.get("lastUpdate") @@ -2279,14 +2276,7 @@ def _assert_no_conflicting_apps(domain, path, ignore_app=None, full_domain=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, - ) - ) + apps.append(f" * {domain}{path} → {app_label} ({app_id})") if full_domain: raise YunohostValidationError("app_full_domain_unavailable", domain=domain) @@ -2415,7 +2405,7 @@ def is_true(arg): elif isinstance(arg, str): return arg.lower() in ["yes", "true", "on"] else: - logger.debug("arg should be a boolean or a string, got %r", arg) + logger.debug(f"arg should be a boolean or a string, got {arg}") return True if arg else False diff --git a/src/app_catalog.py b/src/app_catalog.py index c2cbfcd32..b2b35b8c3 100644 --- a/src/app_catalog.py +++ b/src/app_catalog.py @@ -103,9 +103,7 @@ def _initialize_apps_catalog_system(): ) write_to_yaml(APPS_CATALOG_CONF, default_apps_catalog_list) except Exception as e: - raise YunohostError( - "Could not initialize the apps catalog system... : %s" % str(e) - ) + raise YunohostError(f"Could not initialize the apps catalog system... : {e}", raw_msg=True) logger.success(m18n.n("apps_catalog_init_success")) @@ -121,14 +119,12 @@ def _read_apps_catalog_list(): # by returning [] if list_ is None return list_ if list_ else [] except Exception as e: - raise YunohostError("Could not read the apps_catalog list ... : %s" % str(e)) + raise YunohostError(f"Could not read the apps_catalog list ... : {e}", raw_msg=True) def _actual_apps_catalog_api_url(base_url): - return "{base_url}/v{version}/apps.json".format( - base_url=base_url, version=APPS_CATALOG_API_VERSION - ) + return f"{base_url}/v{APPS_CATALOG_API_VERSION}/apps.json" def _update_apps_catalog(): @@ -172,16 +168,11 @@ def _update_apps_catalog(): apps_catalog_content["from_api_version"] = APPS_CATALOG_API_VERSION # Save the apps_catalog data in the cache - cache_file = "{cache_folder}/{list}.json".format( - cache_folder=APPS_CATALOG_CACHE, list=apps_catalog_id - ) + cache_file = f"{APPS_CATALOG_CACHE}/{apps_catalog_id}.json" try: write_to_json(cache_file, apps_catalog_content) except Exception as e: - raise YunohostError( - "Unable to write cache data for %s apps_catalog : %s" - % (apps_catalog_id, str(e)) - ) + raise YunohostError(f"Unable to write cache data for {apps_catalog_id} apps_catalog : {e}", raw_msg=True) logger.success(m18n.n("apps_catalog_update_success")) @@ -197,9 +188,7 @@ def _load_apps_catalog(): for apps_catalog_id in [L["id"] for L in _read_apps_catalog_list()]: # 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 - ) + cache_file = f"{APPS_CATALOG_CACHE}/{apps_catalog_id}.json" try: apps_catalog_content = ( @@ -230,10 +219,8 @@ def _load_apps_catalog(): # (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 merged_catalog["apps"]: - logger.warning( - "Duplicate app %s found between apps catalog %s and %s" - % (app, apps_catalog_id, merged_catalog["apps"][app]["repository"]) - ) + other_catalog = merged_catalog["apps"][app]["repository"] + logger.warning(f"Duplicate app {app} found between apps catalog {apps_catalog_id} and {other_catalog}") continue info["repository"] = apps_catalog_id diff --git a/src/backup.py b/src/backup.py index 3dc2a31f5..57e667d8d 100644 --- a/src/backup.py +++ b/src/backup.py @@ -72,7 +72,7 @@ from yunohost.utils.filesystem import free_space_in_directory from yunohost.settings import settings_get BACKUP_PATH = "/home/yunohost.backup" -ARCHIVES_PATH = "%s/archives" % BACKUP_PATH +ARCHIVES_PATH = f"{BACKUP_PATH}/archives" APP_MARGIN_SPACE_SIZE = 100 # In MB CONF_MARGIN_SPACE_SIZE = 10 # IN MB POSTINSTALL_ESTIMATE_SPACE_SIZE = 5 # In MB @@ -402,7 +402,7 @@ class BackupManager: # backup and restore scripts for app in target_list: - app_script_folder = "/etc/yunohost/apps/%s/scripts" % app + app_script_folder = f"/etc/yunohost/apps/{app}/scripts" backup_script_path = os.path.join(app_script_folder, "backup") restore_script_path = os.path.join(app_script_folder, "restore") @@ -555,7 +555,7 @@ class BackupManager: self._compute_backup_size() # Create backup info file - with open("%s/info.json" % self.work_dir, "w") as f: + with open(f"{self.work_dir}/info.json", "w") as f: f.write(json.dumps(self.info)) def _get_env_var(self, app=None): @@ -732,7 +732,7 @@ class BackupManager: 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()} - write_to_yaml("%s/permissions.yml" % settings_dir, this_app_permissions) + write_to_yaml(f"{settings_dir}/permissions.yml", this_app_permissions) except Exception as e: logger.debug(e) @@ -921,7 +921,7 @@ class RestoreManager: if not os.path.isfile("/etc/yunohost/installed"): # Retrieve the domain from the backup try: - with open("%s/conf/ynh/current_host" % self.work_dir, "r") as f: + with open(f"{self.work_dir}/conf/ynh/current_host", "r") as f: domain = f.readline().rstrip() except IOError: logger.debug( @@ -1004,7 +1004,7 @@ class RestoreManager: continue hook_paths = self.info["system"][system_part]["paths"] - hook_paths = ["hooks/restore/%s" % os.path.basename(p) for p in hook_paths] + hook_paths = [f"hooks/restore/{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 @@ -1071,7 +1071,7 @@ class RestoreManager: ret = subprocess.call(["umount", self.work_dir]) if ret == 0: subprocess.call(["rmdir", self.work_dir]) - logger.debug("Unmount dir: {}".format(self.work_dir)) + logger.debug(f"Unmount dir: {self.work_dir}") else: raise YunohostError("restore_removing_tmp_dir_failed") elif os.path.isdir(self.work_dir): @@ -1080,7 +1080,7 @@ class RestoreManager: ) ret = subprocess.call(["rm", "-Rf", self.work_dir]) if ret == 0: - logger.debug("Delete dir: {}".format(self.work_dir)) + logger.debug(f"Delete dir: {self.work_dir}") else: raise YunohostError("restore_removing_tmp_dir_failed") @@ -1182,7 +1182,7 @@ class RestoreManager: self._restore_apps() except Exception as e: raise YunohostError( - "The following critical error happened during restoration: %s" % e + f"The following critical error happened during restoration: {e}" ) finally: self.clean() @@ -1429,20 +1429,19 @@ class RestoreManager: restore_script = os.path.join(tmp_workdir_for_app, "restore") # Restore permissions - if not os.path.isfile("%s/permissions.yml" % app_settings_new_path): + if not os.path.isfile(f"{app_settings_new_path}/permissions.yml"): raise YunohostError( "Didnt find a permssions.yml for the app !?", raw_msg=True ) - permissions = read_yaml("%s/permissions.yml" % app_settings_new_path) + permissions = read_yaml(f"{app_settings_new_path}/permissions.yml") existing_groups = user_group_list()["groups"] 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) + f"'allowed' key corresponding to allowed groups for permission {permission_name} not found when restoring app {app_instance_name} … You might have to reconfigure permissions yourself." ) should_be_allowed = ["all_users"] else: @@ -1467,7 +1466,7 @@ class RestoreManager: permission_sync_to_user() - os.remove("%s/permissions.yml" % app_settings_new_path) + os.remove(f"{app_settings_new_path}/permissions.yml") _tools_migrations_run_before_app_restore( backup_version=self.info["from_yunohost_version"], @@ -1816,8 +1815,7 @@ class BackupMethod: # where everything is mapped to /dev/mapper/some-stuff # yet there are different devices behind it or idk ... logger.warning( - "Could not link %s to %s (%s) ... falling back to regular copy." - % (src, dest, str(e)) + f"Could not link {src} to {dest} ({e}) ... falling back to regular copy." ) else: # Success, go to next file to organize @@ -2383,7 +2381,7 @@ def backup_list(with_info=False, human_readable=False): """ # Get local archives sorted according to last modification time # (we do a realpath() to resolve symlinks) - archives = glob("%s/*.tar.gz" % ARCHIVES_PATH) + glob("%s/*.tar" % ARCHIVES_PATH) + archives = glob(f"{ARCHIVES_PATH}/*.tar.gz") + glob(f"{ARCHIVES_PATH}/*.tar") archives = {os.path.realpath(archive) for archive in archives} archives = sorted(archives, key=lambda x: os.path.getctime(x)) # Extract only filename without the extension @@ -2405,10 +2403,9 @@ def backup_list(with_info=False, human_readable=False): logger.warning(str(e)) except Exception: import traceback - + trace_ = "\n" + traceback.format_exc() logger.warning( - "Could not check infos for archive %s: %s" - % (archive, "\n" + traceback.format_exc()) + f"Could not check infos for archive {archive}: {trace_}" ) archives = d diff --git a/src/certificate.py b/src/certificate.py index adf707000..ca3329539 100644 --- a/src/certificate.py +++ b/src/certificate.py @@ -228,10 +228,7 @@ def _certificate_install_selfsigned(domain_list, force=False): ) operation_logger.success() else: - msg = ( - "Installation of self-signed certificate installation for %s failed !" - % (domain) - ) + msg = f"Installation of self-signed certificate installation for {domain} failed !" logger.error(msg) operation_logger.error(msg) @@ -299,8 +296,7 @@ def _certificate_install_letsencrypt( 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 + f"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 {domain}." ) else: logger.success(m18n.n("certmanager_cert_install_success", domain=domain)) @@ -417,11 +413,10 @@ def certificate_renew( stack = StringIO() traceback.print_exc(file=stack) - msg = "Certificate renewing for %s failed!" % (domain) + msg = f"Certificate renewing for {domain} failed!" 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 + f"\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 {domain}." ) logger.error(msg) operation_logger.error(msg) @@ -442,9 +437,9 @@ def certificate_renew( def _email_renewing_failed(domain, exception_message, stack=""): - from_ = "certmanager@%s (Certificate Manager)" % domain + from_ = f"certmanager@{domain} (Certificate Manager)" to_ = "root" - subject_ = "Certificate renewing attempt for %s failed!" % domain + subject_ = f"Certificate renewing attempt for {domain} failed!" logs = _tail(50, "/var/log/yunohost/yunohost-cli.log") message = f"""\ @@ -476,7 +471,7 @@ investigate : def _check_acme_challenge_configuration(domain): - domain_conf = "/etc/nginx/conf.d/%s.conf" % domain + domain_conf = f"/etc/nginx/conf.d/{domain}.conf" return "include /etc/nginx/conf.d/acme-challenge.conf.inc" in read_file(domain_conf) diff --git a/src/diagnosis.py b/src/diagnosis.py index 2b0cb7d1e..2486887b9 100644 --- a/src/diagnosis.py +++ b/src/diagnosis.py @@ -188,7 +188,7 @@ def diagnosis_run( # Call the hook ... diagnosed_categories = [] for category in categories: - logger.debug("Running diagnosis for %s ..." % category) + logger.debug(f"Running diagnosis for {category} ...") diagnoser = _load_diagnoser(category) @@ -282,7 +282,7 @@ def _diagnosis_ignore(add_filter=None, remove_filter=None, list=False): ) category = filter_[0] if category not in all_categories_names: - raise YunohostValidationError("%s is not a diagnosis category" % category) + raise YunohostValidationError(f"{category} is not a diagnosis category") if any("=" not in criteria for criteria in filter_[1:]): raise YunohostValidationError( "Criterias should be of the form key=value (e.g. domain=yolo.test)" @@ -423,7 +423,7 @@ class Diagnoser: not force and self.cached_time_ago() < self.cache_duration ): - logger.debug("Cache still valid : %s" % self.cache_file) + logger.debug(f"Cache still valid : {self.cache_file}") logger.info( m18n.n("diagnosis_cache_still_valid", category=self.description) ) @@ -457,7 +457,7 @@ class Diagnoser: new_report = {"id": self.id_, "cached_for": self.cache_duration, "items": items} - logger.debug("Updating cache %s" % self.cache_file) + logger.debug(f"Updating cache {self.cache_file}") self.write_cache(new_report) Diagnoser.i18n(new_report) add_ignore_flag_to_issues(new_report) @@ -530,7 +530,7 @@ class Diagnoser: @staticmethod def cache_file(id_): - return os.path.join(DIAGNOSIS_CACHE, "%s.json" % id_) + return os.path.join(DIAGNOSIS_CACHE, f"{id_}.json") @staticmethod def get_cached_report(id_, item=None, warn_if_no_cache=True): @@ -633,7 +633,7 @@ class Diagnoser: elif ipversion == 6: socket.getaddrinfo = getaddrinfo_ipv6_only - url = "https://{}/{}".format(DIAGNOSIS_SERVER, uri) + url = f"https://{DIAGNOSIS_SERVER}/{uri}" try: r = requests.post(url, json=data, timeout=timeout) finally: @@ -641,18 +641,16 @@ class Diagnoser: 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.
URL: %s
Status code: %s" - % (url, r.status_code) + f"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: {url}
Status code: {r.status_code}" ) if r.status_code == 400: - raise Exception("Diagnosis request was refused: %s" % r.content) + raise Exception(f"Diagnosis request was refused: {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) + f"Failed to parse json from diagnosis server response.\nError: {e}\nOriginal content: {r.content}" ) return r @@ -681,7 +679,7 @@ def _load_diagnoser(diagnoser_name): # this is python builtin method to import a module using a name, we # use that to import the migration as a python object so we'll be # able to run it in the next loop - module = import_module("yunohost.diagnosers.{}".format(module_id)) + module = import_module(f"yunohost.diagnosers.{module_id}") return module.MyDiagnoser() except Exception as e: import traceback @@ -695,9 +693,9 @@ def _email_diagnosis_issues(): from yunohost.domain import _get_maindomain maindomain = _get_maindomain() - from_ = "diagnosis@{} (Automatic diagnosis on {})".format(maindomain, maindomain) + from_ = f"diagnosis@{maindomain} (Automatic diagnosis on {maindomain})" to_ = "root" - subject_ = "Issues found by automatic diagnosis on %s" % maindomain + subject_ = f"Issues found by automatic diagnosis on {maindomain}" 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." @@ -707,23 +705,17 @@ def _email_diagnosis_issues(): content = _dump_human_readable_reports(issues) - message = """\ -From: {} -To: {} -Subject: {} + message = f"""\ +From: {from_} +To: {to_} +Subject: {subject_} -{} +{disclaimer} --- -{} -""".format( - from_, - to_, - subject_, - disclaimer, - content, - ) +{content} +""" import smtplib diff --git a/src/dns.py b/src/dns.py index 362f02826..8991aa742 100644 --- a/src/dns.py +++ b/src/dns.py @@ -338,7 +338,7 @@ def _build_dns_conf(base_domain, include_empty_AAAA_if_no_ipv6=False): def _get_DKIM(domain): - DKIM_file = "/etc/dkim/{domain}.mail.txt".format(domain=domain) + DKIM_file = f"/etc/dkim/{domain}.mail.txt" if not os.path.isfile(DKIM_file): return (None, None) diff --git a/src/domain.py b/src/domain.py index 57c990762..6fd1724b4 100644 --- a/src/domain.py +++ b/src/domain.py @@ -196,7 +196,7 @@ def domain_add(operation_logger, domain, dyndns=False): } try: - ldap.add("virtualdomain=%s,ou=domains" % domain, attr_dict) + ldap.add(f"virtualdomain={domain},ou=domains", attr_dict) except Exception as e: raise YunohostError("domain_creation_failed", domain=domain, error=e) finally: @@ -215,7 +215,7 @@ def domain_add(operation_logger, domain, dyndns=False): # 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]) + _force_clear_hashes([f"/etc/nginx/conf.d/{domain}.conf"]) regen_conf( names=["nginx", "metronome", "dnsmasq", "postfix", "rspamd", "mdns"] ) @@ -282,8 +282,7 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False): apps_on_that_domain.append( ( app, - ' - %s "%s" on https://%s%s' - % (app, label, domain, settings["path"]) + f" - {app} \"{label}\" on https://{domain}{settings['path']}" if "path" in settings else app, ) @@ -342,14 +341,14 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False): # 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]) + _force_clear_hashes([f"/etc/nginx/conf.d/{domain}.conf"]) # 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): + if os.path.exists(f"/etc/nginx/conf.d/{domain}.conf"): _process_regen_conf( - "/etc/nginx/conf.d/%s.conf" % domain, new_conf=None, save=True + f"/etc/nginx/conf.d/{domain}.conf", new_conf=None, save=True ) regen_conf(names=["nginx", "metronome", "dnsmasq", "postfix", "rspamd", "mdns"]) @@ -388,7 +387,7 @@ def domain_main_domain(operation_logger, new_main_domain=None): domain_list_cache = {} _set_hostname(new_main_domain) except Exception as e: - logger.warning("%s" % e, exc_info=1) + logger.warning(str(e), exc_info=1) raise YunohostError("main_domain_change_failed") # Generate SSOwat configuration file diff --git a/src/hook.py b/src/hook.py index 8b18df772..70d3b281b 100644 --- a/src/hook.py +++ b/src/hook.py @@ -95,7 +95,7 @@ def hook_info(action, name): priorities = set() # Search in custom folder first - for h in iglob("{:s}{:s}/*-{:s}".format(CUSTOM_HOOK_FOLDER, action, name)): + for h in iglob(f"{CUSTOM_HOOK_FOLDER}{action}/*-{name}"): priority, _ = _extract_filename_parts(os.path.basename(h)) priorities.add(priority) hooks.append( @@ -105,7 +105,7 @@ def hook_info(action, name): } ) # Append non-overwritten system hooks - for h in iglob("{:s}{:s}/*-{:s}".format(HOOK_FOLDER, action, name)): + for h in iglob(f"{HOOK_FOLDER}{action}/*-{name}"): priority, _ = _extract_filename_parts(os.path.basename(h)) if priority not in priorities: hooks.append( @@ -431,8 +431,7 @@ def _hook_exec_bash(path, args, chdir, env, user, return_format, loggers): # use xtrace on fd 7 which is redirected to stdout env["BASH_XTRACEFD"] = "7" - cmd = '/bin/bash -x "{script}" {args} 7>&1' - command.append(cmd.format(script=cmd_script, args=cmd_args)) + command.append(f'/bin/bash -x "{cmd_script}" {cmd_args} 7>&1') logger.debug("Executing command '%s'" % command) diff --git a/src/permission.py b/src/permission.py index 493f17278..995cd34bb 100644 --- a/src/permission.py +++ b/src/permission.py @@ -133,8 +133,7 @@ def user_permission_list( main_perm_name = name.split(".")[0] + ".main" if main_perm_name not in permissions: logger.debug( - "Uhoh, unknown permission %s ? (Maybe we're in the process or deleting the perm for this app...)" - % main_perm_name + f"Uhoh, unknown permission {main_perm_name} ? (Maybe we're in the process or deleting the perm for this app...)" ) continue main_perm_label = permissions[main_perm_name]["label"] @@ -452,7 +451,7 @@ def permission_create( operation_logger.start() try: - ldap.add("cn=%s,ou=permission" % permission, attr_dict) + ldap.add(f"cn={permission},ou=permission", attr_dict) except Exception as e: raise YunohostError( "permission_creation_failed", permission=permission, error=e @@ -585,7 +584,7 @@ def permission_url( try: ldap.update( - "cn=%s,ou=permission" % permission, + f"cn={permission},ou=permission", { "URL": [url] if url is not None else [], "additionalUrls": new_additional_urls, @@ -633,7 +632,7 @@ def permission_delete(operation_logger, permission, force=False, sync_perm=True) operation_logger.start() try: - ldap.remove("cn=%s,ou=permission" % permission) + ldap.remove(f"cn={permission},ou=permission") except Exception as e: raise YunohostError( "permission_deletion_failed", permission=permission, error=e @@ -679,7 +678,7 @@ def permission_sync_to_user(): new_inherited_perms = { "inheritPermission": [ - "uid=%s,ou=users,dc=yunohost,dc=org" % u + f"uid={u},ou=users,dc=yunohost,dc=org" for u in should_be_allowed_users ], "memberUid": should_be_allowed_users, @@ -687,7 +686,7 @@ def permission_sync_to_user(): # Commit the change with the new inherited stuff try: - ldap.update("cn=%s,ou=permission" % permission_name, new_inherited_perms) + ldap.update(f"cn={permission_name},ou=permission", new_inherited_perms) except Exception as e: raise YunohostError( "permission_update_failed", permission=permission_name, error=e @@ -765,7 +764,7 @@ def _update_ldap_group_permission( update["showTile"] = [str(show_tile).upper()] try: - ldap.update("cn=%s,ou=permission" % permission, update) + ldap.update(f"cn={permission},ou=permission", update) except Exception as e: raise YunohostError("permission_update_failed", permission=permission, error=e) diff --git a/src/regenconf.py b/src/regenconf.py index 9dcec5ae5..0f855878d 100644 --- a/src/regenconf.py +++ b/src/regenconf.py @@ -449,7 +449,7 @@ def _save_regenconf_infos(infos): yaml.safe_dump(infos, f, default_flow_style=False) except Exception as e: logger.warning( - "Error while saving regenconf infos, exception: %s", e, exc_info=1 + f"Error while saving regenconf infos, exception: {e}", exc_info=1 ) raise @@ -506,7 +506,7 @@ def _calculate_hash(path): except IOError as e: logger.warning( - "Error while calculating file '%s' hash: %s", path, e, exc_info=1 + f"Error while calculating file '{path}' hash: {e}", exc_info=1 ) return None @@ -559,11 +559,11 @@ def _get_conf_hashes(category): categories = _get_regenconf_infos() if category not in categories: - logger.debug("category %s is not in categories.yml yet.", category) + logger.debug(f"category {category} is not in categories.yml yet.") return {} elif categories[category] is None or "conffiles" not in categories[category]: - logger.debug("No configuration files for category %s.", category) + logger.debug(f"No configuration files for category {category}.") return {} else: @@ -572,7 +572,7 @@ def _get_conf_hashes(category): def _update_conf_hashes(category, hashes): """Update the registered conf hashes for a category""" - logger.debug("updating conf hashes for '%s' with: %s", category, hashes) + logger.debug(f"updating conf hashes for '{category}' with: {hashes}") categories = _get_regenconf_infos() category_conf = categories.get(category, {}) @@ -603,8 +603,7 @@ def _force_clear_hashes(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) + f"force-clearing old conf hash for {path} in category {category}" ) del categories[category]["conffiles"][path] @@ -647,9 +646,7 @@ def _process_regen_conf(system_conf, new_conf=None, save=True): logger.debug(m18n.n("regenconf_file_updated", conf=system_conf)) except Exception as e: logger.warning( - "Exception while trying to regenerate conf '%s': %s", - system_conf, - e, + f"Exception while trying to regenerate conf '{system_conf}': {e}", exc_info=1, ) if not new_conf and os.path.exists(system_conf): diff --git a/src/service.py b/src/service.py index 0ac03f0c0..4da5d5546 100644 --- a/src/service.py +++ b/src/service.py @@ -407,8 +407,7 @@ def _get_and_format_service_status(service, infos): 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 + f"Failed to get status information via dbus for service {systemd_service}, systemctl didn't recognize this service ('NoSuchUnit')." ) return { "status": "unknown", @@ -424,7 +423,7 @@ 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 + translation_key = f"service_description_{service}" if m18n.key_exists(translation_key): description = m18n.n(translation_key) else: @@ -445,7 +444,7 @@ def _get_and_format_service_status(service, infos): "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 + f"/etc/systemd/system/multi-user.target.wants/{service}.service" ): output["start_on_boot"] = "enabled" @@ -585,8 +584,7 @@ def _run_service_command(action, service): ] if action not in possible_actions: raise ValueError( - "Unknown action '%s', available actions are: %s" - % (action, ", ".join(possible_actions)) + f"Unknown action '{action}', available actions are: {', '.join(possible_actions)}" ) cmd = f"systemctl {action} {service}" @@ -604,7 +602,7 @@ def _run_service_command(action, service): try: # Launch the command - logger.debug("Running '%s'" % cmd) + logger.debug(f"Running '{cmd}'") p = subprocess.Popen(cmd.split(), stderr=subprocess.STDOUT) # If this command needs a lock (because the service uses yunohost # commands inside), find the PID and add a lock for it @@ -651,7 +649,7 @@ def _give_lock(action, service, p): if son_PID != 0: # Append the PID to the lock file logger.debug(f"Giving a lock to PID {son_PID} for service {service} !") - append_to_file(MOULINETTE_LOCK, "\n%s" % str(son_PID)) + append_to_file(MOULINETTE_LOCK, f"\n{son_PID}") return son_PID @@ -815,7 +813,7 @@ def _find_previous_log_file(file): i = int(i[0]) + 1 if len(i) > 0 else 1 previous_file = file if i == 1 else splitext[0] - previous_file = previous_file + ".%d" % (i) + previous_file = previous_file + f".{i}" if os.path.exists(previous_file): return previous_file @@ -835,8 +833,7 @@ def _get_journalctl_logs(service, number="all"): ) except Exception: import traceback - + trace_ = traceback.format_exc() return ( - "error while get services logs from journalctl:\n%s" - % traceback.format_exc() + f"error while get services logs from journalctl:\n{trace_}" ) diff --git a/src/settings.py b/src/settings.py index 0e08a2640..498e6d5cc 100644 --- a/src/settings.py +++ b/src/settings.py @@ -285,7 +285,7 @@ def settings_reset_all(): def _get_setting_description(key): - return m18n.n("global_settings_setting_%s" % key.replace(".", "_")) + return m18n.n(f"global_settings_setting_{key}".replace(".", "_")) def _get_settings(): @@ -315,7 +315,7 @@ def _get_settings(): try: unknown_settings = json.load(open(unknown_settings_path, "r")) except Exception as e: - logger.warning("Error while loading unknown settings %s" % e) + logger.warning(f"Error while loading unknown settings {e}") try: with open(SETTINGS_PATH) as settings_fd: @@ -342,7 +342,7 @@ def _get_settings(): _save_settings(settings) except Exception as e: logger.warning( - "Failed to save unknown settings (because %s), aborting." % e + f"Failed to save unknown settings (because {e}), aborting." ) return settings @@ -374,11 +374,10 @@ post_change_hooks = {} def post_change_hook(setting_name): def decorator(func): assert setting_name in DEFAULTS.keys(), ( - "The setting %s does not exists" % setting_name + f"The setting {setting_name} does not exists" ) assert setting_name not in post_change_hooks, ( - "You can only register one post change hook per setting (in particular for %s)" - % setting_name + f"You can only register one post change hook per setting (in particular for {setting_name})" ) post_change_hooks[setting_name] = func return func @@ -388,7 +387,7 @@ def post_change_hook(setting_name): def trigger_post_change_hook(setting_name, old_value, new_value): if setting_name not in post_change_hooks: - logger.debug("Nothing to do after changing setting %s" % setting_name) + logger.debug(f"Nothing to do after changing setting {setting_name}") return f = post_change_hooks[setting_name] diff --git a/src/ssh.py b/src/ssh.py index 98fa8fb3c..b89dc6c8e 100644 --- a/src/ssh.py +++ b/src/ssh.py @@ -99,7 +99,7 @@ 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), + f"this key doesn't exists ({authorized_keys_file} dosesn't exists)", raw_msg=True, ) @@ -107,7 +107,7 @@ def user_ssh_remove_key(username, key): if key not in authorized_keys_content: raise YunohostValidationError( - "Key '{}' is not present in authorized_keys".format(key), raw_msg=True + f"Key '{key}' is not present in authorized_keys", raw_msg=True ) # don't delete the previous comment because we can't verify if it's legit diff --git a/src/tools.py b/src/tools.py index 1a80d020f..7aa0fa42f 100644 --- a/src/tools.py +++ b/src/tools.py @@ -99,7 +99,7 @@ def tools_adminpw(new_password, check_strength=True): {"userPassword": [new_hash]}, ) except Exception as e: - logger.error("unable to change admin password : %s" % e) + logger.error(f"unable to change admin password : {e}") raise YunohostError("admin_password_change_failed") else: # Write as root password @@ -146,7 +146,7 @@ def _set_hostname(hostname, pretty_hostname=None): """ if not pretty_hostname: - pretty_hostname = "(YunoHost/%s)" % hostname + pretty_hostname = f"(YunoHost/{hostname})" # First clear nsswitch cache for hosts to make sure hostname is resolved... subprocess.call(["nscd", "-i", "hosts"]) @@ -332,7 +332,7 @@ def tools_update(target=None): if target not in ["system", "apps", "all"]: raise YunohostError( - "Unknown target %s, should be 'system', 'apps' or 'all'" % target, + f"Unknown target {target}, should be 'system', 'apps' or 'all'", raw_msg=True, ) @@ -479,7 +479,7 @@ def tools_upgrade( try: app_upgrade(app=upgradable_apps) except Exception as e: - logger.warning("unable to upgrade apps: %s" % str(e)) + logger.warning(f"unable to upgrade apps: {e}") logger.error(m18n.n("app_upgrade_some_app_failed")) return @@ -885,7 +885,7 @@ def _get_migration_by_name(migration_name): try: from . import migrations except ImportError: - raise AssertionError("Unable to find migration with name %s" % migration_name) + raise AssertionError(f"Unable to find migration with name {migration_name}") migrations_path = migrations.__path__[0] migrations_found = [ @@ -895,7 +895,7 @@ def _get_migration_by_name(migration_name): ] assert len(migrations_found) == 1, ( - "Unable to find migration with name %s" % migration_name + f"Unable to find migration with name {migration_name}" ) return _load_migration(migrations_found[0]) @@ -1019,7 +1019,7 @@ class Migration: @property def description(self): - return m18n.n("migration_description_%s" % self.id) + return m18n.n(f"migration_description_{self.id}") def ldap_migration(self, run): def func(self): diff --git a/src/user.py b/src/user.py index 6f99321bb..c03023387 100644 --- a/src/user.py +++ b/src/user.py @@ -163,7 +163,7 @@ def user_create( maindomain = _get_maindomain() domain = Moulinette.prompt( - m18n.n("ask_user_domain") + " (default: %s)" % maindomain + m18n.n("ask_user_domain") + f" (default: {maindomain})" ) if not domain: domain = maindomain @@ -237,7 +237,7 @@ def user_create( attr_dict["mail"] = [attr_dict["mail"]] + aliases try: - ldap.add("uid=%s,ou=users" % username, attr_dict) + ldap.add(f"uid={username},ou=users", attr_dict) except Exception as e: raise YunohostError("user_creation_failed", user=username, error=e) @@ -255,10 +255,10 @@ def user_create( try: subprocess.check_call( - ["setfacl", "-m", "g:all_users:---", "/home/%s" % username] + ["setfacl", "-m", "g:all_users:---", f"/home/{username}"] ) except subprocess.CalledProcessError: - logger.warning("Failed to protect /home/%s" % username, exc_info=1) + logger.warning(f"Failed to protect /home/{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) @@ -318,7 +318,7 @@ def user_delete(operation_logger, username, purge=False, from_import=False): ldap = _get_ldap_interface() try: - ldap.remove("uid=%s,ou=users" % username) + ldap.remove(f"uid={username},ou=users") except Exception as e: raise YunohostError("user_deletion_failed", user=username, error=e) @@ -506,7 +506,7 @@ def user_update( operation_logger.start() try: - ldap.update("uid=%s,ou=users" % username, new_attr_dict) + ldap.update(f"uid={username},ou=users", new_attr_dict) except Exception as e: raise YunohostError("user_update_failed", user=username, error=e) @@ -577,11 +577,11 @@ def user_info(username): logger.warning(m18n.n("mailbox_disabled", user=username)) else: try: - cmd = "doveadm -f flow quota get -u %s" % user["uid"][0] - cmd_result = check_output(cmd) + uid_ = user["uid"][0] + cmd_result = check_output(f"doveadm -f flow quota get -u {uid_}") except Exception as e: cmd_result = "" - logger.warning("Failed to fetch quota info ... : %s " % str(e)) + logger.warning(f"Failed to fetch quota info ... : {e}") # Exemple of return value for cmd: # """Quota name=User quota Type=STORAGE Value=0 Limit=- %=0 @@ -707,8 +707,7 @@ def user_import(operation_logger, csvfile, update=False, delete=False): 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) + f"username '{user['username']}': unknown groups {', '.join(unknown_groups)}" ) # Validate that domains exist @@ -729,8 +728,7 @@ def user_import(operation_logger, csvfile, update=False, delete=False): if unknown_domains: format_errors.append( - f"username '{user['username']}': unknown domains %s" - % ", ".join(unknown_domains) + f"username '{user['username']}': unknown domains {', '.join(unknown_domains)}" ) if format_errors: @@ -1002,7 +1000,7 @@ def user_group_create( 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 + f"sed --in-place '/^{groupname}:/d' /etc/group", shell=True ) else: raise YunohostValidationError( @@ -1032,7 +1030,7 @@ def user_group_create( operation_logger.start() try: - ldap.add("cn=%s,ou=groups" % groupname, attr_dict) + ldap.add(f"cn={groupname},ou=groups", attr_dict) except Exception as e: raise YunohostError("group_creation_failed", group=groupname, error=e) @@ -1075,7 +1073,7 @@ def user_group_delete(operation_logger, groupname, force=False, sync_perm=True): operation_logger.start() ldap = _get_ldap_interface() try: - ldap.remove("cn=%s,ou=groups" % groupname) + ldap.remove(f"cn={groupname},ou=groups") except Exception as e: raise YunohostError("group_deletion_failed", group=groupname, error=e) @@ -1171,7 +1169,7 @@ def user_group_update( ldap = _get_ldap_interface() try: ldap.update( - "cn=%s,ou=groups" % groupname, + f"cn={groupname},ou=groups", {"member": set(new_group_dns), "memberUid": set(new_group)}, ) except Exception as e: diff --git a/src/utils/legacy.py b/src/utils/legacy.py index 4186fa336..306fcc87f 100644 --- a/src/utils/legacy.py +++ b/src/utils/legacy.py @@ -117,7 +117,7 @@ def _patch_legacy_php_versions(app_folder): c = ( "sed -i " + "".join( - "-e 's@{pattern}@{replace}@g' ".format(pattern=p, replace=r) + f"-e 's@{p}@{r}@g' " for p, r in LEGACY_PHP_VERSION_REPLACEMENTS ) + "%s" % filename From d196f0be34d913c39f214c872b5e9a1d38ed6675 Mon Sep 17 00:00:00 2001 From: Boudewijn Date: Thu, 13 Jan 2022 21:06:05 +0000 Subject: [PATCH 1079/1155] Translated using Weblate (Dutch) Currently translated at 13.1% (95 of 720 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/nl/ --- locales/nl.json | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/locales/nl.json b/locales/nl.json index 038d18283..d9f4e7a93 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -40,7 +40,7 @@ "extracting": "Uitpakken...", "installation_complete": "Installatie voltooid", "mail_alias_remove_failed": "Kan mail-alias '{mail}' niet verwijderen", - "pattern_email": "Moet een geldig emailadres bevatten (bv. abc@example.org)", + "pattern_email": "Moet een geldig e-mailadres bevatten, zonder '+' symbool er in (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} is al gesloten voor {ip_version} verbindingen", @@ -129,5 +129,17 @@ "additional_urls_already_removed": "Extra URL '{url}' is al verwijderd in de extra URL voor privilege '{permission}'", "app_label_deprecated": "Dit commando is vervallen. Gebruik alsjeblieft het nieuwe commando 'yunohost user permission update' om het label van de app te beheren.", "app_change_url_no_script": "De app '{app_name}' ondersteunt nog geen URL-aanpassingen. Misschien wel na een upgrade.", - "app_upgrade_some_app_failed": "Sommige apps konden niet worden bijgewerkt" + "app_upgrade_some_app_failed": "Sommige apps konden niet worden bijgewerkt", + "other_available_options": "... en {n} andere beschikbare opties die niet getoond worden", + "password_listed": "Dit wachtwoord is een van de meest gebruikte wachtwoorden ter wereld. Kies alstublieft iets wat minder voor de hand ligt.", + "password_too_simple_4": "Het wachtwoord moet minimaal 12 tekens lang zijn en moet cijfers, hoofdletters, kleine letters en speciale tekens bevatten", + "pattern_email_forward": "Het moet een geldig e-mailadres zijn, '+' symbool is toegestaan (ikzelf@mijndomein.nl bijvoorbeeld, of ikzelf+yunohost@mijndomein.nl)", + "password_too_simple_2": "Het wachtwoord moet minimaal 8 tekens lang zijn en moet cijfers, hoofdletters en kleine letters bevatten", + "operation_interrupted": "Werd de bewerking handmatig onderbroken?", + "packages_upgrade_failed": "Niet alle pakketten konden bijgewerkt worden", + "pattern_backup_archive_name": "Moet een geldige bestandsnaam zijn van maximaal 30 tekens; alleen alfanumerieke tekens en -_. zijn toegestaan", + "pattern_domain": "Moet een geldige domeinnaam zijn (mijneigendomein.nl, bijvoorbeeld)", + "pattern_firstname": "Het moet een geldige voornaam zijn", + "pattern_lastname": "Het moet een geldige achternaam zijn", + "password_too_simple_3": "Het wachtwoord moet minimaal 8 tekens lang zijn en moet cijfers, hoofdletters, kleine letters en speciale tekens bevatten" } From f322783d15200ea558d24b06a4999da3a7389bbc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 14 Jan 2022 01:34:47 +0100 Subject: [PATCH 1080/1155] Tmp remove bullseye migration for minor release --- .../0021_migrate_to_bullseye.py | 427 ------------------ 1 file changed, 427 deletions(-) delete mode 100644 src/yunohost/data_migrations/0021_migrate_to_bullseye.py diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py deleted file mode 100644 index e47087976..000000000 --- a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py +++ /dev/null @@ -1,427 +0,0 @@ -import glob -import os - -from moulinette import m18n -from yunohost.utils.error import YunohostError -from moulinette.utils.log import getActionLogger -from moulinette.utils.process import check_output, call_async_output -from moulinette.utils.filesystem import read_file, rm - -from yunohost.tools import ( - Migration, - tools_update, - tools_upgrade, - _apt_log_line_is_relevant, -) -from yunohost.app import unstable_apps -from yunohost.regenconf import manually_modified_files, _force_clear_hashes -from yunohost.utils.filesystem import free_space_in_directory -from yunohost.utils.packages import ( - get_ynh_package_version, - _list_upgradable_apt_packages, -) -from yunohost.service import _get_services, _save_services - -logger = getActionLogger("yunohost.migration") - -N_CURRENT_DEBIAN = 10 -N_CURRENT_YUNOHOST = 4 - -N_NEXT_DEBAN = 11 -N_NEXT_YUNOHOST = 11 - - -class MyMigration(Migration): - - "Upgrade the system to Debian Bullseye and Yunohost 11.x" - - mode = "manual" - - def run(self): - - self.check_assertions() - - logger.info(m18n.n("migration_0021_start")) - - # - # Add new apt .deb signing key - # - - new_apt_key = "https://forge.yunohost.org/yunohost_bullseye.asc" - check_output(f"wget -O- {new_apt_key} -q | apt-key add -qq -") - - # - # Patch sources.list - # - logger.info(m18n.n("migration_0021_patching_sources_list")) - self.patch_apt_sources_list() - - # Force add sury if it's not there yet - # This is to solve some weird issue with php-common breaking php7.3-common, - # hence breaking many php7.3-deps - # hence triggering some dependency conflict (or foobar-ynh-deps uninstall) - # Adding it there shouldnt be a big deal - Yunohost 11.x does add it - # through its regen conf anyway. - if not os.path.exists("/etc/apt/sources.list.d/extra_php_version.list"): - open("/etc/apt/sources.list.d/extra_php_version.list", "w").write( - "deb https://packages.sury.org/php/ bullseye main" - ) - os.system( - 'wget --timeout 900 --quiet "https://packages.sury.org/php/apt.gpg" --output-document=- | gpg --dearmor >"/etc/apt/trusted.gpg.d/extra_php_version.gpg"' - ) - - # - # Run apt update - # - - tools_update(target="system") - - # Tell libc6 it's okay to restart system stuff during the upgrade - os.system( - "echo 'libc6 libraries/restart-without-asking boolean true' | debconf-set-selections" - ) - - # Don't send an email to root about the postgresql migration. It should be handled automatically after. - os.system( - "echo 'postgresql-common postgresql-common/obsolete-major seen true' | debconf-set-selections" - ) - - # - # Patch yunohost conflicts - # - logger.info(m18n.n("migration_0021_patch_yunohost_conflicts")) - - self.patch_yunohost_conflicts() - - # - # Specific tweaking to get rid of custom my.cnf and use debian's default one - # (my.cnf is actually a symlink to mariadb.cnf) - # - - _force_clear_hashes(["/etc/mysql/my.cnf"]) - rm("/etc/mysql/mariadb.cnf", force=True) - rm("/etc/mysql/my.cnf", force=True) - ret = self.apt_install( - "mariadb-common --reinstall -o Dpkg::Options::='--force-confmiss'" - ) - if ret != 0: - # FIXME: i18n once this is stable? - raise YunohostError("Failed to reinstall mariadb-common ?", raw_msg=True) - - # - # /usr/share/yunohost/yunohost-config/ssl/yunoCA -> /usr/share/yunohost/ssl - # - if os.path.exists("/usr/share/yunohost/yunohost-config/ssl/yunoCA"): - os.system( - "mv /usr/share/yunohost/yunohost-config/ssl/yunoCA /usr/share/yunohost/ssl" - ) - rm("/usr/share/yunohost/yunohost-config", recursive=True, force=True) - - # - # /home/yunohost.conf -> /var/cache/yunohost/regenconf - # - if os.path.exists("/home/yunohost.conf"): - os.system("mv /home/yunohost.conf /var/cache/yunohost/regenconf") - rm("/home/yunohost.conf", recursive=True, force=True) - - # Remove legacy postgresql service record added by helpers, - # will now be dynamically handled by the core in bullseye - services = _get_services() - if "postgresql" in services: - del services["postgresql"] - _save_services(services) - - # - # Main upgrade - # - logger.info(m18n.n("migration_0021_main_upgrade")) - - apps_packages = self.get_apps_equivs_packages() - self.hold(apps_packages) - tools_upgrade(target="system", allow_yunohost_upgrade=False) - - if self.debian_major_version() == N_CURRENT_DEBIAN: - raise YunohostError("migration_0021_still_on_buster_after_main_upgrade") - - # Force explicit install of php7.4-fpm and other old 'default' dependencies - # that are now only in Recommends - # - # Also, we need to install php7.4 equivalents of other php7.3 dependencies. - # For example, Nextcloud may depend on php7.3-zip, and after the php pool migration - # to autoupgrade Nextcloud to 7.4, it will need the php7.4-zip to work. - # The following list is based on an ad-hoc analysis of php deps found in the - # app ecosystem, with a known equivalent on php7.4. - # - # This is kinda a dirty hack as it doesnt properly update the *-ynh-deps virtual packages - # with the proper list of dependencies, and the dependencies install this way - # will get flagged as 'manually installed'. - # - # We'll probably want to do something during the Bullseye->Bookworm migration to re-flag - # these as 'auto' so they get autoremoved if not needed anymore. - # Also hopefully by then we'll have manifestv2 (maybe) and will be able to use - # the apt resource mecanism to regenerate the *-ynh-deps virtual packages ;) - - php73packages_suffixes = [ - "apcu", - "bcmath", - "bz2", - "dom", - "gmp", - "igbinary", - "imagick", - "imap", - "mbstring", - "memcached", - "mysqli", - "mysqlnd", - "pgsql", - "redis", - "simplexml", - "soap", - "sqlite3", - "ssh2", - "tidy", - "xml", - "xmlrpc", - "xsl", - "zip", - ] - - cmd = ( - "apt show '*-ynh-deps' 2>/dev/null" - " | grep Depends" - f" | grep -o -E \"php7.3-({'|'.join(php73packages_suffixes)})\"" - " | sort | uniq" - " | sed 's/php7.3/php7.4/g'" - " || true" - ) - - basephp74packages_to_install = [ - "php7.4-fpm", - "php7.4-common", - "php7.4-ldap", - "php7.4-intl", - "php7.4-mysql", - "php7.4-gd", - "php7.4-curl", - "php-php-gettext", - ] - - php74packages_to_install = basephp74packages_to_install + [ - f.strip() for f in check_output(cmd).split("\n") if f.strip() - ] - - ret = self.apt_install( - f"{' '.join(php74packages_to_install)} " - "$(dpkg --list | grep ynh-deps | awk '{print $2}') " - "-o Dpkg::Options::='--force-confmiss'" - ) - if ret != 0: - # FIXME: i18n once this is stable? - raise YunohostError( - "Failed to force the install of php dependencies ?", raw_msg=True - ) - - os.system(f"apt-mark auto {' '.join(basephp74packages_to_install)}") - - # Clean the mess - logger.info(m18n.n("migration_0021_cleaning_up")) - os.system("apt autoremove --assume-yes") - os.system("apt clean --assume-yes") - - # - # Yunohost upgrade - # - logger.info(m18n.n("migration_0021_yunohost_upgrade")) - - self.unhold(apps_packages) - - cmd = "LC_ALL=C" - cmd += " DEBIAN_FRONTEND=noninteractive" - cmd += " APT_LISTCHANGES_FRONTEND=none" - cmd += " apt dist-upgrade " - cmd += " --quiet -o=Dpkg::Use-Pty=0 --fix-broken --dry-run" - cmd += " | grep -q 'ynh-deps'" - - logger.info("Simulating upgrade...") - if os.system(cmd) == 0: - # FIXME: i18n once this is stable? - raise YunohostError( - "The upgrade cannot be completed, because some app dependencies would need to be removed?", - raw_msg=True, - ) - - tools_upgrade(target="system") - - def debian_major_version(self): - # The python module "platform" and lsb_release are not reliable because - # on some setup, they may still return Release=9 even after upgrading to - # buster ... (Apparently this is related to OVH overriding some stuff - # with /etc/lsb-release for instance -_-) - # Instead, we rely on /etc/os-release which should be the raw info from - # the distribution... - 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_ynh_package_version("yunohost")["version"].split(".")[0]) - - def check_assertions(self): - - # Be on buster (10.x) and yunohost 4.x - # NB : we do both check to cover situations where the upgrade crashed - # in the middle and debian version could be > 9.x but yunohost package - # would still be in 3.x... - if ( - not self.debian_major_version() == N_CURRENT_DEBIAN - and not self.yunohost_major_version() == N_CURRENT_YUNOHOST - ): - raise YunohostError("migration_0021_not_buster") - - # Have > 1 Go free space on /var/ ? - if free_space_in_directory("/var/") / (1024 ** 3) < 1.0: - raise YunohostError("migration_0021_not_enough_free_space") - - # Check system is up to date - # (but we don't if 'bullseye' is already in the sources.list ... - # which means maybe a previous upgrade crashed and we're re-running it) - if " bullseye " not in read_file("/etc/apt/sources.list"): - tools_update(target="system") - upgradable_system_packages = list(_list_upgradable_apt_packages()) - if upgradable_system_packages: - raise YunohostError("migration_0021_system_not_fully_up_to_date") - - @property - def disclaimer(self): - - # Avoid having a super long disclaimer + uncessary check if we ain't - # on buster / yunohost 4.x anymore - # NB : we do both check to cover situations where the upgrade crashed - # in the middle and debian version could be >= 10.x but yunohost package - # would still be in 4.x... - if ( - not self.debian_major_version() == N_CURRENT_DEBIAN - and not self.yunohost_major_version() == N_CURRENT_YUNOHOST - ): - return None - - # Get list of problematic apps ? I.e. not official or community+working - problematic_apps = unstable_apps() - problematic_apps = "".join(["\n - " + app for app in problematic_apps]) - - # Manually modified files ? (c.f. yunohost service regen-conf) - modified_files = manually_modified_files() - modified_files = "".join(["\n - " + f for f in modified_files]) - - message = m18n.n("migration_0021_general_warning") - - # FIXME: re-enable this message with updated topic link once we release the migration as stable - # message = ( - # "N.B.: This migration has been tested by the community over the last few months but has only been declared stable recently. If your server hosts critical services and if you are not too confident with debugging possible issues, we recommend you to wait a little bit more while we gather more feedback and polish things up. If on the other hand you are relatively confident with debugging small issues that may arise, you are encouraged to run this migration ;)! You can read about remaining known issues and feedback from the community here: https://forum.yunohost.org/t/12195\n\n" - # + message - # ) - - if problematic_apps: - message += "\n\n" + m18n.n( - "migration_0021_problematic_apps_warning", - problematic_apps=problematic_apps, - ) - - if modified_files: - message += "\n\n" + m18n.n( - "migration_0021_modified_files", manually_modified_files=modified_files - ) - - return message - - def patch_apt_sources_list(self): - - sources_list = glob.glob("/etc/apt/sources.list.d/*.list") - sources_list.append("/etc/apt/sources.list") - - # This : - # - replace single 'buster' occurence by 'bulleye' - # - comments lines containing "backports" - # - replace 'buster/updates' by 'bullseye/updates' (or same with -) - # Special note about the security suite: - # https://www.debian.org/releases/bullseye/amd64/release-notes/ch-information.en.html#security-archive - for f in sources_list: - command = ( - f"sed -i {f} " - "-e 's@ buster @ bullseye @g' " - "-e '/backports/ s@^#*@#@' " - "-e 's@ buster/updates @ bullseye-security @g' " - "-e 's@ buster-@ bullseye-@g' " - ) - os.system(command) - - def get_apps_equivs_packages(self): - - command = ( - "dpkg --get-selections" - " | grep -v deinstall" - " | awk '{print $1}'" - " | { grep 'ynh-deps$' || true; }" - ) - - output = check_output(command) - - return output.split("\n") if output else [] - - def hold(self, packages): - for package in packages: - os.system(f"apt-mark hold {package}") - - def unhold(self, packages): - for package in packages: - os.system(f"apt-mark unhold {package}") - - def apt_install(self, cmd): - def is_relevant(line): - return "Reading database ..." not in line.rstrip() - - callbacks = ( - lambda l: logger.info("+ " + l.rstrip() + "\r") - if _apt_log_line_is_relevant(l) - else logger.debug(l.rstrip() + "\r"), - lambda l: logger.warning(l.rstrip()) - if _apt_log_line_is_relevant(l) - else logger.debug(l.rstrip()), - ) - - cmd = ( - "LC_ALL=C DEBIAN_FRONTEND=noninteractive APT_LISTCHANGES_FRONTEND=none apt install --quiet -o=Dpkg::Use-Pty=0 --fix-broken --assume-yes " - + cmd - ) - - logger.debug("Running: %s" % cmd) - - return call_async_output(cmd, callbacks, shell=True) - - def patch_yunohost_conflicts(self): - # - # This is a super dirty hack to remove the conflicts from yunohost's debian/control file - # Those conflicts are there to prevent mistakenly upgrading critical packages - # such as dovecot, postfix, nginx, openssl, etc... usually related to mistakenly - # using backports etc. - # - # The hack consists in savagely removing the conflicts directly in /var/lib/dpkg/status - # - - # We only patch the conflict if we're on yunohost 4.x - if self.yunohost_major_version() != N_CURRENT_YUNOHOST: - return - - conflicts = check_output("dpkg-query -s yunohost | grep '^Conflicts:'").strip() - if conflicts: - # We want to keep conflicting with apache/bind9 tho - new_conflicts = "Conflicts: apache2, bind9" - - command = ( - f"sed -i /var/lib/dpkg/status -e 's@{conflicts}@{new_conflicts}@g'" - ) - logger.debug(f"Running: {command}") - os.system(command) From 4e560a93950d2ea09a2eb4ed83391707c8649b67 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 14 Jan 2022 01:35:09 +0100 Subject: [PATCH 1081/1155] Update changelog for 4.3.6 --- debian/changelog | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/debian/changelog b/debian/changelog index 49227f946..a59dfe2b0 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,18 @@ +yunohost (4.3.6) stable; urgency=low + + - [enh] ssh: add a new setting to manage PasswordAuthentication in sshd_config ([#1388](https://github.com/YunoHost/yunohost/pull/1388)) + - [enh] upgrades: filter more boring apt messages (3cc1a0a5) + - [fix] ynh_add_config: crons should be owned by root, otherwise they probably don't run? (0973301b) + - [fix] domains: force cert install during domain_add ([#1404](https://github.com/YunoHost/yunohost/pull/1404)) + - [fix] logs: remove 'args' for metadata, may contain unredacted secrets in edge cases + - [fix] helpers, apt: upgrade apt dependencies from extra repos ([#1407](https://github.com/YunoHost/yunohost/pull/1407)) + - [fix] diagnosis: incorrect dns check (relative vs absolute) for CNAME on subdomain (d81b85a4) + - [i18n] Translations updated for Dutch, French, Galician, German, Spanish, Ukrainian + + Thanks to all contributors <3 ! (Boudewijn, Christian Wehrli, Éric Gaspar, Germain Edy, José M, Kay0u, Kayou, ljf, Tagada, Tymofii-Lytvynenko) + + -- Alexandre Aubin Fri, 14 Jan 2022 01:29:58 +0100 + yunohost (4.3.5) stable; urgency=low - [fix] backup: bug in backup_delete when compress_tar_archives is True ([#1381](https://github.com/YunoHost/yunohost/pull/1381)) From 08efbbb9045eaed8e64d3dfc3f5e22e6ac0b5ecd Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 14 Jan 2022 01:37:17 +0100 Subject: [PATCH 1082/1155] Revert "Tmp remove bullseye migration for minor release" This reverts commit f322783d15200ea558d24b06a4999da3a7389bbc. --- .../0021_migrate_to_bullseye.py | 427 ++++++++++++++++++ 1 file changed, 427 insertions(+) create mode 100644 src/yunohost/data_migrations/0021_migrate_to_bullseye.py diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py new file mode 100644 index 000000000..e47087976 --- /dev/null +++ b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py @@ -0,0 +1,427 @@ +import glob +import os + +from moulinette import m18n +from yunohost.utils.error import YunohostError +from moulinette.utils.log import getActionLogger +from moulinette.utils.process import check_output, call_async_output +from moulinette.utils.filesystem import read_file, rm + +from yunohost.tools import ( + Migration, + tools_update, + tools_upgrade, + _apt_log_line_is_relevant, +) +from yunohost.app import unstable_apps +from yunohost.regenconf import manually_modified_files, _force_clear_hashes +from yunohost.utils.filesystem import free_space_in_directory +from yunohost.utils.packages import ( + get_ynh_package_version, + _list_upgradable_apt_packages, +) +from yunohost.service import _get_services, _save_services + +logger = getActionLogger("yunohost.migration") + +N_CURRENT_DEBIAN = 10 +N_CURRENT_YUNOHOST = 4 + +N_NEXT_DEBAN = 11 +N_NEXT_YUNOHOST = 11 + + +class MyMigration(Migration): + + "Upgrade the system to Debian Bullseye and Yunohost 11.x" + + mode = "manual" + + def run(self): + + self.check_assertions() + + logger.info(m18n.n("migration_0021_start")) + + # + # Add new apt .deb signing key + # + + new_apt_key = "https://forge.yunohost.org/yunohost_bullseye.asc" + check_output(f"wget -O- {new_apt_key} -q | apt-key add -qq -") + + # + # Patch sources.list + # + logger.info(m18n.n("migration_0021_patching_sources_list")) + self.patch_apt_sources_list() + + # Force add sury if it's not there yet + # This is to solve some weird issue with php-common breaking php7.3-common, + # hence breaking many php7.3-deps + # hence triggering some dependency conflict (or foobar-ynh-deps uninstall) + # Adding it there shouldnt be a big deal - Yunohost 11.x does add it + # through its regen conf anyway. + if not os.path.exists("/etc/apt/sources.list.d/extra_php_version.list"): + open("/etc/apt/sources.list.d/extra_php_version.list", "w").write( + "deb https://packages.sury.org/php/ bullseye main" + ) + os.system( + 'wget --timeout 900 --quiet "https://packages.sury.org/php/apt.gpg" --output-document=- | gpg --dearmor >"/etc/apt/trusted.gpg.d/extra_php_version.gpg"' + ) + + # + # Run apt update + # + + tools_update(target="system") + + # Tell libc6 it's okay to restart system stuff during the upgrade + os.system( + "echo 'libc6 libraries/restart-without-asking boolean true' | debconf-set-selections" + ) + + # Don't send an email to root about the postgresql migration. It should be handled automatically after. + os.system( + "echo 'postgresql-common postgresql-common/obsolete-major seen true' | debconf-set-selections" + ) + + # + # Patch yunohost conflicts + # + logger.info(m18n.n("migration_0021_patch_yunohost_conflicts")) + + self.patch_yunohost_conflicts() + + # + # Specific tweaking to get rid of custom my.cnf and use debian's default one + # (my.cnf is actually a symlink to mariadb.cnf) + # + + _force_clear_hashes(["/etc/mysql/my.cnf"]) + rm("/etc/mysql/mariadb.cnf", force=True) + rm("/etc/mysql/my.cnf", force=True) + ret = self.apt_install( + "mariadb-common --reinstall -o Dpkg::Options::='--force-confmiss'" + ) + if ret != 0: + # FIXME: i18n once this is stable? + raise YunohostError("Failed to reinstall mariadb-common ?", raw_msg=True) + + # + # /usr/share/yunohost/yunohost-config/ssl/yunoCA -> /usr/share/yunohost/ssl + # + if os.path.exists("/usr/share/yunohost/yunohost-config/ssl/yunoCA"): + os.system( + "mv /usr/share/yunohost/yunohost-config/ssl/yunoCA /usr/share/yunohost/ssl" + ) + rm("/usr/share/yunohost/yunohost-config", recursive=True, force=True) + + # + # /home/yunohost.conf -> /var/cache/yunohost/regenconf + # + if os.path.exists("/home/yunohost.conf"): + os.system("mv /home/yunohost.conf /var/cache/yunohost/regenconf") + rm("/home/yunohost.conf", recursive=True, force=True) + + # Remove legacy postgresql service record added by helpers, + # will now be dynamically handled by the core in bullseye + services = _get_services() + if "postgresql" in services: + del services["postgresql"] + _save_services(services) + + # + # Main upgrade + # + logger.info(m18n.n("migration_0021_main_upgrade")) + + apps_packages = self.get_apps_equivs_packages() + self.hold(apps_packages) + tools_upgrade(target="system", allow_yunohost_upgrade=False) + + if self.debian_major_version() == N_CURRENT_DEBIAN: + raise YunohostError("migration_0021_still_on_buster_after_main_upgrade") + + # Force explicit install of php7.4-fpm and other old 'default' dependencies + # that are now only in Recommends + # + # Also, we need to install php7.4 equivalents of other php7.3 dependencies. + # For example, Nextcloud may depend on php7.3-zip, and after the php pool migration + # to autoupgrade Nextcloud to 7.4, it will need the php7.4-zip to work. + # The following list is based on an ad-hoc analysis of php deps found in the + # app ecosystem, with a known equivalent on php7.4. + # + # This is kinda a dirty hack as it doesnt properly update the *-ynh-deps virtual packages + # with the proper list of dependencies, and the dependencies install this way + # will get flagged as 'manually installed'. + # + # We'll probably want to do something during the Bullseye->Bookworm migration to re-flag + # these as 'auto' so they get autoremoved if not needed anymore. + # Also hopefully by then we'll have manifestv2 (maybe) and will be able to use + # the apt resource mecanism to regenerate the *-ynh-deps virtual packages ;) + + php73packages_suffixes = [ + "apcu", + "bcmath", + "bz2", + "dom", + "gmp", + "igbinary", + "imagick", + "imap", + "mbstring", + "memcached", + "mysqli", + "mysqlnd", + "pgsql", + "redis", + "simplexml", + "soap", + "sqlite3", + "ssh2", + "tidy", + "xml", + "xmlrpc", + "xsl", + "zip", + ] + + cmd = ( + "apt show '*-ynh-deps' 2>/dev/null" + " | grep Depends" + f" | grep -o -E \"php7.3-({'|'.join(php73packages_suffixes)})\"" + " | sort | uniq" + " | sed 's/php7.3/php7.4/g'" + " || true" + ) + + basephp74packages_to_install = [ + "php7.4-fpm", + "php7.4-common", + "php7.4-ldap", + "php7.4-intl", + "php7.4-mysql", + "php7.4-gd", + "php7.4-curl", + "php-php-gettext", + ] + + php74packages_to_install = basephp74packages_to_install + [ + f.strip() for f in check_output(cmd).split("\n") if f.strip() + ] + + ret = self.apt_install( + f"{' '.join(php74packages_to_install)} " + "$(dpkg --list | grep ynh-deps | awk '{print $2}') " + "-o Dpkg::Options::='--force-confmiss'" + ) + if ret != 0: + # FIXME: i18n once this is stable? + raise YunohostError( + "Failed to force the install of php dependencies ?", raw_msg=True + ) + + os.system(f"apt-mark auto {' '.join(basephp74packages_to_install)}") + + # Clean the mess + logger.info(m18n.n("migration_0021_cleaning_up")) + os.system("apt autoremove --assume-yes") + os.system("apt clean --assume-yes") + + # + # Yunohost upgrade + # + logger.info(m18n.n("migration_0021_yunohost_upgrade")) + + self.unhold(apps_packages) + + cmd = "LC_ALL=C" + cmd += " DEBIAN_FRONTEND=noninteractive" + cmd += " APT_LISTCHANGES_FRONTEND=none" + cmd += " apt dist-upgrade " + cmd += " --quiet -o=Dpkg::Use-Pty=0 --fix-broken --dry-run" + cmd += " | grep -q 'ynh-deps'" + + logger.info("Simulating upgrade...") + if os.system(cmd) == 0: + # FIXME: i18n once this is stable? + raise YunohostError( + "The upgrade cannot be completed, because some app dependencies would need to be removed?", + raw_msg=True, + ) + + tools_upgrade(target="system") + + def debian_major_version(self): + # The python module "platform" and lsb_release are not reliable because + # on some setup, they may still return Release=9 even after upgrading to + # buster ... (Apparently this is related to OVH overriding some stuff + # with /etc/lsb-release for instance -_-) + # Instead, we rely on /etc/os-release which should be the raw info from + # the distribution... + 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_ynh_package_version("yunohost")["version"].split(".")[0]) + + def check_assertions(self): + + # Be on buster (10.x) and yunohost 4.x + # NB : we do both check to cover situations where the upgrade crashed + # in the middle and debian version could be > 9.x but yunohost package + # would still be in 3.x... + if ( + not self.debian_major_version() == N_CURRENT_DEBIAN + and not self.yunohost_major_version() == N_CURRENT_YUNOHOST + ): + raise YunohostError("migration_0021_not_buster") + + # Have > 1 Go free space on /var/ ? + if free_space_in_directory("/var/") / (1024 ** 3) < 1.0: + raise YunohostError("migration_0021_not_enough_free_space") + + # Check system is up to date + # (but we don't if 'bullseye' is already in the sources.list ... + # which means maybe a previous upgrade crashed and we're re-running it) + if " bullseye " not in read_file("/etc/apt/sources.list"): + tools_update(target="system") + upgradable_system_packages = list(_list_upgradable_apt_packages()) + if upgradable_system_packages: + raise YunohostError("migration_0021_system_not_fully_up_to_date") + + @property + def disclaimer(self): + + # Avoid having a super long disclaimer + uncessary check if we ain't + # on buster / yunohost 4.x anymore + # NB : we do both check to cover situations where the upgrade crashed + # in the middle and debian version could be >= 10.x but yunohost package + # would still be in 4.x... + if ( + not self.debian_major_version() == N_CURRENT_DEBIAN + and not self.yunohost_major_version() == N_CURRENT_YUNOHOST + ): + return None + + # Get list of problematic apps ? I.e. not official or community+working + problematic_apps = unstable_apps() + problematic_apps = "".join(["\n - " + app for app in problematic_apps]) + + # Manually modified files ? (c.f. yunohost service regen-conf) + modified_files = manually_modified_files() + modified_files = "".join(["\n - " + f for f in modified_files]) + + message = m18n.n("migration_0021_general_warning") + + # FIXME: re-enable this message with updated topic link once we release the migration as stable + # message = ( + # "N.B.: This migration has been tested by the community over the last few months but has only been declared stable recently. If your server hosts critical services and if you are not too confident with debugging possible issues, we recommend you to wait a little bit more while we gather more feedback and polish things up. If on the other hand you are relatively confident with debugging small issues that may arise, you are encouraged to run this migration ;)! You can read about remaining known issues and feedback from the community here: https://forum.yunohost.org/t/12195\n\n" + # + message + # ) + + if problematic_apps: + message += "\n\n" + m18n.n( + "migration_0021_problematic_apps_warning", + problematic_apps=problematic_apps, + ) + + if modified_files: + message += "\n\n" + m18n.n( + "migration_0021_modified_files", manually_modified_files=modified_files + ) + + return message + + def patch_apt_sources_list(self): + + sources_list = glob.glob("/etc/apt/sources.list.d/*.list") + sources_list.append("/etc/apt/sources.list") + + # This : + # - replace single 'buster' occurence by 'bulleye' + # - comments lines containing "backports" + # - replace 'buster/updates' by 'bullseye/updates' (or same with -) + # Special note about the security suite: + # https://www.debian.org/releases/bullseye/amd64/release-notes/ch-information.en.html#security-archive + for f in sources_list: + command = ( + f"sed -i {f} " + "-e 's@ buster @ bullseye @g' " + "-e '/backports/ s@^#*@#@' " + "-e 's@ buster/updates @ bullseye-security @g' " + "-e 's@ buster-@ bullseye-@g' " + ) + os.system(command) + + def get_apps_equivs_packages(self): + + command = ( + "dpkg --get-selections" + " | grep -v deinstall" + " | awk '{print $1}'" + " | { grep 'ynh-deps$' || true; }" + ) + + output = check_output(command) + + return output.split("\n") if output else [] + + def hold(self, packages): + for package in packages: + os.system(f"apt-mark hold {package}") + + def unhold(self, packages): + for package in packages: + os.system(f"apt-mark unhold {package}") + + def apt_install(self, cmd): + def is_relevant(line): + return "Reading database ..." not in line.rstrip() + + callbacks = ( + lambda l: logger.info("+ " + l.rstrip() + "\r") + if _apt_log_line_is_relevant(l) + else logger.debug(l.rstrip() + "\r"), + lambda l: logger.warning(l.rstrip()) + if _apt_log_line_is_relevant(l) + else logger.debug(l.rstrip()), + ) + + cmd = ( + "LC_ALL=C DEBIAN_FRONTEND=noninteractive APT_LISTCHANGES_FRONTEND=none apt install --quiet -o=Dpkg::Use-Pty=0 --fix-broken --assume-yes " + + cmd + ) + + logger.debug("Running: %s" % cmd) + + return call_async_output(cmd, callbacks, shell=True) + + def patch_yunohost_conflicts(self): + # + # This is a super dirty hack to remove the conflicts from yunohost's debian/control file + # Those conflicts are there to prevent mistakenly upgrading critical packages + # such as dovecot, postfix, nginx, openssl, etc... usually related to mistakenly + # using backports etc. + # + # The hack consists in savagely removing the conflicts directly in /var/lib/dpkg/status + # + + # We only patch the conflict if we're on yunohost 4.x + if self.yunohost_major_version() != N_CURRENT_YUNOHOST: + return + + conflicts = check_output("dpkg-query -s yunohost | grep '^Conflicts:'").strip() + if conflicts: + # We want to keep conflicting with apache/bind9 tho + new_conflicts = "Conflicts: apache2, bind9" + + command = ( + f"sed -i /var/lib/dpkg/status -e 's@{conflicts}@{new_conflicts}@g'" + ) + logger.debug(f"Running: {command}") + os.system(command) From 9354227a4e06b888d81498e23369ae5723a3148a Mon Sep 17 00:00:00 2001 From: Kay0u Date: Fri, 14 Jan 2022 12:07:46 +0100 Subject: [PATCH 1083/1155] [dnsmasq] ensure interface is up --- 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 3029b4a1f..c4c3d5498 100755 --- a/data/hooks/conf_regen/43-dnsmasq +++ b/data/hooks/conf_regen/43-dnsmasq @@ -28,7 +28,7 @@ do_pre_regen() { interfaces="$(ip -j addr show | jq -r '[.[].ifname]|join(" ")')" wireless_interfaces="lo" for dev in $(ls /sys/class/net); do - if [ -d "/sys/class/net/$dev/wireless" ]; then + if [ -d "/sys/class/net/$dev/wireless" ] && grep -q "up" "/sys/class/net/$dev/operstate"; then wireless_interfaces+=" $dev" fi done From 5479befabbc027503c985b2f9dc40ba4e3aab7c4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 16 Jan 2022 03:27:14 +0100 Subject: [PATCH 1084/1155] [fix] helpers apt: Remove boring message about 'file.asc' wasnt deleted because it doesnt exists --- helpers/apt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/helpers/apt b/helpers/apt index a38289dd2..ee352aebe 100644 --- a/helpers/apt +++ b/helpers/apt @@ -497,8 +497,14 @@ ynh_remove_extra_repo() { 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 --file="/etc/apt/trusted.gpg.d/$name.gpg" >/dev/null - ynh_secure_remove --file="/etc/apt/trusted.gpg.d/$name.asc" >/dev/null + if [ -e /etc/apt/trusted.gpg.d/$name.gpg ]; then + ynh_secure_remove --file="/etc/apt/trusted.gpg.d/$name.gpg" + fi + + # (Do we even create a .asc file anywhere ...?) + if [ -e /etc/apt/trusted.gpg.d/$name.asc ]; then + ynh_secure_remove --file="/etc/apt/trusted.gpg.d/$name.asc" + fi # Update the list of package to exclude the old repo ynh_package_update From 938d08134ec17dc7e85e0df037fb5f63839d3a66 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 17 Jan 2022 14:12:53 +0100 Subject: [PATCH 1085/1155] migrate_to_bullseye: Try to tell the system to not yolorestart nginx in the middle of the big upgrade because of nginx-common... --- .../data_migrations/0021_migrate_to_bullseye.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py index e47087976..82847c9a6 100644 --- a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py +++ b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py @@ -5,7 +5,7 @@ from moulinette import m18n from yunohost.utils.error import YunohostError from moulinette.utils.log import getActionLogger from moulinette.utils.process import check_output, call_async_output -from moulinette.utils.filesystem import read_file, rm +from moulinette.utils.filesystem import read_file, rm, write_to_file from yunohost.tools import ( Migration, @@ -81,6 +81,16 @@ class MyMigration(Migration): "echo 'libc6 libraries/restart-without-asking boolean true' | debconf-set-selections" ) + # Do not restart nginx during the upgrade of nginx-common ... + # c.f. https://manpages.debian.org/bullseye/init-system-helpers/deb-systemd-invoke.1p.en.html + # and the code inside /usr/bin/deb-systemd-invoke to see how it calls /usr/sbin/policy-rc.d ... + write_to_file( + '/usr/sbin/policy-rc.d', + '#!/bin/bash\n[[ "$1" == "nginx" ]] && return 101 || return 0' + ) + os.system("chmod +x /usr/sbin/policy-rc.d") + # FIXME: we still need to explicitly restart nginx somewhere ... + # Don't send an email to root about the postgresql migration. It should be handled automatically after. os.system( "echo 'postgresql-common postgresql-common/obsolete-major seen true' | debconf-set-selections" From b80158befe8d39946170940557ba1f52e0487116 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 17 Jan 2022 14:31:06 +0100 Subject: [PATCH 1086/1155] typo, in bash return is only inside functions... --- src/yunohost/data_migrations/0021_migrate_to_bullseye.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py index 82847c9a6..0cfa0ab7d 100644 --- a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py +++ b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py @@ -86,7 +86,7 @@ class MyMigration(Migration): # and the code inside /usr/bin/deb-systemd-invoke to see how it calls /usr/sbin/policy-rc.d ... write_to_file( '/usr/sbin/policy-rc.d', - '#!/bin/bash\n[[ "$1" == "nginx" ]] && return 101 || return 0' + '#!/bin/bash\n[[ "$1" == "nginx" ]] && exit 101 || exit 0' ) os.system("chmod +x /usr/sbin/policy-rc.d") # FIXME: we still need to explicitly restart nginx somewhere ... From 2612df97d09123303124c0494443f0257e432fd5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 17 Jan 2022 15:47:19 +0100 Subject: [PATCH 1087/1155] hmpf --- src/yunohost/data_migrations/0021_migrate_to_bullseye.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py index 0cfa0ab7d..fad29332c 100644 --- a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py +++ b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py @@ -81,12 +81,14 @@ class MyMigration(Migration): "echo 'libc6 libraries/restart-without-asking boolean true' | debconf-set-selections" ) - # Do not restart nginx during the upgrade of nginx-common ... + # Do not restart nginx during the upgrade of nginx-common and nginx-extras ... # c.f. https://manpages.debian.org/bullseye/init-system-helpers/deb-systemd-invoke.1p.en.html + # and zcat /usr/share/doc/init-system-helpers/README.policy-rc.d.gz # and the code inside /usr/bin/deb-systemd-invoke to see how it calls /usr/sbin/policy-rc.d ... + # and also invoke-rc.d ... write_to_file( '/usr/sbin/policy-rc.d', - '#!/bin/bash\n[[ "$1" == "nginx" ]] && exit 101 || exit 0' + '#!/bin/bash\n[[ "$1" =~ "nginx" ]] && [[ "$2" == "restart" ]] && exit 101 || exit 0' ) os.system("chmod +x /usr/sbin/policy-rc.d") # FIXME: we still need to explicitly restart nginx somewhere ... From c2c03587814a7898c3cf2b72a7ea97a7663a4626 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 17 Jan 2022 16:23:27 +0100 Subject: [PATCH 1088/1155] Moar boring apt warnings to filter --- src/yunohost/tools.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 021a8f3a1..c3202cf54 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -686,6 +686,12 @@ def _apt_log_line_is_relevant(line): ", does not exist on system.", "unable to delete old directory", "update-alternatives:", + "Configuration file '/etc", + "==> Modified (by you or by a script) since installation.", + "==> Package distributor has shipped an updated version.", + "==> Keeping old config file as default.", + "is a disabled or a static unit", + " update-rc.d: warning: start and stop actions are no longer supported; falling back to defaults", ] return line.rstrip() and all(i not in line.rstrip() for i in irrelevants) From 6ea327282875d06367612cd4cf06775fcc65efb7 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Mon, 17 Jan 2022 17:36:54 +0100 Subject: [PATCH 1089/1155] fix ynh_install_app_dependencies when an app change his default phpversion --- data/helpers.d/apt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index 0b75138aa..b50f4a87b 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -321,6 +321,20 @@ EOF # Set the default php version back as the default version for php-cli. update-alternatives --set php /usr/bin/php$YNH_DEFAULT_PHP_VERSION + local old_phpversion=$(ynh_app_setting_get --app=$app --key=phpversion) + + # If the PHP version changed, remove the old fpm conf + if [ -n "$old_phpversion" ] && [ "$old_phpversion" != "$specific_php_version" ]; then + local old_php_fpm_config_dir=$(ynh_app_setting_get --app=$app --key=fpm_config_dir) + local old_php_finalphpconf="$old_php_fpm_config_dir/pool.d/$app.conf" + + if [[ -f "$old_php_finalphpconf" ]] + then + ynh_backup_if_checksum_is_different --file="$old_php_finalphpconf" + ynh_remove_fpm_config + fi + fi + # Store phpversion into the config of this app ynh_app_setting_set --app=$app --key=phpversion --value=$specific_php_version From ac5718c66629603221d81e1f4511755a211893c3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 17 Jan 2022 20:38:00 +0100 Subject: [PATCH 1090/1155] =?UTF-8?q?migrate=5Fto=5Fbullseye:=20add=20supe?= =?UTF-8?q?r=20ugly=20and=20boring=20hack=20to=20tools=5Fupgrade=20to=20ru?= =?UTF-8?q?n=20some=20commands=20needed=20after=20migrations=20=C3=A9=5F?= =?UTF-8?q?=C3=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../0021_migrate_to_bullseye.py | 9 +++++--- src/yunohost/tools.py | 23 ++++++++++--------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py index fad29332c..88825dfa1 100644 --- a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py +++ b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py @@ -91,7 +91,6 @@ class MyMigration(Migration): '#!/bin/bash\n[[ "$1" =~ "nginx" ]] && [[ "$2" == "restart" ]] && exit 101 || exit 0' ) os.system("chmod +x /usr/sbin/policy-rc.d") - # FIXME: we still need to explicitly restart nginx somewhere ... # Don't send an email to root about the postgresql migration. It should be handled automatically after. os.system( @@ -234,8 +233,6 @@ class MyMigration(Migration): "Failed to force the install of php dependencies ?", raw_msg=True ) - os.system(f"apt-mark auto {' '.join(basephp74packages_to_install)}") - # Clean the mess logger.info(m18n.n("migration_0021_cleaning_up")) os.system("apt autoremove --assume-yes") @@ -263,6 +260,12 @@ class MyMigration(Migration): raw_msg=True, ) + + postupgradecmds = f"apt-mark auto {' '.join(basephp74packages_to_install)}\n" + postupgradecmds += "rm -f /usr/sbin/policy-rc.d\n" + postupgradecmds += "echo 'Restarting nginx...' >&2\n" + postupgradecmds += "systemctl restart nginx\n" + tools_upgrade(target="system") def debian_major_version(self): diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index c3202cf54..1eaad28bf 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -448,7 +448,7 @@ def _list_upgradable_apps(): @is_unit_operation() def tools_upgrade( - operation_logger, target=None, apps=False, system=False, allow_yunohost_upgrade=True + operation_logger, target=None, apps=False, system=False, allow_yunohost_upgrade=True, postupgradecmds="" ): """ Update apps & package cache, then display changelog @@ -646,19 +646,20 @@ def tools_upgrade( upgrade_completed = "\n" + m18n.n( "tools_upgrade_special_packages_completed" ) - command = "({wait} && {dist_upgrade}) && {mark_success} || {mark_failure}; {update_metadata}; echo '{done}'".format( - wait=wait_until_end_of_yunohost_command, - dist_upgrade=dist_upgrade, - mark_success=mark_success, - mark_failure=mark_failure, - update_metadata=update_log_metadata, - done=upgrade_completed, - ) + script = f""" +({wait_until_end_of_yunohost_command} && {dist_upgrade}) \\ +&& {mark_success} \\ +|| {mark_failure} + +{update_log_metadata} +{postupgradecmds} +echo '{upgrade_completed}' +""" logger.warning(m18n.n("tools_upgrade_special_packages_explanation")) - logger.debug("Running command :\n{}".format(command)) + logger.debug("Running script :\n{}".format(script)) open("/tmp/yunohost-selfupgrade", "w").write( - "rm /tmp/yunohost-selfupgrade; " + command + "rm /tmp/yunohost-selfupgrade\n " + script ) # Using systemd-run --scope is like nohup/disown and &, but more robust somehow # (despite using nohup/disown and &, the self-upgrade process was still getting killed...) From ecb8419ee0bd5055784f4c11cf7d927c4e4b77c7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 17 Jan 2022 21:21:46 +0100 Subject: [PATCH 1091/1155] dyndns: fix dyndns_update, needs to construct the update object using the target zone, not the domain itself --- src/dyndns.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/dyndns.py b/src/dyndns.py index 69f1989f6..c9da4f1be 100644 --- a/src/dyndns.py +++ b/src/dyndns.py @@ -219,8 +219,8 @@ def dyndns_update( return # Extract 'host', e.g. 'nohost.me' from 'foo.nohost.me' - host = domain.split(".")[1:] - host = ".".join(host) + zone = domain.split(".")[1:] + zone = ".".join(zone) logger.debug("Building zone update ...") @@ -229,7 +229,7 @@ def dyndns_update( keyring = dns.tsigkeyring.from_text({f'{domain}.': key}) # Python's dns.update is similar to the old nsupdate cli tool - update = dns.update.Update(domain, keyring=keyring, keyalgorithm=dns.tsig.HMAC_SHA512) + update = dns.update.Update(zone, keyring=keyring, keyalgorithm=dns.tsig.HMAC_SHA512) auth_resolvers = [] @@ -322,11 +322,15 @@ def dyndns_update( if not dry_run: try: - dns.query.tcp(update, auth_resolvers[0]) + r = dns.query.tcp(update, auth_resolvers[0]) except Exception as e: logger.error(e) raise YunohostError("dyndns_ip_update_failed") + if "rcode NOERROR" not in str(r): + logger.error(str(r)) + raise YunohostError("dyndns_ip_update_failed") + logger.success(m18n.n("dyndns_ip_updated")) else: print( From 019839dbf92a5d36c162a80abf6944d8f4a5f3a3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 17 Jan 2022 22:06:49 +0100 Subject: [PATCH 1092/1155] certificates: fix edge case where None is returned, triggering 'NoneType has no attribute get' --- src/yunohost/certificate.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 2f3676202..3d90895c7 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -860,14 +860,14 @@ def _check_domain_is_ready_for_ACME(domain): if is_yunohost_dyndns_domain(parent_domain): record_name = "@" - A_record_status = dnsrecords.get("data").get(f"A:{record_name}") - AAAA_record_status = dnsrecords.get("data").get(f"AAAA:{record_name}") + A_record_status = dnsrecords.get("data", {}).get(f"A:{record_name}") + AAAA_record_status = dnsrecords.get("data", {}).get(f"AAAA:{record_name}") # Fallback to wildcard in case no result yet for the DNS name? if not A_record_status: - A_record_status = dnsrecords.get("data").get("A:*") + A_record_status = dnsrecords.get("data", {}).get("A:*") if not AAAA_record_status: - AAAA_record_status = dnsrecords.get("data").get("AAAA:*") + AAAA_record_status = dnsrecords.get("data", {}).get("AAAA:*") if ( not httpreachable From deda909eba18e1c5d05343e5e0756d2dd16eb5a6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 17 Jan 2022 22:48:45 +0100 Subject: [PATCH 1093/1155] =?UTF-8?q?typoe=20/=20Aaaand=20ofc=20I=20forgot?= =?UTF-8?q?=20to=20pass=20the=20var=20as=20argument=20=E2=88=95o\?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/yunohost/data_migrations/0021_migrate_to_bullseye.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py index 88825dfa1..58bb259f7 100644 --- a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py +++ b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py @@ -266,7 +266,7 @@ class MyMigration(Migration): postupgradecmds += "echo 'Restarting nginx...' >&2\n" postupgradecmds += "systemctl restart nginx\n" - tools_upgrade(target="system") + tools_upgrade(target="system", postupgradecmds=postupgradecmds) def debian_major_version(self): # The python module "platform" and lsb_release are not reliable because From 1bbe3441d863467215ea63ad9378c30a861b9deb Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 17 Jan 2022 04:07:48 +0000 Subject: [PATCH 1094/1155] Translated using Weblate (German) Currently translated at 100.0% (720 of 720 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/de/ --- locales/de.json | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/locales/de.json b/locales/de.json index 3aa07475e..9e1087316 100644 --- a/locales/de.json +++ b/locales/de.json @@ -350,7 +350,7 @@ "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}' 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} scheinen keine Informationen über das Ablaufdatum zu enthalten. Stimmt das?", - "diagnosis_domain_expiration_warning": "Einige Domänen werden bald ablaufen.", + "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}.", "diagnosis_swap_none": "Das System hat gar keinen Swap. Sie sollten sich überlegen mindestens {recommended} an Swap einzurichten, um Situationen zu verhindern, in welchen der RAM des Systems knapp wird.", @@ -698,10 +698,25 @@ "ldap_attribute_already_exists": "LDAP-Attribut '{attribute}' existiert bereits mit dem Wert '{value}'", "user_import_success": "Benutzer:innen erfolgreich importiert", "domain_registrar_is_not_configured": "Der DNS-Registrar ist noch nicht für die Domäne '{domain}' konfiguriert.", - "domain_dns_push_not_applicable": "Die automatische DNS-Konfiguration ist nicht auf die Domäne {domain} anwendbar. Konfiguriere die DNS-Einträge manuell, wie unter https://yunohost.org/dns_config beschrieben.", + "domain_dns_push_not_applicable": "Die automatische DNS-Konfiguration ist nicht auf die Domäne {domain} anwendbar. Konfiguriere die DNS-Einträge manuell, wie unter https://yunohost.org/dns_config beschrieben.", "domain_dns_registrar_experimental": "Bislang wurde die Schnittstelle zur API von **{registrar}** noch nicht außreichend von der YunoHost-Community getestet und geprüft. Der Support ist **sehr experimentell** – sei vorsichtig!", "domain_dns_push_failed_to_authenticate": "Die Authentifizierung bei der API des Registrars für die Domäne '{domain}' ist fehlgeschlagen. Wahrscheinlich sind die Anmeldedaten falsch? (Fehler: {error})", "log_domain_config_set": "Konfiguration für die Domäne '{}' aktualisieren", "log_domain_dns_push": "DNS-Einträge für die Domäne '{}' übertragen", - "service_description_yunomdns": "Ermöglicht es dir, deinen Server über 'yunohost.local' in deinem lokalen Netzwerk zu erreichen" + "service_description_yunomdns": "Ermöglicht es dir, deinen Server über 'yunohost.local' in deinem lokalen Netzwerk zu erreichen", + "migration_0021_start": "Beginnen von Migration zu Bullseye", + "migration_0021_patching_sources_list": "Aktualisieren der sources.lists...", + "migration_0021_main_upgrade": "Starte Hauptupdate...", + "migration_0021_still_on_buster_after_main_upgrade": "Irgendetwas ist während des Haupt-Upgrades schief gelaufen, das System scheint immer noch auf Debian Buster zu laufen", + "migration_0021_yunohost_upgrade": "Start des YunoHost Kern-Upgrades...", + "migration_0021_not_buster": "Die aktuelle Debian-Distribution ist nicht Buster!", + "migration_0021_not_enough_free_space": "Der freie Speicherplatz in /var/ ist ziemlich gering! Sie sollten mindestens 1 GB frei haben, um diese Migration durchzuführen.", + "migration_0021_system_not_fully_up_to_date": "Ihr System ist nicht ganz aktuell. Bitte führen Sie ein reguläres Update durch, bevor Sie die Migration zu Bullseye durchführen.", + "migration_0021_problematic_apps_warning": "Bitte beachten Sie, dass die folgenden möglicherweise problematischen installierten Anwendungen erkannt wurden. Es sieht so aus, als ob diese nicht aus dem YunoHost-App-Katalog installiert wurden oder nicht als \"funktionierend\" gekennzeichnet sind. Es kann daher nicht garantiert werden, dass sie nach dem Update noch funktionieren werden: {problematic_apps}", + "migration_0021_modified_files": "Bitte beachten Sie, dass die folgenden Dateien manuell geändert wurden und nach dem Update möglicherweise überschrieben werden: {manually_modified_files}", + "migration_0021_cleaning_up": "Bereinigung von Cache und Paketen nicht mehr nötig...", + "migration_0021_patch_yunohost_conflicts": "Patch anwenden, um das Konfliktproblem zu umgehen...", + "global_settings_setting_security_ssh_password_authentication": "Passwort-Authentifizierung für SSH zulassen", + "migration_description_0021_migrate_to_bullseye": "Upgrade des Systems auf Debian Bullseye und YunoHost 11.x", + "migration_0021_general_warning": "Bitte beachten Sie, dass diese Migration ein heikler Vorgang ist. Das YunoHost-Team hat sein Bestes getan, um sie zu überprüfen und zu testen, aber die Migration könnte immer noch Teile des Systems oder seiner Anwendungen beschädigen.\n\nEs wird daher empfohlen,:\n - Führen Sie ein Backup aller kritischen Daten oder Anwendungen durch. Mehr Informationen unter https://yunohost.org/backup;\n - Haben Sie Geduld, nachdem Sie die Migration gestartet haben: Je nach Internetverbindung und Hardware kann es bis zu ein paar Stunden dauern, bis alles aktualisiert ist." } From 7373971573a13b72da9eef8b9c7f106d27dc5552 Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Mon, 17 Jan 2022 22:03:13 +0000 Subject: [PATCH 1095/1155] [CI] Format code with Black --- src/yunohost/data_migrations/0021_migrate_to_bullseye.py | 5 ++--- src/yunohost/tools.py | 7 ++++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py index 58bb259f7..eace6ca2e 100644 --- a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py +++ b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py @@ -87,8 +87,8 @@ class MyMigration(Migration): # and the code inside /usr/bin/deb-systemd-invoke to see how it calls /usr/sbin/policy-rc.d ... # and also invoke-rc.d ... write_to_file( - '/usr/sbin/policy-rc.d', - '#!/bin/bash\n[[ "$1" =~ "nginx" ]] && [[ "$2" == "restart" ]] && exit 101 || exit 0' + "/usr/sbin/policy-rc.d", + '#!/bin/bash\n[[ "$1" =~ "nginx" ]] && [[ "$2" == "restart" ]] && exit 101 || exit 0', ) os.system("chmod +x /usr/sbin/policy-rc.d") @@ -260,7 +260,6 @@ class MyMigration(Migration): raw_msg=True, ) - postupgradecmds = f"apt-mark auto {' '.join(basephp74packages_to_install)}\n" postupgradecmds += "rm -f /usr/sbin/policy-rc.d\n" postupgradecmds += "echo 'Restarting nginx...' >&2\n" diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 1eaad28bf..dbd3af5f5 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -448,7 +448,12 @@ def _list_upgradable_apps(): @is_unit_operation() def tools_upgrade( - operation_logger, target=None, apps=False, system=False, allow_yunohost_upgrade=True, postupgradecmds="" + operation_logger, + target=None, + apps=False, + system=False, + allow_yunohost_upgrade=True, + postupgradecmds="", ): """ Update apps & package cache, then display changelog From 89dda11b5649d28ccafe83a2911e09ed857728ab Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 17 Jan 2022 23:58:04 +0100 Subject: [PATCH 1096/1155] postgresql migration: Attempt to fix cluster upgrade, complaining that cluster for 13 already existed, suspecting a race condition maybe --- src/migrations/0023_postgresql_11_to_13.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/migrations/0023_postgresql_11_to_13.py b/src/migrations/0023_postgresql_11_to_13.py index 145f15558..8f03f8c5f 100644 --- a/src/migrations/0023_postgresql_11_to_13.py +++ b/src/migrations/0023_postgresql_11_to_13.py @@ -1,4 +1,5 @@ import subprocess +import time from moulinette import m18n from yunohost.utils.error import YunohostError, YunohostValidationError @@ -42,9 +43,11 @@ class MyMigration(Migration): ) self.runcmd("systemctl stop postgresql") + time.sleep(3) self.runcmd( "LC_ALL=C pg_dropcluster --stop 13 main || true" ) # We do not trigger an exception if the command fails because that probably means cluster 13 doesn't exists, which is fine because it's created during the pg_upgradecluster) + time.sleep(3) self.runcmd("LC_ALL=C pg_upgradecluster -m upgrade 11 main") self.runcmd("LC_ALL=C pg_dropcluster --stop 11 main") self.runcmd("systemctl start postgresql") From 383e540ee0d95ed46cedb7bdc5dbb92d9d3c8a5c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 19 Jan 2022 20:08:34 +0100 Subject: [PATCH 1097/1155] Tmp remove bullseye migration for stable release --- .../0021_migrate_to_bullseye.py | 441 ------------------ 1 file changed, 441 deletions(-) delete mode 100644 src/yunohost/data_migrations/0021_migrate_to_bullseye.py diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py deleted file mode 100644 index eace6ca2e..000000000 --- a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py +++ /dev/null @@ -1,441 +0,0 @@ -import glob -import os - -from moulinette import m18n -from yunohost.utils.error import YunohostError -from moulinette.utils.log import getActionLogger -from moulinette.utils.process import check_output, call_async_output -from moulinette.utils.filesystem import read_file, rm, write_to_file - -from yunohost.tools import ( - Migration, - tools_update, - tools_upgrade, - _apt_log_line_is_relevant, -) -from yunohost.app import unstable_apps -from yunohost.regenconf import manually_modified_files, _force_clear_hashes -from yunohost.utils.filesystem import free_space_in_directory -from yunohost.utils.packages import ( - get_ynh_package_version, - _list_upgradable_apt_packages, -) -from yunohost.service import _get_services, _save_services - -logger = getActionLogger("yunohost.migration") - -N_CURRENT_DEBIAN = 10 -N_CURRENT_YUNOHOST = 4 - -N_NEXT_DEBAN = 11 -N_NEXT_YUNOHOST = 11 - - -class MyMigration(Migration): - - "Upgrade the system to Debian Bullseye and Yunohost 11.x" - - mode = "manual" - - def run(self): - - self.check_assertions() - - logger.info(m18n.n("migration_0021_start")) - - # - # Add new apt .deb signing key - # - - new_apt_key = "https://forge.yunohost.org/yunohost_bullseye.asc" - check_output(f"wget -O- {new_apt_key} -q | apt-key add -qq -") - - # - # Patch sources.list - # - logger.info(m18n.n("migration_0021_patching_sources_list")) - self.patch_apt_sources_list() - - # Force add sury if it's not there yet - # This is to solve some weird issue with php-common breaking php7.3-common, - # hence breaking many php7.3-deps - # hence triggering some dependency conflict (or foobar-ynh-deps uninstall) - # Adding it there shouldnt be a big deal - Yunohost 11.x does add it - # through its regen conf anyway. - if not os.path.exists("/etc/apt/sources.list.d/extra_php_version.list"): - open("/etc/apt/sources.list.d/extra_php_version.list", "w").write( - "deb https://packages.sury.org/php/ bullseye main" - ) - os.system( - 'wget --timeout 900 --quiet "https://packages.sury.org/php/apt.gpg" --output-document=- | gpg --dearmor >"/etc/apt/trusted.gpg.d/extra_php_version.gpg"' - ) - - # - # Run apt update - # - - tools_update(target="system") - - # Tell libc6 it's okay to restart system stuff during the upgrade - os.system( - "echo 'libc6 libraries/restart-without-asking boolean true' | debconf-set-selections" - ) - - # Do not restart nginx during the upgrade of nginx-common and nginx-extras ... - # c.f. https://manpages.debian.org/bullseye/init-system-helpers/deb-systemd-invoke.1p.en.html - # and zcat /usr/share/doc/init-system-helpers/README.policy-rc.d.gz - # and the code inside /usr/bin/deb-systemd-invoke to see how it calls /usr/sbin/policy-rc.d ... - # and also invoke-rc.d ... - write_to_file( - "/usr/sbin/policy-rc.d", - '#!/bin/bash\n[[ "$1" =~ "nginx" ]] && [[ "$2" == "restart" ]] && exit 101 || exit 0', - ) - os.system("chmod +x /usr/sbin/policy-rc.d") - - # Don't send an email to root about the postgresql migration. It should be handled automatically after. - os.system( - "echo 'postgresql-common postgresql-common/obsolete-major seen true' | debconf-set-selections" - ) - - # - # Patch yunohost conflicts - # - logger.info(m18n.n("migration_0021_patch_yunohost_conflicts")) - - self.patch_yunohost_conflicts() - - # - # Specific tweaking to get rid of custom my.cnf and use debian's default one - # (my.cnf is actually a symlink to mariadb.cnf) - # - - _force_clear_hashes(["/etc/mysql/my.cnf"]) - rm("/etc/mysql/mariadb.cnf", force=True) - rm("/etc/mysql/my.cnf", force=True) - ret = self.apt_install( - "mariadb-common --reinstall -o Dpkg::Options::='--force-confmiss'" - ) - if ret != 0: - # FIXME: i18n once this is stable? - raise YunohostError("Failed to reinstall mariadb-common ?", raw_msg=True) - - # - # /usr/share/yunohost/yunohost-config/ssl/yunoCA -> /usr/share/yunohost/ssl - # - if os.path.exists("/usr/share/yunohost/yunohost-config/ssl/yunoCA"): - os.system( - "mv /usr/share/yunohost/yunohost-config/ssl/yunoCA /usr/share/yunohost/ssl" - ) - rm("/usr/share/yunohost/yunohost-config", recursive=True, force=True) - - # - # /home/yunohost.conf -> /var/cache/yunohost/regenconf - # - if os.path.exists("/home/yunohost.conf"): - os.system("mv /home/yunohost.conf /var/cache/yunohost/regenconf") - rm("/home/yunohost.conf", recursive=True, force=True) - - # Remove legacy postgresql service record added by helpers, - # will now be dynamically handled by the core in bullseye - services = _get_services() - if "postgresql" in services: - del services["postgresql"] - _save_services(services) - - # - # Main upgrade - # - logger.info(m18n.n("migration_0021_main_upgrade")) - - apps_packages = self.get_apps_equivs_packages() - self.hold(apps_packages) - tools_upgrade(target="system", allow_yunohost_upgrade=False) - - if self.debian_major_version() == N_CURRENT_DEBIAN: - raise YunohostError("migration_0021_still_on_buster_after_main_upgrade") - - # Force explicit install of php7.4-fpm and other old 'default' dependencies - # that are now only in Recommends - # - # Also, we need to install php7.4 equivalents of other php7.3 dependencies. - # For example, Nextcloud may depend on php7.3-zip, and after the php pool migration - # to autoupgrade Nextcloud to 7.4, it will need the php7.4-zip to work. - # The following list is based on an ad-hoc analysis of php deps found in the - # app ecosystem, with a known equivalent on php7.4. - # - # This is kinda a dirty hack as it doesnt properly update the *-ynh-deps virtual packages - # with the proper list of dependencies, and the dependencies install this way - # will get flagged as 'manually installed'. - # - # We'll probably want to do something during the Bullseye->Bookworm migration to re-flag - # these as 'auto' so they get autoremoved if not needed anymore. - # Also hopefully by then we'll have manifestv2 (maybe) and will be able to use - # the apt resource mecanism to regenerate the *-ynh-deps virtual packages ;) - - php73packages_suffixes = [ - "apcu", - "bcmath", - "bz2", - "dom", - "gmp", - "igbinary", - "imagick", - "imap", - "mbstring", - "memcached", - "mysqli", - "mysqlnd", - "pgsql", - "redis", - "simplexml", - "soap", - "sqlite3", - "ssh2", - "tidy", - "xml", - "xmlrpc", - "xsl", - "zip", - ] - - cmd = ( - "apt show '*-ynh-deps' 2>/dev/null" - " | grep Depends" - f" | grep -o -E \"php7.3-({'|'.join(php73packages_suffixes)})\"" - " | sort | uniq" - " | sed 's/php7.3/php7.4/g'" - " || true" - ) - - basephp74packages_to_install = [ - "php7.4-fpm", - "php7.4-common", - "php7.4-ldap", - "php7.4-intl", - "php7.4-mysql", - "php7.4-gd", - "php7.4-curl", - "php-php-gettext", - ] - - php74packages_to_install = basephp74packages_to_install + [ - f.strip() for f in check_output(cmd).split("\n") if f.strip() - ] - - ret = self.apt_install( - f"{' '.join(php74packages_to_install)} " - "$(dpkg --list | grep ynh-deps | awk '{print $2}') " - "-o Dpkg::Options::='--force-confmiss'" - ) - if ret != 0: - # FIXME: i18n once this is stable? - raise YunohostError( - "Failed to force the install of php dependencies ?", raw_msg=True - ) - - # Clean the mess - logger.info(m18n.n("migration_0021_cleaning_up")) - os.system("apt autoremove --assume-yes") - os.system("apt clean --assume-yes") - - # - # Yunohost upgrade - # - logger.info(m18n.n("migration_0021_yunohost_upgrade")) - - self.unhold(apps_packages) - - cmd = "LC_ALL=C" - cmd += " DEBIAN_FRONTEND=noninteractive" - cmd += " APT_LISTCHANGES_FRONTEND=none" - cmd += " apt dist-upgrade " - cmd += " --quiet -o=Dpkg::Use-Pty=0 --fix-broken --dry-run" - cmd += " | grep -q 'ynh-deps'" - - logger.info("Simulating upgrade...") - if os.system(cmd) == 0: - # FIXME: i18n once this is stable? - raise YunohostError( - "The upgrade cannot be completed, because some app dependencies would need to be removed?", - raw_msg=True, - ) - - postupgradecmds = f"apt-mark auto {' '.join(basephp74packages_to_install)}\n" - postupgradecmds += "rm -f /usr/sbin/policy-rc.d\n" - postupgradecmds += "echo 'Restarting nginx...' >&2\n" - postupgradecmds += "systemctl restart nginx\n" - - tools_upgrade(target="system", postupgradecmds=postupgradecmds) - - def debian_major_version(self): - # The python module "platform" and lsb_release are not reliable because - # on some setup, they may still return Release=9 even after upgrading to - # buster ... (Apparently this is related to OVH overriding some stuff - # with /etc/lsb-release for instance -_-) - # Instead, we rely on /etc/os-release which should be the raw info from - # the distribution... - 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_ynh_package_version("yunohost")["version"].split(".")[0]) - - def check_assertions(self): - - # Be on buster (10.x) and yunohost 4.x - # NB : we do both check to cover situations where the upgrade crashed - # in the middle and debian version could be > 9.x but yunohost package - # would still be in 3.x... - if ( - not self.debian_major_version() == N_CURRENT_DEBIAN - and not self.yunohost_major_version() == N_CURRENT_YUNOHOST - ): - raise YunohostError("migration_0021_not_buster") - - # Have > 1 Go free space on /var/ ? - if free_space_in_directory("/var/") / (1024 ** 3) < 1.0: - raise YunohostError("migration_0021_not_enough_free_space") - - # Check system is up to date - # (but we don't if 'bullseye' is already in the sources.list ... - # which means maybe a previous upgrade crashed and we're re-running it) - if " bullseye " not in read_file("/etc/apt/sources.list"): - tools_update(target="system") - upgradable_system_packages = list(_list_upgradable_apt_packages()) - if upgradable_system_packages: - raise YunohostError("migration_0021_system_not_fully_up_to_date") - - @property - def disclaimer(self): - - # Avoid having a super long disclaimer + uncessary check if we ain't - # on buster / yunohost 4.x anymore - # NB : we do both check to cover situations where the upgrade crashed - # in the middle and debian version could be >= 10.x but yunohost package - # would still be in 4.x... - if ( - not self.debian_major_version() == N_CURRENT_DEBIAN - and not self.yunohost_major_version() == N_CURRENT_YUNOHOST - ): - return None - - # Get list of problematic apps ? I.e. not official or community+working - problematic_apps = unstable_apps() - problematic_apps = "".join(["\n - " + app for app in problematic_apps]) - - # Manually modified files ? (c.f. yunohost service regen-conf) - modified_files = manually_modified_files() - modified_files = "".join(["\n - " + f for f in modified_files]) - - message = m18n.n("migration_0021_general_warning") - - # FIXME: re-enable this message with updated topic link once we release the migration as stable - # message = ( - # "N.B.: This migration has been tested by the community over the last few months but has only been declared stable recently. If your server hosts critical services and if you are not too confident with debugging possible issues, we recommend you to wait a little bit more while we gather more feedback and polish things up. If on the other hand you are relatively confident with debugging small issues that may arise, you are encouraged to run this migration ;)! You can read about remaining known issues and feedback from the community here: https://forum.yunohost.org/t/12195\n\n" - # + message - # ) - - if problematic_apps: - message += "\n\n" + m18n.n( - "migration_0021_problematic_apps_warning", - problematic_apps=problematic_apps, - ) - - if modified_files: - message += "\n\n" + m18n.n( - "migration_0021_modified_files", manually_modified_files=modified_files - ) - - return message - - def patch_apt_sources_list(self): - - sources_list = glob.glob("/etc/apt/sources.list.d/*.list") - sources_list.append("/etc/apt/sources.list") - - # This : - # - replace single 'buster' occurence by 'bulleye' - # - comments lines containing "backports" - # - replace 'buster/updates' by 'bullseye/updates' (or same with -) - # Special note about the security suite: - # https://www.debian.org/releases/bullseye/amd64/release-notes/ch-information.en.html#security-archive - for f in sources_list: - command = ( - f"sed -i {f} " - "-e 's@ buster @ bullseye @g' " - "-e '/backports/ s@^#*@#@' " - "-e 's@ buster/updates @ bullseye-security @g' " - "-e 's@ buster-@ bullseye-@g' " - ) - os.system(command) - - def get_apps_equivs_packages(self): - - command = ( - "dpkg --get-selections" - " | grep -v deinstall" - " | awk '{print $1}'" - " | { grep 'ynh-deps$' || true; }" - ) - - output = check_output(command) - - return output.split("\n") if output else [] - - def hold(self, packages): - for package in packages: - os.system(f"apt-mark hold {package}") - - def unhold(self, packages): - for package in packages: - os.system(f"apt-mark unhold {package}") - - def apt_install(self, cmd): - def is_relevant(line): - return "Reading database ..." not in line.rstrip() - - callbacks = ( - lambda l: logger.info("+ " + l.rstrip() + "\r") - if _apt_log_line_is_relevant(l) - else logger.debug(l.rstrip() + "\r"), - lambda l: logger.warning(l.rstrip()) - if _apt_log_line_is_relevant(l) - else logger.debug(l.rstrip()), - ) - - cmd = ( - "LC_ALL=C DEBIAN_FRONTEND=noninteractive APT_LISTCHANGES_FRONTEND=none apt install --quiet -o=Dpkg::Use-Pty=0 --fix-broken --assume-yes " - + cmd - ) - - logger.debug("Running: %s" % cmd) - - return call_async_output(cmd, callbacks, shell=True) - - def patch_yunohost_conflicts(self): - # - # This is a super dirty hack to remove the conflicts from yunohost's debian/control file - # Those conflicts are there to prevent mistakenly upgrading critical packages - # such as dovecot, postfix, nginx, openssl, etc... usually related to mistakenly - # using backports etc. - # - # The hack consists in savagely removing the conflicts directly in /var/lib/dpkg/status - # - - # We only patch the conflict if we're on yunohost 4.x - if self.yunohost_major_version() != N_CURRENT_YUNOHOST: - return - - conflicts = check_output("dpkg-query -s yunohost | grep '^Conflicts:'").strip() - if conflicts: - # We want to keep conflicting with apache/bind9 tho - new_conflicts = "Conflicts: apache2, bind9" - - command = ( - f"sed -i /var/lib/dpkg/status -e 's@{conflicts}@{new_conflicts}@g'" - ) - logger.debug(f"Running: {command}") - os.system(command) From 1725da45d43226c7d008fdaa5230e3f1ebe0f368 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 19 Jan 2022 20:08:50 +0100 Subject: [PATCH 1098/1155] Update changelog for 4.3.6.1 --- debian/changelog | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/debian/changelog b/debian/changelog index a59dfe2b0..520f6485e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,14 @@ +yunohost (4.3.6.1) stable; urgency=low + + - [fix] dnsmasq: ensure interface is up ([#1410](https://github.com/YunoHost/yunohost/pull/1410)) + - [fix] apt helpers: fix ynh_install_app_dependencies when an app change his default phpversion (6ea32728) + - [fix] certificates: fix edge case where None is returned, triggering 'NoneType has no attribute get' (019839db) + - [i18n] Translations updated for German + + Thanks to all contributors <3 ! (Gregor, Kay0u) + + -- Alexandre Aubin Wed, 19 Jan 2022 20:05:13 +0100 + yunohost (4.3.6) stable; urgency=low - [enh] ssh: add a new setting to manage PasswordAuthentication in sshd_config ([#1388](https://github.com/YunoHost/yunohost/pull/1388)) From 7920cc6280ac8ca3f93a6fd3e418532a186bcea7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 19 Jan 2022 20:30:11 +0100 Subject: [PATCH 1099/1155] apt helpers: fix bug when var is empty... --- 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 b50f4a87b..490a59f24 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -443,7 +443,7 @@ ynh_install_extra_app_dependencies() { # Without doing apt install, an already installed dep is not upgraded local apps_auto_installed="$(apt-mark showauto $package)" ynh_package_install "$package" - apt-mark auto $apps_auto_installed + [ -z "$apps_auto_installed" ] || apt-mark auto $apps_auto_installed # Remove this extra repository after packages are installed ynh_remove_extra_repo --name=$app From 981fca64e234848510a2daeafec1eda07516e0ac Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 19 Jan 2022 20:31:04 +0100 Subject: [PATCH 1100/1155] Update changelog for 4.3.6.2 --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index 520f6485e..066fcbe9d 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +yunohost (4.3.6.2) stable; urgency=low + + - [fix] apt helpers: fix bug when var is empty... (7920cc62) + + -- Alexandre Aubin Wed, 19 Jan 2022 20:30:25 +0100 + yunohost (4.3.6.1) stable; urgency=low - [fix] dnsmasq: ensure interface is up ([#1410](https://github.com/YunoHost/yunohost/pull/1410)) From 9c1f3c4be885abc3b39268f451cfe325cb6ff005 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 19 Jan 2022 20:34:59 +0100 Subject: [PATCH 1101/1155] Revert "Tmp remove bullseye migration for stable release" This reverts commit 383e540ee0d95ed46cedb7bdc5dbb92d9d3c8a5c. --- .../0021_migrate_to_bullseye.py | 441 ++++++++++++++++++ 1 file changed, 441 insertions(+) create mode 100644 src/yunohost/data_migrations/0021_migrate_to_bullseye.py diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py new file mode 100644 index 000000000..eace6ca2e --- /dev/null +++ b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py @@ -0,0 +1,441 @@ +import glob +import os + +from moulinette import m18n +from yunohost.utils.error import YunohostError +from moulinette.utils.log import getActionLogger +from moulinette.utils.process import check_output, call_async_output +from moulinette.utils.filesystem import read_file, rm, write_to_file + +from yunohost.tools import ( + Migration, + tools_update, + tools_upgrade, + _apt_log_line_is_relevant, +) +from yunohost.app import unstable_apps +from yunohost.regenconf import manually_modified_files, _force_clear_hashes +from yunohost.utils.filesystem import free_space_in_directory +from yunohost.utils.packages import ( + get_ynh_package_version, + _list_upgradable_apt_packages, +) +from yunohost.service import _get_services, _save_services + +logger = getActionLogger("yunohost.migration") + +N_CURRENT_DEBIAN = 10 +N_CURRENT_YUNOHOST = 4 + +N_NEXT_DEBAN = 11 +N_NEXT_YUNOHOST = 11 + + +class MyMigration(Migration): + + "Upgrade the system to Debian Bullseye and Yunohost 11.x" + + mode = "manual" + + def run(self): + + self.check_assertions() + + logger.info(m18n.n("migration_0021_start")) + + # + # Add new apt .deb signing key + # + + new_apt_key = "https://forge.yunohost.org/yunohost_bullseye.asc" + check_output(f"wget -O- {new_apt_key} -q | apt-key add -qq -") + + # + # Patch sources.list + # + logger.info(m18n.n("migration_0021_patching_sources_list")) + self.patch_apt_sources_list() + + # Force add sury if it's not there yet + # This is to solve some weird issue with php-common breaking php7.3-common, + # hence breaking many php7.3-deps + # hence triggering some dependency conflict (or foobar-ynh-deps uninstall) + # Adding it there shouldnt be a big deal - Yunohost 11.x does add it + # through its regen conf anyway. + if not os.path.exists("/etc/apt/sources.list.d/extra_php_version.list"): + open("/etc/apt/sources.list.d/extra_php_version.list", "w").write( + "deb https://packages.sury.org/php/ bullseye main" + ) + os.system( + 'wget --timeout 900 --quiet "https://packages.sury.org/php/apt.gpg" --output-document=- | gpg --dearmor >"/etc/apt/trusted.gpg.d/extra_php_version.gpg"' + ) + + # + # Run apt update + # + + tools_update(target="system") + + # Tell libc6 it's okay to restart system stuff during the upgrade + os.system( + "echo 'libc6 libraries/restart-without-asking boolean true' | debconf-set-selections" + ) + + # Do not restart nginx during the upgrade of nginx-common and nginx-extras ... + # c.f. https://manpages.debian.org/bullseye/init-system-helpers/deb-systemd-invoke.1p.en.html + # and zcat /usr/share/doc/init-system-helpers/README.policy-rc.d.gz + # and the code inside /usr/bin/deb-systemd-invoke to see how it calls /usr/sbin/policy-rc.d ... + # and also invoke-rc.d ... + write_to_file( + "/usr/sbin/policy-rc.d", + '#!/bin/bash\n[[ "$1" =~ "nginx" ]] && [[ "$2" == "restart" ]] && exit 101 || exit 0', + ) + os.system("chmod +x /usr/sbin/policy-rc.d") + + # Don't send an email to root about the postgresql migration. It should be handled automatically after. + os.system( + "echo 'postgresql-common postgresql-common/obsolete-major seen true' | debconf-set-selections" + ) + + # + # Patch yunohost conflicts + # + logger.info(m18n.n("migration_0021_patch_yunohost_conflicts")) + + self.patch_yunohost_conflicts() + + # + # Specific tweaking to get rid of custom my.cnf and use debian's default one + # (my.cnf is actually a symlink to mariadb.cnf) + # + + _force_clear_hashes(["/etc/mysql/my.cnf"]) + rm("/etc/mysql/mariadb.cnf", force=True) + rm("/etc/mysql/my.cnf", force=True) + ret = self.apt_install( + "mariadb-common --reinstall -o Dpkg::Options::='--force-confmiss'" + ) + if ret != 0: + # FIXME: i18n once this is stable? + raise YunohostError("Failed to reinstall mariadb-common ?", raw_msg=True) + + # + # /usr/share/yunohost/yunohost-config/ssl/yunoCA -> /usr/share/yunohost/ssl + # + if os.path.exists("/usr/share/yunohost/yunohost-config/ssl/yunoCA"): + os.system( + "mv /usr/share/yunohost/yunohost-config/ssl/yunoCA /usr/share/yunohost/ssl" + ) + rm("/usr/share/yunohost/yunohost-config", recursive=True, force=True) + + # + # /home/yunohost.conf -> /var/cache/yunohost/regenconf + # + if os.path.exists("/home/yunohost.conf"): + os.system("mv /home/yunohost.conf /var/cache/yunohost/regenconf") + rm("/home/yunohost.conf", recursive=True, force=True) + + # Remove legacy postgresql service record added by helpers, + # will now be dynamically handled by the core in bullseye + services = _get_services() + if "postgresql" in services: + del services["postgresql"] + _save_services(services) + + # + # Main upgrade + # + logger.info(m18n.n("migration_0021_main_upgrade")) + + apps_packages = self.get_apps_equivs_packages() + self.hold(apps_packages) + tools_upgrade(target="system", allow_yunohost_upgrade=False) + + if self.debian_major_version() == N_CURRENT_DEBIAN: + raise YunohostError("migration_0021_still_on_buster_after_main_upgrade") + + # Force explicit install of php7.4-fpm and other old 'default' dependencies + # that are now only in Recommends + # + # Also, we need to install php7.4 equivalents of other php7.3 dependencies. + # For example, Nextcloud may depend on php7.3-zip, and after the php pool migration + # to autoupgrade Nextcloud to 7.4, it will need the php7.4-zip to work. + # The following list is based on an ad-hoc analysis of php deps found in the + # app ecosystem, with a known equivalent on php7.4. + # + # This is kinda a dirty hack as it doesnt properly update the *-ynh-deps virtual packages + # with the proper list of dependencies, and the dependencies install this way + # will get flagged as 'manually installed'. + # + # We'll probably want to do something during the Bullseye->Bookworm migration to re-flag + # these as 'auto' so they get autoremoved if not needed anymore. + # Also hopefully by then we'll have manifestv2 (maybe) and will be able to use + # the apt resource mecanism to regenerate the *-ynh-deps virtual packages ;) + + php73packages_suffixes = [ + "apcu", + "bcmath", + "bz2", + "dom", + "gmp", + "igbinary", + "imagick", + "imap", + "mbstring", + "memcached", + "mysqli", + "mysqlnd", + "pgsql", + "redis", + "simplexml", + "soap", + "sqlite3", + "ssh2", + "tidy", + "xml", + "xmlrpc", + "xsl", + "zip", + ] + + cmd = ( + "apt show '*-ynh-deps' 2>/dev/null" + " | grep Depends" + f" | grep -o -E \"php7.3-({'|'.join(php73packages_suffixes)})\"" + " | sort | uniq" + " | sed 's/php7.3/php7.4/g'" + " || true" + ) + + basephp74packages_to_install = [ + "php7.4-fpm", + "php7.4-common", + "php7.4-ldap", + "php7.4-intl", + "php7.4-mysql", + "php7.4-gd", + "php7.4-curl", + "php-php-gettext", + ] + + php74packages_to_install = basephp74packages_to_install + [ + f.strip() for f in check_output(cmd).split("\n") if f.strip() + ] + + ret = self.apt_install( + f"{' '.join(php74packages_to_install)} " + "$(dpkg --list | grep ynh-deps | awk '{print $2}') " + "-o Dpkg::Options::='--force-confmiss'" + ) + if ret != 0: + # FIXME: i18n once this is stable? + raise YunohostError( + "Failed to force the install of php dependencies ?", raw_msg=True + ) + + # Clean the mess + logger.info(m18n.n("migration_0021_cleaning_up")) + os.system("apt autoremove --assume-yes") + os.system("apt clean --assume-yes") + + # + # Yunohost upgrade + # + logger.info(m18n.n("migration_0021_yunohost_upgrade")) + + self.unhold(apps_packages) + + cmd = "LC_ALL=C" + cmd += " DEBIAN_FRONTEND=noninteractive" + cmd += " APT_LISTCHANGES_FRONTEND=none" + cmd += " apt dist-upgrade " + cmd += " --quiet -o=Dpkg::Use-Pty=0 --fix-broken --dry-run" + cmd += " | grep -q 'ynh-deps'" + + logger.info("Simulating upgrade...") + if os.system(cmd) == 0: + # FIXME: i18n once this is stable? + raise YunohostError( + "The upgrade cannot be completed, because some app dependencies would need to be removed?", + raw_msg=True, + ) + + postupgradecmds = f"apt-mark auto {' '.join(basephp74packages_to_install)}\n" + postupgradecmds += "rm -f /usr/sbin/policy-rc.d\n" + postupgradecmds += "echo 'Restarting nginx...' >&2\n" + postupgradecmds += "systemctl restart nginx\n" + + tools_upgrade(target="system", postupgradecmds=postupgradecmds) + + def debian_major_version(self): + # The python module "platform" and lsb_release are not reliable because + # on some setup, they may still return Release=9 even after upgrading to + # buster ... (Apparently this is related to OVH overriding some stuff + # with /etc/lsb-release for instance -_-) + # Instead, we rely on /etc/os-release which should be the raw info from + # the distribution... + 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_ynh_package_version("yunohost")["version"].split(".")[0]) + + def check_assertions(self): + + # Be on buster (10.x) and yunohost 4.x + # NB : we do both check to cover situations where the upgrade crashed + # in the middle and debian version could be > 9.x but yunohost package + # would still be in 3.x... + if ( + not self.debian_major_version() == N_CURRENT_DEBIAN + and not self.yunohost_major_version() == N_CURRENT_YUNOHOST + ): + raise YunohostError("migration_0021_not_buster") + + # Have > 1 Go free space on /var/ ? + if free_space_in_directory("/var/") / (1024 ** 3) < 1.0: + raise YunohostError("migration_0021_not_enough_free_space") + + # Check system is up to date + # (but we don't if 'bullseye' is already in the sources.list ... + # which means maybe a previous upgrade crashed and we're re-running it) + if " bullseye " not in read_file("/etc/apt/sources.list"): + tools_update(target="system") + upgradable_system_packages = list(_list_upgradable_apt_packages()) + if upgradable_system_packages: + raise YunohostError("migration_0021_system_not_fully_up_to_date") + + @property + def disclaimer(self): + + # Avoid having a super long disclaimer + uncessary check if we ain't + # on buster / yunohost 4.x anymore + # NB : we do both check to cover situations where the upgrade crashed + # in the middle and debian version could be >= 10.x but yunohost package + # would still be in 4.x... + if ( + not self.debian_major_version() == N_CURRENT_DEBIAN + and not self.yunohost_major_version() == N_CURRENT_YUNOHOST + ): + return None + + # Get list of problematic apps ? I.e. not official or community+working + problematic_apps = unstable_apps() + problematic_apps = "".join(["\n - " + app for app in problematic_apps]) + + # Manually modified files ? (c.f. yunohost service regen-conf) + modified_files = manually_modified_files() + modified_files = "".join(["\n - " + f for f in modified_files]) + + message = m18n.n("migration_0021_general_warning") + + # FIXME: re-enable this message with updated topic link once we release the migration as stable + # message = ( + # "N.B.: This migration has been tested by the community over the last few months but has only been declared stable recently. If your server hosts critical services and if you are not too confident with debugging possible issues, we recommend you to wait a little bit more while we gather more feedback and polish things up. If on the other hand you are relatively confident with debugging small issues that may arise, you are encouraged to run this migration ;)! You can read about remaining known issues and feedback from the community here: https://forum.yunohost.org/t/12195\n\n" + # + message + # ) + + if problematic_apps: + message += "\n\n" + m18n.n( + "migration_0021_problematic_apps_warning", + problematic_apps=problematic_apps, + ) + + if modified_files: + message += "\n\n" + m18n.n( + "migration_0021_modified_files", manually_modified_files=modified_files + ) + + return message + + def patch_apt_sources_list(self): + + sources_list = glob.glob("/etc/apt/sources.list.d/*.list") + sources_list.append("/etc/apt/sources.list") + + # This : + # - replace single 'buster' occurence by 'bulleye' + # - comments lines containing "backports" + # - replace 'buster/updates' by 'bullseye/updates' (or same with -) + # Special note about the security suite: + # https://www.debian.org/releases/bullseye/amd64/release-notes/ch-information.en.html#security-archive + for f in sources_list: + command = ( + f"sed -i {f} " + "-e 's@ buster @ bullseye @g' " + "-e '/backports/ s@^#*@#@' " + "-e 's@ buster/updates @ bullseye-security @g' " + "-e 's@ buster-@ bullseye-@g' " + ) + os.system(command) + + def get_apps_equivs_packages(self): + + command = ( + "dpkg --get-selections" + " | grep -v deinstall" + " | awk '{print $1}'" + " | { grep 'ynh-deps$' || true; }" + ) + + output = check_output(command) + + return output.split("\n") if output else [] + + def hold(self, packages): + for package in packages: + os.system(f"apt-mark hold {package}") + + def unhold(self, packages): + for package in packages: + os.system(f"apt-mark unhold {package}") + + def apt_install(self, cmd): + def is_relevant(line): + return "Reading database ..." not in line.rstrip() + + callbacks = ( + lambda l: logger.info("+ " + l.rstrip() + "\r") + if _apt_log_line_is_relevant(l) + else logger.debug(l.rstrip() + "\r"), + lambda l: logger.warning(l.rstrip()) + if _apt_log_line_is_relevant(l) + else logger.debug(l.rstrip()), + ) + + cmd = ( + "LC_ALL=C DEBIAN_FRONTEND=noninteractive APT_LISTCHANGES_FRONTEND=none apt install --quiet -o=Dpkg::Use-Pty=0 --fix-broken --assume-yes " + + cmd + ) + + logger.debug("Running: %s" % cmd) + + return call_async_output(cmd, callbacks, shell=True) + + def patch_yunohost_conflicts(self): + # + # This is a super dirty hack to remove the conflicts from yunohost's debian/control file + # Those conflicts are there to prevent mistakenly upgrading critical packages + # such as dovecot, postfix, nginx, openssl, etc... usually related to mistakenly + # using backports etc. + # + # The hack consists in savagely removing the conflicts directly in /var/lib/dpkg/status + # + + # We only patch the conflict if we're on yunohost 4.x + if self.yunohost_major_version() != N_CURRENT_YUNOHOST: + return + + conflicts = check_output("dpkg-query -s yunohost | grep '^Conflicts:'").strip() + if conflicts: + # We want to keep conflicting with apache/bind9 tho + new_conflicts = "Conflicts: apache2, bind9" + + command = ( + f"sed -i /var/lib/dpkg/status -e 's@{conflicts}@{new_conflicts}@g'" + ) + logger.debug(f"Running: {command}") + os.system(command) From d07cf83d93086d57060d4afb4284df45326f3bae Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 19 Jan 2022 20:44:51 +0100 Subject: [PATCH 1102/1155] migrate_to_bullseye: tweaks to point to the current beta-testing forum thread --- src/yunohost/app.py | 3 ++- src/yunohost/data_migrations/0021_migrate_to_bullseye.py | 9 +++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index d6bb5d92f..997403a07 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2502,13 +2502,14 @@ def is_true(arg): def unstable_apps(): output = [] + deprecated_apps = ["mailman"] for infos in app_list(full=True)["apps"]: if not infos.get("from_catalog") or infos.get("from_catalog").get("state") in [ "inprogress", "notworking", - ]: + ] or infos["id"] in deprecated_apps: output.append(infos["id"]) return output diff --git a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py index eace6ca2e..77797c63f 100644 --- a/src/yunohost/data_migrations/0021_migrate_to_bullseye.py +++ b/src/yunohost/data_migrations/0021_migrate_to_bullseye.py @@ -116,7 +116,6 @@ class MyMigration(Migration): "mariadb-common --reinstall -o Dpkg::Options::='--force-confmiss'" ) if ret != 0: - # FIXME: i18n once this is stable? raise YunohostError("Failed to reinstall mariadb-common ?", raw_msg=True) # @@ -228,7 +227,6 @@ class MyMigration(Migration): "-o Dpkg::Options::='--force-confmiss'" ) if ret != 0: - # FIXME: i18n once this is stable? raise YunohostError( "Failed to force the install of php dependencies ?", raw_msg=True ) @@ -254,7 +252,6 @@ class MyMigration(Migration): logger.info("Simulating upgrade...") if os.system(cmd) == 0: - # FIXME: i18n once this is stable? raise YunohostError( "The upgrade cannot be completed, because some app dependencies would need to be removed?", raw_msg=True, @@ -332,7 +329,11 @@ class MyMigration(Migration): message = m18n.n("migration_0021_general_warning") - # FIXME: re-enable this message with updated topic link once we release the migration as stable + # FIXME: update this message with updated topic link once we release the migration as stable + message = ( + "N.B.: **THIS MIGRATION IS STILL IN BETA-STAGE** ! If your server hosts critical services and if you are not too confident with debugging possible issues, we recommend you to wait a little bit more while we gather more feedback and polish things up. If on the other hand you are relatively confident with debugging small issues that may arise, you are encouraged to run this migration ;)! You can read and share feedbacks on this forum thread: https://forum.yunohost.org/t/18531\n\n" + + message + ) # message = ( # "N.B.: This migration has been tested by the community over the last few months but has only been declared stable recently. If your server hosts critical services and if you are not too confident with debugging possible issues, we recommend you to wait a little bit more while we gather more feedback and polish things up. If on the other hand you are relatively confident with debugging small issues that may arise, you are encouraged to run this migration ;)! You can read about remaining known issues and feedback from the community here: https://forum.yunohost.org/t/12195\n\n" # + message From 61a01f5c813f0bb6b1f8f00ad8fa584a966aa27b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 19 Jan 2022 20:47:41 +0100 Subject: [PATCH 1103/1155] Update changelog for 4.4.0 --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index 066fcbe9d..900ed7beb 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +yunohost (4.4.0) testing; urgency=low + + - [enh] Add buster->bullseye migration + + -- Alexandre Aubin Wed, 19 Jan 2022 20:45:22 +0100 + yunohost (4.3.6.2) stable; urgency=low - [fix] apt helpers: fix bug when var is empty... (7920cc62) From 140e50253fac0d3c9aa6fcab9e392a462c914e98 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 19 Jan 2022 20:53:27 +0100 Subject: [PATCH 1104/1155] Update changelog for 11.0.2 --- debian/changelog | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/debian/changelog b/debian/changelog index c1c60e69c..cbc6cfc99 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,16 +1,17 @@ -yunohost (11.0.1~alpha) unstable; urgency=low +yunohost (11.0.2) testing; urgency=low - - [mod] Various tweaks for Python 3.9, PHP 7.4, and other changes related to Buster->Bullseye ecosystem - - [mod] quality: Rework repository code architecture ([#1377](https://github.com/YunoHost/yunohost/pull/1377)) - - [mod] quality: Rework where yunohost files are deployed (yunohost now a proper python lib with files in /usr/lib/python3/dist-packages/yunohost/, and other files are in /usr/share/yunohost) ([#1377](https://github.com/YunoHost/yunohost/pull/1377)) + - [mod] Various tweaks for Python 3.9, PHP 7.4, PostgreSQL 13, and other changes related to Buster->Bullseye ecosystem - [mod] debian: Moved mysql, php, and metronome from Depends to Recommends ([#1369](https://github.com/YunoHost/yunohost/pull/1369)) - - [mod] apt: Add sury by default ([#1369](https://github.com/YunoHost/yunohost/pull/1369)) + - [mod] apt: **Add sury by default** ([#1369](https://github.com/YunoHost/yunohost/pull/1369)) + - [enh] mysql: **Drop super old mysql config, now rely on Debian default** ([44c972f...144126f](https://github.com/YunoHost/yunohost/compare/44c972f2dd65...144126f56a3d)) - [enh] regenconf/helpers: Better integration for postgresql ([#1369](https://github.com/YunoHost/yunohost/pull/1369)) - - [enh] regenconf: Store regenconf cache in /var/cache/yunohost/regenconf instead of /home/yunohost.conf (00d535a6) - - [enh] mysql: Drop super old mysql config, now rely on Debian default's one ([44c972f...144126f](https://github.com/YunoHost/yunohost/compare/44c972f2dd65...144126f56a3d)) - - [enh] upgrade: Try to implement a smarter self-upgrade mechanism to prevent/limit API downtime and related UX issues ([#1374](https://github.com/YunoHost/yunohost/pull/1374)) + - [mod] quality: **Rework repository code architecture** ([#1377](https://github.com/YunoHost/yunohost/pull/1377)) + - [mod] quality: **Rework where yunohost files are deployed** (yunohost now a much closer to a python lib with files in /usr/lib/python3/dist-packages/yunohost/, and other "common" files are in /usr/share/yunohost) ([#1377](https://github.com/YunoHost/yunohost/pull/1377)) + - [enh] upgrade: Try to implement **a smarter self-upgrade mechanism to prevent/limit API downtime and related UX issues** ([#1374](https://github.com/YunoHost/yunohost/pull/1374)) + - [mod] regenconf: store tmp files in /var/cache/yunohost/ instead of the misleading /home/yunohost.conf folder (00d535a6) + - [mod] dyndns: rewrite tsig keygen + nsupdate using full python, now that dnssec-keygen doesnt support hmacsha512 anymore (63a84f53) - [mod] app: During app scripts (and all stuff run in hook_exec), do not inject the HOME variable if it exists. This aims to prevent inconsistencies between CLI (where HOME usually is defined) and API (where HOME doesnt exists) (f43e567b) - - [mod] quality: Cleanup legacy stuff + - [mod] quality: **Drop legacy commands or arguments** listed below - Drop `--other_vars` options in ynh_add_fail2ban_config and systemd_config helpers - Drop deprecated/superold `ynh_bind_or_cp`, `ynh_mkdir_tmp`, `ynh_get_plain_key` helpers - Drop obsolete `yunohost-reset-ldap-password` command @@ -18,12 +19,12 @@ yunohost (11.0.1~alpha) unstable; urgency=low - Drop deprecated `yunohost service regen-conf` command (see `tools regen-conf` instead) - Drop deprecated `yunohost app fetchlist` command - Drop obsolete `yunohost app add/remove/clearaccess` commands - - Drop depcreated `--list` and `--filter` options in `yunohost app list` + - Drop deprecated `--installed` and `--filter` options in `yunohost app list` - Drop deprecated `--apps` and `--system` options in `yunohost tools update/upgrade` (no double dashes anymore) - Drop deprecated `--status` and `--log_type` options in `yunohost service add` - Drop deprecated `--mail` option in `yunohost user create` - -- Alexandre Aubin Fri, 05 Feb 2021 00:02:38 +0100 + -- Alexandre Aubin Wed, 19 Jan 2022 20:52:39 +0100 yunohost (4.4.0) testing; urgency=low From 30983a4efd89585b778257df695fb636616876bf Mon Sep 17 00:00:00 2001 From: Tagada <36127788+Tagadda@users.noreply.github.com> Date: Sat, 8 Jan 2022 00:12:45 +0100 Subject: [PATCH 1105/1155] [enh] Manage default applications with DomainConfigPanel [enh] makedefault with domain_config_set() [enh] Allow webadmin to reverse makedefault [legacy] translate legacy redirections to new domain config --- locales/en.json | 1 + share/actionsmap.yml | 4 +++ share/config_domain.toml | 5 ++++ src/app.py | 61 ++++++++++++++++++++-------------------- src/domain.py | 15 ++++++++++ src/utils/config.py | 19 +++++++++++++ src/utils/legacy.py | 46 ++++++++++++++++++++++++++++++ 7 files changed, 120 insertions(+), 31 deletions(-) diff --git a/locales/en.json b/locales/en.json index 91db42cb5..72aca192f 100644 --- a/locales/en.json +++ b/locales/en.json @@ -310,6 +310,7 @@ "domain_config_auth_key": "Authentication key", "domain_config_auth_secret": "Authentication secret", "domain_config_auth_token": "Authentication token", + "domain_config_default_app": "Default app", "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/share/actionsmap.yml b/share/actionsmap.yml index 9eee48716..ce395942f 100644 --- a/share/actionsmap.yml +++ b/share/actionsmap.yml @@ -896,6 +896,10 @@ app: -d: full: --domain help: Specific domain to put app on (the app domain by default) + -u: + full: --undo + help: Undo redirection + action: store_true ### app_ssowatconf() ssowatconf: diff --git a/share/config_domain.toml b/share/config_domain.toml index 93551458b..b0131f1c1 100644 --- a/share/config_domain.toml +++ b/share/config_domain.toml @@ -11,6 +11,11 @@ i18n = "domain_config" # [feature] + [feature.app] + [feature.app.default_app] + type = "app" + filters = ["is_webapp"] + default = "_none" [feature.mail] #services = ['postfix', 'dovecot'] diff --git a/src/app.py b/src/app.py index af13765e3..beee7799b 100644 --- a/src/app.py +++ b/src/app.py @@ -117,6 +117,7 @@ def app_info(app, full=False): Get info for a specific app """ from yunohost.permission import user_permission_list + from yunohost.domain import domain_config_get _assert_is_installed(app) @@ -153,6 +154,9 @@ def app_info(app, full=False): ret["is_webapp"] = "domain" in settings and "path" in settings + if ret["is_webapp"]: + ret["is_default"] = domain_config_get(settings["domain"], "feature.app.default_app") == app + ret["supports_change_url"] = os.path.exists( os.path.join(setting_path, "scripts", "change_url") ) @@ -989,6 +993,7 @@ def app_remove(operation_logger, app, purge=False): permission_delete, permission_sync_to_user, ) + from yunohost.domain import domain_list, domain_config_get, domain_config_set if not _is_installed(app): raise YunohostValidationError( @@ -1048,12 +1053,16 @@ def app_remove(operation_logger, app, purge=False): hook_remove(app) + for domain in domain_list()["domains"]: + if (domain_config_get(domain, "feature.app.default_app") == app): + domain_config_set(domain, "feature.app.default_app", "_none") + permission_sync_to_user() _assert_system_is_sane_for_app(manifest, "post") @is_unit_operation() -def app_makedefault(operation_logger, app, domain=None): +def app_makedefault(operation_logger, app, domain=None, undo=False): """ Redirect domain root to an app @@ -1062,11 +1071,10 @@ def app_makedefault(operation_logger, app, domain=None): domain """ - from yunohost.domain import _assert_domain_exists + from yunohost.domain import _assert_domain_exists, domain_config_set app_settings = _get_app_settings(app) app_domain = app_settings["domain"] - app_path = app_settings["path"] if domain is None: domain = app_domain @@ -1075,36 +1083,12 @@ def app_makedefault(operation_logger, app, domain=None): operation_logger.related_to.append(("domain", domain)) - if "/" in app_map(raw=True)[domain]: - raise YunohostValidationError( - "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 - # default app or idk... - if not os.path.exists("/etc/ssowat/conf.json.persistent"): - ssowat_conf = {} + if undo: + domain_config_set(domain, 'feature.app.default_app', "_none") 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 - - write_to_json( - "/etc/ssowat/conf.json.persistent", ssowat_conf, sort_keys=True, indent=4 - ) - chmod("/etc/ssowat/conf.json.persistent", 0o644) - - logger.success(m18n.n("ssowat_conf_updated")) + domain_config_set(domain, 'feature.app.default_app', app) def app_setting(app, key, value=None, delete=False): @@ -1303,7 +1287,7 @@ def app_ssowatconf(): """ - from yunohost.domain import domain_list, _get_maindomain + from yunohost.domain import domain_list, _get_maindomain, domain_config_get from yunohost.permission import user_permission_list main_domain = _get_maindomain() @@ -1341,6 +1325,21 @@ def app_ssowatconf(): redirected_urls.update(app_settings.get("redirected_urls", {})) redirected_regex.update(app_settings.get("redirected_regex", {})) + from .utils.legacy import translate_legacy_default_app_in_ssowant_conf_json_persistent + + translate_legacy_default_app_in_ssowant_conf_json_persistent() + + for domain in domains: + default_app = domain_config_get(domain, "feature.app.default_app") + if default_app != "_none" and _is_installed(default_app): + app_settings = _get_app_settings(default_app) + app_domain = app_settings["domain"] + app_path = app_settings["path"] + + # Prevent infinite redirect loop... + if domain + "/" != app_domain + app_path: + redirected_urls[domain + "/"] = app_domain + app_path + # New permission system for perm_name, perm_info in all_permissions.items(): diff --git a/src/domain.py b/src/domain.py index 6fd1724b4..f2e7fd7f4 100644 --- a/src/domain.py +++ b/src/domain.py @@ -456,6 +456,21 @@ class DomainConfigPanel(ConfigPanel): save_path_tpl = f"{DOMAIN_SETTINGS_DIR}/{{entity}}.yml" save_mode = "diff" + def _apply(self): + if ("default_app" in self.future_values and self.future_values["default_app"] != self.values["default_app"]): + from yunohost.app import app_ssowatconf, app_map + + if "/" in app_map(raw=True)[self.entity]: + raise YunohostValidationError( + "app_make_default_location_already_used", + app=self.future_values["default_app"], + domain=self.entity, + other_app=app_map(raw=True)[self.entity]["/"]["id"], + ) + + super()._apply() + app_ssowatconf() + def _get_toml(self): from yunohost.dns import _get_registrar_config_section diff --git a/src/utils/config.py b/src/utils/config.py index 99a002404..f3c8f8177 100644 --- a/src/utils/config.py +++ b/src/utils/config.py @@ -438,6 +438,7 @@ class ConfigPanel: "step", "accept", "redact", + "filters", ], "defaults": {}, }, @@ -703,6 +704,7 @@ class Question: self.ask = question.get("ask", {"en": self.name}) self.help = question.get("help") self.redact = question.get("redact", False) + self.filters = question.get("filters", []) # .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 @@ -1123,6 +1125,22 @@ class DomainQuestion(Question): return value +class AppQuestion(Question): + argument_type = "app" + + def __init__( + self, question, context: Mapping[str, Any] = {}, hooks: Dict[str, Callable] = {} + ): + from yunohost.app import app_list + + super().__init__(question, context, hooks) + + apps = app_list(full=True)["apps"] + for _filter in self.filters: + print(_filter) + apps = [ app for app in apps if _filter in app and app[_filter] ] + + self.choices = ["_none"] + [app['id'] for app in apps] class UserQuestion(Question): argument_type = "user" @@ -1315,6 +1333,7 @@ ARGUMENTS_TYPE_PARSERS = { "alert": DisplayTextQuestion, "markdown": DisplayTextQuestion, "file": FileQuestion, + "app": AppQuestion, } diff --git a/src/utils/legacy.py b/src/utils/legacy.py index 306fcc87f..7a8a4540a 100644 --- a/src/utils/legacy.py +++ b/src/utils/legacy.py @@ -68,6 +68,52 @@ def legacy_permission_label(app, permission_type): ) +def translate_legacy_default_app_in_ssowant_conf_json_persistent(): + from yunohost.app import app_list + from yunohost.domain import domain_config_set + + persistent_file_name = "/etc/ssowat/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) + + if "redirected_urls" not in persistent: + return + + redirected_urls = persistent["redirected_urls"] + + if not any(from_url.count('/') == 1 and from_url.endswith('/') for from_url in redirected_urls): + return + + apps = app_list()['apps'] + + if not any('domain_path' in app and app['domain_path'] in redirected_urls.values() for app in apps): + return + + for from_url, dest_url in redirected_urls.items(): + # Not a root domain, skip + if from_url.count('/') != 1 or not from_url.endswith('/'): + continue + for app in apps: + if 'domain_path' not in app or app['domain_path'] is not dest_url: + continue + domain_config_set(from_url.strip('/'), "feature.app.default", app['id']) + del redirected_urls[from_url] + + persistent["redirected_urls"] = redirected_urls + + write_to_json(persistent_file_name, persistent, sort_keys=True, indent=4) + + logger.warning( + "YunoHost automatically translated some legacy redirections in /etc/ssowat/conf.json.persistent to match the new default application using domain configuration" + ) + + LEGACY_PHP_VERSION_REPLACEMENTS = [ ("/etc/php5", "/etc/php/7.4"), ("/etc/php/7.0", "/etc/php/7.4"), From 9ef3a1bfff71570716a6775b58818cb4b8fd4ce5 Mon Sep 17 00:00:00 2001 From: Tagadda <36127788+Tagadda@users.noreply.github.com> Date: Wed, 19 Jan 2022 20:10:28 +0000 Subject: [PATCH 1106/1155] [enh] Add SNI support to postfix and dovecot --- conf/dovecot/dovecot.conf | 5 +++++ conf/postfix/main.cf | 7 +++++-- conf/postfix/sni | 2 ++ hooks/conf_regen/19-postfix | 3 +++ hooks/conf_regen/25-dovecot | 1 + 5 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 conf/postfix/sni diff --git a/conf/dovecot/dovecot.conf b/conf/dovecot/dovecot.conf index 273bd45dc..72fd71c4d 100644 --- a/conf/dovecot/dovecot.conf +++ b/conf/dovecot/dovecot.conf @@ -21,6 +21,11 @@ ssl = required ssl_cert = /path/to/dhparam ssl_dh = Date: Thu, 20 Jan 2022 11:46:01 +0100 Subject: [PATCH 1107/1155] [mdns] Set the StandardOutput to journal --- conf/mdns/yunomdns.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/mdns/yunomdns.service b/conf/mdns/yunomdns.service index c1f1b7b06..1102f18f6 100644 --- a/conf/mdns/yunomdns.service +++ b/conf/mdns/yunomdns.service @@ -8,7 +8,7 @@ Group=mdns Type=simple Environment=PYTHONUNBUFFERED=1 ExecStart=/usr/bin/yunomdns -StandardOutput=syslog +StandardOutput=journal [Install] WantedBy=default.target From d6bbdfa5f49146ae577ee829e9eb39f8b4d458ae Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 20 Jan 2022 19:22:40 +0100 Subject: [PATCH 1108/1155] fix tox --- tox.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index 3e3fcbf2e..b037df0b8 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ deps = py39-mypy: mypy >= 0.900 commands = py39-lint: flake8 src doc data tests --ignore E402,E501,E203,W503 --exclude src/vendor - py39-invalidcode: flake8 src data --exclude src/tests,src/vendor --select F,E722,W605 - py39-black-check: black --check --diff src doc data tests - py39-black-run: black src doc data tests + py39-invalidcode: flake8 src bin maintenance --exclude src/tests,src/vendor --select F,E722,W605 + py39-black-check: black --check --diff bin src doc maintenance tests + py39-black-run: bin src doc maintenance tests py39-mypy: mypy --ignore-missing-import --install-types --non-interactive --follow-imports silent src/ --exclude (acme_tiny|migrations) From 7ee5565a83e0e202e275b6055d8d80de6463239a Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 20 Jan 2022 19:23:45 +0100 Subject: [PATCH 1109/1155] another one --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index b037df0b8..1ebf47ad1 100644 --- a/tox.ini +++ b/tox.ini @@ -8,7 +8,7 @@ deps = py39-black-{run,check}: black py39-mypy: mypy >= 0.900 commands = - py39-lint: flake8 src doc data tests --ignore E402,E501,E203,W503 --exclude src/vendor + py39-lint: flake8 src doc maintenance tests --ignore E402,E501,E203,W503 --exclude src/vendor py39-invalidcode: flake8 src bin maintenance --exclude src/tests,src/vendor --select F,E722,W605 py39-black-check: black --check --diff bin src doc maintenance tests py39-black-run: bin src doc maintenance tests From cf971362377fb25e5eeceecc4d896b13fcf8e435 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 20 Jan 2022 19:27:00 +0100 Subject: [PATCH 1110/1155] fix autofix locale format path --- maintenance/autofix_locale_format.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/maintenance/autofix_locale_format.py b/maintenance/autofix_locale_format.py index 400704ddb..6eb78b497 100644 --- a/maintenance/autofix_locale_format.py +++ b/maintenance/autofix_locale_format.py @@ -89,11 +89,11 @@ def autofix_orthotypography_and_standardized_words(): def reformat(lang, transformations): - locale = open(f"../locales/{lang}.json").read() + locale = open(f"{LOCALE_FOLDER}{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"{LOCALE_FOLDER}{lang}.json", "w").write(locale) ###################################################### From 3779b9203a5d8cef712f95f32493d9a4bfc7d234 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 20 Jan 2022 19:32:13 +0100 Subject: [PATCH 1111/1155] oupsi add black again --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 1ebf47ad1..dc2c52074 100644 --- a/tox.ini +++ b/tox.ini @@ -11,5 +11,5 @@ commands = py39-lint: flake8 src doc maintenance tests --ignore E402,E501,E203,W503 --exclude src/vendor py39-invalidcode: flake8 src bin maintenance --exclude src/tests,src/vendor --select F,E722,W605 py39-black-check: black --check --diff bin src doc maintenance tests - py39-black-run: bin src doc maintenance tests + py39-black-run: black bin src doc maintenance tests py39-mypy: mypy --ignore-missing-import --install-types --non-interactive --follow-imports silent src/ --exclude (acme_tiny|migrations) From b9060da7508e1e3ec6d5624aca331a878a95f2c4 Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Thu, 20 Jan 2022 18:43:33 +0000 Subject: [PATCH 1112/1155] [CI] Format code with Black --- maintenance/autofix_locale_format.py | 15 ++++++++------- src/__init__.py | 2 +- src/app.py | 15 ++++++++++----- src/app_catalog.py | 17 +++++++++++++---- src/backup.py | 7 +++---- src/certificate.py | 9 +++++---- src/diagnosers/10-ip.py | 4 +--- src/diagnosis.py | 18 +++++++++++------- src/domain.py | 4 +--- src/dyndns.py | 17 +++++++++-------- src/migrations/0021_migrate_to_bullseye.py | 4 ++-- src/permission.py | 7 ++----- src/regenconf.py | 4 +--- src/service.py | 19 ++++++++++++------- src/settings.py | 16 +++++++--------- src/tools.py | 10 ++++------ src/user.py | 8 ++------ src/utils/error.py | 1 - src/utils/legacy.py | 21 ++++++++++++++------- 19 files changed, 106 insertions(+), 92 deletions(-) diff --git a/maintenance/autofix_locale_format.py b/maintenance/autofix_locale_format.py index 6eb78b497..1c56ea386 100644 --- a/maintenance/autofix_locale_format.py +++ b/maintenance/autofix_locale_format.py @@ -60,18 +60,20 @@ def autofix_i18n_placeholders(): 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): - raise Exception("""\n + raise Exception( + """\n ========================== Format inconsistency for string {key} in {locale_file}:" en.json -> {string} {locale_file} -> {translated_string} Please fix it manually ! """.format( - key=key, - string=string.encode("utf-8"), - locale_file=locale_file, - translated_string=this_locale[key].encode("utf-8"), - )) + key=key, + string=string.encode("utf-8"), + locale_file=locale_file, + translated_string=this_locale[key].encode("utf-8"), + ) + ) if fixed_stuff: json.dump( @@ -86,7 +88,6 @@ Please fix it manually ! def autofix_orthotypography_and_standardized_words(): - def reformat(lang, transformations): locale = open(f"{LOCALE_FOLDER}{lang}.json").read() diff --git a/src/__init__.py b/src/__init__.py index b9dcd93d9..608917185 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -28,7 +28,7 @@ def cli(debug, quiet, output_as, timeout, args, parser): locales_dir="/usr/share/yunohost/locales/", output_as=output_as, timeout=timeout, - top_parser=parser + top_parser=parser, ) sys.exit(ret) diff --git a/src/app.py b/src/app.py index af13765e3..e03d94317 100644 --- a/src/app.py +++ b/src/app.py @@ -1425,7 +1425,7 @@ def app_action_run(operation_logger, app, action, args=None): actions = {x["id"]: x for x in actions} if action not in actions: - available_actions = ", ".join(actions.keys()), + available_actions = (", ".join(actions.keys()),) raise YunohostValidationError( f"action '{action}' not available for app '{app}', available actions are: {available_actions}", raw_msg=True, @@ -2416,10 +2416,15 @@ def unstable_apps(): for infos in app_list(full=True)["apps"]: - if not infos.get("from_catalog") or infos.get("from_catalog").get("state") in [ - "inprogress", - "notworking", - ] or infos["id"] in deprecated_apps: + if ( + not infos.get("from_catalog") + or infos.get("from_catalog").get("state") + in [ + "inprogress", + "notworking", + ] + or infos["id"] in deprecated_apps + ): output.append(infos["id"]) return output diff --git a/src/app_catalog.py b/src/app_catalog.py index b2b35b8c3..5ae8ef30b 100644 --- a/src/app_catalog.py +++ b/src/app_catalog.py @@ -103,7 +103,9 @@ def _initialize_apps_catalog_system(): ) write_to_yaml(APPS_CATALOG_CONF, default_apps_catalog_list) except Exception as e: - raise YunohostError(f"Could not initialize the apps catalog system... : {e}", raw_msg=True) + raise YunohostError( + f"Could not initialize the apps catalog system... : {e}", raw_msg=True + ) logger.success(m18n.n("apps_catalog_init_success")) @@ -119,7 +121,9 @@ def _read_apps_catalog_list(): # by returning [] if list_ is None return list_ if list_ else [] except Exception as e: - raise YunohostError(f"Could not read the apps_catalog list ... : {e}", raw_msg=True) + raise YunohostError( + f"Could not read the apps_catalog list ... : {e}", raw_msg=True + ) def _actual_apps_catalog_api_url(base_url): @@ -172,7 +176,10 @@ def _update_apps_catalog(): try: write_to_json(cache_file, apps_catalog_content) except Exception as e: - raise YunohostError(f"Unable to write cache data for {apps_catalog_id} apps_catalog : {e}", raw_msg=True) + raise YunohostError( + f"Unable to write cache data for {apps_catalog_id} apps_catalog : {e}", + raw_msg=True, + ) logger.success(m18n.n("apps_catalog_update_success")) @@ -220,7 +227,9 @@ def _load_apps_catalog(): # in which case we keep only the first one found) if app in merged_catalog["apps"]: other_catalog = merged_catalog["apps"][app]["repository"] - logger.warning(f"Duplicate app {app} found between apps catalog {apps_catalog_id} and {other_catalog}") + logger.warning( + f"Duplicate app {app} found between apps catalog {apps_catalog_id} and {other_catalog}" + ) continue info["repository"] = apps_catalog_id diff --git a/src/backup.py b/src/backup.py index 57e667d8d..bba60b895 100644 --- a/src/backup.py +++ b/src/backup.py @@ -866,7 +866,7 @@ class RestoreManager: from_version = self.info.get("from_yunohost_version", "") # Remove any '~foobar' in the version ... c.f ~alpha, ~beta version during # early dev for next debian version - from_version = re.sub(r'~\w+', '', from_version) + from_version = re.sub(r"~\w+", "", from_version) if not from_version or version.parse(from_version) < version.parse("4.2.0"): raise YunohostValidationError("restore_backup_too_old") @@ -2403,10 +2403,9 @@ def backup_list(with_info=False, human_readable=False): logger.warning(str(e)) except Exception: import traceback + trace_ = "\n" + traceback.format_exc() - logger.warning( - f"Could not check infos for archive {archive}: {trace_}" - ) + logger.warning(f"Could not check infos for archive {archive}: {trace_}") archives = d diff --git a/src/certificate.py b/src/certificate.py index a681d106b..2ad294605 100644 --- a/src/certificate.py +++ b/src/certificate.py @@ -415,9 +415,7 @@ def certificate_renew( traceback.print_exc(file=stack) msg = f"Certificate renewing for {domain} failed!" if no_checks: - msg += ( - f"\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 {domain}." - ) + msg += f"\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 {domain}." logger.error(msg) operation_logger.error(msg) logger.error(stack.getvalue()) @@ -788,7 +786,10 @@ def _enable_certificate(domain, new_cert_folder): for service in ("postfix", "dovecot", "metronome"): # Ugly trick to not restart metronome if it's not installed - if service == "metronome" and os.system("dpkg --list | grep -q 'ii *metronome'") != 0: + if ( + service == "metronome" + and os.system("dpkg --list | grep -q 'ii *metronome'") != 0 + ): continue _run_service_command("restart", service) diff --git a/src/diagnosers/10-ip.py b/src/diagnosers/10-ip.py index 5e49ae927..247c486fc 100644 --- a/src/diagnosers/10-ip.py +++ b/src/diagnosers/10-ip.py @@ -155,9 +155,7 @@ class MyDiagnoser(Diagnoser): 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/conf/dnsmasq/plain/resolv.dnsmasq.conf" - ) + resolver_file = "/usr/share/yunohost/conf/dnsmasq/plain/resolv.dnsmasq.conf" resolvers = [ r.split(" ")[1] for r in read_file(resolver_file).split("\n") diff --git a/src/diagnosis.py b/src/diagnosis.py index 2486887b9..b44028d29 100644 --- a/src/diagnosis.py +++ b/src/diagnosis.py @@ -419,10 +419,7 @@ class Diagnoser: def diagnose(self, force=False): - if ( - not force - and self.cached_time_ago() < self.cache_duration - ): + if not force and self.cached_time_ago() < self.cache_duration: logger.debug(f"Cache still valid : {self.cache_file}") logger.info( m18n.n("diagnosis_cache_still_valid", category=self.description) @@ -659,7 +656,9 @@ class Diagnoser: def _list_diagnosis_categories(): paths = glob.glob(os.path.dirname(__file__) + "/diagnosers/??-*.py") - names = sorted([os.path.basename(path)[: -len(".py")].split("-")[-1] for path in paths]) + names = sorted( + [os.path.basename(path)[: -len(".py")].split("-")[-1] for path in paths] + ) return names @@ -671,7 +670,10 @@ def _load_diagnoser(diagnoser_name): paths = glob.glob(os.path.dirname(__file__) + f"/diagnosers/??-{diagnoser_name}.py") if len(paths) != 1: - raise YunohostError(f"Uhoh, found several matches (or none?) for diagnoser {diagnoser_name} : {paths}", raw_msg=True) + raise YunohostError( + f"Uhoh, found several matches (or none?) for diagnoser {diagnoser_name} : {paths}", + raw_msg=True, + ) module_id = os.path.basename(paths[0][: -len(".py")]) @@ -686,7 +688,9 @@ def _load_diagnoser(diagnoser_name): traceback.print_exc() - raise YunohostError(f"Failed to load diagnoser {diagnoser_name} : {e}", raw_msg=True) + raise YunohostError( + f"Failed to load diagnoser {diagnoser_name} : {e}", raw_msg=True + ) def _email_diagnosis_issues(): diff --git a/src/domain.py b/src/domain.py index 6fd1724b4..c94dc694d 100644 --- a/src/domain.py +++ b/src/domain.py @@ -68,9 +68,7 @@ def domain_list(exclude_subdomains=False): ldap = _get_ldap_interface() result = [ entry["virtualdomain"][0] - for entry in ldap.search( - "ou=domains", "virtualdomain=*", ["virtualdomain"] - ) + for entry in ldap.search("ou=domains", "virtualdomain=*", ["virtualdomain"]) ] result_list = [] diff --git a/src/dyndns.py b/src/dyndns.py index c9da4f1be..34f3dd5dc 100644 --- a/src/dyndns.py +++ b/src/dyndns.py @@ -190,7 +190,6 @@ def dyndns_update( import dns.tsigkeyring import dns.update - # If domain is not given, try to guess it from keys available... key = None if domain is None: @@ -227,7 +226,7 @@ def dyndns_update( with open(key) as f: key = f.readline().strip().split(" ", 6)[-1] - keyring = dns.tsigkeyring.from_text({f'{domain}.': key}) + keyring = dns.tsigkeyring.from_text({f"{domain}.": key}) # Python's dns.update is similar to the old nsupdate cli tool update = dns.update.Update(zone, keyring=keyring, keyalgorithm=dns.tsig.HMAC_SHA512) @@ -300,7 +299,9 @@ def dyndns_update( # [{"name": "...", "ttl": "...", "type": "...", "value": "..."}] for records in dns_conf.values(): for record in records: - name = f"{record['name']}.{domain}." if record['name'] != "@" else f"{domain}." + name = ( + f"{record['name']}.{domain}." if record["name"] != "@" else f"{domain}." + ) update.delete(name) # Add the new records for all domain/subdomains @@ -313,9 +314,11 @@ def dyndns_update( if record["value"] == "@": record["value"] = domain record["value"] = record["value"].replace(";", r"\;") - name = f"{record['name']}.{domain}." if record['name'] != "@" else f"{domain}." + name = ( + f"{record['name']}.{domain}." if record["name"] != "@" else f"{domain}." + ) - update.add(name, record['ttl'], record['type'], record['value']) + update.add(name, record["ttl"], record["type"], record["value"]) logger.debug("Now pushing new conf to DynDNS host...") logger.debug(update) @@ -347,9 +350,7 @@ def _guess_current_dyndns_domain(): dynette...) """ - DYNDNS_KEY_REGEX = re.compile( - r".*/K(?P[^\s\+]+)\.\+165.+\.key$" - ) + DYNDNS_KEY_REGEX = re.compile(r".*/K(?P[^\s\+]+)\.\+165.+\.key$") # Retrieve the first registered domain paths = list(glob.iglob("/etc/yunohost/dyndns/K*.key")) diff --git a/src/migrations/0021_migrate_to_bullseye.py b/src/migrations/0021_migrate_to_bullseye.py index 77797c63f..f4361cb19 100644 --- a/src/migrations/0021_migrate_to_bullseye.py +++ b/src/migrations/0021_migrate_to_bullseye.py @@ -331,8 +331,8 @@ class MyMigration(Migration): # FIXME: update this message with updated topic link once we release the migration as stable message = ( - "N.B.: **THIS MIGRATION IS STILL IN BETA-STAGE** ! If your server hosts critical services and if you are not too confident with debugging possible issues, we recommend you to wait a little bit more while we gather more feedback and polish things up. If on the other hand you are relatively confident with debugging small issues that may arise, you are encouraged to run this migration ;)! You can read and share feedbacks on this forum thread: https://forum.yunohost.org/t/18531\n\n" - + message + "N.B.: **THIS MIGRATION IS STILL IN BETA-STAGE** ! If your server hosts critical services and if you are not too confident with debugging possible issues, we recommend you to wait a little bit more while we gather more feedback and polish things up. If on the other hand you are relatively confident with debugging small issues that may arise, you are encouraged to run this migration ;)! You can read and share feedbacks on this forum thread: https://forum.yunohost.org/t/18531\n\n" + + message ) # message = ( # "N.B.: This migration has been tested by the community over the last few months but has only been declared stable recently. If your server hosts critical services and if you are not too confident with debugging possible issues, we recommend you to wait a little bit more while we gather more feedback and polish things up. If on the other hand you are relatively confident with debugging small issues that may arise, you are encouraged to run this migration ;)! You can read about remaining known issues and feedback from the community here: https://forum.yunohost.org/t/12195\n\n" diff --git a/src/permission.py b/src/permission.py index 995cd34bb..2a6f6d954 100644 --- a/src/permission.py +++ b/src/permission.py @@ -406,9 +406,7 @@ def permission_create( permission = permission + ".main" # Validate uniqueness of permission in LDAP - if ldap.get_conflict( - {"cn": permission}, base_dn="ou=permission" - ): + if ldap.get_conflict({"cn": permission}, base_dn="ou=permission"): raise YunohostValidationError("permission_already_exist", permission=permission) # Get random GID @@ -678,8 +676,7 @@ def permission_sync_to_user(): new_inherited_perms = { "inheritPermission": [ - f"uid={u},ou=users,dc=yunohost,dc=org" - for u in should_be_allowed_users + f"uid={u},ou=users,dc=yunohost,dc=org" for u in should_be_allowed_users ], "memberUid": should_be_allowed_users, } diff --git a/src/regenconf.py b/src/regenconf.py index 0f855878d..5922e6832 100644 --- a/src/regenconf.py +++ b/src/regenconf.py @@ -505,9 +505,7 @@ def _calculate_hash(path): return hasher.hexdigest() except IOError as e: - logger.warning( - f"Error while calculating file '{path}' hash: {e}", exc_info=1 - ) + logger.warning(f"Error while calculating file '{path}' hash: {e}", exc_info=1) return None diff --git a/src/service.py b/src/service.py index 4da5d5546..988c7eb13 100644 --- a/src/service.py +++ b/src/service.py @@ -695,19 +695,25 @@ def _get_services(): if "log" not in services["ynh-vpnclient"]: services["ynh-vpnclient"]["log"] = ["/var/log/ynh-vpnclient.log"] - services_with_package_condition = [name for name, infos in services.items() if infos.get("ignore_if_package_is_not_installed")] + services_with_package_condition = [ + name + for name, infos in services.items() + if infos.get("ignore_if_package_is_not_installed") + ] for name in services_with_package_condition: package = services[name]["ignore_if_package_is_not_installed"] if os.system(f"dpkg --list | grep -q 'ii *{package}'") != 0: del services[name] - php_fpm_versions = check_output(r"dpkg --list | grep -P 'ii php\d.\d-fpm' | awk '{print $2}' | grep -o -P '\d.\d' || true") - php_fpm_versions = [v for v in php_fpm_versions.split('\n') if v.strip()] + php_fpm_versions = check_output( + r"dpkg --list | grep -P 'ii php\d.\d-fpm' | awk '{print $2}' | grep -o -P '\d.\d' || true" + ) + php_fpm_versions = [v for v in php_fpm_versions.split("\n") if v.strip()] for version in php_fpm_versions: services[f"php{version}-fpm"] = { "log": f"/var/log/php{version}-fpm.log", "test_conf": f"php-fpm{version} --test", # ofc the service is phpx.y-fpm but the program is php-fpmx.y because why not ... - "category": "web" + "category": "web", } # Remove legacy /var/log/daemon.log and /var/log/syslog from log entries @@ -833,7 +839,6 @@ def _get_journalctl_logs(service, number="all"): ) except Exception: import traceback + trace_ = traceback.format_exc() - return ( - f"error while get services logs from journalctl:\n{trace_}" - ) + return f"error while get services logs from journalctl:\n{trace_}" diff --git a/src/settings.py b/src/settings.py index 498e6d5cc..cec416550 100644 --- a/src/settings.py +++ b/src/settings.py @@ -341,9 +341,7 @@ def _get_settings(): _save_settings(unknown_settings, location=unknown_settings_path) _save_settings(settings) except Exception as e: - logger.warning( - f"Failed to save unknown settings (because {e}), aborting." - ) + logger.warning(f"Failed to save unknown settings (because {e}), aborting.") return settings @@ -373,12 +371,12 @@ post_change_hooks = {} def post_change_hook(setting_name): def decorator(func): - assert setting_name in DEFAULTS.keys(), ( - f"The setting {setting_name} does not exists" - ) - assert setting_name not in post_change_hooks, ( - f"You can only register one post change hook per setting (in particular for {setting_name})" - ) + assert ( + setting_name in DEFAULTS.keys() + ), f"The setting {setting_name} does not exists" + assert ( + setting_name not in post_change_hooks + ), f"You can only register one post change hook per setting (in particular for {setting_name})" post_change_hooks[setting_name] = func return func diff --git a/src/tools.py b/src/tools.py index 9ede24551..28b4457b4 100644 --- a/src/tools.py +++ b/src/tools.py @@ -536,9 +536,7 @@ def tools_upgrade(operation_logger, target=None): # Restart the API after 10 sec (at now doesn't support sub-minute times...) # We do this so that the API / webadmin still gets the proper HTTP response # It's then up to the webadmin to implement a proper UX process to wait 10 sec and then auto-fresh the webadmin - cmd = ( - "at -M now >/dev/null 2>&1 <<< \"sleep 10; systemctl restart yunohost-api\"" - ) + cmd = 'at -M now >/dev/null 2>&1 <<< "sleep 10; systemctl restart yunohost-api"' # For some reason subprocess doesn't like the redirections so we have to use bash -c explicity... subprocess.check_call(["bash", "-c", cmd]) @@ -894,9 +892,9 @@ def _get_migration_by_name(migration_name): if re.match(r"^\d+_%s\.py$" % migration_name, x) ] - assert len(migrations_found) == 1, ( - f"Unable to find migration with name {migration_name}" - ) + assert ( + len(migrations_found) == 1 + ), f"Unable to find migration with name {migration_name}" return _load_migration(migrations_found[0]) diff --git a/src/user.py b/src/user.py index c03023387..7d023fd83 100644 --- a/src/user.py +++ b/src/user.py @@ -254,9 +254,7 @@ def user_create( logger.warning(m18n.n("user_home_creation_failed", home=home), exc_info=1) try: - subprocess.check_call( - ["setfacl", "-m", "g:all_users:---", f"/home/{username}"] - ) + subprocess.check_call(["setfacl", "-m", "g:all_users:---", f"/home/{username}"]) except subprocess.CalledProcessError: logger.warning(f"Failed to protect /home/{username}", exc_info=1) @@ -986,9 +984,7 @@ def user_group_create( ldap = _get_ldap_interface() # Validate uniqueness of groupname in LDAP - conflict = ldap.get_conflict( - {"cn": groupname}, base_dn="ou=groups" - ) + conflict = ldap.get_conflict({"cn": groupname}, base_dn="ou=groups") if conflict: raise YunohostValidationError("group_already_exist", group=groupname) diff --git a/src/utils/error.py b/src/utils/error.py index aa76ba67e..a92f3bd5a 100644 --- a/src/utils/error.py +++ b/src/utils/error.py @@ -65,4 +65,3 @@ class YunohostValidationError(YunohostError): class YunohostAuthenticationError(MoulinetteAuthenticationError): pass - diff --git a/src/utils/legacy.py b/src/utils/legacy.py index 306fcc87f..910dfd5a3 100644 --- a/src/utils/legacy.py +++ b/src/utils/legacy.py @@ -116,10 +116,7 @@ def _patch_legacy_php_versions(app_folder): c = ( "sed -i " - + "".join( - f"-e 's@{p}@{r}@g' " - for p, r in LEGACY_PHP_VERSION_REPLACEMENTS - ) + + "".join(f"-e 's@{p}@{r}@g' " for p, r in LEGACY_PHP_VERSION_REPLACEMENTS) + "%s" % filename ) os.system(c) @@ -137,7 +134,11 @@ def _patch_legacy_php_versions_in_settings(app_folder): settings["phpversion"] = "7.4" # We delete these checksums otherwise the file will appear as manually modified - list_to_remove = ["checksum__etc_php_7.3_fpm_pool", "checksum__etc_php_7.0_fpm_pool", "checksum__etc_nginx_conf.d"] + list_to_remove = [ + "checksum__etc_php_7.3_fpm_pool", + "checksum__etc_php_7.0_fpm_pool", + "checksum__etc_nginx_conf.d", + ] settings = { k: v for k, v in settings.items() @@ -168,9 +169,15 @@ def _patch_legacy_helpers(app_folder): "important": False, }, # Old $1, $2 in backup/restore scripts... - "app=$2": {"only_for": ["scripts/backup", "scripts/restore"], "important": True}, + "app=$2": { + "only_for": ["scripts/backup", "scripts/restore"], + "important": True, + }, # Old $1, $2 in backup/restore scripts... - "backup_dir=$1": {"only_for": ["scripts/backup", "scripts/restore"], "important": True}, + "backup_dir=$1": { + "only_for": ["scripts/backup", "scripts/restore"], + "important": True, + }, # Old $1, $2 in backup/restore scripts... "restore_dir=$1": {"only_for": ["scripts/restore"], "important": True}, # Old $1, $2 in install scripts... From 409cd48b4242f138e64dde861e168636bbeca142 Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Thu, 20 Jan 2022 18:44:27 +0000 Subject: [PATCH 1113/1155] [CI] Reformat / remove stale translated strings --- locales/ca.json | 9 --------- locales/de.json | 11 +---------- locales/en.json | 3 +-- locales/eo.json | 9 --------- locales/es.json | 11 +---------- locales/eu.json | 9 --------- locales/fa.json | 9 --------- locales/fr.json | 11 +---------- locales/gl.json | 11 +---------- locales/it.json | 9 --------- locales/nl.json | 3 +-- locales/oc.json | 9 --------- locales/pt.json | 1 - locales/uk.json | 11 +---------- locales/zh_Hans.json | 9 --------- 15 files changed, 7 insertions(+), 118 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index 4b1c51edd..b8d7a6718 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -215,7 +215,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", - "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}...", "migrations_migration_has_failed": "La migració {id} ha fallat, cancel·lant. Error: {exception}", @@ -224,7 +223,6 @@ "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}»", - "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)", "pattern_email": "Ha de ser una adreça de correu vàlida, sense el símbol «+» (ex.: algu@domini.cat)", @@ -313,13 +311,6 @@ "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_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 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à", "unexpected_error": "Hi ha hagut un error inesperat: {error}", "unlimit": "Sense quota", diff --git a/locales/de.json b/locales/de.json index 836893eb6..c1b97104c 100644 --- a/locales/de.json +++ b/locales/de.json @@ -71,7 +71,6 @@ "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", "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 ohne '+' Symbol sein (z.B. someone@example.com)", @@ -478,7 +477,6 @@ "migrations_pending_cant_rerun": "Diese Migrationen sind immer noch anstehend und können deshalb nicht erneut durchgeführt werden: {ids}", "migrations_not_pending_cant_skip": "Diese Migrationen sind nicht anstehend und können deshalb nicht übersprungen werden: {ids}", "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.", @@ -577,15 +575,8 @@ "root_password_replaced_by_admin_password": "Ihr Root Passwort wurde durch Ihr Admin Passwort ersetzt.", "show_tile_cant_be_enabled_for_regex": "Du kannst 'show_tile' momentan 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...", "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...", "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": "Benutzer:in '{user}' ist bereits vorhanden", @@ -686,4 +677,4 @@ "global_settings_setting_security_ssh_password_authentication": "Passwort-Authentifizierung für SSH zulassen", "migration_description_0021_migrate_to_bullseye": "Upgrade des Systems auf Debian Bullseye und YunoHost 11.x", "migration_0021_general_warning": "Bitte beachten Sie, dass diese Migration ein heikler Vorgang ist. Das YunoHost-Team hat sein Bestes getan, um sie zu überprüfen und zu testen, aber die Migration könnte immer noch Teile des Systems oder seiner Anwendungen beschädigen.\n\nEs wird daher empfohlen,:\n - Führen Sie ein Backup aller kritischen Daten oder Anwendungen durch. Mehr Informationen unter https://yunohost.org/backup;\n - Haben Sie Geduld, nachdem Sie die Migration gestartet haben: Je nach Internetverbindung und Hardware kann es bis zu ein paar Stunden dauern, bis alles aktualisiert ist." -} +} \ No newline at end of file diff --git a/locales/en.json b/locales/en.json index 91db42cb5..856cbc418 100644 --- a/locales/en.json +++ b/locales/en.json @@ -528,7 +528,6 @@ "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", "password_too_simple_2": "The password needs to be at least 8 characters long and contain a digit, upper and lower characters", @@ -686,4 +685,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/locales/eo.json b/locales/eo.json index 1d98e8597..255d873d8 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -114,7 +114,6 @@ "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}\" 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!", @@ -154,8 +153,6 @@ "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} 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", "group_created": "Grupo '{group}' kreita", "group_creation_failed": "Ne povis krei la grupon '{group}': {error}", @@ -168,7 +165,6 @@ "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}' 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}'", @@ -184,10 +180,8 @@ "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}", - "packages_upgrade_failed": "Ne povis ĝisdatigi ĉiujn pakojn", "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}'\n\nLastatempaj servaj protokoloj: {logs}", "service_reloaded": "Servo '{service}' reŝargita", "system_upgraded": "Sistemo ĝisdatigita", @@ -201,7 +195,6 @@ "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}", "backup_running_hooks": "Kurado de apogaj hokoj …", @@ -251,7 +244,6 @@ "downloading": "Elŝutante …", "user_deleted": "Uzanto forigita", "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", @@ -331,7 +323,6 @@ "regenconf_file_copy_failed": "Ne povis kopii la novan agordodosieron '{new}' al '{conf}'", "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}'", "regenconf_file_manually_removed": "La dosiero de agordo '{conf}' estis forigita permane, kaj ne estos kreita", diff --git a/locales/es.json b/locales/es.json index e3a6ca87a..62831fc1b 100644 --- a/locales/es.json +++ b/locales/es.json @@ -80,7 +80,6 @@ "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}»", - "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)", "pattern_email": "Debe ser una dirección de correo electrónico válida, sin el símbolo '+' (ej. alguien@ejemplo.com)", @@ -203,13 +202,6 @@ "password_too_simple_4": "La contraseña debe ser de al menos 12 caracteres de longitud e incluir un número, mayúsculas, minúsculas y caracteres especiales", "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": "Imposible actualizar la caché de APT (gestor de paquetes de Debian). Aquí tienes un volcado de las líneas de sources.list que podrían ayudarte 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": "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)…", - "tools_upgrade_cant_unhold_critical_packages": "No se pudo liberar los paquetes críticos…", - "tools_upgrade_cant_hold_critical_packages": "Imposible etiquetar con 'hold' los paquetes críticos…", "this_action_broke_dpkg": "Esta acción rompió dpkg/APT(los gestores de paquetes del sistema)… Puedes tratar de solucionar este problema conectándote mediante SSH y ejecutando `sudo apt install --fix-broken` and/or `sudo dpkg --configure -a`.", "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}", @@ -281,7 +273,6 @@ "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": "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}", "mail_unavailable": "Esta dirección de correo está reservada y será asignada automáticamente al primer usuario", "mailbox_disabled": "Correo desactivado para usuario {user}", @@ -604,4 +595,4 @@ "diagnosis_sshd_config_insecure": "Parece que la configuración SSH ha sido modificada manualmente, y es insegura porque no tiene ninguna instrucción 'AllowGroups' o 'AllowUsers' para limitar el acceso a los usuarios autorizados.", "domain_dns_push_not_applicable": "La configuración automática de los registros DNS no puede realizarse en el dominio {domain}. Deberìas configurar manualmente los registros DNS siguiendo la documentación.", "domain_dns_push_managed_in_parent_domain": "La configuración automática de los registros DNS es administrada desde el dominio superior {parent_domain}." -} +} \ No newline at end of file diff --git a/locales/eu.json b/locales/eu.json index 91d744a7f..6cf1ed9f6 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -482,8 +482,6 @@ "pattern_lastname": "Abizen horrek ez du balio", "permission_deleted": "'{permission}' baimena ezabatu da", "service_disabled": "'{service}' zerbitzua ez da etorkizunean zerbitzaria abiaraztearekin batera exekutatuko.", - "tools_upgrade_regular_packages_failed": "Ezin izan dira paketeak eguneratu: {packages_list}", - "tools_upgrade_special_packages_completed": "YunoHosten paketeak eguneratu dira.\nSakatu [Enter] komando-lerrora bueltatzeko", "unexpected_error": "Ezusteko zerbaitek huts egin du: {error}", "updating_apt_cache": "Sistemaren paketeen eguneraketak eskuratzen…", "mail_forward_remove_failed": "Ezinezkoa izan da '{mail}' posta elektronikoko birbidalketa ezabatzea", @@ -507,13 +505,9 @@ "permission_require_account": "'{permission}' baimena zerbitzarian kontua duten erabiltzaileentzat da eta, beraz, ezin da gaitu bisitarientzat.", "postinstall_low_rootfsspace": "'root' fitxategi-sistemak 10 GB edo espazio gutxiago dauka, kezkatzekoa dena! Litekeena da espaziorik gabe geratzea aurki! Gomendagarria da 'root' fitxategi-sistemak gutxienez 16 GB libre izatea. Jakinarazpen honen ondoren YunoHost instalatzen jarraitu nahi baduzu, berrabiarazi agindua '--force-diskspace' gehituz", "this_action_broke_dpkg": "Eragiketa honek dpkg/APT (sistemaren pakete kudeatzaileak) kaltetu ditu… Arazoa konpontzeko SSH bidez konektatu eta 'sudo apt install --fix-broken' edota 'sudo dpkg --configure -a' exekutatu dezakezu.", - "tools_upgrade_special_packages_explanation": "Eguneraketa bereziak atzeko planoan jarraituko du. Mesedez, ez abiarazi bestelako eragiketarik datozen ~10 minutuetan (zure hardwarearen abiaduraren arabera). Honen ondoren litekeena da saioa berriro hasi behar izatea. Eguneraketaren erregistroa Erramintak → Erregistroak atalean (administrazio-atarian) edo 'yunohost log list' komandoa erabiliz egongo da ikusgai.", "user_import_bad_line": "{line} lerro okerra: {details}", "restore_complete": "Lehengoratzea amaitu da", "restore_extracting": "Behar diren fitxategiak ateratzen…", - "tools_upgrade_cant_unhold_critical_packages": "Ezin izan dira pakete kritikoak deuseztatu…", - "tools_upgrade_regular_packages": "Orain pakete \"arruntak\" (YunoHostekin zerikusia ez dutenak) eguneratzen…", - "tools_upgrade_special_packages": "Orain pakete \"bereziak\" (YunoHostekin zerikusia dutenak) eguneratzen…", "regenconf_would_be_updated": "'{category}' atalerako konfigurazioa eguneratu izango litzatekeen", "migrations_dependencies_not_satisfied": "Exekutatu honako migrazioak: '{dependencies_id}', {id} migratu baino lehen.", "permission_created": "'{permission}' baimena sortu da", @@ -542,8 +536,6 @@ "migrations_pending_cant_rerun": "Migrazio hauek exekutatzeke daude eta, beraz, ezin dira berriro abiarazi: {ids}", "regenconf_file_kept_back": "'{conf}' konfigurazio fitxategia regen-conf-ek ({category} atala) ezabatzekoa zen baina mantendu egin da.", "regenconf_file_removed": "'{conf}' konfigurazio fitxategia ezabatu da", - "tools_upgrade_cant_hold_critical_packages": "Ezin izan dira pakete kritikoak mantendu…", - "migrations_cant_reach_migration_file": "Ezinezkoa izan da '%s' migrazioen fitxategia eskuratzea", "permission_already_allowed": "'{group} taldeak badauka dagoeneko '{permission}' baimena", "permission_cant_add_to_all_users": "{permission} baimena ezin da erabiltzaile guztiei ezarri.", "mailbox_disabled": "Posta elektronikoa desgaituta dago {user} erabiltzailearentzat", @@ -562,7 +554,6 @@ "user_import_missing_columns": "Ondorengo zutabeak falta dira: {columns}", "service_disable_failed": "Ezin izan da '{service}' zerbitzua geldiarazi zerbitzaria abiaraztean.\n\nZerbitzuen erregistro berrienak: {logs}", "migrations_skip_migration": "{id} migrazioa saihesten…", - "packages_upgrade_failed": "Ezinezkoa izan da pakete guztiak eguneratzea", "upnp_disabled": "UPnP itzalita dago", "main_domain_change_failed": "Ezinezkoa izan da domeinu nagusia aldatzea", "regenconf_failed": "Ezinezkoa izan da ondorengo atal(ar)en konfigurazioa berregitea: {categories}", diff --git a/locales/fa.json b/locales/fa.json index ce2ba91bd..fa5045fbb 100644 --- a/locales/fa.json +++ b/locales/fa.json @@ -386,7 +386,6 @@ "password_too_simple_2": "گذرواژه باید حداقل 8 کاراکتر طول داشته باشد و شامل عدد ، حروف الفبائی کوچک و بزرگ باشد", "password_too_simple_1": "رمز عبور باید حداقل 8 کاراکتر باشد", "password_listed": "این رمز در بین پر استفاده ترین رمزهای عبور در جهان قرار دارد. لطفاً چیزی منحصر به فرد تر انتخاب کنید.", - "packages_upgrade_failed": "همه بسته ها را نمی توان ارتقا داد", "operation_interrupted": "عملیات به صورت دستی قطع شد؟", "invalid_password": "رمز عبور نامعتبر", "invalid_number": "باید یک عدد باشد", @@ -407,7 +406,6 @@ "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_ldap_rollback_success": "سیستم برگردانده شد.", "migration_ldap_migration_failed_trying_to_rollback": "نمی توان مهاجرت کرد... تلاش برای بازگرداندن سیستم.", @@ -498,13 +496,6 @@ "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": "بسته های مهم و حیاتی را نمی توان نگه داشت...", "this_action_broke_dpkg": "این اقدام dpkg/APT (مدیران بسته های سیستم) را خراب کرد... می توانید با اتصال از طریق SSH و اجرای فرمان `sudo apt install --fix -break` و/یا` sudo dpkg --configure -a` این مشکل را حل کنید.", "system_username_exists": "نام کاربری قبلاً در لیست کاربران سیستم وجود دارد", "system_upgraded": "سیستم ارتقا یافت", diff --git a/locales/fr.json b/locales/fr.json index 00df04bae..de88b79f8 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -82,7 +82,6 @@ "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}'", - "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)", "pattern_email": "Il faut une adresse électronique valide, sans le symbole '+' (par exemple johndoe@exemple.com)", @@ -212,7 +211,6 @@ "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}...", "migrations_migration_has_failed": "La migration {id} a échoué avec l'exception {exception} : annulation", "migrations_no_migrations_to_run": "Aucune migration à lancer", @@ -338,14 +336,7 @@ "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}'...", - "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_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 à 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}", @@ -686,4 +677,4 @@ "migration_0021_not_buster": "La distribution Debian actuelle n'est pas Buster !", "migration_description_0021_migrate_to_bullseye": "Mise à niveau du système vers Debian Bullseye et YunoHost 11.x", "global_settings_setting_security_ssh_password_authentication": "Autoriser l'authentification par mot de passe pour SSH" -} +} \ No newline at end of file diff --git a/locales/gl.json b/locales/gl.json index fb68860e0..5ac7ea9da 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -447,7 +447,6 @@ "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?", "invalid_number": "Ten que ser un número", "not_enough_disk_space": "Non hai espazo libre abondo en '{path}'", @@ -467,7 +466,6 @@ "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'", "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.", @@ -572,13 +570,6 @@ "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...", "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", @@ -686,4 +677,4 @@ "migration_0021_system_not_fully_up_to_date": "O teu sistema non está completamente actualizado. Fai unha actualización normal antes de executar a migración a Bullseye.", "migration_0021_general_warning": "Ten en conta que a migración é unha operación delicada. O equipo de YunoHost fixo todo o que puido para revisalo e probalo, pero aínda así poderían acontecer fallos no sistema ou apps.\n\nAsí as cousas, é recomendable:\n - Facer unha copia de apoio dos datos e apps importantes. Máis info en https://yunohost.org/backup;\n - Ter paciencia unha vez inicias a migración: dependendo da túa conexión a internet e hardware, podería levarlle varias horas completar o proceso.", "global_settings_setting_security_ssh_password_authentication": "Permitir autenticación con contrasinal para SSH" -} +} \ No newline at end of file diff --git a/locales/it.json b/locales/it.json index bb2e7af6c..5c4a19df7 100644 --- a/locales/it.json +++ b/locales/it.json @@ -95,7 +95,6 @@ "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}'", - "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)", "pattern_firstname": "Deve essere un nome valido", @@ -391,13 +390,6 @@ "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}", "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_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...", "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}' è stato ricaricato o riavviato", @@ -484,7 +476,6 @@ "migrations_exclusive_options": "'--auto', '--skip', e '--force-rerun' sono opzioni che si escludono a vicenda.", "migrations_failed_to_load_migration": "Impossibile caricare la migrazione {id}: {error}", "migrations_dependencies_not_satisfied": "Esegui queste migrazioni: '{dependencies_id}', prima di {id}.", - "migrations_cant_reach_migration_file": "Impossibile accedere ai file di migrazione nel path '%s'", "migrations_already_ran": "Migrazioni già effettuate: {ids}", "mailbox_disabled": "E-mail disabilitate per l'utente {user}", "log_user_permission_reset": "Resetta il permesso '{}'", diff --git a/locales/nl.json b/locales/nl.json index d9f4e7a93..f8b6df327 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -136,10 +136,9 @@ "pattern_email_forward": "Het moet een geldig e-mailadres zijn, '+' symbool is toegestaan (ikzelf@mijndomein.nl bijvoorbeeld, of ikzelf+yunohost@mijndomein.nl)", "password_too_simple_2": "Het wachtwoord moet minimaal 8 tekens lang zijn en moet cijfers, hoofdletters en kleine letters bevatten", "operation_interrupted": "Werd de bewerking handmatig onderbroken?", - "packages_upgrade_failed": "Niet alle pakketten konden bijgewerkt worden", "pattern_backup_archive_name": "Moet een geldige bestandsnaam zijn van maximaal 30 tekens; alleen alfanumerieke tekens en -_. zijn toegestaan", "pattern_domain": "Moet een geldige domeinnaam zijn (mijneigendomein.nl, bijvoorbeeld)", "pattern_firstname": "Het moet een geldige voornaam zijn", "pattern_lastname": "Het moet een geldige achternaam zijn", "password_too_simple_3": "Het wachtwoord moet minimaal 8 tekens lang zijn en moet cijfers, hoofdletters, kleine letters en speciale tekens bevatten" -} +} \ No newline at end of file diff --git a/locales/oc.json b/locales/oc.json index d9ec19b09..6901b0f0b 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -126,11 +126,9 @@ "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", "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", - "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)", "pattern_firstname": "Deu èsser un pichon nom valid", @@ -310,8 +308,6 @@ "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} » 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}. Error : {msg}. Contengut brut : {raw_content}", @@ -330,7 +326,6 @@ "regenconf_dry_pending_applying": "Verificacion de la configuracion que seriá estada aplicada a la categoria « {category} »…", "regenconf_failed": "Regeneracion impossibla de la configuracion per la(s) categoria(s) : {categories}", "regenconf_pending_applying": "Aplicacion de la configuracion en espèra per la categoria « {category} »…", - "tools_upgrade_cant_hold_critical_packages": "Manteniment impossible dels paquets critiques…", "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)", @@ -339,10 +334,6 @@ "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_cant_unhold_critical_packages": "Se pòt pas quitar de manténer los paquets critics…", - "tools_upgrade_regular_packages": "Actualizacion dels paquets « normals » (pas ligats a YunoHost)…", - "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}", "backup_permission": "Autorizacion de salvagarda per l’aplicacion {app}", diff --git a/locales/pt.json b/locales/pt.json index 154ab700f..681bafb73 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -48,7 +48,6 @@ "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", "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", diff --git a/locales/uk.json b/locales/uk.json index 7e3395aba..1f99ba1b4 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -137,7 +137,6 @@ "password_too_simple_2": "Пароль має складатися не менше ніж з 8 символів і містити цифри, великі та малі символи", "password_too_simple_1": "Пароль має складатися не менше ніж з 8 символів", "password_listed": "Цей пароль входить в число найбільш часто використовуваних паролів у світі. Будь ласка, виберіть щось неповторюваніше.", - "packages_upgrade_failed": "Не вдалося оновити всі пакети", "operation_interrupted": "Операція була вручну перервана?", "invalid_number": "Має бути числом", "not_enough_disk_space": "Недостатньо вільного місця на '{path}'", @@ -157,7 +156,6 @@ "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_ldap_rollback_success": "Система відкотилася.", "migration_ldap_migration_failed_trying_to_rollback": "Не вдалося виконати міграцію... Пробуємо відкотити систему.", @@ -404,13 +402,6 @@ "unknown_main_domain_path": "Невідомий домен або шлях для '{app}'. Вам необхідно вказати домен і шлях, щоб мати можливість вказати URL для дозволу.", "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": "Тепер оновлюємо 'спеціальні' (пов'язані з 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": "Не вдалося утримати критичні пакети…", "this_action_broke_dpkg": "Ця дія порушила dpkg/APT (системні менеджери пакетів)... Ви можете спробувати вирішити цю проблему, під'єднавшись по SSH і запустивши `sudo apt install --fix-broken` та/або `sudo dpkg --configure -a`.", "system_username_exists": "Ім'я користувача вже існує в списку користувачів системи", "system_upgraded": "Систему оновлено", @@ -686,4 +677,4 @@ "migration_0021_general_warning": "Будь ласка, зверніть увагу, що ця міграція є делікатною операцією. Команда YunoHost зробила все можливе, щоб перевірити і протестувати її, але міграція все ще може порушити частину системи або її застосунків.\n\nТому рекомендовано:\n - Виконати резервне копіювання всіх важливих даних або застосунків. Подробиці на сайті https://yunohost.org/backup; \n - Наберіться терпіння після запуску міграції: В залежності від вашого з'єднання з Інтернетом і апаратного забезпечення, оновлення може зайняти до декількох годин.", "migration_description_0021_migrate_to_bullseye": "Оновлення системи до Debian Bullseye і YunoHost 11.x", "global_settings_setting_security_ssh_password_authentication": "Дозволити автентифікацію паролем для SSH" -} +} \ No newline at end of file diff --git a/locales/zh_Hans.json b/locales/zh_Hans.json index 4b3067909..a05a44437 100644 --- a/locales/zh_Hans.json +++ b/locales/zh_Hans.json @@ -237,9 +237,6 @@ "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": "无法保存重要软件包…", "this_action_broke_dpkg": "此操作破坏了dpkg / APT(系统软件包管理器)...您可以尝试通过SSH连接并运行`sudo apt install --fix-broken`和/或`sudo dpkg --configure -a`来解决此问题。", "system_username_exists": "用户名已存在于系统用户列表中", "system_upgraded": "系统升级", @@ -261,10 +258,6 @@ "unknown_main_domain_path": "'{app}'的域或路径未知。您需要指定一个域和一个路径,以便能够指定用于许可的URL。", "unexpected_error": "出乎意料的错误: {error}", "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相关的)程序包…", - "tools_upgrade_regular_packages_failed": "无法升级软件包: {packages_list}", "yunohost_installing": "正在安装YunoHost ...", "yunohost_configured": "现在已配置YunoHost", "yunohost_already_installed": "YunoHost已经安装", @@ -543,7 +536,6 @@ "password_too_simple_3": "密码长度至少为8个字符,并且包含数字,大写,小写和特殊字符", "password_too_simple_2": "密码长度至少为8个字符,并且包含数字,大写和小写字符", "password_listed": "该密码是世界上最常用的密码之一。 请选择一些更独特的东西。", - "packages_upgrade_failed": "无法升级所有软件包", "invalid_number": "必须是数字", "not_enough_disk_space": "'{path}'上的可用空间不足", "migrations_to_be_ran_manually": "迁移{id}必须手动运行。请转到webadmin页面上的工具→迁移,或运行`yunohost tools migrations run`。", @@ -562,7 +554,6 @@ "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_ldap_rollback_success": "系统回滚。", "migration_ldap_migration_failed_trying_to_rollback": "无法迁移...试图回滚系统。", From 98167e35e59a4c13ed0fc3e413ad3d86e0897c96 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 20 Jan 2022 20:26:53 +0100 Subject: [PATCH 1114/1155] trying to fix autofix-translated-strings --- .gitlab/ci/lint.gitlab-ci.yml | 4 ++-- .gitlab/ci/translation.gitlab-ci.yml | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.gitlab/ci/lint.gitlab-ci.yml b/.gitlab/ci/lint.gitlab-ci.yml index 169b32ccf..ef60dd427 100644 --- a/.gitlab/ci/lint.gitlab-ci.yml +++ b/.gitlab/ci/lint.gitlab-ci.yml @@ -44,5 +44,5 @@ black: - git push -f origin "ci-format-${CI_COMMIT_REF_NAME}":"ci-format-${CI_COMMIT_REF_NAME}" - hub pull-request -m "[CI] Format code with Black" -b Yunohost:dev -p || true # GITHUB_USER and GITHUB_TOKEN registered here https://gitlab.com/yunohost/yunohost/-/settings/ci_cd only: - refs: - - dev + variables: + - $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH diff --git a/.gitlab/ci/translation.gitlab-ci.yml b/.gitlab/ci/translation.gitlab-ci.yml index 0d73852c7..631f52327 100644 --- a/.gitlab/ci/translation.gitlab-ci.yml +++ b/.gitlab/ci/translation.gitlab-ci.yml @@ -19,15 +19,16 @@ autofix-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 + - hub clone --branch ${CI_COMMIT_REF_NAME} "https://$GITHUB_TOKEN:x-oauth-basic@github.com/YunoHost/yunohost.git" github_repo + - cd github_repo script: # create a local branch that will overwrite distant one - git checkout -b "ci-autofix-translated-strings-${CI_COMMIT_REF_NAME}" --no-track - python3 maintenance/missing_i18n_keys.py --fix - python3 maintenance/autofix_locale_format.py - - '[ $(git diff -w | wc -l) != 0 ] || exit 0' # stop if there is nothing to commit + - '[ $(git diff | 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 "HEAD":"ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}" + - git push -f origin "ci-autofix-translated-strings-${CI_COMMIT_REF_NAME}":"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 ac4771058446df5c79f73b73bbb0339f8b7d6aa6 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 20 Jan 2022 20:34:56 +0100 Subject: [PATCH 1115/1155] remove of the default hard-coded branch in ci scripts --- .gitlab-ci.yml | 2 +- .gitlab/ci/lint.gitlab-ci.yml | 2 +- .gitlab/ci/translation.gitlab-ci.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 43afb7d4e..4d0f30679 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -30,7 +30,7 @@ workflow: - 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-format-dev" # Ignore black formatting branch created by the CI + - if: $CI_COMMIT_REF_NAME == "ci-format-$CI_DEFAULT_BRANCH" # Ignore black formatting branch created by the CI when: never - 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 diff --git a/.gitlab/ci/lint.gitlab-ci.yml b/.gitlab/ci/lint.gitlab-ci.yml index ef60dd427..2c2bdcc1d 100644 --- a/.gitlab/ci/lint.gitlab-ci.yml +++ b/.gitlab/ci/lint.gitlab-ci.yml @@ -42,7 +42,7 @@ black: - '[ $(git diff | wc -l) != 0 ] || exit 0' # stop if there is nothing to commit - git commit -am "[CI] Format code with Black" || true - git push -f origin "ci-format-${CI_COMMIT_REF_NAME}":"ci-format-${CI_COMMIT_REF_NAME}" - - hub pull-request -m "[CI] Format code with Black" -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] Format code with Black" -b Yunohost:$CI_COMMIT_REF_NAME -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/.gitlab/ci/translation.gitlab-ci.yml b/.gitlab/ci/translation.gitlab-ci.yml index 631f52327..b6c683f57 100644 --- a/.gitlab/ci/translation.gitlab-ci.yml +++ b/.gitlab/ci/translation.gitlab-ci.yml @@ -29,7 +29,7 @@ autofix-translated-strings: - '[ $(git diff | 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-autofix-translated-strings-${CI_COMMIT_REF_NAME}":"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 + - hub pull-request -m "[CI] Reformat / remove stale translated strings" -b Yunohost:$CI_COMMIT_REF_NAME -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 From cb7bfe7a669943e0a0cbaf9ce5cd8b92da22f0d1 Mon Sep 17 00:00:00 2001 From: Tagadda <36127788+Tagadda@users.noreply.github.com> Date: Thu, 20 Jan 2022 20:23:32 +0000 Subject: [PATCH 1116/1155] fix for bullseye --- src/utils/config.py | 1 - src/utils/legacy.py | 9 +++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/utils/config.py b/src/utils/config.py index f3c8f8177..837416ab5 100644 --- a/src/utils/config.py +++ b/src/utils/config.py @@ -1137,7 +1137,6 @@ class AppQuestion(Question): apps = app_list(full=True)["apps"] for _filter in self.filters: - print(_filter) apps = [ app for app in apps if _filter in app and app[_filter] ] self.choices = ["_none"] + [app['id'] for app in apps] diff --git a/src/utils/legacy.py b/src/utils/legacy.py index 7a8a4540a..3d42af20b 100644 --- a/src/utils/legacy.py +++ b/src/utils/legacy.py @@ -7,6 +7,7 @@ from moulinette.utils.filesystem import ( read_file, write_to_file, write_to_yaml, + write_to_json, read_yaml, ) @@ -92,17 +93,17 @@ def translate_legacy_default_app_in_ssowant_conf_json_persistent(): apps = app_list()['apps'] - if not any('domain_path' in app and app['domain_path'] in redirected_urls.values() for app in apps): + if not any(app.get('domain_path') in redirected_urls.values() for app in apps): return - for from_url, dest_url in redirected_urls.items(): + for from_url, dest_url in redirected_urls.copy().items(): # Not a root domain, skip if from_url.count('/') != 1 or not from_url.endswith('/'): continue for app in apps: - if 'domain_path' not in app or app['domain_path'] is not dest_url: + if app.get('domain_path') != dest_url: continue - domain_config_set(from_url.strip('/'), "feature.app.default", app['id']) + domain_config_set(from_url.strip('/'), "feature.app.default_app", app['id']) del redirected_urls[from_url] persistent["redirected_urls"] = redirected_urls From 4571c5b22f09f8113ae037a4ab48b0d94d1cef72 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 21 Jan 2022 15:56:08 +0100 Subject: [PATCH 1117/1155] services: fix a couple edge cases --- src/service.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/service.py b/src/service.py index 988c7eb13..f135a3a80 100644 --- a/src/service.py +++ b/src/service.py @@ -744,14 +744,14 @@ def _save_services(services): diff = {} for service_name, service_infos in services.items(): - service_conf_base = conf_base.get(service_name, {}) + service_conf_base = conf_base.get(service_name, {}) or {} 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} + diff = {name: infos for name, infos in diff.items() if infos or name not in conf_base} write_to_yaml(SERVICES_CONF, diff) From 5b30347dfa4561996e953eb1d7230af73ffae16f Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Fri, 21 Jan 2022 15:06:31 +0000 Subject: [PATCH 1118/1155] [CI] Format code with Black --- src/service.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/service.py b/src/service.py index f135a3a80..3cb83d084 100644 --- a/src/service.py +++ b/src/service.py @@ -751,7 +751,9 @@ def _save_services(services): if service_conf_base.get(key) != value: diff[service_name][key] = value - diff = {name: infos for name, infos in diff.items() if infos or name not in conf_base} + diff = { + name: infos for name, infos in diff.items() if infos or name not in conf_base + } write_to_yaml(SERVICES_CONF, diff) From 5d0f8021b84707e6704aa662368cd1d9691e6cc6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 21 Jan 2022 18:28:16 +0100 Subject: [PATCH 1119/1155] [fix] Do not save php-fpm services in services.yml --- src/service.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/service.py b/src/service.py index 3cb83d084..506d3223e 100644 --- a/src/service.py +++ b/src/service.py @@ -744,6 +744,12 @@ def _save_services(services): diff = {} for service_name, service_infos in services.items(): + + # Ignore php-fpm services, they are to be added dynamically by the core, + # but not actually saved + if service_name.startswith("php") and service_name.endswith("-fpm"): + continue + service_conf_base = conf_base.get(service_name, {}) or {} diff[service_name] = {} From d5008bc656507293de622bd35e32de6aedad8867 Mon Sep 17 00:00:00 2001 From: Tagadda <36127788+Tagadda@users.noreply.github.com> Date: Fri, 21 Jan 2022 17:29:31 +0000 Subject: [PATCH 1120/1155] [fix] reorder diagnosers un-funkify the order 8) --- src/diagnosis.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/diagnosis.py b/src/diagnosis.py index b44028d29..34bdd2e54 100644 --- a/src/diagnosis.py +++ b/src/diagnosis.py @@ -656,9 +656,9 @@ class Diagnoser: def _list_diagnosis_categories(): paths = glob.glob(os.path.dirname(__file__) + "/diagnosers/??-*.py") - names = sorted( - [os.path.basename(path)[: -len(".py")].split("-")[-1] for path in paths] - ) + names = [name.split("-")[-1] for name in sorted( + [os.path.basename(path)[: -len(".py")] for path in paths] + )] return names From 5565e6c55c4ca640bcd3354577c8b4a24eadde6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20M?= Date: Fri, 21 Jan 2022 08:16:53 +0000 Subject: [PATCH 1121/1155] Translated using Weblate (Galician) Currently translated at 100.0% (686 of 686 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/gl/ --- locales/gl.json | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/locales/gl.json b/locales/gl.json index 5ac7ea9da..e25c698c0 100644 --- a/locales/gl.json +++ b/locales/gl.json @@ -676,5 +676,13 @@ "migration_description_0021_migrate_to_bullseye": "Actualizar o sistema a Debian Bullseye e YunoHost 11.x", "migration_0021_system_not_fully_up_to_date": "O teu sistema non está completamente actualizado. Fai unha actualización normal antes de executar a migración a Bullseye.", "migration_0021_general_warning": "Ten en conta que a migración é unha operación delicada. O equipo de YunoHost fixo todo o que puido para revisalo e probalo, pero aínda así poderían acontecer fallos no sistema ou apps.\n\nAsí as cousas, é recomendable:\n - Facer unha copia de apoio dos datos e apps importantes. Máis info en https://yunohost.org/backup;\n - Ter paciencia unha vez inicias a migración: dependendo da túa conexión a internet e hardware, podería levarlle varias horas completar o proceso.", - "global_settings_setting_security_ssh_password_authentication": "Permitir autenticación con contrasinal para SSH" -} \ No newline at end of file + "global_settings_setting_security_ssh_password_authentication": "Permitir autenticación con contrasinal para SSH", + "tools_upgrade_failed": "Non se actualizaron os paquetes: {packages_list}", + "migration_0023_not_enough_space": "Crear espazo suficiente en {path} para realizar a migración.", + "migration_0023_postgresql_11_not_installed": "PostgreSQL non estaba instalado no sistema. Nada que facer.", + "migration_0023_postgresql_13_not_installed": "PostgreSQL 11 está instalado, pero PostgreSQL 13 non!? Algo raro debeu pasarlle ao teu sistema :(...", + "migration_description_0022_php73_to_php74_pools": "Migrar ficheiros de configuración de php7.3-fpm 'pool' a php7.4", + "migration_description_0023_postgresql_11_to_13": "Migrar bases de datos de PostgreSQL 11 a 13", + "service_description_postgresql": "Almacena datos da app (Base datos SQL)", + "tools_upgrade": "Actualizando paquetes do sistema" +} From 2f7ec5b368b5b393dcc166077e970eda96281baf Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 21 Jan 2022 20:05:59 +0100 Subject: [PATCH 1122/1155] configpanels: config_get should return possible choices for domain, user questions (and other dynamic-choices questions) --- src/utils/config.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/utils/config.py b/src/utils/config.py index 99a002404..90bc0be9f 100644 --- a/src/utils/config.py +++ b/src/utils/config.py @@ -285,8 +285,10 @@ class ConfigPanel: ask = m18n.n(self.config["i18n"] + "_" + option["id"]) if mode == "full": - # edit self.config directly option["ask"] = ask + question_class = ARGUMENTS_TYPE_PARSERS[option.get("type", "string")] + # FIXME : maybe other properties should be taken from the question, not just choices ?. + option["choices"] = question_class(option).choices else: result[key] = {"ask": ask} if "current_value" in option: @@ -1109,7 +1111,7 @@ class DomainQuestion(Question): if self.default is None: self.default = _get_maindomain() - self.choices = domain_list()["domains"] + self.choices = {domain: domain + ' ★' if domain == self.default else '' for domain in domain_list()['domains']} @staticmethod def normalize(value, option={}): @@ -1134,7 +1136,9 @@ class UserQuestion(Question): from yunohost.domain import _get_maindomain super().__init__(question, context, hooks) - self.choices = list(user_list()["users"].keys()) + + self.choices = {username: f"{infos['fullname']} ({infos['mail']})" + for username, infos in user_list()["users"].items()} if not self.choices: raise YunohostValidationError( From dfa021fbf7482243f30b1d0e50df92ba099f2bc4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 22 Jan 2022 15:44:37 +0100 Subject: [PATCH 1123/1155] Fix tests --- src/tests/test_app_config.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/tests/test_app_config.py b/src/tests/test_app_config.py index 8de03bfd5..d6cf8045d 100644 --- a/src/tests/test_app_config.py +++ b/src/tests/test_app_config.py @@ -19,6 +19,7 @@ from yunohost.app import ( app_config_set, app_ssowatconf, ) +from yunohost.user import user_create, user_delete from yunohost.utils.error import YunohostError, YunohostValidationError @@ -101,6 +102,8 @@ def config_app(request): def test_app_config_get(config_app): + user_create("alice", "Alice", "White", _get_maindomain(), "test123Ynh") + 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) @@ -108,6 +111,8 @@ def test_app_config_get(config_app): assert isinstance(app_config_get(config_app, "main.components"), dict) assert app_config_get(config_app, "main.components.boolean") == "0" + user_delete("alice") + def test_app_config_nopanel(legacy_app): From fde01fafd7c3375dd6323c449e39ea33419f8224 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 24 Jan 2022 18:31:04 +0100 Subject: [PATCH 1124/1155] app questions in config panel: handle choices with nice display names for webadmin --- src/utils/config.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/utils/config.py b/src/utils/config.py index 79d73b6ec..1ba38c604 100644 --- a/src/utils/config.py +++ b/src/utils/config.py @@ -1127,6 +1127,7 @@ class DomainQuestion(Question): return value + class AppQuestion(Question): argument_type = "app" @@ -1139,9 +1140,15 @@ class AppQuestion(Question): apps = app_list(full=True)["apps"] for _filter in self.filters: - apps = [ app for app in apps if _filter in app and app[_filter] ] + apps = [app for app in apps if _filter in app and app[_filter]] + + def _app_display(app): + domain_path = f" ({app['domain_path']})" if 'domain_path' in app else "" + return app["label"] + domain_path + + self.choices = {"_none": "---"} + self.choices.update({app['id']: _app_display(app) for app in apps}) - self.choices = ["_none"] + [app['id'] for app in apps] class UserQuestion(Question): argument_type = "user" From 71e73c7cf2f77d7ea83157e26afb37632bfb5f8d Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Mon, 24 Jan 2022 18:16:08 +0000 Subject: [PATCH 1125/1155] [CI] Format code with Black --- src/app.py | 16 ++++++++++------ src/diagnosis.py | 7 ++++--- src/domain.py | 7 +++++-- src/utils/config.py | 15 ++++++++++----- src/utils/legacy.py | 17 ++++++++++------- 5 files changed, 39 insertions(+), 23 deletions(-) diff --git a/src/app.py b/src/app.py index 22946eabc..a75f25bc4 100644 --- a/src/app.py +++ b/src/app.py @@ -155,7 +155,9 @@ def app_info(app, full=False): ret["is_webapp"] = "domain" in settings and "path" in settings if ret["is_webapp"]: - ret["is_default"] = domain_config_get(settings["domain"], "feature.app.default_app") == app + ret["is_default"] = ( + domain_config_get(settings["domain"], "feature.app.default_app") == app + ) ret["supports_change_url"] = os.path.exists( os.path.join(setting_path, "scripts", "change_url") @@ -1054,7 +1056,7 @@ def app_remove(operation_logger, app, purge=False): hook_remove(app) for domain in domain_list()["domains"]: - if (domain_config_get(domain, "feature.app.default_app") == app): + if domain_config_get(domain, "feature.app.default_app") == app: domain_config_set(domain, "feature.app.default_app", "_none") permission_sync_to_user() @@ -1086,9 +1088,9 @@ def app_makedefault(operation_logger, app, domain=None, undo=False): operation_logger.start() if undo: - domain_config_set(domain, 'feature.app.default_app', "_none") + domain_config_set(domain, "feature.app.default_app", "_none") else: - domain_config_set(domain, 'feature.app.default_app', app) + domain_config_set(domain, "feature.app.default_app", app) def app_setting(app, key, value=None, delete=False): @@ -1325,8 +1327,10 @@ def app_ssowatconf(): redirected_urls.update(app_settings.get("redirected_urls", {})) redirected_regex.update(app_settings.get("redirected_regex", {})) - from .utils.legacy import translate_legacy_default_app_in_ssowant_conf_json_persistent - + from .utils.legacy import ( + translate_legacy_default_app_in_ssowant_conf_json_persistent, + ) + translate_legacy_default_app_in_ssowant_conf_json_persistent() for domain in domains: diff --git a/src/diagnosis.py b/src/diagnosis.py index 34bdd2e54..007719dfc 100644 --- a/src/diagnosis.py +++ b/src/diagnosis.py @@ -656,9 +656,10 @@ class Diagnoser: def _list_diagnosis_categories(): paths = glob.glob(os.path.dirname(__file__) + "/diagnosers/??-*.py") - names = [name.split("-")[-1] for name in sorted( - [os.path.basename(path)[: -len(".py")] for path in paths] - )] + names = [ + name.split("-")[-1] + for name in sorted([os.path.basename(path)[: -len(".py")] for path in paths]) + ] return names diff --git a/src/domain.py b/src/domain.py index 1fdded817..e73d6ed5f 100644 --- a/src/domain.py +++ b/src/domain.py @@ -455,7 +455,10 @@ class DomainConfigPanel(ConfigPanel): save_mode = "diff" def _apply(self): - if ("default_app" in self.future_values and self.future_values["default_app"] != self.values["default_app"]): + if ( + "default_app" in self.future_values + and self.future_values["default_app"] != self.values["default_app"] + ): from yunohost.app import app_ssowatconf, app_map if "/" in app_map(raw=True)[self.entity]: @@ -468,7 +471,7 @@ class DomainConfigPanel(ConfigPanel): super()._apply() app_ssowatconf() - + def _get_toml(self): from yunohost.dns import _get_registrar_config_section diff --git a/src/utils/config.py b/src/utils/config.py index 1ba38c604..56c45d5f8 100644 --- a/src/utils/config.py +++ b/src/utils/config.py @@ -1113,7 +1113,10 @@ class DomainQuestion(Question): if self.default is None: self.default = _get_maindomain() - self.choices = {domain: domain + ' ★' if domain == self.default else '' for domain in domain_list()['domains']} + self.choices = { + domain: domain + " ★" if domain == self.default else "" + for domain in domain_list()["domains"] + } @staticmethod def normalize(value, option={}): @@ -1143,11 +1146,11 @@ class AppQuestion(Question): apps = [app for app in apps if _filter in app and app[_filter]] def _app_display(app): - domain_path = f" ({app['domain_path']})" if 'domain_path' in app else "" + domain_path = f" ({app['domain_path']})" if "domain_path" in app else "" return app["label"] + domain_path self.choices = {"_none": "---"} - self.choices.update({app['id']: _app_display(app) for app in apps}) + self.choices.update({app["id"]: _app_display(app) for app in apps}) class UserQuestion(Question): @@ -1161,8 +1164,10 @@ class UserQuestion(Question): super().__init__(question, context, hooks) - self.choices = {username: f"{infos['fullname']} ({infos['mail']})" - for username, infos in user_list()["users"].items()} + self.choices = { + username: f"{infos['fullname']} ({infos['mail']})" + for username, infos in user_list()["users"].items() + } if not self.choices: raise YunohostValidationError( diff --git a/src/utils/legacy.py b/src/utils/legacy.py index 4c61edb05..85898f28d 100644 --- a/src/utils/legacy.py +++ b/src/utils/legacy.py @@ -88,24 +88,27 @@ def translate_legacy_default_app_in_ssowant_conf_json_persistent(): redirected_urls = persistent["redirected_urls"] - if not any(from_url.count('/') == 1 and from_url.endswith('/') for from_url in redirected_urls): + if not any( + from_url.count("/") == 1 and from_url.endswith("/") + for from_url in redirected_urls + ): return - apps = app_list()['apps'] + apps = app_list()["apps"] - if not any(app.get('domain_path') in redirected_urls.values() for app in apps): + if not any(app.get("domain_path") in redirected_urls.values() for app in apps): return for from_url, dest_url in redirected_urls.copy().items(): # Not a root domain, skip - if from_url.count('/') != 1 or not from_url.endswith('/'): + if from_url.count("/") != 1 or not from_url.endswith("/"): continue for app in apps: - if app.get('domain_path') != dest_url: + if app.get("domain_path") != dest_url: continue - domain_config_set(from_url.strip('/'), "feature.app.default_app", app['id']) + domain_config_set(from_url.strip("/"), "feature.app.default_app", app["id"]) del redirected_urls[from_url] - + persistent["redirected_urls"] = redirected_urls write_to_json(persistent_file_name, persistent, sort_keys=True, indent=4) From 07396b8b34c29b582c5fd16a57fcc0ceee8f4579 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 25 Jan 2022 13:04:06 +0100 Subject: [PATCH 1126/1155] [fix] When no main app permission found, fallback to default label instead of having a 'None' label to prevent the webadmin from displaying an empty app list --- src/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app.py b/src/app.py index a75f25bc4..3b6b0a5aa 100644 --- a/src/app.py +++ b/src/app.py @@ -177,6 +177,7 @@ def app_info(app, full=False): if not ret["label"]: logger.warning(f"Failed to get label for app {app} ?") + ret["label"] = local_manifest["name"] return ret From 9b89f66bba2465c57ad82d841a64882e446ee6f9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 25 Jan 2022 13:09:19 +0100 Subject: [PATCH 1127/1155] Update changelog for 11.0.3 --- debian/changelog | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/debian/changelog b/debian/changelog index cbc6cfc99..b0229fe8d 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,18 @@ +yunohost (11.0.3) testing; urgency=low + + - [enh] mail: Add SNI support for postfix and dovecot ([#1413](https://github.com/YunoHost/yunohost/pull/1413)) + - [fix] services: fix a couple edge cases (4571c5b2) + - [fix] services: Do not save php-fpm services in services.yml (5d0f8021) + - [fix] diagnosis: diagnosers were run in a funky order ([#1418](https://github.com/YunoHost/yunohost/pull/1418)) + - [fix] configpanels: config_get should return possible choices for domain, user questions (and other dynamic-choices questions) ([#1420](https://github.com/YunoHost/yunohost/pull/1420)) + - [enh] apps/domain: Clarify the default app mecanism, handle it fron domain config panel ([#1406](https://github.com/YunoHost/yunohost/pull/1406)) + - [fix] apps: When no main app permission found, fallback to default label instead of having a 'None' label to prevent the webadmin from displaying an empty app list (07396b8b) + - [i18n] Translations updated for Galician + + Thanks to all contributors <3 ! (José M, Kay0u, Tagadda, tituspijean) + + -- Alexandre Aubin Tue, 25 Jan 2022 13:06:10 +0100 + yunohost (11.0.2) testing; urgency=low - [mod] Various tweaks for Python 3.9, PHP 7.4, PostgreSQL 13, and other changes related to Buster->Bullseye ecosystem From dcf1a892bfb530ee782546d2f0f852a164f9ef3d Mon Sep 17 00:00:00 2001 From: Kay0u Date: Wed, 26 Jan 2022 14:19:09 +0100 Subject: [PATCH 1128/1155] fix bash_completion --- debian/install | 2 +- doc/generate_bash_completion.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/debian/install b/debian/install index db88462ab..5169d0b62 100644 --- a/debian/install +++ b/debian/install @@ -5,6 +5,6 @@ helpers/* /usr/share/yunohost/helpers.d/ conf/* /usr/share/yunohost/conf/ locales/* /usr/share/yunohost/locales/ doc/yunohost.8.gz /usr/share/man/man8/ -doc/bash-completion.sh /etc/bash_completion.d/yunohost +doc/bash_completion.d/* /etc/bash_completion.d/ conf/metronome/modules/* /usr/lib/metronome/modules/ src/* /usr/lib/python3/dist-packages/yunohost/ diff --git a/doc/generate_bash_completion.py b/doc/generate_bash_completion.py index 72a524210..d55973010 100644 --- a/doc/generate_bash_completion.py +++ b/doc/generate_bash_completion.py @@ -13,7 +13,8 @@ import yaml THIS_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) ACTIONSMAP_FILE = THIS_SCRIPT_DIR + "/../share/actionsmap.yml" -BASH_COMPLETION_FILE = THIS_SCRIPT_DIR + "/bash-completion.sh" +BASH_COMPLETION_FOLDER = THIS_SCRIPT_DIR + "/bash_completion.d" +BASH_COMPLETION_FILE = BASH_COMPLETION_FOLDER + "/yunohost" def get_dict_actions(OPTION_SUBTREE, category): @@ -61,6 +62,8 @@ with open(ACTIONSMAP_FILE, "r") as stream: OPTION_TREE[category]["subcategories"], subcategory ) + os.makedirs(BASH_COMPLETION_FOLDER, exist_ok=True) + with open(BASH_COMPLETION_FILE, "w") as generated_file: # header of the file From 4b78e8e32796ef412499e525c8bb49ea40d908fb Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 26 Jan 2022 14:28:45 +0100 Subject: [PATCH 1129/1155] [mod] certificate: drop unused 'staging' LE mode --- share/actionsmap.yml | 12 ---------- src/certificate.py | 52 +++++++++----------------------------------- src/domain.py | 8 +++---- 3 files changed, 14 insertions(+), 58 deletions(-) diff --git a/share/actionsmap.yml b/share/actionsmap.yml index ce395942f..43b610a9d 100644 --- a/share/actionsmap.yml +++ b/share/actionsmap.yml @@ -531,9 +531,6 @@ domain: --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() cert-renew: @@ -552,9 +549,6 @@ domain: --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 ### domain_url_available() url-available: @@ -677,9 +671,6 @@ domain: --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: @@ -698,9 +689,6 @@ domain: --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 ############################# diff --git a/src/certificate.py b/src/certificate.py index 2ad294605..308b79f1c 100644 --- a/src/certificate.py +++ b/src/certificate.py @@ -60,8 +60,6 @@ KEY_SIZE = 3072 VALIDITY_LIMIT = 15 # days -# For tests -STAGING_CERTIFICATION_AUTHORITY = "https://acme-staging-v02.api.letsencrypt.org" # For prod PRODUCTION_CERTIFICATION_AUTHORITY = "https://acme-v02.api.letsencrypt.org" @@ -114,7 +112,7 @@ def certificate_status(domains, full=False): def certificate_install( - domain_list, force=False, no_checks=False, self_signed=False, staging=False + domain_list, force=False, no_checks=False, self_signed=False ): """ Install a Let's Encrypt certificate for given domains (all by default) @@ -130,7 +128,7 @@ def certificate_install( if self_signed: _certificate_install_selfsigned(domain_list, force) else: - _certificate_install_letsencrypt(domain_list, force, no_checks, staging) + _certificate_install_letsencrypt(domain_list, force, no_checks) def _certificate_install_selfsigned(domain_list, force=False): @@ -234,7 +232,7 @@ def _certificate_install_selfsigned(domain_list, force=False): def _certificate_install_letsencrypt( - domains, force=False, no_checks=False, staging=False + domains, force=False, no_checks=False ): from yunohost.domain import domain_list, _assert_domain_exists @@ -264,11 +262,6 @@ def _certificate_install_letsencrypt( "certmanager_domain_cert_not_selfsigned", domain=domain ) - if staging: - logger.warning( - "Please note that you used the --staging option, and that no new certificate will actually be enabled !" - ) - # Actual install steps for domain in domains: @@ -284,12 +277,12 @@ def _certificate_install_letsencrypt( operation_logger = OperationLogger( "letsencrypt_cert_install", [("domain", domain)], - args={"force": force, "no_checks": no_checks, "staging": staging}, + args={"force": force, "no_checks": no_checks}, ) operation_logger.start() try: - _fetch_and_enable_new_certificate(domain, staging, no_checks=no_checks) + _fetch_and_enable_new_certificate(domain, no_checks=no_checks) except Exception as e: msg = f"Certificate installation for {domain} failed !\nException: {e}" logger.error(msg) @@ -305,7 +298,7 @@ def _certificate_install_letsencrypt( def certificate_renew( - domains, force=False, no_checks=False, email=False, staging=False + domains, force=False, no_checks=False, email=False ): """ Renew Let's Encrypt certificate for given domains (all by default) @@ -373,11 +366,6 @@ def certificate_renew( "certmanager_acme_not_configured_for_domain", domain=domain ) - if staging: - logger.warning( - "Please note that you used the --staging option, and that no new certificate will actually be enabled !" - ) - # Actual renew steps for domain in domains: @@ -399,14 +387,13 @@ def certificate_renew( args={ "force": force, "no_checks": no_checks, - "staging": staging, "email": email, }, ) operation_logger.start() try: - _fetch_and_enable_new_certificate(domain, staging, no_checks=no_checks) + _fetch_and_enable_new_certificate(domain, no_checks=no_checks) except Exception as e: import traceback from io import StringIO @@ -473,7 +460,7 @@ def _check_acme_challenge_configuration(domain): return "include /etc/nginx/conf.d/acme-challenge.conf.inc" in read_file(domain_conf) -def _fetch_and_enable_new_certificate(domain, staging=False, no_checks=False): +def _fetch_and_enable_new_certificate(domain, no_checks=False): if not os.path.exists(ACCOUNT_KEY_FILE): _generate_account_key() @@ -507,11 +494,6 @@ def _fetch_and_enable_new_certificate(domain, staging=False, no_checks=False): domain_csr_file = f"{TMP_FOLDER}/{domain}.csr" - if staging: - certification_authority = STAGING_CERTIFICATION_AUTHORITY - else: - certification_authority = PRODUCTION_CERTIFICATION_AUTHORITY - try: signed_certificate = sign_certificate( ACCOUNT_KEY_FILE, @@ -519,7 +501,7 @@ def _fetch_and_enable_new_certificate(domain, staging=False, no_checks=False): WEBROOT_FOLDER, log=logger, disable_check=no_checks, - CA=certification_authority, + CA=PRODUCTION_CERTIFICATION_AUTHORITY, ) except ValueError as e: if "urn:acme:error:rateLimited" in str(e): @@ -539,12 +521,7 @@ def _fetch_and_enable_new_certificate(domain, staging=False, no_checks=False): # Create corresponding directory date_tag = datetime.utcnow().strftime("%Y%m%d.%H%M%S") - if staging: - folder_flag = "staging" - else: - folder_flag = "letsencrypt" - - new_cert_folder = f"{CERT_FOLDER}/{domain}-history/{date_tag}-{folder_flag}" + new_cert_folder = f"{CERT_FOLDER}/{domain}-history/{date_tag}-letsencrypt" os.makedirs(new_cert_folder) @@ -563,9 +540,6 @@ def _fetch_and_enable_new_certificate(domain, staging=False, no_checks=False): _set_permissions(domain_cert_file, "root", "ssl-cert", 0o640) - if staging: - return - _enable_certificate(domain, new_cert_folder) # Check the status of the certificate is now good @@ -677,12 +651,6 @@ def _get_status(domain): "verbose": "Let's Encrypt", } - elif cert_issuer.startswith("Fake LE"): - CA_type = { - "code": "fake-lets-encrypt", - "verbose": "Fake Let's Encrypt", - } - else: CA_type = { "code": "other-unknown", diff --git a/src/domain.py b/src/domain.py index e73d6ed5f..7481e4088 100644 --- a/src/domain.py +++ b/src/domain.py @@ -528,19 +528,19 @@ def domain_cert_status(domain_list, full=False): def domain_cert_install( - domain_list, force=False, no_checks=False, self_signed=False, staging=False + domain_list, force=False, no_checks=False, self_signed=False ): from yunohost.certificate import certificate_install - return certificate_install(domain_list, force, no_checks, self_signed, staging) + return certificate_install(domain_list, force, no_checks, self_signed) def domain_cert_renew( - domain_list, force=False, no_checks=False, email=False, staging=False + domain_list, force=False, no_checks=False, email=False ): from yunohost.certificate import certificate_renew - return certificate_renew(domain_list, force, no_checks, email, staging) + return certificate_renew(domain_list, force, no_checks, email) def domain_dns_conf(domain): From 67cce15d0276e3f1a916540c06ad77274a53aa46 Mon Sep 17 00:00:00 2001 From: tituspijean Date: Wed, 26 Jan 2022 20:59:56 +0000 Subject: [PATCH 1130/1155] Enhance yunomdns service startup Make it rely on network-online.target instead of network.target Co-authored-by: Alexandre Aubin --- conf/mdns/yunomdns.service | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/conf/mdns/yunomdns.service b/conf/mdns/yunomdns.service index 1102f18f6..5fc98afbd 100644 --- a/conf/mdns/yunomdns.service +++ b/conf/mdns/yunomdns.service @@ -1,6 +1,7 @@ [Unit] Description=YunoHost mDNS service -After=network.target +Wants=network-online.target +After=network-online.target [Service] User=mdns From 377558b54e75ea372930e2096266f238f7fcd7f5 Mon Sep 17 00:00:00 2001 From: ericgaspar Date: Thu, 27 Jan 2022 12:15:09 +0100 Subject: [PATCH 1131/1155] Fix typo --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 4a8f2c1b2..e902ecc2a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -501,7 +501,7 @@ "migration_0021_yunohost_upgrade": "Starting YunoHost core upgrade...", "migration_0023_not_enough_space": "Make sufficient space available in {path} to run the migration.", "migration_0023_postgresql_11_not_installed": "PostgreSQL was not installed on your system. Nothing to do.", - "migration_0023_postgresql_13_not_installed": "PostgreSQL 11 is installed, but not postgresql 13!? Something weird might have happened on your system :(...", + "migration_0023_postgresql_13_not_installed": "PostgreSQL 11 is installed, but not PostgreSQL 13!? Something weird might have happened on your system :(...", "migration_description_0021_migrate_to_bullseye": "Upgrade the system to Debian Bullseye and YunoHost 11.x", "migration_description_0022_php73_to_php74_pools": "Migrate php7.3-fpm 'pool' conf files to php7.4", "migration_description_0023_postgresql_11_to_13": "Migrate databases from PostgreSQL 11 to 13", From 740eaa43aef51c1125851b99005185c826e363d2 Mon Sep 17 00:00:00 2001 From: Tagadda <36127788+Tagadda@users.noreply.github.com> Date: Fri, 28 Jan 2022 02:39:02 +0000 Subject: [PATCH 1132/1155] typos --- src/utils/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/config.py b/src/utils/config.py index 56c45d5f8..f3b906f00 100644 --- a/src/utils/config.py +++ b/src/utils/config.py @@ -1114,7 +1114,7 @@ class DomainQuestion(Question): self.default = _get_maindomain() self.choices = { - domain: domain + " ★" if domain == self.default else "" + domain: domain + " ★" if domain == self.default else domain for domain in domain_list()["domains"] } @@ -1178,7 +1178,7 @@ class UserQuestion(Question): if self.default is None: root_mail = "root@%s" % _get_maindomain() - for user in self.choices: + for user in self.choices.keys(): if root_mail in user_info(user).get("mail-aliases", []): self.default = user break From 9486931674230f65a5fe87f036ebcf231f309c4d Mon Sep 17 00:00:00 2001 From: Tagadda <36127788+Tagadda@users.noreply.github.com> Date: Fri, 28 Jan 2022 02:39:20 +0000 Subject: [PATCH 1133/1155] Hydrate manifest with choices --- src/app.py | 4 ++++ src/utils/config.py | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/app.py b/src/app.py index 3b6b0a5aa..d7310c0ea 100644 --- a/src/app.py +++ b/src/app.py @@ -57,6 +57,7 @@ from yunohost.utils.config import ( ask_questions_and_parse_answers, DomainQuestion, PathQuestion, + hydrate_questions_with_choices, ) from yunohost.utils.i18n import _value_for_locale from yunohost.utils.error import YunohostError, YunohostValidationError @@ -678,6 +679,9 @@ def app_manifest(app): shutil.rmtree(extracted_app_folder) + raw_questions = manifest.get("arguments", {}).get("install", []) + manifest['arguments']['install'] = hydrate_questions_with_choices(raw_questions) + return manifest diff --git a/src/utils/config.py b/src/utils/config.py index f3b906f00..f44fe5c22 100644 --- a/src/utils/config.py +++ b/src/utils/config.py @@ -1396,3 +1396,15 @@ def ask_questions_and_parse_answers( out.append(question) return out + +def hydrate_questions_with_choices(raw_questions: List) -> List: + out = [] + + for raw_question in raw_questions: + question = ARGUMENTS_TYPE_PARSERS[raw_question.get("type", "string")](raw_question) + if question.choices: + raw_question["choices"] = question.choices + raw_question["default"] = question.default + out.append(raw_question) + + return out From 49b444e4809b927b54af6ec9e5ef563dad04a524 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 29 Jan 2022 14:36:10 +0100 Subject: [PATCH 1134/1155] tools_upgrade: raise a Yunohost error, not a raw exception, when misusing args --- src/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools.py b/src/tools.py index 28b4457b4..f85d0abdf 100644 --- a/src/tools.py +++ b/src/tools.py @@ -453,7 +453,7 @@ def tools_upgrade(operation_logger, target=None): raise YunohostValidationError("dpkg_lock_not_available") if target not in ["apps", "system"]: - raise Exception( + raise YunohostValidationError( "Uhoh ?! tools_upgrade should have 'apps' or 'system' value for argument target" ) From fc3834bff56917df6e3d791b938873b821e77d74 Mon Sep 17 00:00:00 2001 From: Tymofii-Lytvynenko Date: Tue, 25 Jan 2022 01:28:28 +0000 Subject: [PATCH 1135/1155] Translated using Weblate (Ukrainian) Currently translated at 100.0% (687 of 687 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/uk/ --- locales/uk.json | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/locales/uk.json b/locales/uk.json index 1f99ba1b4..ae363bd35 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -676,5 +676,14 @@ "migration_0021_system_not_fully_up_to_date": "Ваша система не повністю оновлена. Будь ласка, виконайте регулярне оновлення перед запуском міграції на Bullseye.", "migration_0021_general_warning": "Будь ласка, зверніть увагу, що ця міграція є делікатною операцією. Команда YunoHost зробила все можливе, щоб перевірити і протестувати її, але міграція все ще може порушити частину системи або її застосунків.\n\nТому рекомендовано:\n - Виконати резервне копіювання всіх важливих даних або застосунків. Подробиці на сайті https://yunohost.org/backup; \n - Наберіться терпіння після запуску міграції: В залежності від вашого з'єднання з Інтернетом і апаратного забезпечення, оновлення може зайняти до декількох годин.", "migration_description_0021_migrate_to_bullseye": "Оновлення системи до Debian Bullseye і YunoHost 11.x", - "global_settings_setting_security_ssh_password_authentication": "Дозволити автентифікацію паролем для SSH" -} \ No newline at end of file + "global_settings_setting_security_ssh_password_authentication": "Дозволити автентифікацію паролем для SSH", + "service_description_postgresql": "Зберігає дані застосунків (база даних SQL)", + "domain_config_default_app": "Типовий застосунок", + "migration_0023_postgresql_13_not_installed": "PostgreSQL 11 встановлено, але не PostgreSQL 13!? У вашій системі могло статися щось неприємне :(...", + "migration_description_0023_postgresql_11_to_13": "Перенесення баз даних з PostgreSQL 11 на 13", + "tools_upgrade": "Оновлення системних пакетів", + "tools_upgrade_failed": "Не вдалося оновити наступні пакети: {packages_list}", + "migration_0023_not_enough_space": "Звільніть достатньо місця в {path} для виконання міграції.", + "migration_0023_postgresql_11_not_installed": "PostgreSQL не було встановлено у вашій системі. Нічого робити.", + "migration_description_0022_php73_to_php74_pools": "Перенесення конфігураційних файлів php7.3-fpm 'pool' на php7.4" +} From c9606d353bc095077769ce0aaae18dc02c4df33e Mon Sep 17 00:00:00 2001 From: Weblate Date: Wed, 26 Jan 2022 13:41:52 +0000 Subject: [PATCH 1136/1155] Added translation using Weblate (Danish) --- locales/da.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 locales/da.json diff --git a/locales/da.json b/locales/da.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/locales/da.json @@ -0,0 +1 @@ +{} From d79e3618de1204216f29f4c869030f51bc0c0b11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Gaspar?= Date: Thu, 27 Jan 2022 11:07:18 +0000 Subject: [PATCH 1137/1155] Translated using Weblate (French) Currently translated at 100.0% (687 of 687 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index de88b79f8..a14850b8b 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -676,5 +676,14 @@ "migration_0021_patch_yunohost_conflicts": "Application du correctif pour contourner le problème de conflit...", "migration_0021_not_buster": "La distribution Debian actuelle n'est pas Buster !", "migration_description_0021_migrate_to_bullseye": "Mise à niveau du système vers Debian Bullseye et YunoHost 11.x", - "global_settings_setting_security_ssh_password_authentication": "Autoriser l'authentification par mot de passe pour SSH" -} \ No newline at end of file + "global_settings_setting_security_ssh_password_authentication": "Autoriser l'authentification par mot de passe pour SSH", + "domain_config_default_app": "Application par défaut", + "migration_description_0022_php73_to_php74_pools": "Migration des fichiers de configuration php7.3-fpm 'pool' vers php7.4", + "migration_description_0023_postgresql_11_to_13": "Migration des bases de données de PostgreSQL 11 vers 13", + "service_description_postgresql": "Stocke les données d'application (base de données SQL)", + "tools_upgrade": "Mise à niveau des packages système", + "migration_0023_postgresql_13_not_installed": "PostgreSQL 11 est installé, mais pas PostgreSQL 13 ! ? Quelque chose d'anormal s'est peut-être produit sur votre système :(...", + "tools_upgrade_failed": "Impossible de mettre à jour les paquets : {packages_list}", + "migration_0023_not_enough_space": "Prévoyez suffisamment d'espace disponible dans {path} pour exécuter la migration.", + "migration_0023_postgresql_11_not_installed": "PostgreSQL n'a pas été installé sur votre système. Il n'y a rien à faire." +} From bccff1b4421eaa68d2da45493eaf99517233e5aa Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 29 Jan 2022 15:52:16 +0100 Subject: [PATCH 1138/1155] regenconf: make some systemctl enable/disable quiet --- hooks/conf_regen/01-yunohost | 2 +- hooks/conf_regen/37-mdns | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/hooks/conf_regen/01-yunohost b/hooks/conf_regen/01-yunohost index 1f6c143a6..ceab4b2f6 100755 --- a/hooks/conf_regen/01-yunohost +++ b/hooks/conf_regen/01-yunohost @@ -62,7 +62,7 @@ do_init_regen() { systemctl daemon-reload - systemctl enable yunohost-api.service + systemctl enable yunohost-api.service --quiet systemctl start yunohost-api.service # Yunohost-firewall is enabled only during postinstall, not init, not 100% sure why diff --git a/hooks/conf_regen/37-mdns b/hooks/conf_regen/37-mdns index 246b5b469..b99584863 100755 --- a/hooks/conf_regen/37-mdns +++ b/hooks/conf_regen/37-mdns @@ -27,7 +27,7 @@ _generate_config() { do_init_regen() { do_pre_regen do_post_regen /etc/systemd/system/yunomdns.service - systemctl enable yunomdns + systemctl enable yunomdns --quiet } do_pre_regen() { @@ -53,12 +53,12 @@ do_post_regen() { systemctl daemon-reload fi - systemctl disable avahi-daemon.socket --now 2>&1|| true - systemctl disable avahi-daemon --now 2>&1 || true + systemctl disable avahi-daemon.socket --quiet --now 2>&1 || true + systemctl disable avahi-daemon --quiet --now 2>&1 || true # 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 --now + systemctl enable yunomdns --now --quiet sleep 2 fi From 345e50ae0098e4fc6f45a52b783bf6acca46a373 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 29 Jan 2022 16:49:03 +0100 Subject: [PATCH 1139/1155] =?UTF-8?q?regenconf:=20make=20some=20systemctl?= =?UTF-8?q?=20enable/disable=20quiet=C2=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hooks/conf_regen/37-mdns | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hooks/conf_regen/37-mdns b/hooks/conf_regen/37-mdns index b99584863..3a877970b 100755 --- a/hooks/conf_regen/37-mdns +++ b/hooks/conf_regen/37-mdns @@ -53,8 +53,8 @@ do_post_regen() { systemctl daemon-reload fi - systemctl disable avahi-daemon.socket --quiet --now 2>&1 || true - systemctl disable avahi-daemon --quiet --now 2>&1 || true + systemctl disable avahi-daemon.socket --quiet --now 2>/dev/null || true + systemctl disable avahi-daemon --quiet --now 2>/dev/null || true # 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 From 3ca302b85dd4d4a86d1a406b8f56d8a9b0e95526 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 29 Jan 2022 16:54:27 +0100 Subject: [PATCH 1140/1155] Tmp fix to try to debug the CI @_@ --- src/domain.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/domain.py b/src/domain.py index 7481e4088..c4c8d0160 100644 --- a/src/domain.py +++ b/src/domain.py @@ -389,7 +389,11 @@ def domain_main_domain(operation_logger, new_main_domain=None): raise YunohostError("main_domain_change_failed") # Generate SSOwat configuration file - app_ssowatconf() + try: + app_ssowatconf() + except Exception as e: + logger.warning(str(e), exc_info=1) + raise YunohostError("main_domain_change_failed") # Regen configurations if os.path.exists("/etc/yunohost/installed"): From 16946372cca69c12fa3f6a2f0069206aa6911964 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 29 Jan 2022 17:18:24 +0100 Subject: [PATCH 1141/1155] Moar tmp tweaks to try to debug the CI --- src/domain.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/domain.py b/src/domain.py index c4c8d0160..974a8d72c 100644 --- a/src/domain.py +++ b/src/domain.py @@ -385,6 +385,8 @@ def domain_main_domain(operation_logger, new_main_domain=None): domain_list_cache = {} _set_hostname(new_main_domain) except Exception as e: + import traceback + traceback.print_exc() logger.warning(str(e), exc_info=1) raise YunohostError("main_domain_change_failed") @@ -392,6 +394,8 @@ def domain_main_domain(operation_logger, new_main_domain=None): try: app_ssowatconf() except Exception as e: + import traceback + traceback.print_exc() logger.warning(str(e), exc_info=1) raise YunohostError("main_domain_change_failed") From bf6252ac1db0247637f8978d0a2b71a95dd4c822 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 29 Jan 2022 17:49:45 +0100 Subject: [PATCH 1142/1155] configpanels: optimize _get_toml for domains to not load the whole DNS section stuff when just getting a simple info from another section --- src/domain.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/domain.py b/src/domain.py index 974a8d72c..a773bcaad 100644 --- a/src/domain.py +++ b/src/domain.py @@ -481,18 +481,24 @@ class DomainConfigPanel(ConfigPanel): app_ssowatconf() def _get_toml(self): - from yunohost.dns import _get_registrar_config_section toml = super()._get_toml() toml["feature"]["xmpp"]["xmpp"]["default"] = ( 1 if self.entity == _get_maindomain() else 0 ) - toml["dns"]["registrar"] = _get_registrar_config_section(self.entity) - # 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"] + # Optimize wether or not to load the DNS section, + # e.g. we don't want to trigger the whole _get_registary_config_section + # when just getting the current value from the feature section + filter_key = self.filter_key.split(".") if self.filter_key != "" else [] + if not filter_key or filter_key[0] == "dns": + from yunohost.dns import _get_registrar_config_section + toml["dns"]["registrar"] = _get_registrar_config_section(self.entity) + + # 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 @@ -502,7 +508,8 @@ class DomainConfigPanel(ConfigPanel): 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 + if self.hasattr("registrar_id"): + self.values["registrar"] = self.registar_id def _get_domain_settings(domain: str) -> dict: From 74e73c470f6ad4c8f078eb6506e5b0797f582ac5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 29 Jan 2022 17:49:59 +0100 Subject: [PATCH 1143/1155] Revert "Moar tmp tweaks to try to debug the CI" This reverts commit 16946372cca69c12fa3f6a2f0069206aa6911964. --- src/domain.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/domain.py b/src/domain.py index a773bcaad..1e869c985 100644 --- a/src/domain.py +++ b/src/domain.py @@ -385,8 +385,6 @@ def domain_main_domain(operation_logger, new_main_domain=None): domain_list_cache = {} _set_hostname(new_main_domain) except Exception as e: - import traceback - traceback.print_exc() logger.warning(str(e), exc_info=1) raise YunohostError("main_domain_change_failed") @@ -394,8 +392,6 @@ def domain_main_domain(operation_logger, new_main_domain=None): try: app_ssowatconf() except Exception as e: - import traceback - traceback.print_exc() logger.warning(str(e), exc_info=1) raise YunohostError("main_domain_change_failed") From 73777983855d51c11da398ff5634d2b4ce7ef920 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 29 Jan 2022 17:50:03 +0100 Subject: [PATCH 1144/1155] Revert "Tmp fix to try to debug the CI @_@" This reverts commit 3ca302b85dd4d4a86d1a406b8f56d8a9b0e95526. --- src/domain.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/domain.py b/src/domain.py index 1e869c985..ad3eaaed2 100644 --- a/src/domain.py +++ b/src/domain.py @@ -389,11 +389,7 @@ def domain_main_domain(operation_logger, new_main_domain=None): raise YunohostError("main_domain_change_failed") # Generate SSOwat configuration file - try: - app_ssowatconf() - except Exception as e: - logger.warning(str(e), exc_info=1) - raise YunohostError("main_domain_change_failed") + app_ssowatconf() # Regen configurations if os.path.exists("/etc/yunohost/installed"): From e46fe6b77b05704b3dab6f98de25722912a47a72 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 29 Jan 2022 18:05:23 +0100 Subject: [PATCH 1145/1155] Typo: self.hasattr is not a thing /o\ --- src/domain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/domain.py b/src/domain.py index ad3eaaed2..594c18d27 100644 --- a/src/domain.py +++ b/src/domain.py @@ -500,7 +500,7 @@ class DomainConfigPanel(ConfigPanel): super()._load_current_values() # FIXME: Ugly hack to save the registar id/value and reinject it in _load_current_values ... - if self.hasattr("registrar_id"): + if hasattr(self, "registrar_id"): self.values["registrar"] = self.registar_id From adb0e67b25d8385dea8f1b2b319321b11157faf1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 29 Jan 2022 18:28:07 +0100 Subject: [PATCH 1146/1155] Aaand hasattr not working as expected --- src/domain.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/domain.py b/src/domain.py index 594c18d27..338abc21b 100644 --- a/src/domain.py +++ b/src/domain.py @@ -500,7 +500,8 @@ class DomainConfigPanel(ConfigPanel): super()._load_current_values() # FIXME: Ugly hack to save the registar id/value and reinject it in _load_current_values ... - if hasattr(self, "registrar_id"): + filter_key = self.filter_key.split(".") if self.filter_key != "" else [] + if not filter_key or filter_key[0] == "dns": self.values["registrar"] = self.registar_id From e1ffe57282dc39a0ac9759011d68cb6ca086df1e Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Sat, 29 Jan 2022 17:48:05 +0000 Subject: [PATCH 1147/1155] [CI] Format code with Black --- src/app.py | 2 +- src/certificate.py | 12 +++--------- src/domain.py | 9 +++------ src/utils/config.py | 5 ++++- 4 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/app.py b/src/app.py index d7310c0ea..c200a66c6 100644 --- a/src/app.py +++ b/src/app.py @@ -680,7 +680,7 @@ def app_manifest(app): shutil.rmtree(extracted_app_folder) raw_questions = manifest.get("arguments", {}).get("install", []) - manifest['arguments']['install'] = hydrate_questions_with_choices(raw_questions) + manifest["arguments"]["install"] = hydrate_questions_with_choices(raw_questions) return manifest diff --git a/src/certificate.py b/src/certificate.py index 308b79f1c..2a9fb4ce9 100644 --- a/src/certificate.py +++ b/src/certificate.py @@ -111,9 +111,7 @@ def certificate_status(domains, full=False): return {"certificates": certificates} -def certificate_install( - domain_list, force=False, no_checks=False, self_signed=False -): +def certificate_install(domain_list, force=False, no_checks=False, self_signed=False): """ Install a Let's Encrypt certificate for given domains (all by default) @@ -231,9 +229,7 @@ def _certificate_install_selfsigned(domain_list, force=False): operation_logger.error(msg) -def _certificate_install_letsencrypt( - domains, force=False, no_checks=False -): +def _certificate_install_letsencrypt(domains, force=False, no_checks=False): from yunohost.domain import domain_list, _assert_domain_exists if not os.path.exists(ACCOUNT_KEY_FILE): @@ -297,9 +293,7 @@ def _certificate_install_letsencrypt( operation_logger.success() -def certificate_renew( - domains, force=False, no_checks=False, email=False -): +def certificate_renew(domains, force=False, no_checks=False, email=False): """ Renew Let's Encrypt certificate for given domains (all by default) diff --git a/src/domain.py b/src/domain.py index 338abc21b..ab6ce54a5 100644 --- a/src/domain.py +++ b/src/domain.py @@ -486,6 +486,7 @@ class DomainConfigPanel(ConfigPanel): filter_key = self.filter_key.split(".") if self.filter_key != "" else [] if not filter_key or filter_key[0] == "dns": from yunohost.dns import _get_registrar_config_section + toml["dns"]["registrar"] = _get_registrar_config_section(self.entity) # FIXME: Ugly hack to save the registar id/value and reinject it in _load_current_values ... @@ -535,17 +536,13 @@ def domain_cert_status(domain_list, full=False): return certificate_status(domain_list, full) -def domain_cert_install( - domain_list, force=False, no_checks=False, self_signed=False -): +def domain_cert_install(domain_list, force=False, no_checks=False, self_signed=False): from yunohost.certificate import certificate_install return certificate_install(domain_list, force, no_checks, self_signed) -def domain_cert_renew( - domain_list, force=False, no_checks=False, email=False -): +def domain_cert_renew(domain_list, force=False, no_checks=False, email=False): from yunohost.certificate import certificate_renew return certificate_renew(domain_list, force, no_checks, email) diff --git a/src/utils/config.py b/src/utils/config.py index f44fe5c22..ab18a19b0 100644 --- a/src/utils/config.py +++ b/src/utils/config.py @@ -1397,11 +1397,14 @@ def ask_questions_and_parse_answers( return out + def hydrate_questions_with_choices(raw_questions: List) -> List: out = [] for raw_question in raw_questions: - question = ARGUMENTS_TYPE_PARSERS[raw_question.get("type", "string")](raw_question) + question = ARGUMENTS_TYPE_PARSERS[raw_question.get("type", "string")]( + raw_question + ) if question.choices: raw_question["choices"] = question.choices raw_question["default"] = question.default From 0a59f863294ad6e57902387ea20cbf85933ebc55 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 29 Jan 2022 19:10:28 +0100 Subject: [PATCH 1148/1155] configpanel: oopsies, super()._apply() was only called when changing the default app :P --- src/domain.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/domain.py b/src/domain.py index 338abc21b..361536011 100644 --- a/src/domain.py +++ b/src/domain.py @@ -469,7 +469,13 @@ class DomainConfigPanel(ConfigPanel): other_app=app_map(raw=True)[self.entity]["/"]["id"], ) - super()._apply() + super()._apply() + + # Reload ssowat if default app changed + if ( + "default_app" in self.future_values + and self.future_values["default_app"] != self.values["default_app"] + ): app_ssowatconf() def _get_toml(self): From f0a01ba25db5c57f3efc86286012662b90e5aaed Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 29 Jan 2022 19:18:42 +0100 Subject: [PATCH 1149/1155] php73_to_php74: another search&replace for synapse --- src/migrations/0022_php73_to_php74_pools.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/migrations/0022_php73_to_php74_pools.py b/src/migrations/0022_php73_to_php74_pools.py index 3b6db919a..fd76adb77 100644 --- a/src/migrations/0022_php73_to_php74_pools.py +++ b/src/migrations/0022_php73_to_php74_pools.py @@ -17,6 +17,10 @@ NEWPHP_POOLS = "/etc/php/7.4/fpm/pool.d" OLDPHP_SOCKETS_PREFIX = "/run/php/php7.3-fpm" NEWPHP_SOCKETS_PREFIX = "/run/php/php7.4-fpm" +# Because of synapse é_è +OLDPHP_SOCKETS_PREFIX2 = "/run/php7.3-fpm" +NEWPHP_SOCKETS_PREFIX2 = "/run/php7.4-fpm" + MIGRATION_COMMENT = ( "; YunoHost note : this file was automatically moved from {}".format(OLDPHP_POOLS) ) @@ -50,6 +54,10 @@ class MyMigration(Migration): OLDPHP_SOCKETS_PREFIX, NEWPHP_SOCKETS_PREFIX, dest ) os.system(c) + c = "sed -i -e 's@{}@{}@g' {}".format( + OLDPHP_SOCKETS_PREFIX2, NEWPHP_SOCKETS_PREFIX2, dest + ) + os.system(c) # Also add a comment that it was automatically moved from php7.3 # (for human traceability and backward migration) @@ -69,6 +77,10 @@ class MyMigration(Migration): OLDPHP_SOCKETS_PREFIX, NEWPHP_SOCKETS_PREFIX, nf ) os.system(c) + c = "sed -i -e 's@{}@{}@g' {}".format( + OLDPHP_SOCKETS_PREFIX2, NEWPHP_SOCKETS_PREFIX2, nf + ) + os.system(c) os.system( "rm /etc/logrotate.d/php7.3-fpm" From 9ae7ec5930179f189d438b256347e456cb401caa Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 29 Jan 2022 19:19:19 +0100 Subject: [PATCH 1150/1155] php73_to_php74: stopping php7.3 before starting 7.4 should be more robust in case confs are conflicting --- src/migrations/0022_php73_to_php74_pools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/migrations/0022_php73_to_php74_pools.py b/src/migrations/0022_php73_to_php74_pools.py index fd76adb77..a2e5eae54 100644 --- a/src/migrations/0022_php73_to_php74_pools.py +++ b/src/migrations/0022_php73_to_php74_pools.py @@ -87,10 +87,10 @@ class MyMigration(Migration): ) # We remove this otherwise the logrotate cron will be unhappy # Reload/restart the php pools - _run_service_command("restart", "php7.4-fpm") - _run_service_command("enable", "php7.4-fpm") os.system("systemctl stop php7.3-fpm") os.system("systemctl disable php7.3-fpm") + _run_service_command("restart", "php7.4-fpm") + _run_service_command("enable", "php7.4-fpm") # Reload nginx _run_service_command("reload", "nginx") From ab5580cb1197087292a7be8ad3a85b8d31ba292f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 29 Jan 2022 19:23:07 +0100 Subject: [PATCH 1151/1155] Update changelof for 11.0.4 --- debian/changelog | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/debian/changelog b/debian/changelog index b0229fe8d..7e35024c8 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,20 @@ +yunohost (11.0.4) testing; urgency=low + + - [mod] certificate: drop unused 'staging' LE mode (4b78e8e3) + - [fix] cli: bash_completion was broken ([#1423](https://github.com/YunoHost/yunohost/pull/1423)) + - [enh] mdns: Wait for network to be fully up to start the service ([#1425](https://github.com/YunoHost/yunohost/pull/1425)) + - [fix] regenconf: make some systemctl enable/disable quiet (bccff1b4, 345e50ae) + - [fix] configpanels: Compute choices for the yunohost admin when installing an app ([#1427](https://github.com/YunoHost/yunohost/pull/1427)) + - [fix] configpanels: optimize _get_toml for domains to not load the whole DNS section stuff when just getting a simple info from another section (bf6252ac) + - [fix] configpanel: oopsies, could only change the default app for domain configs :P (0a59f863) + - [fix] php73_to_php74: another search&replace for synapse (f0a01ba2) + - [fix] php73_to_php74: stopping php7.3 before starting 7.4 should be more robust in case confs are conflicting (9ae7ec59) + - [i18n] Translations updated for French, Ukrainian + + Thanks to all contributors <3 ! (Éric Gaspar, Kay0u, Tagadda, tituspijean, Tymofii-Lytvynenko) + + -- Alexandre Aubin Sat, 29 Jan 2022 19:19:44 +0100 + yunohost (11.0.3) testing; urgency=low - [enh] mail: Add SNI support for postfix and dovecot ([#1413](https://github.com/YunoHost/yunohost/pull/1413)) From f2b95e5fbeabec1f4a99e81c6a93aac0b876223d Mon Sep 17 00:00:00 2001 From: Tagadda <36127788+Tagadda@users.noreply.github.com> Date: Sun, 30 Jan 2022 20:45:06 +0000 Subject: [PATCH 1152/1155] configpanel: filter as a simple_js_expression --- share/config_domain.toml | 2 +- src/utils/config.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/share/config_domain.toml b/share/config_domain.toml index b0131f1c1..65e755365 100644 --- a/share/config_domain.toml +++ b/share/config_domain.toml @@ -14,7 +14,7 @@ i18n = "domain_config" [feature.app] [feature.app.default_app] type = "app" - filters = ["is_webapp"] + filter = "is_webapp" default = "_none" [feature.mail] diff --git a/src/utils/config.py b/src/utils/config.py index ab18a19b0..1e4f4cd1e 100644 --- a/src/utils/config.py +++ b/src/utils/config.py @@ -440,7 +440,7 @@ class ConfigPanel: "step", "accept", "redact", - "filters", + "filter", ], "defaults": {}, }, @@ -706,7 +706,7 @@ class Question: self.ask = question.get("ask", {"en": self.name}) self.help = question.get("help") self.redact = question.get("redact", False) - self.filters = question.get("filters", []) + self.filter = question.get("filter", []) # .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 @@ -1142,12 +1142,12 @@ class AppQuestion(Question): super().__init__(question, context, hooks) apps = app_list(full=True)["apps"] - for _filter in self.filters: - apps = [app for app in apps if _filter in app and app[_filter]] + + apps = [app for app in apps if evaluate_simple_js_expression(self.filter, context=app)] def _app_display(app): - domain_path = f" ({app['domain_path']})" if "domain_path" in app else "" - return app["label"] + domain_path + domain_path_or_id = f" ({app.get('domain_path', app['id'])})" + return app["label"] + domain_path_or_id self.choices = {"_none": "---"} self.choices.update({app["id"]: _app_display(app) for app in apps}) From 3de3205648cdfaf530148defc51b58460e03ea10 Mon Sep 17 00:00:00 2001 From: Tagadda <36127788+Tagadda@users.noreply.github.com> Date: Sun, 30 Jan 2022 22:39:26 +0000 Subject: [PATCH 1153/1155] set default filter to always return true --- src/utils/config.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/utils/config.py b/src/utils/config.py index 1e4f4cd1e..0b2aca414 100644 --- a/src/utils/config.py +++ b/src/utils/config.py @@ -706,7 +706,7 @@ class Question: self.ask = question.get("ask", {"en": self.name}) self.help = question.get("help") self.redact = question.get("redact", False) - self.filter = question.get("filter", []) + self.filter = question.get("filter", "true") # .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 @@ -1142,8 +1142,9 @@ class AppQuestion(Question): super().__init__(question, context, hooks) apps = app_list(full=True)["apps"] - - apps = [app for app in apps if evaluate_simple_js_expression(self.filter, context=app)] + + if self.filter: + apps = [app for app in apps if evaluate_simple_js_expression(self.filter, context=app)] def _app_display(app): domain_path_or_id = f" ({app.get('domain_path', app['id'])})" From d28f725762f8d60b1bf772a4d492ed9affde95ab Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Mon, 31 Jan 2022 02:32:46 +0000 Subject: [PATCH 1154/1155] [CI] Format code with Black --- src/diagnosers/50-systemresources.py | 2 +- src/migrations/0021_migrate_to_bullseye.py | 2 +- src/tools.py | 2 +- src/utils/config.py | 6 +++++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/diagnosers/50-systemresources.py b/src/diagnosers/50-systemresources.py index 1096daebf..6ac7f0ec4 100644 --- a/src/diagnosers/50-systemresources.py +++ b/src/diagnosers/50-systemresources.py @@ -18,7 +18,7 @@ class MyDiagnoser(Diagnoser): def run(self): - MB = 1024 ** 2 + MB = 1024**2 GB = MB * 1024 # diff --git a/src/migrations/0021_migrate_to_bullseye.py b/src/migrations/0021_migrate_to_bullseye.py index f4361cb19..551a6f64b 100644 --- a/src/migrations/0021_migrate_to_bullseye.py +++ b/src/migrations/0021_migrate_to_bullseye.py @@ -293,7 +293,7 @@ class MyMigration(Migration): raise YunohostError("migration_0021_not_buster") # Have > 1 Go free space on /var/ ? - if free_space_in_directory("/var/") / (1024 ** 3) < 1.0: + if free_space_in_directory("/var/") / (1024**3) < 1.0: raise YunohostError("migration_0021_not_enough_free_space") # Check system is up to date diff --git a/src/tools.py b/src/tools.py index f85d0abdf..45163135f 100644 --- a/src/tools.py +++ b/src/tools.py @@ -226,7 +226,7 @@ def tools_postinstall( main_space = sum( psutil.disk_usage(d.mountpoint).total for d in main_disk_partitions ) - GB = 1024 ** 3 + GB = 1024**3 if not force_diskspace and main_space < 10 * GB: raise YunohostValidationError("postinstall_low_rootfsspace") diff --git a/src/utils/config.py b/src/utils/config.py index 0b2aca414..b8ea93589 100644 --- a/src/utils/config.py +++ b/src/utils/config.py @@ -1144,7 +1144,11 @@ class AppQuestion(Question): apps = app_list(full=True)["apps"] if self.filter: - apps = [app for app in apps if evaluate_simple_js_expression(self.filter, context=app)] + apps = [ + app + for app in apps + if evaluate_simple_js_expression(self.filter, context=app) + ] def _app_display(app): domain_path_or_id = f" ({app.get('domain_path', app['id'])})" From de6844250f11952b13f706717e15260ada64f836 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 1 Feb 2022 15:43:03 +0100 Subject: [PATCH 1155/1155] postinstall: migrate_to_bullseye should be skipped on bullseye --- src/tools.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/tools.py b/src/tools.py index 45163135f..c6283e701 100644 --- a/src/tools.py +++ b/src/tools.py @@ -930,10 +930,6 @@ def _skip_all_migrations(): all_migrations = _get_migrations_list() new_states = {"migrations": {}} for migration in all_migrations: - # Don't skip bullseye migration while we're - # still on buster - if "migrate_to_bullseye" in migration.id: - continue new_states["migrations"][migration.id] = "skipped" write_to_yaml(MIGRATIONS_STATE_PATH, new_states)