From 4106ed669e5a53652b46d9e7804dbdfa14d699fc Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 6 Dec 2021 23:01:06 +0100 Subject: [PATCH 01/47] [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 02/47] [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 f49f03d11e4e297920e084bb00f3c7934fdce6ad Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 22 Dec 2021 19:07:19 +0100 Subject: [PATCH 03/47] 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 6aaf47493d0cc26e617fe3fc6f9fe589a98666ad Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 23 Dec 2021 16:55:12 +0100 Subject: [PATCH 04/47] 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 a063b63d1c86a1f30dac670cea17f2f24e9c944e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 27 Dec 2021 15:35:02 +0100 Subject: [PATCH 05/47] 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 06/47] 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 fa9365d5fa50aff9b94b3356492679fa19ad8dca Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 3 Jan 2022 16:28:35 +0100 Subject: [PATCH 07/47] 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 08/47] 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 09/47] [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 10/47] 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 11/47] 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 12/47] 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 13/47] 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 14/47] 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 15/47] 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 16/47] 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 17/47] 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 18/47] 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 19/47] 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 20/47] 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 21/47] [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 22/47] 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 23/47] 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 24/47] 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 25/47] 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 26/47] 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 27/47] 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 28/47] 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 29/47] 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 30/47] [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 31/47] [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 32/47] 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 33/47] 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 34/47] 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 35/47] 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 36/47] 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 37/47] 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 38/47] 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 39/47] 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 40/47] 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 41/47] 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 42/47] 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 43/47] 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 44/47] 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 45/47] 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 46/47] 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 b1fe61ed68be0f80b3ffcce09d63395e5a95f1c8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 11 Jan 2022 21:37:11 +0100 Subject: [PATCH 47/47] 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