From cbb85f8c3b860422382c5ea5353b251127464442 Mon Sep 17 00:00:00 2001 From: axolotle Date: Sun, 24 Sep 2023 17:13:33 +0200 Subject: [PATCH 01/34] dyndns: handle too many requests in availability testing --- locales/en.json | 1 + src/dyndns.py | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/locales/en.json b/locales/en.json index 60f58f639..05194c7f0 100644 --- a/locales/en.json +++ b/locales/en.json @@ -396,6 +396,7 @@ "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` and/or `sudo dpkg --audit`.", "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_availability_too_many_requests": "YunoHost's free domain service received too many requests from you, wait 1 minute or so before trying again.", "dyndns_could_not_check_available": "Could not check if {domain} is available on {provider}.", "dyndns_domain_not_provided": "DynDNS provider {provider} cannot provide domain {domain}.", "dyndns_ip_update_failed": "Could not update IP address to DynDNS", diff --git a/src/dyndns.py b/src/dyndns.py index a3afd655f..c9bf8afe6 100644 --- a/src/dyndns.py +++ b/src/dyndns.py @@ -63,19 +63,28 @@ def _dyndns_available(domain): Returns: True if the domain is available, False otherwise. """ + import requests # lazy loading this module for performance reasons + 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 = requests.get(f"https://{DYNDNS_PROVIDER}/test/{domain}", timeout=30) except MoulinetteError as e: logger.error(str(e)) raise YunohostError( "dyndns_could_not_check_available", domain=domain, provider=DYNDNS_PROVIDER ) - return r == f"Domain {domain} is available" + if r.status_code == 200: + return r == f"Domain {domain} is available" + elif r.status_code == 409: + return False + elif r.status_code == 429: + raise YunohostValidationError("dyndns_availability_too_many_requests") + else: + raise YunohostError( + "dyndns_could_not_check_available", domain=domain, provider=DYNDNS_PROVIDER + ) @is_unit_operation(exclude=["recovery_password"]) From 322fc3b7124d0cf5d274b4d822a511a896670588 Mon Sep 17 00:00:00 2001 From: axolotle Date: Sun, 24 Sep 2023 17:15:56 +0200 Subject: [PATCH 02/34] dyndns: try to unsubscribe with password before subscribing if domain is not available --- src/dyndns.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/dyndns.py b/src/dyndns.py index c9bf8afe6..c634f169d 100644 --- a/src/dyndns.py +++ b/src/dyndns.py @@ -103,14 +103,26 @@ def dyndns_subscribe(operation_logger, domain=None, recovery_password=None): "dyndns_domain_not_provided", domain=domain, provider=DYNDNS_PROVIDER ) - # Verify if domain is available - if not _dyndns_available(domain): - raise YunohostValidationError("dyndns_unavailable", domain=domain) - # Check adding another dyndns domain is still allowed if not is_subscribing_allowed(): raise YunohostValidationError("domain_dyndns_already_subscribed") + # Verify if domain is available + if not _dyndns_available(domain): + # Prompt for a password if running in CLI and no password provided + if not recovery_password and Moulinette.interface.type == "cli": + logger.warning(m18n.n("ask_dyndns_recovery_password_explain_unavailable")) + recovery_password = Moulinette.prompt( + m18n.n("ask_dyndns_recovery_password"), is_password=True, confirm=True + ) + + if recovery_password: + # Try to unsubscribe the domain so it can be subscribed again + # If successful, it will be resubscribed with the same recovery password + dyndns_unsubscribe(domain=domain, recovery_password=recovery_password) + else: + raise YunohostValidationError("dyndns_unavailable", domain=domain) + # Prompt for a password if running in CLI and no password provided if not recovery_password and Moulinette.interface.type == "cli": logger.warning(m18n.n("ask_dyndns_recovery_password_explain")) From 67e28567ff6d4865dc8c3a3c3a2de89885b0b3e5 Mon Sep 17 00:00:00 2001 From: axolotle Date: Sun, 24 Sep 2023 17:19:24 +0200 Subject: [PATCH 03/34] dyndns: switch to ValidationError for some service response --- locales/en.json | 2 ++ src/dyndns.py | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index 05194c7f0..d9f42dede 100644 --- a/locales/en.json +++ b/locales/en.json @@ -91,6 +91,7 @@ "ask_new_path": "New path", "ask_password": "Password", "ask_dyndns_recovery_password_explain": "Please pick a recovery password for your DynDNS domain, in case you need to reset it later.", + "ask_dyndns_recovery_password_explain_unavailable": "This DynDNS domain is not available, please enter the recovery password if this your domain.", "ask_dyndns_recovery_password": "DynDNS recovery password", "ask_dyndns_recovery_password_explain_during_unsubscribe": "Please enter the recovery password for this DynDNS domain.", "ask_user_domain": "Domain to use for the user's email address and XMPP account", @@ -411,6 +412,7 @@ "dyndns_unsubscribed": "DynDNS domain unsubscribed", "dyndns_unsubscribe_denied": "Failed to unsubscribe domain: invalid credentials", "dyndns_unsubscribe_already_unsubscribed": "Domain is already unsubscribed", + "dyndns_unsubscribe_too_many_requests": "YunoHost's free domain service received too many requests from you, wait 1 hour or so before trying again.", "dyndns_set_recovery_password_denied": "Failed to set recovery password: invalid key", "dyndns_set_recovery_password_unknown_domain": "Failed to set recovery password: domain not registered", "dyndns_set_recovery_password_invalid_password": "Failed to set recovery password: password is not strong enough", diff --git a/src/dyndns.py b/src/dyndns.py index c634f169d..8efa5e6df 100644 --- a/src/dyndns.py +++ b/src/dyndns.py @@ -273,9 +273,11 @@ def dyndns_unsubscribe(operation_logger, domain, recovery_password=None): # in /etc/yunohost/dyndns regen_conf(["yunohost"]) elif r.status_code == 403: - raise YunohostError("dyndns_unsubscribe_denied") + raise YunohostValidationError("dyndns_unsubscribe_denied") elif r.status_code == 409: - raise YunohostError("dyndns_unsubscribe_already_unsubscribed") + raise YunohostValidationError("dyndns_unsubscribe_already_unsubscribed") + elif r.status_code == 429: + raise YunohostValidationError("dyndns_unsubscribe_too_many_requests") else: raise YunohostError( "dyndns_unsubscribe_failed", From 65843bda6dd01ec679c809f8c501d12f06e8e6fd Mon Sep 17 00:00:00 2001 From: axolotle Date: Sun, 24 Sep 2023 18:11:28 +0200 Subject: [PATCH 04/34] dyndns: add postinstall recovery password handling --- src/tools.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/tools.py b/src/tools.py index cd48f00ee..2b0b566e2 100644 --- a/src/tools.py +++ b/src/tools.py @@ -157,7 +157,7 @@ def tools_postinstall( overwrite_root_password=True, ): from yunohost.dyndns import _dyndns_available - from yunohost.utils.dns import is_yunohost_dyndns_domain + from yunohost.utils.dns import is_yunohost_dyndns_domain, dyndns_unsubscribe from yunohost.utils.password import ( assert_password_is_strong_enough, assert_password_is_compatible, @@ -218,7 +218,14 @@ def tools_postinstall( ) else: if not available: - raise YunohostValidationError("dyndns_unavailable", domain=domain) + if dyndns_recovery_password: + # Try to unsubscribe the domain so it can be subscribed again + # If successful, it will be resubscribed with the same recovery password + dyndns_unsubscribe( + domain=domain, recovery_password=dyndns_recovery_password + ) + else: + raise YunohostValidationError("dyndns_unavailable", domain=domain) if os.system("iptables -V >/dev/null 2>/dev/null") != 0: raise YunohostValidationError( From 97c2cdc593966b554ad792fd54d33af6be3d84dd Mon Sep 17 00:00:00 2001 From: axolotle Date: Mon, 25 Sep 2023 13:55:01 +0200 Subject: [PATCH 05/34] domain: move domain files removal so dyndns API key still exists while trying to unsubscribe --- src/domain.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/domain.py b/src/domain.py index 8fc9799cd..40e8b50d0 100644 --- a/src/domain.py +++ b/src/domain.py @@ -342,6 +342,7 @@ def domain_remove( dyndns_recovery_password -- Recovery password used at the creation of the DynDNS domain ignore_dyndns -- If we just remove the DynDNS domain, without unsubscribing """ + import glob from yunohost.hook import hook_callback from yunohost.app import app_ssowatconf, app_info, app_remove from yunohost.utils.ldap import _get_ldap_interface @@ -427,15 +428,6 @@ def domain_remove( global domain_list_cache domain_list_cache = [] - stuff_to_delete = [ - f"/etc/yunohost/certs/{domain}", - f"/etc/yunohost/dyndns/K{domain}.+*", - f"{DOMAIN_SETTINGS_DIR}/{domain}.yml", - ] - - for stuff in stuff_to_delete: - 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 ... # There are a few ideas why this happens (like backup/restore nginx @@ -469,6 +461,11 @@ def domain_remove( domain=domain, recovery_password=dyndns_recovery_password ) + rm(f"/etc/yunohost/certs/{domain}", force=True, recursive=True) + for key_file in glob.glob(f"/etc/yunohost/dyndns/K{domain}.+*"): + rm(key_file, force=True) + rm(f"{DOMAIN_SETTINGS_DIR}/{domain}.yml", force=True) + logger.success(m18n.n("domain_deleted")) From fc68f769f9c2e3923838be99fe8701ca239cdd48 Mon Sep 17 00:00:00 2001 From: axolotle Date: Mon, 25 Sep 2023 14:37:18 +0200 Subject: [PATCH 06/34] domain: add recovery passoword in config panel --- src/dns.py | 3 +++ src/domain.py | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/dns.py b/src/dns.py index e25d0f3ec..fbb461463 100644 --- a/src/dns.py +++ b/src/dns.py @@ -545,6 +545,9 @@ def _get_registrar_config_section(domain): "value": "yunohost", } ) + registrar_infos["recovery_password"] = OrderedDict( + {"type": "password", "ask": m18n.n("ask_dyndns_recovery_password"), "default": ""} + ) return OrderedDict(registrar_infos) elif is_special_use_tld(dns_zone): registrar_infos["registrar"] = OrderedDict( diff --git a/src/domain.py b/src/domain.py index 40e8b50d0..b68c7dffc 100644 --- a/src/domain.py +++ b/src/domain.py @@ -317,6 +317,11 @@ def domain_add( pass raise e + if dyndns and dyndns_recovery_password: + domain_config_set( + domain, "dns.registrar.recovery_password", dyndns_recovery_password + ) + hook_callback("post_domain_add", args=[domain]) logger.success(m18n.n("domain_created")) @@ -704,6 +709,14 @@ class DomainConfigPanel(ConfigPanel): domain=self.entity, other_app=app_map(raw=True)[self.entity]["/"]["id"], ) + if ( + "recovery_password" in self.future_values + and self.future_values["recovery_password"] + != self.values["recovery_password"] + ): + domain_dyndns_set_recovery_password( + self.entity, self.future_values["recovery_password"] + ) super()._apply() From e9802ce2dca0f91a175f121ebc31ff6f9a99c1f4 Mon Sep 17 00:00:00 2001 From: axolotle Date: Wed, 27 Sep 2023 19:31:50 +0200 Subject: [PATCH 07/34] domain dyndns: do not save recovery password --- src/domain.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/domain.py b/src/domain.py index b68c7dffc..154da54dc 100644 --- a/src/domain.py +++ b/src/domain.py @@ -317,11 +317,6 @@ def domain_add( pass raise e - if dyndns and dyndns_recovery_password: - domain_config_set( - domain, "dns.registrar.recovery_password", dyndns_recovery_password - ) - hook_callback("post_domain_add", args=[domain]) logger.success(m18n.n("domain_created")) @@ -710,13 +705,14 @@ class DomainConfigPanel(ConfigPanel): other_app=app_map(raw=True)[self.entity]["/"]["id"], ) if ( - "recovery_password" in self.future_values - and self.future_values["recovery_password"] - != self.values["recovery_password"] + "recovery_password" in self.new_values + and self.new_values["recovery_password"] ): domain_dyndns_set_recovery_password( - self.entity, self.future_values["recovery_password"] + self.entity, self.new_values["recovery_password"] ) + # Do not save password in yaml settings + del self.new_values["recovery_password"] super()._apply() From 2e86bae4efbeacff1026af9039f47090c65d504f Mon Sep 17 00:00:00 2001 From: axolotle Date: Wed, 27 Sep 2023 19:58:52 +0200 Subject: [PATCH 08/34] domain: make sure of it --- src/domain.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/domain.py b/src/domain.py index 154da54dc..04d8370e4 100644 --- a/src/domain.py +++ b/src/domain.py @@ -712,7 +712,9 @@ class DomainConfigPanel(ConfigPanel): self.entity, self.new_values["recovery_password"] ) # Do not save password in yaml settings + del self.values["recovery_password"] del self.new_values["recovery_password"] + assert "recovery_password" not in self.future_values super()._apply() From cd079459b98566d4fa2042ad1b30cde6a5a681cf Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 27 Sep 2023 20:47:17 +0200 Subject: [PATCH 09/34] dyndns: fix import typo --- src/tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools.py b/src/tools.py index 2b0b566e2..088400067 100644 --- a/src/tools.py +++ b/src/tools.py @@ -156,8 +156,8 @@ def tools_postinstall( force_diskspace=False, overwrite_root_password=True, ): - from yunohost.dyndns import _dyndns_available - from yunohost.utils.dns import is_yunohost_dyndns_domain, dyndns_unsubscribe + from yunohost.dyndns import _dyndns_available, dyndns_unsubscribe + from yunohost.utils.dns import is_yunohost_dyndns_domain from yunohost.utils.password import ( assert_password_is_strong_enough, assert_password_is_compatible, From 2e4f2e8e3a7df7f45fb228fefe1ab8b757cc86f3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 27 Sep 2023 20:57:16 +0200 Subject: [PATCH 10/34] quality: unused import --- src/dyndns.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/dyndns.py b/src/dyndns.py index 8efa5e6df..78a6b0a90 100644 --- a/src/dyndns.py +++ b/src/dyndns.py @@ -27,7 +27,6 @@ from moulinette import Moulinette, m18n from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger 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 yunohost.domain import _get_maindomain From 82affd298452cbab360df4d93303c9817f146707 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 28 Sep 2023 14:19:00 +0200 Subject: [PATCH 11/34] dyndns: fix availability check, polish UX --- locales/en.json | 6 +++--- src/domain.py | 11 +++++++---- src/dyndns.py | 4 ++-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/locales/en.json b/locales/en.json index d9f42dede..dbf95caa6 100644 --- a/locales/en.json +++ b/locales/en.json @@ -91,7 +91,7 @@ "ask_new_path": "New path", "ask_password": "Password", "ask_dyndns_recovery_password_explain": "Please pick a recovery password for your DynDNS domain, in case you need to reset it later.", - "ask_dyndns_recovery_password_explain_unavailable": "This DynDNS domain is not available, please enter the recovery password if this your domain.", + "ask_dyndns_recovery_password_explain_unavailable": "This DynDNS domain is aleady registered. If you are the person who originally registered this domain, you may enter the recovery password to reclaim this domain.", "ask_dyndns_recovery_password": "DynDNS recovery password", "ask_dyndns_recovery_password_explain_during_unsubscribe": "Please enter the recovery password for this DynDNS domain.", "ask_user_domain": "Domain to use for the user's email address and XMPP account", @@ -397,7 +397,7 @@ "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` and/or `sudo dpkg --audit`.", "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_availability_too_many_requests": "YunoHost's free domain service received too many requests from you, wait 1 minute or so before trying again.", + "dyndns_availability_too_many_requests": "YunoHost's dyndns service received too many requests from you, wait 1 minute or so before trying again.", "dyndns_could_not_check_available": "Could not check if {domain} is available on {provider}.", "dyndns_domain_not_provided": "DynDNS provider {provider} cannot provide domain {domain}.", "dyndns_ip_update_failed": "Could not update IP address to DynDNS", @@ -412,7 +412,7 @@ "dyndns_unsubscribed": "DynDNS domain unsubscribed", "dyndns_unsubscribe_denied": "Failed to unsubscribe domain: invalid credentials", "dyndns_unsubscribe_already_unsubscribed": "Domain is already unsubscribed", - "dyndns_unsubscribe_too_many_requests": "YunoHost's free domain service received too many requests from you, wait 1 hour or so before trying again.", + "dyndns_unsubscribe_too_many_requests": "YunoHost's dyndns service received too many requests from you, wait 1 hour or so before trying again.", "dyndns_set_recovery_password_denied": "Failed to set recovery password: invalid key", "dyndns_set_recovery_password_unknown_domain": "Failed to set recovery password: domain not registered", "dyndns_set_recovery_password_invalid_password": "Failed to set recovery password: password is not strong enough", diff --git a/src/domain.py b/src/domain.py index 04d8370e4..beedf43e0 100644 --- a/src/domain.py +++ b/src/domain.py @@ -456,10 +456,13 @@ def domain_remove( # If a password is provided, delete the DynDNS record if dyndns: - # Actually unsubscribe - domain_dyndns_unsubscribe( - domain=domain, recovery_password=dyndns_recovery_password - ) + try: + # Actually unsubscribe + domain_dyndns_unsubscribe( + domain=domain, recovery_password=dyndns_recovery_password + ) + except Exception as e: + logger.warning(str(e)) rm(f"/etc/yunohost/certs/{domain}", force=True, recursive=True) for key_file in glob.glob(f"/etc/yunohost/dyndns/K{domain}.+*"): diff --git a/src/dyndns.py b/src/dyndns.py index 78a6b0a90..70f44672d 100644 --- a/src/dyndns.py +++ b/src/dyndns.py @@ -75,7 +75,7 @@ def _dyndns_available(domain): ) if r.status_code == 200: - return r == f"Domain {domain} is available" + return r.text.strip('"') == f"Domain {domain} is available" elif r.status_code == 409: return False elif r.status_code == 429: @@ -112,7 +112,7 @@ def dyndns_subscribe(operation_logger, domain=None, recovery_password=None): if not recovery_password and Moulinette.interface.type == "cli": logger.warning(m18n.n("ask_dyndns_recovery_password_explain_unavailable")) recovery_password = Moulinette.prompt( - m18n.n("ask_dyndns_recovery_password"), is_password=True, confirm=True + m18n.n("ask_dyndns_recovery_password"), is_password=True ) if recovery_password: From 53ffe3c1c07078998d192fb2128fdc5a8fa6868c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 28 Sep 2023 14:25:36 +0200 Subject: [PATCH 12/34] dyndns: fix tests --- src/domain.py | 6 ++++-- src/tests/test_domains.py | 12 +++--------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/domain.py b/src/domain.py index beedf43e0..a503a129c 100644 --- a/src/domain.py +++ b/src/domain.py @@ -715,8 +715,10 @@ class DomainConfigPanel(ConfigPanel): self.entity, self.new_values["recovery_password"] ) # Do not save password in yaml settings - del self.values["recovery_password"] - del self.new_values["recovery_password"] + if "recovery_password" in self.values: + del self.values["recovery_password"] + if "recovery_password" in self.new_values: + del self.new_values["recovery_password"] assert "recovery_password" not in self.future_values super()._apply() diff --git a/src/tests/test_domains.py b/src/tests/test_domains.py index 1bbbb7890..03141b4fe 100644 --- a/src/tests/test_domains.py +++ b/src/tests/test_domains.py @@ -75,12 +75,13 @@ def test_domain_add(): assert TEST_DOMAINS[2] in domain_list()["domains"] -def test_domain_add_subscribe(): +def test_domain_add_and_remove_dyndns(): time.sleep(35) # Dynette blocks requests that happen too frequently assert TEST_DYNDNS_DOMAIN not in domain_list()["domains"] domain_add(TEST_DYNDNS_DOMAIN, dyndns_recovery_password=TEST_DYNDNS_PASSWORD) assert TEST_DYNDNS_DOMAIN in domain_list()["domains"] - + domain_remove(TEST_DYNDNS_DOMAIN, dyndns_recovery_password=TEST_DYNDNS_PASSWORD) + assert TEST_DYNDNS_DOMAIN not in domain_list()["domains"] def test_domain_add_existing_domain(): with pytest.raises(MoulinetteError): @@ -94,13 +95,6 @@ def test_domain_remove(): assert TEST_DOMAINS[1] not in domain_list()["domains"] -def test_domain_remove_unsubscribe(): - time.sleep(35) # Dynette blocks requests that happen too frequently - assert TEST_DYNDNS_DOMAIN in domain_list()["domains"] - domain_remove(TEST_DYNDNS_DOMAIN, dyndns_recovery_password=TEST_DYNDNS_PASSWORD) - assert TEST_DYNDNS_DOMAIN not in domain_list()["domains"] - - def test_main_domain(): current_main_domain = _get_maindomain() assert domain_main_domain()["current_main_domain"] == current_main_domain From 125af4670f74cf65890d4dc87d44749a9b546b32 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 28 Sep 2023 14:29:18 +0200 Subject: [PATCH 13/34] dyndns: typo --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index dbf95caa6..dddad3ef9 100644 --- a/locales/en.json +++ b/locales/en.json @@ -91,7 +91,7 @@ "ask_new_path": "New path", "ask_password": "Password", "ask_dyndns_recovery_password_explain": "Please pick a recovery password for your DynDNS domain, in case you need to reset it later.", - "ask_dyndns_recovery_password_explain_unavailable": "This DynDNS domain is aleady registered. If you are the person who originally registered this domain, you may enter the recovery password to reclaim this domain.", + "ask_dyndns_recovery_password_explain_unavailable": "This DynDNS domain is already registered. If you are the person who originally registered this domain, you may enter the recovery password to reclaim this domain.", "ask_dyndns_recovery_password": "DynDNS recovery password", "ask_dyndns_recovery_password_explain_during_unsubscribe": "Please enter the recovery password for this DynDNS domain.", "ask_user_domain": "Domain to use for the user's email address and XMPP account", From 0f109db6cadfa8bf29d45bfd76278197cf4a37e2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 28 Sep 2023 14:29:53 +0200 Subject: [PATCH 14/34] dyndns/domain_remove: be paranoid and keep the dyndns unsubscribe + cert/dyndns/setting cleanup before the regenconf --- src/domain.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/domain.py b/src/domain.py index a503a129c..79194df2d 100644 --- a/src/domain.py +++ b/src/domain.py @@ -428,6 +428,21 @@ def domain_remove( global domain_list_cache domain_list_cache = [] + # If a password is provided, delete the DynDNS record + if dyndns: + try: + # Actually unsubscribe + domain_dyndns_unsubscribe( + domain=domain, recovery_password=dyndns_recovery_password + ) + except Exception as e: + logger.warning(str(e)) + + rm(f"/etc/yunohost/certs/{domain}", force=True, recursive=True) + for key_file in glob.glob(f"/etc/yunohost/dyndns/K{domain}.+*"): + rm(key_file, force=True) + rm(f"{DOMAIN_SETTINGS_DIR}/{domain}.yml", force=True) + # 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 @@ -454,21 +469,6 @@ def domain_remove( hook_callback("post_domain_remove", args=[domain]) - # If a password is provided, delete the DynDNS record - if dyndns: - try: - # Actually unsubscribe - domain_dyndns_unsubscribe( - domain=domain, recovery_password=dyndns_recovery_password - ) - except Exception as e: - logger.warning(str(e)) - - rm(f"/etc/yunohost/certs/{domain}", force=True, recursive=True) - for key_file in glob.glob(f"/etc/yunohost/dyndns/K{domain}.+*"): - rm(key_file, force=True) - rm(f"{DOMAIN_SETTINGS_DIR}/{domain}.yml", force=True) - logger.success(m18n.n("domain_deleted")) From ca1e088f2948d53905d3477fbcccb77fc61ae8e3 Mon Sep 17 00:00:00 2001 From: axolotle Date: Thu, 28 Sep 2023 15:41:57 +0200 Subject: [PATCH 15/34] test:domains: add complex recovery password test --- src/tests/test_domains.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/tests/test_domains.py b/src/tests/test_domains.py index 03141b4fe..b968777d2 100644 --- a/src/tests/test_domains.py +++ b/src/tests/test_domains.py @@ -83,6 +83,32 @@ def test_domain_add_and_remove_dyndns(): domain_remove(TEST_DYNDNS_DOMAIN, dyndns_recovery_password=TEST_DYNDNS_PASSWORD) assert TEST_DYNDNS_DOMAIN not in domain_list()["domains"] + +def test_domain_dyndns_recovery(): + # time.sleep(35) + assert TEST_DYNDNS_DOMAIN not in domain_list()["domains"] + # add domain without recovery password + domain_add(TEST_DYNDNS_DOMAIN) + assert TEST_DYNDNS_DOMAIN in domain_list()["domains"] + # set the recovery password with config panel + domain_config_set(TEST_DYNDNS_DOMAIN, "dns.registrar.recovery_password", TEST_DYNDNS_PASSWORD) + # remove domain without unsubscribing + domain_remove(TEST_DYNDNS_DOMAIN, ignore_dyndns=True) + assert TEST_DYNDNS_DOMAIN not in domain_list()["domains"] + # readding domain with bad password should fail + with pytest.raises(YunohostValidationError): + domain_add( + TEST_DYNDNS_DOMAIN, dyndns_recovery_password="wrong" + TEST_DYNDNS_PASSWORD + ) + assert TEST_DYNDNS_DOMAIN not in domain_list()["domains"] + # readding domain with password should work + domain_add(TEST_DYNDNS_DOMAIN, dyndns_recovery_password=TEST_DYNDNS_PASSWORD) + assert TEST_DYNDNS_DOMAIN in domain_list()["domains"] + # remove the dyndns domain + domain_remove(TEST_DYNDNS_DOMAIN, dyndns_recovery_password=TEST_DYNDNS_PASSWORD) + assert TEST_DYNDNS_DOMAIN not in domain_list()["domains"] + + def test_domain_add_existing_domain(): with pytest.raises(MoulinetteError): assert TEST_DOMAINS[1] in domain_list()["domains"] From 4a7b2b2cbfdb57f50dc2376272942050a922462e Mon Sep 17 00:00:00 2001 From: axolotle Date: Thu, 28 Sep 2023 15:54:57 +0200 Subject: [PATCH 16/34] domains: unique i18n key for dyndns too many requests --- locales/en.json | 3 +-- src/dyndns.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/locales/en.json b/locales/en.json index dddad3ef9..e7deb9e95 100644 --- a/locales/en.json +++ b/locales/en.json @@ -397,7 +397,6 @@ "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` and/or `sudo dpkg --audit`.", "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_availability_too_many_requests": "YunoHost's dyndns service received too many requests from you, wait 1 minute or so before trying again.", "dyndns_could_not_check_available": "Could not check if {domain} is available on {provider}.", "dyndns_domain_not_provided": "DynDNS provider {provider} cannot provide domain {domain}.", "dyndns_ip_update_failed": "Could not update IP address to DynDNS", @@ -408,11 +407,11 @@ "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_subscribed": "DynDNS domain subscribed", "dyndns_subscribe_failed": "Could not subscribe DynDNS domain: {error}", + "dyndns_too_many_requests": "YunoHost's dyndns service received too many requests from you, wait 1 hour or so before trying again.", "dyndns_unsubscribe_failed": "Could not unsubscribe DynDNS domain: {error}", "dyndns_unsubscribed": "DynDNS domain unsubscribed", "dyndns_unsubscribe_denied": "Failed to unsubscribe domain: invalid credentials", "dyndns_unsubscribe_already_unsubscribed": "Domain is already unsubscribed", - "dyndns_unsubscribe_too_many_requests": "YunoHost's dyndns service received too many requests from you, wait 1 hour or so before trying again.", "dyndns_set_recovery_password_denied": "Failed to set recovery password: invalid key", "dyndns_set_recovery_password_unknown_domain": "Failed to set recovery password: domain not registered", "dyndns_set_recovery_password_invalid_password": "Failed to set recovery password: password is not strong enough", diff --git a/src/dyndns.py b/src/dyndns.py index 70f44672d..387f33930 100644 --- a/src/dyndns.py +++ b/src/dyndns.py @@ -79,7 +79,7 @@ def _dyndns_available(domain): elif r.status_code == 409: return False elif r.status_code == 429: - raise YunohostValidationError("dyndns_availability_too_many_requests") + raise YunohostValidationError("dyndns_too_many_requests") else: raise YunohostError( "dyndns_could_not_check_available", domain=domain, provider=DYNDNS_PROVIDER @@ -276,7 +276,7 @@ def dyndns_unsubscribe(operation_logger, domain, recovery_password=None): elif r.status_code == 409: raise YunohostValidationError("dyndns_unsubscribe_already_unsubscribed") elif r.status_code == 429: - raise YunohostValidationError("dyndns_unsubscribe_too_many_requests") + raise YunohostValidationError("dyndns_too_many_requests") else: raise YunohostError( "dyndns_unsubscribe_failed", From c019f7f24ab80a9ab63d8f4bb8656dad24186f75 Mon Sep 17 00:00:00 2001 From: axolotle Date: Thu, 28 Sep 2023 16:48:31 +0200 Subject: [PATCH 17/34] test:domains: remove sleep --- src/tests/test_domains.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tests/test_domains.py b/src/tests/test_domains.py index b968777d2..bc2c31465 100644 --- a/src/tests/test_domains.py +++ b/src/tests/test_domains.py @@ -76,7 +76,7 @@ def test_domain_add(): def test_domain_add_and_remove_dyndns(): - time.sleep(35) # Dynette blocks requests that happen too frequently + # Devs: if you get `too_many_request` errors, ask the team to add your IP to the rate limit excempt assert TEST_DYNDNS_DOMAIN not in domain_list()["domains"] domain_add(TEST_DYNDNS_DOMAIN, dyndns_recovery_password=TEST_DYNDNS_PASSWORD) assert TEST_DYNDNS_DOMAIN in domain_list()["domains"] @@ -85,7 +85,7 @@ def test_domain_add_and_remove_dyndns(): def test_domain_dyndns_recovery(): - # time.sleep(35) + # Devs: if you get `too_many_request` errors, ask the team to add your IP to the rate limit excempt assert TEST_DYNDNS_DOMAIN not in domain_list()["domains"] # add domain without recovery password domain_add(TEST_DYNDNS_DOMAIN) From 253a042314ea5b4f24556c7019e054fdc59a190d Mon Sep 17 00:00:00 2001 From: axolotle Date: Thu, 28 Sep 2023 17:23:51 +0200 Subject: [PATCH 18/34] test:domains: dyndns_recovery mock as api call to avoid cli prompts --- src/tests/test_domains.py | 45 ++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/src/tests/test_domains.py b/src/tests/test_domains.py index bc2c31465..cc33a87d5 100644 --- a/src/tests/test_domains.py +++ b/src/tests/test_domains.py @@ -1,8 +1,10 @@ import pytest import os -import time import random +from mock import patch + +from moulinette import Moulinette from moulinette.core import MoulinetteError from yunohost.utils.error import YunohostError, YunohostValidationError @@ -87,25 +89,28 @@ def test_domain_add_and_remove_dyndns(): def test_domain_dyndns_recovery(): # Devs: if you get `too_many_request` errors, ask the team to add your IP to the rate limit excempt assert TEST_DYNDNS_DOMAIN not in domain_list()["domains"] - # add domain without recovery password - domain_add(TEST_DYNDNS_DOMAIN) - assert TEST_DYNDNS_DOMAIN in domain_list()["domains"] - # set the recovery password with config panel - domain_config_set(TEST_DYNDNS_DOMAIN, "dns.registrar.recovery_password", TEST_DYNDNS_PASSWORD) - # remove domain without unsubscribing - domain_remove(TEST_DYNDNS_DOMAIN, ignore_dyndns=True) - assert TEST_DYNDNS_DOMAIN not in domain_list()["domains"] - # readding domain with bad password should fail - with pytest.raises(YunohostValidationError): - domain_add( - TEST_DYNDNS_DOMAIN, dyndns_recovery_password="wrong" + TEST_DYNDNS_PASSWORD - ) - assert TEST_DYNDNS_DOMAIN not in domain_list()["domains"] - # readding domain with password should work - domain_add(TEST_DYNDNS_DOMAIN, dyndns_recovery_password=TEST_DYNDNS_PASSWORD) - assert TEST_DYNDNS_DOMAIN in domain_list()["domains"] - # remove the dyndns domain - domain_remove(TEST_DYNDNS_DOMAIN, dyndns_recovery_password=TEST_DYNDNS_PASSWORD) + # mocked as API call to avoid CLI prompts + with patch.object(Moulinette.interface, "type", "api"): + # add domain without recovery password + domain_add(TEST_DYNDNS_DOMAIN) + assert TEST_DYNDNS_DOMAIN in domain_list()["domains"] + # set the recovery password with config panel + domain_config_set(TEST_DYNDNS_DOMAIN, "dns.registrar.recovery_password", TEST_DYNDNS_PASSWORD) + # remove domain without unsubscribing + domain_remove(TEST_DYNDNS_DOMAIN, ignore_dyndns=True) + assert TEST_DYNDNS_DOMAIN not in domain_list()["domains"] + # readding domain with bad password should fail + with pytest.raises(YunohostValidationError): + domain_add( + TEST_DYNDNS_DOMAIN, dyndns_recovery_password="wrong" + TEST_DYNDNS_PASSWORD + ) + assert TEST_DYNDNS_DOMAIN not in domain_list()["domains"] + # readding domain with password should work + domain_add(TEST_DYNDNS_DOMAIN, dyndns_recovery_password=TEST_DYNDNS_PASSWORD) + assert TEST_DYNDNS_DOMAIN in domain_list()["domains"] + # remove the dyndns domain + domain_remove(TEST_DYNDNS_DOMAIN, dyndns_recovery_password=TEST_DYNDNS_PASSWORD) + assert TEST_DYNDNS_DOMAIN not in domain_list()["domains"] From bb097fedca2db668cbc8cc490887048478c86362 Mon Sep 17 00:00:00 2001 From: tituspijean Date: Fri, 29 Sep 2023 12:05:48 +0200 Subject: [PATCH 19/34] Add home.arpa as special TLD (#1718) * Add home.arpa as special TLD * Update dns.py --- src/utils/dns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/dns.py b/src/utils/dns.py index b3ca4b564..569dad466 100644 --- a/src/utils/dns.py +++ b/src/utils/dns.py @@ -21,7 +21,7 @@ from typing import List from moulinette.utils.filesystem import read_file -SPECIAL_USE_TLDS = ["local", "localhost", "onion", "test"] +SPECIAL_USE_TLDS = ["home.arpa", "local", "localhost", "onion", "test"] YNH_DYNDNS_DOMAINS = ["nohost.me", "noho.st", "ynh.fr"] From a457f8dbcb754997cdcfb9205634839ee4a90394 Mon Sep 17 00:00:00 2001 From: axolotle Date: Fri, 29 Sep 2023 16:35:24 +0200 Subject: [PATCH 20/34] app: add "support_purge" to app info --- src/app.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app.py b/src/app.py index df5eecae2..78c61dfcd 100644 --- a/src/app.py +++ b/src/app.py @@ -237,6 +237,10 @@ def app_info(app, full=False, upgradable=False): ret["supports_config_panel"] = os.path.exists( os.path.join(setting_path, "config_panel.toml") ) + ret["supports_purge"] = ( + local_manifest["packaging_format"] >= 2 + and local_manifest["resources"].get("data_dir") is not None + ) ret["permissions"] = permissions ret["label"] = permissions.get(app + ".main", {}).get("label") From 58a539bf7d4edd87a9921d4381495a5bf441bf0c Mon Sep 17 00:00:00 2001 From: orhtej2 <2871798+orhtej2@users.noreply.github.com> Date: Mon, 2 Oct 2023 23:19:01 +0200 Subject: [PATCH 21/34] Allow `phpX.Y` as sole dependency that'll trigger usage of `$phpversion=X.Y` --- helpers/apt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers/apt b/helpers/apt index a2f2d3de8..8ff9a3cd1 100644 --- a/helpers/apt +++ b/helpers/apt @@ -250,7 +250,7 @@ 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" # The (?<=php) syntax corresponds to lookbehind ;) - local specific_php_version=$(echo $dependencies | grep -oP '(?<=php)[0-9.]+(?=-|\>)' | sort -u) + local specific_php_version=$(echo $dependencies | grep -oP '(?<=php)[0-9.]+(?=-|\>|)' | sort -u) if [[ -n "$specific_php_version" ]] then From a83fbc1bc96a2e9d0763cf916a3d728d5e384c5a Mon Sep 17 00:00:00 2001 From: xabirequejo Date: Sat, 2 Sep 2023 20:37:20 +0000 Subject: [PATCH 22/34] Translated using Weblate (Basque) Currently translated at 95.0% (741 of 780 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eu/ --- locales/eu.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/locales/eu.json b/locales/eu.json index 0267b3366..85e7c36fc 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -766,5 +766,13 @@ "group_mailalias_add": "'{mail}' ePosta aliasa jarri zaio '{group}' taldeari", "group_mailalias_remove": "'{mail}' ePosta aliasa kendu zaio '{group}' taldeari", "group_user_remove": "'{user}' erabiltzailea '{group}' taldetik kenduko da", - "group_user_add": "'{user}' erabiltzailea '{group}' taldera gehituko da" + "group_user_add": "'{user}' erabiltzailea '{group}' taldera gehituko da", + "ask_dyndns_recovery_password_explain": "Aukeratu DynDNS domeinua berreskuratzeko pasahitza, etorkizunean berrezarri beharko bazenu.", + "ask_dyndns_recovery_password_explain_during_unsubscribe": "Sartu DynDNS domeinurako berreskuraketa pasahitza.", + "dyndns_no_recovery_password": "Ez da berreskurapen pasahitzik zehaztu! Domeinuaren gaineko kontrola galduz gero, YunoHost taldeko administrariarekin jarri beharko zara harremanetan!", + "ask_dyndns_recovery_password": "DynDNS berreskuratzeko pasahitza", + "dyndns_subscribed": "DynDNS domeinua harpidetu da", + "dyndns_subscribe_failed": "Ezin izan da DynDNS domeinua harpidetu: {error}", + "dyndns_unsubscribe_failed": "Ezin izan da DynDNS domeinuaren harpidetza utzi: {error}", + "dyndns_unsubscribed": "DynDNS domeinuaren harpidetza utzi da" } From 179b60062191d333cf32225f309df9d45123569a Mon Sep 17 00:00:00 2001 From: ppr Date: Sat, 30 Sep 2023 11:07:30 +0000 Subject: [PATCH 23/34] Translated using Weblate (French) Currently translated at 98.3% (769 of 782 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 2cc0f612c..a6e41ba8e 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -781,6 +781,8 @@ "dyndns_set_recovery_password_unknown_domain": "Échec de la définition du mot de passe de récupération : le domaine n'est pas enregistré", "dyndns_set_recovery_password_invalid_password": "Échec de la définition du mot de passe de récupération : le mot de passe n'est pas assez fort/solide", "dyndns_set_recovery_password_failed": "Échec de la définition du mot de passe de récupération : {erreur}", - "dyndns_set_recovery_password_success": "Mot de passe de récupération défini/configuré !", - "log_dyndns_unsubscribe": "Se désabonner d'un sous-domaine YunoHost '{}'" + "dyndns_set_recovery_password_success": "Mot de passe de récupération activé/défini/configuré !", + "log_dyndns_unsubscribe": "Se désabonner d'un sous-domaine YunoHost '{}'", + "dyndns_too_many_requests": "Le service dyndns de YunoHost a reçu trop de requêtes/demandes de votre part, attendez environ 1 heure avant de réessayer.", + "ask_dyndns_recovery_password_explain_unavailable": "Ce domaine DynDNS est déjà enregistré. Si vous êtes la personne qui a enregistré ce domaine lors de sa création, vous pouvez entrer le mot de passe de récupération pour récupérer ce domaine." } From f4d8ada3688917a5915486a8ce70d14d4cd74c37 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin <4533074+alexAubin@users.noreply.github.com> Date: Mon, 9 Oct 2023 23:12:44 +0200 Subject: [PATCH 24/34] 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 a6e41ba8e..2e4d1602b 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -781,7 +781,7 @@ "dyndns_set_recovery_password_unknown_domain": "Échec de la définition du mot de passe de récupération : le domaine n'est pas enregistré", "dyndns_set_recovery_password_invalid_password": "Échec de la définition du mot de passe de récupération : le mot de passe n'est pas assez fort/solide", "dyndns_set_recovery_password_failed": "Échec de la définition du mot de passe de récupération : {erreur}", - "dyndns_set_recovery_password_success": "Mot de passe de récupération activé/défini/configuré !", + "dyndns_set_recovery_password_success": "Mot de passe de récupération changé !", "log_dyndns_unsubscribe": "Se désabonner d'un sous-domaine YunoHost '{}'", "dyndns_too_many_requests": "Le service dyndns de YunoHost a reçu trop de requêtes/demandes de votre part, attendez environ 1 heure avant de réessayer.", "ask_dyndns_recovery_password_explain_unavailable": "Ce domaine DynDNS est déjà enregistré. Si vous êtes la personne qui a enregistré ce domaine lors de sa création, vous pouvez entrer le mot de passe de récupération pour récupérer ce domaine." From e8700bfe7b621a236dae3d68730cb4189ddda78f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 9 Oct 2023 23:15:54 +0200 Subject: [PATCH 25/34] debian: fix conflict with openssl that is too harsh, openssl version on bullseye is now 1.1.1w, bookworm has 3.x --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 0258eaac7..2fa1a1220 100644 --- a/debian/control +++ b/debian/control @@ -43,7 +43,7 @@ Conflicts: iptables-persistent , apache2 , bind9 , nginx-extras (>= 1.19) - , openssl (>= 1.1.1o-0) + , openssl (>= 3.0) , slapd (>= 2.4.58) , dovecot-core (>= 1:2.3.14) , redis-server (>= 5:6.1) From 6f1a00922a8fa866823ecd87dc6e6da2ab8e5005 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 10 Oct 2023 01:07:19 +0200 Subject: [PATCH 26/34] Update changelog for 11.2.5 --- debian/changelog | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/debian/changelog b/debian/changelog index 610109fcd..0effd40ba 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,21 @@ +yunohost (11.2.5) stable; urgency=low + + - debian: fix conflict with openssl that is too harsh, openssl version on bullseye is now 1.1.1w, bookworm has 3.x (e8700bfe7) + - dyndns: tweak dyndns subscribe/unsubscribe for dyndns recovery password integration in webadmin ([#1715](https://github.com/yunohost/yunohost/pull/1715)) + - helpers: ynh_setup_source: check and re-download a prefetched file that doesn't match the checksum (3dfab89c1) + - helpers: ynh_setup_source: fix misleading example ([#1714](https://github.com/yunohost/yunohost/pull/1714)) + - helpers: php/apt: allow `phpX.Y` as sole dependency for `$phpversion=X.Y` ([#1722](https://github.com/yunohost/yunohost/pull/1722)) + - apps: fix typo in log statement ([#1709](https://github.com/yunohost/yunohost/pull/1709)) + - apps: allow system users to send mails from IPv6 localhost. ([#1710](https://github.com/yunohost/yunohost/pull/1710)) + - apps: add "support_purge" to app info for webadmin integration ([#1719](https://github.com/yunohost/yunohost/pull/1719)) + - diagnosis: be more flexible regarding accepted values for DMARC DNS records ([#1713](https://github.com/yunohost/yunohost/pull/1713)) + - dns: add home.arpa as special TLD (#1718) (bb097fedc) + - i18n: Translations updated for Basque, French + + Thanks to all contributors <3 ! (axolotle, Florian, Kayou, orhtej2, Pierre de La Morinerie, ppr, stanislas, tituspijean, xabirequejo) + + -- Alexandre Aubin Mon, 09 Oct 2023 23:16:13 +0200 + yunohost (11.2.4) stable; urgency=low - doc: Improve --help for 'yunohost app install' ([#1702](https://github.com/yunohost/yunohost/pull/1702)) From f003565074f2349a1341c0a672c94888992635e0 Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Tue, 10 Oct 2023 02:31:47 +0000 Subject: [PATCH 27/34] [CI] Format code with Black --- src/app.py | 6 ++++-- src/dns.py | 6 +++++- src/tests/test_domains.py | 7 +++++-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/app.py b/src/app.py index 78c61dfcd..12b3c4233 100644 --- a/src/app.py +++ b/src/app.py @@ -2255,11 +2255,11 @@ def _parse_app_doc_and_notifications(path): def _hydrate_app_template(template, data): - # Apply jinja for stuff like {% if .. %} blocks, # but only if there's indeed an if block (to try to reduce overhead or idk) if "{%" in template: from jinja2 import Template + template = Template(template).render(**data) stuff_to_replace = set(re.findall(r"__[A-Z0-9]+?[A-Z0-9_]*?[A-Z0-9]*?__", template)) @@ -3187,7 +3187,9 @@ def _filter_and_hydrate_notifications(notifications, current_version=None, data= } # Filter out empty notifications (notifications may be empty because of if blocks) - return {name:content for name, content in out.items() if content and content.strip()} + return { + name: content for name, content in out.items() if content and content.strip() + } def _display_notifications(notifications, force=False): diff --git a/src/dns.py b/src/dns.py index fbb461463..1ec88a5c0 100644 --- a/src/dns.py +++ b/src/dns.py @@ -546,7 +546,11 @@ def _get_registrar_config_section(domain): } ) registrar_infos["recovery_password"] = OrderedDict( - {"type": "password", "ask": m18n.n("ask_dyndns_recovery_password"), "default": ""} + { + "type": "password", + "ask": m18n.n("ask_dyndns_recovery_password"), + "default": "", + } ) return OrderedDict(registrar_infos) elif is_special_use_tld(dns_zone): diff --git a/src/tests/test_domains.py b/src/tests/test_domains.py index cc33a87d5..13a9f63b8 100644 --- a/src/tests/test_domains.py +++ b/src/tests/test_domains.py @@ -95,14 +95,17 @@ def test_domain_dyndns_recovery(): domain_add(TEST_DYNDNS_DOMAIN) assert TEST_DYNDNS_DOMAIN in domain_list()["domains"] # set the recovery password with config panel - domain_config_set(TEST_DYNDNS_DOMAIN, "dns.registrar.recovery_password", TEST_DYNDNS_PASSWORD) + domain_config_set( + TEST_DYNDNS_DOMAIN, "dns.registrar.recovery_password", TEST_DYNDNS_PASSWORD + ) # remove domain without unsubscribing domain_remove(TEST_DYNDNS_DOMAIN, ignore_dyndns=True) assert TEST_DYNDNS_DOMAIN not in domain_list()["domains"] # readding domain with bad password should fail with pytest.raises(YunohostValidationError): domain_add( - TEST_DYNDNS_DOMAIN, dyndns_recovery_password="wrong" + TEST_DYNDNS_PASSWORD + TEST_DYNDNS_DOMAIN, + dyndns_recovery_password="wrong" + TEST_DYNDNS_PASSWORD, ) assert TEST_DYNDNS_DOMAIN not in domain_list()["domains"] # readding domain with password should work From 6a8693fa44dc4617015421f1364788b2001d6e3b Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Sun, 15 Oct 2023 05:10:23 +0000 Subject: [PATCH 28/34] Upgrade n to v --- helpers/vendor/n/n | 61 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/helpers/vendor/n/n b/helpers/vendor/n/n index 2a877c45b..eb276a2f5 100755 --- a/helpers/vendor/n/n +++ b/helpers/vendor/n/n @@ -61,7 +61,7 @@ function n_grep() { # Setup and state # -VERSION="v9.1.0" +VERSION="v9.2.0" N_PREFIX="${N_PREFIX-/usr/local}" N_PREFIX=${N_PREFIX%/} @@ -135,6 +135,7 @@ g_target_node= DOWNLOAD=false # set to opt-out of activate (install), and opt-in to download (run, exec) ARCH= SHOW_VERBOSE_LOG="true" +OFFLINE=false # ANSI escape codes # https://en.wikipedia.org/wiki/ANSI_escape_code @@ -393,6 +394,7 @@ Options: -q, --quiet Disable curl output. Disable log messages processing "auto" and "engine" labels. -d, --download Download if necessary, and don't make active -a, --arch Override system architecture + --offline Resolve target version against cached downloads instead of internet lookup --all ls-remote displays all matches instead of last 20 --insecure Turn off certificate checking for https requests (may be needed from behind a proxy server) --use-xz/--no-use-xz Override automatic detection of xz support and enable/disable use of xz compressed node downloads. @@ -784,6 +786,9 @@ install() { exit fi fi + if [[ "$OFFLINE" == "true" ]]; then + abort "version unavailable offline" + fi log installing "${g_mirror_folder_name}-v$version" @@ -1103,6 +1108,7 @@ function get_package_engine_version() { verbose_log "target" "${version}" else command -v npx &> /dev/null || abort "an active version of npx is required to use complex 'engine' ranges from package.json" + [[ "$OFFLINE" != "true" ]] || abort "offline: an internet connection is required for looking up complex 'engine' ranges from package.json" verbose_log "resolving" "${range}" local version_per_line="$(n lsr --all)" local versions_one_line=$(echo "${version_per_line}" | tr '\n' ' ') @@ -1199,6 +1205,8 @@ function get_latest_resolved_version() { # Just numbers, already resolved, no need to lookup first. simple_version="${simple_version#v}" g_target_node="${simple_version}" + elif [[ "$OFFLINE" == "true" ]]; then + g_target_node=$(display_local_versions "${version}") else # Complicated recognising exact version, KISS and lookup. g_target_node=$(N_MAX_REMOTE_MATCHES=1 display_remote_versions "$version") @@ -1232,6 +1240,56 @@ function display_match_limit(){ fi } +# +# Synopsis: display_local_versions version +# + +function display_local_versions() { + local version="$1" + local match='.' + verbose_log "offline" "matching cached versions" + + # Transform some labels before processing further. + if is_node_support_version "${version}"; then + version="$(display_latest_node_support_alias "${version}")" + match_count=1 + elif [[ "${version}" = "auto" ]]; then + # suppress stdout logging so lsr layout same as usual for scripting + get_auto_version || return 2 + version="${g_target_node}" + elif [[ "${version}" = "engine" ]]; then + # suppress stdout logging so lsr layout same as usual for scripting + get_engine_version || return 2 + version="${g_target_node}" + fi + + if [[ "${version}" = "latest" || "${version}" = "current" ]]; then + match='^node/.' + elif is_exact_numeric_version "${version}"; then + # Quote any dots in version so they are literal for expression + match="^node/${version//\./\.}" + elif is_numeric_version "${version}"; then + version="${version#v}" + # Quote any dots in version so they are literal for expression + match="${version//\./\.}" + # Avoid 1.2 matching 1.23 + match="^node/${match}[^0-9]" + # elif is_lts_codename "${version}"; then + # see if demand + elif is_download_folder "${version}"; then + match="^${version}/" + # elif is_download_version "${version}"; then + # see if demand + else + abort "invalid version '$1' for offline matching" + fi + + display_versions_paths \ + | n_grep -E "${match}" \ + | tail -n 1 \ + | sed 's|node/||' +} + # # Synopsis: display_remote_versions version # @@ -1577,6 +1635,7 @@ while [[ $# -ne 0 ]]; do -h|--help|help) display_help; exit ;; -q|--quiet) set_quiet ;; -d|--download) DOWNLOAD="true" ;; + --offline) OFFLINE="true" ;; --insecure) set_insecure ;; -p|--preserve) N_PRESERVE_NPM="true" N_PRESERVE_COREPACK="true" ;; --no-preserve) N_PRESERVE_NPM="" N_PRESERVE_COREPACK="" ;; From 075095b4d7d72c6abb8870a1914a5da436401930 Mon Sep 17 00:00:00 2001 From: tituspijean Date: Sun, 15 Oct 2023 14:59:45 +0200 Subject: [PATCH 29/34] Display n version in auto-update PRs --- .github/workflows/n_updater.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/n_updater.yml b/.github/workflows/n_updater.yml index ce3e9c925..a1a835a78 100644 --- a/.github/workflows/n_updater.yml +++ b/.github/workflows/n_updater.yml @@ -20,9 +20,11 @@ jobs: # Setting up Git user git config --global user.name 'yunohost-bot' git config --global user.email 'yunohost-bot@users.noreply.github.com' - # Run the updater script + # Download n wget https://raw.githubusercontent.com/tj/n/master/bin/n --output-document=helpers/vendor/n/n + # Proceed only if there is a change [[ -z "$(git diff helpers/vendor/n/n)" ]] || echo "PROCEED=true" >> $GITHUB_ENV + echo "VERSION=$(sed -n 's/^VERSION=\"\(.*\)\"/\1/p' < n)" >> $GITHUB_ENV - name: Commit changes id: commit if: ${{ env.PROCEED == 'true' }} @@ -34,14 +36,14 @@ jobs: uses: peter-evans/create-pull-request@v3 with: token: ${{ secrets.GITHUB_TOKEN }} - commit-message: Update n to version ${{ env.VERSION }} + commit-message: Update n to ${{ env.VERSION }} committer: 'yunohost-bot ' author: 'yunohost-bot ' signoff: false base: dev branch: ci-auto-update-n-v${{ env.VERSION }} delete-branch: true - title: 'Upgrade n to version ${{ env.VERSION }}' + title: 'Upgrade n to ${{ env.VERSION }}' body: | - Upgrade `n` to v${{ env.VERSION }} + Upgrade `n` to ${{ env.VERSION }} draft: false From a2faa8add90d512e56c4dae999a813b53d513a5b Mon Sep 17 00:00:00 2001 From: Chris Vogel Date: Tue, 17 Oct 2023 14:49:32 +0200 Subject: [PATCH 30/34] add redis database configuration https://github.com/YunoHost/issues/issues/2266 without this rspamd does not use the redis database --- conf/rspamd/redis.conf | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 conf/rspamd/redis.conf diff --git a/conf/rspamd/redis.conf b/conf/rspamd/redis.conf new file mode 100644 index 000000000..03152581e --- /dev/null +++ b/conf/rspamd/redis.conf @@ -0,0 +1,2 @@ +# set redis server +servers = "127.0.0.1"; From 379b6922ad16cb744c9c3fba2138613fbf2e68d5 Mon Sep 17 00:00:00 2001 From: Chris Vogel Date: Tue, 17 Oct 2023 21:13:09 +0200 Subject: [PATCH 31/34] install file to redis configuration ouch... Thanks @Tagadda! --- hooks/conf_regen/31-rspamd | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hooks/conf_regen/31-rspamd b/hooks/conf_regen/31-rspamd index 6807ce0cd..6333a755f 100755 --- a/hooks/conf_regen/31-rspamd +++ b/hooks/conf_regen/31-rspamd @@ -13,6 +13,8 @@ do_pre_regen() { "${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 redis.conf \ + "${pending_dir}/etc/rspamd/local.d/redis.conf" } do_post_regen() { From e6e58ec2695fff37b617f67361ef218fb8d70c94 Mon Sep 17 00:00:00 2001 From: Chris Vogel Date: Wed, 18 Oct 2023 23:04:24 +0200 Subject: [PATCH 32/34] improve dovecots rspamd integration For rspamd being able to learn ham or spam from messages being moved into spam/junk folders or out of them dovecot needs to know how spam/junk folders and trash folders are named. The former rules narrowed the folders being recognized as spam/trash down to just 'Junk, SPAM, Trash' (case-senistive). Since users and admins can change the foldernames and write their own seive filters to use those folders I think it is a big improvement if more folders will be recognized. The change is supposed to accept some more commonly used folder names for spam and trash in a case-insensitive manner. --- conf/dovecot/dovecot.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conf/dovecot/dovecot.conf b/conf/dovecot/dovecot.conf index 152f4c01c..4f491d1df 100644 --- a/conf/dovecot/dovecot.conf +++ b/conf/dovecot/dovecot.conf @@ -119,8 +119,8 @@ plugin { antispam_debug_target = syslog antispam_verbose_debug = 0 antispam_backend = pipe - antispam_spam = Junk;SPAM - antispam_trash = Trash + antispam_spam_pattern_ignorecase = junk;spam + antispam_trash_pattern_ignorecase = trash;papierkorb;deleted messages antispam_pipe_program = /usr/bin/rspamc antispam_pipe_program_args = -h;localhost:11334;-P;q1 antispam_pipe_program_spam_arg = learn_spam From 74fd75ea33e8ddf246a8c83e3035d6f11b733089 Mon Sep 17 00:00:00 2001 From: Sebastian Gumprich Date: Sun, 22 Oct 2023 18:43:26 +0200 Subject: [PATCH 33/34] fix typos --- src/utils/resources.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/utils/resources.py b/src/utils/resources.py index 69b260334..1720b4890 100644 --- a/src/utils/resources.py +++ b/src/utils/resources.py @@ -61,10 +61,10 @@ class AppResourceManager: try: if todo == "deprovision": # FIXME : i18n, better info strings - logger.info(f"Deprovisionning {name}...") + logger.info(f"Deprovisioning {name}...") old.deprovision(context=context) elif todo == "provision": - logger.info(f"Provisionning {name}...") + logger.info(f"Provisioning {name}...") new.provision_or_update(context=context) elif todo == "update": logger.info(f"Updating {name}...") @@ -90,14 +90,14 @@ class AppResourceManager: # (NB. here we want to undo the todo) if todo == "deprovision": # FIXME : i18n, better info strings - logger.info(f"Reprovisionning {name}...") + logger.info(f"Reprovisioning {name}...") old.provision_or_update(context=context) elif todo == "provision": - logger.info(f"Deprovisionning {name}...") + logger.info(f"Deprovisioning {name}...") new.deprovision(context=context) elif todo == "update": logger.info(f"Reverting {name}...") - old.provision_or_update(context=context) + old.sion_or_update(context=context) except (KeyboardInterrupt, Exception) as e: if isinstance(e, KeyboardInterrupt): logger.error(m18n.n("operation_interrupted")) From 54bc79ed587fc55c467e03e349d9097f56e1118d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin <4533074+alexAubin@users.noreply.github.com> Date: Sun, 22 Oct 2023 18:53:57 +0200 Subject: [PATCH 34/34] Apply suggestions from code review --- src/utils/resources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/resources.py b/src/utils/resources.py index 1720b4890..3f9719c39 100644 --- a/src/utils/resources.py +++ b/src/utils/resources.py @@ -97,7 +97,7 @@ class AppResourceManager: new.deprovision(context=context) elif todo == "update": logger.info(f"Reverting {name}...") - old.sion_or_update(context=context) + old.provision_or_update(context=context) except (KeyboardInterrupt, Exception) as e: if isinstance(e, KeyboardInterrupt): logger.error(m18n.n("operation_interrupted"))