From bb2424e9b878ec3f573b6224d302073ddcff67db Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Wed, 23 Aug 2017 17:25:16 +0200 Subject: [PATCH 01/29] [enh] Escape some special character in ynh_replace_string --- data/helpers.d/string | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/string b/data/helpers.d/string index 772681fb9..3e68b382d 100644 --- a/data/helpers.d/string +++ b/data/helpers.d/string @@ -18,8 +18,18 @@ ynh_string_random() { # | arg: target_file - File in which the string will be replaced. ynh_replace_string () { delimit=@ - match_string=${1//${delimit}/"\\${delimit}"} # Escape the delimiter if it's in the string. + # Escape the delimiter if it's in the string. + match_string=${1//${delimit}/"\\${delimit}"} replace_string=${2//${delimit}/"\\${delimit}"} + + # Escape any backslash to preserve them as simple backslash. + match_string=${match_string//\\/"\\\\"} + replace_string=${replace_string//\\/"\\\\"} + + # Escape the & character, who has a special function in sed. + match_string=${match_string//&/"\&"} + replace_string=${replace_string//&/"\&"} + workfile=$3 sudo sed --in-place "s${delimit}${match_string}${delimit}${replace_string}${delimit}g" "$workfile" From 171258e7faa3f310f2a6024b493c832a04280fc0 Mon Sep 17 00:00:00 2001 From: ljf Date: Mon, 28 Aug 2017 17:17:19 +0200 Subject: [PATCH 02/29] [enh] Return log after app upgrade with api --- src/yunohost/app.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 025de2e19..476723000 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -38,7 +38,7 @@ import pwd import grp from collections import OrderedDict -from moulinette import msignals, m18n +from moulinette import msignals, m18n, msettings from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger @@ -531,6 +531,9 @@ def app_upgrade(auth, app=[], url=None, file=None): """ from yunohost.hook import hook_add, hook_remove, hook_exec + # Retrieve interface + is_api = msettings.get('interface') == 'api' + try: app_list() except MoulinetteError: @@ -632,6 +635,10 @@ def app_upgrade(auth, app=[], url=None, file=None): logger.success(m18n.n('upgrade_complete')) + # Return API logs if it is an API call + if is_api: + return {"log": service_log('yunohost-api', number="100").values()[0]} + def app_install(auth, app, label=None, args=None, no_remove_on_failure=False): """ From f4955341bb5ad71969eb765915509708ebb70d0f Mon Sep 17 00:00:00 2001 From: Jimmy Monin Date: Tue, 5 Sep 2017 21:09:24 +0200 Subject: [PATCH 03/29] Complete ynh_replace_string helper comments to mention the possible use of regexp --- data/helpers.d/string | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/string b/data/helpers.d/string index 772681fb9..b77b42ef7 100644 --- a/data/helpers.d/string +++ b/data/helpers.d/string @@ -10,12 +10,16 @@ ynh_string_random() { | sed -n 's/\(.\{'"${1:-24}"'\}\).*/\1/p' } -# Substitute/replace a string by another in a file +# Substitute/replace a string (or expression) by another in a file # # usage: ynh_replace_string match_string replace_string target_file # | arg: match_string - String to be searched and replaced in the file # | arg: replace_string - String that will replace matches # | arg: target_file - File in which the string will be replaced. +# +# As this helper is based on sed command, regular expressions and +# references to sub-expressions can be used +# (see sed manual page for more information) ynh_replace_string () { delimit=@ match_string=${1//${delimit}/"\\${delimit}"} # Escape the delimiter if it's in the string. From 85f0b02f1fe87cc6794d4f299329299a26ed877e Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Thu, 7 Sep 2017 15:06:12 +0200 Subject: [PATCH 04/29] [fix] '/' was resulting in '//' --- src/yunohost/app.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 84092580f..fe71cfd85 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -445,8 +445,9 @@ def app_change_url(auth, app, domain, path): # Normalize path and domain format domain = domain.strip().lower() - old_path = '/' + old_path.strip("/").strip() + '/' - path = '/' + path.strip("/").strip() + '/' + + old_path = normalize_url_path(old_path) + path = normalize_url_path(path) if (domain, path) == (old_domain, old_path): raise MoulinetteError(errno.EINVAL, m18n.n("app_change_url_identical_domains", domain=domain, path=path)) @@ -2105,3 +2106,10 @@ def random_password(length=8): char_set = string.ascii_uppercase + string.digits + string.ascii_lowercase return ''.join([random.SystemRandom().choice(char_set) for x in range(length)]) + + +def normalize_url_path(url_path): + if url_path.strip("/").strip(): + return '/' + url_path.strip("/").strip() + '/' + + return "/" From 1086a50eb2d8c9400f3da347862916eeee746291 Mon Sep 17 00:00:00 2001 From: JimboJoe Date: Wed, 13 Sep 2017 19:35:29 +0200 Subject: [PATCH 05/29] Fix #1005 (add missing 'ask_path' key) (#369) --- locales/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/locales/en.json b/locales/en.json index 80ff22655..9ef41a3f2 100644 --- a/locales/en.json +++ b/locales/en.json @@ -55,6 +55,7 @@ "ask_main_domain": "Main domain", "ask_new_admin_password": "New administration password", "ask_password": "Password", + "ask_path": "Path", "backup_abstract_method": "This backup method hasn't yet been implemented", "backup_action_required": "You must specify something to save", "backup_app_failed": "Unable to back up the app '{app:s}'", From 8a559f2ae1de4fe03d0b4636d84c518b88ce1347 Mon Sep 17 00:00:00 2001 From: Jimmy Monin Date: Thu, 14 Sep 2017 22:12:01 +0200 Subject: [PATCH 06/29] Make MySQL dumps with a single transaction to ensure backup consistency --- data/helpers.d/mysql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/mysql b/data/helpers.d/mysql index 1c0ece114..0774adbce 100644 --- a/data/helpers.d/mysql +++ b/data/helpers.d/mysql @@ -73,7 +73,7 @@ ynh_mysql_drop_db() { # | arg: db - the database name to dump # | ret: the mysqldump output ynh_mysql_dump_db() { - mysqldump -u "root" -p"$(sudo cat $MYSQL_ROOT_PWD_FILE)" "$1" + mysqldump -u "root" -p"$(sudo cat $MYSQL_ROOT_PWD_FILE)" --single-transaction "$1" } # Create a user From d42cc00a8765b77339ca089fd134684a8a50890b Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 19 Sep 2017 01:34:23 +0200 Subject: [PATCH 07/29] [enh] Remove date from sql dump --- data/helpers.d/mysql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/mysql b/data/helpers.d/mysql index 1c0ece114..5bc598cbc 100644 --- a/data/helpers.d/mysql +++ b/data/helpers.d/mysql @@ -73,7 +73,7 @@ ynh_mysql_drop_db() { # | arg: db - the database name to dump # | ret: the mysqldump output ynh_mysql_dump_db() { - mysqldump -u "root" -p"$(sudo cat $MYSQL_ROOT_PWD_FILE)" "$1" + mysqldump -u "root" -p"$(sudo cat $MYSQL_ROOT_PWD_FILE)" --skip-dump-date "$1" } # Create a user From f4b95ca5ff5dccbc96e18d34f0b3f8995e8e0a14 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Tue, 26 Sep 2017 01:01:00 +0200 Subject: [PATCH 08/29] [fix] fix this UnicodeDecodingError on backup-restore --- src/yunohost/backup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index d794a2fca..def7fb27b 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -166,11 +166,11 @@ class BackupRestoreTargetsManager(object): or (exclude and isinstance(exclude, list) and not include) if include: - return [target for target in self.targets[category] + return [target.encode("Utf-8") for target in self.targets[category] if self.results[category][target] in include] if exclude: - return [target for target in self.targets[category] + return [target.encode("Utf-8") for target in self.targets[category] if self.results[category][target] not in exclude] From 220c447074534fc2c283559ab255fd86a54ebb9c Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 30 Sep 2017 13:01:15 +0200 Subject: [PATCH 09/29] [enh] Handle root path in nginx conf (#361) Avoid to put a double slash when the path is /. That can induce some errors in a nginx config. --- data/helpers.d/backend | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index c54e82754..c0cbc616c 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -123,6 +123,9 @@ ynh_add_nginx_config () { # To avoid a break by set -u, use a void substitution ${var:-}. If the variable is not set, it's simply set with an empty variable. # Substitute in a nginx config file only if the variable is not empty if test -n "${path_url:-}"; then + # path_url_slash_less if path_url or a blank value if path_url is only '/' + path_url_slash_less=${path_url%/} + ynh_replace_string "__PATH__/" "$path_url_slash_less/" "$finalnginxconf" ynh_replace_string "__PATH__" "$path_url" "$finalnginxconf" fi if test -n "${domain:-}"; then From 2a49613e59117811b0da09f2d212ba4046429de0 Mon Sep 17 00:00:00 2001 From: YunoHost Bot Date: Mon, 2 Oct 2017 19:56:31 +0200 Subject: [PATCH 10/29] Update from Weblate. (#355) * [i18n] Translated using Weblate (French) Currently translated at 100.0% (351 of 351 strings) * [i18n] Translated using Weblate (French) Currently translated at 100.0% (351 of 351 strings) --- locales/fr.json | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 86df7b10d..f4afdceb3 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -14,7 +14,7 @@ "app_install_files_invalid": "Fichiers d'installation incorrects", "app_location_already_used": "Une application est déjà installée à cet emplacement", "app_location_install_failed": "Impossible d'installer l'application à cet emplacement", - "app_manifest_invalid": "Manifeste d'application incorrect", + "app_manifest_invalid": "Manifeste d'application incorrect : {error}", "app_no_upgrade": "Aucune application à mettre à jour", "app_not_correctly_installed": "{app:s} semble être mal installé", "app_not_installed": "{app:s} n'est pas installé", @@ -320,7 +320,7 @@ "backup_archive_system_part_not_available": "La partie « {part:s} » du système n’est pas disponible dans cette sauvegarde", "backup_archive_mount_failed": "Le montage de l’archive de sauvegarde a échoué", "backup_archive_writing_error": "Impossible d’ajouter les fichiers à la sauvegarde dans l’archive compressée", - "backup_ask_for_copying_if_needed": "Votre système ne prend pas complètement en charge la méthode rapide d’organisation des fichiers dans l’archive, voulez-vous les organiser en copiant {size:s} Mio ?", + "backup_ask_for_copying_if_needed": "Certains fichiers n’ont pas pu être préparés pour être sauvegardée en utilisant la méthode qui évite de temporairement gaspiller de l’espace sur le système. Pour mener la sauvegarde, {size:s} Mio doivent être temporairement utilisés. Acceptez-vous ?", "backup_borg_not_implemented": "La méthode de sauvegarde Bord n’est pas encore implémentée", "backup_cant_mount_uncompress_archive": "Impossible de monter en lecture seule le dossier de l’archive décompressée", "backup_copying_to_organize_the_archive": "Copie de {size:s} Mio pour organiser l’archive", @@ -344,5 +344,24 @@ "restore_mounting_archive": "Montage de l’archive dans « {path:s} »", "restore_may_be_not_enough_disk_space": "Votre système semble ne pas avoir suffisamment d’espace disponible (libre : {free_space:d} octets, nécessaire : {needed_space:d} octets, marge de sécurité : {margin:d} octets)", "restore_not_enough_disk_space": "Espace disponible insuffisant (libre : {free_space:d} octets, nécessaire : {needed_space:d} octets, marge de sécurité : {margin:d} octets)", - "restore_system_part_failed": "Impossible de restaurer la partie « {part:s} » du système" + "restore_system_part_failed": "Impossible de restaurer la partie « {part:s} » du système", + "backup_couldnt_bind": "Impossible de lier {src:s} avec {dest:s}.", + "domain_dns_conf_is_just_a_recommendation": "Cette page montre la configuration *recommandée*. Elle ne configure *pas* le DNS pour vous. Il est de votre responsabilité que de configurer votre zone DNS chez votre registrar DNS avec cette recommandation.", + "domain_dyndns_dynette_is_unreachable": "Impossible de contacter la dynette YunoHost, soit YunoHost n’est pas correctement connecté à internet ou alors le serveur de dynette est arrêté. Erreur : {error}", + "migrations_backward": "Migration en arrière.", + "migrations_bad_value_for_target": "Nombre invalide pour le paramètre « target », les numéros de migration sont ou {}", + "migrations_cant_reach_migration_file": "Impossible d’accéder aux fichiers de migrations avec le chemin %s", + "migrations_current_target": "La cible de migration est {}", + "migrations_error_failed_to_load_migration": "ERREUR : échec du chargement de migration {number} {name}", + "migrations_forward": "Migration en avant", + "migrations_loading_migration": "Chargement de la migration {number} {name}…", + "migrations_migration_has_failed": "La migration {number} {name} a échoué avec l’exception {exception}, annulation", + "migrations_no_migrations_to_run": "Aucune migration à lancer", + "migrations_show_currently_running_migration": "Application de la migration {number} {name}…", + "migrations_show_last_migration": "La dernière migration appliquée est {}", + "migrations_skip_migration": "Omission de la migration {number} {name}…", + "server_shutdown": "Le serveur sera éteint", + "server_shutdown_confirm": "Le serveur immédiatement être éteint, le voulez-vous vraiment ? [{answers:s}]", + "server_reboot": "Le serveur va redémarrer", + "server_reboot_confirm": "Le serveur va redémarrer immédiatement, le voulez-vous vraiment ? [{answers:s}]" } From e3a4b307f7c307820c83afce9489f8a128ffb966 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 3 Oct 2017 22:11:24 +0200 Subject: [PATCH 11/29] Fix double backslash in case of delimiter used --- data/helpers.d/string | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/string b/data/helpers.d/string index 3e68b382d..677350e56 100644 --- a/data/helpers.d/string +++ b/data/helpers.d/string @@ -18,9 +18,6 @@ ynh_string_random() { # | arg: target_file - File in which the string will be replaced. ynh_replace_string () { delimit=@ - # Escape the delimiter if it's in the string. - match_string=${1//${delimit}/"\\${delimit}"} - replace_string=${2//${delimit}/"\\${delimit}"} # Escape any backslash to preserve them as simple backslash. match_string=${match_string//\\/"\\\\"} @@ -30,6 +27,10 @@ ynh_replace_string () { match_string=${match_string//&/"\&"} replace_string=${replace_string//&/"\&"} + # Escape the delimiter if it's in the string. + match_string=${1//${delimit}/"\\${delimit}"} + replace_string=${2//${delimit}/"\\${delimit}"} + workfile=$3 sudo sed --in-place "s${delimit}${match_string}${delimit}${replace_string}${delimit}g" "$workfile" From 43dbf03ac8fd1c5ac599ca7b0089d5b17cbbd85b Mon Sep 17 00:00:00 2001 From: Weblate Admin Date: Wed, 4 Oct 2017 10:26:25 +0000 Subject: [PATCH 12/29] Translated using Weblate (French) Currently translated at 100.0% (353 of 353 strings) Translation: YunoHost/YunoHost Translate-URL: https://holcroft.fr/weblate/projects/yunohost/yunohost/fr/ --- locales/fr.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index f4afdceb3..2685a231a 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -363,5 +363,7 @@ "server_shutdown": "Le serveur sera éteint", "server_shutdown_confirm": "Le serveur immédiatement être éteint, le voulez-vous vraiment ? [{answers:s}]", "server_reboot": "Le serveur va redémarrer", - "server_reboot_confirm": "Le serveur va redémarrer immédiatement, le voulez-vous vraiment ? [{answers:s}]" + "server_reboot_confirm": "Le serveur va redémarrer immédiatement, le voulez-vous vraiment ? [{answers:s}]", + "app_upgrade_some_app_failed": "Impossible de mettre à jour certaines applications", + "ask_path": "Chemin" } From 0086c8c16a93c1ff350e41e7a53d37cf57e93535 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 7 Oct 2017 15:06:00 +0200 Subject: [PATCH 13/29] [fix] match_string: unbound variable (#379) * [fix] match_string: unbound variable * Define local variables at beginning of function --- data/helpers.d/string | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/data/helpers.d/string b/data/helpers.d/string index adfdf89fe..fbf598738 100644 --- a/data/helpers.d/string +++ b/data/helpers.d/string @@ -21,7 +21,10 @@ ynh_string_random() { # references to sub-expressions can be used # (see sed manual page for more information) ynh_replace_string () { - delimit=@ + local delimit=@ + local match_string=$1 + local replace_string=$2 + local workfile=$3 # Escape any backslash to preserve them as simple backslash. match_string=${match_string//\\/"\\\\"} @@ -32,10 +35,8 @@ ynh_replace_string () { replace_string=${replace_string//&/"\&"} # Escape the delimiter if it's in the string. - match_string=${1//${delimit}/"\\${delimit}"} - replace_string=${2//${delimit}/"\\${delimit}"} - - workfile=$3 + match_string=${match_string//${delimit}/"\\${delimit}"} + replace_string=${replace_string//${delimit}/"\\${delimit}"} sudo sed --in-place "s${delimit}${match_string}${delimit}${replace_string}${delimit}g" "$workfile" } From 5ae558edc915ec3b4284848db8585d3c9c4ca3a0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 8 Oct 2017 23:44:07 +0200 Subject: [PATCH 14/29] [fix] Clean madness related to DynDNS (#353) * Add a function to check if a dyndns provider provides a domain * Add a function to check a domain availability from a dyndns provider * Use new functions in dyndns_subscribe * Replace complete madness by _dyndns_available in dyndns_update * This regex is only used in dyndns_update, no need to define it at the whole file scope level * Try to clarify management of old ipv4/ipv6 ... * Add a nice helper to get both ipv4 and ipv6... * Clarify the dyndns_update madness to get current ipv4/v6 * Remove now useless IPRouteLine * Change default values of old ipv4/v6 to None, otherwise shit gets update just because IPv6 = None * Rearrange thing a bit, move path to global variable * Copypasta typo * Dyndns zone file as a global variable * Use helper to write zone content to file * Adding some debugs/info messages * Move the domain guessing to a dedicated function... * Adding comments.. * Using subprocess check_call instead of os.system for nsupdate * Removing dump of the zone update because it's kinda duplicated from what nsupdate already does * Ignore error if old_ipvx file is non existent * Add docstring for _dyndns_available * Remove parenthesis otherwise this gonna displease Bram-sama :P * Start working on postinstall .. use _dyndns_provides to check if domain is a .nohost.me or .nohost.st * Use _dyndns_available to cleanly check for domain availability * Forget about the weird 'domain split' check... * Clean dyndns stuff in domain.py * Missing argument for string --- locales/en.json | 4 +- src/yunohost/domain.py | 40 ++++--- src/yunohost/dyndns.py | 232 ++++++++++++++++++++++++----------------- src/yunohost/tools.py | 46 +++++--- 4 files changed, 198 insertions(+), 124 deletions(-) diff --git a/locales/en.json b/locales/en.json index 9ef41a3f2..8dac6e799 100644 --- a/locales/en.json +++ b/locales/en.json @@ -158,6 +158,7 @@ "domains_available": "Available domains:", "done": "Done", "downloading": "Downloading...", + "dyndns_could_not_check_provide": "Could not check if {provider:s} can provide {domain:s}.", "dyndns_cron_installed": "The DynDNS cron job has been installed", "dyndns_cron_remove_failed": "Unable to remove the DynDNS cron job", "dyndns_cron_removed": "The DynDNS cron job has been removed", @@ -168,7 +169,8 @@ "dyndns_no_domain_registered": "No domain has been registered with DynDNS", "dyndns_registered": "The DynDNS domain has been registered", "dyndns_registration_failed": "Unable to register DynDNS domain: {error:s}", - "dyndns_unavailable": "Unavailable DynDNS subdomain", + "dyndns_domain_not_provided": "Dyndns provider {provider:s} cannot provide domain {domain:s}.", + "dyndns_unavailable": "Domain {domain:s} is not available.", "executing_command": "Executing command '{command:s}'...", "executing_script": "Executing script '{script:s}'...", "extracting": "Extracting...", diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index f17cc75f6..f828b0973 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -82,28 +82,23 @@ def domain_add(auth, domain, dyndns=False): # DynDNS domain if dyndns: - if len(domain.split('.')) < 3: - raise MoulinetteError(errno.EINVAL, m18n.n('domain_dyndns_invalid')) + # Do not allow to subscribe to multiple dyndns domains... if os.path.exists('/etc/cron.d/yunohost-dyndns'): raise MoulinetteError(errno.EPERM, m18n.n('domain_dyndns_already_subscribed')) - try: - r = requests.get('https://dyndns.yunohost.org/domains', timeout=30) - except requests.ConnectionError as e: - raise MoulinetteError(errno.EHOSTUNREACH, - m18n.n('domain_dyndns_dynette_is_unreachable', error=str(e))) - from yunohost.dyndns import dyndns_subscribe + from yunohost.dyndns import dyndns_subscribe, _dyndns_provides - dyndomains = json.loads(r.text) - dyndomain = '.'.join(domain.split('.')[1:]) - if dyndomain in dyndomains: - dyndns_subscribe(domain=domain) - else: + # Check that this domain can effectively be provided by + # dyndns.yunohost.org. (i.e. is it a nohost.me / noho.st) + if not _dyndns_provides("dyndns.yunohost.org", domain): raise MoulinetteError(errno.EINVAL, m18n.n('domain_dyndns_root_unknown')) + # Actually subscribe + dyndns_subscribe(domain=domain) + try: yunohost.certificate._certificate_install_selfsigned([domain], False) @@ -281,6 +276,25 @@ def get_public_ip(protocol=4): raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection')) +def get_public_ips(): + """ + Retrieve the public IPv4 and v6 from ip. and ip6.yunohost.org + + Returns a 2-tuple (ipv4, ipv6). ipv4 or ipv6 can be None if they were not + found. + """ + + try: + ipv4 = get_public_ip() + except: + ipv4 = None + try: + ipv6 = get_public_ip(6) + except: + ipv6 = None + + return (ipv4, ipv6) + def _get_maindomain(): with open('/etc/yunohost/current_host', 'r') as f: diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 621043c3e..55a2be692 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -35,37 +35,72 @@ import subprocess from moulinette import m18n from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger +from moulinette.utils.filesystem import read_file, write_to_file, rm +from moulinette.utils.network import download_json -from yunohost.domain import get_public_ip, _get_maindomain, _build_dns_conf +from yunohost.domain import get_public_ips, _get_maindomain, _build_dns_conf logger = getActionLogger('yunohost.dyndns') +OLD_IPV4_FILE = '/etc/yunohost/dyndns/old_ip' +OLD_IPV6_FILE = '/etc/yunohost/dyndns/old_ipv6' +DYNDNS_ZONE = '/etc/yunohost/dyndns/zone' -class IPRouteLine(object): - """ Utility class to parse an ip route output line - The output of ip ro is variable and hard to parse completly, it would - require a real parser, not just a regexp, so do minimal parsing here... - - >>> a = IPRouteLine('2001:: from :: via fe80::c23f:fe:1e:cafe dev eth0 src 2000:de:beef:ca:0:fe:1e:cafe metric 0') - >>> a.src_addr - "2000:de:beef:ca:0:fe:1e:cafe" +def _dyndns_provides(provider, domain): """ - regexp = re.compile( - r'(?Punreachable)?.*src\s+(?P[0-9a-f:]+).*') + Checks if a provider provide/manage a given domain. - def __init__(self, line): - self.m = self.regexp.match(line) - if not self.m: - raise ValueError("Not a valid ip route get line") + Keyword arguments: + provider -- The url of the provider, e.g. "dyndns.yunohost.org" + domain -- The full domain that you'd like.. e.g. "foo.nohost.me" - # make regexp group available as object attributes - for k, v in self.m.groupdict().items(): - setattr(self, k, v) + Returns: + True if the provider provide/manages the domain. False otherwise. + """ -re_dyndns_private_key = re.compile( - r'.*/K(?P[^\s\+]+)\.\+157.+\.private$' -) + logger.debug("Checking if %s is managed by %s ..." % (domain, provider)) + + try: + # Dyndomains will be a list of domains supported by the provider + # e.g. [ "nohost.me", "noho.st" ] + dyndomains = download_json('https://%s/domains' % provider, timeout=30) + except MoulinetteError as e: + logger.error(str(e)) + raise MoulinetteError(errno.EIO, + m18n.n('dyndns_could_not_check_provide', + domain=domain, provider=provider)) + + # Extract 'dyndomain' from 'domain', e.g. 'nohost.me' from 'foo.nohost.me' + dyndomain = '.'.join(domain.split('.')[1:]) + + return dyndomain in dyndomains + + +def _dyndns_available(provider, domain): + """ + Checks if a domain is available from a given provider. + + Keyword arguments: + provider -- The url of the provider, e.g. "dyndns.yunohost.org" + domain -- The full domain that you'd like.. e.g. "foo.nohost.me" + + Returns: + True if the domain is avaible, False otherwise. + """ + logger.debug("Checking if domain %s is available on %s ..." + % (domain, provider)) + + try: + r = download_json('https://%s/test/%s' % (provider, domain), + expected_status_code=None) + except MoulinetteError as e: + logger.error(str(e)) + raise MoulinetteError(errno.EIO, + m18n.n('dyndns_could_not_check_available', + domain=domain, provider=provider)) + + return r == u"Domain %s is available" % domain def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None): @@ -81,12 +116,16 @@ def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None if domain is None: domain = _get_maindomain() + # Verify if domain is provided by subscribe_host + if not _dyndns_provides(subscribe_host, domain): + raise MoulinetteError(errno.ENOENT, + m18n.n('dyndns_domain_not_provided', + domain=domain, provider=subscribe_host)) + # Verify if domain is available - try: - if requests.get('https://%s/test/%s' % (subscribe_host, domain)).status_code != 200: - raise MoulinetteError(errno.EEXIST, m18n.n('dyndns_unavailable')) - except requests.ConnectionError: - raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection')) + if not _dyndns_available(subscribe_host, domain): + raise MoulinetteError(errno.ENOENT, + m18n.n('dyndns_unavailable', domain=domain)) if key is None: if len(glob.glob('/etc/yunohost/dyndns/*.key')) == 0: @@ -133,73 +172,40 @@ def dyndns_update(dyn_host="dyndns.yunohost.org", domain=None, key=None, ipv6 -- IPv6 address to send """ - # IPv4 + # Get old ipv4/v6 + + old_ipv4, old_ipv6 = (None, None) # (default values) + + if os.path.isfile(OLD_IPV4_FILE): + old_ipv4 = read_file(OLD_IPV4_FILE).rstrip() + + if os.path.isfile(OLD_IPV6_FILE): + old_ipv6 = read_file(OLD_IPV6_FILE).rstrip() + + # Get current IPv4 and IPv6 + (ipv4_, ipv6_) = get_public_ips() + if ipv4 is None: - ipv4 = get_public_ip() + ipv4 = ipv4_ - try: - with open('/etc/yunohost/dyndns/old_ip', 'r') as f: - old_ip = f.readline().rstrip() - except IOError: - old_ip = '0.0.0.0' - - # IPv6 if ipv6 is None: - try: - ip_route_out = subprocess.check_output( - ['ip', 'route', 'get', '2000::']).split('\n') + ipv6 = ipv6_ - if len(ip_route_out) > 0: - route = IPRouteLine(ip_route_out[0]) - if not route.unreachable: - ipv6 = route.src_addr - - except (OSError, ValueError) as e: - # Unlikely case "ip route" does not return status 0 - # or produces unexpected output - raise MoulinetteError(errno.EBADMSG, - "ip route cmd error : {}".format(e)) - - if ipv6 is None: - logger.info(m18n.n('no_ipv6_connectivity')) - - try: - with open('/etc/yunohost/dyndns/old_ipv6', 'r') as f: - old_ipv6 = f.readline().rstrip() - except IOError: - old_ipv6 = '0000:0000:0000:0000:0000:0000:0000:0000' + logger.debug("Old IPv4/v6 are (%s, %s)" % (old_ipv4, old_ipv6)) + logger.debug("Requested IPv4/v6 are (%s, %s)" % (ipv4, ipv6)) # no need to update - if old_ip == ipv4 and old_ipv6 == ipv6: + if old_ipv4 == ipv4 and old_ipv6 == ipv6: + logger.info("No updated needed.") return + else: + logger.info("Updated needed, going on...") + # If domain is not given, try to guess it from keys available... if domain is None: - # Retrieve the first registered domain - for path in glob.iglob('/etc/yunohost/dyndns/K*.private'): - match = re_dyndns_private_key.match(path) - if not match: - continue - _domain = match.group('domain') - - try: - # Check if domain is registered - request_url = 'https://{0}/test/{1}'.format(dyn_host, _domain) - if requests.get(request_url, timeout=30).status_code == 200: - continue - except requests.ConnectionError: - raise MoulinetteError(errno.ENETUNREACH, - m18n.n('no_internet_connection')) - except requests.exceptions.Timeout: - logger.warning("Correction timed out on {}, skip it".format( - request_url)) - domain = _domain - key = path - break - if not domain: - raise MoulinetteError(errno.EINVAL, - m18n.n('dyndns_no_domain_registered')) - - if key is None: + (domain, key) = _guess_current_dyndns_domain(dyn_host) + # If key is not given, pick the first file we find with the domain given + elif key is None: keys = glob.glob('/etc/yunohost/dyndns/K{0}.+*.private'.format(domain)) if not keys: @@ -207,9 +213,12 @@ def dyndns_update(dyn_host="dyndns.yunohost.org", domain=None, key=None, key = keys[0] + # Extract 'host', e.g. 'nohost.me' from 'foo.nohost.me' host = domain.split('.')[1:] host = '.'.join(host) + logger.debug("Building zone update file ...") + lines = [ 'server %s' % dyn_host, 'zone %s' % host, @@ -246,21 +255,27 @@ def dyndns_update(dyn_host="dyndns.yunohost.org", domain=None, key=None, 'send' ] - with open('/etc/yunohost/dyndns/zone', 'w') as zone: - zone.write('\n'.join(lines)) + # 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)) - if os.system('/usr/bin/nsupdate -k %s /etc/yunohost/dyndns/zone' % key) != 0: - os.system('rm -f /etc/yunohost/dyndns/old_ip') - os.system('rm -f /etc/yunohost/dyndns/old_ipv6') + logger.info("Now pushing new conf to DynDNS host...") + + try: + command = ["/usr/bin/nsupdate", "-k", key, DYNDNS_ZONE] + subprocess.check_call(command) + except subprocess.CalledProcessError: + rm(OLD_IPV4_FILE, force=True) # Remove file (ignore if non-existent) + rm(OLD_IPV6_FILE, force=True) # Remove file (ignore if non-existent) raise MoulinetteError(errno.EPERM, m18n.n('dyndns_ip_update_failed')) logger.success(m18n.n('dyndns_ip_updated')) - with open('/etc/yunohost/dyndns/old_ip', 'w') as f: - f.write(ipv4) + + if ipv4 is not None: + write_to_file(OLD_IPV4_FILE, ipv4) if ipv6 is not None: - with open('/etc/yunohost/dyndns/old_ipv6', 'w') as f: - f.write(ipv6) + write_to_file(OLD_IPV6_FILE, ipv6) def dyndns_installcron(): @@ -287,3 +302,34 @@ def dyndns_removecron(): raise MoulinetteError(errno.EIO, m18n.n('dyndns_cron_remove_failed')) logger.success(m18n.n('dyndns_cron_removed')) + + +def _guess_current_dyndns_domain(dyn_host): + """ + This function tries to guess which domain should be updated by + "dyndns_update()" because there's not proper management of the current + dyndns domain :/ (and at the moment the code doesn't support having several + dyndns domain, which is sort of a feature so that people don't abuse the + dynette...) + """ + + re_dyndns_private_key = re.compile( + r'.*/K(?P[^\s\+]+)\.\+157.+\.private$' + ) + + # Retrieve the first registered domain + for path in glob.iglob('/etc/yunohost/dyndns/K*.private'): + match = re_dyndns_private_key.match(path) + if not match: + continue + _domain = match.group('domain') + + # Verify if domain is registered (i.e., if it's available, skip + # current domain beause that's not the one we want to update..) + if _dyndns_available(dyn_host, _domain): + continue + else: + return (_domain, path) + + raise MoulinetteError(errno.EINVAL, + m18n.n('dyndns_no_domain_registered')) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 22ac7894f..042671125 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -45,7 +45,7 @@ from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import read_json, write_to_json from yunohost.app import app_fetchlist, app_info, app_upgrade, app_ssowatconf, app_list, _install_appslist_fetch_cron from yunohost.domain import domain_add, domain_list, get_public_ip, _get_maindomain, _set_maindomain -from yunohost.dyndns import dyndns_subscribe +from yunohost.dyndns import _dyndns_available, _dyndns_provides from yunohost.firewall import firewall_upnp from yunohost.service import service_status, service_regen_conf, service_log from yunohost.monitor import monitor_disk, monitor_system @@ -253,29 +253,41 @@ def tools_postinstall(domain, password, ignore_dyndns=False): password -- YunoHost admin password """ - dyndns = not ignore_dyndns + dyndns_provider = "dyndns.yunohost.org" # Do some checks at first if os.path.isfile('/etc/yunohost/installed'): raise MoulinetteError(errno.EPERM, m18n.n('yunohost_already_installed')) - if len(domain.split('.')) >= 3 and not ignore_dyndns: - try: - r = requests.get('https://dyndns.yunohost.org/domains') - except requests.ConnectionError: - pass - else: - dyndomains = json.loads(r.text) - dyndomain = '.'.join(domain.split('.')[1:]) - if dyndomain in dyndomains: - if requests.get('https://dyndns.yunohost.org/test/%s' % domain).status_code == 200: - dyndns = True - else: - raise MoulinetteError(errno.EEXIST, - m18n.n('dyndns_unavailable')) - else: + if not ignore_dyndns: + # Check if yunohost dyndns can handle the given domain + # (i.e. is it a .nohost.me ? a .noho.st ?) + try: + is_nohostme_or_nohost = _dyndns_provides(dyndns_provider, domain) + # If an exception is thrown, most likely we don't have internet + # connectivity or something. Assume that this domain isn't manageable + # and inform the user that we could not contact the dyndns host server. + except: + logger.warning(m18n.n('dyndns_provider_unreachable', + provider=dyndns_provider)) + is_nohostme_or_nohost = False + + # If this is a nohost.me/noho.st, actually check for availability + if is_nohostme_or_nohost: + # (Except if the user explicitly said he/she doesn't care about dyndns) + if ignore_dyndns: dyndns = False + # Check if the domain is available... + elif _dyndns_available(dyndns_provider, domain): + dyndns = True + # If not, abort the postinstall + else: + raise MoulinetteError(errno.EEXIST, + m18n.n('dyndns_unavailable', + domain=domain)) + else: + dyndns = False else: dyndns = False From 9903c48f3a44fb5105866643ffe9d935e13fe3d9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 8 Oct 2017 23:46:10 +0200 Subject: [PATCH 15/29] Be able to give lock to son processes detached by systemctl (#367) * Give lock to systemctl sons * Get the 'need_lock' flag from services.yml * Don't need the lock for enable/disable and other stuff --- data/templates/yunohost/services.yml | 1 + src/yunohost/service.py | 57 ++++++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/data/templates/yunohost/services.yml b/data/templates/yunohost/services.yml index 514cf5258..fb8c076f9 100644 --- a/data/templates/yunohost/services.yml +++ b/data/templates/yunohost/services.yml @@ -47,6 +47,7 @@ yunohost-api: log: /var/log/yunohost/yunohost-api.log yunohost-firewall: status: service + need_lock: true nslcd: status: service log: /var/log/syslog diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 2ea953873..5401a1fab 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -39,10 +39,10 @@ from moulinette.utils import log, filesystem from yunohost.hook import hook_callback - BASE_CONF_PATH = '/home/yunohost.conf' BACKUP_CONF_DIR = os.path.join(BASE_CONF_PATH, 'backup') PENDING_CONF_DIR = os.path.join(BASE_CONF_PATH, 'pending') +MOULINETTE_LOCK = "/var/run/moulinette_yunohost.lock" logger = log.getActionLogger('yunohost.service') @@ -493,7 +493,8 @@ def _run_service_command(action, service): service -- Service name """ - if service not in _get_services().keys(): + services = _get_services() + if service not in services.keys(): raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown', service=service)) cmd = None @@ -505,8 +506,23 @@ def _run_service_command(action, service): else: raise ValueError("Unknown action '%s'" % action) + need_lock = (services[service].get('need_lock') or False) \ + and action in ['start', 'stop', 'restart', 'reload'] + try: - ret = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT) + # Launch the command + logger.debug("Running '%s'" % 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 + if need_lock: + PID = _give_lock(action, service, p) + # Wait for the command to complete + p.communicate() + # Remove the lock if one was given + if need_lock and PID != 0: + _remove_lock(PID) + except subprocess.CalledProcessError as e: # TODO: Log output? logger.warning(m18n.n('service_cmd_exec_failed', command=' '.join(e.cmd))) @@ -514,6 +530,41 @@ def _run_service_command(action, service): return True +def _give_lock(action, service, p): + + # Depending of the action, systemctl calls the PID differently :/ + if action == "start" or action == "restart": + systemctl_PID_name = "MainPID" + else: + systemctl_PID_name = "ControlPID" + + cmd_get_son_PID ="systemctl show %s -p %s" % (service, systemctl_PID_name) + son_PID = 0 + # As long as we did not found the PID and that the command is still running + while son_PID == 0 and p.poll() == None: + # Call systemctl to get the PID + # Output of the command is e.g. ControlPID=1234 + son_PID = subprocess.check_output(cmd_get_son_PID.split()) \ + .strip().split("=")[1] + son_PID = int(son_PID) + time.sleep(1) + + # If we found a PID + if son_PID != 0: + # Append the PID to the lock file + logger.debug("Giving a lock to PID %s for service %s !" + % (str(son_PID), service)) + filesystem.append_to_file(MOULINETTE_LOCK, "\n%s" % str(son_PID)) + + return son_PID + +def _remove_lock(PID_to_remove): + + PIDs = filesystem.read_file(MOULINETTE_LOCK).split("\n") + PIDs_to_keep = [ PID for PID in PIDs if int(PID) != PID_to_remove ] + filesystem.write_to_file(MOULINETTE_LOCK, '\n'.join(PIDs_to_keep)) + + def _get_services(): """ Get a dict of managed services with their parameters From 968ff5196d51cee44f0c5a181957d02ccdc55c1a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 12 Oct 2017 16:44:26 -0400 Subject: [PATCH 16/29] Update changelog for 2.7.3 release --- debian/changelog | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/debian/changelog b/debian/changelog index 312c626e3..e8f85388b 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,31 @@ +yunohost (2.7.3) testing; urgency=low + + Major changes : + + * [fix] Refactor/clean madness related to DynDNS (#353) + * [i18n] Improve french translation (#355) + * [fix] Use cryptorandom to generate password (#358) + * [enh] Support for single app upgrade from the webadmin (#359) + * [enh] Be able to give lock to son processes detached by systemctl (#367) + * [enh] Make MySQL dumps with a single transaction to ensure backup consistency (#370) + + Misc fixes/improvements : + + * [enh] Escape some special character in ynh_replace_string (#354) + * [fix] Allow dash at the beginning of app settings value (#357) + * [enh] Handle root path in nginx conf (#361) + * [enh] Add debugging in ldap init (#365) + * [fix] Fix app_upgrade_string with missing key + * [fix] Fix for change_url path normalizing with root url (#368) + * [fix] Missing 'ask_path' string (#369) + * [enh] Remove date from sql dump (#371) + * [fix] Fix unicode error in backup/restore (#375) + * [fix] Fix an error in ynh_replace_string (#379) + +Thanks to all contributors <3 ! (Bram, Maniack C, ljf, JimboJoe, ariasuni, Jibec, Aleks) + + -- Alexandre Aubin Thu, 12 Oct 2017 16:18:51 -0400 + yunohost (2.7.2) stable; urgency=low * [mod] pep8 From 4b99df7d07877cdaced5f8704a19f8f4cd713264 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Holcroft Date: Fri, 13 Oct 2017 08:44:42 +0000 Subject: [PATCH 17/29] Translated using Weblate (French) Currently translated at 100.0% (355 of 355 strings) Translation: YunoHost/YunoHost Translate-URL: https://holcroft.fr/weblate/projects/yunohost/yunohost/fr/ --- locales/fr.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 2685a231a..8bda0ddc3 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -98,7 +98,7 @@ "dyndns_no_domain_registered": "Aucun domaine n'a été enregistré avec DynDNS", "dyndns_registered": "Le domaine DynDNS a été enregistré", "dyndns_registration_failed": "Impossible d'enregistrer le domaine DynDNS : {error:s}", - "dyndns_unavailable": "Sous-domaine DynDNS indisponible", + "dyndns_unavailable": "Le domaine {domain:s} est indisponible.", "executing_command": "Exécution de la commande « {command:s} »...", "executing_script": "Exécution du script « {script:s} »...", "extracting": "Extraction...", @@ -365,5 +365,7 @@ "server_reboot": "Le serveur va redémarrer", "server_reboot_confirm": "Le serveur va redémarrer immédiatement, le voulez-vous vraiment ? [{answers:s}]", "app_upgrade_some_app_failed": "Impossible de mettre à jour certaines applications", - "ask_path": "Chemin" + "ask_path": "Chemin", + "dyndns_could_not_check_provide": "Impossible de vérifier si {provider:s} peut fournir {domain:s}.", + "dyndns_domain_not_provided": "Le fournisseur Dyndns {provider:s} ne peut pas fournir le domaine {domain:s}." } From dfa7ca28bf2e8f5180fcfc4519d8d88bcfc6fec7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 16 Oct 2017 21:31:59 +0200 Subject: [PATCH 18/29] [microdecision] Typo --- data/helpers.d/backend | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index c0cbc616c..b715776e2 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -123,7 +123,7 @@ ynh_add_nginx_config () { # To avoid a break by set -u, use a void substitution ${var:-}. If the variable is not set, it's simply set with an empty variable. # Substitute in a nginx config file only if the variable is not empty if test -n "${path_url:-}"; then - # path_url_slash_less if path_url or a blank value if path_url is only '/' + # path_url_slash_less is path_url, or a blank value if path_url is only '/' path_url_slash_less=${path_url%/} ynh_replace_string "__PATH__/" "$path_url_slash_less/" "$finalnginxconf" ynh_replace_string "__PATH__" "$path_url" "$finalnginxconf" From 46469b8893db8bc0e491bfa341261e702cf29789 Mon Sep 17 00:00:00 2001 From: M5oul Date: Tue, 21 Nov 2017 16:46:45 +0100 Subject: [PATCH 19/29] [fix] Update acme-tiny as LE updated its ToS (#386) - fix https://dev.yunohost.org/issues/1007 - Renewing a certificate is not problematic, but creating a certificate for a new domain name may failed according to recent feedbacks. --- src/yunohost/vendor/acme_tiny/acme_tiny.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/yunohost/vendor/acme_tiny/acme_tiny.py b/src/yunohost/vendor/acme_tiny/acme_tiny.py index d0ba33d1e..6fd8558d5 100644 --- a/src/yunohost/vendor/acme_tiny/acme_tiny.py +++ b/src/yunohost/vendor/acme_tiny/acme_tiny.py @@ -39,7 +39,7 @@ def get_crt(account_key, csr, acme_dir, log=LOGGER, CA=DEFAULT_CA): raise IOError("OpenSSL Error: {0}".format(err)) pub_hex, pub_exp = re.search( r"modulus:\n\s+00:([a-f0-9\:\s]+?)\npublicExponent: ([0-9]+)", - out.decode('utf8'), re.MULTILINE | re.DOTALL).groups() + out.decode('utf8'), re.MULTILINE|re.DOTALL).groups() pub_exp = "{0:x}".format(int(pub_exp)) pub_exp = "0{0}".format(pub_exp) if len(pub_exp) % 2 else pub_exp header = { @@ -82,10 +82,10 @@ def get_crt(account_key, csr, acme_dir, log=LOGGER, CA=DEFAULT_CA): if proc.returncode != 0: raise IOError("Error loading {0}: {1}".format(csr, err)) domains = set([]) - common_name = re.search(r"Subject:.*? CN=([^\s,;/]+)", out.decode('utf8')) + common_name = re.search(r"Subject:.*? CN\s?=\s?([^\s,;/]+)", out.decode('utf8')) if common_name is not None: domains.add(common_name.group(1)) - subject_alt_names = re.search(r"X509v3 Subject Alternative Name: \n +([^\n]+)\n", out.decode('utf8'), re.MULTILINE | re.DOTALL) + subject_alt_names = re.search(r"X509v3 Subject Alternative Name: \n +([^\n]+)\n", out.decode('utf8'), re.MULTILINE|re.DOTALL) if subject_alt_names is not None: for san in subject_alt_names.group(1).split(", "): if san.startswith("DNS:"): @@ -95,7 +95,7 @@ def get_crt(account_key, csr, acme_dir, log=LOGGER, CA=DEFAULT_CA): log.info("Registering account...") code, result = _send_signed_request(CA + "/acme/new-reg", { "resource": "new-reg", - "agreement": "https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf", + "agreement": json.loads(urlopen(CA + "/directory").read().decode('utf8'))['meta']['terms-of-service'], }) if code == 201: log.info("Registered!") From b560207423194411cf5d3289a4a5c4e0960ca3a7 Mon Sep 17 00:00:00 2001 From: YunoHost Bot Date: Sun, 26 Nov 2017 17:29:14 +0100 Subject: [PATCH 20/29] [i18n] Translated using Weblate (French) (#387) Currently translated at 100.0% (355 of 355 strings) --- locales/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index 8bda0ddc3..8baccbd70 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -1,7 +1,7 @@ { "action_invalid": "Action « {action:s} » incorrecte", "admin_password": "Mot de passe d'administration", - "admin_password_change_failed": "Impossible de modifier le mot de passe d'administration", + "admin_password_change_failed": "Impossible de changer le mot de passe", "admin_password_changed": "Le mot de passe d'administration a été modifié", "app_already_installed": "{app:s} est déjà installé", "app_argument_choice_invalid": "Choix invalide pour le paramètre « {name:s} », il doit être l'un de {choices:s}", From 7df6874fc999e7615e0bf8082a4f00e161c47656 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Tue, 28 Nov 2017 00:23:43 +0100 Subject: [PATCH 21/29] [fix] Open NTP port to update date --- data/templates/yunohost/firewall.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/templates/yunohost/firewall.yml b/data/templates/yunohost/firewall.yml index df5b0fe88..3ee284d97 100644 --- a/data/templates/yunohost/firewall.yml +++ b/data/templates/yunohost/firewall.yml @@ -4,7 +4,7 @@ uPnP: UDP: [53] ipv4: TCP: [22, 25, 53, 80, 443, 465, 587, 993, 5222, 5269] - UDP: [53, 5353] + UDP: [53, 5353, 123] ipv6: TCP: [22, 25, 53, 80, 443, 465, 587, 993, 5222, 5269] - UDP: [53, 5353] + UDP: [53, 5353, 123] From b0849cc43d944dd9b68ee5e15c3d6cb02730c8c5 Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Tue, 28 Nov 2017 00:25:06 +0100 Subject: [PATCH 22/29] Bad branch --- data/templates/yunohost/firewall.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/templates/yunohost/firewall.yml b/data/templates/yunohost/firewall.yml index 3ee284d97..df5b0fe88 100644 --- a/data/templates/yunohost/firewall.yml +++ b/data/templates/yunohost/firewall.yml @@ -4,7 +4,7 @@ uPnP: UDP: [53] ipv4: TCP: [22, 25, 53, 80, 443, 465, 587, 993, 5222, 5269] - UDP: [53, 5353, 123] + UDP: [53, 5353] ipv6: TCP: [22, 25, 53, 80, 443, 465, 587, 993, 5222, 5269] - UDP: [53, 5353, 123] + UDP: [53, 5353] From 17ba10ad924bebc94914cb7209164a356a39b499 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 28 Nov 2017 19:58:51 +0100 Subject: [PATCH 23/29] [fix] Fix helper for old apps without backup script (#388) --- data/helpers.d/utils | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 44c679471..2cb18c5c0 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -59,14 +59,19 @@ ynh_restore_upgradebackup () { # ynh_abort_if_errors # ynh_backup_before_upgrade () { + if [ ! -e "/etc/yunohost/apps/$app/scripts/backup" ] + then + echo "This app doesn't have any backup script." >&2 + return + fi backup_number=1 old_backup_number=2 app_bck=${app//_/-} # Replace all '_' by '-' - - # Check if a backup already exists with the prefix 1 + + # Check if a backup already exists with the prefix 1 if sudo yunohost backup list | grep -q $app_bck-pre-upgrade1 - then - # Prefix becomes 2 to preserve the previous backup + then + # Prefix becomes 2 to preserve the previous backup backup_number=2 old_backup_number=1 fi @@ -74,7 +79,7 @@ ynh_backup_before_upgrade () { # Create backup sudo yunohost backup create --ignore-system --apps $app --name $app_bck-pre-upgrade$backup_number if [ "$?" -eq 0 ] - then + then # If the backup succeeded, remove the previous backup if sudo yunohost backup list | grep -q $app_bck-pre-upgrade$old_backup_number then From cb1728f32c701219c6a4692c1940883eb1d084ae Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 28 Nov 2017 21:06:16 +0100 Subject: [PATCH 24/29] Remove port 53 from UPnP (but keep it open on local network) (#362) --- data/templates/yunohost/firewall.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/templates/yunohost/firewall.yml b/data/templates/yunohost/firewall.yml index df5b0fe88..201a39092 100644 --- a/data/templates/yunohost/firewall.yml +++ b/data/templates/yunohost/firewall.yml @@ -1,7 +1,7 @@ uPnP: enabled: false - TCP: [22, 25, 53, 80, 443, 465, 587, 993, 5222, 5269] - UDP: [53] + TCP: [22, 25, 80, 443, 465, 587, 993, 5222, 5269] + UDP: [] ipv4: TCP: [22, 25, 53, 80, 443, 465, 587, 993, 5222, 5269] UDP: [53, 5353] From a0d7279ad07c12117aa8db608365eb334b72fdb9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 28 Nov 2017 19:03:29 -0500 Subject: [PATCH 25/29] Update changelog for 2.7.4 release --- debian/changelog | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/debian/changelog b/debian/changelog index e8f85388b..c684283c3 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,14 @@ +yunohost (2.7.4) testing; urgency=low + + * [fix] Update acme-tiny as LE updated its ToS (#386) + * [fix] Fix helper for old apps without backup script (#388) + * [mod] Remove port 53 from UPnP (but keep it open on local network) (#362) + * [i18n] Improve French translation + +Thanks to all contributors <3 ! (jibec, Moul, Maniack, Aleks) + + -- Alexandre Aubin Tue, 28 Nov 2017 19:01:41 -0500 + yunohost (2.7.3) testing; urgency=low Major changes : From 4f62eb5320323d4f4de83b2af306ae53e89bc5ba Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 2 Dec 2017 16:44:47 +0100 Subject: [PATCH 26/29] Fix upgrade fake package --- data/helpers.d/package | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/data/helpers.d/package b/data/helpers.d/package index 36777aa52..f28691579 100644 --- a/data/helpers.d/package +++ b/data/helpers.d/package @@ -126,10 +126,7 @@ ynh_install_app_dependencies () { version=$(grep '\"version\": ' "$manifest_path" | cut -d '"' -f 4) # Retrieve the version number in the manifest file. dep_app=${app//_/-} # Replace all '_' by '-' - if ynh_package_is_installed "${dep_app}-ynh-deps"; then - echo "A package named ${dep_app}-ynh-deps is already installed" >&2 - else - cat > /tmp/${dep_app}-ynh-deps.control << EOF # Make a control file for equivs-build + cat > /tmp/${dep_app}-ynh-deps.control << EOF # Make a control file for equivs-build Section: misc Priority: optional Package: ${dep_app}-ynh-deps @@ -139,11 +136,10 @@ Architecture: all Description: Fake package for ${app} (YunoHost app) dependencies This meta-package is only responsible of installing its dependencies. EOF - ynh_package_install_from_equivs /tmp/${dep_app}-ynh-deps.control \ - || ynh_die "Unable to install dependencies" # Install the fake package and its dependencies - rm /tmp/${dep_app}-ynh-deps.control - ynh_app_setting_set $app apt_dependencies $dependencies - fi + ynh_package_install_from_equivs /tmp/${dep_app}-ynh-deps.control \ + || ynh_die "Unable to install dependencies" # Install the fake package and its dependencies + rm /tmp/${dep_app}-ynh-deps.control + ynh_app_setting_set $app apt_dependencies $dependencies } # Remove fake package and its dependencies From c4ae0fcb169af713d34e944e0a7e7f89cbfaaa28 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 2 Dec 2017 12:38:16 -0500 Subject: [PATCH 27/29] Update changelog for 2.7.5 release --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index c684283c3..bd7430cb7 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +yunohost (2.7.5) stable; urgency=low + + (Bumping version number for stable release) + + -- Alexandre Aubin Sat, 02 Dec 2017 12:38:00 -0500 + yunohost (2.7.4) testing; urgency=low * [fix] Update acme-tiny as LE updated its ToS (#386) From c13c75f3e76d33363f314b4634b937c33bb837f4 Mon Sep 17 00:00:00 2001 From: Josue-T Date: Fri, 8 Dec 2017 23:04:45 +0100 Subject: [PATCH 28/29] Solve ynh_use_logrotate Actually when we use the command `ynh_use_logrotate /var/log/abcd/` the result in the logrotate conf file is `/opt/yunohost/abcd/logs//.log` witch is not what we want. --- data/helpers.d/backend | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index b715776e2..c32ef02ac 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -6,7 +6,7 @@ # # If no argument provided, a standard directory will be use. /var/log/${app} # You can provide a path with the directory only or with the logfile. -# /parentdir/logdir/ +# /parentdir/logdir # /parentdir/logdir/logfile.log # # It's possible to use this helper several times, each config will be added to the same logrotate config file. @@ -24,7 +24,7 @@ ynh_use_logrotate () { if [ "$(echo ${1##*.})" == "log" ]; then # Keep only the extension to check if it's a logfile logfile=$1 # In this case, focus logrotate on the logfile else - logfile=$1/.log # Else, uses the directory and all logfile into it. + logfile=$1/*.log # Else, uses the directory and all logfile into it. fi else logfile="/var/log/${app}/*.log" # Without argument, use a defaut directory in /var/log From 542528ab05716c5335cdc6079e9bb0029292e24d Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Thu, 21 Dec 2017 19:19:33 +0100 Subject: [PATCH 29/29] Fix broken ynh_replace_string (#394) * Fix broken ynh_replace_string * Replace name for ynh_replace_special_string --- data/helpers.d/string | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/data/helpers.d/string b/data/helpers.d/string index fbf598738..13399ffe0 100644 --- a/data/helpers.d/string +++ b/data/helpers.d/string @@ -26,6 +26,27 @@ ynh_replace_string () { local replace_string=$2 local workfile=$3 + # Escape the delimiter if it's in the string. + match_string=${match_string//${delimit}/"\\${delimit}"} + replace_string=${replace_string//${delimit}/"\\${delimit}"} + + sudo sed --in-place "s${delimit}${match_string}${delimit}${replace_string}${delimit}g" "$workfile" +} + +# Substitute/replace a special string by another in a file +# +# usage: ynh_replace_special_string match_string replace_string target_file +# | arg: match_string - String to be searched and replaced in the file +# | arg: replace_string - String that will replace matches +# | arg: target_file - File in which the string will be replaced. +# +# This helper will use ynh_replace_string, but as you can use special +# characters, you can't use some regular expressions and sub-expressions. +ynh_replace_special_string () { + local match_string=$1 + local replace_string=$2 + local workfile=$3 + # Escape any backslash to preserve them as simple backslash. match_string=${match_string//\\/"\\\\"} replace_string=${replace_string//\\/"\\\\"} @@ -34,9 +55,5 @@ ynh_replace_string () { match_string=${match_string//&/"\&"} replace_string=${replace_string//&/"\&"} - # Escape the delimiter if it's in the string. - match_string=${match_string//${delimit}/"\\${delimit}"} - replace_string=${replace_string//${delimit}/"\\${delimit}"} - - sudo sed --in-place "s${delimit}${match_string}${delimit}${replace_string}${delimit}g" "$workfile" + ynh_replace_string "$match_string" "$replace_string" "$workfile" }