From 7da60a6aec78802462c1a11b9512fd781ef4bf59 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Fri, 2 Oct 2015 15:23:12 -0400 Subject: [PATCH 001/170] Add an app_debug function --- data/actionsmap/yunohost.yml | 8 ++++++++ lib/yunohost/app.py | 13 +++++++++++++ 2 files changed, 21 insertions(+) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 546f7b504..f8ed8f153 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -499,6 +499,14 @@ app: full: --sql help: Initial SQL file + ### app_debug() + debug: + action_help: Display all debug informations for an application + api: GET /apps//debug + arguments: + app: + help: App name + ### app_makedefault() makedefault: action_help: Redirect domain root to an app diff --git a/lib/yunohost/app.py b/lib/yunohost/app.py index 3376a7aad..d4fb7f5d1 100644 --- a/lib/yunohost/app.py +++ b/lib/yunohost/app.py @@ -722,6 +722,19 @@ def app_clearaccess(auth, apps): app_ssowatconf(auth) +def app_debug(app): + with open(apps_setting_path + app + '/manifest.json') as f: + manifest = json.loads(f.read()) + + return { + 'name': manifest['name'], + 'services_logs': [{ + 'service': x, + 'log': service_log(x), + } for x in manifest.get("services", [])] + } + + def app_makedefault(auth, app, domain=None): """ Redirect domain root to an app From aed11d921261a8e7a207a23b377be968575c316e Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Fri, 2 Oct 2015 16:15:01 -0400 Subject: [PATCH 002/170] Fix: missing import --- lib/yunohost/app.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/yunohost/app.py b/lib/yunohost/app.py index d4fb7f5d1..338555df9 100644 --- a/lib/yunohost/app.py +++ b/lib/yunohost/app.py @@ -39,6 +39,8 @@ import subprocess from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger +from .service import service_log + logger = getActionLogger('yunohost.app') repo_path = '/var/cache/yunohost/repo' From fd81c0950d0a175b53da9fbd589635de6ecad41c Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Fri, 2 Oct 2015 16:19:37 -0400 Subject: [PATCH 003/170] Simplify app debug json structure --- lib/yunohost/app.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/yunohost/app.py b/lib/yunohost/app.py index 338555df9..f18b45f9f 100644 --- a/lib/yunohost/app.py +++ b/lib/yunohost/app.py @@ -731,9 +731,8 @@ def app_debug(app): return { 'name': manifest['name'], 'services_logs': [{ - 'service': x, - 'log': service_log(x), - } for x in manifest.get("services", [])] + x: service_log(x), + } for x in sorted(manifest.get("services", []))] } From e7fd25c82e97df69c1ced2d82dfb6ae393ab4303 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Fri, 2 Oct 2015 21:15:33 -0400 Subject: [PATCH 004/170] [mod] mustache is a bit stupid, modify js to match its lameness --- lib/yunohost/app.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/yunohost/app.py b/lib/yunohost/app.py index f18b45f9f..6a38d8420 100644 --- a/lib/yunohost/app.py +++ b/lib/yunohost/app.py @@ -729,9 +729,14 @@ def app_debug(app): manifest = json.loads(f.read()) return { - 'name': manifest['name'], - 'services_logs': [{ - x: service_log(x), + 'name': manifest['id'], + 'label': manifest['name'], + 'services': [{ + "name": x, + "logs": [{ + "file_name": y, + "file_content": "\n".join(z), + } for (y, z) in sorted(service_log(x).items(), key=lambda x: x[0])], } for x in sorted(manifest.get("services", []))] } From c2cd0bdc1f6a0854a9f05c8969532ca70632eb15 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sat, 3 Oct 2015 15:57:36 -0400 Subject: [PATCH 005/170] Add docstring to app_debug --- lib/yunohost/app.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/yunohost/app.py b/lib/yunohost/app.py index 6a38d8420..eabddba0b 100644 --- a/lib/yunohost/app.py +++ b/lib/yunohost/app.py @@ -725,6 +725,12 @@ def app_clearaccess(auth, apps): def app_debug(app): + """ + Display debug informations for an app + + Keyword argument: + app + """ with open(apps_setting_path + app + '/manifest.json') as f: manifest = json.loads(f.read()) From 76c7b3b3dbb2ce1178261f4ba4712a6dda119b9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Mon, 5 Oct 2015 15:01:40 +0200 Subject: [PATCH 006/170] [fix] Restructure app restauration and catch app script failure * Do not copy again the restore app script * Set app folder permissions and properly remove it on failure * Add a raise_on_error argument to hook_exec * Review displayed messages --- data/actionsmap/yunohost.yml | 3 + lib/yunohost/backup.py | 109 +++++++++++++++++++---------------- lib/yunohost/hook.py | 5 +- locales/en.json | 9 +-- 4 files changed, 71 insertions(+), 55 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 66744525b..1693ac271 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1346,3 +1346,6 @@ hook: -a: full: --args help: Arguments to pass to the script + --raise-on-error: + help: Raise if the script returns a non-zero exit code + action: store_true diff --git a/lib/yunohost/backup.py b/lib/yunohost/backup.py index 0e9ab63c5..7a911ddec 100644 --- a/lib/yunohost/backup.py +++ b/lib/yunohost/backup.py @@ -162,31 +162,21 @@ def backup_create(name=None, description=None, output_directory=None, for app_id in apps_filtered: app_setting_path = '/etc/yunohost/apps/' + app_id - tmp_app_dir = '{:s}/apps/{:s}'.format(tmp_dir, app_id) - - # Check if the app has a backup script + # Check if the app has a backup and restore script app_script = app_setting_path + '/scripts/backup' + app_restore_script = app_setting_path + '/scripts/restore' if not os.path.isfile(app_script): logger.warning("backup script '%s' not found", app_script) - msignals.display(m18n.n('unbackup_app', app_id), + msignals.display(m18n.n('unbackup_app', app=app_id), 'warning') continue - - # Copy the app restore script - app_restore_script = app_setting_path + '/scripts/restore' - if os.path.isfile(app_script): - try: - filesystem.mkdir(tmp_app_dir, 0750, True, uid='admin') - shutil.copy(app_restore_script, tmp_app_dir) - except: - logger.exception("error while copying restore script of '%s'", app_id) - msignals.display(m18n.n('restore_app_copy_failed', app=app_id), - 'warning') - else: - logger.warning("restore script '%s' not found", app_script) - msignals.display(m18n.n('unrestorable_app', app_id), + elif not os.path.isfile(app_restore_script): + logger.warning("restore script '%s' not found", + app_restore_script) + msignals.display(m18n.n('unrestore_app', app=app_id), 'warning') + tmp_app_dir = '{:s}/apps/{:s}'.format(tmp_dir, app_id) tmp_app_bkp_dir = tmp_app_dir + '/backup' msignals.display(m18n.n('backup_running_app_script', app_id)) try: @@ -196,7 +186,8 @@ def backup_create(name=None, description=None, output_directory=None, # Copy app backup script in a temporary folder and execute it subprocess.call(['install', '-Dm555', app_script, tmp_script]) - hook_exec(tmp_script, args=[tmp_app_bkp_dir, app_id]) + hook_exec(tmp_script, args=[tmp_app_bkp_dir, app_id], + raise_on_error=True) except: logger.exception("error while executing backup of '%s'", app_id) msignals.display(m18n.n('backup_app_failed', app=app_id), @@ -351,7 +342,7 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals if not ignore_hooks: if hooks is None or len(hooks)==0: hooks=info['hooks'].keys() - + hooks_filtered=list(set(hooks) & set(info['hooks'].keys())) hooks_unexecuted=set(hooks) - set(info['hooks'].keys()) for hook in hooks_unexecuted: @@ -359,49 +350,67 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals msignals.display(m18n.n('backup_hook_unavailable', hook), 'warning') msignals.display(m18n.n('restore_running_hooks')) hook_callback('restore', hooks_filtered, args=[tmp_dir]) - + # Add apps restore hook if not ignore_apps: + from yunohost.app import _is_installed + # Filter applications to restore apps_list = set(info['apps'].keys()) apps_filtered = set() - if not apps: - apps=apps_list - - from yunohost.app import _is_installed - for app_id in apps: - if app_id not in apps_list: - logger.warning("app '%s' not found", app_id) - msignals.display(m18n.n('unrestore_app', app_id), 'warning') - elif _is_installed(app_id): + if apps: + for a in apps: + if a not in apps_list: + logger.warning("app '%s' not found in the backup '%s'", + a, archive_file) + msignals.display(m18n.n('backup_archive_app_not_found', + app=a), + 'error') + else: + apps_filtered.add(a) + else: + apps_filtered = apps_list + + for app_id in apps_filtered: + tmp_app_dir = '{:s}/apps/{:s}'.format(tmp_dir, app_id) + + # Check if the app is not already installed + if _is_installed(app_id): logger.warning("app '%s' already installed", app_id) - msignals.display(m18n.n('restore_already_installed_app', app=app_id), 'warning') - elif not os.path.isfile('{:s}/apps/{:s}/restore'.format(tmp_dir, app_id)): - logger.warning("backup for '%s' doesn't contain a restore script", app_id) - msignals.display(m18n.n('no_restore_script', app=app_id), 'warning') - else: - apps_filtered.add(app_id) + msignals.display(m18n.n('restore_already_installed_app', + app=app_id), + 'error') + continue - for app_id in apps_filtered: - app_bkp_dir='{:s}/apps/{:s}'.format(tmp_dir, app_id) + # Check if the app has a restore script + app_script = tmp_app_dir + '/settings/scripts/restore' + if not os.path.isfile(app_script): + logger.warning("restore script for the app '%s' not found " \ + "in the backup '%s'", app_id, archive_file) + msignals.display(m18n.n('unrestore_app', app=app_id), 'warning') + continue + + tmp_script = '/tmp/restore_' + app_id + app_setting_path = '/etc/yunohost/apps/' + app_id + msignals.display(m18n.n('restore_running_app_script', app=app_id)) try: - # Copy app settings - app_setting_path = '/etc/yunohost/apps/' + app_id - shutil.copytree(app_bkp_dir + '/settings', app_setting_path ) - - # Execute app restore script - app_restore_script=app_bkp_dir+'/restore' - tmp_script = '/tmp/restore_%s_%s' % (name,app_id) - subprocess.call(['install', '-Dm555', app_restore_script, tmp_script]) - hook_exec(tmp_script, args=[app_bkp_dir+'/backup', app_id]) + # Copy app settings and set permissions + shutil.copytree(tmp_app_dir + '/settings', app_setting_path) + filesystem.chmod(app_setting_path, 0555, 0444, True) + filesystem.chmod(app_setting_path + '/settings.yml', 0400) + # Execute app restore script + subprocess.call(['install', '-Dm555', app_script, tmp_script]) + hook_exec(tmp_script, args=[tmp_app_dir + '/backup', app_id], + raise_on_error=True) except: logger.exception("error while restoring backup of '%s'", app_id) msignals.display(m18n.n('restore_app_failed', app=app_id), 'error') - # Cleaning settings directory - shutil.rmtree(app_setting_path + '/settings', ignore_errors=True) - + # Cleaning app directory + shutil.rmtree(app_setting_path, ignore_errors=True) + finally: + filesystem.rm(tmp_script, force=True) # Remove temporary directory os.system('rm -rf %s' % tmp_dir) diff --git a/lib/yunohost/hook.py b/lib/yunohost/hook.py index 96ffff35d..1661edc83 100644 --- a/lib/yunohost/hook.py +++ b/lib/yunohost/hook.py @@ -260,13 +260,14 @@ def hook_check(file): return {} -def hook_exec(file, args=None): +def hook_exec(file, args=None, raise_on_error=False): """ Execute hook from a file with arguments Keyword argument: file -- Script to execute args -- Arguments to pass to the script + raise_on_error -- Raise if the script returns a non-zero exit code """ from moulinette.utils.stream import NonBlockingStreamReader @@ -344,6 +345,8 @@ def hook_exec(file, args=None): msignals.display(line.rstrip(), 'log') stream.close() + if raise_on_error and returncode != 0: + raise MoulinetteError(m18n.n('hook_exec_failed')) return returncode diff --git a/locales/en.json b/locales/en.json index ba4e6b072..1890f5c73 100644 --- a/locales/en.json +++ b/locales/en.json @@ -86,6 +86,7 @@ "hook_name_unknown" : "Unknown hook name '{:s}'", "hook_choice_invalid" : "Invalid choice '{:s}'", "hook_argument_missing" : "Missing argument '{:s}'", + "hook_exec_failed" : "Script execution failed", "mountpoint_unknown" : "Unknown mountpoint", "unit_unknown" : "Unknown unit '{:s}'", @@ -150,6 +151,7 @@ "backup_archive_open_failed" : "Unable to open the backup archive", "backup_archive_name_unknown" : "Unknown local backup archive named '{:s}'", "backup_archive_name_exists" : "Backup archive name already exists", + "backup_archive_app_not_found" : "App '{app:s}' not found in the backup archive", "backup_app_failed" : "Unable to back up the app '{app:s}'", "backup_nothings_done" : "There is nothing to save", "backup_cleaning_failed" : "Unable to clean backup directory", @@ -159,14 +161,13 @@ "restore_confirm_yunohost_installed" : "Do you really want to restore an already installed system? [{answers:s}]", "restore_app_failed" : "Unable to restore the app '{app:s}'", "restore_running_hooks" : "Running restoration hooks...", + "restore_running_app_script" : "Running restore script of app '{app:s}'...", "restore_failed" : "Unable to restore the system", "restore_complete" : "Restore complete", "restore_already_installed_app": "An app is already installed with the id '{app:s}'", - "unbackup_app" : "App '{:s}' will not be saved", - "unrestorable_app" : "App '{:s}' will not be restored", - "restore_app_copy_failed" : "Unable to copy the restore script of app '{app:s}'", + "unbackup_app" : "App '{app:s}' will not be saved", "no_restore_script": "No restore script found for the app '{app:s}'", - "unrestore_app" : "App '{:s}' will not be restored", + "unrestore_app" : "App '{app:s}' will not be restored", "backup_delete_error" : "Unable to delete '{:s}'", "backup_deleted" : "Backup successfully deleted", From a51e395bcaaecd91caa253ade9388c8115b799cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Mon, 5 Oct 2015 17:43:48 +0200 Subject: [PATCH 007/170] [fix] Validate backup/restore hooks and show restoration result --- lib/yunohost/backup.py | 106 ++++++++++++++++++++++++++++++++--------- locales/en.json | 9 +++- 2 files changed, 90 insertions(+), 25 deletions(-) diff --git a/lib/yunohost/backup.py b/lib/yunohost/backup.py index 7a911ddec..30911e8fc 100644 --- a/lib/yunohost/backup.py +++ b/lib/yunohost/backup.py @@ -62,7 +62,7 @@ def backup_create(name=None, description=None, output_directory=None, """ # TODO: Add a 'clean' argument to clean output directory - from yunohost.hook import hook_callback, hook_exec + from yunohost.hook import hook_list, hook_callback, hook_exec tmp_dir = None @@ -136,9 +136,24 @@ def backup_create(name=None, description=None, output_directory=None, # Run system hooks if not ignore_hooks: - msignals.display(m18n.n('backup_running_hooks')) - hooks_ret = hook_callback('backup', hooks, args=[tmp_dir]) - info['hooks'] = hooks_ret['succeed'] + # Check hooks availibility + hooks_available = hook_list('backup')['hooks'] + hooks_filtered = set() + if hooks: + for hook in hooks: + if hook not in hooks_available: + logger.exception("backup hook '%s' not found", hook) + msignals.display(m18n.n('backup_hook_unknown', hook=hook), + 'error') + else: + hooks_filtered.add(hook) + else: + hooks_filtered = hooks_available + + if hooks_filtered: + msignals.display(m18n.n('backup_running_hooks')) + ret = hook_callback('backup', hooks_filtered, args=[tmp_dir]) + info['hooks'] = ret['succeed'] # Backup apps if not ignore_apps: @@ -205,10 +220,10 @@ def backup_create(name=None, description=None, output_directory=None, finally: filesystem.rm(tmp_script, force=True) - # Check if something has been saved - if ignore_hooks and not info['apps']: - _clean_tmp_dir(1) - raise MoulinetteError(errno.EINVAL, m18n.n('backup_nothings_done')) + # Check if something has been saved + if not info['hooks'] and not info['apps']: + _clean_tmp_dir(1) + raise MoulinetteError(errno.EINVAL, m18n.n('backup_nothings_done')) # Create backup info file with open("%s/info.json" % tmp_dir, 'w') as f: @@ -270,9 +285,12 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals force -- Force restauration on an already installed system """ - from yunohost.hook import hook_add - from yunohost.hook import hook_callback - from yunohost.hook import hook_exec + from yunohost.hook import hook_add, hook_list, hook_callback, hook_exec + + # Validate what to restore + if ignore_hooks and ignore_apps: + raise MoulinetteError(errno.EINVAL, + m18n.n('restore_action_required')) # Retrieve and open the archive info = backup_info(name) @@ -291,6 +309,13 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals tmp_dir) os.system('rm -rf %s' % tmp_dir) + def _clean_tmp_dir(retcode=0): + ret = hook_callback('post_backup_restore', args=[tmp_dir, retcode]) + if not ret['failed']: + filesystem.rm(tmp_dir, True, True) + else: + msignals.display(m18n.n('restore_cleaning_failed'), 'warning') + # Extract the tarball msignals.display(m18n.n('backup_extracting_archive')) tar.extractall(tmp_dir) @@ -308,6 +333,12 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals logger.info("restoring from backup '%s' created on %s", name, time.ctime(info['created_at'])) + # Initialize restauration summary result + result = { + 'apps': [], + 'hooks': {}, + } + # Check if YunoHost is installed if os.path.isfile('/etc/yunohost/installed'): msignals.display(m18n.n('yunohost_already_installed'), 'warning') @@ -338,18 +369,40 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals logger.info("executing the post-install...") tools_postinstall(domain, 'yunohost', True) - # Run hooks + # Run system hooks if not ignore_hooks: - if hooks is None or len(hooks)==0: - hooks=info['hooks'].keys() + # Filter hooks to execute + hooks_list = set(info['hooks'].keys()) + _is_hook_in_backup = lambda h: True + if hooks: + def _is_hook_in_backup(h): + if h in hooks_list: + return True + logger.warning("hook '%s' not executed in the backup '%s'", + h, archive_file) + msignals.display(m18n.n('backup_archive_hook_not_exec', hook=h), + 'error') + return False + else: + hooks = hooks_list - hooks_filtered=list(set(hooks) & set(info['hooks'].keys())) - hooks_unexecuted=set(hooks) - set(info['hooks'].keys()) - for hook in hooks_unexecuted: - logger.warning("hook '%s' not in this backup", hook) - msignals.display(m18n.n('backup_hook_unavailable', hook), 'warning') - msignals.display(m18n.n('restore_running_hooks')) - hook_callback('restore', hooks_filtered, args=[tmp_dir]) + # Check hooks availibility + hooks_available = hook_list('restore')['hooks'] + hooks_filtered = set() + for hook in hooks: + if not _is_hook_in_backup(hook): + continue + if hook not in hooks_available: + logger.exception("restoration hook '%s' not found", hook) + msignals.display(m18n.n('restore_hook_unavailable', hook=hook), + 'error') + continue + hooks_filtered.add(hook) + + if hooks_filtered: + msignals.display(m18n.n('restore_running_hooks')) + ret = hook_callback('restore', hooks_filtered, args=[tmp_dir]) + result['hooks'] = ret['succeed'] # Add apps restore hook if not ignore_apps: @@ -409,14 +462,21 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals 'error') # Cleaning app directory shutil.rmtree(app_setting_path, ignore_errors=True) + else: + result['apps'].append(app_id) finally: filesystem.rm(tmp_script, force=True) - # Remove temporary directory - os.system('rm -rf %s' % tmp_dir) + # Check if something has been restored + if not result['hooks'] and not result['apps']: + _clean_tmp_dir(1) + raise MoulinetteError(errno.EINVAL, m18n.n('restore_nothings_done')) + _clean_tmp_dir() msignals.display(m18n.n('restore_complete'), 'success') + return result + def backup_list(with_info=False, human_readable=False): """ diff --git a/locales/en.json b/locales/en.json index 1890f5c73..02ddc02a2 100644 --- a/locales/en.json +++ b/locales/en.json @@ -144,6 +144,7 @@ "backup_output_directory_required" : "You must provide an output directory for the backup", "backup_output_directory_forbidden" : "Forbidden output directory", "backup_output_directory_not_empty" : "Output directory is not empty", + "backup_hook_unknown" : "Backup hook '{hook:s}' unknown", "backup_running_hooks" : "Running backup hooks...", "backup_running_app_script" : "Running backup script of app '{:s}'...", "backup_creating_archive" : "Creating the backup archive...", @@ -151,18 +152,22 @@ "backup_archive_open_failed" : "Unable to open the backup archive", "backup_archive_name_unknown" : "Unknown local backup archive named '{:s}'", "backup_archive_name_exists" : "Backup archive name already exists", + "backup_archive_hook_not_exec" : "Hook '{hook:s}' not executed in this backup", "backup_archive_app_not_found" : "App '{app:s}' not found in the backup archive", "backup_app_failed" : "Unable to back up the app '{app:s}'", "backup_nothings_done" : "There is nothing to save", - "backup_cleaning_failed" : "Unable to clean backup directory", + "backup_cleaning_failed" : "Unable to clean backup temporary directory", "backup_complete" : "Backup complete", "backup_invalid_archive" : "Invalid backup archive", - "backup_hook_unavailable" : "The hook '{:s}' is not in this backup", + "restore_action_required" : "You must specify something to restore", "restore_confirm_yunohost_installed" : "Do you really want to restore an already installed system? [{answers:s}]", + "restore_hook_unavailable" : "Restauration hook '{hook:s}' not available on your system", "restore_app_failed" : "Unable to restore the app '{app:s}'", "restore_running_hooks" : "Running restoration hooks...", "restore_running_app_script" : "Running restore script of app '{app:s}'...", "restore_failed" : "Unable to restore the system", + "restore_nothings_done" : "Nothing has been restored", + "restore_cleaning_failed" : "Unable to clean restoration temporary directory", "restore_complete" : "Restore complete", "restore_already_installed_app": "An app is already installed with the id '{app:s}'", "unbackup_app" : "App '{app:s}' will not be saved", From c16203bd2a0e2e4fae1cd9b474fea62ba9ea91a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Mon, 5 Oct 2015 17:49:07 +0200 Subject: [PATCH 008/170] [enh] Set default backup name to a more readable format --- lib/yunohost/backup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/yunohost/backup.py b/lib/yunohost/backup.py index 30911e8fc..aa74b28a8 100644 --- a/lib/yunohost/backup.py +++ b/lib/yunohost/backup.py @@ -74,7 +74,7 @@ def backup_create(name=None, description=None, output_directory=None, # Validate and define backup name timestamp = int(time.time()) if not name: - name = str(timestamp) + name = time.strftime('%Y%m%d-%H%M%S') if name in backup_list()['archives']: raise MoulinetteError(errno.EINVAL, m18n.n('backup_archive_name_exists')) From 369dec6775837063b98c031772780d5c2c139d6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Tue, 6 Oct 2015 12:43:10 +0200 Subject: [PATCH 009/170] [enh] Add a hook_info action --- data/actionsmap/yunohost.yml | 10 +++++++++ lib/yunohost/hook.py | 43 +++++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 1693ac271..958bc8c1f 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1292,6 +1292,16 @@ hook: app: help: Scripts related to app will be removed + ### hook_info() + info: + action_help: Get information about a given hook + api: GET /hooks// + arguments: + action: + help: Action name + name: + help: Hook name + ### hook_list() list: action_help: List available hooks for an action diff --git a/lib/yunohost/hook.py b/lib/yunohost/hook.py index 1661edc83..d4c7962cc 100644 --- a/lib/yunohost/hook.py +++ b/lib/yunohost/hook.py @@ -29,6 +29,7 @@ import re import json import errno import subprocess +from glob import iglob from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger @@ -77,6 +78,46 @@ def hook_remove(app): except OSError: pass +def hook_info(action, name): + """ + Get information about a given hook + + Keyword argument: + action -- Action name + name -- Hook name + + """ + hooks = [] + priorities = set() + + # Search in custom folder first + for h in iglob('{:s}{:s}/*-{:s}'.format( + custom_hook_folder, action, name)): + priority, _ = _extract_filename_parts(os.path.basename(h)) + priorities.add(priority) + hooks.append({ + 'priority': priority, + 'path': h, + }) + # Append non-overwritten system hooks + for h in iglob('{:s}{:s}/*-{:s}'.format( + hook_folder, action, name)): + priority, _ = _extract_filename_parts(os.path.basename(h)) + if priority not in priorities: + hooks.append({ + 'priority': priority, + 'path': h, + }) + + if not hooks: + raise MoulinetteError(errno.EINVAL, m18n.n('hook_name_unknown', name)) + return { + 'action': action, + 'name': name, + 'hooks': hooks, + } + + def hook_list(action, list_by='name', show_info=False): """ List available hooks for an action @@ -194,7 +235,7 @@ def hook_callback(action, hooks=[], args=None): if key == n or key.startswith("%s_" % n) \ and key not in all_hooks: all_hooks.append(key) - + # Iterate over given hooks names list for n in all_hooks: try: From dd309205d24f5b83106c882c94eac8b1d6d1f433 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Tue, 6 Oct 2015 12:48:16 +0200 Subject: [PATCH 010/170] [enh] Use hook_info in backup and save/restore restoration hooks --- lib/yunohost/backup.py | 56 ++++++++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/lib/yunohost/backup.py b/lib/yunohost/backup.py index aa74b28a8..796d1488b 100644 --- a/lib/yunohost/backup.py +++ b/lib/yunohost/backup.py @@ -32,6 +32,7 @@ import time import tarfile import shutil import subprocess +from glob import glob from collections import OrderedDict from moulinette.core import MoulinetteError @@ -62,7 +63,7 @@ def backup_create(name=None, description=None, output_directory=None, """ # TODO: Add a 'clean' argument to clean output directory - from yunohost.hook import hook_list, hook_callback, hook_exec + from yunohost.hook import hook_info, hook_callback, hook_exec tmp_dir = None @@ -137,11 +138,12 @@ def backup_create(name=None, description=None, output_directory=None, # Run system hooks if not ignore_hooks: # Check hooks availibility - hooks_available = hook_list('backup')['hooks'] hooks_filtered = set() if hooks: for hook in hooks: - if hook not in hooks_available: + try: + hook_info('backup', hook) + except: logger.exception("backup hook '%s' not found", hook) msignals.display(m18n.n('backup_hook_unknown', hook=hook), 'error') @@ -153,7 +155,23 @@ def backup_create(name=None, description=None, output_directory=None, if hooks_filtered: msignals.display(m18n.n('backup_running_hooks')) ret = hook_callback('backup', hooks_filtered, args=[tmp_dir]) - info['hooks'] = ret['succeed'] + if ret['succeed']: + info['hooks'] = ret['succeed'] + + # Save relevant restoration hooks + tmp_hooks_dir = tmp_dir + '/hooks/restore' + filesystem.mkdir(tmp_hooks_dir, 0750, True, uid='admin') + for h in ret['succeed'].keys(): + try: + i = hook_info('restore', h) + except: + logger.exception("no restoration hook for '%s'", h) + msignals.display(m18n.n('restore_hook_unavailable', + hook=h), + 'warning') + else: + for f in i['hooks']: + shutil.copy(f['path'], tmp_hooks_dir) # Backup apps if not ignore_apps: @@ -285,7 +303,8 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals force -- Force restauration on an already installed system """ - from yunohost.hook import hook_add, hook_list, hook_callback, hook_exec + from yunohost.hook import hook_info, hook_callback, hook_exec + from yunohost.hook import custom_hook_folder # Validate what to restore if ignore_hooks and ignore_apps: @@ -387,17 +406,28 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals hooks = hooks_list # Check hooks availibility - hooks_available = hook_list('restore')['hooks'] hooks_filtered = set() - for hook in hooks: - if not _is_hook_in_backup(hook): + for h in hooks: + if not _is_hook_in_backup(h): continue - if hook not in hooks_available: - logger.exception("restoration hook '%s' not found", hook) - msignals.display(m18n.n('restore_hook_unavailable', hook=hook), + try: + hook_info('restore', h) + except: + tmp_hooks = glob('{:s}/hooks/restore/*-{:s}'.format(tmp_dir, h)) + if not tmp_hooks: + logger.exception("restoration hook '%s' not found", h) + msignals.display(m18n.n('restore_hook_unavailable', hook=h), 'error') - continue - hooks_filtered.add(hook) + continue + # Add restoration hook from the backup to the system + # FIXME: Refactor hook_add and use it instead + restore_hook_folder = custom_hook_folder + 'restore' + filesystem.mkdir(restore_hook_folder, 755, True) + for f in tmp_hooks: + logger.info("adding restoration hook '%s' to the system " \ + "from the backup archive '%s'", f, archive_file) + shutil.copy(f, restore_hook_folder) + hooks_filtered.add(h) if hooks_filtered: msignals.display(m18n.n('restore_running_hooks')) From c9b8099d74ce8d2e43d37bac120a98498cc60a57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Tue, 6 Oct 2015 23:00:31 +0200 Subject: [PATCH 011/170] [fix] Add Python2 in Build-Depends and review description --- debian/control | 13 ++++++++----- debian/rules | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/debian/control b/debian/control index e9d036216..3bfe4916b 100644 --- a/debian/control +++ b/debian/control @@ -2,13 +2,14 @@ Source: yunohost Section: utils Priority: extra Maintainer: YunoHost Contributors -Build-Depends: debhelper (>=9), dh-systemd +Build-Depends: debhelper (>=9), dh-systemd, dh-python Standards-Version: 3.9.6 +X-Python-Version: >= 2.7 Homepage: https://yunohost.org/ Package: yunohost Architecture: all -Depends: ${misc:Depends}, ${shlibs:Depends}, +Depends: ${misc:Depends}, ${shlibs:Depends}, ${python:Depends}, moulinette (>= 2.2.1), python-psutil, python-requests, @@ -51,8 +52,10 @@ Replaces: moulinette-yunohost, yunohost-config, yunohost-config-nginx, yunohost-config-amavis, yunohost-config-mysql, yunohost-predepends Description: YunoHost installation package - YunoHost aims to make self-hosting accessible to everyone. + The goal of YunoHost is to make self-hosting accessible to everyone. It + configures an email, Web and IM server alongside a LDAP base. It also + provides facilities to manage users, domains, apps and so. . This package contains YunoHost scripts and binaries to be used by the - moulinette. It allows to manage the server with a command-line tool and - an API. + moulinette. It allows one to manage the server with a command-line tool + and an API. diff --git a/debian/rules b/debian/rules index 5de55b6d6..c61bb8858 100755 --- a/debian/rules +++ b/debian/rules @@ -5,7 +5,7 @@ #export DH_VERBOSE=1 %: - dh ${@} --with=systemd + dh ${@} --with=systemd,python2 override_dh_installinit: dh_installinit --name=yunohost-api From 37c84a6c080e6923e942bbb62dbcb6df10945ef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Tue, 6 Oct 2015 23:09:36 +0200 Subject: [PATCH 012/170] [fix] Remove python2 from debian/rules --- debian/rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/rules b/debian/rules index c61bb8858..5de55b6d6 100755 --- a/debian/rules +++ b/debian/rules @@ -5,7 +5,7 @@ #export DH_VERBOSE=1 %: - dh ${@} --with=systemd,python2 + dh ${@} --with=systemd override_dh_installinit: dh_installinit --name=yunohost-api From 15ccd6054544862eb63c287958a2f612c925dae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Wed, 7 Oct 2015 00:43:48 +0200 Subject: [PATCH 013/170] [fix] Fix some lintian issues and add yunohost-firewall.service --- debian/control | 8 ++++---- debian/postinst | 4 ++++ debian/rules | 2 +- debian/yunohost-firewall.service | 14 ++++++++++++++ 4 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 debian/yunohost-firewall.service diff --git a/debian/control b/debian/control index 3bfe4916b..31796381f 100644 --- a/debian/control +++ b/debian/control @@ -2,14 +2,14 @@ Source: yunohost Section: utils Priority: extra Maintainer: YunoHost Contributors -Build-Depends: debhelper (>=9), dh-systemd, dh-python +Build-Depends: debhelper (>=9), dh-systemd, dh-python, python-all (>= 2.7) Standards-Version: 3.9.6 X-Python-Version: >= 2.7 Homepage: https://yunohost.org/ Package: yunohost Architecture: all -Depends: ${misc:Depends}, ${shlibs:Depends}, ${python:Depends}, +Depends: ${python:Depends}, ${misc:Depends}, moulinette (>= 2.2.1), python-psutil, python-requests, @@ -51,8 +51,8 @@ Replaces: moulinette-yunohost, yunohost-config, yunohost-config-dovecot, yunohost-config-slapd, yunohost-config-nginx, yunohost-config-amavis, yunohost-config-mysql, yunohost-predepends -Description: YunoHost installation package - The goal of YunoHost is to make self-hosting accessible to everyone. It +Description: manageable and configured self-hosting server + YunoHost aims to to make self-hosting accessible to everyone. It configures an email, Web and IM server alongside a LDAP base. It also provides facilities to manage users, domains, apps and so. . diff --git a/debian/postinst b/debian/postinst index 2a13c8e83..ceaca3e4b 100644 --- a/debian/postinst +++ b/debian/postinst @@ -38,3 +38,7 @@ case "$1" in exit 1 ;; esac + +#DEBHELPER# + +exit 0 diff --git a/debian/rules b/debian/rules index 5de55b6d6..c61bb8858 100755 --- a/debian/rules +++ b/debian/rules @@ -5,7 +5,7 @@ #export DH_VERBOSE=1 %: - dh ${@} --with=systemd + dh ${@} --with=systemd,python2 override_dh_installinit: dh_installinit --name=yunohost-api diff --git a/debian/yunohost-firewall.service b/debian/yunohost-firewall.service new file mode 100644 index 000000000..1dd46f477 --- /dev/null +++ b/debian/yunohost-firewall.service @@ -0,0 +1,14 @@ +[Unit] +Description=YunoHost Firewall +Requires=network.target +After=network.target + +[Service] +Type=oneshot +ExecStart=/usr/bin/yunohost firewall reload +ExecReload=/usr/bin/yunohost firewall reload +ExecStop=/usr/bin/yunohost firewall stop +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target From ec9465059504a0292f770f46e3acc2c0b79ed27c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Wed, 7 Oct 2015 01:36:34 +0200 Subject: [PATCH 014/170] [typo] Duplicated word 'to' in package description --- debian/control | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/debian/control b/debian/control index 31796381f..f95292075 100644 --- a/debian/control +++ b/debian/control @@ -52,9 +52,9 @@ Replaces: moulinette-yunohost, yunohost-config, yunohost-config-nginx, yunohost-config-amavis, yunohost-config-mysql, yunohost-predepends Description: manageable and configured self-hosting server - YunoHost aims to to make self-hosting accessible to everyone. It - configures an email, Web and IM server alongside a LDAP base. It also - provides facilities to manage users, domains, apps and so. + YunoHost aims to make self-hosting accessible to everyone. It configures + an email, Web and IM server alongside a LDAP base. It also provides + facilities to manage users, domains, apps and so. . This package contains YunoHost scripts and binaries to be used by the moulinette. It allows one to manage the server with a command-line tool From e439737259350a9127cd83735692bda126efd4cf Mon Sep 17 00:00:00 2001 From: Adrien Beudin Date: Wed, 7 Oct 2015 14:19:29 +0200 Subject: [PATCH 015/170] Sieve permission denied --- data/hooks/conf_regen/25-dovecot | 1 + 1 file changed, 1 insertion(+) diff --git a/data/hooks/conf_regen/25-dovecot b/data/hooks/conf_regen/25-dovecot index 974fa3e0b..fe4f0c8eb 100644 --- a/data/hooks/conf_regen/25-dovecot +++ b/data/hooks/conf_regen/25-dovecot @@ -43,6 +43,7 @@ sudo rm -rf /etc/dovecot/global_script sudo mkdir -p -m 0770 /etc/dovecot/global_script safe_copy sa-learn-pipe.sh /usr/bin/sa-learn-pipe.sh sudo chmod 755 /usr/bin/sa-learn-pipe.sh +sudo chown vmail:mail /etc/dovecot/global_script safe_copy dovecot.sieve /etc/dovecot/global_script/dovecot.sieve sudo chmod 660 /etc/dovecot/global_script/dovecot.sieve > /dev/null 2>&1 \ From 00e066414e95c57e43b872e2d084efb4af239359 Mon Sep 17 00:00:00 2001 From: Adrien Beudin Date: Wed, 7 Oct 2015 16:39:06 +0200 Subject: [PATCH 016/170] [fix] Sieve permission denied --- data/hooks/conf_regen/25-dovecot | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/data/hooks/conf_regen/25-dovecot b/data/hooks/conf_regen/25-dovecot index fe4f0c8eb..642fa9570 100644 --- a/data/hooks/conf_regen/25-dovecot +++ b/data/hooks/conf_regen/25-dovecot @@ -43,13 +43,12 @@ sudo rm -rf /etc/dovecot/global_script sudo mkdir -p -m 0770 /etc/dovecot/global_script safe_copy sa-learn-pipe.sh /usr/bin/sa-learn-pipe.sh sudo chmod 755 /usr/bin/sa-learn-pipe.sh -sudo chown vmail:mail /etc/dovecot/global_script safe_copy dovecot.sieve /etc/dovecot/global_script/dovecot.sieve sudo chmod 660 /etc/dovecot/global_script/dovecot.sieve > /dev/null 2>&1 \ || safe_copy dovecot.sieve /etc/dovecot/global_script/dovecot.sieve sudo sievec /etc/dovecot/global_script/dovecot.sieve sudo chmod 660 /etc/dovecot/global_script/dovecot.svbin - +sudo chown -R vmail:mail /etc/dovecot/global_script sudo service dovecot restart From 4905dfc9cca99218868d121fb53a1684d9e1f51c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Thu, 8 Oct 2015 12:01:19 +0200 Subject: [PATCH 017/170] [rip] Replace old moulinette-yunohost package name --- lib/yunohost/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/yunohost/__init__.py b/lib/yunohost/__init__.py index 97cd1f5f3..3a7dd37c9 100644 --- a/lib/yunohost/__init__.py +++ b/lib/yunohost/__init__.py @@ -33,6 +33,6 @@ def get_versions(*args, **kwargs): from collections import OrderedDict return OrderedDict([ ('moulinette', get_version('moulinette')), - ('moulinette-yunohost', get_version('moulinette-yunohost')), + ('yunohost', get_version('yunohost')), ('yunohost-admin', get_version('yunohost-admin')), ]) From 911bcfa2eb4e37e659990b6e989d021961954374 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Thu, 8 Oct 2015 12:20:53 +0200 Subject: [PATCH 018/170] [fix] Remove undefined hooks variable in backup_create --- lib/yunohost/backup.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/yunohost/backup.py b/lib/yunohost/backup.py index 796d1488b..429780a12 100644 --- a/lib/yunohost/backup.py +++ b/lib/yunohost/backup.py @@ -149,10 +149,8 @@ def backup_create(name=None, description=None, output_directory=None, 'error') else: hooks_filtered.add(hook) - else: - hooks_filtered = hooks_available - if hooks_filtered: + if not hooks or hooks_filtered: msignals.display(m18n.n('backup_running_hooks')) ret = hook_callback('backup', hooks_filtered, args=[tmp_dir]) if ret['succeed']: From bff9916a90f89b937a86cd3d2854ee9575203224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Thu, 8 Oct 2015 13:34:44 +0200 Subject: [PATCH 019/170] Translated using Weblate (French) Currently translated at 88.0% (177 of 201 strings) --- locales/fr.json | 355 ++++++++++++++++++++++++------------------------ 1 file changed, 178 insertions(+), 177 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index d46c4c3e4..ca38c94f4 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -1,178 +1,179 @@ { - "yunohost_not_installed": "YunoHost n'est pas ou pas correctement installé. Veuillez exécuter 'yunohost tools postinstall'.", - "upgrade_complete": "Mise à jour terminée", - "installation_complete": "Installation terminée", - "installation_failed": "Échec de l'installation", - "unexpected_error": "Une erreur inattendue est survenue", - "action_invalid": "Action '{:s}' incorrecte", - "license_undefined": "indéfinie", - "no_appslist_found": "Aucune liste d'applications trouvée", - "custom_appslist_name_required": "Vous devez spécifier un nom pour votre liste d'applications personnalisée", - "appslist_retrieve_error": "Impossible de récupérer la liste d'applications distante", - "appslist_fetched": "Liste d'applications récupérée avec succès", - "appslist_unknown": "Liste d'applications inconnue", - "appslist_removed": "Liste d'applications supprimée avec succès", - "app_unknown": "Application inconnue", - "app_no_upgrade": "Aucune application à mettre à jour", - "app_not_installed": "{:s} n'est pas installé", - "custom_app_url_required": "Vous devez spécifier une URL pour mettre à jour votre application locale {:s}", - "app_recent_version_required": "{:s} nécessite une version plus récente de la moulinette", - "app_upgraded": "{:s} mis à jour avec succès", - "app_upgrade_failed": "Impossible de mettre à jour toutes les applications", - "app_id_invalid": "Id d'application incorrect", - "app_already_installed": "{:s} est déjà installé", - "app_removed": "{:s} supprimé avec succès", - "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_extraction_failed": "Impossible d'extraire les fichiers d'installation", - "app_install_files_invalid": "Fichiers d'installation incorrects", - "app_manifest_invalid": "Manifeste d'application incorrect", - "app_sources_fetch_failed": "Impossible de récupérer les fichiers sources", - "ssowat_conf_updated": "Configuration persistante de SSOwat mise à jour avec succès", - "ssowat_conf_generated": "Configuration de SSOwat générée avec succès", - "mysql_db_creation_failed": "Impossible de créer la base de donnée MySQL", - "mysql_db_init_failed": "Impossible d'initialiser la base de donnée MySQL", - "mysql_db_initialized": "Base de donnée MySQL initialisée avec succès", - "extracting": "Extraction...", - "downloading": "Téléchargement...", - "executing_script": "Exécution du script...", - "done": "Terminé.", - "path_removal_failed": "Impossible de supprimer le chemin {:s}", - "domain_unknown": "Domaine inconnu", - "domain_dyndns_invalid": "Domaine incorrect pour un usage avec DynDNS", - "domain_dyndns_already_subscribed": "Vous avez déjà souscris à un domaine DynDNS", - "domain_dyndns_root_unknown": "Domaine DynDNS principal inconnu", - "domain_cert_gen_failed": "Impossible de générer le certificat", - "domain_exists": "Le domaine existe déjà", - "dnsmasq_isnt_installed": "dnsmasq ne semble pas être installé, veuillez lancer 'apt-get remove bind9 && apt-get install dnsmasq'", - "domain_zone_exists": "Le fichier de zone DNS existe déjà", - "domain_zone_not_found": "Fichier de zone DNS introuvable pour le domaine {:s}", - "domain_creation_failed": "Impossible de créer le domaine", - "domain_created": "Domaine créé avec succès", - "domain_uninstall_app_first": "Une ou plusieurs applications sont installées sur ce domaine. Veuillez d'abord les désinstaller avant de supprimer ce domaine.", - "domain_deletion_failed": "Impossible de supprimer le domaine", - "domain_deleted": "Domaine supprimé avec succès", - "no_internet_connection": "Le serveur n'est pas connecté à Internet", - "no_ipv6_connectivity": "IPv6 n'est pas disponible", - "dyndns_key_generating": "La clé DNS est en cours de génération, cela peut prendre du temps...", - "dyndns_unavailable": "Sous-domaine DynDNS indisponible", - "dyndns_registration_failed": "Impossible d'enregistrer le domaine DynDNS : {:s}", - "dyndns_registered": "Domaine DynDNS enregistré avec succès", - "dyndns_ip_update_failed": "Impossible de mettre à jour l'adresse IP sur le domaine DynDNS", - "dyndns_ip_updated": "Adresse IP mise à jour avec succès sur le domaine DynDNS", - "dyndns_cron_installed": "Tâche cron pour DynDNS installée avec succès", - "dyndns_cron_remove_failed": "Impossible d'enlever la tâche cron pour DynDNS", - "dyndns_cron_removed": "Tâche cron pour DynDNS enlevée avec succès", - "port_available": "Le port {} est disponible", - "port_unavailable": "Le port {} n'est pas disponible", - "port_already_opened": "Le port {} est déjà ouvert pour les connexions {:s}", - "port_already_closed": "Le port {} est déjà fermé pour les connexions {:s}", - "iptables_unavailable": "Vous ne pouvez pas faire joujou avec iptables ici. Vous êtes sûrement dans un conteneur, autrement votre noyau ne le supporte pas.", - "ip6tables_unavailable": "Vous ne pouvez pas faire joujou avec ip6tables ici. Vous êtes sûrement dans un conteneur, autrement votre noyau ne le supporte pas.", - "upnp_dev_not_found": "Aucun périphérique compatible UPnP trouvé", - "upnp_port_open_failed": "Impossible d'ouvrir les ports avec UPnP", - "upnp_enabled": "UPnP activé avec succès", - "upnp_disabled": "UPnP désactivé avec succès", - "firewall_rules_cmd_failed": "Certaines règles du pare-feu n'ont pas pu être appliquées. Pour plus d'informations, consultez le journal.", - "firewall_reload_failed": "Impossible de recharger le pare-feu", - "firewall_reloaded": "Pare-feu rechargé avec succès", - "hook_list_by_invalid": "Propriété pour lister les scripts incorrecte", - "hook_name_unknown": "Nom de script '{:s}' inconnu", - "hook_choice_invalid": "Choix incorrect : '{:s}'", - "hook_argument_missing": "Argument manquant : '{:s}'", - "mountpoint_unknown": "Point de montage inconnu", - "unit_unknown": "Unité '{:s}' inconnue", - "monitor_period_invalid": "Période de temps incorrect", - "monitor_stats_no_update": "Aucune donnée de l'état du serveur à mettre à jour", - "monitor_stats_file_not_found": "Fichier de données de l'état du serveur introuvable", - "monitor_stats_period_unavailable": "Aucune donnée de l'état du serveur disponible pour la période", - "monitor_enabled": "Suivi de l'état du serveur activé avec succès", - "monitor_disabled": "Suivi de l'état du serveur désactivé avec succès", - "monitor_not_enabled": "Le suivi de l'état du serveur n'est pas activé", - "monitor_glances_con_failed": "Impossible de se connecter au serveur Glances", - "service_unknown": "Service '{:s}' inconnu", - "service_add_failed": "Impossible d'ajouter le service '{:s}'", - "service_added": "Service ajouté avec succès", - "service_remove_failed": "Impossible d'enlever le service '{:s}'", - "service_removed": "Service enlevé avec succès", - "service_start_failed": "Impossible de démarrer le service '{:s}'", - "service_already_started": "Le service '{:s}' est déjà démarré", - "service_started": "Service '{:s}' démarré avec succès", - "service_stop_failed": "Impossible d'arrêter le service '{:s}'", - "service_already_stopped": "Le service '{:s}' est déjà arrêté", - "service_stopped": "Service '{:s}' arrêté avec succès", - "service_enable_failed": "Impossible d'activer le service '{:s}'", - "service_enabled": "Service '{:s}' activé avec succès", - "service_disable_failed": "Impossible de désactiver le service '{:s}'", - "service_disabled": "Service '{:s}' désactivé avec succès", - "service_status_failed": "Impossible de déterminer le statut du service '{:s}'", - "service_no_log": "Aucun journal a afficher pour le service '{:s}'", - "service_cmd_exec_failed": "Impossible d'exécuter la commande '{:s}'", - "ldap_initialized": "Répertoire LDAP initialisé avec succès", - "admin_password_change_failed": "Impossible de modifier le mot de passe d'administration", - "admin_password_changed": "Mot de passe d'administration modifié avec succès", - "new_domain_required": "Vous devez spécifier le nouveau domaine principal", - "maindomain_change_failed": "Impossible de modifier le domaine principal", - "maindomain_changed": "Domaine principal modifié avec succès", - "yunohost_installing": "Installation de YunoHost...", - "yunohost_already_installed": "YunoHost est déjà installé", - "yunohost_ca_creation_failed": "Impossible de créer l'autorité de certification", - "yunohost_configured": "YunoHost configuré avec succès", - "updating_apt_cache": "Mise à jour de la liste des paquets disponibles...", - "update_cache_failed": "Impossible de mettre à jour le cache de l'APT", - "packages_no_upgrade": "Il n'y a aucun paquet à mettre à jour", - "packages_upgrade_critical_later": "Les paquets critiques ({:s}) seront mis à jour plus tard", - "upgrading_packages": "Mise à jour des paquets...", - "packages_upgrade_failed": "Impossible de mettre à jour tous les paquets", - "system_upgraded": "Système mis à jour avec succès", - "backup_output_directory_required": "Vous devez spécifier un dossier de sortie pour la sauvegarde", - "backup_output_directory_forbidden": "Dossier de sortie interdit", - "backup_output_directory_not_empty": "Le dossier de sortie n'est pas vide", - "backup_running_hooks": "Exécution des scripts de sauvegarde...", - "backup_creating_archive": "Création de l'archive de sauvegarde...", - "backup_extracting_archive": "Extraction de l'archive de sauvegarde...", - "backup_archive_open_failed": "Impossible d'ouvrir l'archive de sauvegarde", - "backup_archive_name_unknown": "Nom d'archive de sauvegarde locale inconnu", - "backup_archive_name_exists": "Une archive de sauvegarde avec ce nom existe déjà", - "backup_complete": "Sauvegarde terminée", - "backup_invalid_archive": "Archive de sauvegarde incorrecte", - "restore_confirm_yunohost_installed": "Voulez-vous vraiment restaurer un système déjà installé ? [{answers:s}]", - "restore_running_hooks": "Exécution des scripts de restauration...", - "restore_failed": "Impossible de restaurer le système", - "restore_complete": "Restauration terminée", - "unbackup_app": "L'application '{:s}' ne sera pas sauvegardée", - "unrestore_app": "L'application '{:s}' ne sera pas restaurée", - "field_invalid": "Champ incorrect : {:s}", - "mail_domain_unknown": "Domaine '{:s}' de l'adresse mail inconnu", - "mail_alias_remove_failed": "Impossible de supprimer l'adresse mail supplémentaire '{:s}'", - "mail_forward_remove_failed": "Impossible de supprimer l'adresse mail de transfert '{:s}'", - "system_username_exists": "Le nom d'utilisateur existe déjà dans les utilisateurs système", - "user_unknown": "Utilisateur inconnu", - "user_creation_failed": "Impossible de créer l'utilisateur", - "user_created": "Utilisateur créé avec succès", - "user_deletion_failed": "Impossible de supprimer l'utilisateur", - "user_deleted": "Utilisateur supprimé avec succès", - "user_update_failed": "Impossible de modifier l'utilisateur", - "user_updated": "Utilisateur modifié avec succès", - "user_info_failed": "Impossible de récupérer les informations de l'utilisateur", - "admin_password": "Mot de passe d'administration", - "ask_firstname": "Prénom", - "ask_lastname": "Nom", - "ask_email": "Adresse mail", - "ask_password": "Mot de passe", - "ask_current_admin_password": "Mot de passe d'administration actuel", - "ask_new_admin_password": "Nouveau mot de passe d'administration", - "ask_main_domain": "Domaine principal", - "ask_list_to_remove": "Liste à supprimer", - "pattern_username": "Doit être composé uniquement de caractères alphanumérique minuscule et de tiret bas", - "pattern_firstname": "Doit être un prénom valide", - "pattern_lastname": "Doit être un nom valide", - "pattern_email": "Doit être une adresse mail valide (ex. : someone@domain.org)", - "pattern_password": "Doit être composé d'au moins 3 caractères", - "pattern_domain": "Doit être un nom de domaine valide (ex : mon-domaine.org)", - "pattern_listname": "Doit être composé uniquement de caractères alphanumérique et de tiret bas", - "pattern_port": "Doit être un numéro de port valide (0-65535)", - "pattern_port_or_range": "Doit être un numéro de port valide (0-65535) ou une gamme de ports (ex : 100:200)", - "pattern_backup_archive_name": "Doit être un nom de fichier valide composé de caractères alphanumérique et -_. uniquement", - "format_datetime_short": "%d/%m/%Y %H:%M" -} + "action_invalid": "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_changed": "Mot de passe d'administration modifié avec succès", + "app_already_installed": "{:s} est déjà installé", + "app_extraction_failed": "Impossible d'extraire les fichiers d'installation", + "app_id_invalid": "Id d'application incorrect", + "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_no_upgrade": "Aucune application à mettre à jour", + "app_not_installed": "{:s} n'est pas installé", + "app_recent_version_required": "{:s} nécessite une version plus récente de la moulinette", + "app_removed": "{:s} supprimé avec succès", + "app_sources_fetch_failed": "Impossible de récupérer les fichiers sources", + "app_unknown": "Application inconnue", + "app_upgrade_failed": "Impossible de mettre à jour toutes les applications", + "app_upgraded": "{:s} mis à jour avec succès", + "appslist_fetched": "Liste d'applications récupérée avec succès", + "appslist_removed": "Liste d'applications supprimée avec succès", + "appslist_retrieve_error": "Impossible de récupérer la liste d'applications distante", + "appslist_unknown": "Liste d'applications inconnue", + "ask_current_admin_password": "Mot de passe d'administration actuel", + "ask_email": "Adresse mail", + "ask_firstname": "Prénom", + "ask_lastname": "Nom", + "ask_list_to_remove": "Liste à supprimer", + "ask_main_domain": "Domaine principal", + "ask_new_admin_password": "Nouveau mot de passe d'administration", + "ask_password": "Mot de passe", + "backup_archive_name_exists": "Une archive de sauvegarde avec ce nom existe déjà", + "backup_archive_name_unknown": "Nom d'archive de sauvegarde locale inconnu", + "backup_archive_open_failed": "Impossible d'ouvrir l'archive de sauvegarde", + "backup_complete": "Sauvegarde terminée", + "backup_creating_archive": "Création de l'archive de sauvegarde...", + "backup_extracting_archive": "Extraction de l'archive de sauvegarde...", + "backup_invalid_archive": "Archive de sauvegarde incorrecte", + "backup_output_directory_forbidden": "Dossier de sortie interdit", + "backup_output_directory_not_empty": "Le dossier de sortie n'est pas vide", + "backup_output_directory_required": "Vous devez spécifier un dossier de sortie pour la sauvegarde", + "backup_running_app_script": "Lancement du script de sauvegarde de l'application '{:s}'...", + "backup_running_hooks": "Exécution des scripts de sauvegarde...", + "custom_app_url_required": "Vous devez spécifier une URL pour mettre à jour votre application locale {:s}", + "custom_appslist_name_required": "Vous devez spécifier un nom pour votre liste d'applications personnalisée", + "dnsmasq_isnt_installed": "dnsmasq ne semble pas être installé, veuillez lancer 'apt-get remove bind9 && apt-get install dnsmasq'", + "domain_cert_gen_failed": "Impossible de générer le certificat", + "domain_created": "Domaine créé avec succès", + "domain_creation_failed": "Impossible de créer le domaine", + "domain_deleted": "Domaine supprimé avec succès", + "domain_deletion_failed": "Impossible de supprimer le domaine", + "domain_dyndns_already_subscribed": "Vous avez déjà souscris à un domaine DynDNS", + "domain_dyndns_invalid": "Domaine incorrect pour un usage avec DynDNS", + "domain_dyndns_root_unknown": "Domaine DynDNS principal inconnu", + "domain_exists": "Le domaine existe déjà", + "domain_uninstall_app_first": "Une ou plusieurs applications sont installées sur ce domaine. Veuillez d'abord les désinstaller avant de supprimer ce domaine.", + "domain_unknown": "Domaine inconnu", + "domain_zone_exists": "Le fichier de zone DNS existe déjà", + "domain_zone_not_found": "Fichier de zone DNS introuvable pour le domaine {:s}", + "done": "Terminé.", + "downloading": "Téléchargement...", + "dyndns_cron_installed": "Tâche cron pour DynDNS installée avec succès", + "dyndns_cron_remove_failed": "Impossible d'enlever la tâche cron pour DynDNS", + "dyndns_cron_removed": "Tâche cron pour DynDNS enlevée avec succès", + "dyndns_ip_update_failed": "Impossible de mettre à jour l'adresse IP sur le domaine DynDNS", + "dyndns_ip_updated": "Adresse IP mise à jour avec succès sur le domaine DynDNS", + "dyndns_key_generating": "La clé DNS est en cours de génération, cela peut prendre du temps...", + "dyndns_registered": "Domaine DynDNS enregistré avec succès", + "dyndns_registration_failed": "Impossible d'enregistrer le domaine DynDNS : {:s}", + "dyndns_unavailable": "Sous-domaine DynDNS indisponible", + "executing_script": "Exécution du script...", + "extracting": "Extraction...", + "field_invalid": "Champ incorrect : {:s}", + "firewall_reload_failed": "Impossible de recharger le pare-feu", + "firewall_reloaded": "Pare-feu rechargé avec succès", + "firewall_rules_cmd_failed": "Certaines règles du pare-feu n'ont pas pu être appliquées. Pour plus d'informations, consultez le journal.", + "format_datetime_short": "%d/%m/%Y %H:%M", + "hook_argument_missing": "Argument manquant : '{:s}'", + "hook_choice_invalid": "Choix incorrect : '{:s}'", + "hook_list_by_invalid": "Propriété pour lister les scripts incorrecte", + "hook_name_unknown": "Nom de script '{:s}' inconnu", + "installation_complete": "Installation terminée", + "installation_failed": "Échec de l'installation", + "ip6tables_unavailable": "Vous ne pouvez pas faire joujou avec ip6tables ici. Vous êtes sûrement dans un conteneur, autrement votre noyau ne le supporte pas.", + "iptables_unavailable": "Vous ne pouvez pas faire joujou avec iptables ici. Vous êtes sûrement dans un conteneur, autrement votre noyau ne le supporte pas.", + "ldap_initialized": "Répertoire LDAP initialisé avec succès", + "license_undefined": "indéfinie", + "mail_alias_remove_failed": "Impossible de supprimer l'adresse mail supplémentaire '{:s}'", + "mail_domain_unknown": "Domaine '{:s}' de l'adresse mail inconnu", + "mail_forward_remove_failed": "Impossible de supprimer l'adresse mail de transfert '{:s}'", + "maindomain_change_failed": "Impossible de modifier le domaine principal", + "maindomain_changed": "Domaine principal modifié avec succès", + "monitor_disabled": "Suivi de l'état du serveur désactivé avec succès", + "monitor_enabled": "Suivi de l'état du serveur activé avec succès", + "monitor_glances_con_failed": "Impossible de se connecter au serveur Glances", + "monitor_not_enabled": "Le suivi de l'état du serveur n'est pas activé", + "monitor_period_invalid": "Période de temps incorrect", + "monitor_stats_file_not_found": "Fichier de données de l'état du serveur introuvable", + "monitor_stats_no_update": "Aucune donnée de l'état du serveur à mettre à jour", + "monitor_stats_period_unavailable": "Aucune donnée de l'état du serveur disponible pour la période", + "mountpoint_unknown": "Point de montage inconnu", + "mysql_db_creation_failed": "Impossible de créer la base de donnée MySQL", + "mysql_db_init_failed": "Impossible d'initialiser la base de donnée MySQL", + "mysql_db_initialized": "Base de donnée MySQL initialisée avec succès", + "new_domain_required": "Vous devez spécifier le nouveau domaine principal", + "no_appslist_found": "Aucune liste d'applications trouvée", + "no_internet_connection": "Le serveur n'est pas connecté à Internet", + "no_ipv6_connectivity": "IPv6 n'est pas disponible", + "packages_no_upgrade": "Il n'y a aucun paquet à mettre à jour", + "packages_upgrade_critical_later": "Les paquets critiques ({:s}) seront mis à jour plus tard", + "packages_upgrade_failed": "Impossible de mettre à jour tous les paquets", + "path_removal_failed": "Impossible de supprimer le chemin {:s}", + "pattern_backup_archive_name": "Doit être un nom de fichier valide composé de caractères alphanumérique et -_. uniquement", + "pattern_domain": "Doit être un nom de domaine valide (ex : mon-domaine.org)", + "pattern_email": "Doit être une adresse mail valide (ex. : someone@domain.org)", + "pattern_firstname": "Doit être un prénom valide", + "pattern_lastname": "Doit être un nom valide", + "pattern_listname": "Doit être composé uniquement de caractères alphanumérique et de tiret bas", + "pattern_password": "Doit être composé d'au moins 3 caractères", + "pattern_port": "Doit être un numéro de port valide (0-65535)", + "pattern_port_or_range": "Doit être un numéro de port valide (0-65535) ou une gamme de ports (ex : 100:200)", + "pattern_username": "Doit être composé uniquement de caractères alphanumérique minuscule et de tiret bas", + "port_already_closed": "Le port {} est déjà fermé pour les connexions {:s}", + "port_already_opened": "Le port {} est déjà ouvert pour les connexions {:s}", + "port_available": "Le port {} est disponible", + "port_unavailable": "Le port {} n'est pas disponible", + "restore_complete": "Restauration terminée", + "restore_confirm_yunohost_installed": "Voulez-vous vraiment restaurer un système déjà installé ? [{answers:s}]", + "restore_failed": "Impossible de restaurer le système", + "restore_running_hooks": "Exécution des scripts de restauration...", + "service_add_failed": "Impossible d'ajouter le service '{:s}'", + "service_added": "Service ajouté avec succès", + "service_already_started": "Le service '{:s}' est déjà démarré", + "service_already_stopped": "Le service '{:s}' est déjà arrêté", + "service_cmd_exec_failed": "Impossible d'exécuter la commande '{:s}'", + "service_disable_failed": "Impossible de désactiver le service '{:s}'", + "service_disabled": "Service '{:s}' désactivé avec succès", + "service_enable_failed": "Impossible d'activer le service '{:s}'", + "service_enabled": "Service '{:s}' activé avec succès", + "service_no_log": "Aucun journal a afficher pour le service '{:s}'", + "service_remove_failed": "Impossible d'enlever le service '{:s}'", + "service_removed": "Service enlevé avec succès", + "service_start_failed": "Impossible de démarrer le service '{:s}'", + "service_started": "Service '{:s}' démarré avec succès", + "service_status_failed": "Impossible de déterminer le statut du service '{:s}'", + "service_stop_failed": "Impossible d'arrêter le service '{:s}'", + "service_stopped": "Service '{:s}' arrêté avec succès", + "service_unknown": "Service '{:s}' inconnu", + "ssowat_conf_generated": "Configuration de SSOwat générée avec succès", + "ssowat_conf_updated": "Configuration persistante de SSOwat mise à jour avec succès", + "system_upgraded": "Système mis à jour avec succès", + "system_username_exists": "Le nom d'utilisateur existe déjà dans les utilisateurs système", + "unbackup_app": "L'application '{:s}' ne sera pas sauvegardée", + "unexpected_error": "Une erreur inattendue est survenue", + "unit_unknown": "Unité '{:s}' inconnue", + "unrestore_app": "L'application '{:s}' ne sera pas restaurée", + "update_cache_failed": "Impossible de mettre à jour le cache de l'APT", + "updating_apt_cache": "Mise à jour de la liste des paquets disponibles...", + "upgrade_complete": "Mise à jour terminée", + "upgrading_packages": "Mise à jour des paquets...", + "upnp_dev_not_found": "Aucun périphérique compatible UPnP trouvé", + "upnp_disabled": "UPnP désactivé avec succès", + "upnp_enabled": "UPnP activé avec succès", + "upnp_port_open_failed": "Impossible d'ouvrir les ports avec UPnP", + "user_created": "Utilisateur créé avec succès", + "user_creation_failed": "Impossible de créer l'utilisateur", + "user_deleted": "Utilisateur supprimé avec succès", + "user_deletion_failed": "Impossible de supprimer l'utilisateur", + "user_info_failed": "Impossible de récupérer les informations de l'utilisateur", + "user_unknown": "Utilisateur inconnu", + "user_update_failed": "Impossible de modifier l'utilisateur", + "user_updated": "Utilisateur modifié avec succès", + "yunohost_already_installed": "YunoHost est déjà installé", + "yunohost_ca_creation_failed": "Impossible de créer l'autorité de certification", + "yunohost_configured": "YunoHost configuré avec succès", + "yunohost_installing": "Installation de YunoHost...", + "yunohost_not_installed": "YunoHost n'est pas ou pas correctement installé. Veuillez exécuter 'yunohost tools postinstall'." +} \ No newline at end of file From f47c8f2ae5294ccf1c326939498a362c56373ddd Mon Sep 17 00:00:00 2001 From: aoz roon Date: Thu, 8 Oct 2015 13:35:15 +0200 Subject: [PATCH 020/170] Translated using Weblate (French) Currently translated at 88.5% (178 of 201 strings) --- locales/fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index ca38c94f4..59a174042 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -128,6 +128,7 @@ "restore_complete": "Restauration terminée", "restore_confirm_yunohost_installed": "Voulez-vous vraiment restaurer un système déjà installé ? [{answers:s}]", "restore_failed": "Impossible de restaurer le système", + "restore_running_app_script": "Lancement du script de restauration pour l'application '{app:s}'...", "restore_running_hooks": "Exécution des scripts de restauration...", "service_add_failed": "Impossible d'ajouter le service '{:s}'", "service_added": "Service ajouté avec succès", @@ -154,7 +155,7 @@ "unbackup_app": "L'application '{:s}' ne sera pas sauvegardée", "unexpected_error": "Une erreur inattendue est survenue", "unit_unknown": "Unité '{:s}' inconnue", - "unrestore_app": "L'application '{:s}' ne sera pas restaurée", + "unrestore_app": "L'application '{app:s}' ne sera pas restaurée", "update_cache_failed": "Impossible de mettre à jour le cache de l'APT", "updating_apt_cache": "Mise à jour de la liste des paquets disponibles...", "upgrade_complete": "Mise à jour terminée", From 95108ddb540b6518d8ff83f6be06fc31904c9c9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Thu, 8 Oct 2015 13:35:53 +0200 Subject: [PATCH 021/170] Translated using Weblate (French) Currently translated at 89.0% (179 of 201 strings) --- locales/fr.json | 1 + 1 file changed, 1 insertion(+) diff --git a/locales/fr.json b/locales/fr.json index 59a174042..86ab7fbf9 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -36,6 +36,7 @@ "backup_complete": "Sauvegarde terminée", "backup_creating_archive": "Création de l'archive de sauvegarde...", "backup_extracting_archive": "Extraction de l'archive de sauvegarde...", + "backup_hook_unknown": "Script de sauvegarde '{hook:s}' inconnu", "backup_invalid_archive": "Archive de sauvegarde incorrecte", "backup_output_directory_forbidden": "Dossier de sortie interdit", "backup_output_directory_not_empty": "Le dossier de sortie n'est pas vide", From 908d77f23497a01759abb11fe462292df6b968a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Thu, 8 Oct 2015 15:47:24 +0200 Subject: [PATCH 022/170] [ref] Rename lib folder to src --- debian/install | 2 +- {lib => src}/yunohost/__init__.py | 0 {lib => src}/yunohost/app.py | 0 {lib => src}/yunohost/backup.py | 0 {lib => src}/yunohost/domain.py | 0 {lib => src}/yunohost/dyndns.py | 0 {lib => src}/yunohost/firewall.py | 0 {lib => src}/yunohost/hook.py | 0 {lib => src}/yunohost/monitor.py | 0 {lib => src}/yunohost/service.py | 0 {lib => src}/yunohost/tools.py | 0 {lib => src}/yunohost/user.py | 0 12 files changed, 1 insertion(+), 1 deletion(-) rename {lib => src}/yunohost/__init__.py (100%) rename {lib => src}/yunohost/app.py (100%) rename {lib => src}/yunohost/backup.py (100%) rename {lib => src}/yunohost/domain.py (100%) rename {lib => src}/yunohost/dyndns.py (100%) rename {lib => src}/yunohost/firewall.py (100%) rename {lib => src}/yunohost/hook.py (100%) rename {lib => src}/yunohost/monitor.py (100%) rename {lib => src}/yunohost/service.py (100%) rename {lib => src}/yunohost/tools.py (100%) rename {lib => src}/yunohost/user.py (100%) diff --git a/debian/install b/debian/install index 772027be8..82cd04ad5 100644 --- a/debian/install +++ b/debian/install @@ -4,5 +4,5 @@ data/hooks/* /usr/share/yunohost/hooks/ data/other/* /usr/share/yunohost/yunohost-config/moulinette/ data/templates/* /usr/share/yunohost/templates/ data/apps/* /usr/share/yunohost/apps/ -lib/yunohost/*.py /usr/lib/moulinette/yunohost/ locales/* /usr/lib/moulinette/yunohost/locales/ +src/yunohost/*.py /usr/lib/moulinette/yunohost/ diff --git a/lib/yunohost/__init__.py b/src/yunohost/__init__.py similarity index 100% rename from lib/yunohost/__init__.py rename to src/yunohost/__init__.py diff --git a/lib/yunohost/app.py b/src/yunohost/app.py similarity index 100% rename from lib/yunohost/app.py rename to src/yunohost/app.py diff --git a/lib/yunohost/backup.py b/src/yunohost/backup.py similarity index 100% rename from lib/yunohost/backup.py rename to src/yunohost/backup.py diff --git a/lib/yunohost/domain.py b/src/yunohost/domain.py similarity index 100% rename from lib/yunohost/domain.py rename to src/yunohost/domain.py diff --git a/lib/yunohost/dyndns.py b/src/yunohost/dyndns.py similarity index 100% rename from lib/yunohost/dyndns.py rename to src/yunohost/dyndns.py diff --git a/lib/yunohost/firewall.py b/src/yunohost/firewall.py similarity index 100% rename from lib/yunohost/firewall.py rename to src/yunohost/firewall.py diff --git a/lib/yunohost/hook.py b/src/yunohost/hook.py similarity index 100% rename from lib/yunohost/hook.py rename to src/yunohost/hook.py diff --git a/lib/yunohost/monitor.py b/src/yunohost/monitor.py similarity index 100% rename from lib/yunohost/monitor.py rename to src/yunohost/monitor.py diff --git a/lib/yunohost/service.py b/src/yunohost/service.py similarity index 100% rename from lib/yunohost/service.py rename to src/yunohost/service.py diff --git a/lib/yunohost/tools.py b/src/yunohost/tools.py similarity index 100% rename from lib/yunohost/tools.py rename to src/yunohost/tools.py diff --git a/lib/yunohost/user.py b/src/yunohost/user.py similarity index 100% rename from lib/yunohost/user.py rename to src/yunohost/user.py From f4a9f1b85b676d36f16f8fa538a878984625fe65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Thu, 8 Oct 2015 15:55:17 +0200 Subject: [PATCH 023/170] [fix] Install updated metronome 3.7 modules instead of copying --- data/hooks/conf_regen/12-metronome | 11 - .../metronome/modules/mod_auth_ldap2.lua | 81 ------ .../metronome/modules/mod_storage_ldap.lua | 180 ------------- debian/install | 1 + .../metronome/modules/ldap.lib.lua | 0 lib/metronome/modules/mod_auth_ldap2.lua | 89 +++++++ .../metronome/modules/mod_legacyauth.lua | 0 lib/metronome/modules/mod_storage_ldap.lua | 243 ++++++++++++++++++ .../metronome/modules/vcard.lib.lua | 0 9 files changed, 333 insertions(+), 272 deletions(-) delete mode 100644 data/templates/metronome/modules/mod_auth_ldap2.lua delete mode 100644 data/templates/metronome/modules/mod_storage_ldap.lua rename {data/templates => lib}/metronome/modules/ldap.lib.lua (100%) create mode 100644 lib/metronome/modules/mod_auth_ldap2.lua rename {data/templates => lib}/metronome/modules/mod_legacyauth.lua (100%) create mode 100644 lib/metronome/modules/mod_storage_ldap.lua rename {data/templates => lib}/metronome/modules/vcard.lib.lua (100%) diff --git a/data/hooks/conf_regen/12-metronome b/data/hooks/conf_regen/12-metronome index 70fa64dd3..5b4d45c88 100644 --- a/data/hooks/conf_regen/12-metronome +++ b/data/hooks/conf_regen/12-metronome @@ -18,17 +18,6 @@ function safe_copy () { cd /usr/share/yunohost/templates/metronome -# Copy additional modules -files="ldap.lib.lua -mod_auth_ldap2.lua -mod_legacyauth.lua -mod_storage_ldap.lua -vcard.lib.lua" - -for file in $files; do - safe_copy modules/$file /usr/lib/metronome/modules/$file -done - # Copy configuration files main_domain=$(cat /etc/yunohost/current_host) cat metronome.cfg.lua.sed \ diff --git a/data/templates/metronome/modules/mod_auth_ldap2.lua b/data/templates/metronome/modules/mod_auth_ldap2.lua deleted file mode 100644 index 8c50a99fd..000000000 --- a/data/templates/metronome/modules/mod_auth_ldap2.lua +++ /dev/null @@ -1,81 +0,0 @@ --- vim:sts=4 sw=4 - --- Prosody IM --- Copyright (C) 2008-2010 Matthew Wild --- Copyright (C) 2008-2010 Waqas Hussain --- Copyright (C) 2012 Rob Hoelz --- --- This project is MIT/X11 licensed. Please see the --- COPYING file in the source package for more information. --- --- http://code.google.com/p/prosody-modules/source/browse/mod_auth_ldap/mod_auth_ldap.lua --- adapted to use common LDAP store - -local ldap = module:require 'ldap'; -local new_sasl = require 'util.sasl'.new; -local jsplit = require 'util.jid'.split; - -if not ldap then - return; -end - -local provider = {} - -function provider.test_password(username, password) - return ldap.bind(username, password); -end - -function provider.user_exists(username) - local params = ldap.getparams() - - local filter = ldap.filter.combine_and(params.user.filter, params.user.usernamefield .. '=' .. username); - if params.user.usernamefield == 'mail' then - filter = ldap.filter.combine_and(params.user.filter, 'mail=' .. username .. '@*'); - end - - return ldap.singlematch { - base = params.user.basedn, - filter = filter, - }; -end - -function provider.get_password(username) - return nil, "Passwords unavailable for LDAP."; -end - -function provider.set_password(username, password) - return nil, "Passwords unavailable for LDAP."; -end - -function provider.create_user(username, password) - return nil, "Account creation/modification not available with LDAP."; -end - -function provider.get_sasl_handler() - local testpass_authentication_profile = { - plain_test = function(sasl, username, password, realm) - return provider.test_password(username, password), true; - end, - mechanisms = { PLAIN = true }, - }; - return new_sasl(module.host, testpass_authentication_profile); -end - -function provider.is_admin(jid) - local admin_config = ldap.getparams().admin; - - if not admin_config then - return; - end - - local ld = ldap:getconnection(); - local username = jsplit(jid); - local filter = ldap.filter.combine_and(admin_config.filter, admin_config.namefield .. '=' .. username); - - return ldap.singlematch { - base = admin_config.basedn, - filter = filter, - }; -end - -module:provides("auth", provider); diff --git a/data/templates/metronome/modules/mod_storage_ldap.lua b/data/templates/metronome/modules/mod_storage_ldap.lua deleted file mode 100644 index 17850a217..000000000 --- a/data/templates/metronome/modules/mod_storage_ldap.lua +++ /dev/null @@ -1,180 +0,0 @@ --- vim:sts=4 sw=4 - --- Prosody IM --- Copyright (C) 2008-2010 Matthew Wild --- Copyright (C) 2008-2010 Waqas Hussain --- Copyright (C) 2012 Rob Hoelz --- --- This project is MIT/X11 licensed. Please see the --- COPYING file in the source package for more information. --- - ----------------------------------------- --- Constants and such -- ----------------------------------------- - -local setmetatable = setmetatable; -local ldap = module:require 'ldap'; -local vcardlib = module:require 'vcard'; -local st = require 'util.stanza'; -local gettime = require 'socket'.gettime; - -if not ldap then - return; -end - -local CACHE_EXPIRY = 300; -local params = module:get_option('ldap'); - ----------------------------------------- --- Utility Functions -- ----------------------------------------- - -local function ldap_record_to_vcard(record) - return vcardlib.create { - record = record, - format = params.vcard_format, - } -end - -local get_alias_for_user; - -do - local user_cache; - local last_fetch_time; - - local function populate_user_cache() - local ld = ldap.getconnection(); - - local usernamefield = params.user.usernamefield; - local namefield = params.user.namefield; - - user_cache = {}; - - for _, attrs in ld:search { base = params.user.basedn, scope = 'onelevel', filter = params.user.filter } do - user_cache[attrs[usernamefield]] = attrs[namefield]; - end - last_fetch_time = gettime(); - end - - function get_alias_for_user(user) - if last_fetch_time and last_fetch_time + CACHE_EXPIRY < gettime() then - user_cache = nil; - end - if not user_cache then - populate_user_cache(); - end - return user_cache[user]; - end -end - ----------------------------------------- --- General Setup -- ----------------------------------------- - -local ldap_store = {}; -ldap_store.__index = ldap_store; - -local adapters = { - roster = {}, - vcard = {}, -} - -for k, v in pairs(adapters) do - setmetatable(v, ldap_store); - v.__index = v; - v.name = k; -end - -function ldap_store:get(username) - return nil, "get method unimplemented on store '" .. tostring(self.name) .. "'" -end - -function ldap_store:set(username, data) - return nil, "LDAP storage is currently read-only"; -end - ----------------------------------------- --- Roster Storage Implementation -- ----------------------------------------- - -function adapters.roster:get(username) - local ld = ldap.getconnection(); - local contacts = {}; - - local memberfield = params.groups.memberfield; - local namefield = params.groups.namefield; - local filter = memberfield .. '=' .. tostring(username); - - local groups = {}; - for _, config in ipairs(params.groups) do - groups[ config[namefield] ] = config.name; - end - - -- XXX this kind of relies on the way we do groups at INOC - for _, attrs in ld:search { base = params.groups.basedn, scope = 'onelevel', filter = filter } do - if groups[ attrs[namefield] ] then - local members = attrs[memberfield]; - - for _, user in ipairs(members) do - if user ~= username then - local jid = user .. '@' .. module.host; - local record = contacts[jid]; - - if not record then - record = { - subscription = 'both', - groups = {}, - name = get_alias_for_user(user), - }; - contacts[jid] = record; - end - - record.groups[ groups[ attrs[namefield] ] ] = true; - end - end - end - end - - return contacts; -end - ----------------------------------------- --- vCard Storage Implementation -- ----------------------------------------- - -function adapters.vcard:get(username) - if not params.vcard_format then - return nil, ''; - end - - local ld = ldap.getconnection(); - local filter = params.user.usernamefield .. '=' .. tostring(username); - - local match = ldap.singlematch { - base = params.user.basedn, - filter = filter, - }; - if match then - match.jid = username .. '@' .. module.host - return st.preserialize(ldap_record_to_vcard(match)); - else - return nil, 'not found'; - end -end - ----------------------------------------- --- Driver Definition -- ----------------------------------------- - -local driver = {}; - -function driver:open(store, typ) - local adapter = adapters[store]; - - if adapter and not typ then - return adapter; - end - return nil, "unsupported-store"; -end -module:provides("storage", driver); diff --git a/debian/install b/debian/install index 82cd04ad5..ca458b688 100644 --- a/debian/install +++ b/debian/install @@ -4,5 +4,6 @@ data/hooks/* /usr/share/yunohost/hooks/ data/other/* /usr/share/yunohost/yunohost-config/moulinette/ data/templates/* /usr/share/yunohost/templates/ data/apps/* /usr/share/yunohost/apps/ +lib/metronome/modules/* /usr/lib/metronome/modules/ locales/* /usr/lib/moulinette/yunohost/locales/ src/yunohost/*.py /usr/lib/moulinette/yunohost/ diff --git a/data/templates/metronome/modules/ldap.lib.lua b/lib/metronome/modules/ldap.lib.lua similarity index 100% rename from data/templates/metronome/modules/ldap.lib.lua rename to lib/metronome/modules/ldap.lib.lua diff --git a/lib/metronome/modules/mod_auth_ldap2.lua b/lib/metronome/modules/mod_auth_ldap2.lua new file mode 100644 index 000000000..bb62ca546 --- /dev/null +++ b/lib/metronome/modules/mod_auth_ldap2.lua @@ -0,0 +1,89 @@ +-- vim:sts=4 sw=4 + +-- Metronome IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- Copyright (C) 2012 Rob Hoelz +-- Copyright (C) 2015 YUNOHOST.ORG +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- +-- https://github.com/YunoHost/yunohost-config-metronome/blob/unstable/lib/modules/mod_auth_ldap2.lua +-- adapted to use common LDAP store on Metronome + +local ldap = module:require 'ldap'; +local new_sasl = require 'util.sasl'.new; +local jsplit = require 'util.jid'.split; + +local log = module._log + +if not ldap then + return; +end + +function new_default_provider(host) + local provider = { name = "ldap2" }; + log("debug", "initializing ldap2 authentication provider for host '%s'", host); + + function provider.test_password(username, password) + return ldap.bind(username, password); + end + + function provider.user_exists(username) + local params = ldap.getparams() + + local filter = ldap.filter.combine_and(params.user.filter, params.user.usernamefield .. '=' .. username); + if params.user.usernamefield == 'mail' then + filter = ldap.filter.combine_and(params.user.filter, 'mail=' .. username .. '@*'); + end + + return ldap.singlematch { + base = params.user.basedn, + filter = filter, + }; + end + + function provider.get_password(username) + return nil, "Passwords unavailable for LDAP."; + end + + function provider.set_password(username, password) + return nil, "Passwords unavailable for LDAP."; + end + + function provider.create_user(username, password) + return nil, "Account creation/modification not available with LDAP."; + end + + function provider.get_sasl_handler() + local testpass_authentication_profile = { + plain_test = function(sasl, username, password, realm) + return provider.test_password(username, password), true; + end, + order = { "plain_test" }, + }; + return new_sasl(module.host, testpass_authentication_profile); + end + + function provider.is_admin(jid) + local admin_config = ldap.getparams().admin; + + if not admin_config then + return; + end + + local ld = ldap:getconnection(); + local username = jsplit(jid); + local filter = ldap.filter.combine_and(admin_config.filter, admin_config.namefield .. '=' .. username); + + return ldap.singlematch { + base = admin_config.basedn, + filter = filter, + }; + end + + return provider; +end + +module:add_item("auth-provider", new_default_provider(module.host)); diff --git a/data/templates/metronome/modules/mod_legacyauth.lua b/lib/metronome/modules/mod_legacyauth.lua similarity index 100% rename from data/templates/metronome/modules/mod_legacyauth.lua rename to lib/metronome/modules/mod_legacyauth.lua diff --git a/lib/metronome/modules/mod_storage_ldap.lua b/lib/metronome/modules/mod_storage_ldap.lua new file mode 100644 index 000000000..83fb4d003 --- /dev/null +++ b/lib/metronome/modules/mod_storage_ldap.lua @@ -0,0 +1,243 @@ +-- vim:sts=4 sw=4 + +-- Metronome IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- Copyright (C) 2012 Rob Hoelz +-- Copyright (C) 2015 YUNOHOST.ORG +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. + +---------------------------------------- +-- Constants and such -- +---------------------------------------- + +local setmetatable = setmetatable; + +local get_config = require "core.configmanager".get; +local ldap = module:require 'ldap'; +local vcardlib = module:require 'vcard'; +local st = require 'util.stanza'; +local gettime = require 'socket'.gettime; + +local log = module._log + +if not ldap then + return; +end + +local CACHE_EXPIRY = 300; + +---------------------------------------- +-- Utility Functions -- +---------------------------------------- + +local function ldap_record_to_vcard(record, format) + return vcardlib.create { + record = record, + format = format, + } +end + +local get_alias_for_user; + +do + local user_cache; + local last_fetch_time; + + local function populate_user_cache() + local user_c = get_config(module.host, 'ldap').user; + if not user_c then return; end + + local ld = ldap.getconnection(); + + local usernamefield = user_c.usernamefield; + local namefield = user_c.namefield; + + user_cache = {}; + + for _, attrs in ld:search { base = user_c.basedn, scope = 'onelevel', filter = user_c.filter } do + user_cache[attrs[usernamefield]] = attrs[namefield]; + end + last_fetch_time = gettime(); + end + + function get_alias_for_user(user) + if last_fetch_time and last_fetch_time + CACHE_EXPIRY < gettime() then + user_cache = nil; + end + if not user_cache then + populate_user_cache(); + end + return user_cache[user]; + end +end + +---------------------------------------- +-- Base LDAP store class -- +---------------------------------------- + +local function ldap_store(config) + local self = {}; + local config = config; + + function self:get(username) + return nil, "Data getting is not available for this storage backend"; + end + + function self:set(username, data) + return nil, "Data setting is not available for this storage backend"; + end + + return self; +end + +local adapters = {}; + +---------------------------------------- +-- Roster Storage Implementation -- +---------------------------------------- + +adapters.roster = function (config) + -- Validate configuration requirements + if not config.groups then return nil; end + + local self = ldap_store(config) + + function self:get(username) + local ld = ldap.getconnection(); + local contacts = {}; + + local memberfield = config.groups.memberfield; + local namefield = config.groups.namefield; + local filter = memberfield .. '=' .. tostring(username); + + local groups = {}; + for _, config in ipairs(config.groups) do + groups[ config[namefield] ] = config.name; + end + + log("debug", "Found %d group(s) for user %s", select('#', groups), username) + + -- XXX this kind of relies on the way we do groups at INOC + for _, attrs in ld:search { base = config.groups.basedn, scope = 'onelevel', filter = filter } do + if groups[ attrs[namefield] ] then + local members = attrs[memberfield]; + + for _, user in ipairs(members) do + if user ~= username then + local jid = user .. '@' .. module.host; + local record = contacts[jid]; + + if not record then + record = { + subscription = 'both', + groups = {}, + name = get_alias_for_user(user), + }; + contacts[jid] = record; + end + + record.groups[ groups[ attrs[namefield] ] ] = true; + end + end + end + end + + return contacts; + end + + function self:set(username, data) + log("warn", "Setting data in Roster LDAP storage is not supported yet") + return nil, "not supported"; + end + + return self; +end + +---------------------------------------- +-- vCard Storage Implementation -- +---------------------------------------- + +adapters.vcard = function (config) + -- Validate configuration requirements + if not config.vcard_format or not config.user then return nil; end + + local self = ldap_store(config) + + function self:get(username) + local ld = ldap.getconnection(); + local filter = config.user.usernamefield .. '=' .. tostring(username); + + log("debug", "Retrieving vCard for user '%s'", username); + + local match = ldap.singlematch { + base = config.user.basedn, + filter = filter, + }; + if match then + match.jid = username .. '@' .. module.host + return st.preserialize(ldap_record_to_vcard(match, config.vcard_format)); + else + return nil, "username not found"; + end + end + + function self:set(username, data) + log("warn", "Setting data in vCard LDAP storage is not supported yet") + return nil, "not supported"; + end + + return self; +end + +---------------------------------------- +-- Driver Definition -- +---------------------------------------- + +cache = {}; + +local driver = { name = "ldap" }; + +function driver:open(store) + log("debug", "Opening ldap storage backend for host '%s' and store '%s'", module.host, store); + + if not cache[module.host] then + log("debug", "Caching adapters for the host '%s'", module.host); + + local ad_config = get_config(module.host, "ldap"); + local ad_cache = {}; + for k, v in pairs(adapters) do + ad_cache[k] = v(ad_config); + end + + cache[module.host] = ad_cache; + end + + local adapter = cache[module.host][store]; + + if not adapter then + log("info", "Unavailable adapter for store '%s'", store); + return nil, "unsupported-store"; + end + return adapter; +end + +function driver:stores(username, type, pattern) + return nil, "not implemented"; +end + +function driver:store_exists(username, datastore, type) + return nil, "not implemented"; +end + +function driver:purge(username) + return nil, "not implemented"; +end + +function driver:users() + return nil, "not implemented"; +end + +module:add_item("data-driver", driver); diff --git a/data/templates/metronome/modules/vcard.lib.lua b/lib/metronome/modules/vcard.lib.lua similarity index 100% rename from data/templates/metronome/modules/vcard.lib.lua rename to lib/metronome/modules/vcard.lib.lua From 4e790e2626e0501092644514531b5b9c7ebda924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Fri, 9 Oct 2015 12:48:45 +0200 Subject: [PATCH 024/170] [fix] Do not enable yunohost-firewall service at install --- debian/rules | 5 ++++- src/yunohost/tools.py | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/debian/rules b/debian/rules index c61bb8858..18991261a 100755 --- a/debian/rules +++ b/debian/rules @@ -5,8 +5,11 @@ #export DH_VERBOSE=1 %: - dh ${@} --with=systemd,python2 + dh ${@} --with=python2 override_dh_installinit: + dh_systemd_enable --name yunohost-api + dh_systemd_enable --name yunohost-firewall --no-enable dh_installinit --name=yunohost-api dh_installinit --name=yunohost-firewall + dh_systemd_start diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 867fc7759..50102765e 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -269,10 +269,10 @@ def tools_postinstall(domain, password, ignore_dyndns=False): # Enable UPnP silently and reload firewall firewall_upnp('enable', no_refresh=True) - firewall_reload() - # Enable iptables at boot time - os.system('update-rc.d yunohost-firewall defaults') + # Enable and start YunoHost firewall at boot time + os.system('update-rc.d yunohost-firewall enable') + os.system('service yunohost-firewall start') os.system('touch /etc/yunohost/installed') From 1beadf3051ae190fd859ad234000dc24c97b565b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Fri, 9 Oct 2015 14:26:04 +0200 Subject: [PATCH 025/170] [fix] Clean systemd services install and really fix firewall --- debian/postinst | 1 - debian/rules | 5 +---- debian/yunohost-firewall.service | 3 --- src/yunohost/tools.py | 4 ++-- 4 files changed, 3 insertions(+), 10 deletions(-) diff --git a/debian/postinst b/debian/postinst index ceaca3e4b..5a2c03ec1 100644 --- a/debian/postinst +++ b/debian/postinst @@ -4,7 +4,6 @@ set -e do_configure() { rm -rf /var/cache/moulinette/* - service yunohost-api restart if [ ! -f /etc/yunohost/installed ]; then bash /usr/share/yunohost/hooks/conf_regen/01-yunohost diff --git a/debian/rules b/debian/rules index 18991261a..601c47ea3 100755 --- a/debian/rules +++ b/debian/rules @@ -5,11 +5,8 @@ #export DH_VERBOSE=1 %: - dh ${@} --with=python2 + dh ${@} --with=python2,systemd override_dh_installinit: - dh_systemd_enable --name yunohost-api - dh_systemd_enable --name yunohost-firewall --no-enable dh_installinit --name=yunohost-api dh_installinit --name=yunohost-firewall - dh_systemd_start diff --git a/debian/yunohost-firewall.service b/debian/yunohost-firewall.service index 1dd46f477..f61ca1f64 100644 --- a/debian/yunohost-firewall.service +++ b/debian/yunohost-firewall.service @@ -9,6 +9,3 @@ ExecStart=/usr/bin/yunohost firewall reload ExecReload=/usr/bin/yunohost firewall reload ExecStop=/usr/bin/yunohost firewall stop RemainAfterExit=yes - -[Install] -WantedBy=multi-user.target diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 50102765e..833ef71aa 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -270,12 +270,12 @@ def tools_postinstall(domain, password, ignore_dyndns=False): # Enable UPnP silently and reload firewall firewall_upnp('enable', no_refresh=True) + os.system('touch /etc/yunohost/installed') + # Enable and start YunoHost firewall at boot time os.system('update-rc.d yunohost-firewall enable') os.system('service yunohost-firewall start') - os.system('touch /etc/yunohost/installed') - service_regenconf(force=True) msignals.display(m18n.n('yunohost_configured'), 'success') From 3e1d9fc33bceb1710006af71d272792826247954 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Mon, 12 Oct 2015 21:43:31 +0200 Subject: [PATCH 026/170] [enh] Add hooks for post domain add/remove (fix #108) --- src/yunohost/domain.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index ee53bb39e..53b938356 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -80,6 +80,7 @@ def domain_add(auth, domain, dyndns=False): """ from yunohost.service import service_regenconf + from yunohost.hook import hook_callback attr_dict = { 'objectClass' : ['mailDomain', 'top'] } try: @@ -172,6 +173,8 @@ def domain_add(auth, domain, dyndns=False): except: pass raise + hook_callback('post_domain_add', args=[domain]) + msignals.display(m18n.n('domain_created'), 'success') @@ -185,6 +188,7 @@ def domain_remove(auth, domain, force=False): """ from yunohost.service import service_regenconf + from yunohost.hook import hook_callback if not force and domain not in domain_list(auth)['domains']: raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown')) @@ -211,4 +215,6 @@ def domain_remove(auth, domain, force=False): service_regenconf(service='dnsmasq') os.system('yunohost app ssowatconf > /dev/null 2>&1') + hook_callback('post_domain_remove', args=[domain]) + msignals.display(m18n.n('domain_deleted'), 'success') From adcebb39f90977ae000df8e50c347ee74d2eaeb0 Mon Sep 17 00:00:00 2001 From: opi Date: Mon, 12 Oct 2015 22:24:21 +0200 Subject: [PATCH 027/170] [fix] Use official.json app list. --- data/actionsmap/yunohost.yml | 4 ++-- src/yunohost/app.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 958bc8c1f..867404fd0 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -318,10 +318,10 @@ app: arguments: -u: full: --url - help: URL of remote JSON list (default http://fapp.yunohost.org/app/list/raw) + help: URL of remote JSON list (default https://yunohost.org/official.json) -n: full: --name - help: Name of the list (default fapp) + help: Name of the list (default yunohost) extra: pattern: &pattern_listname - !!str ^[a-z0-9_]+$ diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 57f7676fc..de6c6bb55 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -72,7 +72,7 @@ def app_fetchlist(url=None, name=None): Keyword argument: name -- Name of the list (default yunohost) - url -- URL of remote JSON list (default https://yunohost.org/list.json) + url -- URL of remote JSON list (default https://yunohost.org/official.json) """ # Create app path if not exists @@ -80,7 +80,7 @@ def app_fetchlist(url=None, name=None): except OSError: os.makedirs(repo_path) if url is None: - url = 'https://yunohost.org/list.json' + url = 'https://yunohost.org/official.json' name = 'yunohost' else: if name is None: From 2f0c95399cc03a31d9bb1984e895d6bcccc89d5d Mon Sep 17 00:00:00 2001 From: Le Kload Date: Sun, 25 Oct 2015 14:13:46 +0100 Subject: [PATCH 028/170] Create README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 000000000..6e928e9ce --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +Please report issues here (no registration needed): +https://dev.yunohost.org/projects/yunohost/issues From ee1e3cdd3d01f824f8ea8f8d80c41a6ac4a2bf5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Tue, 27 Oct 2015 09:49:19 +0100 Subject: [PATCH 029/170] [fix] Remove already installed by metronome package files --- data/hooks/conf_regen/12-metronome | 2 - data/templates/metronome/metronome.init | 119 ------------------- data/templates/metronome/metronome.logrotate | 11 -- 3 files changed, 132 deletions(-) delete mode 100644 data/templates/metronome/metronome.init delete mode 100644 data/templates/metronome/metronome.logrotate diff --git a/data/hooks/conf_regen/12-metronome b/data/hooks/conf_regen/12-metronome index 5b4d45c88..b52c18d88 100644 --- a/data/hooks/conf_regen/12-metronome +++ b/data/hooks/conf_regen/12-metronome @@ -24,8 +24,6 @@ cat metronome.cfg.lua.sed \ | sed "s/{{ main_domain }}/$main_domain/g" \ | sudo tee metronome.cfg.lua safe_copy metronome.cfg.lua /etc/metronome/metronome.cfg.lua -safe_copy metronome.init /etc/init.d/metronome -safe_copy metronome.logrotate /etc/logrotate.d/metronome need_restart=False sudo mkdir -p /etc/metronome/conf.d diff --git a/data/templates/metronome/metronome.init b/data/templates/metronome/metronome.init deleted file mode 100644 index 5f6f2ed46..000000000 --- a/data/templates/metronome/metronome.init +++ /dev/null @@ -1,119 +0,0 @@ -#! /bin/sh - -### BEGIN INIT INFO -# Provides: metronome -# Required-Start: $network $local_fs $remote_fs $syslog -# Required-Stop: $remote_fs $syslog -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -# Short-Description: Metronome XMPP Server -### END INIT INFO - -set -e - -# /etc/init.d/metronome: start and stop Metronome XMPP server - -NAME=metronome -USER=metronome -DAEMON=/usr/bin/metronome -PIDPATH=/var/run/metronome -PIDFILE="$PIDPATH"/metronome.pid - -NICE= -MAXFDS= -CPUSCHED= -IOSCHED= - -test -x "$DAEMON" || exit 0 - -. /lib/lsb/init-functions - -if [ -f /etc/default/metronome ] ; then - . /etc/default/metronome -fi - -start_opts() { - test -z "$NICE" || echo -n " --nicelevel $NICE" - test -z "$CPUSCHED" || echo -n " --procsched $CPUSCHED" - test -z "$IOSCHED" || echo -n " --iosched $IOSCHED" -} - -start_metronome () { - mkdir -p `dirname $PIDFILE` - chown metronome:adm `dirname $PIDFILE` - if start-stop-daemon --start --quiet --pidfile "$PIDFILE" \ - --chuid "$USER" --oknodo --user "$USER" --name lua5.1 \ - $(start_opts) --startas "$DAEMON"; - then - return 0 - else - return 1 - fi -} - -stop_metronome () { - if start-stop-daemon --stop --quiet --retry 30 \ - --oknodo --pidfile "$PIDFILE" --user "$USER" --name lua5.1; - then - return 0 - else - return 1 - fi -} - -signal_metronome () { - if start-stop-daemon --stop --quiet --pidfile "$PIDFILE" \ - --user "$USER" --name lua5.1 --oknodo --signal $1; - then - return 0 - else - return 1 - fi -} - -case "$1" in - start) - log_daemon_msg "Starting Metronome XMPP Server" "metronome" - if start_metronome; then - log_end_msg 0; - else - log_end_msg 1; - fi - ;; - stop) - log_daemon_msg "Stopping Metronome XMPP Server" "metronome" - if stop_metronome; then - log_end_msg 0; - else - log_end_msg 1; - fi - ;; - force-reload|restart) - log_daemon_msg "Restarting Metronome XMPP Server" "metronome" - - stop_metronome - - if start_metronome; then - log_end_msg 0; - else - log_end_msg 1; - fi - ;; - reload) - log_daemon_msg "Reloading Metronome XMPP Server" "metronome" - - if signal_metronome 1; then - log_end_msg 0; - else - log_end_msg 1; - fi - ;; - status) - status_of_proc -p $PIDFILE $DAEMON $NAME - ;; - *) - log_action_msg "Usage: /etc/init.d/metronome {start|stop|restart|reload|status}" - exit 1 -esac - -exit 0 diff --git a/data/templates/metronome/metronome.logrotate b/data/templates/metronome/metronome.logrotate deleted file mode 100644 index ccdc2febe..000000000 --- a/data/templates/metronome/metronome.logrotate +++ /dev/null @@ -1,11 +0,0 @@ -/var/log/metronome/metronome.log /var/log/metronome/metronome.err { - daily - rotate 14 - compress - create 640 metronome adm - postrotate - /etc/init.d/metronome reload > /dev/null - endscript - sharedscripts - missingok -} From 813ab01e9d3f456a321c3ba184f26ce749ceebae Mon Sep 17 00:00:00 2001 From: kload Date: Sat, 31 Oct 2015 12:33:06 +0100 Subject: [PATCH 030/170] [enh] Replace the email stack by Rspamd/Rmilter --- data/hooks/conf_regen/22-email-legacy | 26 + data/hooks/conf_regen/22-postgrey | 23 - data/hooks/conf_regen/25-dovecot | 2 - data/hooks/conf_regen/28-rmilter | 37 + data/hooks/conf_regen/28-spamassassin | 21 - data/hooks/conf_regen/31-amavis | 37 - data/hooks/conf_regen/31-rspamd | 27 + data/templates/amavis/05-domain_id | 19 - data/templates/amavis/05-node_id | 13 - data/templates/amavis/15-content_filter_mode | 23 - data/templates/amavis/20-debian_defaults | 216 --- data/templates/amavis/50-user.sed | 30 - data/templates/dovecot/dovecot.conf.sed | 10 +- data/templates/dovecot/sa-learn-pipe.sh | 9 - data/templates/postfix/main.cf.sed | 13 +- data/templates/postfix/master.cf | 29 - data/templates/postgrey/postgrey.default | 12 - data/templates/rmilter/rmilter.conf | 18 + data/templates/rspamd/metrics.conf | 1163 +++++++++++++++++ data/templates/rspamd/rspamd.sieve | 4 + data/templates/spamassassin/local.cf | 94 -- .../spamassassin/spamassassin.default | 31 - debian/control | 7 +- 23 files changed, 1292 insertions(+), 572 deletions(-) create mode 100644 data/hooks/conf_regen/22-email-legacy delete mode 100644 data/hooks/conf_regen/22-postgrey create mode 100644 data/hooks/conf_regen/28-rmilter delete mode 100644 data/hooks/conf_regen/28-spamassassin delete mode 100644 data/hooks/conf_regen/31-amavis create mode 100644 data/hooks/conf_regen/31-rspamd delete mode 100644 data/templates/amavis/05-domain_id delete mode 100644 data/templates/amavis/05-node_id delete mode 100644 data/templates/amavis/15-content_filter_mode delete mode 100644 data/templates/amavis/20-debian_defaults delete mode 100644 data/templates/amavis/50-user.sed delete mode 100644 data/templates/dovecot/sa-learn-pipe.sh delete mode 100644 data/templates/postgrey/postgrey.default create mode 100644 data/templates/rmilter/rmilter.conf create mode 100644 data/templates/rspamd/metrics.conf create mode 100644 data/templates/rspamd/rspamd.sieve delete mode 100644 data/templates/spamassassin/local.cf delete mode 100644 data/templates/spamassassin/spamassassin.default diff --git a/data/hooks/conf_regen/22-email-legacy b/data/hooks/conf_regen/22-email-legacy new file mode 100644 index 000000000..e66a20045 --- /dev/null +++ b/data/hooks/conf_regen/22-email-legacy @@ -0,0 +1,26 @@ +#!/bin/bash +set -e + +# Add new email services +sudo yunohost service add rspamd -l /var/log/mail.log \ + || echo "Rspamd is already listed in services" + +sudo yunohost service add rmilter -l /var/log/mail.log \ + || echo "Rspamd is already listed in services" + +# Remove previous email services +sudo yunohost service disable spamassassin \ + || echo "Spamassassin is already removed" \ + && systemctl disable spamassassin || true + +sudo yunohost service disable amavis \ + || echo "Amavis is already removed" \ + && systemctl disable spamassassin || true + +sudo yunohost service disable postgrey \ + || echo "Postgrey is already removed" \ + && systemctl disable postgrey || true + +sudo yunohost service disable postgrey \ + && sudo yunohost service remove amavis \ + || echo "Amavis is already removed" diff --git a/data/hooks/conf_regen/22-postgrey b/data/hooks/conf_regen/22-postgrey deleted file mode 100644 index b1f924a0e..000000000 --- a/data/hooks/conf_regen/22-postgrey +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -set -e - -force=$1 - -function safe_copy () { - if [[ "$force" == "True" ]]; then - sudo yunohost service safecopy \ - -s postgrey \ - $1 $2 \ - --force - else - sudo yunohost service safecopy \ - -s postgrey \ - $1 $2 - fi -} - -cd /usr/share/yunohost/templates/postgrey - -if [[ "$(safe_copy postgrey.default /etc/default/postgrey)" == "True" ]]; then - sudo service nslcd restart -fi diff --git a/data/hooks/conf_regen/25-dovecot b/data/hooks/conf_regen/25-dovecot index 642fa9570..252828c7f 100644 --- a/data/hooks/conf_regen/25-dovecot +++ b/data/hooks/conf_regen/25-dovecot @@ -41,8 +41,6 @@ safe_copy dovecot-ldap.conf /etc/dovecot/dovecot-ldap.conf # Setup Sieve sudo rm -rf /etc/dovecot/global_script sudo mkdir -p -m 0770 /etc/dovecot/global_script -safe_copy sa-learn-pipe.sh /usr/bin/sa-learn-pipe.sh -sudo chmod 755 /usr/bin/sa-learn-pipe.sh safe_copy dovecot.sieve /etc/dovecot/global_script/dovecot.sieve sudo chmod 660 /etc/dovecot/global_script/dovecot.sieve > /dev/null 2>&1 \ diff --git a/data/hooks/conf_regen/28-rmilter b/data/hooks/conf_regen/28-rmilter new file mode 100644 index 000000000..ef31b6806 --- /dev/null +++ b/data/hooks/conf_regen/28-rmilter @@ -0,0 +1,37 @@ +#!/bin/bash +set -e + +force=$1 + +function safe_copy () { + if [[ "$force" == "True" ]]; then + sudo yunohost service safecopy \ + -s rmilter $1 $2 --force + else + sudo yunohost service safecopy \ + -s rmilter $1 $2 + fi +} + +cd /usr/share/yunohost/templates/rmilter + +# Copy Rmilter configuration +safe_copy rmilter.conf /etc/rmilter/rmilter.conf + +# Create the PID directory +sudo mkdir -p /var/run/rmilter +sudo chown _rmilter: /var/run/rmilter + +# Create DKIM key for each YunoHost domain +sudo mkdir -p /etc/dkim +domain_list=$(sudo yunohost domain list --plain) + +for domain in $domain_list; do + [ -f /etc/dkim/$domain.mail.key ] \ + || sudo opendkim-genkey --domain=$domain --selector=mail + + sudo chown _rmilter /etc/dkim/$domain.mail.key + sudo chmod 400 /etc/dkim/$domain.mail.key +done + +sudo service rmilter restart diff --git a/data/hooks/conf_regen/28-spamassassin b/data/hooks/conf_regen/28-spamassassin deleted file mode 100644 index e55f10dcd..000000000 --- a/data/hooks/conf_regen/28-spamassassin +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -set -e - -force=$1 - -function safe_copy () { - if [[ "$force" == "True" ]]; then - sudo yunohost service safecopy \ - -s spamassassin $1 $2 --force - else - sudo yunohost service safecopy \ - -s spamassassin $1 $2 - fi -} - -cd /usr/share/yunohost/templates/spamassassin - -safe_copy spamassassin.default /etc/default/spamassassin -safe_copy local.cf /etc/spamassassin/local.cf - -sudo service spamassassin restart diff --git a/data/hooks/conf_regen/31-amavis b/data/hooks/conf_regen/31-amavis deleted file mode 100644 index f25c70fe4..000000000 --- a/data/hooks/conf_regen/31-amavis +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash -set -e - -force=$1 - -function safe_copy () { - if [[ "$force" == "True" ]]; then - sudo yunohost service safecopy \ - -s amavis $1 $2 --force - else - sudo yunohost service safecopy \ - -s amavis $1 $2 - fi -} - -cd /usr/share/yunohost/templates/amavis - -sudo mkdir -p /etc/amavis/conf.d/ - -# Copy plain single configuration files -files="05-domain_id -05-node_id -15-content_filter_mode -20-debian_defaults" - -for file in $files; do - safe_copy $file /etc/amavis/conf.d/$file -done - -main_domain=$(cat /etc/yunohost/current_host) -cat 50-user.sed \ - | sed "s/{{ main_domain }}/$main_domain/g" \ - | sudo tee 50-user -safe_copy 50-user /etc/amavis/conf.d/50-user - - -sudo service amavis restart diff --git a/data/hooks/conf_regen/31-rspamd b/data/hooks/conf_regen/31-rspamd new file mode 100644 index 000000000..0f6ef7c16 --- /dev/null +++ b/data/hooks/conf_regen/31-rspamd @@ -0,0 +1,27 @@ +#!/bin/bash +set -e + +force=$1 + +function safe_copy () { + if [[ "$force" == "True" ]]; then + sudo yunohost service safecopy \ + -s rspamd $1 $2 --force + else + sudo yunohost service safecopy \ + -s rspamd $1 $2 + fi +} + +cd /usr/share/yunohost/templates/rspamd + +# Copy Rspamd configuration +safe_copy metrics.conf /etc/rspamd/metrics.conf + +# Install Rspamd sieve script +safe_copy rspamd.sieve /etc/dovecot/global_script/rspamd.sieve +sudo sievec /etc/dovecot/global_script/rspamd.sieve +sudo chmod 660 /etc/dovecot/global_script/rspamd.svbin +sudo chown -R vmail:mail /etc/dovecot/global_script + +sudo service rspamd restart diff --git a/data/templates/amavis/05-domain_id b/data/templates/amavis/05-domain_id deleted file mode 100644 index 01a71e4b3..000000000 --- a/data/templates/amavis/05-domain_id +++ /dev/null @@ -1,19 +0,0 @@ -use strict; - -# $mydomain is used just for convenience in the config files and it is not -# used internally by amavisd-new except in the default X_HEADER_LINE (which -# Debian overrides by default anyway). - -#chomp($mydomain = `head -n 1 /etc/mailname`); - -# amavisd-new needs to know which email domains are to be considered local -# to the administrative domain. Only emails to "local" domains are subject -# to certain functionality, such as the addition of spam tags. -# -# Default local domains to $mydomain and all subdomains. Remember to -# override or redefine this if $mydomain is changed later in the config -# sequence. - -@local_domains_acl = ( ".$mydomain" ); - -1; # ensure a defined return diff --git a/data/templates/amavis/05-node_id b/data/templates/amavis/05-node_id deleted file mode 100644 index ee6665436..000000000 --- a/data/templates/amavis/05-node_id +++ /dev/null @@ -1,13 +0,0 @@ -use strict; - -# $myhostname is used by amavisd-new for node identification, and it is -# important to get it right (e.g. for ESMTP EHLO, loop detection, and so on). - -#chomp($myhostname = `hostname --fqdn`); - -# To manually set $myhostname, edit the following line with the correct Fully -# Qualified Domain Name (FQDN) and remove the # at the beginning of the line. -# -#$myhostname = "mail.example.com"; - -1; # ensure a defined return diff --git a/data/templates/amavis/15-content_filter_mode b/data/templates/amavis/15-content_filter_mode deleted file mode 100644 index 825e9e03c..000000000 --- a/data/templates/amavis/15-content_filter_mode +++ /dev/null @@ -1,23 +0,0 @@ -use strict; - -# You can modify this file to re-enable SPAM checking through spamassassin -# and to re-enable antivirus checking. - -# -# Default antivirus checking mode -# Uncomment the two lines below to enable it back -# - -#@bypass_virus_checks_maps = ( -# \%bypass_virus_checks, \@bypass_virus_checks_acl, \$bypass_virus_checks_re); - - -# -# Default SPAM checking mode -# Uncomment the two lines below to enable it back -# - -@bypass_spam_checks_maps = ( - \%bypass_spam_checks, \@bypass_spam_checks_acl, \$bypass_spam_checks_re); - -1; # ensure a defined return diff --git a/data/templates/amavis/20-debian_defaults b/data/templates/amavis/20-debian_defaults deleted file mode 100644 index 83e553d28..000000000 --- a/data/templates/amavis/20-debian_defaults +++ /dev/null @@ -1,216 +0,0 @@ -use strict; - -# ADMINISTRATORS: -# Debian suggests that any changes you need to do that should never -# be "updated" by the Debian package should be made in another file, -# overriding the settings in this file. -# -# The package will *not* overwrite your settings, but by keeping -# them separate, you will make the task of merging changes on these -# configuration files much simpler... - -# see /usr/share/doc/amavisd-new/examples/amavisd.conf-default for -# a list of all variables with their defaults; -# see /usr/share/doc/amavisd-new/examples/amavisd.conf-sample for -# a traditional-style commented file -# [note: the above files were not converted to Debian settings!] -# -# for more details see documentation in /usr/share/doc/amavisd-new -# and at http://www.ijs.si/software/amavisd/amavisd-new-docs.html - -$QUARANTINEDIR = "$MYHOME/virusmails"; -$quarantine_subdir_levels = 1; # enable quarantine dir hashing - -$log_recip_templ = undef; # disable by-recipient level-0 log entries -$DO_SYSLOG = 1; # log via syslogd (preferred) -$syslog_ident = 'amavis'; # syslog ident tag, prepended to all messages -$syslog_facility = 'mail'; -$syslog_priority = 'debug'; # switch to info to drop debug output, etc - -$enable_db = 1; # enable use of BerkeleyDB/libdb (SNMP and nanny) -$enable_global_cache = 1; # enable use of libdb-based cache if $enable_db=1 - -$inet_socket_port = 10024; # default listening socket - -$sa_spam_subject_tag = '***SPAM*** '; -$sa_tag_level_deflt = undef; # add spam info headers if at, or above that level -$sa_tag2_level_deflt = 4.00; # add 'spam detected' headers at that level -$sa_kill_level_deflt = 20.00; # triggers spam evasive actions -$sa_dsn_cutoff_level = 10; # spam level beyond which a DSN is not sent - -$sa_mail_body_size_limit = 200*1024; # don't waste time on SA if mail is larger -$sa_local_tests_only = 0; # only tests which do not require internet access? - -$recipient_delimiter = '+'; -@addr_extension_spam_maps = ('Junk'); - -# Quota limits to avoid bombs (like 42.zip) - -$MAXLEVELS = 14; -$MAXFILES = 1500; -$MIN_EXPANSION_QUOTA = 100*1024; # bytes -$MAX_EXPANSION_QUOTA = 300*1024*1024; # bytes - -# You should: -# Use D_DISCARD to discard data (viruses) -# Use D_BOUNCE to generate local bounces by amavisd-new -# Use D_REJECT to generate local or remote bounces by the calling MTA -# Use D_PASS to deliver the message -# -# Whatever you do, *NEVER* use D_REJECT if you have other MTAs *forwarding* -# mail to your account. Use D_BOUNCE instead, otherwise you are delegating -# the bounce work to your friendly forwarders, which might not like it at all. -# -# On dual-MTA setups, one can often D_REJECT, as this just makes your own -# MTA generate the bounce message. Test it first. -# -# Bouncing viruses is stupid, always discard them after you are sure the AV -# is working correctly. Bouncing real SPAM is also useless, if you cannot -# D_REJECT it (and don't D_REJECT mail coming from your forwarders!). - -$final_virus_destiny = D_DISCARD; # (data not lost, see virus quarantine) -$final_banned_destiny = D_BOUNCE; # D_REJECT when front-end MTA -$final_spam_destiny = D_DISCARD; -$final_bad_header_destiny = D_PASS; # False-positive prone (for spam) - -$enable_dkim_verification = 1; #disabled to prevent warning -$enable_dkim_signing =1; - -$virus_admin = "postmaster\@$mydomain"; # due to D_DISCARD default - -# Set to empty ("") to add no header -$X_HEADER_LINE = "Debian $myproduct_name at $mydomain"; - -# REMAINING IMPORTANT VARIABLES ARE LISTED HERE BECAUSE OF LONGER ASSIGNMENTS - -# -# DO NOT SEND VIRUS NOTIFICATIONS TO OUTSIDE OF YOUR DOMAIN. EVER. -# -# These days, almost all viruses fake the envelope sender and mail headers. -# Therefore, "virus notifications" became nothing but undesired, aggravating -# SPAM. This holds true even inside one's domain. We disable them all by -# default, except for the EICAR test pattern. -# - -@viruses_that_fake_sender_maps = (new_RE( - [qr'\bEICAR\b'i => 0], # av test pattern name - [qr/.*/ => 1], # true for everything else -)); - -@keep_decoded_original_maps = (new_RE( -# qr'^MAIL$', # retain full original message for virus checking (can be slow) - qr'^MAIL-UNDECIPHERABLE$', # recheck full mail if it contains undecipherables - qr'^(ASCII(?! cpio)|text|uuencoded|xxencoded|binhex)'i, -# qr'^Zip archive data', # don't trust Archive::Zip -)); - - -# for $banned_namepath_re, a new-style of banned table, see amavisd.conf-sample - -$banned_filename_re = new_RE( -# qr'^UNDECIPHERABLE$', # is or contains any undecipherable components - - # block certain double extensions anywhere in the base name - qr'\.[^./]*\.(exe|vbs|pif|scr|bat|cmd|com|cpl|dll)\.?$'i, - - qr'\{[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}\}?$'i, # Windows Class ID CLSID, strict - - qr'^application/x-msdownload$'i, # block these MIME types - qr'^application/x-msdos-program$'i, - qr'^application/hta$'i, - -# qr'^application/x-msmetafile$'i, # Windows Metafile MIME type -# qr'^\.wmf$', # Windows Metafile file(1) type - -# qr'^message/partial$'i, qr'^message/external-body$'i, # rfc2046 MIME types - -# [ qr'^\.(Z|gz|bz2)$' => 0 ], # allow any in Unix-compressed -# [ qr'^\.(rpm|cpio|tar)$' => 0 ], # allow any in Unix-type archives -# [ qr'^\.(zip|rar|arc|arj|zoo)$'=> 0 ], # allow any within such archives -# [ qr'^application/x-zip-compressed$'i => 0], # allow any within such archives - - qr'.\.(exe|vbs|pif|scr|bat|cmd|com|cpl)$'i, # banned extension - basic -# qr'.\.(ade|adp|app|bas|bat|chm|cmd|com|cpl|crt|emf|exe|fxp|grp|hlp|hta| -# inf|ins|isp|js|jse|lnk|mda|mdb|mde|mdw|mdt|mdz|msc|msi|msp|mst| -# ops|pcd|pif|prg|reg|scr|sct|shb|shs|vb|vbe|vbs| -# wmf|wsc|wsf|wsh)$'ix, # banned ext - long - -# qr'.\.(mim|b64|bhx|hqx|xxe|uu|uue)$'i, # banned extension - WinZip vulnerab. - - qr'^\.(exe-ms)$', # banned file(1) types -# qr'^\.(exe|lha|tnef|cab|dll)$', # banned file(1) types -); -# See http://support.microsoft.com/default.aspx?scid=kb;EN-US;q262631 -# and http://www.cknow.com/vtutor/vtextensions.htm - - -# ENVELOPE SENDER SOFT-WHITELISTING / SOFT-BLACKLISTING - -@score_sender_maps = ({ # a by-recipient hash lookup table, - # results from all matching recipient tables are summed - -# ## per-recipient personal tables (NOTE: positive: black, negative: white) -# 'user1@example.com' => [{'bla-mobile.press@example.com' => 10.0}], -# 'user3@example.com' => [{'.ebay.com' => -3.0}], -# 'user4@example.com' => [{'cleargreen@cleargreen.com' => -7.0, -# '.cleargreen.com' => -5.0}], - - ## site-wide opinions about senders (the '.' matches any recipient) - '.' => [ # the _first_ matching sender determines the score boost - - new_RE( # regexp-type lookup table, just happens to be all soft-blacklist - [qr'^(bulkmail|offers|cheapbenefits|earnmoney|foryou)@'i => 5.0], - [qr'^(greatcasino|investments|lose_weight_today|market\.alert)@'i=> 5.0], - [qr'^(money2you|MyGreenCard|new\.tld\.registry|opt-out|opt-in)@'i=> 5.0], - [qr'^(optin|saveonlsmoking2002k|specialoffer|specialoffers)@'i => 5.0], - [qr'^(stockalert|stopsnoring|wantsome|workathome|yesitsfree)@'i => 5.0], - [qr'^(your_friend|greatoffers)@'i => 5.0], - [qr'^(inkjetplanet|marketopt|MakeMoney)\d*@'i => 5.0], - ), - -# read_hash("/var/amavis/sender_scores_sitewide"), - -# This are some examples for whitelists, since envelope senders can be forged -# they are not enabled by default. - { # a hash-type lookup table (associative array) - #'nobody@cert.org' => -3.0, - #'cert-advisory@us-cert.gov' => -3.0, - #'owner-alert@iss.net' => -3.0, - #'slashdot@slashdot.org' => -3.0, - #'securityfocus.com' => -3.0, - #'ntbugtraq@listserv.ntbugtraq.com' => -3.0, - #'security-alerts@linuxsecurity.com' => -3.0, - #'mailman-announce-admin@python.org' => -3.0, - #'amavis-user-admin@lists.sourceforge.net'=> -3.0, - #'amavis-user-bounces@lists.sourceforge.net' => -3.0, - #'spamassassin.apache.org' => -3.0, - #'notification-return@lists.sophos.com' => -3.0, - #'owner-postfix-users@postfix.org' => -3.0, - #'owner-postfix-announce@postfix.org' => -3.0, - #'owner-sendmail-announce@lists.sendmail.org' => -3.0, - #'sendmail-announce-request@lists.sendmail.org' => -3.0, - #'donotreply@sendmail.org' => -3.0, - #'ca+envelope@sendmail.org' => -3.0, - #'noreply@freshmeat.net' => -3.0, - #'owner-technews@postel.acm.org' => -3.0, - #'ietf-123-owner@loki.ietf.org' => -3.0, - #'cvs-commits-list-admin@gnome.org' => -3.0, - #'rt-users-admin@lists.fsck.com' => -3.0, - #'clp-request@comp.nus.edu.sg' => -3.0, - #'surveys-errors@lists.nua.ie' => -3.0, - #'emailnews@genomeweb.com' => -5.0, - #'yahoo-dev-null@yahoo-inc.com' => -3.0, - #'returns.groups.yahoo.com' => -3.0, - #'clusternews@linuxnetworx.com' => -3.0, - #lc('lvs-users-admin@LinuxVirtualServer.org') => -3.0, - #lc('owner-textbreakingnews@CNNIMAIL12.CNN.COM') => -5.0, - - # soft-blacklisting (positive score) - #'sender@example.net' => 3.0, - #'.example.net' => 1.0, - - }, - ], # end of site-wide tables -}); - -1; # ensure a defined return diff --git a/data/templates/amavis/50-user.sed b/data/templates/amavis/50-user.sed deleted file mode 100644 index b0e7ce148..000000000 --- a/data/templates/amavis/50-user.sed +++ /dev/null @@ -1,30 +0,0 @@ -use strict; - -# -# Place your configuration directives here. They will override those in -# earlier files. -# -# See /usr/share/doc/amavisd-new/ for documentation and examples of -# the directives you can use in this file -# - -$myhostname = "{{ main_domain }}"; - -$mydomain = "{{ main_domain }}"; - -# Enable LDAP support -$enable_ldap = 1; - -# Default LDAP settings -$default_ldap = { - hostname => "127.0.0.1", - tls => 0, - version => 3, - base => "dc=yunohost,dc=org", - scope => "sub", - query_filter => "(&(objectClass=inetOrgPerson)(mail=%m))", -}; - - -#------------ Do not modify anything below this line ------------- -1; # ensure a defined return diff --git a/data/templates/dovecot/dovecot.conf.sed b/data/templates/dovecot/dovecot.conf.sed index 6a5070078..44ce55147 100644 --- a/data/templates/dovecot/dovecot.conf.sed +++ b/data/templates/dovecot/dovecot.conf.sed @@ -57,12 +57,12 @@ plugin { antispam_debug_target = syslog antispam_verbose_debug = 0 antispam_backend = pipe + antispam_spam = Junk;SPAM antispam_trash = Trash - antispam_spam = SPAM;Junk - antispam_allow_append_to_spam = no - antispam_pipe_program = /usr/bin/sa-learn-pipe.sh - antispam_pipe_program_spam_arg = --spam - antispam_pipe_program_notspam_arg = --ham + antispam_pipe_program = /usr/bin/rspamc + antispam_pipe_program_args = -h;localhost:11334;-P;q1 + antispam_pipe_program_spam_arg = learn_spam + antispam_pipe_program_notspam_arg = learn_ham } plugin { diff --git a/data/templates/dovecot/sa-learn-pipe.sh b/data/templates/dovecot/sa-learn-pipe.sh deleted file mode 100644 index 67437e559..000000000 --- a/data/templates/dovecot/sa-learn-pipe.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh -echo /usr/bin/sa-learn $* /tmp/sendmail-msg-$$.txt ; -echo "$$-start ($*)" >> /tmp/sa-learn-pipe.log ; -#echo $* > /tmp/sendmail-parms.txt ; -cat<&0 >> /tmp/sendmail-msg-$$.txt ; -/usr/bin/sa-learn $* /tmp/sendmail-msg-$$.txt ; -rm -f /tmp/sendmail-msg-$$.txt ; -echo "$$-end" >> /tmp/sa-learn-pipe.log ; -exit 0; diff --git a/data/templates/postfix/main.cf.sed b/data/templates/postfix/main.cf.sed index fd81ae64f..c6354bb42 100644 --- a/data/templates/postfix/main.cf.sed +++ b/data/templates/postfix/main.cf.sed @@ -86,9 +86,6 @@ smtpd_sasl_security_options = noanonymous smtpd_sasl_local_domain = -# Use AMaVis -content_filter = amavis:[127.0.0.1]:10024 - # Wait until the RCPT TO command before evaluating restrictions smtpd_delay_reject = yes @@ -128,8 +125,6 @@ smtpd_recipient_restrictions = reject_non_fqdn_recipient, reject_unknown_recipient_domain, reject_unauth_destination, - check_policy_service unix:private/policy-spf - check_policy_service inet:127.0.0.1:10023 permit # Use SPF @@ -143,3 +138,11 @@ sender_canonical_classes = envelope_sender smtp_header_checks = regexp:/etc/postfix/header_checks smtp_reply_filter = pcre:/etc/postfix/smtp_reply_filter + +# Rmilter +milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen} +milter_protocol = 6 +smtpd_milters = inet:localhost:11000 + +# Skip email without checking if milter has died +milter_default_action = accept diff --git a/data/templates/postfix/master.cf b/data/templates/postfix/master.cf index cf7fd6267..ed6d87bd3 100644 --- a/data/templates/postfix/master.cf +++ b/data/templates/postfix/master.cf @@ -116,32 +116,3 @@ dovecot unix - n n - - pipe # (yes) (yes) (yes) (never) (100) # ========================================================================== # Added using postfix-add-filter script: -amavis unix - - - - 2 smtp - -o smtp_data_done_timeout=1200 - -o smtp_send_xforward_command=yes - -o smtp_tls_note_starttls_offer=no - -policy-spf unix - n n - - spawn - user=nobody argv=/usr/bin/perl /usr/sbin/postfix-policyd-spf-perl - -127.0.0.1:10025 inet n - - - - smtpd - -o content_filter= - -o smtpd_delay_reject=no - -o smtpd_client_restrictions=permit_mynetworks,reject - -o smtpd_helo_restrictions= - -o smtpd_sender_restrictions= - -o smtpd_recipient_restrictions=permit_mynetworks,reject - -o smtpd_data_restrictions=reject_unauth_pipelining - -o smtpd_end_of_data_restrictions= - -o smtpd_restriction_classes= - -o mynetworks=127.0.0.0/8 - -o smtpd_error_sleep_time=0 - -o smtpd_soft_error_limit=1001 - -o smtpd_hard_error_limit=1000 - -o smtpd_client_connection_count_limit=0 - -o smtpd_client_connection_rate_limit=0 - -o receive_override_options=no_header_body_checks,no_unknown_recipient_checks,no_milters - -o local_header_rewrite_clients= - -o smtpd_milters= - -o local_recipient_maps= - -o relay_recipient_maps= diff --git a/data/templates/postgrey/postgrey.default b/data/templates/postgrey/postgrey.default deleted file mode 100644 index 1af70c149..000000000 --- a/data/templates/postgrey/postgrey.default +++ /dev/null @@ -1,12 +0,0 @@ -# postgrey startup options, created for Debian - -# you may want to set -# --delay=N how long to greylist, seconds (default: 300) -# --max-age=N delete old entries after N days (default: 35) -# see also the postgrey(8) manpage - -POSTGREY_OPTS="--inet=10023 --delay=30" - -# the --greylist-text commandline argument can not be easily passed through -# POSTGREY_OPTS when it contains spaces. So, insert your text here: -#POSTGREY_TEXT="Your customized rejection message here" diff --git a/data/templates/rmilter/rmilter.conf b/data/templates/rmilter/rmilter.conf new file mode 100644 index 000000000..d74196df8 --- /dev/null +++ b/data/templates/rmilter/rmilter.conf @@ -0,0 +1,18 @@ +# systemd-specific settings for rmilter + +.include /etc/rmilter.conf.common + +pidfile = /var/run/rmilter/rmilter.pid; + +# listen on TCP socket +bind_socket = inet:11000@localhost; + +# DKIM signing +dkim { + domain { + key = /etc/dkim; + domain = "*"; + selector = "mail"; + }; +}; + diff --git a/data/templates/rspamd/metrics.conf b/data/templates/rspamd/metrics.conf new file mode 100644 index 000000000..1236b2a3e --- /dev/null +++ b/data/templates/rspamd/metrics.conf @@ -0,0 +1,1163 @@ +# Metrics settings + +metric { + name = "default"; + # If this param is set to non-zero + # then a metric would accept all symbols + # unknown_weight = 1.0 + + actions { + reject = 21; + add_header = 8; + greylist = 4; + }; + + group { + name = "header"; + symbol { + weight = 2.0; + description = "Subject is missing inside message"; + name = "MISSING_SUBJECT"; + } + symbol { + weight = 2.100000; + description = "Message pretends to be send from Outlook but has 'strange' tags "; + name = "FORGED_OUTLOOK_TAGS"; + } + symbol { + weight = 0.30; + description = "Sender is forged (different From: header and smtp MAIL FROM: addresses)"; + name = "FORGED_SENDER"; + } + symbol { + weight = 3.500000; + description = "Recipients seems to be autogenerated (works if recipients count is more than 5)"; + name = "SUSPICIOUS_RECIPS"; + } + symbol { + weight = 6.0; + description = "Fake reply (has RE in subject, but has not References header)"; + name = "FAKE_REPLY_C"; + } + symbol { + weight = 1.0; + description = "Messages that have only HTML part"; + name = "MIME_HTML_ONLY"; + } + symbol { + weight = 2.0; + description = "Forged yahoo msgid"; + name = "FORGED_MSGID_YAHOO"; + } + symbol { + weight = 2.0; + description = "Forged The Bat! MUA headers"; + name = "FORGED_MUA_THEBAT_BOUN"; + } + symbol { + weight = 5.0; + description = "Charset is missing in a message"; + name = "R_MISSING_CHARSET"; + } + symbol { + weight = 2.0; + description = "Two received headers with ip addresses"; + name = "RCVD_DOUBLE_IP_SPAM"; + } + symbol { + weight = 5.0; + description = "Forged outlook HTML signature"; + name = "FORGED_OUTLOOK_HTML"; + } + symbol { + weight = 5.0; + description = "Recipients are absent or undisclosed"; + name = "R_UNDISC_RCPT"; + } + symbol { + weight = 2.0; + description = "Fake helo for verizon provider"; + name = "FM_FAKE_HELO_VERIZON"; + } + symbol { + weight = 2.0; + description = "Quoted reply-to from yahoo (seems to be forged)"; + name = "REPTO_QUOTE_YAHOO"; + } + symbol { + weight = 5.0; + description = "Mime-OLE is needed but absent (e.g. fake Outlook or fake Exchange)"; + name = "MISSING_MIMEOLE"; + } + symbol { + weight = 2.0; + description = "To header is missing"; + name = "MISSING_TO"; + } + symbol { + weight = 1.500000; + description = "From that contains encoded characters while base 64 is not needed as all symbols are 7bit"; + name = "FROM_EXCESS_BASE64"; + } + symbol { + weight = 1.200000; + description = "From that contains encoded characters while quoted-printable is not needed as all symbols are 7bit"; + name = "FROM_EXCESS_QP"; + } + symbol { + weight = 1.500000; + description = "To that contains encoded characters while base 64 is not needed as all symbols are 7bit"; + name = "TO_EXCESS_BASE64"; + } + symbol { + weight = 1.200000; + description = "To that contains encoded characters while quoted-printable is not needed as all symbols are 7bit"; + name = "TO_EXCESS_QP"; + } + symbol { + weight = 1.500000; + description = "Reply-To that contains encoded characters while base 64 is not needed as all symbols are 7bit"; + name = "REPLYTO_EXCESS_BASE64"; + } + symbol { + weight = 1.200000; + description = "Reply-To that contains encoded characters while quoted-printable is not needed as all symbols are 7bit"; + name = "REPLYTO_EXCESS_QP"; + } + symbol { + weight = 1.500000; + description = "Cc that contains encoded characters while base 64 is not needed as all symbols are 7bit"; + name = "CC_EXCESS_BASE64"; + } + symbol { + weight = 1.200000; + description = "Cc that contains encoded characters while quoted-printable is not needed as all symbols are 7bit"; + name = "CC_EXCESS_QP"; + } + symbol { + weight = 5.0; + description = "Mixed characters in a message"; + name = "R_MIXED_CHARSET"; + } + symbol { + weight = 3.500000; + description = "Recipients list seems to be sorted"; + name = "SORTED_RECIPS"; + } + symbol { + weight = 3.0; + description = "Spambots signatures in received headers"; + name = "R_RCVD_SPAMBOTS"; + } + symbol { + weight = 2.0; + description = "To header seems to be autogenerated"; + name = "R_TO_SEEMS_AUTO"; + } + symbol { + weight = 1.0; + description = "Subject needs encoding"; + name = "SUBJECT_NEEDS_ENCODING"; + } + symbol { + weight = 3.840000; + description = "Spam string at the end of message to make statistics faults 0"; + name = "TRACKER_ID"; + } + symbol { + weight = 1.0; + description = "No space in from header"; + name = "R_NO_SPACE_IN_FROM"; + } + symbol { + weight = 8.0; + description = "Subject seems to be spam"; + name = "R_SAJDING"; + } + symbol { + weight = 3.0; + description = "Detects bad content-transfer-encoding for text parts"; + name = "R_BAD_CTE_7BIT"; + } + symbol { + weight = 10.0; + description = "Flash redirect on imageshack.us"; + name = "R_FLASH_REDIR_IMGSHACK"; + } + symbol { + weight = 5.0; + description = "Message id is incorrect"; + name = "INVALID_MSGID"; + } + symbol { + weight = 3.0; + description = "Message id is missing "; + name = "MISSING_MID"; + } + symbol { + weight = 1.0; + description = "Recipients are not the same as RCPT TO: mail command"; + name = "FORGED_RECIPIENTS"; + } + symbol { + weight = 0.0; + description = "Recipients are not the same as RCPT TO: mail command, but a message from a maillist"; + name = "FORGED_RECIPIENTS_MAILLIST"; + } + symbol { + weight = 0.0; + description = "Sender is not the same as MAIL FROM: envelope, but a message is from a maillist"; + name = "FORGED_SENDER_MAILLIST"; + } + symbol { + weight = 2.0; + description = "Forged Exchange messages "; + name = "RATWARE_MS_HASH"; + } + symbol { + weight = 1.0; + description = "Reply-type in content-type"; + name = "STOX_REPLY_TYPE"; + } + symbol { + weight = 0.1; + description = "One received header in a message "; + name = "ONCE_RECEIVED"; + } + symbol { + weight = 2.0; + description = "One received header with 'bad' patterns inside"; + name = "ONCE_RECEIVED_STRICT"; + } + symbol { + weight = 2.0; + description = "Only Content-Type header without other MIME headers"; + name = "MIME_HEADER_CTYPE_ONLY"; + } + symbol { + weight = -1.0; + description = "Message seems to be from maillist"; + name = "MAILLIST"; + } + symbol { + weight = 1.0; + description = "Header From begins with tab"; + name = "HEADER_FROM_DELIMITER_TAB"; + } + symbol { + weight = 1.0; + description = "Header To begins with tab"; + name = "HEADER_TO_DELIMITER_TAB"; + } + symbol { + weight = 1.0; + description = "Header Cc begins with tab"; + name = "HEADER_CC_DELIMITER_TAB"; + } + symbol { + weight = 1.0; + description = "Header Reply-To begins with tab"; + name = "HEADER_REPLYTO_DELIMITER_TAB"; + } + symbol { + weight = 1.0; + description = "Header Date begins with tab"; + name = "HEADER_DATE_DELIMITER_TAB"; + } + symbol { + weight = 1.0; + description = "Header From has no delimiter between header name and header value"; + name = "HEADER_FROM_EMPTY_DELIMITER"; + } + symbol { + weight = 1.0; + description = "Header To has no delimiter between header name and header value"; + name = "HEADER_TO_EMPTY_DELIMITER"; + } + symbol { + weight = 1.0; + description = "Header Cc has no delimiter between header name and header value"; + name = "HEADER_CC_EMPTY_DELIMITER"; + } + symbol { + weight = 1.0; + description = "Header Reply-To has no delimiter between header name and header value"; + name = "HEADER_REPLYTO_EMPTY_DELIMITER"; + } + symbol { + weight = 1.0; + description = "Header Date has no delimiter between header name and header value"; + name = "HEADER_DATE_EMPTY_DELIMITER"; + } + symbol { + weight = 4.0; + description = "Header Received has raw illegal character"; + name = "RCVD_ILLEGAL_CHARS"; + } + symbol { + weight = 4.0; + description = "Fake helo mail.ru in header Received from non mail.ru sender address"; + name = "FAKE_RECEIVED_mail_ru"; + } + symbol { + weight = 4.0; + description = "Fake smtp.yandex.ru Received"; + name = "FAKE_RECEIVED_smtp_yandex_ru"; + } + symbol { + weight = 3.600000; + description = "Forged generic Received"; + name = "FORGED_GENERIC_RECEIVED"; + } + symbol { + weight = 3.600000; + description = "Forged generic Received"; + name = "FORGED_GENERIC_RECEIVED2"; + } + symbol { + weight = 3.600000; + description = "Forged generic Received"; + name = "FORGED_GENERIC_RECEIVED3"; + } + symbol { + weight = 3.600000; + description = "Forged generic Received"; + name = "FORGED_GENERIC_RECEIVED4"; + } + symbol { + weight = 4.600000; + description = "Forged generic Received"; + name = "FORGED_GENERIC_RECEIVED5"; + } + symbol { + weight = 3.0; + description = "Invalid Postfix Received"; + name = "INVALID_POSTFIX_RECEIVED"; + } + symbol { + weight = 5.0; + description = "Invalid Exim Received"; + name = "INVALID_EXIM_RECEIVED"; + } + symbol { + weight = 3.0; + description = "Invalid Exim Received"; + name = "INVALID_EXIM_RECEIVED2"; + } + } + + group { + name = "mua"; + symbol { + weight = 4.0; + description = "Message pretends to be send from The Bat! but has forged Message-ID"; + name = "FORGED_MUA_THEBAT_MSGID"; + } + symbol { + weight = 3.0; + description = "Message pretends to be send from The Bat! but has forged Message-ID"; + name = "FORGED_MUA_THEBAT_MSGID_UNKNOWN"; + } + symbol { + weight = 3.0; + description = "Message pretends to be send from KMail but has forged Message-ID"; + name = "FORGED_MUA_KMAIL_MSGID"; + } + symbol { + weight = 2.500000; + description = "Message pretends to be send from KMail but has forged Message-ID"; + name = "FORGED_MUA_KMAIL_MSGID_UNKNOWN"; + } + symbol { + weight = 4.0; + description = "Message pretends to be send from Opera Mail but has forged Message-ID"; + name = "FORGED_MUA_OPERA_MSGID"; + } + symbol { + weight = 4.0; + description = "Message pretends to be send from suspicious Opera Mail/10.x (Windows) but has forged Message-ID, apparently from KMail"; + name = "SUSPICIOUS_OPERA_10W_MSGID"; + } + symbol { + weight = 4.0; + description = "Message pretends to be send from Mozilla Mail but has forged Message-ID"; + name = "FORGED_MUA_MOZILLA_MAIL_MSGID"; + } + symbol { + weight = 2.500000; + description = "Message pretends to be send from Mozilla Mail but has forged Message-ID"; + name = "FORGED_MUA_MOZILLA_MAIL_MSGID_UNKNOWN"; + } + symbol { + weight = 4.0; + description = "Forged mail pretending to be from Mozilla Thunderbird but has forged Message-ID"; + name = "FORGED_MUA_THUNDERBIRD_MSGID"; + } + symbol { + weight = 2.500000; + description = "Forged mail pretending to be from Mozilla Thunderbird but has forged Message-ID"; + name = "FORGED_MUA_THUNDERBIRD_MSGID_UNKNOWN"; + } + symbol { + weight = 4.0; + description = "Forged mail pretending to be from Mozilla Seamonkey but has forged Message-ID"; + name = "FORGED_MUA_SEAMONKEY_MSGID"; + } + symbol { + weight = 2.500000; + description = "Forged mail pretending to be from Mozilla Seamonkey but has forged Message-ID"; + name = "FORGED_MUA_SEAMONKEY_MSGID_UNKNOWN"; + } + symbol { + weight = 3.0; + description = "Forged outlook MUA"; + name = "FORGED_MUA_OUTLOOK"; + } + } + symbol { + weight = 0.0; + description = "Avoid false positives for FORGED_MUA_* in maillist"; + name = "FORGED_MUA_MAILLIST"; + } + + group { + name = "body"; + symbol { + weight = 9.0; + description = "White color on white background in HTML messages"; + name = "R_WHITE_ON_WHITE"; + } + symbol { + weight = 3.0; + description = "Short html part with a link to an image"; + name = "HTML_SHORT_LINK_IMG_1"; + } + symbol { + weight = 1.0; + description = "Short html part with a link to an image"; + name = "HTML_SHORT_LINK_IMG_2"; + } + symbol { + weight = 0.5; + description = "Short html part with a link to an image"; + name = "HTML_SHORT_LINK_IMG_3"; + } + symbol { + weight = 5.0; + description = "Suspicious boundary in header Content-Type"; + name = "SUSPICIOUS_BOUNDARY"; + } + symbol { + weight = 4.0; + description = "Suspicious boundary in header Content-Type"; + name = "SUSPICIOUS_BOUNDARY2"; + } + symbol { + weight = 3.0; + description = "Suspicious boundary in header Content-Type"; + name = "SUSPICIOUS_BOUNDARY3"; + } + symbol { + weight = 4.0; + description = "Suspicious boundary in header Content-Type"; + name = "SUSPICIOUS_BOUNDARY4"; + } + symbol { + weight = 3.0; + description = "Text and HTML parts differ"; + name = "R_PARTS_DIFFER"; + } + + symbol { + weight = 2.0; + description = "Message contains empty parts and image"; + name = "R_EMPTY_IMAGE"; + } + symbol { + weight = 2.0; + description = "Drugs patterns inside message"; + name = "DRUGS_MANYKINDS"; + } + symbol { + weight = 2.0; + description = ""; + name = "DRUGS_ANXIETY"; + } + symbol { + weight = 2.0; + description = ""; + name = "DRUGS_MUSCLE"; + } + symbol { + weight = 2.0; + description = ""; + name = "DRUGS_ANXIETY_EREC"; + } + symbol { + weight = 2.0; + description = ""; + name = "DRUGS_DIET"; + } + symbol { + weight = 2.0; + description = ""; + name = "DRUGS_ERECTILE"; + } + symbol { + weight = 3.300000; + description = "2 'advance fee' patterns in a message"; + name = "ADVANCE_FEE_2"; + } + symbol { + weight = 2.120000; + description = "3 'advance fee' patterns in a message"; + name = "ADVANCE_FEE_3"; + } + symbol { + weight = 8.0; + description = "Lotto signatures"; + name = "R_LOTTO"; + } + } + + group { + name = "rbl"; + symbol { + name = "DNSWL_BLOCKED"; + weight = 0.0; + description = "Resolver blocked due to excessive queries"; + } + symbol { + name = "RCVD_IN_DNSWL"; + weight = 0.0; + description = "Unrecognised result from dnswl.org"; + } + symbol { + name = "RCVD_IN_DNSWL_NONE"; + weight = 0.0; + description = "Sender listed at http://www.dnswl.org, low none"; + } + symbol { + name = "RCVD_IN_DNSWL_LOW"; + weight = 0.0; + description = "Sender listed at http://www.dnswl.org, low trust"; + } + symbol { + name = "RCVD_IN_DNSWL_MED"; + weight = 0.0; + description = "Sender listed at http://www.dnswl.org, medium trust"; + } + symbol { + name = "RCVD_IN_DNSWL_HI"; + weight = 0.0; + description = "Sender listed at http://www.dnswl.org, high trust"; + } + + symbol { + name = "RBL_SPAMHAUS"; + weight = 0.0; + description = "Unrecognised result from Spamhaus zen"; + } + symbol { + name = "RBL_SPAMHAUS_SBL"; + weight = 2.0; + description = "From address is listed in zen sbl"; + } + symbol { + name = "RBL_SPAMHAUS_CSS"; + weight = 2.0; + description = "From address is listed in zen css"; + } + symbol { + name = "RBL_SPAMHAUS_XBL"; + weight = 4.0; + description = "From address is listed in zen xbl"; + } + symbol { + name = "RBL_SPAMHAUS_PBL"; + weight = 2.0; + description = "From address is listed in zen pbl"; + } + symbol { + name = "RECEIVED_SPAMHAUS_XBL"; + weight = 3.0; + description = "Received address is listed in zen pbl"; + one_shot = true; + } + + symbol { + name = "RWL_SPAMHAUS_WL"; + weight = 0.0; + description = "Unrecognised result from Spamhaus whitelist"; + } + symbol { + name = "RWL_SPAMHAUS_WL_IND"; + weight = 0.0; + description = "Sender listed at Spamhaus whitelist"; + } + symbol { + name = "RWL_SPAMHAUS_WL_TRANS"; + weight = 0.0; + description = "Sender listed at Spamhaus whitelist"; + } + symbol { + name = "RWL_SPAMHAUS_WL_IND_EXP"; + weight = 0.0; + description = "Sender listed at Spamhaus whitelist"; + } + symbol { + name = "RWL_SPAMHAUS_WL_TRANS_EXP"; + weight = 0.0; + description = "Sender listed at Spamhaus whitelist"; + } + + symbol { + weight = 2.0; + description = "From address is listed in senderscore.com BL"; + name = "RBL_SENDERSCORE"; + } + symbol { + weight = 1.0; + description = "From address is listed in ABUSE.CH BL"; + name = "RBL_ABUSECH"; + } + symbol { + weight = 1.0; + description = "From address is listed in UCEPROTECT LEVEL1 BL"; + name = "RBL_UCEPROTECT_LEVEL1"; + } + symbol { + name = "RBL_MAILSPIKE"; + weight = 0.0; + description = "Unrecognised result from Mailspike blacklist"; + } + symbol { + name = "RWL_MAILSPIKE"; + weight = 0.0; + description = "Unrecognised result from Mailspike whitelist"; + } + symbol { + name = "RBL_MAILSPIKE_ZOMBIE"; + weight = 2.0; + description = "From address is listed in RBL"; + } + symbol { + name = "RBL_MAILSPIKE_WORST"; + weight = 2.0; + description = "From address is listed in RBL"; + } + symbol { + name = "RBL_MAILSPIKE_VERYBAD"; + weight = 1.5; + description = "From address is listed in RBL"; + } + symbol { + name = "RBL_MAILSPIKE_BAD"; + weight = 1.0; + description = "From address is listed in RBL"; + } + symbol { + name = "RWL_MAILSPIKE_POSSIBLE"; + weight = 0.0; + description = "From address is listed in RWL"; + } + symbol { + name = "RWL_MAILSPIKE_GOOD"; + weight = 0.0; + description = "From address is listed in RWL"; + } + symbol { + name = "RWL_MAILSPIKE_VERYGOOD"; + weight = 0.0; + description = "From address is listed in RWL"; + } + symbol { + name = "RWL_MAILSPIKE_EXCELLENT"; + weight = 0.0; + description = "From address is listed in RWL"; + } + + symbol { + weight = 0.0; + name = "RBL_SORBS"; + description = "Unrecognised result from SORBS RBL"; + } + symbol { + weight = 2.5; + name = "RBL_SORBS_HTTP"; + description = "List of Open HTTP Proxy Servers."; + } + symbol { + weight = 2.5; + name = "RBL_SORBS_SOCKS"; + description = "List of Open SOCKS Proxy Servers."; + } + symbol { + weight = 1.0; + name = "RBL_SORBS_MISC"; + description = "List of open Proxy Servers not listed in the SOCKS or HTTP lists."; + } + symbol { + weight = 3.0; + name = "RBL_SORBS_SMTP"; + description = "List of Open SMTP relay servers."; + } + symbol { + weight = 1.5; + name = "RBL_SORBS_RECENT"; + description = "List of hosts that have been noted as sending spam/UCE/UBE to the admins of SORBS within the last 28 days (includes new.spam.dnsbl.sorbs.net)."; + } + symbol { + weight = 0.4; + name = "RBL_SORBS_WEB"; + description = "List of web (WWW) servers which have spammer abusable vulnerabilities (e.g. FormMail scripts)"; + } + symbol { + weight = 2.0; + name = "RBL_SORBS_DUL"; + description = "Dynamic IP Address ranges (NOT a Dial Up list!)"; + } + symbol { + weight = 1.0; + name = "RBL_SORBS_BLOCK"; + description = "List of hosts demanding that they never be tested by SORBS."; + } + symbol { + weight = 1.0; + name = "RBL_SORBS_ZOMBIE"; + description = "List of networks hijacked from their original owners, some of which have already used for spamming."; + } + + symbol { + weight = 1.0; + name = "RBL_SEM"; + description = "Address is listed in Spameatingmonkey RBL"; + } + + symbol { + weight = 1.0; + name = "RBL_SEM_IPV6"; + description = "Address is listed in Spameatingmonkey RBL (ipv6)"; + } + } + + group { + name = "bayes"; + + symbol { + weight = 3.0; + description = "Message probably spam, probability: "; + name = "BAYES_SPAM"; + } + symbol { + weight = -3.0; + description = "Message probably ham, probability: "; + name = "BAYES_HAM"; + } + } + + group { + name = "fuzzy"; + symbol { + weight = 5.0; + description = "Generic fuzzy hash match"; + name = "FUZZY_UNKNOWN"; + } + symbol { + weight = 10.0; + description = "Denied fuzzy hash"; + name = "FUZZY_DENIED"; + } + symbol { + weight = 5.0; + description = "Probable fuzzy hash"; + name = "FUZZY_PROB"; + } + symbol { + weight = -2.1; + description = "Whitelisted fuzzy hash"; + name = "FUZZY_WHITE"; + } + } + + group { + name = "spf"; + symbol { + weight = 1.0; + description = "SPF verification failed"; + name = "R_SPF_FAIL"; + } + symbol { + weight = 0.0; + description = "SPF verification soft-failed"; + name = "R_SPF_SOFTFAIL"; + } + symbol { + weight = 0.0; + description = "SPF policy is neutral"; + name = "R_SPF_NEUTRAL"; + } + symbol { + weight = -1.1; + description = "SPF verification alowed"; + name = "R_SPF_ALLOW"; + } + } + + group { + name = "dkim"; + symbol { + weight = 1.0; + description = "DKIM verification failed"; + name = "R_DKIM_REJECT"; + } + symbol { + weight = 0.0; + description = "DKIM verification soft-failed"; + name = "R_DKIM_TEMPFAIL"; + } + symbol { + weight = -1.1; + description = "DKIM verification succeed"; + name = "R_DKIM_ALLOW"; + one_shot = true; + } + } + + group { + name = "surbl"; + symbol { + weight = 5.500000; + description = "SURBL: Phishing sites"; + name = "PH_SURBL_MULTI"; + } + symbol { + weight = 5.500000; + description = "SURBL: Malware sites"; + name = "MW_SURBL_MULTI"; + } + symbol { + weight = 5.500000; + description = "SURBL: AbuseButler web sites"; + name = "AB_SURBL_MULTI"; + } + symbol { + weight = 5.500000; + description = "SURBL: SpamCop web sites"; + name = "SC_SURBL_MULTI"; + } + symbol { + weight = 5.500000; + description = "SURBL: jwSpamSpy + Prolocation sites"; + name = "JP_SURBL_MULTI"; + } + symbol { + weight = 5.500000; + description = "SURBL: sa-blacklist web sites "; + name = "WS_SURBL_MULTI"; + } + symbol { + weight = 4.500000; + description = "rambler.ru uribl"; + name = "RAMBLER_URIBL"; + } + + symbol { + weight = 0.0; + name = "SEM_URIBL_UNKNOWN"; + description = "Spameatingmonkey uribl unknown"; + } + symbol { + weight = 3.5; + name = "SEM_URIBL"; + description = "Spameatingmonkey uribl"; + } + + symbol { + weight = 0.0; + name = "SEM_URIBL_FRESH15_UNKNOWN"; + description = "Spameatingmonkey uribl unknown"; + } + symbol { + weight = 3.0; + name = "SEM_URIBL_FRESH15"; + description = "Spameatingmonkey uribl. Domains registered in the last 15 days (.AERO,.BIZ,.COM,.INFO,.NAME,.NET,.PRO,.SK,.TEL,.US)"; + } + + symbol { + weight = 0.000000; + description = "DBL uribl unknown symbol (error)"; + name = "DBL"; + } + symbol { + weight = 6.500000; + description = "DBL uribl spam"; + name = "DBL_SPAM"; + } + symbol { + weight = 6.500000; + description = "DBL uribl phishing"; + name = "DBL_PHISH"; + } + symbol { + weight = 6.500000; + description = "DBL uribl malware"; + name = "DBL_MALWARE"; + } + symbol { + weight = 5.500000; + description = "DBL uribl botnet C&C domain"; + name = "DBL_BOTNET"; + } + symbol { + weight = 6.500000; + description = "DBL uribl abused legit spam"; + name = "DBL_ABUSE"; + } + symbol { + weight = 1.500000; + description = "DBL uribl abused spammed redirector domain"; + name = "DBL_ABUSE_REDIR"; + } + symbol { + weight = 7.500000; + description = "DBL uribl abused legit phish"; + name = "DBL_ABUSE_PHISH"; + } + symbol { + weight = 7.500000; + description = "DBL uribl abused legit malware"; + name = "DBL_ABUSE_MALWARE"; + } + symbol { + weight = 5.500000; + description = "DBL uribl abused legit botnet C&C"; + name = "DBL_ABUSE_BOTNET"; + } + symbol { + weight = 0.00000; + description = "DBL uribl IP queries prohibited!"; + name = "DBL_PROHIBIT"; + } + symbol { + weight = 7.5; + description = "uribl.com black url"; + name = "URIBL_BLACK"; + } + symbol { + weight = 3.5; + description = "uribl.com red url"; + name = "URIBL_RED"; + } + symbol { + weight = 1.5; + description = "uribl.com grey url"; + name = "URIBL_GREY"; + } + symbol { + weight = 9.500000; + description = "rambler.ru emailbl"; + name = "RAMBLER_EMAILBL"; + } + } + + group { + name = "phishing"; + + symbol { + weight = 5.0; + description = "Phished mail"; + name = "PHISHING"; + } + } + + group { + name = "date"; + + symbol { + weight = 4.0; + description = "Message date is in future"; + name = "DATE_IN_FUTURE"; + } + symbol { + weight = 1.0; + description = "Message date is in past"; + name = "DATE_IN_PAST"; + } + symbol { + weight = 1.0; + description = "Message date is missing"; + name = "MISSING_DATE"; + } + } + + group { + name = "hfilter"; + + symbol { + weight = 3.00; + name = "HFILTER_HELO_BAREIP"; + description = "Helo host is bare ip"; + } + symbol { + weight = 4.50; + name = "HFILTER_HELO_BADIP"; + description = "Helo host is very bad ip"; + } + symbol { + weight = 2.00; + name = "HFILTER_HELO_UNKNOWN"; + description = "Helo host empty or unknown"; + } + symbol { + weight = 0.5; + name = "HFILTER_HELO_1"; + description = "Helo host checks (very low)"; + } + symbol { + weight = 1.00; + name = "HFILTER_HELO_2"; + description = "Helo host checks (low)"; + } + symbol { + weight = 2.00; + name = "HFILTER_HELO_3"; + description = "Helo host checks (medium)"; + } + symbol { + weight = 2.50; + name = "HFILTER_HELO_4"; + description = "Helo host checks (hard)"; + } + symbol { + weight = 3.00; + name = "HFILTER_HELO_5"; + description = "Helo host checks (very hard)"; + } + symbol { + weight = 0.5; + name = "HFILTER_HOSTNAME_1"; + description = "Hostname checks (very low)"; + } + symbol { + weight = 1.00; + name = "HFILTER_HOSTNAME_2"; + description = "Hostname checks (low)"; + } + symbol { + weight = 2.00; + name = "HFILTER_HOSTNAME_3"; + description = "Hostname checks (medium)"; + } + symbol { + weight = 2.50; + name = "HFILTER_HOSTNAME_4"; + description = "Hostname checks (hard)"; + } + symbol { + weight = 3.00; + name = "HFILTER_HOSTNAME_5"; + description = "Hostname checks (very hard)"; + } + symbol { + weight = 0.20; + name = "HFILTER_HELO_NORESOLVE_MX"; + description = "MX found in Helo and no resolve"; + } + symbol { + weight = 0.3; + name = "HFILTER_HELO_NORES_A_OR_MX"; + description = "Helo no resolve to A or MX"; + } + symbol { + weight = 1.00; + name = "HFILTER_HELO_IP_A"; + description = "Helo A IP != hostname IP"; + } + symbol { + weight = 2.00; + name = "HFILTER_HELO_NOT_FQDN"; + description = "Helo not FQDN"; + } + symbol { + weight = 0.5; + name = "HFILTER_FROMHOST_NORESOLVE_MX"; + description = "MX found in FROM host and no resolve"; + } + symbol { + weight = 1.50; + name = "HFILTER_FROMHOST_NORES_A_OR_MX"; + description = "FROM host no resolve to A or MX"; + } + symbol { + weight = 3.00; + name = "HFILTER_FROMHOST_NOT_FQDN"; + description = "FROM host not FQDN"; + } + symbol { + weight = 0.00; + name = "HFILTER_FROM_BOUNCE"; + description = "Bounce message"; + } + symbol { + weight = 0.50; + name = "HFILTER_MID_NORESOLVE_MX"; + description = "MX found in Message-id host and no resolve"; + } + symbol { + weight = 0.50; + name = "HFILTER_MID_NORES_A_OR_MX"; + description = "Message-id host no resolve to A or MX"; + } + symbol { + weight = 0.50; + name = "HFILTER_MID_NOT_FQDN"; + description = "Message-id host not FQDN"; + } + symbol { + weight = 4.00; + name = "HFILTER_HOSTNAME_UNKNOWN"; + description = "Unknown hostname (no PTR or no resolve PTR to hostname)"; + } + symbol { + weight = 1.50; + name = "HFILTER_RCPT_BOUNCEMOREONE"; + description = "Message from bounce and over 1 recepient"; + } + symbol { + weight = 3.50; + name = "HFILTER_URL_ONLY"; + description = "URL only in body"; + } + symbol { + weight = 2.20; + name = "HFILTER_URL_ONELINE"; + description = "One line URL and text in body"; + } + } + + group { + name = "dmarc"; + + symbol { + weight = -1.0; + name = "DMARC_POLICY_ALLOW"; + description = "DMARC permit policy"; + } + symbol { + weight = 2.0; + name = "DMARC_POLICY_REJECT"; + description = "DMARC reject policy"; + } + symbol { + weight = 1.5; + name = "DMARC_POLICY_QUARANTINE"; + description = "DMARC quarantine policy"; + } + symbol { + weight = 0.1; + name = "DMARC_POLICY_SOFTFAIL"; + description = "DMARC failed"; + } + } +} diff --git a/data/templates/rspamd/rspamd.sieve b/data/templates/rspamd/rspamd.sieve new file mode 100644 index 000000000..38943eefa --- /dev/null +++ b/data/templates/rspamd/rspamd.sieve @@ -0,0 +1,4 @@ +require ["fileinto"]; +if header :is "X-Spam" "yes" { + fileinto "Junk"; +} diff --git a/data/templates/spamassassin/local.cf b/data/templates/spamassassin/local.cf deleted file mode 100644 index bc37b3a60..000000000 --- a/data/templates/spamassassin/local.cf +++ /dev/null @@ -1,94 +0,0 @@ -# This is the right place to customize your installation of SpamAssassin. -report_safe 0 -lock_method flock - -# Bayes-related operations -use_bayes 1 -use_bayes_rules 1 -bayes_auto_learn 1 -bayes_auto_expire 1 -bayes_path /var/lib/amavis/.spamassassin/bayes -bayes_file_mode 0777 - -# External network tests -dns_available yes -skip_rbl_checks 0 -use_razor2 1 -use_pyzor 1 - -# Use URIBL (http://www.uribl.com/about.shtml) -urirhssub URIBL_BLACK multi.uribl.com. A 2 -body URIBL_BLACK eval:check_uridnsbl('URIBL_BLACK') -describe URIBL_BLACK Contains an URL listed in the URIBL blacklist -tflags URIBL_BLACK net -score URIBL_BLACK 3.0 - -urirhssub URIBL_GREY multi.uribl.com. A 4 -body URIBL_GREY eval:check_uridnsbl('URIBL_GREY') -describe URIBL_GREY Contains an URL listed in the URIBL greylist -tflags URIBL_GREY net -score URIBL_GREY 0.25 - -# Use SURBL (http://www.surbl.org/) -urirhssub URIBL_JP_SURBL multi.surbl.org. A 64 -body URIBL_JP_SURBL eval:check_uridnsbl('URIBL_JP_SURBL') -describe URIBL_JP_SURBL Has URI in JP at http://www.surbl.org/lists.html -tflags URIBL_JP_SURBL net -score URIBL_JP_SURBL 3.0 - - -score SPF_FAIL 10.000 -score SPF_HELO_FAIL 10.000 -score RAZOR2_CHECK 2.500 -score RAZOR2_CF_RANGE_51_100 3.500 -# -# See 'perldoc Mail::SpamAssassin::Conf' for details of what can be -# tweaked. -# -# Only a small subset of options are listed below -# -########################################################################### - -# Add *****SPAM***** to the Subject header of spam e-mails -# -# rewrite_header Subject *****SPAM***** - - -# Save spam messages as a message/rfc822 MIME attachment instead of -# modifying the original message (0: off, 2: use text/plain instead) -# -# report_safe 1 - - -# Set which networks or hosts are considered 'trusted' by your mail -# server (i.e. not spammers) -# -# trusted_networks 212.17.35. - - -# Set file-locking method (flock is not safe over NFS, but is faster) -# -# lock_method flock - - -# Set the threshold at which a message is considered spam (default: 5.0) -# -# required_score 5.0 - - -# Use Bayesian classifier (default: 1) -# -# use_bayes 1 - - -# Bayesian classifier auto-learning (default: 1) -# -# bayes_auto_learn 1 - - -# Set headers which may provide inappropriate cues to the Bayesian -# classifier -# -# bayes_ignore_header X-Bogosity -# bayes_ignore_header X-Spam-Flag -# bayes_ignore_header X-Spam-Status diff --git a/data/templates/spamassassin/spamassassin.default b/data/templates/spamassassin/spamassassin.default deleted file mode 100644 index da1b33110..000000000 --- a/data/templates/spamassassin/spamassassin.default +++ /dev/null @@ -1,31 +0,0 @@ -# /etc/default/spamassassin -# Duncan Findlay - -# WARNING: please read README.spamd before using. -# There may be security risks. - -# Change to one to enable spamd -ENABLED=0 - -# Options -# See man spamd for possible options. The -d option is automatically added. - -# SpamAssassin uses a preforking model, so be careful! You need to -# make sure --max-children is not set to anything higher than 5, -# unless you know what you're doing. - -OPTIONS="--create-prefs --max-children 5 --helper-home-dir" - -# Pid file -# Where should spamd write its PID to file? If you use the -u or -# --username option above, this needs to be writable by that user. -# Otherwise, the init script will not be able to shut spamd down. -PIDFILE="/var/run/spamd.pid" - -# Set nice level of spamd -#NICE="--nicelevel 15" - -# Cronjob -# Set to anything but 0 to enable the cron job to automatically update -# spamassassin's rules on a nightly basis -CRON=1 diff --git a/debian/control b/debian/control index f95292075..1dacf81c9 100644 --- a/debian/control +++ b/debian/control @@ -28,12 +28,13 @@ Depends: ${python:Depends}, ${misc:Depends}, curl, mariadb-server | mysql-server, php5-mysql | php5-mysqlnd, slapd, ldap-utils, sudo-ldap, libnss-ldapd, - postfix-ldap, postfix-policyd-spf-perl, postfix-pcre, postgrey, + postfix-ldap, postfix-policyd-spf-perl, postfix-pcre, dovecot-ldap, dovecot-lmtpd, dovecot-managesieved, - amavisd-new, razor, pyzor, dovecot-antispam, spamassassin, fail2ban, + dovecot-antispam, fail2ban, nginx-extras (>=1.6.2), php5-fpm, php5-ldap, php5-intl, dnsmasq, openssl, avahi-daemon, - ssowat, metronome + ssowat, metronome, + rspamd, rmilter, memcached, opendkim-tools Recommends: yunohost-admin, bash-completion, rsyslog, ntp, openssh-server, php5-gd, php5-curl, php-gettext, php5-mcrypt, From 95cbff27a9f27ce7d0a661c8548c2a408f96e5a2 Mon Sep 17 00:00:00 2001 From: kload Date: Sat, 31 Oct 2015 13:58:02 +0100 Subject: [PATCH 031/170] [fix] Allow yunohost firewall service to be run even if YunoHost is unconfigured --- data/actionsmap/yunohost.yml | 3 +++ src/yunohost/firewall.py | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 867404fd0..053aee467 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1017,6 +1017,9 @@ firewall: reload: action_help: Reload all firewall rules api: PUT /firewall + configuration: + authenticate: false + lock: false ### firewall_allow() allow: diff --git a/src/yunohost/firewall.py b/src/yunohost/firewall.py index c0b06e64b..59c6a1a03 100644 --- a/src/yunohost/firewall.py +++ b/src/yunohost/firewall.py @@ -199,6 +199,10 @@ def firewall_reload(): reloaded = False errors = False + # Do not continue if YunoHost is not configured + try: open('/etc/yunohost/installed') + except IOError: return True + # Check if SSH port is allowed ssh_port = _get_ssh_port() if ssh_port not in firewall_list()['opened_ports']: From 8abb5237497bac8268409cb5c57765b4d339058e Mon Sep 17 00:00:00 2001 From: kload Date: Sat, 31 Oct 2015 14:14:34 +0100 Subject: [PATCH 032/170] [fix] rmilter configuration path --- data/hooks/conf_regen/22-email-legacy | 3 +++ data/hooks/conf_regen/28-rmilter | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/data/hooks/conf_regen/22-email-legacy b/data/hooks/conf_regen/22-email-legacy index e66a20045..ba13e4b89 100644 --- a/data/hooks/conf_regen/22-email-legacy +++ b/data/hooks/conf_regen/22-email-legacy @@ -8,6 +8,9 @@ sudo yunohost service add rspamd -l /var/log/mail.log \ sudo yunohost service add rmilter -l /var/log/mail.log \ || echo "Rspamd is already listed in services" +sudo yunohost service add memcached \ + || echo "Memcached is already listed in services" + # Remove previous email services sudo yunohost service disable spamassassin \ || echo "Spamassassin is already removed" \ diff --git a/data/hooks/conf_regen/28-rmilter b/data/hooks/conf_regen/28-rmilter index ef31b6806..7999318d9 100644 --- a/data/hooks/conf_regen/28-rmilter +++ b/data/hooks/conf_regen/28-rmilter @@ -16,7 +16,7 @@ function safe_copy () { cd /usr/share/yunohost/templates/rmilter # Copy Rmilter configuration -safe_copy rmilter.conf /etc/rmilter/rmilter.conf +safe_copy rmilter.conf /etc/rmilter.conf # Create the PID directory sudo mkdir -p /var/run/rmilter @@ -28,7 +28,11 @@ domain_list=$(sudo yunohost domain list --plain) for domain in $domain_list; do [ -f /etc/dkim/$domain.mail.key ] \ - || sudo opendkim-genkey --domain=$domain --selector=mail + || (sudo opendkim-genkey --domain=$domain \ + --selector=mail\ + --directory=/etc/dkim \ + && sudo mv /etc/dkim/mail.private /etc/dkim/$domain.mail.key \ + && sudo mv /etc/dkim/mail.txt /etc/dkim/$domain.mail.txt) sudo chown _rmilter /etc/dkim/$domain.mail.key sudo chmod 400 /etc/dkim/$domain.mail.key From c7310e6b6e0410b72fb3d97cd23f2d63e459b1fb Mon Sep 17 00:00:00 2001 From: kload Date: Sat, 31 Oct 2015 14:23:38 +0100 Subject: [PATCH 033/170] [fix] Typo in email-legacy hook --- data/hooks/conf_regen/22-email-legacy | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/data/hooks/conf_regen/22-email-legacy b/data/hooks/conf_regen/22-email-legacy index ba13e4b89..0b11ae10c 100644 --- a/data/hooks/conf_regen/22-email-legacy +++ b/data/hooks/conf_regen/22-email-legacy @@ -12,18 +12,14 @@ sudo yunohost service add memcached \ || echo "Memcached is already listed in services" # Remove previous email services -sudo yunohost service disable spamassassin \ +sudo yunohost service remove spamassassin \ || echo "Spamassassin is already removed" \ - && systemctl disable spamassassin || true + && sudo systemctl disable spamassassin || true -sudo yunohost service disable amavis \ +sudo yunohost service remove amavis \ || echo "Amavis is already removed" \ - && systemctl disable spamassassin || true + && sudo systemctl disable amavis || true -sudo yunohost service disable postgrey \ +sudo yunohost service remove postgrey \ || echo "Postgrey is already removed" \ - && systemctl disable postgrey || true - -sudo yunohost service disable postgrey \ - && sudo yunohost service remove amavis \ - || echo "Amavis is already removed" + && sudo systemctl disable postgrey || true From 009fe2f6372faf503e1cd602fc3649b4c41e00a5 Mon Sep 17 00:00:00 2001 From: kload Date: Sat, 31 Oct 2015 14:26:11 +0100 Subject: [PATCH 034/170] [fix] Force hook execution when YunoHost is not configured --- debian/postinst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/debian/postinst b/debian/postinst index 5a2c03ec1..de714208b 100644 --- a/debian/postinst +++ b/debian/postinst @@ -6,10 +6,10 @@ do_configure() { rm -rf /var/cache/moulinette/* if [ ! -f /etc/yunohost/installed ]; then - bash /usr/share/yunohost/hooks/conf_regen/01-yunohost - bash /usr/share/yunohost/hooks/conf_regen/02-ssl - bash /usr/share/yunohost/hooks/conf_regen/06-slapd - bash /usr/share/yunohost/hooks/conf_regen/15-nginx + bash /usr/share/yunohost/hooks/conf_regen/01-yunohost True + bash /usr/share/yunohost/hooks/conf_regen/02-ssl True + bash /usr/share/yunohost/hooks/conf_regen/06-slapd True + bash /usr/share/yunohost/hooks/conf_regen/15-nginx True else yunohost service regenconf fi From 2019fb21ec99beb01c6645993a3660d4e0550f0a Mon Sep 17 00:00:00 2001 From: kload Date: Sat, 31 Oct 2015 14:37:20 +0100 Subject: [PATCH 035/170] [fix] header_check path typo --- data/hooks/conf_regen/19-postfix | 2 +- data/templates/postfix/{header_check => header_checks} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename data/templates/postfix/{header_check => header_checks} (100%) diff --git a/data/hooks/conf_regen/19-postfix b/data/hooks/conf_regen/19-postfix index 38061ee63..cf339df14 100644 --- a/data/hooks/conf_regen/19-postfix +++ b/data/hooks/conf_regen/19-postfix @@ -19,7 +19,7 @@ function safe_copy () { cd /usr/share/yunohost/templates/postfix # Copy plain single configuration files -files="header_check +files="header_checks ldap-accounts.cf ldap-aliases.cf ldap-domains.cf diff --git a/data/templates/postfix/header_check b/data/templates/postfix/header_checks similarity index 100% rename from data/templates/postfix/header_check rename to data/templates/postfix/header_checks From 70fb1f3a296193484c1a84ce09d650a0bedcd3be Mon Sep 17 00:00:00 2001 From: kload Date: Sat, 31 Oct 2015 16:21:21 +0100 Subject: [PATCH 036/170] [fix] Restart rspamd socket before the service --- data/hooks/conf_regen/31-rspamd | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/hooks/conf_regen/31-rspamd b/data/hooks/conf_regen/31-rspamd index 0f6ef7c16..3908c386e 100644 --- a/data/hooks/conf_regen/31-rspamd +++ b/data/hooks/conf_regen/31-rspamd @@ -24,4 +24,5 @@ sudo sievec /etc/dovecot/global_script/rspamd.sieve sudo chmod 660 /etc/dovecot/global_script/rspamd.svbin sudo chown -R vmail:mail /etc/dovecot/global_script -sudo service rspamd restart +sudo systemctl restart rspamd.socket +sudo systemctl restart rspamd.service From e133acdc848575eb6ca567f9620dc4ae2999ae7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sat, 31 Oct 2015 16:31:47 +0100 Subject: [PATCH 037/170] [fix] Do not start yunohost-firewall service by default --- debian/postinst | 4 ++++ debian/prerm | 11 +++++++++++ debian/rules | 2 +- 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 debian/prerm diff --git a/debian/postinst b/debian/postinst index de714208b..de5febe92 100644 --- a/debian/postinst +++ b/debian/postinst @@ -12,6 +12,10 @@ do_configure() { bash /usr/share/yunohost/hooks/conf_regen/15-nginx True else yunohost service regenconf + + # restart yunohost-firewall if it's running + service yunohost-firewall status > /dev/null \ + && service yunohost-firewall restart fi } diff --git a/debian/prerm b/debian/prerm new file mode 100644 index 000000000..01aee685b --- /dev/null +++ b/debian/prerm @@ -0,0 +1,11 @@ +#!/bin/bash + +set -e + +if [ -x "/etc/init.d/yunohost-firewall" ]; then + invoke-rc.d yunohost-firewall stop || exit $? +fi + +#DEBHELPER# + +exit 0 diff --git a/debian/rules b/debian/rules index 601c47ea3..311b79678 100755 --- a/debian/rules +++ b/debian/rules @@ -9,4 +9,4 @@ override_dh_installinit: dh_installinit --name=yunohost-api - dh_installinit --name=yunohost-firewall + dh_installinit --name=yunohost-firewall --no-start From b51c450873c6f3a7dbd2213e0a2c8319ff099c63 Mon Sep 17 00:00:00 2001 From: kload Date: Sat, 31 Oct 2015 16:49:35 +0100 Subject: [PATCH 038/170] [fix] do not start Rspamd service, socket is enough --- data/hooks/conf_regen/31-rspamd | 1 - 1 file changed, 1 deletion(-) diff --git a/data/hooks/conf_regen/31-rspamd b/data/hooks/conf_regen/31-rspamd index 3908c386e..5aadb7ccc 100644 --- a/data/hooks/conf_regen/31-rspamd +++ b/data/hooks/conf_regen/31-rspamd @@ -25,4 +25,3 @@ sudo chmod 660 /etc/dovecot/global_script/rspamd.svbin sudo chown -R vmail:mail /etc/dovecot/global_script sudo systemctl restart rspamd.socket -sudo systemctl restart rspamd.service From eea9b2e3af76077c37aa072ae98354cc303c8454 Mon Sep 17 00:00:00 2001 From: kload Date: Sat, 31 Oct 2015 18:03:51 +0100 Subject: [PATCH 039/170] [fix] Do not mind notices and warnings when parsing command response --- data/hooks/conf_regen/12-metronome | 4 ++-- data/hooks/conf_regen/15-nginx | 2 +- data/hooks/conf_regen/34-mysql | 2 +- data/hooks/conf_regen/37-avahi-daemon | 2 +- data/hooks/conf_regen/40-glances | 2 +- data/hooks/conf_regen/46-nsswitch | 2 +- data/hooks/conf_regen/49-udisks-glue | 2 +- data/hooks/conf_regen/52-fail2ban | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/data/hooks/conf_regen/12-metronome b/data/hooks/conf_regen/12-metronome index b52c18d88..ada37e8d8 100644 --- a/data/hooks/conf_regen/12-metronome +++ b/data/hooks/conf_regen/12-metronome @@ -38,7 +38,7 @@ for domain in $domain_list; do cat domain.cfg.lua.sed \ | sed "s/{{ domain }}/$domain/g" \ | sudo tee $domain.cfg.lua - if [[ $(safe_copy $domain.cfg.lua /etc/metronome/conf.d/$domain.cfg.lua) == "True" ]]; then + if [[ $(safe_copy $domain.cfg.lua /etc/metronome/conf.d/$domain.cfg.lua | tail -n1) == "True" ]]; then need_restart=True fi done @@ -50,7 +50,7 @@ for file in /etc/metronome/conf.d/*; do | sed 's|.cfg.lua||') sanitzed_domain="$(echo $domain | sed 's/\./%2e/g')" [[ $domain_list =~ $domain ]] \ - || ($(sudo yunohost service saferemove -s metronome $file) == "True" \ + || ($(sudo yunohost service saferemove -s metronome $file | tail -n1) == "True" \ && rm -rf /var/lib/metronome/$sanitzed_domain) done diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx index e5a8716b3..889d7408f 100644 --- a/data/hooks/conf_regen/15-nginx +++ b/data/hooks/conf_regen/15-nginx @@ -45,7 +45,7 @@ if [ -f /etc/yunohost/installed ]; then cat server.conf.sed \ | sed "s/{{ domain }}/$domain/g" \ | sudo tee $domain.conf - [[ $(safe_copy $domain.conf /etc/nginx/conf.d/$domain.conf) == "True" ]] \ + [[ $(safe_copy $domain.conf /etc/nginx/conf.d/$domain.conf | tail -n1) == "True" ]] \ && need_restart=True [ -f /etc/nginx/conf.d/$domain.d/yunohost_local.conf ] \ diff --git a/data/hooks/conf_regen/34-mysql b/data/hooks/conf_regen/34-mysql index 021e4537a..d34b30880 100644 --- a/data/hooks/conf_regen/34-mysql +++ b/data/hooks/conf_regen/34-mysql @@ -21,7 +21,7 @@ function randpass () { cd /usr/share/yunohost/templates/mysql -if [[ "$(safe_copy my.cnf /etc/mysql/my.cnf)" == "True" ]]; then +if [[ "$(safe_copy my.cnf /etc/mysql/my.cnf | tail -n1)" == "True" ]]; then sudo service mysql restart fi diff --git a/data/hooks/conf_regen/37-avahi-daemon b/data/hooks/conf_regen/37-avahi-daemon index 31306de53..02bca0386 100644 --- a/data/hooks/conf_regen/37-avahi-daemon +++ b/data/hooks/conf_regen/37-avahi-daemon @@ -15,6 +15,6 @@ function safe_copy () { cd /usr/share/yunohost/templates/avahi-daemon -if [[ "$(safe_copy avahi-daemon.conf /etc/avahi/avahi-daemon.conf)" == "True" ]]; then +if [[ "$(safe_copy avahi-daemon.conf /etc/avahi/avahi-daemon.conf | tail -n1)" == "True" ]]; then sudo service avahi-daemon restart fi diff --git a/data/hooks/conf_regen/40-glances b/data/hooks/conf_regen/40-glances index 9f7c9c5c8..d9bb8aa20 100644 --- a/data/hooks/conf_regen/40-glances +++ b/data/hooks/conf_regen/40-glances @@ -15,6 +15,6 @@ function safe_copy () { cd /usr/share/yunohost/templates/glances -if [[ "$(safe_copy glances.default /etc/default/glances)" == "True" ]]; then +if [[ "$(safe_copy glances.default /etc/default/glances | tail -n1)" == "True" ]]; then sudo service glances restart fi diff --git a/data/hooks/conf_regen/46-nsswitch b/data/hooks/conf_regen/46-nsswitch index 73535eeda..90d05e087 100644 --- a/data/hooks/conf_regen/46-nsswitch +++ b/data/hooks/conf_regen/46-nsswitch @@ -15,6 +15,6 @@ function safe_copy () { cd /usr/share/yunohost/templates/nsswitch -if [[ "$(safe_copy nsswitch.conf /etc/nsswitch.conf)" == "True" ]]; then +if [[ "$(safe_copy nsswitch.conf /etc/nsswitch.conf | tail -n1)" == "True" ]]; then sudo service nscd restart fi diff --git a/data/hooks/conf_regen/49-udisks-glue b/data/hooks/conf_regen/49-udisks-glue index 85de9182d..03dbc75d4 100644 --- a/data/hooks/conf_regen/49-udisks-glue +++ b/data/hooks/conf_regen/49-udisks-glue @@ -15,6 +15,6 @@ function safe_copy () { cd /usr/share/yunohost/templates/udisks-glue -if [[ "$(safe_copy udisks-glue.conf /etc/udisks-glue.conf)" == "True" ]]; then +if [[ "$(safe_copy udisks-glue.conf /etc/udisks-glue.conf | tail -n1)" == "True" ]]; then sudo service udisks-glue restart fi diff --git a/data/hooks/conf_regen/52-fail2ban b/data/hooks/conf_regen/52-fail2ban index 9c609c74a..b9d76afeb 100644 --- a/data/hooks/conf_regen/52-fail2ban +++ b/data/hooks/conf_regen/52-fail2ban @@ -24,6 +24,6 @@ version=$(sed 's/\..*//' /etc/debian_version) && sudo cp jail-jessie.conf jail.conf \ || sudo cp jail-wheezy.conf jail.conf -if [[ $(safe_copy jail.conf /etc/fail2ban/jail.conf) == "True" ]]; then +if [[ $(safe_copy jail.conf /etc/fail2ban/jail.conf | tail -n1) == "True" ]]; then sudo service fail2ban restart fi From cdb862dfaf5df80301cc274e5af7dfab5c02cf17 Mon Sep 17 00:00:00 2001 From: kload Date: Sat, 31 Oct 2015 19:21:25 +0100 Subject: [PATCH 040/170] [fix] Do not fail if firewall is stopped --- debian/postinst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/debian/postinst b/debian/postinst index de5febe92..3cefc3c15 100644 --- a/debian/postinst +++ b/debian/postinst @@ -15,7 +15,8 @@ do_configure() { # restart yunohost-firewall if it's running service yunohost-firewall status > /dev/null \ - && service yunohost-firewall restart + && service yunohost-firewall restart \ + || echo "Firewall stopped" fi } From 12127d1a7119e5d603961b994e5d00379df1ff8c Mon Sep 17 00:00:00 2001 From: kload Date: Sat, 31 Oct 2015 19:21:48 +0100 Subject: [PATCH 041/170] [fix] Use a recent moulinette version --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 1dacf81c9..5305160e8 100644 --- a/debian/control +++ b/debian/control @@ -10,7 +10,7 @@ Homepage: https://yunohost.org/ Package: yunohost Architecture: all Depends: ${python:Depends}, ${misc:Depends}, - moulinette (>= 2.2.1), + moulinette (> 2.2.1.1), python-psutil, python-requests, glances, From 0bbff4a4167db7dd2bfae91aa3701f6a33b54120 Mon Sep 17 00:00:00 2001 From: kload Date: Sat, 31 Oct 2015 19:38:08 +0100 Subject: [PATCH 042/170] [fix] Disable/stop previous email services only if we are forcing conf regeneration --- data/hooks/conf_regen/22-email-legacy | 42 ++++++++++++++++----------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/data/hooks/conf_regen/22-email-legacy b/data/hooks/conf_regen/22-email-legacy index 0b11ae10c..59cf75b1c 100644 --- a/data/hooks/conf_regen/22-email-legacy +++ b/data/hooks/conf_regen/22-email-legacy @@ -1,25 +1,33 @@ #!/bin/bash set -e -# Add new email services -sudo yunohost service add rspamd -l /var/log/mail.log \ - || echo "Rspamd is already listed in services" +# Execute this hook only if we force the configuration regeneration +if [[ "$1" == "True" ]]; then -sudo yunohost service add rmilter -l /var/log/mail.log \ - || echo "Rspamd is already listed in services" + # Add new email services + sudo yunohost service add rspamd -l /var/log/mail.log \ + || echo "Rspamd is already listed in services" -sudo yunohost service add memcached \ - || echo "Memcached is already listed in services" + sudo yunohost service add rmilter -l /var/log/mail.log \ + || echo "Rspamd is already listed in services" -# Remove previous email services -sudo yunohost service remove spamassassin \ - || echo "Spamassassin is already removed" \ - && sudo systemctl disable spamassassin || true + sudo yunohost service add memcached \ + || echo "Memcached is already listed in services" -sudo yunohost service remove amavis \ - || echo "Amavis is already removed" \ - && sudo systemctl disable amavis || true + # Remove previous email services + sudo yunohost service remove spamassassin \ + || echo "Spamassassin is already removed" \ + && sudo systemctl disable spamassassin || true -sudo yunohost service remove postgrey \ - || echo "Postgrey is already removed" \ - && sudo systemctl disable postgrey || true + sudo yunohost service remove amavis \ + || echo "Amavis is already removed" \ + && sudo systemctl disable amavis || true + + sudo yunohost service remove postgrey \ + || echo "Postgrey is already removed" \ + && sudo systemctl disable postgrey || true + + systemctl stop spamassassin + systemctl stop amavis + systemctl stop postgrey +fi From 4e72595aaa64f1372532203ad9728d3c7c63a220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sat, 31 Oct 2015 21:06:24 +0100 Subject: [PATCH 043/170] [fix] Open port 1900 when enabling UPnP (fixes #30) --- data/actionsmap/yunohost.yml | 4 ++++ src/yunohost/firewall.py | 20 +++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 053aee467..c104f0005 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1020,6 +1020,10 @@ firewall: configuration: authenticate: false lock: false + arguments: + --skip-upnp: + help: Do not refresh port forwarding using UPnP + action: store_true ### firewall_allow() allow: diff --git a/src/yunohost/firewall.py b/src/yunohost/firewall.py index 59c6a1a03..7cfb15091 100644 --- a/src/yunohost/firewall.py +++ b/src/yunohost/firewall.py @@ -112,7 +112,7 @@ def firewall_disallow(protocol, port, ipv4_only=False, ipv6_only=False, firewall = firewall_list(raw=True) # Validate port - if ':' not in port: + if not isinstance(port, int) and ':' not in port: port = int(port) # Validate protocols @@ -188,10 +188,12 @@ def firewall_list(raw=False, by_ip_version=False, list_forwarded=False): return ret -def firewall_reload(): +def firewall_reload(skip_upnp=False): """ Reload all firewall rules + Keyword arguments: + skip_upnp -- Do not refresh port forwarding using UPnP """ from yunohost.hook import hook_callback @@ -210,7 +212,7 @@ def firewall_reload(): # Retrieve firewall rules and UPnP status firewall = firewall_list(raw=True) - upnp = firewall_upnp()['enabled'] + upnp = firewall_upnp()['enabled'] if not skip_upnp else False # IPv4 try: @@ -324,6 +326,11 @@ def firewall_upnp(action='status', no_refresh=False): with open(upnp_cron_job, 'w+') as f: f.write('*/50 * * * * root ' '/usr/bin/yunohost firewall upnp status >>/dev/null\n') + # Open port 1900 to receive discovery message + if 1900 not in firewall['ipv4']['UDP']: + firewall_allow('UDP', 1900, no_upnp=True, no_reload=True) + if not enabled: + firewall_reload(skip_upnp=True) enabled = True elif action == 'disable' or (not enabled and action == 'status'): try: @@ -376,6 +383,7 @@ def firewall_upnp(action='status', no_refresh=False): enabled = False if enabled != firewall['uPnP']['enabled']: + firewall = firewall_list(raw=True) firewall['uPnP']['enabled'] = enabled # Make a backup and update firewall file @@ -393,6 +401,12 @@ def firewall_upnp(action='status', no_refresh=False): elif action != 'disable' and not enabled: firewall_upnp('disable', no_refresh=True) + if not enabled and (action == 'enable' or 1900 in firewall['ipv4']['UDP']): + # Close unused port 1900 + firewall_disallow('UDP', 1900, no_reload=True) + if not no_refresh: + firewall_reload(skip_upnp=True) + if action == 'enable' and not enabled: raise MoulinetteError(errno.ENXIO, m18n.n('upnp_port_open_failed')) return { 'enabled': enabled } From c3d379be5ba536e950984f45e0a221e844d46e30 Mon Sep 17 00:00:00 2001 From: kload Date: Sat, 31 Oct 2015 21:43:25 +0100 Subject: [PATCH 044/170] Revert 95cbff27a9f27ce7d0a661c8548c2a408f96e5a2 --- data/actionsmap/yunohost.yml | 3 --- src/yunohost/firewall.py | 4 ---- 2 files changed, 7 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index c104f0005..bf6291614 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1017,9 +1017,6 @@ firewall: reload: action_help: Reload all firewall rules api: PUT /firewall - configuration: - authenticate: false - lock: false arguments: --skip-upnp: help: Do not refresh port forwarding using UPnP diff --git a/src/yunohost/firewall.py b/src/yunohost/firewall.py index 7cfb15091..65bf89fe5 100644 --- a/src/yunohost/firewall.py +++ b/src/yunohost/firewall.py @@ -201,10 +201,6 @@ def firewall_reload(skip_upnp=False): reloaded = False errors = False - # Do not continue if YunoHost is not configured - try: open('/etc/yunohost/installed') - except IOError: return True - # Check if SSH port is allowed ssh_port = _get_ssh_port() if ssh_port not in firewall_list()['opened_ports']: From 08621f8b323bd1132a29efd1dc28e3284ea358c8 Mon Sep 17 00:00:00 2001 From: kload Date: Sun, 1 Nov 2015 10:24:33 +0100 Subject: [PATCH 045/170] [fix] Remove spamassassin CRON as well --- data/hooks/conf_regen/22-email-legacy | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/data/hooks/conf_regen/22-email-legacy b/data/hooks/conf_regen/22-email-legacy index 59cf75b1c..7c7edf03d 100644 --- a/data/hooks/conf_regen/22-email-legacy +++ b/data/hooks/conf_regen/22-email-legacy @@ -19,6 +19,8 @@ if [[ "$1" == "True" ]]; then || echo "Spamassassin is already removed" \ && sudo systemctl disable spamassassin || true + sudo rm -f etc/cron.daily/spamassassin + sudo yunohost service remove amavis \ || echo "Amavis is already removed" \ && sudo systemctl disable amavis || true @@ -27,7 +29,7 @@ if [[ "$1" == "True" ]]; then || echo "Postgrey is already removed" \ && sudo systemctl disable postgrey || true - systemctl stop spamassassin - systemctl stop amavis - systemctl stop postgrey + sudo systemctl stop spamassassin + sudo systemctl stop amavis + sudo systemctl stop postgrey fi From 21b67ee7d00c3543c088150e93511a3513fcca90 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 1 Nov 2015 13:00:06 +0100 Subject: [PATCH 046/170] [mod] remove useless imports --- src/yunohost/dyndns.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index b4eb376dd..acc1df54a 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -24,7 +24,6 @@ Subscribe and Update DynDNS Hosts """ import os -import sys import requests import re import json From 2d88ab9a248e15c88dae64ecff976c5481ee2d2a Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 1 Nov 2015 13:00:15 +0100 Subject: [PATCH 047/170] [fix] unreferenced exceptions --- src/yunohost/dyndns.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index acc1df54a..e1d0afd40 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -96,7 +96,7 @@ def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None # Send subscription try: r = requests.post('https://%s/key/%s' % (subscribe_host, base64.b64encode(key)), data={ 'subdomain': domain }) - except ConnectionError: + except requests.ConnectionError: raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection')) if r.status_code != 201: try: error = json.loads(r.text)['error'] @@ -129,7 +129,7 @@ def dyndns_update(dyn_host="dynhost.yunohost.org", domain=None, key=None, ip=Non if ip is None: try: new_ip = requests.get('http://ip.yunohost.org').text - except ConnectionError: + except requests.ConnectionError: raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection')) else: new_ip = ip From 15c59e6d672023c8e3411fb83a08a667e12d8b2e Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 2 Nov 2015 13:54:34 +0100 Subject: [PATCH 048/170] Avoid self corruption of mysql on power failure By default, mysql creates its mysql database in MyISAN mode which self corrupt itself way too frequently on the internet cube configuration. InnoDB seems fine in comparison. Also MyISAN is bullcrap. --- data/templates/mysql/my.cnf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/templates/mysql/my.cnf b/data/templates/mysql/my.cnf index 2d4e1df2a..cf9e6ddd7 100644 --- a/data/templates/mysql/my.cnf +++ b/data/templates/mysql/my.cnf @@ -36,6 +36,9 @@ read_rnd_buffer_size = 256K net_buffer_length = 2K thread_stack = 128K +# to avoid corruption on powerfailure +default-storage-engine=innodb + # Don't listen on a TCP/IP port at all. This can be a security enhancement, # if all processes that need to connect to mysqld run on the same host. # All interaction with mysqld must be made via Unix sockets or named pipes. From 954ac318d80d08da3ff0d2d4825f48fd269b9bf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Mon, 2 Nov 2015 23:17:17 +0100 Subject: [PATCH 049/170] [fix] Set proper version of moulinette dependency --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 5305160e8..5da5cf8c6 100644 --- a/debian/control +++ b/debian/control @@ -10,7 +10,7 @@ Homepage: https://yunohost.org/ Package: yunohost Architecture: all Depends: ${python:Depends}, ${misc:Depends}, - moulinette (> 2.2.1.1), + moulinette (>= 2.3), python-psutil, python-requests, glances, From acd1d051c2c58a0325e0f0d201390843c3809ce8 Mon Sep 17 00:00:00 2001 From: kload Date: Tue, 3 Nov 2015 12:14:59 +0000 Subject: [PATCH 050/170] Update changelog for 2.3.0 release --- debian/changelog | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/debian/changelog b/debian/changelog index c8d5a231a..d4538a00e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,45 @@ +yunohost (2.3.0) testing; urgency=low + + [ breaking changes ] + * Merge all packages into one + * Wheezy compatibility drop + + [ features ] + * Implement a regenconf command + * Implement local backup/restore functions + * Allow to filter which app to backup/restore + * Replace the email stack by Rspamd/Rmilter + * Create shallow clone to increase app installation time + * Add helper bash functions for apps developers + * Update app_info to show app installation status + * Implement an app_debug function + * IPv6 compatibility enhancement + + [ bugfixes ] + * Display YunoHost packages versions (fix #11) + * Allow empty app arguments in app_install + * Invalidate passwd at user creation/deletion (fix #70) + * Fix skipped_urls for each domain and #68 + * Correct logger object in backup_list (fix #75) + * 2nd installation of apps with a hooks directory + * Add netcat-openbsd dependency + * Ensure that arguments are passed to the hook as string + * Use SSL/TLS to fetch app list + * IPv6 record in DynDNS + * Use sudo to execute hook script + * Debian postinst script : only respond to configure + * Handle SSL generation better + * Ensure that the service yunohost-api is always running + * Sieve permission denied + * Do not enable yunohost-firewall service at install + * Open port 1900 when enabling UPnP (fixes #30) + + [ other ] + * Add AGPL license + * French translation using Weblate + + -- kload Tue, 03 Nov 2015 11:55:19 +0000 + moulinette-yunohost (2.3.1) testing; urgency=low [ Julien Malik ] From 8ebfdd217c88c7cb4b0e7f4feb37bfd963757f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Thu, 12 Nov 2015 13:14:38 +0100 Subject: [PATCH 051/170] [enh] Add logrotate configuration --- debian/logrotate | 11 +++++++++++ debian/yunohost-api.init | 13 +++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 debian/logrotate diff --git a/debian/logrotate b/debian/logrotate new file mode 100644 index 000000000..0bc07ca59 --- /dev/null +++ b/debian/logrotate @@ -0,0 +1,11 @@ +/var/log/yunohost/*.log { + weekly + rotate 4 + delaycompress + compress + notifempty + missingok + postrotate + invoke-rc.d yunohost-api rotate >/dev/null 2>&1 + endscript +} diff --git a/debian/yunohost-api.init b/debian/yunohost-api.init index 0a27554d2..bd9f3a015 100755 --- a/debian/yunohost-api.init +++ b/debian/yunohost-api.init @@ -75,6 +75,14 @@ do_reload() { return 0 } +# +# Function called when rotating logs +# +do_rotate() { + start-stop-daemon --stop --signal USR1 --quiet --pidfile $PID --name $NAME + return 0 +} + case "$1" in start) [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" @@ -118,6 +126,11 @@ case "$1" in ;; esac ;; + rotate) + log_daemon_msg "Re-opening $DESC log files" "$NAME" + do_rotate + log_end_msg $? + ;; *) echo "Usage: $SCRIPTNAME {start|stop|status|restart|reload}" >&2 exit 3 From ce57a406c11e8fa0fe1e37521f09339a783e2d7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Thu, 12 Nov 2015 13:32:30 +0100 Subject: [PATCH 052/170] [fix] Enable yunohost-api systemd service manually --- debian/rules | 3 +++ 1 file changed, 3 insertions(+) diff --git a/debian/rules b/debian/rules index 311b79678..2208e0623 100755 --- a/debian/rules +++ b/debian/rules @@ -10,3 +10,6 @@ override_dh_installinit: dh_installinit --name=yunohost-api dh_installinit --name=yunohost-firewall --no-start + +override_dh_systemd_enable: + dh_systemd_enable --name=yunohost-api From 7435ba7cee9220770ca281ec44c8d7f98d1dedbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Thu, 12 Nov 2015 13:48:48 +0100 Subject: [PATCH 053/170] [enh] Allow to set default options for yunohost-api service --- debian/yunohost-api.default | 4 ++++ debian/yunohost-api.init | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 debian/yunohost-api.default diff --git a/debian/yunohost-api.default b/debian/yunohost-api.default new file mode 100644 index 000000000..b6a9e5a99 --- /dev/null +++ b/debian/yunohost-api.default @@ -0,0 +1,4 @@ +# Override yunohost-api options. +# Example to log debug: DAEMON_OPTS="--debug" +# +#DAEMON_OPTS="" diff --git a/debian/yunohost-api.init b/debian/yunohost-api.init index bd9f3a015..02a0560b7 100755 --- a/debian/yunohost-api.init +++ b/debian/yunohost-api.init @@ -21,6 +21,11 @@ PIDFILE=/var/run/$NAME.pid SCRIPTNAME=/etc/init.d/$NAME LOGFILE=/var/log/$NAME.log +# Include yunohost-api defaults if available +if [ -r /etc/default/yunohost-api ]; then + . /etc/default/yunohost-api +fi + # Exit if the package is not installed [ -x "$DAEMON" ] || exit 0 @@ -45,7 +50,7 @@ do_start() || return 1 start-stop-daemon --start --background --make-pidfile --quiet --no-close \ --pidfile $PIDFILE --exec $DAEMON -- \ - $DAEMON_ARGS >>$LOGFILE 2>&1 \ + $DAEMON_OPTS >>$LOGFILE 2>&1 \ || return 2 } From 71a21d71d2208ab71940aa529c4cf5c9b9ddcd2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Thu, 12 Nov 2015 17:14:03 +0100 Subject: [PATCH 054/170] [fix] Use default options in yunohost-api systemd service too --- debian/yunohost-api.service | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/debian/yunohost-api.service b/debian/yunohost-api.service index c7a913b61..4e71eadac 100644 --- a/debian/yunohost-api.service +++ b/debian/yunohost-api.service @@ -4,7 +4,9 @@ After=network.target [Service] Type=simple -ExecStart=/usr/bin/yunohost-api +Environment=DAEMON_OPTS= +EnvironmentFile=-/etc/default/yunohost-api +ExecStart=/usr/bin/yunohost-api $DAEMON_OPTS ExecReload=/bin/kill -HUP $MAINPID Restart=always RestartSec=1 From 4978e48c9dffa510634df5d6912c92a74159cc99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Thu, 12 Nov 2015 22:39:09 +0100 Subject: [PATCH 055/170] [enh] Make use of new logging facilities with the cli in the firewall --- bin/yunohost | 43 +++++++++++++++++++++------------------- src/yunohost/firewall.py | 40 ++++++++++++++++++------------------- 2 files changed, 42 insertions(+), 41 deletions(-) diff --git a/bin/yunohost b/bin/yunohost index 6800a4a9c..fd74b44f5 100755 --- a/bin/yunohost +++ b/bin/yunohost @@ -18,11 +18,12 @@ PRINT_PLAIN = False # Level for which loggers will log LOGGERS_LEVEL = 'INFO' +TTY_LOG_LEVEL = 'SUCCESS' # Handlers that will be used by loggers # - file: log to the file LOG_DIR/LOG_FILE -# - console: log to stderr -LOGGERS_HANDLERS = ['file'] +# - tty: log to current tty +LOGGERS_HANDLERS = ['file', 'tty'] # Directory and file to be used by logging LOG_DIR = '/var/log/yunohost' @@ -54,30 +55,30 @@ def _check_in_devel(): def _parse_argv(): """Parse additional arguments and return remaining ones""" + global USE_CACHE, PRINT_JSON, PRINT_PLAIN + global TTY_LOG_LEVEL, LOGGERS_LEVEL, LOGGERS_HANDLERS argv = list(sys.argv) argv.pop(0) if '--no-cache' in argv: - global USE_CACHE USE_CACHE = False argv.remove('--no-cache') if '--json' in argv: - global PRINT_JSON PRINT_JSON = True argv.remove('--json') if '--plain' in argv: - global PRINT_PLAIN PRINT_PLAIN = True argv.remove('--plain') if '--debug' in argv: - global LOGGERS_LEVEL - LOGGERS_LEVEL = 'DEBUG' + LOGGERS_LEVEL = TTY_LOG_LEVEL = 'DEBUG' argv.remove('--debug') if '--verbose' in argv: - global LOGGERS_HANDLERS - if 'console' not in LOGGERS_HANDLERS: - LOGGERS_HANDLERS.append('console') + TTY_LOG_LEVEL = 'INFO' argv.remove('--verbose') + if '--quiet' in argv: + if 'tty' in LOGGERS_HANDLERS: + LOGGERS_HANDLERS.remove('tty') + argv.remove('--quiet') return argv def _init_moulinette(): @@ -89,33 +90,35 @@ def _init_moulinette(): 'version': 1, 'disable_existing_loggers': True, 'formatters': { - 'simple': { - 'format': '%(relativeCreated)-5d %(levelname)-8s %(name)s - %(message)s' - }, 'precise': { - 'format': '%(asctime)-15s %(levelname)-8s %(name)s %(funcName)s - %(message)s' + 'format': '%(asctime)-15s %(levelname)-8s %(name)s %(funcName)s - %(fmessage)s' + }, + }, + 'filters': { + 'action': { + '()': 'moulinette.utils.log.ActionFilter', }, }, 'handlers': { - 'console': { - 'class': 'logging.StreamHandler', - 'formatter': 'simple', - 'stream': 'ext://sys.stderr', + 'tty': { + 'level': TTY_LOG_LEVEL, + 'class': 'moulinette.interfaces.cli.TTYHandler', }, 'file': { 'class': 'logging.FileHandler', 'formatter': 'precise', 'filename': '%s/%s' % (LOG_DIR, LOG_FILE), + 'filters': ['action'], }, }, 'loggers': { 'moulinette': { - 'level': LOGGERS_LEVEL, 'handlers': LOGGERS_HANDLERS, + 'level': LOGGERS_LEVEL, }, 'yunohost': { - 'level': LOGGERS_LEVEL, 'handlers': LOGGERS_HANDLERS, + 'level': LOGGERS_LEVEL, }, }, } diff --git a/src/yunohost/firewall.py b/src/yunohost/firewall.py index 65bf89fe5..678ff7db5 100644 --- a/src/yunohost/firewall.py +++ b/src/yunohost/firewall.py @@ -83,8 +83,7 @@ def firewall_allow(protocol, port, ipv4_only=False, ipv6_only=False, firewall[i][p].append(port) else: ipv = "IPv%s" % i[3] - msignals.display(m18n.n('port_already_opened', port, ipv), - 'warning') + logger.warning(m18n.n('port_already_opened', port, ipv)) # Add port forwarding with UPnP if not no_upnp and port not in firewall['uPnP'][p]: firewall['uPnP'][p].append(port) @@ -141,8 +140,7 @@ def firewall_disallow(protocol, port, ipv4_only=False, ipv6_only=False, firewall[i][p].remove(port) else: ipv = "IPv%s" % i[3] - msignals.display(m18n.n('port_already_closed', port, ipv), - 'warning') + logger.warning(m18n.n('port_already_closed', port, ipv)) # Remove port forwarding with UPnP if upnp and port in firewall['uPnP'][p]: firewall['uPnP'][p].remove(port) @@ -214,9 +212,9 @@ def firewall_reload(skip_upnp=False): try: process.check_output("iptables -L") except process.CalledProcessError as e: - logger.info('iptables seems to be not available, it outputs:\n%s', - prependlines(e.output.rstrip(), '> ')) - msignals.display(m18n.n('iptables_unavailable'), 'info') + logger.debug('iptables seems to be not available, it outputs:\n%s', + prependlines(e.output.rstrip(), '> ')) + logger.warning(m18n.n('iptables_unavailable')) else: rules = [ "iptables -F", @@ -243,9 +241,9 @@ def firewall_reload(skip_upnp=False): try: process.check_output("ip6tables -L") except process.CalledProcessError as e: - logger.info('ip6tables seems to be not available, it outputs:\n%s', - prependlines(e.output.rstrip(), '> ')) - msignals.display(m18n.n('ip6tables_unavailable'), 'info') + logger.debug('ip6tables seems to be not available, it outputs:\n%s', + prependlines(e.output.rstrip(), '> ')) + logger.warning(m18n.n('ip6tables_unavailable')) else: rules = [ "ip6tables -F", @@ -282,9 +280,9 @@ def firewall_reload(skip_upnp=False): os.system("service fail2ban restart") if errors: - msignals.display(m18n.n('firewall_rules_cmd_failed'), 'warning') + logger.warning(m18n.n('firewall_rules_cmd_failed')) else: - msignals.display(m18n.n('firewall_reloaded'), 'success') + logger.success(m18n.n('firewall_reloaded')) return firewall_list() @@ -306,7 +304,7 @@ def firewall_upnp(action='status', no_refresh=False): # Compatibility with previous version if action == 'reload': - logger.warning("'reload' action is deprecated and will be removed") + logger.info("'reload' action is deprecated and will be removed") try: # Remove old cron job os.remove('/etc/cron.d/yunohost-firewall') @@ -349,14 +347,14 @@ def firewall_upnp(action='status', no_refresh=False): nb_dev = upnpc.discover() logger.debug('found %d UPnP device(s)', int(nb_dev)) if nb_dev < 1: - msignals.display(m18n.n('upnp_dev_not_found'), 'error') + logger.error(m18n.n('upnp_dev_not_found')) enabled = False else: try: # Select UPnP device upnpc.selectigd() except: - logger.exception('unable to select UPnP device') + logger.info('unable to select UPnP device', exc_info=1) enabled = False else: # Iterate over ports @@ -374,8 +372,8 @@ def firewall_upnp(action='status', no_refresh=False): upnpc.addportmapping(port, protocol, upnpc.lanaddr, port, 'yunohost firewall: port %d' % port, '') except: - logger.exception('unable to add port %d using UPnP', - port) + logger.info('unable to add port %d using UPnP', + port, exc_info=1) enabled = False if enabled != firewall['uPnP']['enabled']: @@ -390,9 +388,9 @@ def firewall_upnp(action='status', no_refresh=False): if not no_refresh: # Display success message if needed if action == 'enable' and enabled: - msignals.display(m18n.n('upnp_enabled'), 'success') + logger.success(m18n.n('upnp_enabled')) elif action == 'disable' and not enabled: - msignals.display(m18n.n('upnp_disabled'), 'success') + logger.success(m18n.n('upnp_disabled')) # Make sure to disable UPnP elif action != 'disable' and not enabled: firewall_upnp('disable', no_refresh=True) @@ -455,6 +453,6 @@ def _update_firewall_file(rules): def _on_rule_command_error(returncode, cmd, output): """Callback for rules commands error""" # Log error and continue commands execution - logger.error('"%s" returned non-zero exit status %d:\n%s', - cmd, returncode, prependlines(output.rstrip(), '> ')) + logger.info('"%s" returned non-zero exit status %d:\n%s', + cmd, returncode, prependlines(output.rstrip(), '> ')) return True From 7b88f95b1c379e45959a5b5334b7e976101b9aa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Fri, 13 Nov 2015 11:58:58 +0100 Subject: [PATCH 056/170] [enh] Refactor bin/yunohost to follow moulinette changes and add help --- bin/yunohost | 106 ++++++++++++++++++++++++++++----------------------- 1 file changed, 58 insertions(+), 48 deletions(-) diff --git a/bin/yunohost b/bin/yunohost index fd74b44f5..0474df058 100755 --- a/bin/yunohost +++ b/bin/yunohost @@ -10,11 +10,10 @@ IN_DEVEL = False # Either cache has to be used inside the moulinette or not USE_CACHE = True -# Either the output has to be encoded as a JSON encoded string or not -PRINT_JSON = False - -# Either the output has to printed for scripting usage or not -PRINT_PLAIN = False +# Output result in another format. Possible values are: +# - json: return a JSON encoded string +# - plain: return a script-readable output +OUTPUT_AS = None # Level for which loggers will log LOGGERS_LEVEL = 'INFO' @@ -29,6 +28,15 @@ LOGGERS_HANDLERS = ['file', 'tty'] LOG_DIR = '/var/log/yunohost' LOG_FILE = 'yunohost-cli.log' +# Check and load - as needed - development environment +if not __file__.startswith('/usr/'): + IN_DEVEL = True +if IN_DEVEL: + basedir = os.path.abspath('%s/../' % os.path.dirname(__file__)) + if os.path.isdir(os.path.join(basedir, 'moulinette')): + sys.path.insert(0, basedir) + LOG_DIR = os.path.join(basedir, 'log') + # Initialization & helpers functions ----------------------------------- @@ -41,50 +49,52 @@ def _die(message, title='Error:'): print('%s %s' % (colorize(title, 'red'), message)) sys.exit(1) -def _check_in_devel(): - """Check and load if needed development environment""" - global IN_DEVEL, LOG_DIR - basedir = os.path.abspath('%s/../' % os.path.dirname(__file__)) - if os.path.isdir('%s/moulinette' % basedir) and not IN_DEVEL: - # Add base directory to python path - sys.path.insert(0, basedir) +def _parse_cli_args(): + """Parse additional arguments for the cli""" + import argparse - # Update global variables - IN_DEVEL = True - LOG_DIR = '%s/log' % basedir + parser = argparse.ArgumentParser(add_help=False) + parser.add_argument('--no-cache', + action='store_false', default=USE_CACHE, dest='use_cache', + help="Don't use actions map cache", + ) + parser.add_argument('--output-as', + choices=['json', 'plain'], default=OUTPUT_AS, + help="Output result in another format", + ) + parser.add_argument('--debug', + action='store_true', default=False, + help="Log and print debug messages", + ) + parser.add_argument('--verbose', + action='store_true', default=False, + help="Be more verbose in the output", + ) + parser.add_argument('--quiet', + action='store_true', default=False, + help="Don't produce any output", + ) -def _parse_argv(): - """Parse additional arguments and return remaining ones""" - global USE_CACHE, PRINT_JSON, PRINT_PLAIN - global TTY_LOG_LEVEL, LOGGERS_LEVEL, LOGGERS_HANDLERS - argv = list(sys.argv) - argv.pop(0) + return (parser,) + parser.parse_known_args() - if '--no-cache' in argv: - USE_CACHE = False - argv.remove('--no-cache') - if '--json' in argv: - PRINT_JSON = True - argv.remove('--json') - if '--plain' in argv: - PRINT_PLAIN = True - argv.remove('--plain') - if '--debug' in argv: - LOGGERS_LEVEL = TTY_LOG_LEVEL = 'DEBUG' - argv.remove('--debug') - if '--verbose' in argv: - TTY_LOG_LEVEL = 'INFO' - argv.remove('--verbose') - if '--quiet' in argv: - if 'tty' in LOGGERS_HANDLERS: - LOGGERS_HANDLERS.remove('tty') - argv.remove('--quiet') - return argv - -def _init_moulinette(): +def _init_moulinette(debug=False, verbose=False, quiet=False): """Configure logging and initialize the moulinette""" from moulinette import init + # Define loggers handlers + global LOGGERS_HANDLERS + if quiet and 'tty' in LOGGERS_HANDLERS: + LOGGERS_HANDLERS.remove('tty') + elif verbose and 'tty' not in LOGGERS_HANDLERS: + LOGGERS_HANDLERS.append('tty') + + # Define loggers level + global LOGGERS_LEVEL, TTY_LOG_LEVEL + if verbose: + TTY_LOG_LEVEL = 'INFO' + if debug: + LOGGERS_LEVEL = TTY_LOG_LEVEL = 'DEBUG' + # Custom logging configuration logging = { 'version': 1, @@ -147,9 +157,8 @@ def _retrieve_namespaces(): # Main action ---------------------------------------------------------- if __name__ == '__main__': - _check_in_devel() - args = _parse_argv() - _init_moulinette() + parser, opts, args = _parse_cli_args() + _init_moulinette(opts.debug, opts.verbose, opts.quiet) # Check that YunoHost is installed if not os.path.isfile('/etc/yunohost/installed') and \ @@ -166,6 +175,7 @@ if __name__ == '__main__': # Execute the action from moulinette import cli - ret = cli(_retrieve_namespaces(), args, use_cache=USE_CACHE, - print_json=PRINT_JSON, print_plain=PRINT_PLAIN) + ret = cli(_retrieve_namespaces(), args, + use_cache=opts.use_cache, output_as=opts.output_as, + parser_kwargs={'top_parser': parser}) sys.exit(ret) From 2ce41e0fa8654a8e92a5acffe7d025769d8da2f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Fri, 13 Nov 2015 13:21:30 +0100 Subject: [PATCH 057/170] [enh] Add bash completion for bin/yunohost --- data/bash-completion.d/yunohost | 12 ++++++++++++ debian/install | 1 + 2 files changed, 13 insertions(+) create mode 100644 data/bash-completion.d/yunohost diff --git a/data/bash-completion.d/yunohost b/data/bash-completion.d/yunohost new file mode 100644 index 000000000..106f8fbdf --- /dev/null +++ b/data/bash-completion.d/yunohost @@ -0,0 +1,12 @@ +# +# Bash completion for yunohost +# + +_python_argcomplete() { + local IFS=' ' + COMPREPLY=( $(IFS="$IFS" COMP_LINE="$COMP_LINE" COMP_POINT="$COMP_POINT" _ARGCOMPLETE_COMP_WORDBREAKS="$COMP_WORDBREAKS" _ARGCOMPLETE=1 "$1" 8>&1 9>&2 1>/dev/null 2>/dev/null) ) + if [[ $? != 0 ]]; then + unset COMPREPLY + fi +} +complete -o nospace -o default -F _python_argcomplete "yunohost" diff --git a/debian/install b/debian/install index ca458b688..3b8407635 100644 --- a/debian/install +++ b/debian/install @@ -1,4 +1,5 @@ bin/* /usr/bin/ +data/bash-completion.d/yunohost /etc/bash-completion.d/ data/actionsmap/* /usr/share/moulinette/actionsmap/ data/hooks/* /usr/share/yunohost/hooks/ data/other/* /usr/share/yunohost/yunohost-config/moulinette/ From 2a6d7b7c4a87cbd321cc99f696857d1e2457d67b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Fri, 13 Nov 2015 13:40:53 +0100 Subject: [PATCH 058/170] [fix] Create home directory during login (fixbug #80) --- debian/conf/pam/mkhomedir | 6 ++++++ debian/install | 1 + debian/postinst | 3 +++ 3 files changed, 10 insertions(+) create mode 100644 debian/conf/pam/mkhomedir diff --git a/debian/conf/pam/mkhomedir b/debian/conf/pam/mkhomedir new file mode 100644 index 000000000..eedc8b745 --- /dev/null +++ b/debian/conf/pam/mkhomedir @@ -0,0 +1,6 @@ +Name: Create home directory during login +Default: yes +Priority: 900 +Session-Type: Additional +Session: + required pam_mkhomedir.so umask=0022 skel=/etc/skel diff --git a/debian/install b/debian/install index ca458b688..f0dc0f633 100644 --- a/debian/install +++ b/debian/install @@ -4,6 +4,7 @@ data/hooks/* /usr/share/yunohost/hooks/ data/other/* /usr/share/yunohost/yunohost-config/moulinette/ data/templates/* /usr/share/yunohost/templates/ data/apps/* /usr/share/yunohost/apps/ +debian/conf/pam/* /usr/share/pam-configs/ lib/metronome/modules/* /usr/lib/metronome/modules/ locales/* /usr/lib/moulinette/yunohost/locales/ src/yunohost/*.py /usr/lib/moulinette/yunohost/ diff --git a/debian/postinst b/debian/postinst index 3cefc3c15..1831ccaaa 100644 --- a/debian/postinst +++ b/debian/postinst @@ -18,6 +18,9 @@ do_configure() { && service yunohost-firewall restart \ || echo "Firewall stopped" fi + + # update PAM configs + pam-auth-update --package } # summary of how this script can be called: From f0ab6776d258caa81006c9c7be2c6bb6ab5fe87d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Fri, 13 Nov 2015 14:15:22 +0100 Subject: [PATCH 059/170] [fix] Keep compat with deprecated --plain and --json in the cli --- bin/yunohost | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/bin/yunohost b/bin/yunohost index 0474df058..7f1e34dee 100755 --- a/bin/yunohost +++ b/bin/yunohost @@ -74,8 +74,23 @@ def _parse_cli_args(): action='store_true', default=False, help="Don't produce any output", ) + # deprecated arguments + parser.add_argument('--plain', + action='store_true', default=False, help=argparse.SUPPRESS + ) + parser.add_argument('--json', + action='store_true', default=False, help=argparse.SUPPRESS + ) - return (parser,) + parser.parse_known_args() + opts, args = parser.parse_known_args() + + # output compatibility + if opts.plain: + opts.output_as = 'plain' + elif opts.json: + opts.output_as = 'json' + + return (parser, opts, args) def _init_moulinette(debug=False, verbose=False, quiet=False): """Configure logging and initialize the moulinette""" From 1e447d84ce4714b3e8abddbb9317187ace157b4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Fri, 13 Nov 2015 14:20:35 +0100 Subject: [PATCH 060/170] [fix] Correct bash-completion installation directory --- debian/install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/install b/debian/install index 468a89db5..57a6830e1 100644 --- a/debian/install +++ b/debian/install @@ -1,5 +1,5 @@ bin/* /usr/bin/ -data/bash-completion.d/yunohost /etc/bash-completion.d/ +data/bash-completion.d/yunohost /etc/bash_completion.d/ data/actionsmap/* /usr/share/moulinette/actionsmap/ data/hooks/* /usr/share/yunohost/hooks/ data/other/* /usr/share/yunohost/yunohost-config/moulinette/ From fe6a01f2066ccbb74a5838e6f39abdf227d4b588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Fri, 13 Nov 2015 21:45:55 +0100 Subject: [PATCH 061/170] [enh] Define root logger and clean some globals in bin/yunohost --- bin/yunohost | 51 ++++++++++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/bin/yunohost b/bin/yunohost index 7f1e34dee..fe9bd533e 100755 --- a/bin/yunohost +++ b/bin/yunohost @@ -7,14 +7,6 @@ import os # Either we are in a development environment or not IN_DEVEL = False -# Either cache has to be used inside the moulinette or not -USE_CACHE = True - -# Output result in another format. Possible values are: -# - json: return a JSON encoded string -# - plain: return a script-readable output -OUTPUT_AS = None - # Level for which loggers will log LOGGERS_LEVEL = 'INFO' TTY_LOG_LEVEL = 'SUCCESS' @@ -55,11 +47,11 @@ def _parse_cli_args(): parser = argparse.ArgumentParser(add_help=False) parser.add_argument('--no-cache', - action='store_false', default=USE_CACHE, dest='use_cache', + action='store_false', default=True, dest='use_cache', help="Don't use actions map cache", ) parser.add_argument('--output-as', - choices=['json', 'plain'], default=OUTPUT_AS, + choices=['json', 'plain'], default=None, help="Output result in another format", ) parser.add_argument('--debug', @@ -97,18 +89,20 @@ def _init_moulinette(debug=False, verbose=False, quiet=False): from moulinette import init # Define loggers handlers - global LOGGERS_HANDLERS - if quiet and 'tty' in LOGGERS_HANDLERS: - LOGGERS_HANDLERS.remove('tty') - elif verbose and 'tty' not in LOGGERS_HANDLERS: - LOGGERS_HANDLERS.append('tty') + handlers = set(LOGGERS_HANDLERS) + if quiet and 'tty' in handlers: + handlers.remove('tty') + elif verbose and 'tty' not in handlers: + handlers.append('tty') + root_handlers = handlers - set(['tty']) # Define loggers level - global LOGGERS_LEVEL, TTY_LOG_LEVEL + level = LOGGERS_LEVEL + tty_level = TTY_LOG_LEVEL if verbose: - TTY_LOG_LEVEL = 'INFO' + tty_level = 'INFO' if debug: - LOGGERS_LEVEL = TTY_LOG_LEVEL = 'DEBUG' + tty_level = level = 'DEBUG' # Custom logging configuration logging = { @@ -126,7 +120,7 @@ def _init_moulinette(debug=False, verbose=False, quiet=False): }, 'handlers': { 'tty': { - 'level': TTY_LOG_LEVEL, + 'level': tty_level, 'class': 'moulinette.interfaces.cli.TTYHandler', }, 'file': { @@ -138,14 +132,20 @@ def _init_moulinette(debug=False, verbose=False, quiet=False): }, 'loggers': { 'moulinette': { - 'handlers': LOGGERS_HANDLERS, - 'level': LOGGERS_LEVEL, + 'level': level, + 'handlers': handlers, + 'propagate': False, }, 'yunohost': { - 'handlers': LOGGERS_HANDLERS, - 'level': LOGGERS_LEVEL, + 'level': level, + 'handlers': handlers, + 'propagate': False, }, }, + 'root': { + 'level': level, + 'handlers': root_handlers, + }, } # Create log directory @@ -191,6 +191,7 @@ if __name__ == '__main__': # Execute the action from moulinette import cli ret = cli(_retrieve_namespaces(), args, - use_cache=opts.use_cache, output_as=opts.output_as, - parser_kwargs={'top_parser': parser}) + use_cache=opts.use_cache, output_as=opts.output_as, + parser_kwargs={'top_parser': parser} + ) sys.exit(ret) From 0a78238041ffe6eef65c6dc0ee29b97f01943045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Fri, 13 Nov 2015 21:46:54 +0100 Subject: [PATCH 062/170] [enh] Refactor bin/yunohost-api to follow moulinette changes and add help --- bin/yunohost-api | 163 +++++++++++++++++++++++++++++------------------ 1 file changed, 101 insertions(+), 62 deletions(-) diff --git a/bin/yunohost-api b/bin/yunohost-api index 84f38c661..e0de12b36 100755 --- a/bin/yunohost-api +++ b/bin/yunohost-api @@ -7,24 +7,32 @@ import os.path # Either we are in a development environment or not IN_DEVEL = False -# Either cache has to be used inside the moulinette or not -USE_CACHE = True - -# Either WebSocket has to be installed by the moulinette or not -USE_WEBSOCKET = True +# Default server configuration +DEFAULT_HOST = 'localhost' +DEFAULT_PORT = 6787 # Level for which loggers will log LOGGERS_LEVEL = 'INFO' # Handlers that will be used by loggers # - file: log to the file LOG_DIR/LOG_FILE +# - api: serve logs through the api # - console: log to stderr -LOGGERS_HANDLERS = ['file'] +LOGGERS_HANDLERS = ['file', 'api'] # Directory and file to be used by logging LOG_DIR = '/var/log/yunohost' LOG_FILE = 'yunohost-api.log' +# Check and load - as needed - development environment +if not __file__.startswith('/usr/'): + IN_DEVEL = True +if IN_DEVEL: + basedir = os.path.abspath('%s/../' % os.path.dirname(__file__)) + if os.path.isdir(os.path.join(basedir, 'moulinette')): + sys.path.insert(0, basedir) + LOG_DIR = os.path.join(basedir, 'log') + # Initialization & helpers functions ----------------------------------- @@ -37,80 +45,113 @@ def _die(message, title='Error:'): print('%s %s' % (colorize(title, 'red'), message)) sys.exit(1) -def _check_in_devel(): - """Check and load if needed development environment""" - global IN_DEVEL, LOG_DIR - basedir = os.path.abspath('%s/../' % os.path.dirname(__file__)) - if os.path.isdir('%s/moulinette' % basedir) and not IN_DEVEL: - # Add base directory to python path - sys.path.insert(0, basedir) +def _parse_api_args(): + """Parse main arguments for the api""" + import argparse - # Update global variables - IN_DEVEL = True - LOG_DIR = '%s/log' % basedir + parser = argparse.ArgumentParser(add_help=False, + description="Run the YunoHost API to manage your server.", + ) + srv_group = parser.add_argument_group('server configuration') + srv_group.add_argument('-h', '--host', + action='store', default=DEFAULT_HOST, + help="Host to listen on (default: %s)" % DEFAULT_HOST, + ) + srv_group.add_argument('-p', '--port', + action='store', default=DEFAULT_PORT, type=int, + help="Port to listen on (default: %d)" % DEFAULT_PORT, + ) + srv_group.add_argument('--no-websocket', + action='store_true', default=True, dest='use_websocket', + help="Serve without WebSocket support, used to handle " + "asynchronous responses such as the messages", + ) + glob_group = parser.add_argument_group('global arguments') + glob_group.add_argument('--no-cache', + action='store_false', default=True, dest='use_cache', + help="Don't use actions map cache", + ) + glob_group.add_argument('--debug', + action='store_true', default=False, + help="Set log level to DEBUG", + ) + glob_group.add_argument('--verbose', + action='store_true', default=False, + help="Be verbose in the output", + ) + glob_group.add_argument('--help', + action='help', help="Show this help message and exit", + ) -def _parse_argv(): - """Parse additional arguments and return remaining ones""" - argv = list(sys.argv) - argv.pop(0) + return parser.parse_args() - if '--no-cache' in argv: - global USE_CACHE - USE_CACHE = False - argv.remove('--no-cache') - if '--no-websocket' in argv: - global USE_WEBSOCKET - USE_WEBSOCKET = False - argv.remove('--no-websocket') - if '--debug' in argv: - global LOGGERS_LEVEL - LOGGERS_LEVEL = 'DEBUG' - argv.remove('--debug') - if '--verbose' in argv: - global LOGGERS_HANDLERS - if 'console' not in LOGGERS_HANDLERS: - LOGGERS_HANDLERS.append('console') - argv.remove('--verbose') - return argv - -def _init_moulinette(): +def _init_moulinette(use_websocket=True, debug=False, verbose=False): """Configure logging and initialize the moulinette""" from moulinette import init + # Define loggers handlers + handlers = set(LOGGERS_HANDLERS) + if not use_websocket and 'api' in handlers: + handlers.remove('api') + if verbose and 'console' not in handlers: + handlers.add('console') + root_handlers = handlers - set(['api']) + + # Define loggers level + level = LOGGERS_LEVEL + if debug: + level = 'DEBUG' + # Custom logging configuration logging = { 'version': 1, 'disable_existing_loggers': True, 'formatters': { - 'simple': { - 'format': '%(relativeCreated)-5d %(levelname)-8s %(name)s - %(message)s' + 'console': { + 'format': '%(relativeCreated)-5d %(levelname)-8s %(name)s %(funcName)s - %(fmessage)s' }, 'precise': { - 'format': '%(asctime)-15s %(levelname)-8s %(name)s %(funcName)s - %(message)s' + 'format': '%(asctime)-15s %(levelname)-8s %(name)s %(funcName)s - %(fmessage)s' + }, + }, + 'filters': { + 'action': { + '()': 'moulinette.utils.log.ActionFilter', }, }, 'handlers': { - 'console': { - 'class': 'logging.StreamHandler', - 'formatter': 'simple', - 'stream': 'ext://sys.stderr', + 'api': { + 'class': 'moulinette.interfaces.api.APIQueueHandler', }, 'file': { 'class': 'logging.handlers.WatchedFileHandler', 'formatter': 'precise', 'filename': '%s/%s' % (LOG_DIR, LOG_FILE), + 'filters': ['action'], + }, + 'console': { + 'class': 'logging.StreamHandler', + 'formatter': 'console', + 'stream': 'ext://sys.stdout', + 'filters': ['action'], }, }, 'loggers': { 'moulinette': { - 'level': LOGGERS_LEVEL, - 'handlers': LOGGERS_HANDLERS, + 'level': level, + 'handlers': handlers, + 'propagate': False, }, 'yunohost': { - 'level': LOGGERS_LEVEL, - 'handlers': LOGGERS_HANDLERS, + 'level': level, + 'handlers': handlers, + 'propagate': False, }, }, + 'root': { + 'level': level, + 'handlers': root_handlers, + }, } # Create log directory @@ -150,20 +191,18 @@ def is_installed(): # Main action ---------------------------------------------------------- if __name__ == '__main__': - _check_in_devel() - _parse_argv() - _init_moulinette() + opts = _parse_api_args() + _init_moulinette(opts.use_websocket, opts.debug, opts.verbose) - from moulinette import (api, MoulinetteError) + # Run the server + from moulinette import api, MoulinetteError from yunohost import get_versions - try: - # Run the server - api(_retrieve_namespaces(), port=6787, + ret = api(_retrieve_namespaces(), + host=opts.host, port=opts.port, routes={ ('GET', '/installed'): is_installed, ('GET', '/version'): get_versions, }, - use_cache=USE_CACHE, use_websocket=USE_WEBSOCKET) - except MoulinetteError as e: - _die(e.strerror, m18n.g('error')) - sys.exit(0) + use_cache=opts.use_cache, use_websocket=opts.use_websocket + ) + sys.exit(ret) From 85d579b9ff915c48a1c3e5871b9f066693f5b27b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sat, 14 Nov 2015 10:58:12 +0100 Subject: [PATCH 063/170] [fix] Handle moulinette logging with root logger in both api and cli --- bin/yunohost | 10 +++++----- bin/yunohost-api | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/bin/yunohost b/bin/yunohost index fe9bd533e..ed09d6991 100755 --- a/bin/yunohost +++ b/bin/yunohost @@ -131,16 +131,16 @@ def _init_moulinette(debug=False, verbose=False, quiet=False): }, }, 'loggers': { - 'moulinette': { - 'level': level, - 'handlers': handlers, - 'propagate': False, - }, 'yunohost': { 'level': level, 'handlers': handlers, 'propagate': False, }, + 'moulinette': { + 'level': level, + 'handlers': [], + 'propagate': True, + }, }, 'root': { 'level': level, diff --git a/bin/yunohost-api b/bin/yunohost-api index e0de12b36..470f61c66 100755 --- a/bin/yunohost-api +++ b/bin/yunohost-api @@ -137,16 +137,16 @@ def _init_moulinette(use_websocket=True, debug=False, verbose=False): }, }, 'loggers': { - 'moulinette': { - 'level': level, - 'handlers': handlers, - 'propagate': False, - }, 'yunohost': { 'level': level, 'handlers': handlers, 'propagate': False, }, + 'moulinette': { + 'level': level, + 'handlers': [], + 'propagate': True, + }, }, 'root': { 'level': level, From b7d0e977b9d012feb84cba4da8915f43a474ee03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sat, 14 Nov 2015 18:21:36 +0100 Subject: [PATCH 064/170] [enh] Update hook.py to use logging instead of msignals.display --- locales/en.json | 3 ++- src/yunohost/hook.py | 34 ++++++++++++++++++---------------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/locales/en.json b/locales/en.json index 02ddc02a2..6abd33baf 100644 --- a/locales/en.json +++ b/locales/en.json @@ -37,7 +37,8 @@ "mysql_db_initialized" : "MySQL database successfully initialized", "extracting" : "Extracting...", "downloading" : "Downloading...", - "executing_script": "Executing script...", + "executing_script": "Executing script '{script:s}'...", + "executing_command": "Executing command '{command:s}'...", "done" : "Done.", "path_removal_failed" : "Unable to remove path {:s}", diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index d4c7962cc..61723ec66 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -32,12 +32,12 @@ import subprocess from glob import iglob from moulinette.core import MoulinetteError -from moulinette.utils.log import getActionLogger +from moulinette.utils import log hook_folder = '/usr/share/yunohost/hooks/' custom_hook_folder = '/etc/yunohost/hooks.d/' -logger = getActionLogger('yunohost.hook') +logger = log.getActionLogger('yunohost.hook') def hook_add(app, file): @@ -264,14 +264,9 @@ def hook_callback(action, hooks=[], args=None): state = 'succeed' filename = '%s-%s' % (priority, name) try: - ret = hook_exec(info['path'], args=args) - except: - logger.exception("error while executing hook '%s'", - info['path']) - state = 'failed' - if ret != 0: - logger.error("error while executing hook '%s', retcode: %d", - info['path'], ret) + hook_exec(info['path'], args=args, raise_on_error=True) + except MoulinetteError as e: + logger.error(e.strerror) state = 'failed' try: result[state][name].append(info['path']) @@ -364,12 +359,19 @@ def hook_exec(file, args=None, raise_on_error=False): # bash related issue if an argument is empty and is not the last arg_str = '"{:s}"'.format('" "'.join(str(s) for s in arg_list)) - msignals.display(m18n.n('executing_script')) + # Construct command to execute + command = [ + 'sudo', '-u', 'admin', '-H', 'sh', '-c', + 'cd "{:s}" && /bin/bash -x "{:s}" {:s}'.format( + file_path, file, arg_str), + ] + if logger.isEnabledFor(log.DEBUG): + logger.info(m18n.n('executing_command', command=' '.join(command))) + else: + logger.info(m18n.n('executing_script', script='{0}/{1}'.format( + file_path, file))) - p = subprocess.Popen( - ['sudo', '-u', 'admin', '-H', 'sh', '-c', 'cd "{:s}" && ' \ - '/bin/bash -x "{:s}" {:s}'.format( - file_path, file, arg_str)], + p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False) @@ -383,7 +385,7 @@ def hook_exec(file, args=None, raise_on_error=False): if returncode is not None: break else: - msignals.display(line.rstrip(), 'log') + logger.info(line.rstrip()) stream.close() if raise_on_error and returncode != 0: From 947082db069503e6e5e705dd93cd186fe2783492 Mon Sep 17 00:00:00 2001 From: kload Date: Sat, 14 Nov 2015 18:44:15 +0100 Subject: [PATCH 065/170] [fix] Restart Dovecot at the end of Rspamd configuration script Dovecot was not taking Rspamd sieve script into account without it. --- data/hooks/conf_regen/31-rspamd | 1 + 1 file changed, 1 insertion(+) diff --git a/data/hooks/conf_regen/31-rspamd b/data/hooks/conf_regen/31-rspamd index 5aadb7ccc..4c1520062 100644 --- a/data/hooks/conf_regen/31-rspamd +++ b/data/hooks/conf_regen/31-rspamd @@ -25,3 +25,4 @@ sudo chmod 660 /etc/dovecot/global_script/rspamd.svbin sudo chown -R vmail:mail /etc/dovecot/global_script sudo systemctl restart rspamd.socket +sudo systemctl restart dovecot From b72c8563c3461b221ad43f0558dc3e91d0a25d56 Mon Sep 17 00:00:00 2001 From: kload Date: Sat, 14 Nov 2015 18:46:00 +0100 Subject: [PATCH 066/170] [fix] Translate regenconf messages in English and French + use the new {variable:s} format --- locales/en.json | 5 +++++ locales/fr.json | 4 ++++ src/yunohost/service.py | 8 ++++---- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/locales/en.json b/locales/en.json index 02ddc02a2..acd734b1d 100644 --- a/locales/en.json +++ b/locales/en.json @@ -117,6 +117,11 @@ "service_status_failed" : "Unable to determine status of service '{:s}'", "service_no_log" : "No log to display for service '{:s}'", "service_cmd_exec_failed" : "Unable to execute command '{:s}'", + "services_configured": "Configuration successfully generated", + "service_configuration_conflict": "The file {file:s} has been changed since its last generation. Please apply the modifications manually or use the option --force (it will erase all the modifications previously done to the file)", + "no_such_conf_file": "Unable to copy the file {file:s}: the file does not exist", + "service_add_configuration": "Adding the configuration file {file:s}", + "network_check_smtp_ok" : "Outbound mail (SMTP port 25) is not blocked", "network_check_smtp_ko" : "Outbound mail (SMTP port 25) seems to be blocked by your network", diff --git a/locales/fr.json b/locales/fr.json index 86ab7fbf9..0fe221e3c 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -149,6 +149,10 @@ "service_stop_failed": "Impossible d'arrêter le service '{:s}'", "service_stopped": "Service '{:s}' arrêté avec succès", "service_unknown": "Service '{:s}' inconnu", + "services_configured": "La configuration a été générée avec succès", + "service_configuration_conflict": "Le fichier {file:s} a été modifié depuis sa dernière génération. Veuillez y appliquer les modifications manuellement ou utiliser l’option --force (ce qui écrasera toutes les modifications effectuées sur le fichier)", + "no_such_conf_file": "Le fichier {file:s} n’existe pas, il ne peut pas être copié", + "service_add_configuration": "Ajout du fichier de configuration {file:s}", "ssowat_conf_generated": "Configuration de SSOwat générée avec succès", "ssowat_conf_updated": "Configuration persistante de SSOwat mise à jour avec succès", "system_upgraded": "Système mis à jour avec succès", diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 2a69b4d2d..4292fbac7 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -470,7 +470,7 @@ def service_saferemove(service, conf_file, force=False): if os.isatty(1) and \ (len(previous_hash) == 32 or previous_hash[-32:] != current_hash): msignals.display( - m18n.n('service_configuration_changed', conf_file), + m18n.n('service_configuration_conflict', file=conf_file), 'warning' ) @@ -495,7 +495,7 @@ def service_safecopy(service, new_conf_file, conf_file, force=False): services = _get_services() if not os.path.exists(new_conf_file): - raise MoulinetteError(errno.EIO, m18n.n('no_such_conf_file', new_conf_file)) + raise MoulinetteError(errno.EIO, m18n.n('no_such_conf_file', file=new_conf_file)) with open(new_conf_file, 'r') as f: new_conf = ''.join(f.readlines()).rstrip() @@ -509,7 +509,7 @@ def service_safecopy(service, new_conf_file, conf_file, force=False): ) process.wait() else: - msignals.display(m18n.n('service_add_configuration', conf_file), + msignals.display(m18n.n('service_add_configuration', file=conf_file), 'info') # Add the service if it does not exist @@ -540,7 +540,7 @@ def service_safecopy(service, new_conf_file, conf_file, force=False): new_hash = previous_hash if (len(previous_hash) == 32 or previous_hash[-32:] != current_hash): msignals.display( - m18n.n('service_configuration_conflict', conf_file), + m18n.n('service_configuration_conflict', file=conf_file), 'warning' ) print('\n' + conf_file) From 4a071df6f7a7a229b8792b04b5a305aadd7ff2a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sat, 14 Nov 2015 19:18:03 +0100 Subject: [PATCH 067/170] [enh] Set custom logging formatter for the tty in case of debug --- bin/yunohost | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bin/yunohost b/bin/yunohost index ed09d6991..f0f770760 100755 --- a/bin/yunohost +++ b/bin/yunohost @@ -109,6 +109,9 @@ def _init_moulinette(debug=False, verbose=False, quiet=False): 'version': 1, 'disable_existing_loggers': True, 'formatters': { + 'tty-debug': { + 'format': '%(relativeCreated)-4d %(fmessage)s' + }, 'precise': { 'format': '%(asctime)-15s %(levelname)-8s %(name)s %(funcName)s - %(fmessage)s' }, @@ -122,6 +125,7 @@ def _init_moulinette(debug=False, verbose=False, quiet=False): 'tty': { 'level': tty_level, 'class': 'moulinette.interfaces.cli.TTYHandler', + 'formatter': 'tty-debug' if debug else '', }, 'file': { 'class': 'logging.FileHandler', From e14674af887b7108d3a5528d24bd0a10b35e4087 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sat, 14 Nov 2015 23:13:51 +0100 Subject: [PATCH 068/170] [enh] Split stdout/stderr wrapping in hook_exec and add a no_trace option --- data/actionsmap/yunohost.yml | 8 +++- locales/en.json | 1 + src/yunohost/hook.py | 83 ++++++++++++++++++++++-------------- 3 files changed, 58 insertions(+), 34 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index bf6291614..853c4e386 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1355,11 +1355,15 @@ hook: action_help: Execute hook from a file with arguments api: GET /hook arguments: - file: - help: Script to execute + path: + help: Path of the script to execute -a: full: --args help: Arguments to pass to the script --raise-on-error: help: Raise if the script returns a non-zero exit code action: store_true + -q: + full: --no-trace + help: Do not print each command that will be executed + action: store_true diff --git a/locales/en.json b/locales/en.json index 6abd33baf..b4c0a7bb8 100644 --- a/locales/en.json +++ b/locales/en.json @@ -88,6 +88,7 @@ "hook_choice_invalid" : "Invalid choice '{:s}'", "hook_argument_missing" : "Missing argument '{:s}'", "hook_exec_failed" : "Script execution failed", + "hook_exec_not_terminated" : "Script execution hasn’t terminated", "mountpoint_unknown" : "Unknown mountpoint", "unit_unknown" : "Unknown unit '{:s}'", diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index 61723ec66..fd6179e81 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -296,23 +296,31 @@ def hook_check(file): return {} -def hook_exec(file, args=None, raise_on_error=False): +def hook_exec(path, args=None, raise_on_error=False, no_trace=False): """ Execute hook from a file with arguments Keyword argument: - file -- Script to execute + path -- Path of the script to execute args -- Arguments to pass to the script raise_on_error -- Raise if the script returns a non-zero exit code + no_trace -- Do not print each command that will be executed """ - from moulinette.utils.stream import NonBlockingStreamReader + import time + from moulinette.utils.stream import start_async_file_reading from yunohost.app import _value_for_locale + # Validate hook path + if path[0] != '/': + path = os.path.realpath(path) + if not os.path.isfile(path): + raise MoulinetteError(errno.EIO, m18n.g('file_not_exist')) + if isinstance(args, list): arg_list = args else: - required_args = hook_check(file) + required_args = hook_check(path) if args is None: args = {} @@ -346,49 +354,60 @@ def hook_exec(file, args=None, raise_on_error=False): raise MoulinetteError(errno.EINVAL, m18n.n('hook_argument_missing', arg['name'])) - file_path = "./" - if "/" in file and file[0:2] != file_path: - file_path = os.path.dirname(file) - file = file.replace(file_path +"/", "") + # Construct command variables + cmd_fdir, cmd_fname = os.path.split(path) + cmd_fname = './{0}'.format(cmd_fname) - #TODO: Allow python script - - arg_str = '' + cmd_args = '' if arg_list: # Concatenate arguments and escape them with double quotes to prevent # bash related issue if an argument is empty and is not the last - arg_str = '"{:s}"'.format('" "'.join(str(s) for s in arg_list)) + cmd_args = '"{:s}"'.format('" "'.join(str(s) for s in arg_list)) # Construct command to execute - command = [ - 'sudo', '-u', 'admin', '-H', 'sh', '-c', - 'cd "{:s}" && /bin/bash -x "{:s}" {:s}'.format( - file_path, file, arg_str), - ] + command = ['sudo', '-u', 'admin', '-H', 'sh', '-c'] + if no_trace: + cmd = 'cd "{0:s}" && /bin/bash "{1:s}" {2:s}' + else: + # use xtrace on fd 7 which is redirected to stdout + cmd = 'cd "{0:s}" && BASH_XTRACEFD=7 /bin/bash -x "{1:s}" {2:s} 7>&1' + command.append(cmd.format(cmd_fdir, cmd_fname, cmd_args)) + if logger.isEnabledFor(log.DEBUG): logger.info(m18n.n('executing_command', command=' '.join(command))) else: logger.info(m18n.n('executing_script', script='{0}/{1}'.format( - file_path, file))) + cmd_fdir, cmd_fname))) - p = subprocess.Popen(command, - stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + process = subprocess.Popen(command, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False) - # Wrap and get process ouput - stream = NonBlockingStreamReader(p.stdout) - while True: - line = stream.readline(True, 0.1) - if not line: - # Check if process has terminated - returncode = p.poll() - if returncode is not None: - break - else: + # Wrap and get process outputs + stdout_reader, stdout_queue = start_async_file_reading(process.stdout) + stderr_reader, stderr_queue = start_async_file_reading(process.stderr) + while not stdout_reader.eof() or not stderr_reader.eof(): + while not stdout_queue.empty(): + line = stdout_queue.get() logger.info(line.rstrip()) - stream.close() + while not stderr_queue.empty(): + line = stderr_queue.get() + logger.warning(line.rstrip()) + time.sleep(.1) - if raise_on_error and returncode != 0: + # Terminate outputs readers + stdout_reader.join() + stderr_reader.join() + + # Get and return process' return code + returncode = process.poll() + if returncode is None: + if raise_on_error: + raise MoulinetteError(m18n.n('hook_exec_not_terminated')) + else: + logger.error(m18n.n('hook_exec_not_terminated')) + return 1 + elif raise_on_error and returncode != 0: raise MoulinetteError(m18n.n('hook_exec_failed')) return returncode From b7fcf9ffdbe4b940cc487e5a98392463ff234546 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sat, 14 Nov 2015 23:38:44 +0100 Subject: [PATCH 069/170] [enh] Quickly implement logging in service.py and use it to show the diff --- locales/en.json | 2 +- src/yunohost/service.py | 49 ++++++++++++++++++----------------------- 2 files changed, 22 insertions(+), 29 deletions(-) diff --git a/locales/en.json b/locales/en.json index e8fa3f244..d5b17c143 100644 --- a/locales/en.json +++ b/locales/en.json @@ -120,7 +120,7 @@ "service_no_log" : "No log to display for service '{:s}'", "service_cmd_exec_failed" : "Unable to execute command '{:s}'", "services_configured": "Configuration successfully generated", - "service_configuration_conflict": "The file {file:s} has been changed since its last generation. Please apply the modifications manually or use the option --force (it will erase all the modifications previously done to the file)", + "service_configuration_conflict": "The file {file:s} has been changed since its last generation. Please apply the modifications manually or use the option --force (it will erase all the modifications previously done to the file). Here are the differences:\n{diff:s}", "no_such_conf_file": "Unable to copy the file {file:s}: the file does not exist", "service_add_configuration": "Adding the configuration file {file:s}", diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 4292fbac7..984215fe3 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -34,6 +34,7 @@ import difflib import hashlib from moulinette.core import MoulinetteError +from moulinette.utils import log template_dir = os.getenv( 'YUNOHOST_TEMPLATE_DIR', @@ -44,6 +45,9 @@ conf_backup_dir = os.getenv( '/home/yunohost.backup/conffiles' ) +logger = log.getActionLogger('yunohost.service') + + def service_add(name, status=None, log=None, runlevel=None): """ Add a custom service @@ -73,7 +77,7 @@ def service_add(name, status=None, log=None, runlevel=None): except: raise MoulinetteError(errno.EIO, m18n.n('service_add_failed', name)) - msignals.display(m18n.n('service_added'), 'success') + logger.success(m18n.n('service_added')) def service_remove(name): @@ -96,7 +100,7 @@ def service_remove(name): except: raise MoulinetteError(errno.EIO, m18n.n('service_remove_failed', name)) - msignals.display(m18n.n('service_removed'), 'success') + logger.success(m18n.n('service_removed')) def service_start(names): @@ -111,12 +115,12 @@ def service_start(names): names = [names] for name in names: if _run_service_command('start', name): - msignals.display(m18n.n('service_started', name), 'success') + logger.success(m18n.n('service_started', name)) else: if service_status(name)['status'] != 'running': raise MoulinetteError(errno.EPERM, m18n.n('service_start_failed', name)) - msignals.display(m18n.n('service_already_started', name)) + logger.info(m18n.n('service_already_started', name)) def service_stop(names): @@ -131,12 +135,12 @@ def service_stop(names): names = [names] for name in names: if _run_service_command('stop', name): - msignals.display(m18n.n('service_stopped', name), 'success') + logger.success(m18n.n('service_stopped', name)) else: if service_status(name)['status'] != 'inactive': raise MoulinetteError(errno.EPERM, m18n.n('service_stop_failed', name)) - msignals.display(m18n.n('service_already_stopped', name)) + logger.info(m18n.n('service_already_stopped', name)) def service_enable(names): @@ -151,7 +155,7 @@ def service_enable(names): names = [names] for name in names: if _run_service_command('enable', name): - msignals.display(m18n.n('service_enabled', name), 'success') + logger.success(m18n.n('service_enabled', name)) else: raise MoulinetteError(errno.EPERM, m18n.n('service_enable_failed', name)) @@ -169,7 +173,7 @@ def service_disable(names): names = [names] for name in names: if _run_service_command('disable', name): - msignals.display(m18n.n('service_disabled', name), 'success') + logger.success(m18n.n('service_disabled', name)) else: raise MoulinetteError(errno.EPERM, m18n.n('service_disable_failed', name)) @@ -217,8 +221,7 @@ def service_status(names=[]): shell=True) except subprocess.CalledProcessError as e: if 'usage:' in e.output.lower(): - msignals.display(m18n.n('service_status_failed', name), - 'warning') + logger.warning(m18n.n('service_status_failed', name)) else: result[name]['status'] = 'inactive' else: @@ -288,7 +291,7 @@ def service_regenconf(service=None, force=False): hook_callback('conf_regen', [service], args=[force]) else: hook_callback('conf_regen', args=[force]) - msignals.display(m18n.n('services_configured'), 'success') + logger.success(m18n.n('services_configured')) def _run_service_command(action, service): @@ -317,8 +320,7 @@ def _run_service_command(action, service): ret = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT) except subprocess.CalledProcessError as e: # TODO: Log output? - msignals.display(m18n.n('service_cmd_exec_failed', ' '.join(e.cmd)), - 'warning') + logger.warning(m18n.n('service_cmd_exec_failed', ' '.join(e.cmd))) return False return True @@ -469,10 +471,8 @@ def service_saferemove(service, conf_file, force=False): os.remove(conf_backup_file) if os.isatty(1) and \ (len(previous_hash) == 32 or previous_hash[-32:] != current_hash): - msignals.display( - m18n.n('service_configuration_conflict', file=conf_file), - 'warning' - ) + logger.warning(m18n.n('service_configuration_conflict', + file=conf_file)) _save_services(services) @@ -509,8 +509,7 @@ def service_safecopy(service, new_conf_file, conf_file, force=False): ) process.wait() else: - msignals.display(m18n.n('service_add_configuration', file=conf_file), - 'info') + logger.info(m18n.n('service_add_configuration', file=conf_file)) # Add the service if it does not exist if service not in services.keys(): @@ -539,15 +538,9 @@ def service_safecopy(service, new_conf_file, conf_file, force=False): else: new_hash = previous_hash if (len(previous_hash) == 32 or previous_hash[-32:] != current_hash): - msignals.display( - m18n.n('service_configuration_conflict', file=conf_file), - 'warning' - ) - print('\n' + conf_file) - for line in diff: - print(line.strip()) - print('') - + logger.warning(m18n.n('service_configuration_conflict', + file=conf_file, diff=''.join(diff))) + # Remove the backup file if the configuration has not changed if new_hash == previous_hash: try: From 40145648f0a8551fce2c420ba0fe091ec9faa1aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sat, 14 Nov 2015 23:40:16 +0100 Subject: [PATCH 070/170] [fix] Do not restrict warning to tty in service_saferemove --- src/yunohost/service.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 984215fe3..7c445ae30 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -469,8 +469,7 @@ def service_saferemove(service, conf_file, force=False): else: services[service]['conffiles'][conf_file] = previous_hash os.remove(conf_backup_file) - if os.isatty(1) and \ - (len(previous_hash) == 32 or previous_hash[-32:] != current_hash): + if len(previous_hash) == 32 or previous_hash[-32:] != current_hash: logger.warning(m18n.n('service_configuration_conflict', file=conf_file)) From 0da41d7d9058bfcb0b61895b1bf64bd84752a426 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sun, 15 Nov 2015 00:07:22 +0100 Subject: [PATCH 071/170] [i18n] Update French translation of service_configuration_conflict --- locales/fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 0fe221e3c..b2e0f38c6 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -150,7 +150,7 @@ "service_stopped": "Service '{:s}' arrêté avec succès", "service_unknown": "Service '{:s}' inconnu", "services_configured": "La configuration a été générée avec succès", - "service_configuration_conflict": "Le fichier {file:s} a été modifié depuis sa dernière génération. Veuillez y appliquer les modifications manuellement ou utiliser l’option --force (ce qui écrasera toutes les modifications effectuées sur le fichier)", + "service_configuration_conflict": "Le fichier {file:s} a été modifié depuis sa dernière génération. Veuillez y appliquer les modifications manuellement ou utiliser l’option --force (ce qui écrasera toutes les modifications effectuées sur le fichier). Voici les différences:\n{diff:s}", "no_such_conf_file": "Le fichier {file:s} n’existe pas, il ne peut pas être copié", "service_add_configuration": "Ajout du fichier de configuration {file:s}", "ssowat_conf_generated": "Configuration de SSOwat générée avec succès", @@ -182,4 +182,4 @@ "yunohost_configured": "YunoHost configuré avec succès", "yunohost_installing": "Installation de YunoHost...", "yunohost_not_installed": "YunoHost n'est pas ou pas correctement installé. Veuillez exécuter 'yunohost tools postinstall'." -} \ No newline at end of file +} From 25c7d1e7996508ab7714b385d0cfd1dd5053c225 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sun, 15 Nov 2015 00:09:56 +0100 Subject: [PATCH 072/170] [fix] Update moulinette version dependency --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 5da5cf8c6..65bd45c52 100644 --- a/debian/control +++ b/debian/control @@ -10,7 +10,7 @@ Homepage: https://yunohost.org/ Package: yunohost Architecture: all Depends: ${python:Depends}, ${misc:Depends}, - moulinette (>= 2.3), + moulinette (>= 2.3.1), python-psutil, python-requests, glances, From 143e8f3492ed8ed5f726d845c28355a1b247829a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sun, 15 Nov 2015 00:22:54 +0100 Subject: [PATCH 073/170] [fix] Update .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 726bba63a..6dd427aba 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,4 @@ pip-log.txt .mr.developer.cfg # moulinette lib -lib/yunohost/locales +src/yunohost/locales From b06fc7cfb053524fc26a958168069e10fdb1385d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sun, 15 Nov 2015 00:24:22 +0100 Subject: [PATCH 074/170] Update changelog for 2.3.1 release --- debian/changelog | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/debian/changelog b/debian/changelog index d4538a00e..1971e1b24 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,24 @@ +yunohost (2.3.1) testing; urgency=low + + [ Jérôme Lebleu ] + * [enh] Add logrotate configuration + * [enh] Allow to set default options for yunohost-api service + * [enh] Add bash completion for bin/yunohost + * [enh] Make use of new logging facilities in firewall, hook and service + * [enh] Refactor bin/yunohost and bin/yunohost-api to follow moulinette + changes and provide help for global arguments + * [enh] Split stdout/stderr wrapping in hook_exec and add a no_trace option + * [fix] Create home directory during login (fixbug #80) + * [fix] Keep compat with deprecated --plain and --json in the cli + * [fix] Do not restrict warning to tty in service_saferemove + * [fix] Enable yunohost-api systemd service manually + + [ kload ] + * [fix] Restart Dovecot at the end of Rspamd configuration script + * [fix] Translate regenconf messages in English and French + + -- Jérôme Lebleu Sun, 15 Nov 2015 00:23:27 +0100 + yunohost (2.3.0) testing; urgency=low [ breaking changes ] From e6cfab1f6a20da04e0dd503f56f3e289debf0628 Mon Sep 17 00:00:00 2001 From: kload Date: Sun, 15 Nov 2015 00:55:00 +0100 Subject: [PATCH 075/170] [fix] Do not remove the global_script directory --- data/hooks/conf_regen/25-dovecot | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/hooks/conf_regen/25-dovecot b/data/hooks/conf_regen/25-dovecot index 252828c7f..551400bdf 100644 --- a/data/hooks/conf_regen/25-dovecot +++ b/data/hooks/conf_regen/25-dovecot @@ -39,8 +39,8 @@ safe_copy dovecot-ldap.conf /etc/dovecot/dovecot-ldap.conf # Setup Sieve -sudo rm -rf /etc/dovecot/global_script -sudo mkdir -p -m 0770 /etc/dovecot/global_script +sudo mkdir -p /etc/dovecot/global_script +sudo chmod -R 770 /etc/dovecot/global_script safe_copy dovecot.sieve /etc/dovecot/global_script/dovecot.sieve sudo chmod 660 /etc/dovecot/global_script/dovecot.sieve > /dev/null 2>&1 \ From 1c4bd0e796a7634519d5541e1ae95ef0f14682f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sun, 15 Nov 2015 01:05:08 +0100 Subject: [PATCH 076/170] [fix] Add tty in root handlers if debug is set in bin/yunohost --- bin/yunohost | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bin/yunohost b/bin/yunohost index f0f770760..c886c89bc 100755 --- a/bin/yunohost +++ b/bin/yunohost @@ -94,7 +94,10 @@ def _init_moulinette(debug=False, verbose=False, quiet=False): handlers.remove('tty') elif verbose and 'tty' not in handlers: handlers.append('tty') - root_handlers = handlers - set(['tty']) + + root_handlers = handlers + if not debug: + root_handlers.remove('tty') # Define loggers level level = LOGGERS_LEVEL From 62c7079783334fee094660ba2fdbafe24e19581c Mon Sep 17 00:00:00 2001 From: kload Date: Sun, 15 Nov 2015 11:07:27 +0100 Subject: [PATCH 077/170] [fix] Unexpected warnings comming from stderr --- data/hooks/conf_regen/06-slapd | 2 +- data/hooks/conf_regen/43-dnsmasq | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd index 63cffac2e..b5af23538 100644 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -46,7 +46,7 @@ sudo mkdir /etc/ldap/slapd.d sudo chown -R openldap:openldap /etc/ldap/schema/ sudo chown -R openldap:openldap /etc/ldap/slapd.d/ -sudo slaptest -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.d/ +sudo slaptest -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.d/ 2>&1 sudo chown -R openldap:openldap /etc/ldap/slapd.d/ sudo service slapd force-reload diff --git a/data/hooks/conf_regen/43-dnsmasq b/data/hooks/conf_regen/43-dnsmasq index 821bfa392..4ce4ea6d0 100644 --- a/data/hooks/conf_regen/43-dnsmasq +++ b/data/hooks/conf_regen/43-dnsmasq @@ -16,7 +16,7 @@ function safe_copy () { cd /usr/share/yunohost/templates/dnsmasq # Get IP address -ip=$(curl ip.yunohost.org || echo '0.0.0.0') +ip=$(curl ip.yunohost.org 2>/dev/null || echo '0.0.0.0') # Get IPv6 IP address ipv6=$(ip route get 2000:: | grep -q "unreachable" && echo '' \ From db6650af38575ac7902d124feb110818bfff2467 Mon Sep 17 00:00:00 2001 From: kload Date: Sun, 15 Nov 2015 11:13:38 +0100 Subject: [PATCH 078/170] [enh] Warn the user about the waiting at the configuration generation --- debian/postinst | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/postinst b/debian/postinst index 1831ccaaa..a45ff75f9 100644 --- a/debian/postinst +++ b/debian/postinst @@ -11,6 +11,7 @@ do_configure() { bash /usr/share/yunohost/hooks/conf_regen/06-slapd True bash /usr/share/yunohost/hooks/conf_regen/15-nginx True else + echo "Regenerating configuration, this might take a while..." yunohost service regenconf # restart yunohost-firewall if it's running From 68f2dfca817fcf760e0634b3b6d31629fae7c2d0 Mon Sep 17 00:00:00 2001 From: kload Date: Sun, 15 Nov 2015 12:30:47 +0100 Subject: [PATCH 079/170] [fix] Delayed upgrade of the package 'yunohost' --- src/yunohost/tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 833ef71aa..daaae9aa1 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -373,8 +373,8 @@ def tools_upgrade(auth, ignore_apps=False, ignore_packages=False): # If API call if is_api: - critical_packages = ("moulinette", "moulinette-yunohost", - "yunohost-admin", "yunohost-config-nginx", "ssowat", "python") + critical_packages = ("moulinette", "yunohost", + "yunohost-admin", "ssowat", "python") critical_upgrades = set() for pkg in cache.get_changes(): From bcaaec0505f23362076b02cae8608350c486e826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sun, 15 Nov 2015 12:43:42 +0100 Subject: [PATCH 080/170] [fix] Do not rely on dh_installinit and restart service after upgrade --- debian/postinst | 9 +++++++++ debian/rules | 6 ++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/debian/postinst b/debian/postinst index a45ff75f9..50add893e 100644 --- a/debian/postinst +++ b/debian/postinst @@ -47,6 +47,15 @@ case "$1" in ;; esac +if [ -x /etc/init.d/yunohost-api ]; then + update-rc.d yunohost-api defaults >/dev/null + if [ -d /run/systemd/system ]; then + systemctl --system daemon-reload >/dev/null || true + else + invoke-rc.d yunohost-api start || exit $? + fi +fi + #DEBHELPER# exit 0 diff --git a/debian/rules b/debian/rules index 2208e0623..ebcaefd15 100755 --- a/debian/rules +++ b/debian/rules @@ -8,8 +8,10 @@ dh ${@} --with=python2,systemd override_dh_installinit: - dh_installinit --name=yunohost-api - dh_installinit --name=yunohost-firewall --no-start + dh_installinit --noscripts override_dh_systemd_enable: dh_systemd_enable --name=yunohost-api + +override_dh_systemd_start: + dh_systemd_start --restart-after-upgrade yunohost-api.service From 829495905ddd2b46d4e770c53559b35ce0d47fe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sun, 15 Nov 2015 14:03:05 +0100 Subject: [PATCH 081/170] [fix] Update moulinette version dependency --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 65bd45c52..52208855d 100644 --- a/debian/control +++ b/debian/control @@ -10,7 +10,7 @@ Homepage: https://yunohost.org/ Package: yunohost Architecture: all Depends: ${python:Depends}, ${misc:Depends}, - moulinette (>= 2.3.1), + moulinette (>= 2.3.2), python-psutil, python-requests, glances, From 61267ccb1f3d4bd1722651e5dc86c6c2d24bf7b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sun, 15 Nov 2015 14:03:44 +0100 Subject: [PATCH 082/170] Update changelog for 2.3.2 release --- debian/changelog | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/debian/changelog b/debian/changelog index 1971e1b24..d16ab30b3 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,17 @@ +yunohost (2.3.2) testing; urgency=low + + [ Jérôme Lebleu ] + * [fix] Do not rely on dh_installinit and restart service after upgrade + * [fix] Add tty in root handlers if debug is set in bin/yunohost + + [ kload ] + * [fix] Do not remove the global_script directory + * [fix] Unexpected warnings comming from stderr + * [enh] Warn the user about the waiting at the configuration generation + * [fix] Delayed upgrade of the package 'yunohost' + + -- Jérôme Lebleu Sun, 15 Nov 2015 14:03:39 +0100 + yunohost (2.3.1) testing; urgency=low [ Jérôme Lebleu ] From 3f42d12e958a132412ab2711fcc8a86264b074a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sun, 15 Nov 2015 14:58:35 +0100 Subject: [PATCH 083/170] [fix] Do not modify handlers with root_handlers in bin/yunohost --- bin/yunohost | 2 +- src/yunohost/hook.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/yunohost b/bin/yunohost index c886c89bc..a5fadf574 100755 --- a/bin/yunohost +++ b/bin/yunohost @@ -95,7 +95,7 @@ def _init_moulinette(debug=False, verbose=False, quiet=False): elif verbose and 'tty' not in handlers: handlers.append('tty') - root_handlers = handlers + root_handlers = set(handlers) if not debug: root_handlers.remove('tty') diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index fd6179e81..44d6f12be 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -386,7 +386,7 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False): # Wrap and get process outputs stdout_reader, stdout_queue = start_async_file_reading(process.stdout) stderr_reader, stderr_queue = start_async_file_reading(process.stderr) - while not stdout_reader.eof() or not stderr_reader.eof(): + while not stdout_reader.eof() and not stderr_reader.eof(): while not stdout_queue.empty(): line = stdout_queue.get() logger.info(line.rstrip()) From d2f4e190508111bbd6e6b0f477c1dca0b4ddc855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sun, 15 Nov 2015 15:00:06 +0100 Subject: [PATCH 084/170] Update changelog for 2.3.3 release --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index d16ab30b3..c7824ffc4 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +yunohost (2.3.3) testing; urgency=low + + * [fix] Do not modify handlers with root_handlers in bin/yunohost + + -- Jérôme Lebleu Sun, 15 Nov 2015 15:00:04 +0100 + yunohost (2.3.2) testing; urgency=low [ Jérôme Lebleu ] From eaef922d4e2feca20cd9bdc45ee562e479a3f072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sun, 15 Nov 2015 15:22:34 +0100 Subject: [PATCH 085/170] [enh] Make use of call_async_output in hook_exec to get output in real time --- src/yunohost/hook.py | 31 ++++++++----------------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index 44d6f12be..c8e59def5 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -307,8 +307,7 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False): no_trace -- Do not print each command that will be executed """ - import time - from moulinette.utils.stream import start_async_file_reading + from moulinette.utils.process import call_async_output from yunohost.app import _value_for_locale # Validate hook path @@ -379,28 +378,14 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False): logger.info(m18n.n('executing_script', script='{0}/{1}'.format( cmd_fdir, cmd_fname))) - process = subprocess.Popen(command, - stdout=subprocess.PIPE, stderr=subprocess.PIPE, - shell=False) + # Define output callbacks and call command + callbacks = ( + lambda l: logger.info(l.rstrip()), + lambda l: logger.warning(l.rstrip()), + ) + returncode = call_async_output(command, callbacks, shell=False) - # Wrap and get process outputs - stdout_reader, stdout_queue = start_async_file_reading(process.stdout) - stderr_reader, stderr_queue = start_async_file_reading(process.stderr) - while not stdout_reader.eof() and not stderr_reader.eof(): - while not stdout_queue.empty(): - line = stdout_queue.get() - logger.info(line.rstrip()) - while not stderr_queue.empty(): - line = stderr_queue.get() - logger.warning(line.rstrip()) - time.sleep(.1) - - # Terminate outputs readers - stdout_reader.join() - stderr_reader.join() - - # Get and return process' return code - returncode = process.poll() + # Check and return process' return code if returncode is None: if raise_on_error: raise MoulinetteError(m18n.n('hook_exec_not_terminated')) From b83c7317b361db7a88b00115315928fe7f55662e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sun, 15 Nov 2015 15:34:36 +0100 Subject: [PATCH 086/170] [fix] Display a more detailed message when yunohost-firewall is stopped --- debian/postinst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/debian/postinst b/debian/postinst index 50add893e..3fcaa0dd1 100644 --- a/debian/postinst +++ b/debian/postinst @@ -17,7 +17,8 @@ do_configure() { # restart yunohost-firewall if it's running service yunohost-firewall status > /dev/null \ && service yunohost-firewall restart \ - || echo "Firewall stopped" + || echo "Service yunohost-firewall is not running, you should " \ + "consider to start it." fi # update PAM configs From 9b2da4944e60465a4fa890a82f001dd21c88ca75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sun, 15 Nov 2015 15:50:49 +0100 Subject: [PATCH 087/170] [fix] Prevent insserv warning when using systemd at package postinst --- debian/postinst | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/debian/postinst b/debian/postinst index 3fcaa0dd1..17ccd5b82 100644 --- a/debian/postinst +++ b/debian/postinst @@ -48,13 +48,10 @@ case "$1" in ;; esac -if [ -x /etc/init.d/yunohost-api ]; then +# Enable and start yunohost-api service for non-systemd system +if [ -x /etc/init.d/yunohost-api ] && [ ! -d /run/systemd/system ]; then update-rc.d yunohost-api defaults >/dev/null - if [ -d /run/systemd/system ]; then - systemctl --system daemon-reload >/dev/null || true - else - invoke-rc.d yunohost-api start || exit $? - fi + invoke-rc.d yunohost-api start || exit $? fi #DEBHELPER# From 4d439217f1a0cccdeb2013979096eb15faaab04a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sun, 15 Nov 2015 18:33:02 +0100 Subject: [PATCH 088/170] [fix] Log real exception string error in hook_callback --- src/yunohost/hook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index c8e59def5..9c6d430e7 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -266,7 +266,7 @@ def hook_callback(action, hooks=[], args=None): try: hook_exec(info['path'], args=args, raise_on_error=True) except MoulinetteError as e: - logger.error(e.strerror) + logger.error(str(e)) state = 'failed' try: result[state][name].append(info['path']) From eae6ecc9c0371b9d83b4407bece2a2fa9d1347a9 Mon Sep 17 00:00:00 2001 From: julienmalik Date: Mon, 16 Nov 2015 11:57:16 +0100 Subject: [PATCH 089/170] =?UTF-8?q?[fix]=C2=A0Log=20for=20rmilter=20instea?= =?UTF-8?q?d=20of=20rspamd?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/hooks/conf_regen/22-email-legacy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/conf_regen/22-email-legacy b/data/hooks/conf_regen/22-email-legacy index 7c7edf03d..0328dd910 100644 --- a/data/hooks/conf_regen/22-email-legacy +++ b/data/hooks/conf_regen/22-email-legacy @@ -9,7 +9,7 @@ if [[ "$1" == "True" ]]; then || echo "Rspamd is already listed in services" sudo yunohost service add rmilter -l /var/log/mail.log \ - || echo "Rspamd is already listed in services" + || echo "Rmilter is already listed in services" sudo yunohost service add memcached \ || echo "Memcached is already listed in services" From 8362bf2ec0d5e1ecdab61814ddb0a22533b31e18 Mon Sep 17 00:00:00 2001 From: julienmalik Date: Mon, 16 Nov 2015 12:33:07 +0100 Subject: [PATCH 090/170] [fix] Do not exit at first service which can't be stopped --- data/hooks/conf_regen/22-email-legacy | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/hooks/conf_regen/22-email-legacy b/data/hooks/conf_regen/22-email-legacy index 0328dd910..04962c4ca 100644 --- a/data/hooks/conf_regen/22-email-legacy +++ b/data/hooks/conf_regen/22-email-legacy @@ -29,7 +29,7 @@ if [[ "$1" == "True" ]]; then || echo "Postgrey is already removed" \ && sudo systemctl disable postgrey || true - sudo systemctl stop spamassassin - sudo systemctl stop amavis - sudo systemctl stop postgrey + sudo systemctl stop spamassassin || true + sudo systemctl stop amavis || true + sudo systemctl stop postgrey || true fi From 604f29184d182a9c378fc95f4a4102a85bbf901b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Mon, 16 Nov 2015 21:56:30 +0100 Subject: [PATCH 091/170] [fix] Add yunohost-firewall.service but do not enable it --- debian/rules | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/rules b/debian/rules index ebcaefd15..51d60b695 100755 --- a/debian/rules +++ b/debian/rules @@ -12,6 +12,7 @@ override_dh_installinit: override_dh_systemd_enable: dh_systemd_enable --name=yunohost-api + dh_systemd_enable --name=yunohost-firewall --no-enable override_dh_systemd_start: dh_systemd_start --restart-after-upgrade yunohost-api.service From 7f5e98fbc72f3049a2f90e6d10e2b0fd62bf3481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Tue, 17 Nov 2015 11:08:15 +0100 Subject: [PATCH 092/170] [fix] Update moulinette version dependency --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 52208855d..c08155675 100644 --- a/debian/control +++ b/debian/control @@ -10,7 +10,7 @@ Homepage: https://yunohost.org/ Package: yunohost Architecture: all Depends: ${python:Depends}, ${misc:Depends}, - moulinette (>= 2.3.2), + moulinette (>= 2.3.3), python-psutil, python-requests, glances, From 9d0292f2bd490045aa3742a4f401307350b519d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Tue, 17 Nov 2015 11:10:51 +0100 Subject: [PATCH 093/170] Update changelog for 2.3.4 release --- debian/changelog | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/debian/changelog b/debian/changelog index c7824ffc4..e1f459965 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,18 @@ +yunohost (2.3.4) testing; urgency=low + + [ Jérôme Lebleu ] + * [enh] Make use of call_async_output in hook_exec to get output in real time + * [fix] Display a more detailed message when yunohost-firewall is stopped + * [fix] Prevent insserv warning when using systemd at package postinst + * [fix] Log real exception string error in hook_callback + * [fix] Add yunohost-firewall.service but do not enable it + + [ julienmalik ] + * [fix] Log for rmilter instead of rspamd + * [fix] Do not exit at first service which can't be stopped + + -- Jérôme Lebleu Tue, 17 Nov 2015 11:10:42 +0100 + yunohost (2.3.3) testing; urgency=low * [fix] Do not modify handlers with root_handlers in bin/yunohost From 51009cf0646aba931a0b04d173de085a5a0bd052 Mon Sep 17 00:00:00 2001 From: opi Date: Mon, 23 Nov 2015 11:22:54 +0100 Subject: [PATCH 094/170] [enh] Get app label for installed app in app list. --- src/yunohost/app.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index de6c6bb55..0b3ad2a31 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -168,24 +168,29 @@ def app_list(offset=None, limit=None, filter=None, raw=False): sorted_app_dict[sorted_keys] = app_dict[sorted_keys] i = 0 - for app_id, app_info in sorted_app_dict.items(): + for app_id, app_info_dict in sorted_app_dict.items(): if i < limit: - if (filter and ((filter in app_id) or (filter in app_info['manifest']['name']))) or not filter: + if (filter and ((filter in app_id) or (filter in app_info_dict['manifest']['name']))) or not filter: installed = _is_installed(app_id) if raw: - app_info['installed'] = installed + app_info_dict['installed'] = installed if installed: - app_info['status'] = _get_app_status(app_id) - list_dict[app_id] = app_info + app_info_dict['status'] = _get_app_status(app_id) + list_dict[app_id] = app_info_dict else: + label = None + if installed: + app_info_dict_raw = app_info(app=app_id, raw=True) + label = app_info_dict_raw['settings']['label'] list_dict.append({ 'id': app_id, - 'name': app_info['manifest']['name'], + 'name': app_info_dict['manifest']['name'], + 'label': label, 'description': _value_for_locale( - app_info['manifest']['description']), + app_info_dict['manifest']['description']), # FIXME: Temporarly allow undefined license - 'license': app_info['manifest'].get('license', + 'license': app_info_dict['manifest'].get('license', m18n.n('license_undefined')), 'installed': installed }) From 23354e92bc0fe28e8dfb8f1620350920adc6b8ad Mon Sep 17 00:00:00 2001 From: opi Date: Tue, 24 Nov 2015 08:34:14 +0100 Subject: [PATCH 095/170] [enh] Add main domain GET route. --- data/actionsmap/yunohost.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 853c4e386..92b7d547d 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1198,6 +1198,13 @@ tools: pattern: *pattern_password required: True + ### tools_maindomain() + maindomain: + action_help: Get main domain + api: GET /domains/main + configuration: + authenticate: all + ### tools_maindomain() maindomain: action_help: Main domain change tool From 032f9c86edd73c6338b1d92d5f513976aa3b453a Mon Sep 17 00:00:00 2001 From: opi Date: Tue, 24 Nov 2015 11:22:01 +0100 Subject: [PATCH 096/170] [enh] Short cache on handlebars templates Backported from https://github.com/YunoHost/yunohost-config-nginx/pull/5 --- data/templates/nginx/yunohost_admin.conf.inc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/data/templates/nginx/yunohost_admin.conf.inc b/data/templates/nginx/yunohost_admin.conf.inc index 736102944..b0ab4cef6 100644 --- a/data/templates/nginx/yunohost_admin.conf.inc +++ b/data/templates/nginx/yunohost_admin.conf.inc @@ -2,4 +2,10 @@ location /yunohost/admin { alias /usr/share/yunohost/admin/; default_type text/html; index index.html; + + # Short cache on handlebars templates + location ~* \.(?:ms)$ { + expires 5m; + add_header Cache-Control "public"; + } } From 835000b2627a54924770e520b42c34d061d13a8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Mon, 30 Nov 2015 18:23:26 +0100 Subject: [PATCH 097/170] [enh] Provide bash helpers for MySQL databases (wip #97) --- data/apps/helpers.d/mysql | 92 ++++++++++++++++++++++++++++++++++++ data/apps/helpers.d/password | 4 ++ 2 files changed, 96 insertions(+) create mode 100644 data/apps/helpers.d/mysql diff --git a/data/apps/helpers.d/mysql b/data/apps/helpers.d/mysql new file mode 100644 index 000000000..b6ac12301 --- /dev/null +++ b/data/apps/helpers.d/mysql @@ -0,0 +1,92 @@ +MYSQL_ROOT_PWD_FILE=/etc/yunohost/mysql + +# Open a connection as a user +# +# example: ynh_mysql_connect_as 'user' 'pass' <<< "UPDATE ...;" +# example: ynh_mysql_connect_as 'user' 'pass' < /path/to/file.sql +# +# usage: ynh_mysql_connect_as user pwd [db] +# | arg: user - the user name to connect as +# | arg: pwd - the user password +# | arg: db - the database to connect to +ynh_mysql_connect_as() { + mysql -u "$1" --password="$2" -B "${3:-}" +} + +# Execute a command as root user +# +# usage: ynh_mysql_execute_as_root sql [db] +# | arg: sql - the SQL command to execute +# | arg: db - the database to connect to +ynh_mysql_execute_as_root() { + ynh_mysql_connect_as "root" "$(sudo cat $MYSQL_ROOT_PWD_FILE)" \ + "${2:-}" <<< "$1" +} + +# Execute a command from a file as root user +# +# usage: ynh_mysql_execute_file_as_root sql [db] +# | arg: file - the file containing SQL commands +# | arg: db - the database to connect to +ynh_mysql_execute_file_as_root() { + ynh_mysql_connect_as "root" "$(sudo cat $MYSQL_ROOT_PWD_FILE)" \ + "${2:-}" < "$1" +} + +# Create a database and grant optionnaly privilegies to a user +# +# usage: ynh_mysql_create_db db [user [pwd]] +# | arg: db - the database name to create +# | arg: user - the user to grant privilegies +# | arg: pwd - the password to identify user by +ynh_mysql_create_db() { + db=$1 + + sql="CREATE DATABASE ${db};" + + # grant all privilegies to user + if [[ $# -gt 1 ]]; then + sql+="GRANT ALL PRIVILEGES ON ${db}.* TO '${2}'@'localhost'" + [[ -n ${3:-} ]] && sql+=" IDENTIFIED BY '${3}'" + sql+=";" + fi + + ynh_mysql_execute_as_root "$sql" +} + +# Drop a database +# +# usage: ynh_mysql_drop_db db +# | arg: db - the database name to drop +ynh_mysql_drop_db() { + ynh_mysql_execute_as_root "DROP DATABASE ${1};" +} + +# Dump a database +# +# example: ynh_mysql_dump_db 'roundcube' > ./dump.sql +# +# usage: ynh_mysql_dump_db 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" +} + +# Create a user +# +# usage: ynh_mysql_create_user user pwd [host] +# | arg: user - the user name to create +# | arg: pwd - the password to identify user by +ynh_mysql_create_user() { + ynh_mysql_execute_as_root \ + "CREATE USER '${1}'@'localhost' IDENTIFIED BY '${2}';" +} + +# Drop a user +# +# usage: ynh_mysql_drop_user user +# | arg: user - the user name to drop +ynh_mysql_drop_user() { + ynh_mysql_execute_as_root "DROP USER '${1}'@'localhost';" +} diff --git a/data/apps/helpers.d/password b/data/apps/helpers.d/password index 0087921c4..b9a790885 100644 --- a/data/apps/helpers.d/password +++ b/data/apps/helpers.d/password @@ -1,3 +1,7 @@ + +# Generate a random password +# +# usage: ynh_password ynh_password() { echo $(dd if=/dev/urandom bs=1 count=200 2> /dev/null | tr -c -d '[A-Za-z0-9]' | sed -n 's/\(.\{24\}\).*/\1/p') } From 13dd9b4f89e2a4b9d4832378d325b186640ec3eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Mon, 30 Nov 2015 18:35:41 +0100 Subject: [PATCH 098/170] [fix] Use --output-as instead of deprecated options --- data/apps/helpers.d/user | 4 ++-- data/hooks/conf_regen/12-metronome | 2 +- data/hooks/conf_regen/15-nginx | 2 +- data/hooks/conf_regen/28-rmilter | 2 +- data/hooks/conf_regen/43-dnsmasq | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/data/apps/helpers.d/user b/data/apps/helpers.d/user index 1fd8d203c..69f96e0cf 100644 --- a/data/apps/helpers.d/user +++ b/data/apps/helpers.d/user @@ -3,7 +3,7 @@ # usage: ynh_user_exists username # | ret: retcode - 0 if user exists, 1 otherwise ynh_user_exists() { - sudo yunohost user list --json | grep -q "\"username\": \"${1}\"" + sudo yunohost user list --output-as json | grep -q "\"username\": \"${1}\"" } # Retrieve a user information @@ -11,5 +11,5 @@ ynh_user_exists() { # usage: ynh_user_info username key # | ret: string - the key's value ynh_user_get_info() { - sudo yunohost user info "${1}" --plain | ynh_get_plain_key "${2}" + sudo yunohost user info "${1}" --output-as plain | ynh_get_plain_key "${2}" } diff --git a/data/hooks/conf_regen/12-metronome b/data/hooks/conf_regen/12-metronome index ada37e8d8..69a13a1dc 100644 --- a/data/hooks/conf_regen/12-metronome +++ b/data/hooks/conf_regen/12-metronome @@ -28,7 +28,7 @@ safe_copy metronome.cfg.lua /etc/metronome/metronome.cfg.lua need_restart=False sudo mkdir -p /etc/metronome/conf.d -domain_list=$(sudo yunohost domain list --plain) +domain_list=$(sudo yunohost domain list --output-as plain) # Copy a configuration file for each YunoHost domain for domain in $domain_list; do diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx index 889d7408f..7a4795202 100644 --- a/data/hooks/conf_regen/15-nginx +++ b/data/hooks/conf_regen/15-nginx @@ -37,7 +37,7 @@ done if [ -f /etc/yunohost/installed ]; then need_restart=False - domain_list=$(sudo yunohost domain list --plain) + domain_list=$(sudo yunohost domain list --output-as plain) # Copy a configuration file for each YunoHost domain for domain in $domain_list; do diff --git a/data/hooks/conf_regen/28-rmilter b/data/hooks/conf_regen/28-rmilter index 7999318d9..abdb7d670 100644 --- a/data/hooks/conf_regen/28-rmilter +++ b/data/hooks/conf_regen/28-rmilter @@ -24,7 +24,7 @@ sudo chown _rmilter: /var/run/rmilter # Create DKIM key for each YunoHost domain sudo mkdir -p /etc/dkim -domain_list=$(sudo yunohost domain list --plain) +domain_list=$(sudo yunohost domain list --output-as plain) for domain in $domain_list; do [ -f /etc/dkim/$domain.mail.key ] \ diff --git a/data/hooks/conf_regen/43-dnsmasq b/data/hooks/conf_regen/43-dnsmasq index 4ce4ea6d0..dae14b0f6 100644 --- a/data/hooks/conf_regen/43-dnsmasq +++ b/data/hooks/conf_regen/43-dnsmasq @@ -24,7 +24,7 @@ ipv6=$(ip route get 2000:: | grep -q "unreachable" && echo '' \ sudo mkdir -p /etc/dnsmasq.d -domain_list=$(sudo yunohost domain list --plain) +domain_list=$(sudo yunohost domain list --output-as plain) # Copy a configuration file for each YunoHost domain for domain in $domain_list; do From d3c8a1bb2f5db3645adb11449d341251ccb2205d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Mon, 30 Nov 2015 19:02:12 +0100 Subject: [PATCH 099/170] [fix] Prevent error if unset variable is treated in utils helper --- data/apps/helpers.d/utils | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/apps/helpers.d/utils b/data/apps/helpers.d/utils index 87cd47f6b..a6755e3c8 100644 --- a/data/apps/helpers.d/utils +++ b/data/apps/helpers.d/utils @@ -12,7 +12,7 @@ ynh_get_plain_key() { [[ "$line" =~ ^${prefix}[^#] ]] && return echo $line elif [[ "$line" =~ ^${prefix}${key}$ ]]; then - if [[ -n "$1" ]]; then + if [[ -n "${1:-}" ]]; then prefix+="#" key=$1 shift From 3cf32aec261accc1337a4aeb5768e182e61dfed7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Mon, 30 Nov 2015 19:03:11 +0100 Subject: [PATCH 100/170] [doc] Improve usage and add examples for user helpers --- data/apps/helpers.d/user | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/data/apps/helpers.d/user b/data/apps/helpers.d/user index 69f96e0cf..fc58d0027 100644 --- a/data/apps/helpers.d/user +++ b/data/apps/helpers.d/user @@ -1,6 +1,9 @@ # Check if a user exists # +# example: ynh_user_exists 'toto' || exit 1 +# # usage: ynh_user_exists username +# | arg: username - the username to check # | ret: retcode - 0 if user exists, 1 otherwise ynh_user_exists() { sudo yunohost user list --output-as json | grep -q "\"username\": \"${1}\"" @@ -8,7 +11,11 @@ ynh_user_exists() { # Retrieve a user information # -# usage: ynh_user_info username key +# example: mail=$(ynh_user_get_info 'toto' 'mail') +# +# usage: ynh_user_get_info username key +# | arg: username - the username to retrieve info from +# | arg: key - the key to retrieve # | ret: string - the key's value ynh_user_get_info() { sudo yunohost user info "${1}" --output-as plain | ynh_get_plain_key "${2}" From 36bacba93fff392eb3db85fccb934e5db733d729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Mon, 30 Nov 2015 21:23:55 +0100 Subject: [PATCH 101/170] [enh] Rename ynh_password bash helper to ynh_string_random --- data/apps/helpers.d/password | 7 ------- data/apps/helpers.d/string | 11 +++++++++++ 2 files changed, 11 insertions(+), 7 deletions(-) delete mode 100644 data/apps/helpers.d/password create mode 100644 data/apps/helpers.d/string diff --git a/data/apps/helpers.d/password b/data/apps/helpers.d/password deleted file mode 100644 index b9a790885..000000000 --- a/data/apps/helpers.d/password +++ /dev/null @@ -1,7 +0,0 @@ - -# Generate a random password -# -# usage: ynh_password -ynh_password() { - echo $(dd if=/dev/urandom bs=1 count=200 2> /dev/null | tr -c -d '[A-Za-z0-9]' | sed -n 's/\(.\{24\}\).*/\1/p') -} diff --git a/data/apps/helpers.d/string b/data/apps/helpers.d/string new file mode 100644 index 000000000..a2bf0d463 --- /dev/null +++ b/data/apps/helpers.d/string @@ -0,0 +1,11 @@ +# Generate a random string +# +# example: pwd=$(ynh_string_random 8) +# +# usage: ynh_string_random [length] +# | arg: length - the string length to generate (default: 24) +ynh_string_random() { + dd if=/dev/urandom bs=1 count=200 2> /dev/null \ + | tr -c -d '[A-Za-z0-9]' \ + | sed -n 's/\(.\{'"${1:-24}"'\}\).*/\1/p' +} From a96c0d460f37757e50c1e4d378cbeb4912641fa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Thu, 17 Dec 2015 20:20:06 +0100 Subject: [PATCH 102/170] [i18n] Update translations from Transifex belatedly --- locales/it.json | 0 locales/nl.json | 31 +++++++++++++++++++++++++++++++ locales/tr.json | 31 +++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 locales/it.json create mode 100644 locales/nl.json create mode 100644 locales/tr.json diff --git a/locales/it.json b/locales/it.json new file mode 100644 index 000000000..e69de29bb diff --git a/locales/nl.json b/locales/nl.json new file mode 100644 index 000000000..3c43287a9 --- /dev/null +++ b/locales/nl.json @@ -0,0 +1,31 @@ +{ + "colon": "{}:", + "success": "Succes!", + "warning": "Waarschuwing:", + "error": "Fout:", + "permission_denied": "Toegang geweigerd", + "root_required": "Je moet root zijn om deze actie uit te voeren", + "instance_already_running": "Er is al een instantie aan het draaien", + "error_see_log": "Er is een fout opgetreden, zie logboek voor meer informatie", + "unable_authenticate": "Kan niet authenticeren", + "unable_retrieve_session": "Kan de sessie niet ophalen.", + "ldap_server_down": "Kan LDAP server niet bereiken", + "ldap_operation_error": "Er is een fout opgetreden bij het uitvoeren van LDAP Operatie", + "ldap_attribute_already_exists": "Attribuut bestaat al: '{:s}={:s}'", + "argument_required": "Argument{:s} is vereist", + "invalid_argument": "Onjuist argument '{:s]': {:s}", + "pattern_not_match": "Past niet in het patroo", + "password": "Wachtwoord", + "invalid_password": "Ongeldig wachtwoord", + "confirm": "Bevestig {:s}", + "values_mismatch": "Waarden zijn niet gelijk", + "authentication_required_long": "Authenticatie is vereist om deze actie uit te voeren", + "authentication_required": "Authenticatie vereist", + "authentication_profile_required": "Authenticatie tot profiel '{:s}' is vereist", + "operation_interrupted": "Operatie onderbroken", + "logged_in": "Ingelogd", + "logged_out": "Uitgelogd", + "not_logged_in": "U bent niet ingelogd", + "server_already_running": "Er is al een server aan het draaien op die poort", + "websocket_request_expected": "Verwachte een WebSocket request" +} diff --git a/locales/tr.json b/locales/tr.json new file mode 100644 index 000000000..ad87476a9 --- /dev/null +++ b/locales/tr.json @@ -0,0 +1,31 @@ +{ + "colon": "{}:", + "success": "İşlem Başarılı!", + "warning": "Uyarı:", + "error": "Hata:", + "permission_denied": "Erişim reddedildi", + "root_required": "Bu işlemi yapmak için root olmalısınız", + "instance_already_running": "Uygulama zaten çalışıyor.", + "error_see_log": "Bir hata oluştu. Detaylar için lütfen loga bakınız", + "unable_authenticate": "Yetkilendirme başarısız", + "unable_retrieve_session": "Oturum bilgileri alınamadı", + "ldap_server_down": "LDAP sunucusuna erişilemiyor", + "ldap_operation_error": "LDAP işlemi sırasında hata oluştu", + "ldap_attribute_already_exists": " '{:s}={:s}' özelliği zaten mevcut", + "argument_required": "{:s} argümanı gerekli", + "invalid_argument": "Geçersiz argüman '{:s}': {:s}", + "pattern_not_match": "İstenen biçimle uyuşmuyor", + "password": "Parola", + "invalid_password": "Geçersiz parola", + "confirm": "{:s}'i doğrulayın", + "values_mismatch": "Değerler uyuşmuyor", + "authentication_required_long": "Bu işlemi yapmak içi yetkilendirme gerekli", + "authentication_required": "Yetklendirme gerekli", + "authentication_profile_required": "'{:s}' profili için yetkilendirme gerekli", + "operation_interrupted": "İşlem yarıda kesildi", + "logged_in": "Giriş yapıldı", + "logged_out": "Çıkış yapıldı", + "not_logged_in": "Giriş yapmadınız", + "server_already_running": "Bu portta zaten çalışan bir sunucu var", + "websocket_request_expected": "WebSocket isteği gerekli" +} \ No newline at end of file From 7acc353c9aa70dbfc43a6470ed9e41016f442010 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Thu, 17 Dec 2015 20:24:15 +0100 Subject: [PATCH 103/170] [i18n] Initialize Italian translations --- locales/it.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/locales/it.json b/locales/it.json index e69de29bb..2c63c0851 100644 --- a/locales/it.json +++ b/locales/it.json @@ -0,0 +1,2 @@ +{ +} From 7f4b712016b5f9efeebe684390b0fa6d239c138f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Thu, 17 Dec 2015 21:04:15 +0100 Subject: [PATCH 104/170] [i18n] Sort json translation files by keys --- locales/de.json | 110 +++++++++--------- locales/es.json | 292 ++++++++++++++++++++++++------------------------ locales/fr.json | 185 ------------------------------ locales/it.json | 3 +- locales/nl.json | 44 ++++---- locales/pt.json | 228 ++++++++++++++++++------------------- locales/tr.json | 46 ++++---- 7 files changed, 361 insertions(+), 547 deletions(-) diff --git a/locales/de.json b/locales/de.json index 2e5c17edb..b3862b088 100644 --- a/locales/de.json +++ b/locales/de.json @@ -1,75 +1,75 @@ { - "yunohost_not_installed": "YunoHost ist nicht (oder nicht recht) installiert. Bitte 'yunohost tools postinstall' ablaufen.", - "upgrade_complete": "Upgrade erfolgreich beendet", - "installation_complete": "erfolgreich installiert", - "installation_failed": "Fehler beim Installation", - "app_unknown": "unbekannte App", - "app_no_upgrade": "Keine App zu updaten", - "app_not_installed": "{:s} ist nicht intalliert", - "custom_app_url_required": "Bitte eine URL geben, um deine nüzterspezifische App {:s} zu updaten", - "app_recent_version_required": "{:s} braucht eine jüngstere Fassung von \"moulinette\"", - "app_upgraded": "{:s} erfolgreich updaten", - "app_id_invalid": "Falsche App-ID", "app_already_installed": "{:s} ist schon installiert", - "app_removed": "{:s} erfolgreich gelöscht", + "app_extraction_failed": "Installationsdateien nicht extrahierbar", + "app_id_invalid": "Falsche App-ID", + "app_install_files_invalid": "Ungültige Installationsdateien", "app_location_already_used": "Eine App ist auf diesem Ort schon installiert", "app_location_install_failed": "Diese App ist auf diesem Ort nicht Installbar", - "app_extraction_failed": "Installationsdateien nicht extrahierbar", - "app_install_files_invalid": "Ungültige Installationsdateien", + "app_no_upgrade": "Keine App zu updaten", + "app_not_installed": "{:s} ist nicht intalliert", + "app_recent_version_required": "{:s} braucht eine jüngstere Fassung von \"moulinette\"", + "app_removed": "{:s} erfolgreich gelöscht", "app_sources_fetch_failed": "Quelledateien nicht abrufbar", - "ssowat_conf_updated": "SSOwat beständige Einstellung erfolgreich upgedatet", - "ssowat_conf_generated": "SSOwat-einstellung erfolgreich erzeugt", - "mysql_db_creation_failed": "Fehler beim MySQL-datenbankerzeugung", - "mysql_db_init_failed": "Fehler beim MySQL-datenbankinitialisierung", - "mysql_db_initialized": "MySQL-datenbank erfolgreich initialisiert", - "extracting": "Extrahierend...", - "downloading": "Herunterladend...", - "done": "Erledigt.", - "domain_unknown": "Unbekannte Domain", - "domain_dyndns_invalid": "Domain mit DynDNS nicht nützbar", - "domain_dyndns_already_subscribed": "Du hast dich schon für einen DynDNS-domain angemeldet", - "domain_dyndns_root_unknown": "Unbekannte DynDNS-hauptdomain", + "app_unknown": "unbekannte App", + "app_upgraded": "{:s} erfolgreich updaten", + "custom_app_url_required": "Bitte eine URL geben, um deine nüzterspezifische App {:s} zu updaten", "domain_cert_gen_failed": "Zertifizierung nicht erzeugbar", - "domain_exists": "diese Domain existiert schon", - "domain_creation_failed": "Domain nicht erzeugbar", "domain_created": "Domain erfolgreich erzeugt", - "domain_uninstall_app_first": "Mindestens eine App ist schon auf diese Domain installiert. Bitte erst die Apps deinstallieren, und nur dann die Domain löschen.", - "domain_deletion_failed": "Domain nicht löschbar", + "domain_creation_failed": "Domain nicht erzeugbar", "domain_deleted": "Domain erfolgreich gelöscht", - "no_internet_connection": "Server not connected to the Internet", - "dyndns_key_generating": "DNS key is being generated, it may take a while...", - "dyndns_unavailable": "DynDNS-subdomain nicht verfügbar", - "dyndns_registration_failed": "DynDNS-domain {:s} nicht registrierbar", - "dyndns_registered": "DynDNS-domain erfolgreich registriert", - "dyndns_ip_update_failed": "IP-adress auf DynDNS nicht updatbar", - "dyndns_ip_updated": "IP-adress auf DynDNS erfolgreich upgedatet", + "domain_deletion_failed": "Domain nicht löschbar", + "domain_dyndns_already_subscribed": "Du hast dich schon für einen DynDNS-domain angemeldet", + "domain_dyndns_invalid": "Domain mit DynDNS nicht nützbar", + "domain_dyndns_root_unknown": "Unbekannte DynDNS-hauptdomain", + "domain_exists": "diese Domain existiert schon", + "domain_uninstall_app_first": "Mindestens eine App ist schon auf diese Domain installiert. Bitte erst die Apps deinstallieren, und nur dann die Domain löschen.", + "domain_unknown": "Unbekannte Domain", + "done": "Erledigt.", + "downloading": "Herunterladend...", "dyndns_cron_installed": "DynDNS cron job erfolgreich installiert", "dyndns_cron_remove_failed": "DynDNS cron job nicht löschbar", "dyndns_cron_removed": "DynDNS cron job erfolgreich gelöscht", - "iptables_unavailable": "Du kannst nicht hier die IP-Tabelle bearbeiten. Entweder bist du in einen Container oder deinen Systemkern erhält es nicht.", + "dyndns_ip_update_failed": "IP-adress auf DynDNS nicht updatbar", + "dyndns_ip_updated": "IP-adress auf DynDNS erfolgreich upgedatet", + "dyndns_key_generating": "DNS key is being generated, it may take a while...", + "dyndns_registered": "DynDNS-domain erfolgreich registriert", + "dyndns_registration_failed": "DynDNS-domain {:s} nicht registrierbar", + "dyndns_unavailable": "DynDNS-subdomain nicht verfügbar", + "extracting": "Extrahierend...", "firewall_reloaded": "Firewall erfolgreich neu geladen", - "hook_choice_invalid": "ungültige Wahl '{:s}'", "hook_argument_missing": "Fehlend Argument '{:s}'", - "mountpoint_unknown": "unbekannten Einhängepunkt", - "unit_unknown": "unbekannte Einheit '{:s}'", - "monitor_period_invalid": "Falschen Zeitraum", - "monitor_stats_no_update": "Keine Monitoringstatistik zu updaten", - "monitor_stats_file_not_found": "Statistikdatei nicht gefunden", - "monitor_stats_period_unavailable": "Keine verfügbare Statistik für diese Zeitraum", - "monitor_enabled": "Servermonitoring erfolgreich aktiviert", + "hook_choice_invalid": "ungültige Wahl '{:s}'", + "installation_complete": "erfolgreich installiert", + "installation_failed": "Fehler beim Installation", + "iptables_unavailable": "Du kannst nicht hier die IP-Tabelle bearbeiten. Entweder bist du in einen Container oder deinen Systemkern erhält es nicht.", "monitor_disabled": "Servermonitoring erfolgreich deaktiviert", - "monitor_not_enabled": "Servermonitoring ist nicht aktiviert", + "monitor_enabled": "Servermonitoring erfolgreich aktiviert", "monitor_glances_con_failed": "Verbindung mit Glances-server nicht möglich", - "service_unknown": "Unbekannte Dienst '{:s}'", - "service_start_failed": "Kann nicht '{:s}' -dienst starten", + "monitor_not_enabled": "Servermonitoring ist nicht aktiviert", + "monitor_period_invalid": "Falschen Zeitraum", + "monitor_stats_file_not_found": "Statistikdatei nicht gefunden", + "monitor_stats_no_update": "Keine Monitoringstatistik zu updaten", + "monitor_stats_period_unavailable": "Keine verfügbare Statistik für diese Zeitraum", + "mountpoint_unknown": "unbekannten Einhängepunkt", + "mysql_db_creation_failed": "Fehler beim MySQL-datenbankerzeugung", + "mysql_db_init_failed": "Fehler beim MySQL-datenbankinitialisierung", + "mysql_db_initialized": "MySQL-datenbank erfolgreich initialisiert", + "no_internet_connection": "Server not connected to the Internet", "service_already_started": "'{:s}' -dienst ist schon im Betrieb", - "service_started": "'{:s}' -dienst erfolgreich gestartet", - "service_stop_failed": "Kann nicht '{:s}' -dienst stoppen", "service_already_stopped": "'{:s}' -dienst ist schon abgestoppt", - "service_stopped": "'{:s}' -dienst erfolgreich abgestoppt", - "service_enable_failed": "Kann nicht '{:s}' -dienst aktivieren", - "service_enabled": "'{:s}' -dienst erfolgreich aktiviert", "service_disable_failed": "Kann nicht '{:s}' -dienst deaktivieren", "service_disabled": "'{:s}' -dienst erfolgreich deaktiviert", - "service_status_failed": "Kann nicht '{:s}' -dienststatus feststellen" + "service_enable_failed": "Kann nicht '{:s}' -dienst aktivieren", + "service_enabled": "'{:s}' -dienst erfolgreich aktiviert", + "service_start_failed": "Kann nicht '{:s}' -dienst starten", + "service_started": "'{:s}' -dienst erfolgreich gestartet", + "service_status_failed": "Kann nicht '{:s}' -dienststatus feststellen", + "service_stop_failed": "Kann nicht '{:s}' -dienst stoppen", + "service_stopped": "'{:s}' -dienst erfolgreich abgestoppt", + "service_unknown": "Unbekannte Dienst '{:s}'", + "ssowat_conf_generated": "SSOwat-einstellung erfolgreich erzeugt", + "ssowat_conf_updated": "SSOwat beständige Einstellung erfolgreich upgedatet", + "unit_unknown": "unbekannte Einheit '{:s}'", + "upgrade_complete": "Upgrade erfolgreich beendet", + "yunohost_not_installed": "YunoHost ist nicht (oder nicht recht) installiert. Bitte 'yunohost tools postinstall' ablaufen." } diff --git a/locales/es.json b/locales/es.json index 096447989..7eaf09dd9 100644 --- a/locales/es.json +++ b/locales/es.json @@ -1,176 +1,176 @@ { - "yunohost_not_installed": "YunoHost no está instalado o la instilación ha cumplido con errores. Por favor, ejecute 'yunohost tools postinstall'.", - "upgrade_complete": "La actualización se ha completado", - "installation_complete": "La instalación se ha completado", - "installation_failed": "La Instalación se ha fracasado", - "unexpected_error": "Un error ha ocurrido", "action_invalid": "Acción inválida '{:s}'", - "license_undefined": "indefinido", - "no_appslist_found": "No se encontró ninguna lista de Apps", - "custom_appslist_name_required": "Debe proporcionar un nombre para la lista de aplicaciones personalizadas ", - "appslist_retrieve_error": "No se pudo recuperar la lista de aplicaciones a distancia ", - "appslist_fetched": "Lista de aplicaciones se trajo con éxito", - "appslist_unknown": "Lista de aplicaciones desconocidas", - "appslist_removed": "Lista de aplicaciones se eliminó con éxito", - "app_unknown": "App desconocida", - "app_no_upgrade": "Ninguna app a actualizar", - "app_not_installed": "{:s} no está instalado.", - "custom_app_url_required": " Debe proporcionar una URL para actualizar su aplicación personalizada {:s} ", - "app_recent_version_required": "{:s} requiere una versión más reciente de moulinette ", - "app_upgraded": "{:s} actualizado con éxito", - "app_upgrade_failed": "No se pudo actualizar todas las aplicaciones ", - "app_id_invalid": "id de la aplicación inválida ", + "admin_password": "Contraseña administrativa", + "admin_password_change_failed": "No se pudo cambiar la contraseña", + "admin_password_changed": "Contraseña administrativa se cambió con éxito", "app_already_installed": "{:s} ya está instalado ", - "app_removed": "{:s} era eliminado con éxito ", + "app_extraction_failed": "No se pudo extraer los archivos de instalación ", + "app_id_invalid": "id de la aplicación inválida ", + "app_install_files_invalid": "Archivos de instalación inválidos ", "app_location_already_used": "Una aplicación ya está instalado en este lugar", "app_location_install_failed": "No se pudo instalar la aplicación en esta lugar", - "app_extraction_failed": "No se pudo extraer los archivos de instalación ", - "app_install_files_invalid": "Archivos de instalación inválidos ", "app_manifest_invalid": "Manifesto de la aplicación es inválido", + "app_no_upgrade": "Ninguna app a actualizar", + "app_not_installed": "{:s} no está instalado.", + "app_recent_version_required": "{:s} requiere una versión más reciente de moulinette ", + "app_removed": "{:s} era eliminado con éxito ", "app_sources_fetch_failed": "No se pudo descargar los archivos de códigos fuentes", - "ssowat_conf_updated": "Configuración persistente SSOwat actualizada con éxito", - "ssowat_conf_generated": "Configuración SSOwat generado con éxito ", - "mysql_db_creation_failed": "No se pudo crear el base de datos MySQL", - "mysql_db_init_failed": "No se pudo inicializar el base de datos MySQL.", - "mysql_db_initialized": "Base de datos MySQL inicializado con éxito", - "extracting": "Extrayendo...", - "downloading": "Descargando...", - "executing_script": "Ejecutando script...", - "done": "Completo.", - "path_removal_failed": "No se pudo quitar la ruta {:s}", - "domain_unknown": "Dominio desconocido", - "domain_dyndns_invalid": "El dominio no es valido para usar con DynDNS", - "domain_dyndns_already_subscribed": "Ya te has suscrito a un dominio DynDNS.", - "domain_dyndns_root_unknown": "Dominio raíz DynDNS desconocido ", - "domain_cert_gen_failed": "No se pudo crear certificado", - "domain_exists": "El dominio ya existe", + "app_unknown": "App desconocida", + "app_upgrade_failed": "No se pudo actualizar todas las aplicaciones ", + "app_upgraded": "{:s} actualizado con éxito", + "appslist_fetched": "Lista de aplicaciones se trajo con éxito", + "appslist_removed": "Lista de aplicaciones se eliminó con éxito", + "appslist_retrieve_error": "No se pudo recuperar la lista de aplicaciones a distancia ", + "appslist_unknown": "Lista de aplicaciones desconocidas", + "ask_current_admin_password": "Contraseña administrativa presente", + "ask_email": "Correo electrónico", + "ask_firstname": "Nombre", + "ask_lastname": "Apellido", + "ask_list_to_remove": "Lista a quitar", + "ask_main_domain": "Dominio principal", + "ask_new_admin_password": "Contraseña administrativa nueva", + "ask_password": "Contraseña", + "backup_archive_name_exists": "Un archivo ya existe con el nombre del archivo de backup", + "backup_archive_name_unknown": "El nombre archivo local de backup está desconocido", + "backup_archive_open_failed": "No se pudo abrir el archivo backup", + "backup_complete": "El backup se ha completado", + "backup_creating_archive": "Creando el archivo backup...", + "backup_extracting_archive": "Extrayendo el archivo backup...", + "backup_invalid_archive": "Archivo de backup es inválido", + "backup_output_directory_forbidden": "Carpeta de salida prohibida", + "backup_output_directory_not_empty": "La carpeta de salida no está vacía", + "backup_output_directory_required": "Debe proporcionar un directorio de salida para el backup", + "backup_running_hooks": "Ejecutando los hooks de backup...", + "custom_app_url_required": " Debe proporcionar una URL para actualizar su aplicación personalizada {:s} ", + "custom_appslist_name_required": "Debe proporcionar un nombre para la lista de aplicaciones personalizadas ", "dnsmasq_isnt_installed": "Parece que dnsmasq no está instalado, por favor, ejecuta 'apt-get remove bind9 && apt-get install dnsmasq'", + "domain_cert_gen_failed": "No se pudo crear certificado", + "domain_created": "Dominio creado con éxito.", + "domain_creation_failed": "No se pudo crear el dominio", + "domain_deleted": "Dominio borrado con éxito.", + "domain_deletion_failed": "No se pudo borrar el dominio.", + "domain_dyndns_already_subscribed": "Ya te has suscrito a un dominio DynDNS.", + "domain_dyndns_invalid": "El dominio no es valido para usar con DynDNS", + "domain_dyndns_root_unknown": "Dominio raíz DynDNS desconocido ", + "domain_exists": "El dominio ya existe", + "domain_uninstall_app_first": "Uno o más apps están instalados en este dominio. Por favor, desinstalarlos antes de quitar este dominio.", + "domain_unknown": "Dominio desconocido", "domain_zone_exists": "El archivo de zonas DNS ya existe.", "domain_zone_not_found": "Archivo de zonas DNS por el dominio [:s] no estaba encontrado", - "domain_creation_failed": "No se pudo crear el dominio", - "domain_created": "Dominio creado con éxito.", - "domain_uninstall_app_first": "Uno o más apps están instalados en este dominio. Por favor, desinstalarlos antes de quitar este dominio.", - "domain_deletion_failed": "No se pudo borrar el dominio.", - "domain_deleted": "Dominio borrado con éxito.", - "no_internet_connection": "El servidor no está conectado al Internet.", - "dyndns_key_generating": "Generación del llave de DNS está en curso. Este podría durar unos momentos...", - "dyndns_unavailable": "Subdominio DynDNS no disponible", - "dyndns_registration_failed": "No se pudo registrar el dominio DynDNS: {:s}", - "dyndns_registered": "El dominio DynDNS era registrado con éxito.", - "dyndns_ip_update_failed": "No se pudo actualizar la dirección IP en DynDNS", - "dyndns_ip_updated": "La dirección IP era actualizado en DynDNS con éxito", + "done": "Completo.", + "downloading": "Descargando...", "dyndns_cron_installed": "El trabajo cron de DynDNS se ha instalado con éxito", "dyndns_cron_remove_failed": "No se pudo quitar el trabajo cron DynDNS", "dyndns_cron_removed": "Trabajo cron DynDNS se quitó con éxito", - "port_available": "El puerto {} está disponible", - "port_unavailable": "El puerto {} no está disponible", - "port_already_opened": "El puerto {} ya está abierto por {:s} connecciones", - "port_already_closed": "El puerto {} ya está cerrado por {:s} connecciones.", - "iptables_unavailable": "No puedes modificar los iptables aquí. Eres en un contenedor o su kernel no soporte este opción.", - "ip6tables_unavailable": "No puedes modificar los ip6tables aquí. Eres en un contenedor o su kernel no soporte este opción.", - "upnp_dev_not_found": "No se encontró ninguno dispositivo UPnP ", - "upnp_port_open_failed": "No se pudo abrir puertos por UPnP", - "upnp_enabled": "UPnP activado con éxito", - "upnp_disabled": "UPnP deshabilitado con éxito", - "firewall_rules_cmd_failed": "Algunos reglas del cortafuegos han fracasado. Para más información, vea al archivo historial.", + "dyndns_ip_update_failed": "No se pudo actualizar la dirección IP en DynDNS", + "dyndns_ip_updated": "La dirección IP era actualizado en DynDNS con éxito", + "dyndns_key_generating": "Generación del llave de DNS está en curso. Este podría durar unos momentos...", + "dyndns_registered": "El dominio DynDNS era registrado con éxito.", + "dyndns_registration_failed": "No se pudo registrar el dominio DynDNS: {:s}", + "dyndns_unavailable": "Subdominio DynDNS no disponible", + "executing_script": "Ejecutando script...", + "extracting": "Extrayendo...", + "field_invalid": "Campo inválido '{:s}'", "firewall_reload_failed": "No se pudo recargar el cortafuegos", "firewall_reloaded": "Cortafuegos recargado con éxito", + "firewall_rules_cmd_failed": "Algunos reglas del cortafuegos han fracasado. Para más información, vea al archivo historial.", + "hook_argument_missing": "Falta un parámetro '{:s}'", + "hook_choice_invalid": "Selección inválida '{:s}'", "hook_list_by_invalid": "La propiedad de este hook es inválida", "hook_name_unknown": "Hook desconocido '{:s}'", - "hook_choice_invalid": "Selección inválida '{:s}'", - "hook_argument_missing": "Falta un parámetro '{:s}'", - "mountpoint_unknown": "Punto de montaje desconocido", - "unit_unknown": "Unidad '{:s}' desconocido", - "monitor_period_invalid": "Período de tiempo inválido", - "monitor_stats_no_update": "No hay ninguna estadísticos de la supervisión del sistema a realizar", - "monitor_stats_file_not_found": "No se pudo encontrar el archivo de estadísticos", - "monitor_stats_period_unavailable": "No hay estadísticos del período del tiempo", - "monitor_enabled": "Supervisión del sistema activado con éxito", + "installation_complete": "La instalación se ha completado", + "installation_failed": "La Instalación se ha fracasado", + "ip6tables_unavailable": "No puedes modificar los ip6tables aquí. Eres en un contenedor o su kernel no soporte este opción.", + "iptables_unavailable": "No puedes modificar los iptables aquí. Eres en un contenedor o su kernel no soporte este opción.", + "ldap_initialized": "LDAP se inició con éxito", + "license_undefined": "indefinido", + "mail_alias_remove_failed": "No se pudo quitar el alias de correos '{:s}'", + "mail_domain_unknown": "El dominio de correos '{:s}' es desconocido", + "mail_forward_remove_failed": "No se pudo quitar la reenvía de correos '{:s}'", + "maindomain_change_failed": "No se pudo cambiar el dominio principal", + "maindomain_changed": "Dominio principal se cambió con éxito", "monitor_disabled": "Supervisión del sistema era desactivado con éxito", - "monitor_not_enabled": "Supervisión del sistema no está activado", + "monitor_enabled": "Supervisión del sistema activado con éxito", "monitor_glances_con_failed": "No se pudo conectar al servidor de Glances", - "service_unknown": "Servicio desconocido '{:s}'", + "monitor_not_enabled": "Supervisión del sistema no está activado", + "monitor_period_invalid": "Período de tiempo inválido", + "monitor_stats_file_not_found": "No se pudo encontrar el archivo de estadísticos", + "monitor_stats_no_update": "No hay ninguna estadísticos de la supervisión del sistema a realizar", + "monitor_stats_period_unavailable": "No hay estadísticos del período del tiempo", + "mountpoint_unknown": "Punto de montaje desconocido", + "mysql_db_creation_failed": "No se pudo crear el base de datos MySQL", + "mysql_db_init_failed": "No se pudo inicializar el base de datos MySQL.", + "mysql_db_initialized": "Base de datos MySQL inicializado con éxito", + "new_domain_required": "Debe proporcionar el dominio principal nuevo", + "no_appslist_found": "No se encontró ninguna lista de Apps", + "no_internet_connection": "El servidor no está conectado al Internet.", + "packages_no_upgrade": "No hay actualización por ningun paquete", + "packages_upgrade_critical_later": "Los paquetes críticos ({:s}) se actualizarán más tarde", + "packages_upgrade_failed": "No se pudo actualizar todo de los paquetes", + "path_removal_failed": "No se pudo quitar la ruta {:s}", + "pattern_backup_archive_name": "Debe que ser un nombre de archivo válido con los caracteres alfanumericos, o los -_.", + "pattern_domain": "El nombre de dominio debe ser válido (e.g. mi-dominio.org)", + "pattern_email": "Debe ser una direccion de email válido (e.g. alguien@dominio.org)", + "pattern_firstname": "Debe ser un nombre válido", + "pattern_lastname": "Debe ser un apellido válido", + "pattern_listname": "Los caracteres deben ser alfanuméricos o el guion bajo.", + "pattern_password": "Debe ser a menos de 3 caracteres", + "pattern_port": "El numéro del puerto debe ser válido (i.e. 0-65535)", + "pattern_port_or_range": "El numéro del puerto debe ser válido (i.e. 0-65535) o un intervalo de puertos (e.g. 100:200)", + "pattern_username": "Debe contener solamente caracteres alfanuméricos o la guion bajo", + "port_already_closed": "El puerto {} ya está cerrado por {:s} connecciones.", + "port_already_opened": "El puerto {} ya está abierto por {:s} connecciones", + "port_available": "El puerto {} está disponible", + "port_unavailable": "El puerto {} no está disponible", + "restore_complete": "Restauración se ha completado", + "restore_confirm_yunohost_installed": "Estás seguro que quieres restaurar a un sistema que ya está instalado? [{answers:s}]", + "restore_failed": "No se pudo restaurar el sistema", + "restore_running_hooks": "Ejecutando hooks de restauración...", "service_add_failed": "No se pudo añadir el servicio '{:s}'", "service_added": "Servicio añadido con éxito", + "service_already_started": "El servicio '{:s}' ya se ha empezado", + "service_already_stopped": "El servicio '{:s}' ya está parado ", + "service_cmd_exec_failed": "No se pudo ejecutar comando '{:s}'", + "service_disable_failed": "No se pudo desactivar el servicio '{:s}'", + "service_disabled": "Servicio '{:s}' desactivado con éxito", + "service_enable_failed": "No se pudo activar el servicio '{:s}'", + "service_enabled": "Servicio '{:s}' activado con éxito", + "service_no_log": "No hay archivo historial del servicio '{:s}' a exhibir", "service_remove_failed": "No se pudo quitar el servicio '{:s}'", "service_removed": "Servicio quitado con éxito", "service_start_failed": "No se pudo empezar el servicio '{:s}'", - "service_already_started": "El servicio '{:s}' ya se ha empezado", "service_started": "El servicio '{:s}' se empezó con éxito", - "service_stop_failed": "No se pudo parar el servicio '{:s}'", - "service_already_stopped": "El servicio '{:s}' ya está parado ", - "service_stopped": "Servicio '{:s}' parado con éxito", - "service_enable_failed": "No se pudo activar el servicio '{:s}'", - "service_enabled": "Servicio '{:s}' activado con éxito", - "service_disable_failed": "No se pudo desactivar el servicio '{:s}'", - "service_disabled": "Servicio '{:s}' desactivado con éxito", "service_status_failed": "No se pudo discernir el estado del servicio '{:s}'", - "service_no_log": "No hay archivo historial del servicio '{:s}' a exhibir", - "service_cmd_exec_failed": "No se pudo ejecutar comando '{:s}'", - "ldap_initialized": "LDAP se inició con éxito", - "admin_password_change_failed": "No se pudo cambiar la contraseña", - "admin_password_changed": "Contraseña administrativa se cambió con éxito", - "new_domain_required": "Debe proporcionar el dominio principal nuevo", - "maindomain_change_failed": "No se pudo cambiar el dominio principal", - "maindomain_changed": "Dominio principal se cambió con éxito", - "yunohost_installing": "Instalando YunoHost...", + "service_stop_failed": "No se pudo parar el servicio '{:s}'", + "service_stopped": "Servicio '{:s}' parado con éxito", + "service_unknown": "Servicio desconocido '{:s}'", + "ssowat_conf_generated": "Configuración SSOwat generado con éxito ", + "ssowat_conf_updated": "Configuración persistente SSOwat actualizada con éxito", + "system_upgraded": "Actualización del sistema se ha completado con éxito.", + "system_username_exists": "Nombre de usuario ya existe en los usuarios del sistema", + "unbackup_app": "La App '{:s}' no será guardada", + "unexpected_error": "Un error ha ocurrido", + "unit_unknown": "Unidad '{:s}' desconocido", + "unrestore_app": "La App '{:s}' no será restaurada", + "update_cache_failed": "No se pudo actualizar el cache APT", + "updating_apt_cache": "Actualizando la lista de paquetes disponibles...", + "upgrade_complete": "La actualización se ha completado", + "upgrading_packages": "Actualizando paquetes...", + "upnp_dev_not_found": "No se encontró ninguno dispositivo UPnP ", + "upnp_disabled": "UPnP deshabilitado con éxito", + "upnp_enabled": "UPnP activado con éxito", + "upnp_port_open_failed": "No se pudo abrir puertos por UPnP", + "user_created": "Usuario creado con éxito", + "user_creation_failed": "No se pudo crear un usuario nuevo", + "user_deleted": "Usuario creado con éxito", + "user_deletion_failed": "No se pudo quitar el usuario", + "user_info_failed": "No se pudo traer la información del usuario. ", + "user_unknown": "usuario desconocido", + "user_update_failed": "No se pudo actualizar el usuario", + "user_updated": "Usuario actualizado con éxito", "yunohost_already_installed": "YunoHost ya está instalado", "yunohost_ca_creation_failed": "No se pudo crear un autoridad de certificación nuevo", "yunohost_configured": "YunoHost se configuró con éxito.", - "updating_apt_cache": "Actualizando la lista de paquetes disponibles...", - "update_cache_failed": "No se pudo actualizar el cache APT", - "packages_no_upgrade": "No hay actualización por ningun paquete", - "packages_upgrade_critical_later": "Los paquetes críticos ({:s}) se actualizarán más tarde", - "upgrading_packages": "Actualizando paquetes...", - "packages_upgrade_failed": "No se pudo actualizar todo de los paquetes", - "system_upgraded": "Actualización del sistema se ha completado con éxito.", - "backup_output_directory_required": "Debe proporcionar un directorio de salida para el backup", - "backup_output_directory_forbidden": "Carpeta de salida prohibida", - "backup_output_directory_not_empty": "La carpeta de salida no está vacía", - "backup_running_hooks": "Ejecutando los hooks de backup...", - "backup_creating_archive": "Creando el archivo backup...", - "backup_extracting_archive": "Extrayendo el archivo backup...", - "backup_archive_open_failed": "No se pudo abrir el archivo backup", - "backup_archive_name_unknown": "El nombre archivo local de backup está desconocido", - "backup_archive_name_exists": "Un archivo ya existe con el nombre del archivo de backup", - "backup_complete": "El backup se ha completado", - "backup_invalid_archive": "Archivo de backup es inválido", - "restore_confirm_yunohost_installed": "Estás seguro que quieres restaurar a un sistema que ya está instalado? [{answers:s}]", - "restore_running_hooks": "Ejecutando hooks de restauración...", - "restore_failed": "No se pudo restaurar el sistema", - "restore_complete": "Restauración se ha completado", - "unbackup_app": "La App '{:s}' no será guardada", - "unrestore_app": "La App '{:s}' no será restaurada", - "field_invalid": "Campo inválido '{:s}'", - "mail_domain_unknown": "El dominio de correos '{:s}' es desconocido", - "mail_alias_remove_failed": "No se pudo quitar el alias de correos '{:s}'", - "mail_forward_remove_failed": "No se pudo quitar la reenvía de correos '{:s}'", - "user_unknown": "usuario desconocido", - "system_username_exists": "Nombre de usuario ya existe en los usuarios del sistema", - "user_creation_failed": "No se pudo crear un usuario nuevo", - "user_created": "Usuario creado con éxito", - "user_deletion_failed": "No se pudo quitar el usuario", - "user_deleted": "Usuario creado con éxito", - "user_update_failed": "No se pudo actualizar el usuario", - "user_updated": "Usuario actualizado con éxito", - "user_info_failed": "No se pudo traer la información del usuario. ", - "admin_password": "Contraseña administrativa", - "ask_firstname": "Nombre", - "ask_lastname": "Apellido", - "ask_email": "Correo electrónico", - "ask_password": "Contraseña", - "ask_current_admin_password": "Contraseña administrativa presente", - "ask_new_admin_password": "Contraseña administrativa nueva", - "ask_main_domain": "Dominio principal", - "ask_list_to_remove": "Lista a quitar", - "pattern_username": "Debe contener solamente caracteres alfanuméricos o la guion bajo", - "pattern_firstname": "Debe ser un nombre válido", - "pattern_lastname": "Debe ser un apellido válido", - "pattern_email": "Debe ser una direccion de email válido (e.g. alguien@dominio.org)", - "pattern_password": "Debe ser a menos de 3 caracteres", - "pattern_domain": "El nombre de dominio debe ser válido (e.g. mi-dominio.org)", - "pattern_listname": "Los caracteres deben ser alfanuméricos o el guion bajo.", - "pattern_port": "El numéro del puerto debe ser válido (i.e. 0-65535)", - "pattern_port_or_range": "El numéro del puerto debe ser válido (i.e. 0-65535) o un intervalo de puertos (e.g. 100:200)", - "pattern_backup_archive_name": "Debe que ser un nombre de archivo válido con los caracteres alfanumericos, o los -_." + "yunohost_installing": "Instalando YunoHost...", + "yunohost_not_installed": "YunoHost no está instalado o la instilación ha cumplido con errores. Por favor, ejecute 'yunohost tools postinstall'." } diff --git a/locales/fr.json b/locales/fr.json index b2e0f38c6..e69de29bb 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -1,185 +0,0 @@ -{ - "action_invalid": "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_changed": "Mot de passe d'administration modifié avec succès", - "app_already_installed": "{:s} est déjà installé", - "app_extraction_failed": "Impossible d'extraire les fichiers d'installation", - "app_id_invalid": "Id d'application incorrect", - "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_no_upgrade": "Aucune application à mettre à jour", - "app_not_installed": "{:s} n'est pas installé", - "app_recent_version_required": "{:s} nécessite une version plus récente de la moulinette", - "app_removed": "{:s} supprimé avec succès", - "app_sources_fetch_failed": "Impossible de récupérer les fichiers sources", - "app_unknown": "Application inconnue", - "app_upgrade_failed": "Impossible de mettre à jour toutes les applications", - "app_upgraded": "{:s} mis à jour avec succès", - "appslist_fetched": "Liste d'applications récupérée avec succès", - "appslist_removed": "Liste d'applications supprimée avec succès", - "appslist_retrieve_error": "Impossible de récupérer la liste d'applications distante", - "appslist_unknown": "Liste d'applications inconnue", - "ask_current_admin_password": "Mot de passe d'administration actuel", - "ask_email": "Adresse mail", - "ask_firstname": "Prénom", - "ask_lastname": "Nom", - "ask_list_to_remove": "Liste à supprimer", - "ask_main_domain": "Domaine principal", - "ask_new_admin_password": "Nouveau mot de passe d'administration", - "ask_password": "Mot de passe", - "backup_archive_name_exists": "Une archive de sauvegarde avec ce nom existe déjà", - "backup_archive_name_unknown": "Nom d'archive de sauvegarde locale inconnu", - "backup_archive_open_failed": "Impossible d'ouvrir l'archive de sauvegarde", - "backup_complete": "Sauvegarde terminée", - "backup_creating_archive": "Création de l'archive de sauvegarde...", - "backup_extracting_archive": "Extraction de l'archive de sauvegarde...", - "backup_hook_unknown": "Script de sauvegarde '{hook:s}' inconnu", - "backup_invalid_archive": "Archive de sauvegarde incorrecte", - "backup_output_directory_forbidden": "Dossier de sortie interdit", - "backup_output_directory_not_empty": "Le dossier de sortie n'est pas vide", - "backup_output_directory_required": "Vous devez spécifier un dossier de sortie pour la sauvegarde", - "backup_running_app_script": "Lancement du script de sauvegarde de l'application '{:s}'...", - "backup_running_hooks": "Exécution des scripts de sauvegarde...", - "custom_app_url_required": "Vous devez spécifier une URL pour mettre à jour votre application locale {:s}", - "custom_appslist_name_required": "Vous devez spécifier un nom pour votre liste d'applications personnalisée", - "dnsmasq_isnt_installed": "dnsmasq ne semble pas être installé, veuillez lancer 'apt-get remove bind9 && apt-get install dnsmasq'", - "domain_cert_gen_failed": "Impossible de générer le certificat", - "domain_created": "Domaine créé avec succès", - "domain_creation_failed": "Impossible de créer le domaine", - "domain_deleted": "Domaine supprimé avec succès", - "domain_deletion_failed": "Impossible de supprimer le domaine", - "domain_dyndns_already_subscribed": "Vous avez déjà souscris à un domaine DynDNS", - "domain_dyndns_invalid": "Domaine incorrect pour un usage avec DynDNS", - "domain_dyndns_root_unknown": "Domaine DynDNS principal inconnu", - "domain_exists": "Le domaine existe déjà", - "domain_uninstall_app_first": "Une ou plusieurs applications sont installées sur ce domaine. Veuillez d'abord les désinstaller avant de supprimer ce domaine.", - "domain_unknown": "Domaine inconnu", - "domain_zone_exists": "Le fichier de zone DNS existe déjà", - "domain_zone_not_found": "Fichier de zone DNS introuvable pour le domaine {:s}", - "done": "Terminé.", - "downloading": "Téléchargement...", - "dyndns_cron_installed": "Tâche cron pour DynDNS installée avec succès", - "dyndns_cron_remove_failed": "Impossible d'enlever la tâche cron pour DynDNS", - "dyndns_cron_removed": "Tâche cron pour DynDNS enlevée avec succès", - "dyndns_ip_update_failed": "Impossible de mettre à jour l'adresse IP sur le domaine DynDNS", - "dyndns_ip_updated": "Adresse IP mise à jour avec succès sur le domaine DynDNS", - "dyndns_key_generating": "La clé DNS est en cours de génération, cela peut prendre du temps...", - "dyndns_registered": "Domaine DynDNS enregistré avec succès", - "dyndns_registration_failed": "Impossible d'enregistrer le domaine DynDNS : {:s}", - "dyndns_unavailable": "Sous-domaine DynDNS indisponible", - "executing_script": "Exécution du script...", - "extracting": "Extraction...", - "field_invalid": "Champ incorrect : {:s}", - "firewall_reload_failed": "Impossible de recharger le pare-feu", - "firewall_reloaded": "Pare-feu rechargé avec succès", - "firewall_rules_cmd_failed": "Certaines règles du pare-feu n'ont pas pu être appliquées. Pour plus d'informations, consultez le journal.", - "format_datetime_short": "%d/%m/%Y %H:%M", - "hook_argument_missing": "Argument manquant : '{:s}'", - "hook_choice_invalid": "Choix incorrect : '{:s}'", - "hook_list_by_invalid": "Propriété pour lister les scripts incorrecte", - "hook_name_unknown": "Nom de script '{:s}' inconnu", - "installation_complete": "Installation terminée", - "installation_failed": "Échec de l'installation", - "ip6tables_unavailable": "Vous ne pouvez pas faire joujou avec ip6tables ici. Vous êtes sûrement dans un conteneur, autrement votre noyau ne le supporte pas.", - "iptables_unavailable": "Vous ne pouvez pas faire joujou avec iptables ici. Vous êtes sûrement dans un conteneur, autrement votre noyau ne le supporte pas.", - "ldap_initialized": "Répertoire LDAP initialisé avec succès", - "license_undefined": "indéfinie", - "mail_alias_remove_failed": "Impossible de supprimer l'adresse mail supplémentaire '{:s}'", - "mail_domain_unknown": "Domaine '{:s}' de l'adresse mail inconnu", - "mail_forward_remove_failed": "Impossible de supprimer l'adresse mail de transfert '{:s}'", - "maindomain_change_failed": "Impossible de modifier le domaine principal", - "maindomain_changed": "Domaine principal modifié avec succès", - "monitor_disabled": "Suivi de l'état du serveur désactivé avec succès", - "monitor_enabled": "Suivi de l'état du serveur activé avec succès", - "monitor_glances_con_failed": "Impossible de se connecter au serveur Glances", - "monitor_not_enabled": "Le suivi de l'état du serveur n'est pas activé", - "monitor_period_invalid": "Période de temps incorrect", - "monitor_stats_file_not_found": "Fichier de données de l'état du serveur introuvable", - "monitor_stats_no_update": "Aucune donnée de l'état du serveur à mettre à jour", - "monitor_stats_period_unavailable": "Aucune donnée de l'état du serveur disponible pour la période", - "mountpoint_unknown": "Point de montage inconnu", - "mysql_db_creation_failed": "Impossible de créer la base de donnée MySQL", - "mysql_db_init_failed": "Impossible d'initialiser la base de donnée MySQL", - "mysql_db_initialized": "Base de donnée MySQL initialisée avec succès", - "new_domain_required": "Vous devez spécifier le nouveau domaine principal", - "no_appslist_found": "Aucune liste d'applications trouvée", - "no_internet_connection": "Le serveur n'est pas connecté à Internet", - "no_ipv6_connectivity": "IPv6 n'est pas disponible", - "packages_no_upgrade": "Il n'y a aucun paquet à mettre à jour", - "packages_upgrade_critical_later": "Les paquets critiques ({:s}) seront mis à jour plus tard", - "packages_upgrade_failed": "Impossible de mettre à jour tous les paquets", - "path_removal_failed": "Impossible de supprimer le chemin {:s}", - "pattern_backup_archive_name": "Doit être un nom de fichier valide composé de caractères alphanumérique et -_. uniquement", - "pattern_domain": "Doit être un nom de domaine valide (ex : mon-domaine.org)", - "pattern_email": "Doit être une adresse mail valide (ex. : someone@domain.org)", - "pattern_firstname": "Doit être un prénom valide", - "pattern_lastname": "Doit être un nom valide", - "pattern_listname": "Doit être composé uniquement de caractères alphanumérique et de tiret bas", - "pattern_password": "Doit être composé d'au moins 3 caractères", - "pattern_port": "Doit être un numéro de port valide (0-65535)", - "pattern_port_or_range": "Doit être un numéro de port valide (0-65535) ou une gamme de ports (ex : 100:200)", - "pattern_username": "Doit être composé uniquement de caractères alphanumérique minuscule et de tiret bas", - "port_already_closed": "Le port {} est déjà fermé pour les connexions {:s}", - "port_already_opened": "Le port {} est déjà ouvert pour les connexions {:s}", - "port_available": "Le port {} est disponible", - "port_unavailable": "Le port {} n'est pas disponible", - "restore_complete": "Restauration terminée", - "restore_confirm_yunohost_installed": "Voulez-vous vraiment restaurer un système déjà installé ? [{answers:s}]", - "restore_failed": "Impossible de restaurer le système", - "restore_running_app_script": "Lancement du script de restauration pour l'application '{app:s}'...", - "restore_running_hooks": "Exécution des scripts de restauration...", - "service_add_failed": "Impossible d'ajouter le service '{:s}'", - "service_added": "Service ajouté avec succès", - "service_already_started": "Le service '{:s}' est déjà démarré", - "service_already_stopped": "Le service '{:s}' est déjà arrêté", - "service_cmd_exec_failed": "Impossible d'exécuter la commande '{:s}'", - "service_disable_failed": "Impossible de désactiver le service '{:s}'", - "service_disabled": "Service '{:s}' désactivé avec succès", - "service_enable_failed": "Impossible d'activer le service '{:s}'", - "service_enabled": "Service '{:s}' activé avec succès", - "service_no_log": "Aucun journal a afficher pour le service '{:s}'", - "service_remove_failed": "Impossible d'enlever le service '{:s}'", - "service_removed": "Service enlevé avec succès", - "service_start_failed": "Impossible de démarrer le service '{:s}'", - "service_started": "Service '{:s}' démarré avec succès", - "service_status_failed": "Impossible de déterminer le statut du service '{:s}'", - "service_stop_failed": "Impossible d'arrêter le service '{:s}'", - "service_stopped": "Service '{:s}' arrêté avec succès", - "service_unknown": "Service '{:s}' inconnu", - "services_configured": "La configuration a été générée avec succès", - "service_configuration_conflict": "Le fichier {file:s} a été modifié depuis sa dernière génération. Veuillez y appliquer les modifications manuellement ou utiliser l’option --force (ce qui écrasera toutes les modifications effectuées sur le fichier). Voici les différences:\n{diff:s}", - "no_such_conf_file": "Le fichier {file:s} n’existe pas, il ne peut pas être copié", - "service_add_configuration": "Ajout du fichier de configuration {file:s}", - "ssowat_conf_generated": "Configuration de SSOwat générée avec succès", - "ssowat_conf_updated": "Configuration persistante de SSOwat mise à jour avec succès", - "system_upgraded": "Système mis à jour avec succès", - "system_username_exists": "Le nom d'utilisateur existe déjà dans les utilisateurs système", - "unbackup_app": "L'application '{:s}' ne sera pas sauvegardée", - "unexpected_error": "Une erreur inattendue est survenue", - "unit_unknown": "Unité '{:s}' inconnue", - "unrestore_app": "L'application '{app:s}' ne sera pas restaurée", - "update_cache_failed": "Impossible de mettre à jour le cache de l'APT", - "updating_apt_cache": "Mise à jour de la liste des paquets disponibles...", - "upgrade_complete": "Mise à jour terminée", - "upgrading_packages": "Mise à jour des paquets...", - "upnp_dev_not_found": "Aucun périphérique compatible UPnP trouvé", - "upnp_disabled": "UPnP désactivé avec succès", - "upnp_enabled": "UPnP activé avec succès", - "upnp_port_open_failed": "Impossible d'ouvrir les ports avec UPnP", - "user_created": "Utilisateur créé avec succès", - "user_creation_failed": "Impossible de créer l'utilisateur", - "user_deleted": "Utilisateur supprimé avec succès", - "user_deletion_failed": "Impossible de supprimer l'utilisateur", - "user_info_failed": "Impossible de récupérer les informations de l'utilisateur", - "user_unknown": "Utilisateur inconnu", - "user_update_failed": "Impossible de modifier l'utilisateur", - "user_updated": "Utilisateur modifié avec succès", - "yunohost_already_installed": "YunoHost est déjà installé", - "yunohost_ca_creation_failed": "Impossible de créer l'autorité de certification", - "yunohost_configured": "YunoHost configuré avec succès", - "yunohost_installing": "Installation de YunoHost...", - "yunohost_not_installed": "YunoHost n'est pas ou pas correctement installé. Veuillez exécuter 'yunohost tools postinstall'." -} diff --git a/locales/it.json b/locales/it.json index 2c63c0851..0967ef424 100644 --- a/locales/it.json +++ b/locales/it.json @@ -1,2 +1 @@ -{ -} +{} diff --git a/locales/nl.json b/locales/nl.json index 3c43287a9..b26494629 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -1,31 +1,31 @@ { - "colon": "{}:", - "success": "Succes!", - "warning": "Waarschuwing:", - "error": "Fout:", - "permission_denied": "Toegang geweigerd", - "root_required": "Je moet root zijn om deze actie uit te voeren", - "instance_already_running": "Er is al een instantie aan het draaien", - "error_see_log": "Er is een fout opgetreden, zie logboek voor meer informatie", - "unable_authenticate": "Kan niet authenticeren", - "unable_retrieve_session": "Kan de sessie niet ophalen.", - "ldap_server_down": "Kan LDAP server niet bereiken", - "ldap_operation_error": "Er is een fout opgetreden bij het uitvoeren van LDAP Operatie", - "ldap_attribute_already_exists": "Attribuut bestaat al: '{:s}={:s}'", "argument_required": "Argument{:s} is vereist", - "invalid_argument": "Onjuist argument '{:s]': {:s}", - "pattern_not_match": "Past niet in het patroo", - "password": "Wachtwoord", - "invalid_password": "Ongeldig wachtwoord", - "confirm": "Bevestig {:s}", - "values_mismatch": "Waarden zijn niet gelijk", - "authentication_required_long": "Authenticatie is vereist om deze actie uit te voeren", - "authentication_required": "Authenticatie vereist", "authentication_profile_required": "Authenticatie tot profiel '{:s}' is vereist", - "operation_interrupted": "Operatie onderbroken", + "authentication_required": "Authenticatie vereist", + "authentication_required_long": "Authenticatie is vereist om deze actie uit te voeren", + "colon": "{}:", + "confirm": "Bevestig {:s}", + "error": "Fout:", + "error_see_log": "Er is een fout opgetreden, zie logboek voor meer informatie", + "instance_already_running": "Er is al een instantie aan het draaien", + "invalid_argument": "Onjuist argument '{:s]': {:s}", + "invalid_password": "Ongeldig wachtwoord", + "ldap_attribute_already_exists": "Attribuut bestaat al: '{:s}={:s}'", + "ldap_operation_error": "Er is een fout opgetreden bij het uitvoeren van LDAP Operatie", + "ldap_server_down": "Kan LDAP server niet bereiken", "logged_in": "Ingelogd", "logged_out": "Uitgelogd", "not_logged_in": "U bent niet ingelogd", + "operation_interrupted": "Operatie onderbroken", + "password": "Wachtwoord", + "pattern_not_match": "Past niet in het patroo", + "permission_denied": "Toegang geweigerd", + "root_required": "Je moet root zijn om deze actie uit te voeren", "server_already_running": "Er is al een server aan het draaien op die poort", + "success": "Succes!", + "unable_authenticate": "Kan niet authenticeren", + "unable_retrieve_session": "Kan de sessie niet ophalen.", + "values_mismatch": "Waarden zijn niet gelijk", + "warning": "Waarschuwing:", "websocket_request_expected": "Verwachte een WebSocket request" } diff --git a/locales/pt.json b/locales/pt.json index 391e09e37..09a2e86b9 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -1,143 +1,143 @@ { - "yunohost_not_installed": "YunoHost ainda não está corretamente configurado. Por favor execute as 'ferramentas pós-instalação yunohost'", - "upgrade_complete": "Atualização completa", - "installation_complete": "Instalação concluída", - "installation_failed": "A instalação falhou", - "unexpected_error": "Ocorreu um erro inesperado", "action_invalid": "Invalid action '{:s}'", - "license_undefined": "indefinido", - "no_appslist_found": "Não foi encontrada a lista de aplicações", - "custom_appslist_name_required": "Deve fornecer um nome para a sua lista de aplicações personalizada", - "appslist_retrieve_error": "Não foi possível obter a lista de aplicações remotas", - "appslist_fetched": "Lista de aplicações processada com êxito", - "appslist_unknown": "Lista de aplicaçoes desconhecida", - "appslist_removed": "Lista de aplicações removida com êxito", - "app_unknown": "Aplicação desconhecida", - "app_no_upgrade": "Não existem aplicações para atualizar", - "app_not_installed": "{:s} não está instalada", - "custom_app_url_required": "Deve proporcionar uma URL para atualizar a sua aplicação personalizada {:s}", - "app_recent_version_required": "{:s} requer uma versão mais recente da moulinette", - "app_upgraded": "{:s} atualizada com êxito", - "app_upgrade_failed": "Unable to upgrade all apps", - "app_id_invalid": "ID da aplicação invélida", + "admin_password": "Senha de administração", + "admin_password_change_failed": "Não foi possível alterar a senha", + "admin_password_changed": "Senha de administração alterada com êxito", "app_already_installed": "{:s} já está instalada", - "app_removed": "{:s} removida com êxito", + "app_extraction_failed": "Não foi possível extrair os ficheiros para instalação", + "app_id_invalid": "ID da aplicação invélida", + "app_install_files_invalid": "Ficheiros para instalação corrompidos", "app_location_already_used": "Já existe uma aplicação instalada neste diretório", "app_location_install_failed": "Não foi possível instalar a aplicação neste diretório", - "app_extraction_failed": "Não foi possível extrair os ficheiros para instalação", - "app_install_files_invalid": "Ficheiros para instalação corrompidos", "app_manifest_invalid": "Manifesto da aplicação inválido", + "app_no_upgrade": "Não existem aplicações para atualizar", + "app_not_installed": "{:s} não está instalada", + "app_recent_version_required": "{:s} requer uma versão mais recente da moulinette", + "app_removed": "{:s} removida com êxito", "app_sources_fetch_failed": "Impossível obter os códigos fontes", - "ssowat_conf_updated": "Configuração persistente SSOwat atualizada com êxito", - "ssowat_conf_generated": "Configuração SSOwat gerada com êxito", - "mysql_db_creation_failed": "Criação da base de dados MySQL falhou", - "mysql_db_init_failed": "Inicialização da base de dados MySQL falhou", - "mysql_db_initialized": "Base de dados MySQL iniciada com êxito", - "extracting": "Extração em curso...", - "downloading": "Transferência em curso...", - "executing_script": "A executar o script...", - "done": "Concluído.", - "path_removal_failed": "Incapaz remover o caminho {:s}", - "domain_unknown": "Domínio desconhecido", - "domain_dyndns_invalid": "Domínio inválido para ser utilizado com DynDNS", - "domain_dyndns_already_subscribed": "Já subscreveu um domínio DynDNS", - "domain_dyndns_root_unknown": "Domínio root (administrador) DynDNS desconhecido", + "app_unknown": "Aplicação desconhecida", + "app_upgrade_failed": "Unable to upgrade all apps", + "app_upgraded": "{:s} atualizada com êxito", + "appslist_fetched": "Lista de aplicações processada com êxito", + "appslist_removed": "Lista de aplicações removida com êxito", + "appslist_retrieve_error": "Não foi possível obter a lista de aplicações remotas", + "appslist_unknown": "Lista de aplicaçoes desconhecida", + "ask_current_admin_password": "Senha de administração atual", + "ask_email": "Correio eletrónico", + "ask_firstname": "Primeiro nome", + "ask_lastname": "Último nome", + "ask_list_to_remove": "Lista para remover", + "ask_main_domain": "Domínio principal", + "ask_new_admin_password": "Senha de administração nova", + "ask_password": "Senha", + "custom_app_url_required": "Deve proporcionar uma URL para atualizar a sua aplicação personalizada {:s}", + "custom_appslist_name_required": "Deve fornecer um nome para a sua lista de aplicações personalizada", "domain_cert_gen_failed": "Não foi possível gerar o certificado", + "domain_created": "Domínio criado com êxito", + "domain_creation_failed": "Não foi possível criar o domínio", + "domain_deleted": "Domínio removido com êxito", + "domain_deletion_failed": "Não foi possível eliminar o domínio", + "domain_dyndns_already_subscribed": "Já subscreveu um domínio DynDNS", + "domain_dyndns_invalid": "Domínio inválido para ser utilizado com DynDNS", + "domain_dyndns_root_unknown": "Domínio root (administrador) DynDNS desconhecido", "domain_exists": "O domínio já existe", + "domain_uninstall_app_first": "Existem uma ou mais aplicações instaladas neste domínio. Por favor desinstale-as antes de proceder com a remoção do domínio.", + "domain_unknown": "Domínio desconhecido", "domain_zone_exists": "Ficheiro para zona DMZ já existe", "domain_zone_not_found": "Ficheiro para zona DMZ não encontrado no domínio {:s}", - "domain_creation_failed": "Não foi possível criar o domínio", - "domain_created": "Domínio criado com êxito", - "domain_uninstall_app_first": "Existem uma ou mais aplicações instaladas neste domínio. Por favor desinstale-as antes de proceder com a remoção do domínio.", - "domain_deletion_failed": "Não foi possível eliminar o domínio", - "domain_deleted": "Domínio removido com êxito", - "no_internet_connection": "O servidor não está ligado à Internet", - "dyndns_key_generating": "A chave DNS está a ser gerada, isto pode demorar um pouco...", - "dyndns_unavailable": "Subdomínio DynDNS indisponível", - "dyndns_registration_failed": "Não foi possível registar o domínio DynDNS: {:s}", - "dyndns_registered": "Dom+inio DynDNS registado com êxito", - "dyndns_ip_update_failed": "Não foi possível atualizar o endereço IP a partir de DynDNS", - "dyndns_ip_updated": "Endereço IP atualizado com êxito a partir de DynDNS", + "done": "Concluído.", + "downloading": "Transferência em curso...", "dyndns_cron_installed": "Gestor de tarefas cron DynDNS instalado com êxito", "dyndns_cron_remove_failed": "Não foi possível remover o gestor de tarefas cron DynDNS", "dyndns_cron_removed": "Gestor de tarefas cron DynDNS removido com êxito", - "iptables_unavailable": "Não pode alterar aqui a iptables. Ou o seu kernel não o suporta ou está num espaço reservado.", + "dyndns_ip_update_failed": "Não foi possível atualizar o endereço IP a partir de DynDNS", + "dyndns_ip_updated": "Endereço IP atualizado com êxito a partir de DynDNS", + "dyndns_key_generating": "A chave DNS está a ser gerada, isto pode demorar um pouco...", + "dyndns_registered": "Dom+inio DynDNS registado com êxito", + "dyndns_registration_failed": "Não foi possível registar o domínio DynDNS: {:s}", + "dyndns_unavailable": "Subdomínio DynDNS indisponível", + "executing_script": "A executar o script...", + "extracting": "Extração em curso...", + "field_invalid": "Campo inválido '{:s}'", "firewall_reloaded": "Firewall recarregada com êxito", - "hook_choice_invalid": "Escolha inválida '{:s}'", "hook_argument_missing": "Argumento em falta '{:s}'", - "mountpoint_unknown": "Ponto de montagem desconhecido", - "unit_unknown": "Unidade desconhecida '{:s}'", - "monitor_period_invalid": "Período de tempo inválido", - "monitor_stats_no_update": "Não existem estatísticas de monitorização para atualizar", - "monitor_stats_file_not_found": "Ficheiro de estatísticas não encontrado", - "monitor_stats_period_unavailable": "Não existem estatísticas disponíveis para este período", - "monitor_enabled": "Monitorização do servidor ativada com êxito", + "hook_choice_invalid": "Escolha inválida '{:s}'", + "installation_complete": "Instalação concluída", + "installation_failed": "A instalação falhou", + "iptables_unavailable": "Não pode alterar aqui a iptables. Ou o seu kernel não o suporta ou está num espaço reservado.", + "ldap_initialized": "LDAP inicializada com êxito", + "license_undefined": "indefinido", + "mail_alias_remove_failed": "Não foi possível remover a etiqueta de correio '{:s}'", + "mail_domain_unknown": "Domínio de endereço de correio desconhecido '{:s}'", + "mail_forward_remove_failed": "Não foi possível remover o reencaminhamento de correio '{:s}'", + "maindomain_change_failed": "Incapaz alterar o domínio raiz", + "maindomain_changed": "Domínio raiz alterado com êxito", "monitor_disabled": "Monitorização do servidor parada com êxito", - "monitor_not_enabled": "A monitorização do servidor não está ativa", + "monitor_enabled": "Monitorização do servidor ativada com êxito", "monitor_glances_con_failed": "Não foi possível ligar ao servidor Glances", - "service_unknown": "Serviço desconhecido '{:s}'", + "monitor_not_enabled": "A monitorização do servidor não está ativa", + "monitor_period_invalid": "Período de tempo inválido", + "monitor_stats_file_not_found": "Ficheiro de estatísticas não encontrado", + "monitor_stats_no_update": "Não existem estatísticas de monitorização para atualizar", + "monitor_stats_period_unavailable": "Não existem estatísticas disponíveis para este período", + "mountpoint_unknown": "Ponto de montagem desconhecido", + "mysql_db_creation_failed": "Criação da base de dados MySQL falhou", + "mysql_db_init_failed": "Inicialização da base de dados MySQL falhou", + "mysql_db_initialized": "Base de dados MySQL iniciada com êxito", + "new_domain_required": "Deve escrever um novo domínio principal", + "no_appslist_found": "Não foi encontrada a lista de aplicações", + "no_internet_connection": "O servidor não está ligado à Internet", + "packages_no_upgrade": "Não existem pacotes para atualizar", + "packages_upgrade_critical_later": "Os pacotes críticos ({:s}) serão atualizados depois", + "packages_upgrade_failed": "Não foi possível atualizar todos os pacotes", + "path_removal_failed": "Incapaz remover o caminho {:s}", + "pattern_domain": "Deve ser um nome de domínio válido (p.e. meu-dominio.org)", + "pattern_email": "Deve ser um endereço de correio válido (p.e. alguem@dominio.org)", + "pattern_firstname": "Deve ser um primeiro nome válido", + "pattern_lastname": "Deve ser um último nome válido", + "pattern_listname": "Apenas são permitidos caracteres alfanuméricos e travessões", + "pattern_password": "Deve ter no mínimo 3 caracteres", + "pattern_port": "Deve ser um número de porta válido (entre 0-65535)", + "pattern_username": "Must be lower-case alphanumeric and underscore characters only", "service_add_failed": "Incapaz adicionar serviço '{:s}'", "service_added": "Serviço adicionado com êxito", + "service_already_started": "O serviço '{:s}' já está em execussão", + "service_already_stopped": "O serviço '{:s}' já está parado", + "service_cmd_exec_failed": "Incapaz executar o comando '{:s}'", + "service_disable_failed": "Incapaz desativar o serviço '{:s}'", + "service_disabled": "O serviço '{:s}' foi desativado com êxito", + "service_enable_failed": "Incapaz de ativar o serviço '{:s}'", + "service_enabled": "Serviço '{:s}' ativado com êxito", + "service_no_log": "Não existem registos para mostrar do serviço '{:s}'", "service_remove_failed": "Incapaz de remover o serviço '{:s}'", "service_removed": "Serviço eliminado com êxito", "service_start_failed": "Não foi possível iniciar o serviço '{:s}'", - "service_already_started": "O serviço '{:s}' já está em execussão", "service_started": "O serviço '{:s} foi iniciado com êxito", - "service_stop_failed": "Incapaz parar o serviço '{:s}", - "service_already_stopped": "O serviço '{:s}' já está parado", - "service_stopped": "O serviço '{:s}' foi parado com êxito", - "service_enable_failed": "Incapaz de ativar o serviço '{:s}'", - "service_enabled": "Serviço '{:s}' ativado com êxito", - "service_disable_failed": "Incapaz desativar o serviço '{:s}'", - "service_disabled": "O serviço '{:s}' foi desativado com êxito", "service_status_failed": "Incapaz determinar o estado do serviço '{:s}'", - "service_no_log": "Não existem registos para mostrar do serviço '{:s}'", - "service_cmd_exec_failed": "Incapaz executar o comando '{:s}'", - "ldap_initialized": "LDAP inicializada com êxito", - "admin_password_change_failed": "Não foi possível alterar a senha", - "admin_password_changed": "Senha de administração alterada com êxito", - "new_domain_required": "Deve escrever um novo domínio principal", - "maindomain_change_failed": "Incapaz alterar o domínio raiz", - "maindomain_changed": "Domínio raiz alterado com êxito", - "yunohost_installing": "A instalar a YunoHost...", + "service_stop_failed": "Incapaz parar o serviço '{:s}", + "service_stopped": "O serviço '{:s}' foi parado com êxito", + "service_unknown": "Serviço desconhecido '{:s}'", + "ssowat_conf_generated": "Configuração SSOwat gerada com êxito", + "ssowat_conf_updated": "Configuração persistente SSOwat atualizada com êxito", + "system_upgraded": "Sistema atualizado com êxito", + "system_username_exists": "O utilizador já existe no registo do sistema", + "unexpected_error": "Ocorreu um erro inesperado", + "unit_unknown": "Unidade desconhecida '{:s}'", + "update_cache_failed": "Não foi possível atualizar os cabeçalhos APT", + "updating_apt_cache": "A atualizar a lista de pacotes disponíveis...", + "upgrade_complete": "Atualização completa", + "upgrading_packages": "Atualização de pacotes em curso...", + "user_created": "Utilizador criado com êxito", + "user_creation_failed": "Não foi possível criar o utilizador", + "user_deleted": "Utilizador eliminado com êxito", + "user_deletion_failed": "Incapaz eliminar o utilizador", + "user_info_failed": "Incapaz obter informações sobre o utilizador", + "user_unknown": "Utilizador desconhecido", + "user_update_failed": "Não foi possível atualizar o utilizador", + "user_updated": "Utilizador atualizado com êxito", "yunohost_already_installed": "A YunoHost já está instalada...", "yunohost_ca_creation_failed": "Incapaz criar o certificado de autoridade", "yunohost_configured": "YunoHost configurada com êxito", - "updating_apt_cache": "A atualizar a lista de pacotes disponíveis...", - "update_cache_failed": "Não foi possível atualizar os cabeçalhos APT", - "packages_no_upgrade": "Não existem pacotes para atualizar", - "packages_upgrade_critical_later": "Os pacotes críticos ({:s}) serão atualizados depois", - "upgrading_packages": "Atualização de pacotes em curso...", - "packages_upgrade_failed": "Não foi possível atualizar todos os pacotes", - "system_upgraded": "Sistema atualizado com êxito", - "field_invalid": "Campo inválido '{:s}'", - "mail_domain_unknown": "Domínio de endereço de correio desconhecido '{:s}'", - "mail_alias_remove_failed": "Não foi possível remover a etiqueta de correio '{:s}'", - "mail_forward_remove_failed": "Não foi possível remover o reencaminhamento de correio '{:s}'", - "user_unknown": "Utilizador desconhecido", - "system_username_exists": "O utilizador já existe no registo do sistema", - "user_creation_failed": "Não foi possível criar o utilizador", - "user_created": "Utilizador criado com êxito", - "user_deletion_failed": "Incapaz eliminar o utilizador", - "user_deleted": "Utilizador eliminado com êxito", - "user_update_failed": "Não foi possível atualizar o utilizador", - "user_updated": "Utilizador atualizado com êxito", - "user_info_failed": "Incapaz obter informações sobre o utilizador", - "admin_password": "Senha de administração", - "ask_firstname": "Primeiro nome", - "ask_lastname": "Último nome", - "ask_email": "Correio eletrónico", - "ask_password": "Senha", - "ask_current_admin_password": "Senha de administração atual", - "ask_new_admin_password": "Senha de administração nova", - "ask_main_domain": "Domínio principal", - "ask_list_to_remove": "Lista para remover", - "pattern_username": "Must be lower-case alphanumeric and underscore characters only", - "pattern_firstname": "Deve ser um primeiro nome válido", - "pattern_lastname": "Deve ser um último nome válido", - "pattern_email": "Deve ser um endereço de correio válido (p.e. alguem@dominio.org)", - "pattern_password": "Deve ter no mínimo 3 caracteres", - "pattern_domain": "Deve ser um nome de domínio válido (p.e. meu-dominio.org)", - "pattern_listname": "Apenas são permitidos caracteres alfanuméricos e travessões", - "pattern_port": "Deve ser um número de porta válido (entre 0-65535)" + "yunohost_installing": "A instalar a YunoHost...", + "yunohost_not_installed": "YunoHost ainda não está corretamente configurado. Por favor execute as 'ferramentas pós-instalação yunohost'" } diff --git a/locales/tr.json b/locales/tr.json index ad87476a9..06721553c 100644 --- a/locales/tr.json +++ b/locales/tr.json @@ -1,31 +1,31 @@ { - "colon": "{}:", - "success": "İşlem Başarılı!", - "warning": "Uyarı:", - "error": "Hata:", - "permission_denied": "Erişim reddedildi", - "root_required": "Bu işlemi yapmak için root olmalısınız", - "instance_already_running": "Uygulama zaten çalışıyor.", - "error_see_log": "Bir hata oluştu. Detaylar için lütfen loga bakınız", - "unable_authenticate": "Yetkilendirme başarısız", - "unable_retrieve_session": "Oturum bilgileri alınamadı", - "ldap_server_down": "LDAP sunucusuna erişilemiyor", - "ldap_operation_error": "LDAP işlemi sırasında hata oluştu", - "ldap_attribute_already_exists": " '{:s}={:s}' özelliği zaten mevcut", "argument_required": "{:s} argümanı gerekli", - "invalid_argument": "Geçersiz argüman '{:s}': {:s}", - "pattern_not_match": "İstenen biçimle uyuşmuyor", - "password": "Parola", - "invalid_password": "Geçersiz parola", - "confirm": "{:s}'i doğrulayın", - "values_mismatch": "Değerler uyuşmuyor", - "authentication_required_long": "Bu işlemi yapmak içi yetkilendirme gerekli", - "authentication_required": "Yetklendirme gerekli", "authentication_profile_required": "'{:s}' profili için yetkilendirme gerekli", - "operation_interrupted": "İşlem yarıda kesildi", + "authentication_required": "Yetklendirme gerekli", + "authentication_required_long": "Bu işlemi yapmak içi yetkilendirme gerekli", + "colon": "{}:", + "confirm": "{:s}'i doğrulayın", + "error": "Hata:", + "error_see_log": "Bir hata oluştu. Detaylar için lütfen loga bakınız", + "instance_already_running": "Uygulama zaten çalışıyor.", + "invalid_argument": "Geçersiz argüman '{:s}': {:s}", + "invalid_password": "Geçersiz parola", + "ldap_attribute_already_exists": " '{:s}={:s}' özelliği zaten mevcut", + "ldap_operation_error": "LDAP işlemi sırasında hata oluştu", + "ldap_server_down": "LDAP sunucusuna erişilemiyor", "logged_in": "Giriş yapıldı", "logged_out": "Çıkış yapıldı", "not_logged_in": "Giriş yapmadınız", + "operation_interrupted": "İşlem yarıda kesildi", + "password": "Parola", + "pattern_not_match": "İstenen biçimle uyuşmuyor", + "permission_denied": "Erişim reddedildi", + "root_required": "Bu işlemi yapmak için root olmalısınız", "server_already_running": "Bu portta zaten çalışan bir sunucu var", + "success": "İşlem Başarılı!", + "unable_authenticate": "Yetkilendirme başarısız", + "unable_retrieve_session": "Oturum bilgileri alınamadı", + "values_mismatch": "Değerler uyuşmuyor", + "warning": "Uyarı:", "websocket_request_expected": "WebSocket isteği gerekli" -} \ No newline at end of file +} From be43c79db8ae786ce163d7c285311107b0320cca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Thu, 17 Dec 2015 21:22:55 +0100 Subject: [PATCH 105/170] [i18n] Use proper translation files from Transifex --- locales/nl.json | 93 ++++++++++++++++++++++++++++++++++--------------- locales/tr.json | 32 +---------------- 2 files changed, 65 insertions(+), 60 deletions(-) diff --git a/locales/nl.json b/locales/nl.json index b26494629..fca078dcf 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -1,31 +1,66 @@ { - "argument_required": "Argument{:s} is vereist", - "authentication_profile_required": "Authenticatie tot profiel '{:s}' is vereist", - "authentication_required": "Authenticatie vereist", - "authentication_required_long": "Authenticatie is vereist om deze actie uit te voeren", - "colon": "{}:", - "confirm": "Bevestig {:s}", - "error": "Fout:", - "error_see_log": "Er is een fout opgetreden, zie logboek voor meer informatie", - "instance_already_running": "Er is al een instantie aan het draaien", - "invalid_argument": "Onjuist argument '{:s]': {:s}", - "invalid_password": "Ongeldig wachtwoord", - "ldap_attribute_already_exists": "Attribuut bestaat al: '{:s}={:s}'", - "ldap_operation_error": "Er is een fout opgetreden bij het uitvoeren van LDAP Operatie", - "ldap_server_down": "Kan LDAP server niet bereiken", - "logged_in": "Ingelogd", - "logged_out": "Uitgelogd", - "not_logged_in": "U bent niet ingelogd", - "operation_interrupted": "Operatie onderbroken", - "password": "Wachtwoord", - "pattern_not_match": "Past niet in het patroo", - "permission_denied": "Toegang geweigerd", - "root_required": "Je moet root zijn om deze actie uit te voeren", - "server_already_running": "Er is al een server aan het draaien op die poort", - "success": "Succes!", - "unable_authenticate": "Kan niet authenticeren", - "unable_retrieve_session": "Kan de sessie niet ophalen.", - "values_mismatch": "Waarden zijn niet gelijk", - "warning": "Waarschuwing:", - "websocket_request_expected": "Verwachte een WebSocket request" + "action_invalid": "Ongeldige actie '{:s}'", + "admin_password": "Administration password", + "app_already_installed": "{:s} is al geïnstalleerd", + "app_extraction_failed": "Kan installatiebestanden niet uitpakken", + "app_id_invalid": "Ongeldige app-id", + "app_install_files_invalid": "Ongeldige installatiebestanden", + "app_location_already_used": "Er is al een app geïnstalleerd op deze locatie", + "app_location_install_failed": "Kan app niet installeren op deze locatie", + "app_manifest_invalid": "Ongeldig app-manifest", + "app_no_upgrade": "Geen apps op te upgraden", + "app_not_installed": "{:s} is niet geinstalleerd ", + "app_recent_version_required": "{:s} vereist een nieuwere versie van moulinette", + "app_removed": "{:s} succesvol verwijderd", + "app_sources_fetch_failed": "Kan bronbestanden niet ophalen", + "app_unknown": "Onbekende app", + "app_upgrade_failed": "Kan niet alle apps updaten", + "app_upgraded": "{:s} succesvol geüpgrade ", + "appslist_fetched": "App-lijst succesvol aangemaakt.", + "appslist_removed": "App-lijst succesvol verwijderd", + "appslist_unknown": "Onbekende app-lijst", + "ask_current_admin_password": "Huidig administratorwachtwoord", + "ask_email": "Email-adres", + "ask_firstname": "Voornaam", + "ask_lastname": "Achternaam", + "ask_new_admin_password": "Nieuw administratorwachtwoord", + "ask_password": "Wachtwoord", + "custom_app_url_required": "U moet een URL opgeven om uw aangepaste app {:s} bij te werken", + "custom_appslist_name_required": "U moet een naam opgeven voor uw aangepaste app-lijst", + "dnsmasq_isnt_installed": "dnsmasq lijkt niet geïnstalleerd te zijn, voer alstublieft het volgende commando uit: 'apt-get remove bind9 && apt-get install dnsmasq'", + "domain_cert_gen_failed": "Kan certificaat niet genereren", + "domain_created": "Domein succesvol aangemaakt", + "domain_creation_failed": "Kan domein niet aanmaken", + "domain_deleted": "Domein succesvol verwijderd", + "domain_deletion_failed": "Kan domein niet verwijderen", + "domain_dyndns_root_unknown": "Onbekend DynDNS root domein", + "domain_exists": "Domein bestaat al", + "domain_uninstall_app_first": "Een of meerdere apps zijn geïnstalleerd op dit domein, verwijder deze voordat u het domein verwijderd.", + "domain_unknown": "Onbekend domein", + "domain_zone_exists": "DNS zone bestand bestaat al", + "domain_zone_not_found": "DNS zone bestand niet gevonden voor domein: {:s}", + "done": "Voltooid.", + "downloading": "Downloaden...", + "dyndns_key_generating": "DNS sleutel word aangemaakt, wacht een moment...", + "executing_script": "Script uitvoeren...", + "extracting": "Uitpakken...", + "installation_complete": "Installatie voltooid", + "installation_failed": "Installatie gefaald", + "license_undefined": "undefined", + "mysql_db_creation_failed": "Aanmaken MySQL database gefaald", + "mysql_db_init_failed": "Initialiseren MySQL database gefaald", + "mysql_db_initialized": "MySQL database succesvol geïnitialiseerd", + "no_appslist_found": "Geen app-lijsten gevonden", + "no_internet_connection": "Server is niet verbonden met het internet", + "path_removal_failed": "Kan pad niet verwijderen {:s}", + "port_already_closed": "Poort {} is al gesloten voor {:s} verbindingen", + "port_already_opened": "Poort {} is al open voor {:s} verbindingen", + "port_available": "Poort {} is beschikbaar", + "port_unavailable": "Poort {} is niet beschikbaar", + "unexpected_error": "Er is een onbekende fout opgetreden", + "upgrade_complete": "Upgrade voltooid", + "upnp_dev_not_found": "Geen UPnP apparaten gevonden", + "upnp_disabled": "UPnP successvol uitgeschakeld", + "upnp_enabled": "UPnP succesvol ingeschakeld", + "upnp_port_open_failed": "Kan UPnP poorten niet openen" } diff --git a/locales/tr.json b/locales/tr.json index 06721553c..0967ef424 100644 --- a/locales/tr.json +++ b/locales/tr.json @@ -1,31 +1 @@ -{ - "argument_required": "{:s} argümanı gerekli", - "authentication_profile_required": "'{:s}' profili için yetkilendirme gerekli", - "authentication_required": "Yetklendirme gerekli", - "authentication_required_long": "Bu işlemi yapmak içi yetkilendirme gerekli", - "colon": "{}:", - "confirm": "{:s}'i doğrulayın", - "error": "Hata:", - "error_see_log": "Bir hata oluştu. Detaylar için lütfen loga bakınız", - "instance_already_running": "Uygulama zaten çalışıyor.", - "invalid_argument": "Geçersiz argüman '{:s}': {:s}", - "invalid_password": "Geçersiz parola", - "ldap_attribute_already_exists": " '{:s}={:s}' özelliği zaten mevcut", - "ldap_operation_error": "LDAP işlemi sırasında hata oluştu", - "ldap_server_down": "LDAP sunucusuna erişilemiyor", - "logged_in": "Giriş yapıldı", - "logged_out": "Çıkış yapıldı", - "not_logged_in": "Giriş yapmadınız", - "operation_interrupted": "İşlem yarıda kesildi", - "password": "Parola", - "pattern_not_match": "İstenen biçimle uyuşmuyor", - "permission_denied": "Erişim reddedildi", - "root_required": "Bu işlemi yapmak için root olmalısınız", - "server_already_running": "Bu portta zaten çalışan bir sunucu var", - "success": "İşlem Başarılı!", - "unable_authenticate": "Yetkilendirme başarısız", - "unable_retrieve_session": "Oturum bilgileri alınamadı", - "values_mismatch": "Değerler uyuşmuyor", - "warning": "Uyarı:", - "websocket_request_expected": "WebSocket isteği gerekli" -} +{} From 144d27e7df10f7ba0c990b061ec24aae3b1eae2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Thu, 17 Dec 2015 21:29:48 +0100 Subject: [PATCH 106/170] [fix] Recover French translation deleted as if by magic --- locales/fr.json | 185 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) diff --git a/locales/fr.json b/locales/fr.json index e69de29bb..562964456 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -0,0 +1,185 @@ +{ + "action_invalid": "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_changed": "Mot de passe d'administration modifié avec succès", + "app_already_installed": "{:s} est déjà installé", + "app_extraction_failed": "Impossible d'extraire les fichiers d'installation", + "app_id_invalid": "Id d'application incorrect", + "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_no_upgrade": "Aucune application à mettre à jour", + "app_not_installed": "{:s} n'est pas installé", + "app_recent_version_required": "{:s} nécessite une version plus récente de la moulinette", + "app_removed": "{:s} supprimé avec succès", + "app_sources_fetch_failed": "Impossible de récupérer les fichiers sources", + "app_unknown": "Application inconnue", + "app_upgrade_failed": "Impossible de mettre à jour toutes les applications", + "app_upgraded": "{:s} mis à jour avec succès", + "appslist_fetched": "Liste d'applications récupérée avec succès", + "appslist_removed": "Liste d'applications supprimée avec succès", + "appslist_retrieve_error": "Impossible de récupérer la liste d'applications distante", + "appslist_unknown": "Liste d'applications inconnue", + "ask_current_admin_password": "Mot de passe d'administration actuel", + "ask_email": "Adresse mail", + "ask_firstname": "Prénom", + "ask_lastname": "Nom", + "ask_list_to_remove": "Liste à supprimer", + "ask_main_domain": "Domaine principal", + "ask_new_admin_password": "Nouveau mot de passe d'administration", + "ask_password": "Mot de passe", + "backup_archive_name_exists": "Une archive de sauvegarde avec ce nom existe déjà", + "backup_archive_name_unknown": "Nom d'archive de sauvegarde locale inconnu", + "backup_archive_open_failed": "Impossible d'ouvrir l'archive de sauvegarde", + "backup_complete": "Sauvegarde terminée", + "backup_creating_archive": "Création de l'archive de sauvegarde...", + "backup_extracting_archive": "Extraction de l'archive de sauvegarde...", + "backup_hook_unknown": "Script de sauvegarde '{hook:s}' inconnu", + "backup_invalid_archive": "Archive de sauvegarde incorrecte", + "backup_output_directory_forbidden": "Dossier de sortie interdit", + "backup_output_directory_not_empty": "Le dossier de sortie n'est pas vide", + "backup_output_directory_required": "Vous devez spécifier un dossier de sortie pour la sauvegarde", + "backup_running_app_script": "Lancement du script de sauvegarde de l'application '{:s}'...", + "backup_running_hooks": "Exécution des scripts de sauvegarde...", + "custom_app_url_required": "Vous devez spécifier une URL pour mettre à jour votre application locale {:s}", + "custom_appslist_name_required": "Vous devez spécifier un nom pour votre liste d'applications personnalisée", + "dnsmasq_isnt_installed": "dnsmasq ne semble pas être installé, veuillez lancer 'apt-get remove bind9 && apt-get install dnsmasq'", + "domain_cert_gen_failed": "Impossible de générer le certificat", + "domain_created": "Domaine créé avec succès", + "domain_creation_failed": "Impossible de créer le domaine", + "domain_deleted": "Domaine supprimé avec succès", + "domain_deletion_failed": "Impossible de supprimer le domaine", + "domain_dyndns_already_subscribed": "Vous avez déjà souscris à un domaine DynDNS", + "domain_dyndns_invalid": "Domaine incorrect pour un usage avec DynDNS", + "domain_dyndns_root_unknown": "Domaine DynDNS principal inconnu", + "domain_exists": "Le domaine existe déjà", + "domain_uninstall_app_first": "Une ou plusieurs applications sont installées sur ce domaine. Veuillez d'abord les désinstaller avant de supprimer ce domaine.", + "domain_unknown": "Domaine inconnu", + "domain_zone_exists": "Le fichier de zone DNS existe déjà", + "domain_zone_not_found": "Fichier de zone DNS introuvable pour le domaine {:s}", + "done": "Terminé.", + "downloading": "Téléchargement...", + "dyndns_cron_installed": "Tâche cron pour DynDNS installée avec succès", + "dyndns_cron_remove_failed": "Impossible d'enlever la tâche cron pour DynDNS", + "dyndns_cron_removed": "Tâche cron pour DynDNS enlevée avec succès", + "dyndns_ip_update_failed": "Impossible de mettre à jour l'adresse IP sur le domaine DynDNS", + "dyndns_ip_updated": "Adresse IP mise à jour avec succès sur le domaine DynDNS", + "dyndns_key_generating": "La clé DNS est en cours de génération, cela peut prendre du temps...", + "dyndns_registered": "Domaine DynDNS enregistré avec succès", + "dyndns_registration_failed": "Impossible d'enregistrer le domaine DynDNS : {:s}", + "dyndns_unavailable": "Sous-domaine DynDNS indisponible", + "executing_script": "Exécution du script...", + "extracting": "Extraction...", + "field_invalid": "Champ incorrect : {:s}", + "firewall_reload_failed": "Impossible de recharger le pare-feu", + "firewall_reloaded": "Pare-feu rechargé avec succès", + "firewall_rules_cmd_failed": "Certaines règles du pare-feu n'ont pas pu être appliquées. Pour plus d'informations, consultez le journal.", + "format_datetime_short": "%d/%m/%Y %H:%M", + "hook_argument_missing": "Argument manquant : '{:s}'", + "hook_choice_invalid": "Choix incorrect : '{:s}'", + "hook_list_by_invalid": "Propriété pour lister les scripts incorrecte", + "hook_name_unknown": "Nom de script '{:s}' inconnu", + "installation_complete": "Installation terminée", + "installation_failed": "Échec de l'installation", + "ip6tables_unavailable": "Vous ne pouvez pas faire joujou avec ip6tables ici. Vous êtes sûrement dans un conteneur, autrement votre noyau ne le supporte pas.", + "iptables_unavailable": "Vous ne pouvez pas faire joujou avec iptables ici. Vous êtes sûrement dans un conteneur, autrement votre noyau ne le supporte pas.", + "ldap_initialized": "Répertoire LDAP initialisé avec succès", + "license_undefined": "indéfinie", + "mail_alias_remove_failed": "Impossible de supprimer l'adresse mail supplémentaire '{:s}'", + "mail_domain_unknown": "Domaine '{:s}' de l'adresse mail inconnu", + "mail_forward_remove_failed": "Impossible de supprimer l'adresse mail de transfert '{:s}'", + "maindomain_change_failed": "Impossible de modifier le domaine principal", + "maindomain_changed": "Domaine principal modifié avec succès", + "monitor_disabled": "Suivi de l'état du serveur désactivé avec succès", + "monitor_enabled": "Suivi de l'état du serveur activé avec succès", + "monitor_glances_con_failed": "Impossible de se connecter au serveur Glances", + "monitor_not_enabled": "Le suivi de l'état du serveur n'est pas activé", + "monitor_period_invalid": "Période de temps incorrect", + "monitor_stats_file_not_found": "Fichier de données de l'état du serveur introuvable", + "monitor_stats_no_update": "Aucune donnée de l'état du serveur à mettre à jour", + "monitor_stats_period_unavailable": "Aucune donnée de l'état du serveur disponible pour la période", + "mountpoint_unknown": "Point de montage inconnu", + "mysql_db_creation_failed": "Impossible de créer la base de donnée MySQL", + "mysql_db_init_failed": "Impossible d'initialiser la base de donnée MySQL", + "mysql_db_initialized": "Base de donnée MySQL initialisée avec succès", + "new_domain_required": "Vous devez spécifier le nouveau domaine principal", + "no_appslist_found": "Aucune liste d'applications trouvée", + "no_internet_connection": "Le serveur n'est pas connecté à Internet", + "no_ipv6_connectivity": "IPv6 n'est pas disponible", + "no_such_conf_file": "Le fichier {file:s} n’existe pas, il ne peut pas être copié", + "packages_no_upgrade": "Il n'y a aucun paquet à mettre à jour", + "packages_upgrade_critical_later": "Les paquets critiques ({:s}) seront mis à jour plus tard", + "packages_upgrade_failed": "Impossible de mettre à jour tous les paquets", + "path_removal_failed": "Impossible de supprimer le chemin {:s}", + "pattern_backup_archive_name": "Doit être un nom de fichier valide composé de caractères alphanumérique et -_. uniquement", + "pattern_domain": "Doit être un nom de domaine valide (ex : mon-domaine.org)", + "pattern_email": "Doit être une adresse mail valide (ex. : someone@domain.org)", + "pattern_firstname": "Doit être un prénom valide", + "pattern_lastname": "Doit être un nom valide", + "pattern_listname": "Doit être composé uniquement de caractères alphanumérique et de tiret bas", + "pattern_password": "Doit être composé d'au moins 3 caractères", + "pattern_port": "Doit être un numéro de port valide (0-65535)", + "pattern_port_or_range": "Doit être un numéro de port valide (0-65535) ou une gamme de ports (ex : 100:200)", + "pattern_username": "Doit être composé uniquement de caractères alphanumérique minuscule et de tiret bas", + "port_already_closed": "Le port {} est déjà fermé pour les connexions {:s}", + "port_already_opened": "Le port {} est déjà ouvert pour les connexions {:s}", + "port_available": "Le port {} est disponible", + "port_unavailable": "Le port {} n'est pas disponible", + "restore_complete": "Restauration terminée", + "restore_confirm_yunohost_installed": "Voulez-vous vraiment restaurer un système déjà installé ? [{answers:s}]", + "restore_failed": "Impossible de restaurer le système", + "restore_running_app_script": "Lancement du script de restauration pour l'application '{app:s}'...", + "restore_running_hooks": "Exécution des scripts de restauration...", + "service_add_configuration": "Ajout du fichier de configuration {file:s}", + "service_add_failed": "Impossible d'ajouter le service '{:s}'", + "service_added": "Service ajouté avec succès", + "service_already_started": "Le service '{:s}' est déjà démarré", + "service_already_stopped": "Le service '{:s}' est déjà arrêté", + "service_cmd_exec_failed": "Impossible d'exécuter la commande '{:s}'", + "service_configuration_conflict": "Le fichier {file:s} a été modifié depuis sa dernière génération. Veuillez y appliquer les modifications manuellement ou utiliser l’option --force (ce qui écrasera toutes les modifications effectuées sur le fichier). Voici les différences:\n{diff:s}", + "service_disable_failed": "Impossible de désactiver le service '{:s}'", + "service_disabled": "Service '{:s}' désactivé avec succès", + "service_enable_failed": "Impossible d'activer le service '{:s}'", + "service_enabled": "Service '{:s}' activé avec succès", + "service_no_log": "Aucun journal a afficher pour le service '{:s}'", + "service_remove_failed": "Impossible d'enlever le service '{:s}'", + "service_removed": "Service enlevé avec succès", + "service_start_failed": "Impossible de démarrer le service '{:s}'", + "service_started": "Service '{:s}' démarré avec succès", + "service_status_failed": "Impossible de déterminer le statut du service '{:s}'", + "service_stop_failed": "Impossible d'arrêter le service '{:s}'", + "service_stopped": "Service '{:s}' arrêté avec succès", + "service_unknown": "Service '{:s}' inconnu", + "services_configured": "La configuration a été générée avec succès", + "ssowat_conf_generated": "Configuration de SSOwat générée avec succès", + "ssowat_conf_updated": "Configuration persistante de SSOwat mise à jour avec succès", + "system_upgraded": "Système mis à jour avec succès", + "system_username_exists": "Le nom d'utilisateur existe déjà dans les utilisateurs système", + "unbackup_app": "L'application '{:s}' ne sera pas sauvegardée", + "unexpected_error": "Une erreur inattendue est survenue", + "unit_unknown": "Unité '{:s}' inconnue", + "unrestore_app": "L'application '{app:s}' ne sera pas restaurée", + "update_cache_failed": "Impossible de mettre à jour le cache de l'APT", + "updating_apt_cache": "Mise à jour de la liste des paquets disponibles...", + "upgrade_complete": "Mise à jour terminée", + "upgrading_packages": "Mise à jour des paquets...", + "upnp_dev_not_found": "Aucun périphérique compatible UPnP trouvé", + "upnp_disabled": "UPnP désactivé avec succès", + "upnp_enabled": "UPnP activé avec succès", + "upnp_port_open_failed": "Impossible d'ouvrir les ports avec UPnP", + "user_created": "Utilisateur créé avec succès", + "user_creation_failed": "Impossible de créer l'utilisateur", + "user_deleted": "Utilisateur supprimé avec succès", + "user_deletion_failed": "Impossible de supprimer l'utilisateur", + "user_info_failed": "Impossible de récupérer les informations de l'utilisateur", + "user_unknown": "Utilisateur inconnu", + "user_update_failed": "Impossible de modifier l'utilisateur", + "user_updated": "Utilisateur modifié avec succès", + "yunohost_already_installed": "YunoHost est déjà installé", + "yunohost_ca_creation_failed": "Impossible de créer l'autorité de certification", + "yunohost_configured": "YunoHost configuré avec succès", + "yunohost_installing": "Installation de YunoHost...", + "yunohost_not_installed": "YunoHost n'est pas ou pas correctement installé. Veuillez exécuter 'yunohost tools postinstall'." +} From 5cd3a86099ad0916de6ecc2a05ff8dbc167ab163 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Mon, 21 Dec 2015 12:40:50 +0100 Subject: [PATCH 107/170] [fix] Check app min_version with yunohost package (fixbug #113) --- src/yunohost/__init__.py | 11 +++++++++++ src/yunohost/app.py | 7 +++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/yunohost/__init__.py b/src/yunohost/__init__.py index 3a7dd37c9..fd8b653bd 100644 --- a/src/yunohost/__init__.py +++ b/src/yunohost/__init__.py @@ -24,15 +24,26 @@ ## Packages versions def get_version(package): + """Get the version of package""" from moulinette.utils import process return process.check_output( "dpkg-query -W -f='${{Version}}' {0}".format(package) ).strip() def get_versions(*args, **kwargs): + """Get the version of each YunoHost package""" from collections import OrderedDict return OrderedDict([ ('moulinette', get_version('moulinette')), ('yunohost', get_version('yunohost')), ('yunohost-admin', get_version('yunohost-admin')), ]) + +def has_min_version(min_version, package='yunohost', strict=False): + """Check if a package has minimum version""" + from distutils.version import LooseVersion, StrictVersion + cmp_cls = StrictVersion if strict else LooseVersion + version = cmp_cls(get_version(package)) + if version >= cmp_cls(min_version): + return True + return False diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 0b3ad2a31..325a68bb3 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -39,6 +39,7 @@ import subprocess from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger +from . import has_min_version from .service import service_log logger = getActionLogger('yunohost.app') @@ -344,7 +345,8 @@ def app_upgrade(auth, app=[], url=None, file=None): continue # Check min version - if 'min_version' in manifest and __version__ < manifest['min_version']: + if 'min_version' in manifest \ + and not has_min_version(manifest['min_version']): raise MoulinetteError(errno.EPERM, m18n.n('app_recent_version_required', app_id)) @@ -448,7 +450,8 @@ def app_install(auth, app, label=None, args=None): app_id = manifest['id'] # Check min version - if 'min_version' in manifest and __version__ < manifest['min_version']: + if 'min_version' in manifest \ + and not has_min_version(manifest['min_version']): raise MoulinetteError(errno.EPERM, m18n.n('app_recent_version_required', app_id)) From a9046a720080258f0b57cc14fb397c0ca12cc472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Thu, 24 Dec 2015 10:35:39 +0100 Subject: [PATCH 108/170] [enh] Allow to pass the admin password as argument in the cli --- bin/yunohost | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bin/yunohost b/bin/yunohost index a5fadf574..3f947364b 100755 --- a/bin/yunohost +++ b/bin/yunohost @@ -66,6 +66,10 @@ def _parse_cli_args(): action='store_true', default=False, help="Don't produce any output", ) + parser.add_argument('--admin-password', + default=None, dest='password', metavar='PASSWORD', + help="The admin password to use to authenticate", + ) # deprecated arguments parser.add_argument('--plain', action='store_true', default=False, help=argparse.SUPPRESS @@ -199,6 +203,6 @@ if __name__ == '__main__': from moulinette import cli ret = cli(_retrieve_namespaces(), args, use_cache=opts.use_cache, output_as=opts.output_as, - parser_kwargs={'top_parser': parser} + password=opts.password, parser_kwargs={'top_parser': parser} ) sys.exit(ret) From 6840d120c0d2ad6981b11f396ec189d57628eed6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Thu, 24 Dec 2015 10:37:11 +0100 Subject: [PATCH 109/170] [enh] Add a helper to create a directory under /tmp --- data/apps/helpers.d/filesystem | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/data/apps/helpers.d/filesystem b/data/apps/helpers.d/filesystem index c3332a794..087a9e944 100644 --- a/data/apps/helpers.d/filesystem +++ b/data/apps/helpers.d/filesystem @@ -27,3 +27,15 @@ ynh_bind_or_cp() { fi $SUDO_CMD cp -r "$SRCDIR" "$DESTDIR" } + +# Create a directory under /tmp +# +# usage: ynh_mkdir_tmp +# | ret: the created directory path +ynh_mkdir_tmp() { + TMPDIR="/tmp/$(ynh_string_random 6)" + while [ -d $TMPDIR ]; do + TMPDIR="/tmp/$(ynh_string_random 6)" + done + mkdir -p "$TMPDIR" && echo "$TMPDIR" +} From e5c467ad266e11a8691090dcee220e89b147aea3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Thu, 24 Dec 2015 10:38:10 +0100 Subject: [PATCH 110/170] [enh] Add helpers to set and get an application setting --- data/apps/helpers.d/setting | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 data/apps/helpers.d/setting diff --git a/data/apps/helpers.d/setting b/data/apps/helpers.d/setting new file mode 100644 index 000000000..14d47c133 --- /dev/null +++ b/data/apps/helpers.d/setting @@ -0,0 +1,18 @@ +# Get an application setting +# +# usage: ynh_app_setting_get app key +# | arg: app - the application id +# | arg: key - the setting to get +ynh_app_setting_get() { + sudo yunohost app setting "$1" "$2" --output-as plain +} + +# Set an application setting +# +# usage: ynh_app_setting_set app key value +# | arg: app - the application id +# | arg: key - the setting name to set +# | arg: value - the setting value to set +ynh_app_setting_set() { + sudo yunohost app setting "$1" "$2" -v "$3" +} From 1cabe959d05c14796e315cc0e33e04b2e4c07faa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Thu, 24 Dec 2015 10:50:43 +0100 Subject: [PATCH 111/170] Revert "[enh] Add main domain GET route." This reverts commit 23354e92bc0fe28e8dfb8f1620350920adc6b8ad. Action must be defined only once in the actions map. --- data/actionsmap/yunohost.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 92b7d547d..853c4e386 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1198,13 +1198,6 @@ tools: pattern: *pattern_password required: True - ### tools_maindomain() - maindomain: - action_help: Get main domain - api: GET /domains/main - configuration: - authenticate: all - ### tools_maindomain() maindomain: action_help: Main domain change tool From 016c1ae4595458896f547d54eeae6426c23ce80e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Thu, 24 Dec 2015 10:54:02 +0100 Subject: [PATCH 112/170] [enh] Add main domain GET route --- data/actionsmap/yunohost.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 853c4e386..c24266310 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1201,7 +1201,9 @@ tools: ### tools_maindomain() maindomain: action_help: Main domain change tool - api: PUT /domains/main + api: + - GET /domains/main + - PUT /domains/main configuration: authenticate: all arguments: From c42ff4db677a56490c7e7fbeba02e67a7c907976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Thu, 24 Dec 2015 10:55:42 +0100 Subject: [PATCH 113/170] Update changelog for 2.3.5 release --- debian/changelog | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/debian/changelog b/debian/changelog index e1f459965..f8227558f 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,22 @@ +yunohost (2.3.5) testing; urgency=low + + [ opi ] + * [enh] Get app label for installed app in app list + * [enh] Short cache on handlebars templates + + [ Jérôme Lebleu ] + * [enh] Allow to pass the admin password as argument in the cli + * [enh] Add main domain GET route + * [enh] Provide bash helpers for MySQL databases and app settings (wip #97) + * [enh] Rename ynh_password bash helper to ynh_string_random + * [fix] Check app min_version with yunohost package (fixbug #113) + * [fix] Use --output-as instead of deprecated options + * [fix] Prevent error if unset variable is treated in utils helper + * [doc] Improve usage and add examples for user helpers + * [i18n] Update translations from Transifex belatedly + + -- Jérôme Lebleu Thu, 24 Dec 2015 10:55:36 +0100 + yunohost (2.3.4) testing; urgency=low [ Jérôme Lebleu ] From 2efc0f4543bf8fbe91560c8700750e9d07311b11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Thu, 24 Dec 2015 11:05:14 +0100 Subject: [PATCH 114/170] [fix] Update moulinette dependency minimum version --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index c08155675..2f1c5fdfd 100644 --- a/debian/control +++ b/debian/control @@ -10,7 +10,7 @@ Homepage: https://yunohost.org/ Package: yunohost Architecture: all Depends: ${python:Depends}, ${misc:Depends}, - moulinette (>= 2.3.3), + moulinette (>= 2.3.4), python-psutil, python-requests, glances, From 6f0c6b8f50e5575d6349ef48c4a554b6a933dbf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Thu, 24 Dec 2015 16:40:22 +0100 Subject: [PATCH 115/170] [fix] Do not block while set main domain --- data/actionsmap/yunohost.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index c24266310..e39aa8d47 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1206,6 +1206,7 @@ tools: - PUT /domains/main configuration: authenticate: all + lock: false arguments: -o: full: --old-domain From 94ce22f9d5f895301f789fe84bd3cdbcdaed14fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sun, 27 Dec 2015 22:49:53 +0100 Subject: [PATCH 116/170] [enh] Pass app id to scripts and remove hook_check action It passes the current app id to the app install/upgrade/remove scripts as the last argument (wip #126). The hook category has also been revisited to move some app specific parts away from there. The action hook_check has been removed accordingly. --- data/actionsmap/yunohost.yml | 8 ---- src/yunohost/app.py | 75 +++++++++++++++++++++++++++++++----- src/yunohost/hook.py | 64 ++---------------------------- 3 files changed, 69 insertions(+), 78 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index e39aa8d47..0374bce7b 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1345,14 +1345,6 @@ hook: help: Ordered list of arguments to pass to the script nargs: "*" - ### hook_check() - check: - action_help: Parse the script file and get arguments - api: GET /hook/check - arguments: - file: - help: File to check - ### hook_exec() exec: action_help: Execute hook from a file with arguments diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 325a68bb3..ccbed653f 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -383,9 +383,14 @@ def app_upgrade(auth, app=[], url=None, file=None): for hook in os.listdir(app_tmp_folder +'/hooks'): hook_add(app_id, app_tmp_folder +'/hooks/'+ hook) + # Retrieve arguments list for upgrade script + # TODO: Allow to specify arguments + args_list = _parse_args_from_manifest(manifest, 'upgrade') + args_list.append(app_id) + # Execute App upgrade script os.system('chown -hR admin: %s' % install_tmp) - if hook_exec(app_tmp_folder +'/scripts/upgrade') != 0: + if hook_exec(app_tmp_folder +'/scripts/upgrade', args_list) != 0: raise MoulinetteError(errno.EIO, m18n.n('installation_failed')) else: now = int(time.time()) @@ -514,12 +519,11 @@ def app_install(auth, app, label=None, args=None): os.system('chown -R admin: '+ app_tmp_folder) - try: - if args is None: - args = '' - args_dict = dict(urlparse.parse_qsl(args, keep_blank_values=True)) - except: - args_dict = {} + # Retrieve arguments list for install script + args_dict = {} if not args else \ + dict(urlparse.parse_qsl(args, keep_blank_values=True)) + args_list = _parse_args_from_manifest(manifest, 'install', args_dict) + args_list.append(app_id) # Execute App install script os.system('chown -hR admin: %s' % install_tmp) @@ -527,7 +531,7 @@ def app_install(auth, app, label=None, args=None): os.system('cp %s/manifest.json %s' % (app_tmp_folder, app_setting_path)) os.system('cp -R %s/scripts %s' % (app_tmp_folder, app_setting_path)) try: - if hook_exec(app_tmp_folder + '/scripts/install', args_dict) == 0: + if hook_exec(app_tmp_folder + '/scripts/install', args_list) == 0: # Store app status with open(app_setting_path + '/status.json', 'w+') as f: json.dump(status, f) @@ -584,7 +588,9 @@ def app_remove(auth, app): os.system('chown -R admin: /tmp/yunohost_remove') os.system('chmod -R u+rX /tmp/yunohost_remove') - if hook_exec('/tmp/yunohost_remove/scripts/remove') == 0: + args_list = [app] + + if hook_exec('/tmp/yunohost_remove/scripts/remove', args_list) == 0: msignals.display(m18n.n('app_removed', app), 'success') if os.path.exists(app_setting_path): shutil.rmtree(app_setting_path) @@ -1337,6 +1343,57 @@ def _encode_string(value): return value +def _parse_args_from_manifest(manifest, action, args={}): + """Parse arguments needed for an action from the manifest + + Retrieve specified arguments for the action from the manifest, and parse + given args according to that. If some required arguments are not provided, + its values will be asked if interaction is possible. + Parsed arguments will be returned as a list of strings to pass directly + to the proper script. + + Keyword arguments: + manifest -- The app manifest to use + action -- The action to retrieve arguments for + args -- A dictionnary of arguments to parse + + """ + args_list = [] + try: + action_args = manifest['arguments'][action] + except KeyError: + logger.debug("no arguments found for '%s' in '%s'", action, path) + else: + for arg in action_args: + if arg['name'] in args: + if 'choices' in arg and args[arg['name']] not in arg['choices']: + raise MoulinetteError(errno.EINVAL, + m18n.n('hook_choice_invalid', args[arg['name']])) + args_list.append(args[arg['name']]) + else: + if os.isatty(1) and 'ask' in arg: + # Retrieve proper ask string + ask_string = _value_for_locale(arg['ask']) + + # Append extra strings + if 'choices' in arg: + ask_string += ' ({:s})'.format('|'.join(arg['choices'])) + if 'default' in arg: + ask_string += ' (default: {:s})'.format(arg['default']) + + input_string = msignals.prompt(ask_string) + if not input_string and 'default' in arg: + input_string = arg['default'] + + args_list.append(input_string) + elif 'default' in arg: + args_list.append(arg['default']) + else: + raise MoulinetteError(errno.EINVAL, + m18n.n('hook_argument_missing', arg['name'])) + return args_list + + def is_true(arg): """ Convert a string into a boolean diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index 9c6d430e7..2b0902b6f 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -275,34 +275,13 @@ def hook_callback(action, hooks=[], args=None): return result -def hook_check(file): - """ - Parse the script file and get arguments - - Keyword argument: - file -- File to check - - """ - try: - with open(file[:file.index('scripts/')] + 'manifest.json') as f: - manifest = json.loads(str(f.read())) - except: - raise MoulinetteError(errno.EIO, m18n.n('app_manifest_invalid')) - - action = file[file.index('scripts/') + 8:] - if 'arguments' in manifest and action in manifest['arguments']: - return manifest['arguments'][action] - else: - return {} - - def hook_exec(path, args=None, raise_on_error=False, no_trace=False): """ Execute hook from a file with arguments Keyword argument: path -- Path of the script to execute - args -- Arguments to pass to the script + args -- A list of arguments to pass to the script raise_on_error -- Raise if the script returns a non-zero exit code no_trace -- Do not print each command that will be executed @@ -316,52 +295,15 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False): if not os.path.isfile(path): raise MoulinetteError(errno.EIO, m18n.g('file_not_exist')) - if isinstance(args, list): - arg_list = args - else: - required_args = hook_check(path) - if args is None: - args = {} - - arg_list = [] - for arg in required_args: - if arg['name'] in args: - if 'choices' in arg and args[arg['name']] not in arg['choices']: - raise MoulinetteError(errno.EINVAL, - m18n.n('hook_choice_invalid', args[arg['name']])) - arg_list.append(args[arg['name']]) - else: - if os.isatty(1) and 'ask' in arg: - # Retrieve proper ask string - ask_string = _value_for_locale(arg['ask']) - - # Append extra strings - if 'choices' in arg: - ask_string += ' ({:s})'.format('|'.join(arg['choices'])) - if 'default' in arg: - ask_string += ' (default: {:s})'.format(arg['default']) - - input_string = msignals.prompt(ask_string) - - if input_string == '' and 'default' in arg: - input_string = arg['default'] - - arg_list.append(input_string) - elif 'default' in arg: - arg_list.append(arg['default']) - else: - raise MoulinetteError(errno.EINVAL, - m18n.n('hook_argument_missing', arg['name'])) - # Construct command variables cmd_fdir, cmd_fname = os.path.split(path) cmd_fname = './{0}'.format(cmd_fname) cmd_args = '' - if arg_list: + if args and isinstance(args, list): # Concatenate arguments and escape them with double quotes to prevent # bash related issue if an argument is empty and is not the last - cmd_args = '"{:s}"'.format('" "'.join(str(s) for s in arg_list)) + cmd_args = '"{:s}"'.format('" "'.join(str(s) for s in args)) # Construct command to execute command = ['sudo', '-u', 'admin', '-H', 'sh', '-c'] From f0d0499257cc6cd82866dd390df15215377be28c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Mon, 28 Dec 2015 18:53:15 +0100 Subject: [PATCH 117/170] [fix] Add GRANT OPTION in ynh_mysql_create_db helper --- data/apps/helpers.d/mysql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/apps/helpers.d/mysql b/data/apps/helpers.d/mysql index b6ac12301..d4b655aa7 100644 --- a/data/apps/helpers.d/mysql +++ b/data/apps/helpers.d/mysql @@ -46,9 +46,9 @@ ynh_mysql_create_db() { # grant all privilegies to user if [[ $# -gt 1 ]]; then - sql+="GRANT ALL PRIVILEGES ON ${db}.* TO '${2}'@'localhost'" + sql+=" GRANT ALL PRIVILEGES ON ${db}.* TO '${2}'@'localhost'" [[ -n ${3:-} ]] && sql+=" IDENTIFIED BY '${3}'" - sql+=";" + sql+=" WITH GRANT OPTION;" fi ynh_mysql_execute_as_root "$sql" From 5fd2cf666796a7a1d39b46314e56cec5378b0fc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Mon, 28 Dec 2015 19:44:34 +0100 Subject: [PATCH 118/170] [enh] Rely only on app_id argument for multi-instances apps The original app_id hard-coded replacement in scripts and hooks is replaced by the use in app scripts directly of the given app_id argument (wip #126). It's now the app maintainer responsability to make use of it in a way to prevent colisions with other instances. --- src/yunohost/app.py | 54 +++------------------------------------------ 1 file changed, 3 insertions(+), 51 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index ccbed653f..bd96b266b 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -320,13 +320,8 @@ def app_upgrade(auth, app=[], url=None, file=None): if app_id in upgraded_apps: continue - if '__' in app_id: - original_app_id = app_id[:app_id.index('__')] - else: - original_app_id = app_id - current_app_dict = app_info(app_id, raw=True) - new_app_dict = app_info(original_app_id, raw=True) + new_app_dict = app_info(app_id, raw=True) if file: manifest = _extract_app_from_file(file) @@ -356,27 +351,6 @@ def app_upgrade(auth, app=[], url=None, file=None): status = _get_app_status(app_id) status['remote'] = manifest.get('remote', None) - if original_app_id != app_id: - # Replace original_app_id with the forked one in scripts - for script in os.listdir(app_tmp_folder +'/scripts'): - #TODO: do it with sed ? - if script[:1] != '.': - with open(app_tmp_folder +'/scripts/'+ script, "r") as sources: - lines = sources.readlines() - with open(app_tmp_folder +'/scripts/'+ script, "w") as sources: - for line in lines: - sources.write(re.sub(r''+ original_app_id +'', app_id, line)) - - if 'hooks' in os.listdir(app_tmp_folder): - for hook in os.listdir(app_tmp_folder +'/hooks'): - #TODO: do it with sed ? - if hook[:1] != '.': - with open(app_tmp_folder +'/hooks/'+ hook, "r") as sources: - lines = sources.readlines() - with open(app_tmp_folder +'/hooks/'+ hook, "w") as sources: - for line in lines: - sources.write(re.sub(r''+ original_app_id +'', app_id, line)) - # Clean hooks and add new ones hook_remove(app_id) if 'hooks' in os.listdir(app_tmp_folder): @@ -467,30 +441,8 @@ def app_install(auth, app, label=None, args=None): raise MoulinetteError(errno.EEXIST, m18n.n('app_already_installed', app_id)) - app_id_forked = app_id + '__' + str(instance_number) - - # Replace app_id with the new one in scripts - for script in os.listdir(app_tmp_folder +'/scripts'): - #TODO: do it with sed ? - if script[:1] != '.': - with open(app_tmp_folder +'/scripts/'+ script, "r") as sources: - lines = sources.readlines() - with open(app_tmp_folder +'/scripts/'+ script, "w") as sources: - for line in lines: - sources.write(re.sub(r''+ app_id +'', app_id_forked, line)) - - if 'hooks' in os.listdir(app_tmp_folder): - for hook in os.listdir(app_tmp_folder +'/hooks'): - #TODO: do it with sed ? - if hook[:1] != '.': - with open(app_tmp_folder +'/hooks/'+ hook, "r") as sources: - lines = sources.readlines() - with open(app_tmp_folder +'/hooks/'+ hook, "w") as sources: - for line in lines: - sources.write(re.sub(r''+ app_id +'', app_id_forked, line)) - - # Change app_id for the rest of the process - app_id = app_id_forked + # Change app_id to the forked app id + app_id = app_id + '__' + str(instance_number) # Prepare App settings app_setting_path = apps_setting_path +'/'+ app_id From 096c4d0246b53c00f34ae393b55a99ab80dab76e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Mon, 28 Dec 2015 19:58:21 +0100 Subject: [PATCH 119/170] [enh] Add support for app argument 'type' defined in the manifest --- locales/en.json | 1 + src/yunohost/app.py | 44 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/locales/en.json b/locales/en.json index d5b17c143..37f49bf03 100644 --- a/locales/en.json +++ b/locales/en.json @@ -29,6 +29,7 @@ "app_extraction_failed" : "Unable to extract installation files", "app_install_files_invalid" : "Invalid installation files", "app_manifest_invalid" : "Invalid app manifest", + "app_argument_invalid" : "Invalid value for argument '{name:s}': {error:s}", "app_sources_fetch_failed" : "Unable to fetch sources files", "ssowat_conf_updated" : "SSOwat persistent configuration successfully updated", "ssowat_conf_generated" : "SSOwat configuration successfully generated", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index bd96b266b..19e3cd94c 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -359,7 +359,7 @@ def app_upgrade(auth, app=[], url=None, file=None): # Retrieve arguments list for upgrade script # TODO: Allow to specify arguments - args_list = _parse_args_from_manifest(manifest, 'upgrade') + args_list = _parse_args_from_manifest(manifest, 'upgrade', auth=auth) args_list.append(app_id) # Execute App upgrade script @@ -474,7 +474,7 @@ def app_install(auth, app, label=None, args=None): # Retrieve arguments list for install script args_dict = {} if not args else \ dict(urlparse.parse_qsl(args, keep_blank_values=True)) - args_list = _parse_args_from_manifest(manifest, 'install', args_dict) + args_list = _parse_args_from_manifest(manifest, 'install', args_dict, auth) args_list.append(app_id) # Execute App install script @@ -1295,7 +1295,7 @@ def _encode_string(value): return value -def _parse_args_from_manifest(manifest, action, args={}): +def _parse_args_from_manifest(manifest, action, args={}, auth=None): """Parse arguments needed for an action from the manifest Retrieve specified arguments for the action from the manifest, and parse @@ -1310,6 +1310,9 @@ def _parse_args_from_manifest(manifest, action, args={}): args -- A dictionnary of arguments to parse """ + from yunohost.domain import domain_list + from yunohost.user import user_info + args_list = [] try: action_args = manifest['arguments'][action] @@ -1317,11 +1320,14 @@ def _parse_args_from_manifest(manifest, action, args={}): logger.debug("no arguments found for '%s' in '%s'", action, path) else: for arg in action_args: + arg_value = None + + # Attempt to retrieve argument value if arg['name'] in args: if 'choices' in arg and args[arg['name']] not in arg['choices']: raise MoulinetteError(errno.EINVAL, m18n.n('hook_choice_invalid', args[arg['name']])) - args_list.append(args[arg['name']]) + arg_value = args[arg['name']] else: if os.isatty(1) and 'ask' in arg: # Retrieve proper ask string @@ -1335,14 +1341,36 @@ def _parse_args_from_manifest(manifest, action, args={}): input_string = msignals.prompt(ask_string) if not input_string and 'default' in arg: - input_string = arg['default'] - - args_list.append(input_string) + arg_value = arg['default'] + else: + arg_value = input_string elif 'default' in arg: - args_list.append(arg['default']) + arg_value = arg['default'] else: raise MoulinetteError(errno.EINVAL, m18n.n('hook_argument_missing', arg['name'])) + + # Validate argument value + # TODO: Add more type, e.g. boolean + arg_type = arg.get('type', 'string') + if arg_type == 'domain': + if arg_value not in domain_list(auth)['domains']: + raise MoulinetteError(errno.EINVAL, + m18n.n('app_argument_invalid', + name=arg['name'], error=m18n.n('domain_unknown'))) + elif arg_type == 'user': + try: + user_info(auth, arg_value) + except MoulinetteError as e: + raise MoulinetteError(errno.EINVAL, + m18n.n('app_argument_invalid', + name=arg['name'], error=e.strerror)) + elif arg_type == 'app': + if not _is_installed(arg_value): + raise MoulinetteError(errno.EINVAL, + m18n.n('app_argument_invalid', + name=arg['name'], error=m18n.n('app_unknown'))) + args_list.append(arg_value) return args_list From 4a06cbdc31ab2465c8fbb6b98e9ba8f9a7a7bf23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Mon, 28 Dec 2015 20:10:55 +0100 Subject: [PATCH 120/170] [i18n] Review translations and keys related to app arguments --- locales/en.json | 4 ++-- src/yunohost/app.py | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/locales/en.json b/locales/en.json index 37f49bf03..ae2dfb1dd 100644 --- a/locales/en.json +++ b/locales/en.json @@ -29,7 +29,9 @@ "app_extraction_failed" : "Unable to extract installation files", "app_install_files_invalid" : "Invalid installation files", "app_manifest_invalid" : "Invalid app manifest", + "app_argument_choice_invalid" : "Invalid choice for argument '{name:s}', it must be one of {choices:s}", "app_argument_invalid" : "Invalid value for argument '{name:s}': {error:s}", + "app_argument_missing" : "Missing argument '{:s}'", "app_sources_fetch_failed" : "Unable to fetch sources files", "ssowat_conf_updated" : "SSOwat persistent configuration successfully updated", "ssowat_conf_generated" : "SSOwat configuration successfully generated", @@ -86,8 +88,6 @@ "hook_list_by_invalid" : "Invalid property to list hook by", "hook_name_unknown" : "Unknown hook name '{:s}'", - "hook_choice_invalid" : "Invalid choice '{:s}'", - "hook_argument_missing" : "Missing argument '{:s}'", "hook_exec_failed" : "Script execution failed", "hook_exec_not_terminated" : "Script execution hasn’t terminated", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 19e3cd94c..a0d2f1c07 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1320,14 +1320,16 @@ def _parse_args_from_manifest(manifest, action, args={}, auth=None): logger.debug("no arguments found for '%s' in '%s'", action, path) else: for arg in action_args: + arg_name = arg['name'] arg_value = None # Attempt to retrieve argument value - if arg['name'] in args: - if 'choices' in arg and args[arg['name']] not in arg['choices']: + if arg_name in args: + arg_value = args[arg_name] + if 'choices' in arg and arg_value not in arg['choices']: raise MoulinetteError(errno.EINVAL, - m18n.n('hook_choice_invalid', args[arg['name']])) - arg_value = args[arg['name']] + m18n.n('app_argument_choice_invalid', + name=arg_name, choices=', '.join(arg['choices']))) else: if os.isatty(1) and 'ask' in arg: # Retrieve proper ask string @@ -1348,7 +1350,7 @@ def _parse_args_from_manifest(manifest, action, args={}, auth=None): arg_value = arg['default'] else: raise MoulinetteError(errno.EINVAL, - m18n.n('hook_argument_missing', arg['name'])) + m18n.n('app_argument_missing', name=arg_name)) # Validate argument value # TODO: Add more type, e.g. boolean @@ -1357,19 +1359,19 @@ def _parse_args_from_manifest(manifest, action, args={}, auth=None): if arg_value not in domain_list(auth)['domains']: raise MoulinetteError(errno.EINVAL, m18n.n('app_argument_invalid', - name=arg['name'], error=m18n.n('domain_unknown'))) + name=arg_name, error=m18n.n('domain_unknown'))) elif arg_type == 'user': try: user_info(auth, arg_value) except MoulinetteError as e: raise MoulinetteError(errno.EINVAL, m18n.n('app_argument_invalid', - name=arg['name'], error=e.strerror)) + name=arg_name, error=e.strerror)) elif arg_type == 'app': if not _is_installed(arg_value): raise MoulinetteError(errno.EINVAL, m18n.n('app_argument_invalid', - name=arg['name'], error=m18n.n('app_unknown'))) + name=arg_name, error=m18n.n('app_unknown'))) args_list.append(arg_value) return args_list From 96f9ca99613c5c824a05aced55cfa91b9e531ded Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Mon, 28 Dec 2015 20:16:19 +0100 Subject: [PATCH 121/170] [fix] Validate app argument choice for input value too --- src/yunohost/app.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index a0d2f1c07..4affccf12 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1326,10 +1326,6 @@ def _parse_args_from_manifest(manifest, action, args={}, auth=None): # Attempt to retrieve argument value if arg_name in args: arg_value = args[arg_name] - if 'choices' in arg and arg_value not in arg['choices']: - raise MoulinetteError(errno.EINVAL, - m18n.n('app_argument_choice_invalid', - name=arg_name, choices=', '.join(arg['choices']))) else: if os.isatty(1) and 'ask' in arg: # Retrieve proper ask string @@ -1337,7 +1333,7 @@ def _parse_args_from_manifest(manifest, action, args={}, auth=None): # Append extra strings if 'choices' in arg: - ask_string += ' ({:s})'.format('|'.join(arg['choices'])) + ask_string += ' [{:s}]'.format(' | '.join(arg['choices'])) if 'default' in arg: ask_string += ' (default: {:s})'.format(arg['default']) @@ -1353,6 +1349,10 @@ def _parse_args_from_manifest(manifest, action, args={}, auth=None): m18n.n('app_argument_missing', name=arg_name)) # Validate argument value + if 'choices' in arg and arg_value not in arg['choices']: + raise MoulinetteError(errno.EINVAL, + m18n.n('app_argument_choice_invalid', + name=arg_name, choices=', '.join(arg['choices']))) # TODO: Add more type, e.g. boolean arg_type = arg.get('type', 'string') if arg_type == 'domain': From aba617005463057a4fd7729c7ecfedc8a532f644 Mon Sep 17 00:00:00 2001 From: Sebastien Badia Date: Tue, 29 Dec 2015 00:19:01 +0100 Subject: [PATCH 122/170] hooks: Use a more elegant grep command for mysql process check --- data/hooks/conf_regen/34-mysql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/hooks/conf_regen/34-mysql b/data/hooks/conf_regen/34-mysql index d34b30880..8617e5573 100644 --- a/data/hooks/conf_regen/34-mysql +++ b/data/hooks/conf_regen/34-mysql @@ -1,5 +1,5 @@ #!/bin/bash -set -e +set -e force=$1 @@ -26,7 +26,7 @@ if [[ "$(safe_copy my.cnf /etc/mysql/my.cnf | tail -n1)" == "True" ]]; then fi if [ ! -f /etc/yunohost/mysql ]; then - [[ $(/bin/ps aux | grep mysqld | grep -vc "grep") == "0" ]] \ + [[ $(/bin/ps aux | grep '[m]ysqld') == "0" ]] \ && sudo service mysql start mysql_password=$(randpass 10 0) From 68654562349199d92fc9247e8fe721a9016b33d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Tue, 29 Dec 2015 21:01:17 +0100 Subject: [PATCH 123/170] [fix] Log rotation is already handled by WatchedFileHandler (fixbug #137) --- debian/logrotate | 3 --- debian/yunohost-api.init | 13 ------------- 2 files changed, 16 deletions(-) diff --git a/debian/logrotate b/debian/logrotate index 0bc07ca59..4d1cc22df 100644 --- a/debian/logrotate +++ b/debian/logrotate @@ -5,7 +5,4 @@ compress notifempty missingok - postrotate - invoke-rc.d yunohost-api rotate >/dev/null 2>&1 - endscript } diff --git a/debian/yunohost-api.init b/debian/yunohost-api.init index 02a0560b7..3cda507e6 100755 --- a/debian/yunohost-api.init +++ b/debian/yunohost-api.init @@ -80,14 +80,6 @@ do_reload() { return 0 } -# -# Function called when rotating logs -# -do_rotate() { - start-stop-daemon --stop --signal USR1 --quiet --pidfile $PID --name $NAME - return 0 -} - case "$1" in start) [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" @@ -131,11 +123,6 @@ case "$1" in ;; esac ;; - rotate) - log_daemon_msg "Re-opening $DESC log files" "$NAME" - do_rotate - log_end_msg $? - ;; *) echo "Usage: $SCRIPTNAME {start|stop|status|restart|reload}" >&2 exit 3 From d2c393ea5a45f50ffe1ea263339508063cbc97a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Thu, 31 Dec 2015 11:49:32 +0100 Subject: [PATCH 124/170] [enh] Add ping util as recommended package --- debian/control | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/control b/debian/control index 2f1c5fdfd..6e04f136b 100644 --- a/debian/control +++ b/debian/control @@ -37,6 +37,7 @@ Depends: ${python:Depends}, ${misc:Depends}, rspamd, rmilter, memcached, opendkim-tools Recommends: yunohost-admin, bash-completion, rsyslog, ntp, openssh-server, + inetutils-ping | iputils-ping, php5-gd, php5-curl, php-gettext, php5-mcrypt, udisks-glue, unattended-upgrades, libdbd-ldap-perl, libnet-dns-perl From 4b4d91c6d32e075407424c254049e0d9390bbb4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Thu, 31 Dec 2015 13:00:35 +0100 Subject: [PATCH 125/170] [fix] Use rmilter as a socket-activated service --- data/hooks/conf_regen/28-rmilter | 12 +++++++++--- data/templates/rmilter/rmilter.conf | 20 ++++++++++---------- data/templates/rmilter/rmilter.socket | 5 +++++ 3 files changed, 24 insertions(+), 13 deletions(-) create mode 100644 data/templates/rmilter/rmilter.socket diff --git a/data/hooks/conf_regen/28-rmilter b/data/hooks/conf_regen/28-rmilter index abdb7d670..286941f34 100644 --- a/data/hooks/conf_regen/28-rmilter +++ b/data/hooks/conf_regen/28-rmilter @@ -18,9 +18,12 @@ cd /usr/share/yunohost/templates/rmilter # Copy Rmilter configuration safe_copy rmilter.conf /etc/rmilter.conf +# Override socket configuration +safe_copy rmilter.socket /etc/systemd/system/rmilter.socket + # Create the PID directory -sudo mkdir -p /var/run/rmilter -sudo chown _rmilter: /var/run/rmilter +sudo mkdir -p /run/rmilter +sudo chown -hR _rmilter: /run/rmilter # Create DKIM key for each YunoHost domain sudo mkdir -p /etc/dkim @@ -38,4 +41,7 @@ for domain in $domain_list; do sudo chmod 400 /etc/dkim/$domain.mail.key done -sudo service rmilter restart +# Reload systemd daemon and stop rmilter service to take into account the +# new configuration. It will be started again by the socket as needed. +sudo systemctl daemon-reload +sudo systemctl stop rmilter.service 2>&1 || true diff --git a/data/templates/rmilter/rmilter.conf b/data/templates/rmilter/rmilter.conf index d74196df8..d585b9217 100644 --- a/data/templates/rmilter/rmilter.conf +++ b/data/templates/rmilter/rmilter.conf @@ -1,18 +1,18 @@ # systemd-specific settings for rmilter -.include /etc/rmilter.conf.common +.include /etc/rmilter.conf.common -pidfile = /var/run/rmilter/rmilter.pid; +# pidfile - path to pid file +pidfile = /run/rmilter/rmilter.pid; -# listen on TCP socket -bind_socket = inet:11000@localhost; +# rmilter is socket-activated under systemd +bind_socket = fd:3; # DKIM signing dkim { - domain { - key = /etc/dkim; - domain = "*"; - selector = "mail"; - }; + domain { + key = /etc/dkim; + domain = "*"; + selector = "mail"; + }; }; - diff --git a/data/templates/rmilter/rmilter.socket b/data/templates/rmilter/rmilter.socket new file mode 100644 index 000000000..dc3ae7a2a --- /dev/null +++ b/data/templates/rmilter/rmilter.socket @@ -0,0 +1,5 @@ +.include /lib/systemd/system/rmilter.socket + +[Socket] +ListenStream= +ListenStream=127.0.0.1:11000 From 6a836ae0bfd368d4928d9fd7316a7d3fb2cf49b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Thu, 31 Dec 2015 19:03:58 +0100 Subject: [PATCH 126/170] [fix] Parse app arguments before creating app folder and settings --- src/yunohost/app.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 4affccf12..aa245a5ea 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -444,6 +444,12 @@ def app_install(auth, app, label=None, args=None): # Change app_id to the forked app id app_id = app_id + '__' + str(instance_number) + # Retrieve arguments list for install script + args_dict = {} if not args else \ + dict(urlparse.parse_qsl(args, keep_blank_values=True)) + args_list = _parse_args_from_manifest(manifest, 'install', args_dict, auth) + args_list.append(app_id) + # Prepare App settings app_setting_path = apps_setting_path +'/'+ app_id @@ -471,12 +477,6 @@ def app_install(auth, app, label=None, args=None): os.system('chown -R admin: '+ app_tmp_folder) - # Retrieve arguments list for install script - args_dict = {} if not args else \ - dict(urlparse.parse_qsl(args, keep_blank_values=True)) - args_list = _parse_args_from_manifest(manifest, 'install', args_dict, auth) - args_list.append(app_id) - # Execute App install script os.system('chown -hR admin: %s' % install_tmp) # Move scripts and manifest to the right place From 13da1e797c61c54bf186796211f282f080dc864c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Thu, 31 Dec 2015 19:10:32 +0100 Subject: [PATCH 127/170] [fix] Use INFO logging level if app setting is not found --- src/yunohost/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index aa245a5ea..13790dbd5 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -788,7 +788,7 @@ def app_setting(app, key, value=None, delete=False): try: return app_settings[key] except: - logger.exception("cannot get app setting '%s' for '%s'", key, app) + logger.info("cannot get app setting '%s' for '%s'", key, app) return None else: yaml_settings=['redirected_urls','redirected_regex'] From 58145509e2ffd61c90626816349480dd990982f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sat, 2 Jan 2016 11:46:17 +0100 Subject: [PATCH 128/170] [fix] Split service_configuration_conflict translation key (fixbug #136) --- locales/en.json | 4 ++-- src/yunohost/service.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/locales/en.json b/locales/en.json index ae2dfb1dd..520e7660e 100644 --- a/locales/en.json +++ b/locales/en.json @@ -121,10 +121,10 @@ "service_no_log" : "No log to display for service '{:s}'", "service_cmd_exec_failed" : "Unable to execute command '{:s}'", "services_configured": "Configuration successfully generated", - "service_configuration_conflict": "The file {file:s} has been changed since its last generation. Please apply the modifications manually or use the option --force (it will erase all the modifications previously done to the file). Here are the differences:\n{diff:s}", + "service_configuration_conflict": "The file {file:s} has been changed since its last generation. Please apply the modifications manually or use the option --force (it will erase all the modifications previously done to the file).", "no_such_conf_file": "Unable to copy the file {file:s}: the file does not exist", "service_add_configuration": "Adding the configuration file {file:s}", - + "show_diff": "Here are the differences:\n{diff:s}", "network_check_smtp_ok" : "Outbound mail (SMTP port 25) is not blocked", "network_check_smtp_ko" : "Outbound mail (SMTP port 25) seems to be blocked by your network", diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 7c445ae30..3823aef4a 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -537,8 +537,9 @@ def service_safecopy(service, new_conf_file, conf_file, force=False): else: new_hash = previous_hash if (len(previous_hash) == 32 or previous_hash[-32:] != current_hash): - logger.warning(m18n.n('service_configuration_conflict', - file=conf_file, diff=''.join(diff))) + logger.warning('{0} {1}'.format( + m18n.n('service_configuration_conflict', file=conf_file), + m18n.n('show_diff', diff=''.join(diff)))) # Remove the backup file if the configuration has not changed if new_hash == previous_hash: From 9285976acc0a2bdadbd93494f9b9428977db6622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sat, 2 Jan 2016 13:08:03 +0100 Subject: [PATCH 129/170] [enh] Integrate 'optional' key of arguments in app manifest --- locales/en.json | 2 +- src/yunohost/app.py | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/locales/en.json b/locales/en.json index 520e7660e..4c0dd51a5 100644 --- a/locales/en.json +++ b/locales/en.json @@ -31,7 +31,7 @@ "app_manifest_invalid" : "Invalid app manifest", "app_argument_choice_invalid" : "Invalid choice for argument '{name:s}', it must be one of {choices:s}", "app_argument_invalid" : "Invalid value for argument '{name:s}': {error:s}", - "app_argument_missing" : "Missing argument '{:s}'", + "app_argument_required" : "Argument '{name:s}' is required", "app_sources_fetch_failed" : "Unable to fetch sources files", "ssowat_conf_updated" : "SSOwat persistent configuration successfully updated", "ssowat_conf_generated" : "SSOwat configuration successfully generated", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 13790dbd5..f43dd60be 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1344,15 +1344,22 @@ def _parse_args_from_manifest(manifest, action, args={}, auth=None): arg_value = input_string elif 'default' in arg: arg_value = arg['default'] - else: - raise MoulinetteError(errno.EINVAL, - m18n.n('app_argument_missing', name=arg_name)) # Validate argument value + if not arg_value and not arg.get('optional', False): + raise MoulinetteError(errno.EINVAL, + m18n.n('app_argument_required', name=arg_name)) + elif not arg_value: + args_list.append('') + continue + + # Validate argument choice if 'choices' in arg and arg_value not in arg['choices']: raise MoulinetteError(errno.EINVAL, m18n.n('app_argument_choice_invalid', name=arg_name, choices=', '.join(arg['choices']))) + + # Validate argument type # TODO: Add more type, e.g. boolean arg_type = arg.get('type', 'string') if arg_type == 'domain': From 3246dc44dd6f843c5ee700ff0bda4fd9dc357fd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sat, 2 Jan 2016 13:33:25 +0100 Subject: [PATCH 130/170] [fix] Correct debug message when no arguments found in the manifest --- src/yunohost/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index f43dd60be..4fb54d9a5 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1317,7 +1317,7 @@ def _parse_args_from_manifest(manifest, action, args={}, auth=None): try: action_args = manifest['arguments'][action] except KeyError: - logger.debug("no arguments found for '%s' in '%s'", action, path) + logger.debug("no arguments found for '%s' in manifest", action) else: for arg in action_args: arg_name = arg['name'] From bd64a0c661b8a1715d8b52defb3465e6e9888b9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sun, 3 Jan 2016 17:20:32 +0100 Subject: [PATCH 131/170] [enh] Add a helper to check if a user exists on the system --- data/apps/helpers.d/user | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/data/apps/helpers.d/user b/data/apps/helpers.d/user index fc58d0027..bfd044070 100644 --- a/data/apps/helpers.d/user +++ b/data/apps/helpers.d/user @@ -1,15 +1,14 @@ -# Check if a user exists +# Check if a YunoHost user exists # # example: ynh_user_exists 'toto' || exit 1 # # usage: ynh_user_exists username # | arg: username - the username to check -# | ret: retcode - 0 if user exists, 1 otherwise ynh_user_exists() { sudo yunohost user list --output-as json | grep -q "\"username\": \"${1}\"" } -# Retrieve a user information +# Retrieve a YunoHost user information # # example: mail=$(ynh_user_get_info 'toto' 'mail') # @@ -18,5 +17,13 @@ ynh_user_exists() { # | arg: key - the key to retrieve # | ret: string - the key's value ynh_user_get_info() { - sudo yunohost user info "${1}" --output-as plain | ynh_get_plain_key "${2}" + sudo yunohost user info "$1" --output-as plain | ynh_get_plain_key "$2" +} + +# Check if a user exists on the system +# +# usage: ynh_system_user_exists username +# | arg: username - the username to check +ynh_system_user_exists() { + getent passwd "$1" &>/dev/null } From 2ce7c4b8df2a987d312937b2e302b03a9c3de37b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sun, 3 Jan 2016 17:31:18 +0100 Subject: [PATCH 132/170] [enh] Provide bash helpers for packages manipulation (wip #97) --- data/apps/helpers.d/package | 78 +++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 data/apps/helpers.d/package diff --git a/data/apps/helpers.d/package b/data/apps/helpers.d/package new file mode 100644 index 000000000..266b40e05 --- /dev/null +++ b/data/apps/helpers.d/package @@ -0,0 +1,78 @@ +# Check either a package is installed or not +# +# example: ynh_package_is_installed 'yunohost' && echo "ok" +# +# usage: ynh_package_is_installed name +# | arg: name - the package name to check +ynh_package_is_installed() { + dpkg-query -W -f '${Status}' "$1" 2>/dev/null \ + | grep -c "ok installed" &>/dev/null +} + +# Get the version of an installed package +# +# example: version=$(ynh_package_version 'yunohost') +# +# usage: ynh_package_version name +# | arg: name - the package name to get version +# | ret: the version or an empty string +ynh_package_version() { + if ynh_package_is_installed "$1"; then + dpkg-query -W -f '${Version}' "$1" 2>/dev/null + else + echo '' + fi +} + +# Install package(s) +# +# usage: ynh_package_install name [name [...]] +# | arg: name - the package name to install +ynh_package_install() { + sudo apt-get -y -qq install $@ +} + +# Build and install a package from an equivs control file +# +# example: generate an empty control file with `equivs-control`, adjust its +# content and use helper to build and install the package: +# ynh_package_install_from_equivs /path/to/controlfile +# +# usage: ynh_package_install_from_equivs controlfile +# | arg: controlfile - path of the equivs control file +ynh_package_install_from_equivs() { + ynh_package_is_installed 'equivs' \ + || ynh_package_install equivs + + # retrieve package information + pkgname=$(grep '^Package: ' $1 | cut -d' ' -f 2) + pkgversion=$(grep '^Version: ' $1 | cut -d' ' -f 2) + [[ -z "$pkgname" || -z "$pkgversion" ]] \ + && echo "Invalid control file" && exit 1 + controlfile=$(readlink -f "$1") + + # build and install the package + TMPDIR=$(ynh_mkdir_tmp) + (cd $TMPDIR \ + && equivs-build "$controlfile" 1>/dev/null \ + && sudo dpkg --force-depends \ + -i "./${pkgname}_${pkgversion}_all.deb" 2>&1 \ + && sudo apt-get -f -y -qq install) \ + && ([[ -n "$TMPDIR" ]] && rm -rf $TMPDIR) +} + +# Remove package(s) +# +# usage: ynh_package_remove name [name [...]] +# | arg: name - the package name to remove +ynh_package_remove() { + sudo apt-get -y -qq remove $@ +} + +# Remove package(s) and their uneeded dependencies +# +# usage: ynh_package_autoremove name [name [...]] +# | arg: name - the package name to remove +ynh_package_autoremove() { + sudo apt-get -y -qq autoremove $@ +} From 4447e02b3e13d2962cfcd21735053299fff7175d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sun, 3 Jan 2016 20:41:57 +0100 Subject: [PATCH 133/170] [enh] Implement 'boolean' argument type support in app manifest --- src/yunohost/app.py | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 4fb54d9a5..49b264724 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1321,6 +1321,9 @@ def _parse_args_from_manifest(manifest, action, args={}, auth=None): else: for arg in action_args: arg_name = arg['name'] + arg_type = arg.get('type', 'string') + arg_default = arg.get('default', None) + arg_choices = arg.get('choices', []) arg_value = None # Attempt to retrieve argument value @@ -1332,18 +1335,23 @@ def _parse_args_from_manifest(manifest, action, args={}, auth=None): ask_string = _value_for_locale(arg['ask']) # Append extra strings - if 'choices' in arg: - ask_string += ' [{:s}]'.format(' | '.join(arg['choices'])) - if 'default' in arg: - ask_string += ' (default: {:s})'.format(arg['default']) + if arg_type == 'boolean': + ask_string += ' [0 | 1]' + if arg_default is not None: + arg_default = 1 if arg_default else 0 + elif arg_choices: + ask_string += ' [{0}]'.format(' | '.join(arg_choices)) + if arg_default is not None: + ask_string += ' (default: {0})'.format(arg_default) input_string = msignals.prompt(ask_string) - if not input_string and 'default' in arg: - arg_value = arg['default'] + if (input_string == '' or input_string is None) \ + and arg_default is not None: + arg_value = arg_default else: arg_value = input_string - elif 'default' in arg: - arg_value = arg['default'] + elif arg_default is not None: + arg_value = arg_default # Validate argument value if not arg_value and not arg.get('optional', False): @@ -1354,14 +1362,12 @@ def _parse_args_from_manifest(manifest, action, args={}, auth=None): continue # Validate argument choice - if 'choices' in arg and arg_value not in arg['choices']: + if arg_choices and arg_value not in arg_choices: raise MoulinetteError(errno.EINVAL, m18n.n('app_argument_choice_invalid', - name=arg_name, choices=', '.join(arg['choices']))) + name=arg_name, choices=', '.join(arg_choices))) # Validate argument type - # TODO: Add more type, e.g. boolean - arg_type = arg.get('type', 'string') if arg_type == 'domain': if arg_value not in domain_list(auth)['domains']: raise MoulinetteError(errno.EINVAL, @@ -1379,6 +1385,18 @@ def _parse_args_from_manifest(manifest, action, args={}, auth=None): raise MoulinetteError(errno.EINVAL, m18n.n('app_argument_invalid', name=arg_name, error=m18n.n('app_unknown'))) + elif arg_type == 'boolean': + if isinstance(arg_value, bool): + arg_value = 1 if arg_value else 0 + else: + try: + arg_value = int(arg_value) + if arg_value not in [0, 1]: + raise ValueError() + except (TypeError, ValueError): + raise MoulinetteError(errno.EINVAL, + m18n.n('app_argument_choice_invalid', + name=arg_name, choices='0, 1')) args_list.append(arg_value) return args_list From 77d901346e64d980f18d548340cf6493c7e4fca8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Mon, 4 Jan 2016 17:29:45 +0100 Subject: [PATCH 134/170] [fix] Set default value of boolean argument type to false if unset --- src/yunohost/app.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 49b264724..d3ef4682a 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1326,6 +1326,11 @@ def _parse_args_from_manifest(manifest, action, args={}, auth=None): arg_choices = arg.get('choices', []) arg_value = None + # Transpose default value for boolean type and set it to + # false if not defined. + if arg_type == 'boolean': + arg_default = 1 if arg_default else 0 + # Attempt to retrieve argument value if arg_name in args: arg_value = args[arg_name] @@ -1337,8 +1342,6 @@ def _parse_args_from_manifest(manifest, action, args={}, auth=None): # Append extra strings if arg_type == 'boolean': ask_string += ' [0 | 1]' - if arg_default is not None: - arg_default = 1 if arg_default else 0 elif arg_choices: ask_string += ' [{0}]'.format(' | '.join(arg_choices)) if arg_default is not None: From 9427006d015ef7d2cffa03e1d7d68884213c9e20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Fri, 8 Jan 2016 21:51:20 +0100 Subject: [PATCH 135/170] [fix] Remove useless SPF setting in Postfix configuration (fixbug #150) --- data/templates/postfix/main.cf.sed | 3 --- 1 file changed, 3 deletions(-) diff --git a/data/templates/postfix/main.cf.sed b/data/templates/postfix/main.cf.sed index c6354bb42..394e23e0b 100644 --- a/data/templates/postfix/main.cf.sed +++ b/data/templates/postfix/main.cf.sed @@ -127,9 +127,6 @@ smtpd_recipient_restrictions = reject_unauth_destination, permit -# Use SPF -policy-spf_time_limit = 3600s - # SRS sender_canonical_maps = regexp:/etc/postfix/sender_canonical sender_canonical_classes = envelope_sender From c8b481344014ba398046b1055f120184a8de5200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Fri, 8 Jan 2016 22:11:00 +0100 Subject: [PATCH 136/170] [fix] Add procmail to packages dependencies --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 6e04f136b..6f5631193 100644 --- a/debian/control +++ b/debian/control @@ -28,7 +28,7 @@ Depends: ${python:Depends}, ${misc:Depends}, curl, mariadb-server | mysql-server, php5-mysql | php5-mysqlnd, slapd, ldap-utils, sudo-ldap, libnss-ldapd, - postfix-ldap, postfix-policyd-spf-perl, postfix-pcre, + postfix-ldap, postfix-policyd-spf-perl, postfix-pcre, procmail, dovecot-ldap, dovecot-lmtpd, dovecot-managesieved, dovecot-antispam, fail2ban, nginx-extras (>=1.6.2), php5-fpm, php5-ldap, php5-intl, From 07bfae73a48ac665fde87b5ba3bd5e9374ff66f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sun, 17 Jan 2016 02:52:24 +0100 Subject: [PATCH 137/170] [enh] Add ynh_package_update helper and call it in install_from_equivs --- data/apps/helpers.d/package | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/data/apps/helpers.d/package b/data/apps/helpers.d/package index 266b40e05..dd78692f3 100644 --- a/data/apps/helpers.d/package +++ b/data/apps/helpers.d/package @@ -24,6 +24,13 @@ ynh_package_version() { fi } +# Update package index files +# +# usage: ynh_package_update +ynh_package_update() { + sudo apt-get -y -qq update +} + # Install package(s) # # usage: ynh_package_install name [name [...]] @@ -51,6 +58,9 @@ ynh_package_install_from_equivs() { && echo "Invalid control file" && exit 1 controlfile=$(readlink -f "$1") + # update packages cache + ynh_package_update + # build and install the package TMPDIR=$(ynh_mkdir_tmp) (cd $TMPDIR \ From a975091981f9df8368037cd610b30cf3756b0735 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sun, 17 Jan 2016 02:58:08 +0100 Subject: [PATCH 138/170] Update changelog for 2.3.6 release --- debian/changelog | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/debian/changelog b/debian/changelog index f8227558f..5a631f001 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,37 @@ +yunohost (2.3.6) testing; urgency=low + + [ Jérôme Lebleu ] + * [fix] Update moulinette dependency minimum version + * [fix] Do not block while set main domain + * [enh] Pass app id to scripts and remove hook_check action + * [fix] Add GRANT OPTION in ynh_mysql_create_db helper + * [enh] Rely only on app_id argument for multi-instances apps + * [enh] Add support for app argument 'type' defined in the manifest + * [i18n] Review translations and keys related to app arguments + * [fix] Validate app argument choice for input value too + + [ Sebastien Badia ] + * hooks: Use a more elegant grep command for mysql process check + + [ Jérôme Lebleu ] + * [fix] Log rotation is already handled by WatchedFileHandler (fixbug #137) + * [enh] Add ping util as recommended package + * [fix] Use rmilter as a socket-activated service + * [fix] Parse app arguments before creating app folder and settings + * [fix] Use INFO logging level if app setting is not found + * [fix] Split service_configuration_conflict translation key (fixbug #136) + * [enh] Integrate 'optional' key of arguments in app manifest + * [fix] Correct debug message when no arguments found in the manifest + * [enh] Add a helper to check if a user exists on the system + * [enh] Provide bash helpers for packages manipulation (wip #97) + * [enh] Implement 'boolean' argument type support in app manifest + * [fix] Set default value of boolean argument type to false if unset + * [fix] Remove useless SPF setting in Postfix configuration (fixbug #150) + * [fix] Add procmail to packages dependencies + * [enh] Add ynh_package_update helper and call it in install_from_equivs + + -- Jérôme Lebleu Sun, 17 Jan 2016 02:57:53 +0100 + yunohost (2.3.5) testing; urgency=low [ opi ] From 41627e56d7e5ce66a0f4242d9ff9d868c735a94c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sun, 17 Jan 2016 03:03:21 +0100 Subject: [PATCH 139/170] Update changelog for 2.3.6 release --- debian/changelog | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/debian/changelog b/debian/changelog index 5a631f001..2badd9262 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,36 +1,32 @@ yunohost (2.3.6) testing; urgency=low [ Jérôme Lebleu ] - * [fix] Update moulinette dependency minimum version - * [fix] Do not block while set main domain * [enh] Pass app id to scripts and remove hook_check action - * [fix] Add GRANT OPTION in ynh_mysql_create_db helper * [enh] Rely only on app_id argument for multi-instances apps * [enh] Add support for app argument 'type' defined in the manifest - * [i18n] Review translations and keys related to app arguments - * [fix] Validate app argument choice for input value too - - [ Sebastien Badia ] - * hooks: Use a more elegant grep command for mysql process check - - [ Jérôme Lebleu ] - * [fix] Log rotation is already handled by WatchedFileHandler (fixbug #137) + * [enh] Integrate 'optional' key of arguments in app manifest + * [enh] Implement 'boolean' argument type support in app manifest * [enh] Add ping util as recommended package + * [enh] Add a helper to check if a user exists on the system + * [enh] Provide bash helpers for packages manipulation (wip #97) + * [enh] Add ynh_package_update helper and call it in install_from_equivs + * [fix] Do not block while set main domain + * [fix] Add GRANT OPTION in ynh_mysql_create_db helper + * [fix] Validate app argument choice for input value too + * [fix] Log rotation is already handled by WatchedFileHandler (fixbug #137) * [fix] Use rmilter as a socket-activated service * [fix] Parse app arguments before creating app folder and settings * [fix] Use INFO logging level if app setting is not found * [fix] Split service_configuration_conflict translation key (fixbug #136) - * [enh] Integrate 'optional' key of arguments in app manifest - * [fix] Correct debug message when no arguments found in the manifest - * [enh] Add a helper to check if a user exists on the system - * [enh] Provide bash helpers for packages manipulation (wip #97) - * [enh] Implement 'boolean' argument type support in app manifest * [fix] Set default value of boolean argument type to false if unset * [fix] Remove useless SPF setting in Postfix configuration (fixbug #150) * [fix] Add procmail to packages dependencies - * [enh] Add ynh_package_update helper and call it in install_from_equivs + * [i18n] Review translations and keys related to app arguments - -- Jérôme Lebleu Sun, 17 Jan 2016 02:57:53 +0100 + [ Sebastien Badia ] + * hooks: Use a more elegant grep command for mysql process check + + -- Jérôme Lebleu Sun, 17 Jan 2016 02:57:53 +0100 yunohost (2.3.5) testing; urgency=low @@ -49,7 +45,7 @@ yunohost (2.3.5) testing; urgency=low * [doc] Improve usage and add examples for user helpers * [i18n] Update translations from Transifex belatedly - -- Jérôme Lebleu Thu, 24 Dec 2015 10:55:36 +0100 + -- Jérôme Lebleu Thu, 24 Dec 2015 10:55:36 +0100 yunohost (2.3.4) testing; urgency=low From ebdf17791adc36a9672c2e3f164ffc724be004a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sun, 17 Jan 2016 22:30:40 +0100 Subject: [PATCH 140/170] [fix] Allow false and 0 as non-empty values for an app argument --- src/yunohost/app.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index d3ef4682a..7bfacb94f 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1357,7 +1357,8 @@ def _parse_args_from_manifest(manifest, action, args={}, auth=None): arg_value = arg_default # Validate argument value - if not arg_value and not arg.get('optional', False): + if (arg_value is None or arg_value == '') \ + and not arg.get('optional', False): raise MoulinetteError(errno.EINVAL, m18n.n('app_argument_required', name=arg_name)) elif not arg_value: From d041a5f2640b186ff5cebea5114b9175799cd516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Tue, 19 Jan 2016 18:49:06 +0100 Subject: [PATCH 141/170] [enh] Replace msignals.display by logging in backup category --- locales/en.json | 6 +- src/yunohost/backup.py | 157 +++++++++++++++++------------------------ 2 files changed, 66 insertions(+), 97 deletions(-) diff --git a/locales/en.json b/locales/en.json index 4c0dd51a5..8414a0ba9 100644 --- a/locales/en.json +++ b/locales/en.json @@ -154,11 +154,11 @@ "backup_output_directory_not_empty" : "Output directory is not empty", "backup_hook_unknown" : "Backup hook '{hook:s}' unknown", "backup_running_hooks" : "Running backup hooks...", - "backup_running_app_script" : "Running backup script of app '{:s}'...", + "backup_running_app_script" : "Running backup script of app '{app:s}'...", "backup_creating_archive" : "Creating the backup archive...", "backup_extracting_archive" : "Extracting the backup archive...", "backup_archive_open_failed" : "Unable to open the backup archive", - "backup_archive_name_unknown" : "Unknown local backup archive named '{:s}'", + "backup_archive_name_unknown" : "Unknown local backup archive named '{name:s}'", "backup_archive_name_exists" : "Backup archive name already exists", "backup_archive_hook_not_exec" : "Hook '{hook:s}' not executed in this backup", "backup_archive_app_not_found" : "App '{app:s}' not found in the backup archive", @@ -181,7 +181,7 @@ "unbackup_app" : "App '{app:s}' will not be saved", "no_restore_script": "No restore script found for the app '{app:s}'", "unrestore_app" : "App '{app:s}' will not be restored", - "backup_delete_error" : "Unable to delete '{:s}'", + "backup_delete_error" : "Unable to delete '{path:s}'", "backup_deleted" : "Backup successfully deleted", "field_invalid" : "Invalid field '{:s}'", diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 429780a12..0fe3a1baa 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -70,7 +70,7 @@ def backup_create(name=None, description=None, output_directory=None, # Validate what to backup if ignore_hooks and ignore_apps: raise MoulinetteError(errno.EINVAL, - m18n.n('backup_action_required')) + m18n.n('backup_action_required')) # Validate and define backup name timestamp = int(time.time()) @@ -78,12 +78,12 @@ def backup_create(name=None, description=None, output_directory=None, name = time.strftime('%Y%m%d-%H%M%S') if name in backup_list()['archives']: raise MoulinetteError(errno.EINVAL, - m18n.n('backup_archive_name_exists')) + m18n.n('backup_archive_name_exists')) # Validate additional arguments if no_compress and not output_directory: raise MoulinetteError(errno.EINVAL, - m18n.n('backup_output_directory_required')) + m18n.n('backup_output_directory_required')) if output_directory: output_directory = os.path.abspath(output_directory) @@ -91,19 +91,17 @@ def backup_create(name=None, description=None, output_directory=None, if output_directory.startswith(archives_path) or \ re.match(r'^/(|(bin|boot|dev|etc|lib|root|run|sbin|sys|usr|var)(|/.*))$', output_directory): - logger.error("forbidden output directory '%'", output_directory) raise MoulinetteError(errno.EINVAL, - m18n.n('backup_output_directory_forbidden')) + m18n.n('backup_output_directory_forbidden')) # Create the output directory if not os.path.isdir(output_directory): - logger.info("creating output directory '%s'", output_directory) + logger.debug("creating output directory '%s'", output_directory) os.makedirs(output_directory, 0750) # Check that output directory is empty elif no_compress and os.listdir(output_directory): - logger.error("not empty output directory '%'", output_directory) raise MoulinetteError(errno.EIO, - m18n.n('backup_output_directory_not_empty')) + m18n.n('backup_output_directory_not_empty')) # Define temporary directory if no_compress: @@ -115,8 +113,8 @@ def backup_create(name=None, description=None, output_directory=None, if not tmp_dir: tmp_dir = "%s/tmp/%s" % (backup_path, name) if os.path.isdir(tmp_dir): - logger.warning("temporary directory for backup '%s' already exists", - tmp_dir) + logger.debug("temporary directory for backup '%s' already exists", + tmp_dir) filesystem.rm(tmp_dir, recursive=True) filesystem.mkdir(tmp_dir, 0750, parents=True, uid='admin') @@ -125,7 +123,7 @@ def backup_create(name=None, description=None, output_directory=None, if not ret['failed']: filesystem.rm(tmp_dir, True, True) else: - msignals.display(m18n.n('backup_cleaning_failed'), 'warning') + logger.warning(m18n.n('backup_cleaning_failed')) # Initialize backup info info = { @@ -144,14 +142,12 @@ def backup_create(name=None, description=None, output_directory=None, try: hook_info('backup', hook) except: - logger.exception("backup hook '%s' not found", hook) - msignals.display(m18n.n('backup_hook_unknown', hook=hook), - 'error') + logger.error(m18n.n('backup_hook_unknown', hook=hook)) else: hooks_filtered.add(hook) if not hooks or hooks_filtered: - msignals.display(m18n.n('backup_running_hooks')) + logger.info(m18n.n('backup_running_hooks')) ret = hook_callback('backup', hooks_filtered, args=[tmp_dir]) if ret['succeed']: info['hooks'] = ret['succeed'] @@ -163,10 +159,8 @@ def backup_create(name=None, description=None, output_directory=None, try: i = hook_info('restore', h) except: - logger.exception("no restoration hook for '%s'", h) - msignals.display(m18n.n('restore_hook_unavailable', - hook=h), - 'warning') + logger.warning(m18n.n('restore_hook_unavailable', + hook=h), exc_info=1) else: for f in i['hooks']: shutil.copy(f['path'], tmp_hooks_dir) @@ -181,8 +175,7 @@ def backup_create(name=None, description=None, output_directory=None, if apps: for a in apps: if a not in apps_list: - logger.warning("app '%s' not found", a) - msignals.display(m18n.n('unbackup_app', a), 'warning') + logger.warning(m18n.n('unbackup_app', app=a)) else: apps_filtered.add(a) else: @@ -197,19 +190,14 @@ def backup_create(name=None, description=None, output_directory=None, app_script = app_setting_path + '/scripts/backup' app_restore_script = app_setting_path + '/scripts/restore' if not os.path.isfile(app_script): - logger.warning("backup script '%s' not found", app_script) - msignals.display(m18n.n('unbackup_app', app=app_id), - 'warning') + logger.warning(m18n.n('unbackup_app', app=app_id)) continue elif not os.path.isfile(app_restore_script): - logger.warning("restore script '%s' not found", - app_restore_script) - msignals.display(m18n.n('unrestore_app', app=app_id), - 'warning') + logger.warning(m18n.n('unrestore_app', app=app_id)) tmp_app_dir = '{:s}/apps/{:s}'.format(tmp_dir, app_id) tmp_app_bkp_dir = tmp_app_dir + '/backup' - msignals.display(m18n.n('backup_running_app_script', app_id)) + logger.info(m18n.n('backup_running_app_script', app=app_id)) try: # Prepare backup directory for the app filesystem.mkdir(tmp_app_bkp_dir, 0750, True, uid='admin') @@ -220,9 +208,7 @@ def backup_create(name=None, description=None, output_directory=None, hook_exec(tmp_script, args=[tmp_app_bkp_dir, app_id], raise_on_error=True) except: - logger.exception("error while executing backup of '%s'", app_id) - msignals.display(m18n.n('backup_app_failed', app=app_id), - 'error') + logger.exception(m18n.n('backup_app_failed', app=app_id)) # Cleaning app backup directory shutil.rmtree(tmp_app_dir, ignore_errors=True) else: @@ -247,7 +233,7 @@ def backup_create(name=None, description=None, output_directory=None, # Create the archive if not no_compress: - msignals.display(m18n.n('backup_creating_archive')) + logger.info(m18n.n('backup_creating_archive')) archive_file = "%s/%s.tar.gz" % (output_directory, name) try: tar = tarfile.open(archive_file, "w:gz") @@ -260,17 +246,16 @@ def backup_create(name=None, description=None, output_directory=None, try: tar = tarfile.open(archive_file, "w:gz") except: - logger.exception("unable to open '%s' for writing " - "after creating directory '%s'", - archive_file, archives_path) + logger.debug("unable to open '%s' for writing", + archive_file, exc_info=1) tar = None else: - logger.exception("unable to open the archive '%s' for writing", - archive_file) + logger.debug("unable to open '%s' for writing", + archive_file, exc_info=1) if tar is None: _clean_tmp_dir(2) raise MoulinetteError(errno.EIO, - m18n.n('backup_archive_open_failed')) + m18n.n('backup_archive_open_failed')) tar.add(tmp_dir, arcname='') tar.close() @@ -282,7 +267,7 @@ def backup_create(name=None, description=None, output_directory=None, if tmp_dir != output_directory: _clean_tmp_dir() - msignals.display(m18n.n('backup_complete'), 'success') + logger.success(m18n.n('backup_complete')) # Return backup info info['name'] = name @@ -307,7 +292,7 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals # Validate what to restore if ignore_hooks and ignore_apps: raise MoulinetteError(errno.EINVAL, - m18n.n('restore_action_required')) + m18n.n('restore_action_required')) # Retrieve and open the archive info = backup_info(name) @@ -315,15 +300,15 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals try: tar = tarfile.open(archive_file, "r:gz") except: - logger.exception("unable to open the archive '%s' for reading", - archive_file) + logger.debug("cannot open backup archive '%s'", + archive_file, exc_info=1) raise MoulinetteError(errno.EIO, m18n.n('backup_archive_open_failed')) # Check temporary directory tmp_dir = "%s/tmp/%s" % (backup_path, name) if os.path.isdir(tmp_dir): - logger.warning("temporary directory for restoration '%s' already exists", - tmp_dir) + logger.debug("temporary directory for restoration '%s' already exists", + tmp_dir) os.system('rm -rf %s' % tmp_dir) def _clean_tmp_dir(retcode=0): @@ -331,24 +316,24 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals if not ret['failed']: filesystem.rm(tmp_dir, True, True) else: - msignals.display(m18n.n('restore_cleaning_failed'), 'warning') + logger.warning(m18n.n('restore_cleaning_failed')) # Extract the tarball - msignals.display(m18n.n('backup_extracting_archive')) + logger.info(m18n.n('backup_extracting_archive')) tar.extractall(tmp_dir) tar.close() # Retrieve backup info + info_file = "%s/info.json" % tmp_dir try: - with open("%s/info.json" % tmp_dir, 'r') as f: + with open(info_file, 'r') as f: info = json.load(f) except IOError: - logger.error("unable to retrieve backup info from '%s/info.json'", - tmp_dir) + logger.debug("unable to load '%s'", info_file, exc_info=1) raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive')) else: - logger.info("restoring from backup '%s' created on %s", name, - time.ctime(info['created_at'])) + logger.debug("restoring from backup '%s' created on %s", name, + time.ctime(info['created_at'])) # Initialize restauration summary result result = { @@ -358,7 +343,7 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals # Check if YunoHost is installed if os.path.isfile('/etc/yunohost/installed'): - msignals.display(m18n.n('yunohost_already_installed'), 'warning') + logger.warning(m18n.n('yunohost_already_installed')) if not force: try: # Ask confirmation for restoring @@ -379,11 +364,11 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals with open("%s/yunohost/current_host" % tmp_dir, 'r') as f: domain = f.readline().rstrip() except IOError: - logger.error("unable to retrieve domain from '%s/yunohost/current_host'", - tmp_dir) + logger.debug("unable to retrieve domain from " + "'%s/yunohost/current_host'", tmp_dir, exc_info=1) raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive')) - logger.info("executing the post-install...") + logger.debug("executing the post-install...") tools_postinstall(domain, 'yunohost', True) # Run system hooks @@ -395,10 +380,7 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals def _is_hook_in_backup(h): if h in hooks_list: return True - logger.warning("hook '%s' not executed in the backup '%s'", - h, archive_file) - msignals.display(m18n.n('backup_archive_hook_not_exec', hook=h), - 'error') + logger.error(m18n.n('backup_archive_hook_not_exec', hook=h)) return False else: hooks = hooks_list @@ -413,22 +395,20 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals except: tmp_hooks = glob('{:s}/hooks/restore/*-{:s}'.format(tmp_dir, h)) if not tmp_hooks: - logger.exception("restoration hook '%s' not found", h) - msignals.display(m18n.n('restore_hook_unavailable', hook=h), - 'error') + logger.exception(m18n.n('restore_hook_unavailable', hook=h)) continue # Add restoration hook from the backup to the system # FIXME: Refactor hook_add and use it instead restore_hook_folder = custom_hook_folder + 'restore' filesystem.mkdir(restore_hook_folder, 755, True) for f in tmp_hooks: - logger.info("adding restoration hook '%s' to the system " \ - "from the backup archive '%s'", f, archive_file) + logger.debug("adding restoration hook '%s' to the system " + "from the backup archive '%s'", f, archive_file) shutil.copy(f, restore_hook_folder) hooks_filtered.add(h) if hooks_filtered: - msignals.display(m18n.n('restore_running_hooks')) + logger.info(m18n.n('restore_running_hooks')) ret = hook_callback('restore', hooks_filtered, args=[tmp_dir]) result['hooks'] = ret['succeed'] @@ -442,11 +422,7 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals if apps: for a in apps: if a not in apps_list: - logger.warning("app '%s' not found in the backup '%s'", - a, archive_file) - msignals.display(m18n.n('backup_archive_app_not_found', - app=a), - 'error') + logger.error(m18n.n('backup_archive_app_not_found', app=a)) else: apps_filtered.add(a) else: @@ -457,23 +433,19 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals # Check if the app is not already installed if _is_installed(app_id): - logger.warning("app '%s' already installed", app_id) - msignals.display(m18n.n('restore_already_installed_app', - app=app_id), - 'error') + logger.error(m18n.n('restore_already_installed_app', + app=app_id)) continue # Check if the app has a restore script app_script = tmp_app_dir + '/settings/scripts/restore' if not os.path.isfile(app_script): - logger.warning("restore script for the app '%s' not found " \ - "in the backup '%s'", app_id, archive_file) - msignals.display(m18n.n('unrestore_app', app=app_id), 'warning') + logger.warning(m18n.n('unrestore_app', app=app_id)) continue tmp_script = '/tmp/restore_' + app_id app_setting_path = '/etc/yunohost/apps/' + app_id - msignals.display(m18n.n('restore_running_app_script', app=app_id)) + logger.info(m18n.n('restore_running_app_script', app=app_id)) try: # Copy app settings and set permissions shutil.copytree(tmp_app_dir + '/settings', app_setting_path) @@ -485,9 +457,7 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals hook_exec(tmp_script, args=[tmp_app_dir + '/backup', app_id], raise_on_error=True) except: - logger.exception("error while restoring backup of '%s'", app_id) - msignals.display(m18n.n('restore_app_failed', app=app_id), - 'error') + logger.exception(m18n.n('restore_app_failed', app=app_id)) # Cleaning app directory shutil.rmtree(app_setting_path, ignore_errors=True) else: @@ -501,7 +471,7 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals raise MoulinetteError(errno.EINVAL, m18n.n('restore_nothings_done')) _clean_tmp_dir() - msignals.display(m18n.n('restore_complete'), 'success') + logger.success(m18n.n('restore_complete')) return result @@ -520,8 +490,8 @@ def backup_list(with_info=False, human_readable=False): try: # Retrieve local archives archives = os.listdir(archives_path) - except OSError as e: - logger.info("unable to iterate over local archives: %s", str(e)) + except OSError: + logger.debug("unable to iterate over local archives", exc_info=1) else: # Iterate over local archives for f in archives: @@ -555,8 +525,8 @@ def backup_info(name, with_details=False, human_readable=False): archive_file = '%s/%s.tar.gz' % (archives_path, name) if not os.path.isfile(archive_file): - logger.error("no local backup archive found at '%s'", archive_file) - raise MoulinetteError(errno.EIO, m18n.n('backup_archive_name_unknown',name)) + raise MoulinetteError(errno.EIO, + m18n.n('backup_archive_name_unknown', name=name)) info_file = "%s/%s.info.json" % (archives_path, name) try: @@ -565,8 +535,7 @@ def backup_info(name, with_details=False, human_readable=False): info = json.load(f) except: # TODO: Attempt to extract backup info file from tarball - logger.exception("unable to retrive backup info file '%s'", - info_file) + logger.debug("unable to load '%s'", info_file, exc_info=1) raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive')) size = os.path.getsize(archive_file) @@ -603,15 +572,15 @@ def backup_delete(name): info_file = "%s/%s.info.json" % (archives_path, name) for backup_file in [archive_file,info_file]: if not os.path.isfile(backup_file): - logger.error("no local backup archive found at '%s'", backup_file) - raise MoulinetteError(errno.EIO, m18n.n('backup_archive_name_unknown', backup_file)) + raise MoulinetteError(errno.EIO, + m18n.n('backup_archive_name_unknown', name=backup_file)) try: os.remove(backup_file) except: - logger.exception("unable to delete '%s'", backup_file) + logger.debug("unable to delete '%s'", backup_file, exc_info=1) raise MoulinetteError(errno.EIO, - m18n.n('backup_delete_error',backup_file)) + m18n.n('backup_delete_error', path=backup_file)) hook_callback('post_backup_delete', args=[name]) - msignals.display(m18n.n('backup_deleted'), 'success') + logger.success(m18n.n('backup_deleted')) From ea05562c796a36fa8f93b5d463233d4b72c5b51e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Tue, 19 Jan 2016 18:50:00 +0100 Subject: [PATCH 142/170] [enh] Add a ynh_app_setting_delete helper --- data/apps/helpers.d/setting | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/data/apps/helpers.d/setting b/data/apps/helpers.d/setting index 14d47c133..70a81c84e 100644 --- a/data/apps/helpers.d/setting +++ b/data/apps/helpers.d/setting @@ -16,3 +16,12 @@ ynh_app_setting_get() { ynh_app_setting_set() { sudo yunohost app setting "$1" "$2" -v "$3" } + +# Delete an application setting +# +# usage: ynh_app_setting_delete app key +# | arg: app - the application id +# | arg: key - the setting to delete +ynh_app_setting_delete() { + sudo yunohost app setting -d "$1" "$2" +} From e16b5606264eedd94197acf1d6d72a3b50f6a62f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Tue, 19 Jan 2016 18:52:29 +0100 Subject: [PATCH 143/170] [fix] Correct condition syntax in metronome conf_regen hook --- data/hooks/conf_regen/12-metronome | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/data/hooks/conf_regen/12-metronome b/data/hooks/conf_regen/12-metronome index 69a13a1dc..82610cee8 100644 --- a/data/hooks/conf_regen/12-metronome +++ b/data/hooks/conf_regen/12-metronome @@ -50,9 +50,8 @@ for file in /etc/metronome/conf.d/*; do | sed 's|.cfg.lua||') sanitzed_domain="$(echo $domain | sed 's/\./%2e/g')" [[ $domain_list =~ $domain ]] \ - || ($(sudo yunohost service saferemove -s metronome $file | tail -n1) == "True" \ + || ([[ $(sudo yunohost service saferemove -s metronome $file | tail -n1) == "True" ]] \ && rm -rf /var/lib/metronome/$sanitzed_domain) - done # Create domain directory From dbe58e962514183ff254e740bce304b581bbd3e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Tue, 19 Jan 2016 22:55:57 +0100 Subject: [PATCH 144/170] [enh] Update rmilter hook and dependencies for 1.7 release --- data/hooks/conf_regen/28-rmilter | 4 ---- debian/control | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/data/hooks/conf_regen/28-rmilter b/data/hooks/conf_regen/28-rmilter index 286941f34..0b6b93a38 100644 --- a/data/hooks/conf_regen/28-rmilter +++ b/data/hooks/conf_regen/28-rmilter @@ -21,10 +21,6 @@ safe_copy rmilter.conf /etc/rmilter.conf # Override socket configuration safe_copy rmilter.socket /etc/systemd/system/rmilter.socket -# Create the PID directory -sudo mkdir -p /run/rmilter -sudo chown -hR _rmilter: /run/rmilter - # Create DKIM key for each YunoHost domain sudo mkdir -p /etc/dkim domain_list=$(sudo yunohost domain list --output-as plain) diff --git a/debian/control b/debian/control index 6f5631193..2b84d863b 100644 --- a/debian/control +++ b/debian/control @@ -34,7 +34,7 @@ Depends: ${python:Depends}, ${misc:Depends}, nginx-extras (>=1.6.2), php5-fpm, php5-ldap, php5-intl, dnsmasq, openssl, avahi-daemon, ssowat, metronome, - rspamd, rmilter, memcached, opendkim-tools + rspamd, rmilter (>=1.7.0), redis-server, opendkim-tools Recommends: yunohost-admin, bash-completion, rsyslog, ntp, openssh-server, inetutils-ping | iputils-ping, From d6b0c357257e001e3ae18b07a2db4e3c462a7c32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Fri, 22 Jan 2016 18:34:02 +0100 Subject: [PATCH 145/170] [fix] Replace udisks-glue by udisks2 and only suggest it --- debian/control | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/debian/control b/debian/control index 2b84d863b..692878f1e 100644 --- a/debian/control +++ b/debian/control @@ -39,9 +39,9 @@ Recommends: yunohost-admin, bash-completion, rsyslog, ntp, openssh-server, inetutils-ping | iputils-ping, php5-gd, php5-curl, php-gettext, php5-mcrypt, - udisks-glue, unattended-upgrades, + unattended-upgrades, libdbd-ldap-perl, libnet-dns-perl -Suggests: htop, vim, rsync, acpi-support-base +Suggests: htop, vim, rsync, acpi-support-base, udisks2 Conflicts: iptables-persistent, moulinette-yunohost, yunohost-config, yunohost-config-others, yunohost-config-postfix, From e140ed652cddf2a66c39eb2ccbf97062ae8810d1 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Fri, 22 Jan 2016 21:28:35 +0000 Subject: [PATCH 146/170] [enh] new command to generate DNS configuration for a given domain name --- data/actionsmap/yunohost.yml | 11 +++++++++++ src/yunohost/domain.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 0374bce7b..2c9788a28 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -291,6 +291,17 @@ domain: extra: pattern: *pattern_domain + ### domain_generate_dns_config() + generate_dns_configuration: + action_help: Generate DNS configuration for a domain + api: GET /domains//dns_config + configuration: + lock: false + authenticate: all + arguments: + domain: + help: Target domain + ### domain_info() # info: # action_help: Get domain informations diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 53b938356..d1490810f 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -218,3 +218,36 @@ def domain_remove(auth, domain, force=False): hook_callback('post_domain_remove', args=[domain]) msignals.display(m18n.n('domain_deleted'), 'success') + + +def domain_generate_dns_configuration(auth, domain): + """ + Generate DNS configuration for a domain + + Keyword argument: + domain -- Domain name + """ + + ip4 = urlopen("http://ip.yunohost.org").read().strip() + + result = "@ 1400 IN A {ip4}\n* 900 IN A {ip4}\n".format(ip4=ip4) + + ip6 = None + + try: + ip6 = urlopen("http://ip6.yunohost.org").read().strip() + except Exception: + pass + else: + result += "@ 1400 IN A {ip6}\n* 900 IN AAAA {ip6}\n".format(ip6=ip6) + + result += "\n_xmpp-client._tcp 14400 IN SRV 0 5 5222 {domain}.\n_xmpp-server._tcp 14400 IN SRV 0 5 5269 {domain}.\n".format(domain=domain) + + result += "@ 1400 IN MX 10 {domain}.tld.\n".format(domain=domain) + + if ip6 is None: + result += '@ 1400 IN TXT "v=spf1 a mx ip4:{ip4} -all"\n'.format(ip4=ip4) + else: + result += '@ 1400 IN TXT "v=spf1 a mx ip4:{ip4} ip6:{ip6} -all"\n'.format(ip4=ip4, ip6=ip6) + + return result From 141d704a04004f68c17adf21204055178844951e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sat, 23 Jan 2016 19:15:13 +0100 Subject: [PATCH 147/170] [fix] Review LDAP backup and restore hooks The configuration is now saved using slapcat instead of trying to generate it from slapd.conf - which generally fail at restoration. Also, a backup of configuration and database is made before the restoration, which allows to return to a "working" state if it fails. --- data/hooks/backup/05-conf_ldap | 21 ++++++----- data/hooks/restore/05-conf_ldap | 65 ++++++++++++++++++++++----------- 2 files changed, 55 insertions(+), 31 deletions(-) diff --git a/data/hooks/backup/05-conf_ldap b/data/hooks/backup/05-conf_ldap index a0c7b8c09..84ae2fb65 100644 --- a/data/hooks/backup/05-conf_ldap +++ b/data/hooks/backup/05-conf_ldap @@ -1,15 +1,16 @@ -backup_dir="$1/conf/ldap" -sudo mkdir -p $backup_dir +#!/bin/bash + +backup_dir="${1}/conf/ldap" +sudo mkdir -p "$backup_dir" # Fix for first jessie yunohost where slapd.conf is called slapd-yuno.conf # without slapcat doesn't work -if [ ! -f /etc/ldap/slapd.conf ] -then - sudo mv /etc/ldap/slapd-yuno.conf /etc/ldap/slapd.conf -fi +[[ ! -f /etc/ldap/slapd.conf ]] \ + && sudo mv /etc/ldap/slapd-yuno.conf /etc/ldap/slapd.conf -sudo cp -a /etc/ldap/slapd.conf $backup_dir/ +# Back up the configuration +sudo cp -a /etc/ldap/slapd.conf "${backup_dir}/slapd.conf" +sudo slapcat -b cn=config -l "${backup_dir}/cn=config.master.ldif" -sudo slapcat -l $backup_dir/slapcat.ldif.raw -sudo bash -c "egrep -v '^entryCSN:' < $backup_dir/slapcat.ldif.raw > $backup_dir/slapcat.ldif" -sudo rm -f $backup_dir/slapcat.ldif.raw +# Back up the database +sudo slapcat -b dc=yunohost,dc=org -l "${backup_dir}/dc=yunohost-dc=org.ldif" diff --git a/data/hooks/restore/05-conf_ldap b/data/hooks/restore/05-conf_ldap index 22867e6cc..20551761a 100644 --- a/data/hooks/restore/05-conf_ldap +++ b/data/hooks/restore/05-conf_ldap @@ -1,36 +1,59 @@ -backup_dir="$1/conf/ldap" +#!/bin/bash -if [ -z "$2" ]; then +backup_dir="${1}/conf/ldap" + +if [[ $EUID -ne 0 ]]; then # We need to execute this script as root, since the ldap # service will be shut down during the operation (and sudo # won't be available) - sudo bash $(pwd)/$0 $1 sudoed + sudo /bin/bash $(readlink -f $0) $1 else - service slapd stop + service slapd stop || true - # Backup old configuration - mv /var/lib/ldap /var/lib/ldap.old + # Create a directory for backup + TMPDIR="/tmp/$(date +%s)" + mkdir -p "$TMPDIR" - # Recreate new DB folder - mkdir /var/lib/ldap - chown openldap: /var/lib/ldap - chmod go-rwx /var/lib/ldap + die() { + state=$1 + error=$2 - # Restore LDAP configuration (just to be sure) - cp -a $backup_dir/slapd.conf /etc/ldap/slapd.conf + # Restore saved configuration and database + [[ $state -ge 1 ]] \ + && (rm -rf /etc/ldap/slapd.d && + mv "${TMPDIR}/slapd.d" /etc/ldap/slapd.d) + [[ $state -ge 2 ]] \ + && (rm -rf /var/lib/ldap && + mv "${TMPDIR}/ldap" /var/lib/ldap) + chown -R openldap: /etc/ldap/slapd.d /var/lib/ldap - # Regenerate the configuration - rm -rf /etc/ldap/slapd.d/* - slaptest -u -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.d - cp -rfp /var/lib/ldap.old/DB_CONFIG /var/lib/ldap + service slapd start + rm -rf "$TMPDIR" - # Import the database - slapadd -l $backup_dir/slapcat.ldif + # Print an error message and exit + printf "%s" "$error" 1>&2 + exit 1 + } + + # Restore the configuration + mv /etc/ldap/slapd.d "$TMPDIR" + mkdir -p /etc/ldap/slapd.d + cp -a "${backup_dir}/slapd.conf" /etc/ldap/slapd.conf + slapadd -F /etc/ldap/slapd.d -b cn=config \ + -l "${backup_dir}/cn=config.master.ldif" \ + || die 1 "Unable to restore LDAP configuration" + chown -R openldap: /etc/ldap/slapd.d + + # Restore the database + mv /var/lib/ldap "$TMPDIR" + mkdir -p /var/lib/ldap + slapadd -F /etc/ldap/slapd.d -b dc=yunohost,dc=org \ + -l "${backup_dir}/dc=yunohost-dc=org.ldif" \ + || die 2 "Unable to restore LDAP database" + chown -R openldap: /var/lib/ldap - # Change permissions and restart slapd - chown openldap: /var/lib/ldap/* service slapd start - rm -rf /var/lib/ldap.old + rm -rf "$TMPDIR" fi From d5ec3c99c9706fe5cd801757e14772913815a499 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 24 Jan 2016 01:43:07 +0000 Subject: [PATCH 148/170] [mod] modify uri for dns config --- data/actionsmap/yunohost.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 2c9788a28..007bc4465 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -294,7 +294,7 @@ domain: ### domain_generate_dns_config() generate_dns_configuration: action_help: Generate DNS configuration for a domain - api: GET /domains//dns_config + api: GET /domains//dns configuration: lock: false authenticate: all From 4817be949104df48355dbd7d8387d23a26fa0b23 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 24 Jan 2016 01:43:45 +0000 Subject: [PATCH 149/170] [fix] bad IPV6 declaration --- 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 d1490810f..317841e33 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -239,7 +239,7 @@ def domain_generate_dns_configuration(auth, domain): except Exception: pass else: - result += "@ 1400 IN A {ip6}\n* 900 IN AAAA {ip6}\n".format(ip6=ip6) + result += "@ 1400 IN AAAA {ip6}\n* 900 IN AAAA {ip6}\n".format(ip6=ip6) result += "\n_xmpp-client._tcp 14400 IN SRV 0 5 5222 {domain}.\n_xmpp-server._tcp 14400 IN SRV 0 5 5269 {domain}.\n".format(domain=domain) From 34ef4c7092639c3b270e7e90667d9e666087fcbd Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 24 Jan 2016 01:44:01 +0000 Subject: [PATCH 150/170] [mod] uniformise TTL declarations --- src/yunohost/domain.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 317841e33..d8b4abddb 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -230,7 +230,7 @@ def domain_generate_dns_configuration(auth, domain): ip4 = urlopen("http://ip.yunohost.org").read().strip() - result = "@ 1400 IN A {ip4}\n* 900 IN A {ip4}\n".format(ip4=ip4) + result = "@ 1400 IN A {ip4}\n* 1400 IN A {ip4}\n".format(ip4=ip4) ip6 = None @@ -239,7 +239,7 @@ def domain_generate_dns_configuration(auth, domain): except Exception: pass else: - result += "@ 1400 IN AAAA {ip6}\n* 900 IN AAAA {ip6}\n".format(ip6=ip6) + result += "@ 1400 IN AAAA {ip6}\n* 1400 IN AAAA {ip6}\n".format(ip6=ip6) result += "\n_xmpp-client._tcp 14400 IN SRV 0 5 5222 {domain}.\n_xmpp-server._tcp 14400 IN SRV 0 5 5269 {domain}.\n".format(domain=domain) From b3665ede0ffa3ce47e9465b519f922e99d82c26e Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 24 Jan 2016 01:44:19 +0000 Subject: [PATCH 151/170] [mod] add CNAME for xmpp --- src/yunohost/domain.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index d8b4abddb..b3bbc5496 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -243,6 +243,8 @@ def domain_generate_dns_configuration(auth, domain): result += "\n_xmpp-client._tcp 14400 IN SRV 0 5 5222 {domain}.\n_xmpp-server._tcp 14400 IN SRV 0 5 5269 {domain}.\n".format(domain=domain) + result += "muc 1800 IN CNAME @\npubsub 1800 IN CNAME @\nvjud 1800 IN CNAME @\n\n" + result += "@ 1400 IN MX 10 {domain}.tld.\n".format(domain=domain) if ip6 is None: From d289bbe8e84c1638797f9796c50682eb1749d5b7 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 24 Jan 2016 11:44:39 +0000 Subject: [PATCH 152/170] [fix] remove .tld --- 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 b3bbc5496..e35e92e31 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -245,7 +245,7 @@ def domain_generate_dns_configuration(auth, domain): result += "muc 1800 IN CNAME @\npubsub 1800 IN CNAME @\nvjud 1800 IN CNAME @\n\n" - result += "@ 1400 IN MX 10 {domain}.tld.\n".format(domain=domain) + result += "@ 1400 IN MX 10 {domain}.\n".format(domain=domain) if ip6 is None: result += '@ 1400 IN TXT "v=spf1 a mx ip4:{ip4} -all"\n'.format(ip4=ip4) From 2b743115266eaf97a3ca33d877614eb8949eecde Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 24 Jan 2016 15:56:05 +0000 Subject: [PATCH 153/170] [mod] rename function like suggested --- data/actionsmap/yunohost.yml | 4 ++-- src/yunohost/domain.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 007bc4465..b64e94308 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -291,8 +291,8 @@ domain: extra: pattern: *pattern_domain - ### domain_generate_dns_config() - generate_dns_configuration: + ### dns_conf() + dns-conf: action_help: Generate DNS configuration for a domain api: GET /domains//dns configuration: diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index e35e92e31..1385058f9 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -220,7 +220,7 @@ def domain_remove(auth, domain, force=False): msignals.display(m18n.n('domain_deleted'), 'success') -def domain_generate_dns_configuration(auth, domain): +def domain_dns_conf(auth, domain): """ Generate DNS configuration for a domain From d93a1746d7c27ef05a116df0ec2000b59f31543e Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Tue, 26 Jan 2016 15:49:45 +0000 Subject: [PATCH 154/170] [mod] dns_conf only need authentication for the api --- data/actionsmap/yunohost.yml | 3 ++- src/yunohost/domain.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index b64e94308..c97fd9323 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -297,7 +297,8 @@ domain: api: GET /domains//dns configuration: lock: false - authenticate: all + authenticate: + - api arguments: domain: help: Target domain diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 1385058f9..535b94d40 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -220,7 +220,7 @@ def domain_remove(auth, domain, force=False): msignals.display(m18n.n('domain_deleted'), 'success') -def domain_dns_conf(auth, domain): +def domain_dns_conf(domain): """ Generate DNS configuration for a domain From 4d1e43a7b1a87c1e9cf7913949d5fb023aa7b351 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Tue, 26 Jan 2016 15:50:05 +0000 Subject: [PATCH 155/170] [mod] typo --- data/actionsmap/yunohost.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index c97fd9323..80a74251b 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -291,7 +291,7 @@ domain: extra: pattern: *pattern_domain - ### dns_conf() + ### domain_dns_conf() dns-conf: action_help: Generate DNS configuration for a domain api: GET /domains//dns From a5568311db492c24672b6ece57e5a69f12690261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Wed, 27 Jan 2016 18:05:04 +0100 Subject: [PATCH 156/170] [fix] Save LDAP database when switching to MDB (bugfix #169) As Wheezy is not supported anymore by next YunoHost releases, the slapd.conf now comes with MDB as backend. The LDAP database is also saved before switching to MDB backend and imported after the configuration re-generation to prevent data loss. --- data/hooks/conf_regen/06-slapd | 48 +++++++++++++++++-------- data/templates/slapd/slapd.conf | 62 +++++++++++++-------------------- 2 files changed, 57 insertions(+), 53 deletions(-) diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd index b5af23538..491a366a5 100644 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -24,29 +24,47 @@ cd /usr/share/yunohost/templates/slapd || sudo yunohost service saferemove -s slapd \ /etc/ldap/slapd-yuno.conf +# Retrieve current backend +backend=$(sudo slapcat -n 0 | sed -n 's/^dn: olcDatabase={1}\(.*\),cn=config$/\1/p') + +# Save current database in case of a backend change +BACKEND_CHANGE=0 +BACKUP_DIR="/var/backups/dc=yunohost,dc=org-${backend}-$(date +%s)" +if [[ "$backend" != "mdb" && "$force" == "True" ]]; then + BACKEND_CHANGE=1 + sudo mkdir -p "$BACKUP_DIR" + sudo slapcat -b dc=yunohost,dc=org \ + -l "${BACKUP_DIR}/dc=yunohost-dc=org.ldif" +fi + safe_copy sudo.schema /etc/ldap/schema/sudo.schema safe_copy mailserver.schema /etc/ldap/schema/mailserver.schema safe_copy ldap.conf /etc/ldap/ldap.conf safe_copy slapd.default /etc/default/slapd - -# Compatibility: change from HDB to MDB on Jessie -version=$(sed 's/\..*//' /etc/debian_version) -if [[ "$version" == '8' ]]; then - cat slapd.conf \ - | sed "s/hdb$/mdb/g" \ - | sed "s/back_hdb/back_mdb/g" \ - | sed "s/^dbconfig set_/#dbconfig set_/g" \ - | sudo tee slapd.conf -fi - safe_copy slapd.conf /etc/ldap/slapd.conf + +# Fix some permissions sudo chown root:openldap /etc/ldap/slapd.conf -sudo rm -Rf /etc/ldap/slapd.d -sudo mkdir /etc/ldap/slapd.d sudo chown -R openldap:openldap /etc/ldap/schema/ sudo chown -R openldap:openldap /etc/ldap/slapd.d/ -sudo slaptest -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.d/ 2>&1 -sudo chown -R openldap:openldap /etc/ldap/slapd.d/ +if [[ $BACKEND_CHANGE -eq 1 ]]; then + # Regenerate LDAP configuration and import database as root + # since the admin user may be unavailable + sudo sh -c "rm -Rf /etc/ldap/slapd.d; +mkdir /etc/ldap/slapd.d; +slaptest -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.d; +chown -R openldap:openldap /etc/ldap/slapd.d; +slapadd -F /etc/ldap/slapd.d -b dc=yunohost,dc=org \ + -l '${BACKUP_DIR}/dc=yunohost-dc=org.ldif'; +chown -R openldap:openldap /var/lib/ldap" 2>&1 +else + # Regenerate LDAP configuration from slapd.conf if it is valid + sudo slaptest -u -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.d/ \ + && (sudo rm -Rf /etc/ldap/slapd.d \ + && sudo mkdir /etc/ldap/slapd.d \ + && sudo slaptest -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.d/ 2>&1) + sudo chown -R openldap:openldap /etc/ldap/slapd.d/ +fi sudo service slapd force-reload diff --git a/data/templates/slapd/slapd.conf b/data/templates/slapd/slapd.conf index f47e6761b..6178ae00e 100644 --- a/data/templates/slapd/slapd.conf +++ b/data/templates/slapd/slapd.conf @@ -22,14 +22,15 @@ pidfile /var/run/slapd/slapd.pid # List of arguments that were passed to the server argsfile /var/run/slapd/slapd.args -password-hash {SSHA} - # Read slapd.conf(5) for possible values -loglevel 256 +loglevel none + +# Hashes to be used in generation of user passwords +password-hash {SSHA} # Where the dynamically loaded modules are stored modulepath /usr/lib/ldap -moduleload back_hdb +moduleload back_mdb moduleload memberof # The maximum number of entries that is returned for a search operation @@ -40,26 +41,25 @@ sizelimit 500 tool-threads 1 ####################################################################### -# Specific Backend Directives for hdb: +# Specific Backend Directives for mdb: # Backend specific directives apply to this backend until another # 'backend' directive occurs -backend hdb +backend mdb ####################################################################### -# Specific Backend Directives for 'other': -# Backend specific directives apply to this backend until another -# 'backend' directive occurs -#backend - -####################################################################### -# Specific Directives for database #1, of type hdb: +# Specific Directives for database #1, of type mdb: # Database specific directives apply to this databasse until another # 'database' directive occurs -database hdb +database mdb # The base of your directory in database #1 suffix "dc=yunohost,dc=org" +# rootdn directive for specifying a superuser on the database. This is needed +# for syncrepl. +# rootdn "cn=admin,dc=yunohost,dc=org" + +# Where the database file are physically stored for database #1 directory "/var/lib/ldap" # The dbconfig settings are used to generate a DB_CONFIG file the first @@ -67,10 +67,6 @@ directory "/var/lib/ldap" # file. You should therefore change these settings in DB_CONFIG directly # or remove DB_CONFIG and restart slapd for changes to take effect. -# For the Debian package we use 2MB as default but be sure to update this -# value if you have plenty of RAM -dbconfig set_cachesize 0 2097152 0 - # Sven Hartge reported that he had to set this value incredibly high # to get slapd running at all. See http://bugs.debian.org/303057 for more # information. @@ -83,9 +79,9 @@ dbconfig set_lk_max_locks 1500 dbconfig set_lk_max_lockers 1500 # Indexing options for database #1 -index objectClass eq -index uid eq,sub -index entryCSN,entryUUID eq +index objectClass eq +index uid eq,sub +index entryCSN,entryUUID eq # Save the time that the entry gets modified, for database #1 lastmod on @@ -94,26 +90,25 @@ lastmod on # failure and to speed slapd shutdown. checkpoint 512 30 -# Where to store the replica logs for database #1 -# replogfile /var/lib/ldap/replog - # The userPassword by default can be changed # by the entry owning it if they are authenticated. # Others should not be able to see it, except the # admin entry below # These access lines apply to database #1 only -access to attrs=userPassword +access to attrs=userPassword,shadowLastChange by dn="cn=admin,dc=yunohost,dc=org" write - by anonymous auth + by anonymous auth by self write by * none +# Personnal information can be changed by the entry +# owning it if they are authenticated. +# Others should be able to see it. access to attrs=cn,gecos,givenName,mail,maildrop,displayName,sn by dn="cn=admin,dc=yunohost,dc=org" write by self write by * read - # Ensure read access to the base for things like # supportedSASLMechanisms. Without this you may # have problems with SASL not knowing what @@ -129,14 +124,5 @@ access to dn.base="" by * read # can read everything. access to * by dn="cn=admin,dc=yunohost,dc=org" write - by group/groupOfNames/Member="cn=admin,ou=groups,dc=yunohost,dc=org" write - by * read - -####################################################################### -# Specific Directives for database #2, of type 'other' (can be hdb too): -# Database specific directives apply to this databasse until another -# 'database' directive occurs -#database - -# The base of your directory for database #2 -#suffix "dc=debian,dc=org" + by group/groupOfNames/Member="cn=admin,ou=groups,dc=yunohost,dc=org" write + by * read From e5095d0185846a7d1a89a519791fa93496fde7d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Wed, 27 Jan 2016 18:57:21 +0100 Subject: [PATCH 157/170] [fix] Remove old dbconfig settings from slapd.conf --- data/hooks/conf_regen/06-slapd | 2 +- data/templates/slapd/slapd.conf | 16 ---------------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd index 491a366a5..f9d751bce 100644 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -30,7 +30,7 @@ backend=$(sudo slapcat -n 0 | sed -n 's/^dn: olcDatabase={1}\(.*\),cn=config$/\1 # Save current database in case of a backend change BACKEND_CHANGE=0 BACKUP_DIR="/var/backups/dc=yunohost,dc=org-${backend}-$(date +%s)" -if [[ "$backend" != "mdb" && "$force" == "True" ]]; then +if [[ -n "$backend" && "$backend" != "mdb" && "$force" == "True" ]]; then BACKEND_CHANGE=1 sudo mkdir -p "$BACKUP_DIR" sudo slapcat -b dc=yunohost,dc=org \ diff --git a/data/templates/slapd/slapd.conf b/data/templates/slapd/slapd.conf index 6178ae00e..9a8800d9d 100644 --- a/data/templates/slapd/slapd.conf +++ b/data/templates/slapd/slapd.conf @@ -62,22 +62,6 @@ suffix "dc=yunohost,dc=org" # Where the database file are physically stored for database #1 directory "/var/lib/ldap" -# The dbconfig settings are used to generate a DB_CONFIG file the first -# time slapd starts. They do NOT override existing an existing DB_CONFIG -# file. You should therefore change these settings in DB_CONFIG directly -# or remove DB_CONFIG and restart slapd for changes to take effect. - -# Sven Hartge reported that he had to set this value incredibly high -# to get slapd running at all. See http://bugs.debian.org/303057 for more -# information. - -# Number of objects that can be locked at the same time. -dbconfig set_lk_max_objects 1500 -# Number of locks (both requested and granted) -dbconfig set_lk_max_locks 1500 -# Number of lockers -dbconfig set_lk_max_lockers 1500 - # Indexing options for database #1 index objectClass eq index uid eq,sub From 770de28f7530f92289763e4ef6f0ec1148659af9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Wed, 27 Jan 2016 19:45:56 +0100 Subject: [PATCH 158/170] [enh] Check the slapd config file at first in conf_regen --- data/hooks/conf_regen/06-slapd | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd index f9d751bce..d6a4eb28f 100644 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -48,8 +48,11 @@ sudo chown root:openldap /etc/ldap/slapd.conf sudo chown -R openldap:openldap /etc/ldap/schema/ sudo chown -R openldap:openldap /etc/ldap/slapd.d/ +# Check the slapd config file at first +sudo slaptest -u -f /etc/ldap/slapd.conf + if [[ $BACKEND_CHANGE -eq 1 ]]; then - # Regenerate LDAP configuration and import database as root + # Regenerate LDAP config directory and import database as root # since the admin user may be unavailable sudo sh -c "rm -Rf /etc/ldap/slapd.d; mkdir /etc/ldap/slapd.d; @@ -59,11 +62,10 @@ slapadd -F /etc/ldap/slapd.d -b dc=yunohost,dc=org \ -l '${BACKUP_DIR}/dc=yunohost-dc=org.ldif'; chown -R openldap:openldap /var/lib/ldap" 2>&1 else - # Regenerate LDAP configuration from slapd.conf if it is valid - sudo slaptest -u -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.d/ \ - && (sudo rm -Rf /etc/ldap/slapd.d \ - && sudo mkdir /etc/ldap/slapd.d \ - && sudo slaptest -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.d/ 2>&1) + # Regenerate LDAP config directory from slapd.conf + sudo rm -Rf /etc/ldap/slapd.d + sudo mkdir /etc/ldap/slapd.d + sudo slaptest -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.d/ 2>&1 sudo chown -R openldap:openldap /etc/ldap/slapd.d/ fi From 234549d879486ac470c2cdc8fa9d37220ed5001d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Wed, 27 Jan 2016 21:18:56 +0100 Subject: [PATCH 159/170] [enh] Set minimum uid and ignore local users in nslcd.conf --- data/templates/nslcd/nslcd.conf | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/data/templates/nslcd/nslcd.conf b/data/templates/nslcd/nslcd.conf index c927b5f39..091ecb7cc 100644 --- a/data/templates/nslcd/nslcd.conf +++ b/data/templates/nslcd/nslcd.conf @@ -15,24 +15,11 @@ base dc=yunohost,dc=org # The LDAP protocol version to use. #ldap_version 3 -# The DN to bind with for normal lookups. -#binddn cn=annonymous,dc=example,dc=net -#bindpw secret - -# The DN used for password modifications by root. -#rootpwmoddn cn=admin,dc=example,dc=com - -# SSL options -#ssl off -#tls_reqcert never -tls_cacertfile /etc/ssl/certs/ca-certificates.crt - # The search scope. #scope sub +# Build a full list of non-LDAP users on startup. +nss_initgroups_ignoreusers ALLLOCAL - - - - - +# The minimum numeric user id to lookup. +nss_min_uid 1000 From ed49a8f577850e3f72f746e16e5b47cda8491baa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Wed, 27 Jan 2016 21:43:54 +0100 Subject: [PATCH 160/170] [fix] Validate arguments and app settings in app_map (bugfix #168) --- src/yunohost/app.py | 44 +++++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 7bfacb94f..97cab2c1e 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -253,34 +253,44 @@ def app_map(app=None, raw=False, user=None): app -- Specific app to map """ - + apps = [] result = {} - for app_id in os.listdir(apps_setting_path): - if app and (app != app_id): - continue - - if user is not None: - app_dict = app_info(app=app_id, raw=True) - if ('mode' not in app_dict['settings']) or ('mode' in app_dict['settings'] and app_dict['settings']['mode'] == 'private'): - if 'allowed_users' in app_dict['settings'] and user not in app_dict['settings']['allowed_users'].split(','): - continue + if app is not None: + if not _is_installed(app): + raise MoulinetteError(errno.EINVAL, + m18n.n('app_not_installed', app)) + apps = [app,] + else: + apps = os.listdir(apps_setting_path) + for app_id in apps: with open(apps_setting_path + app_id +'/settings.yml') as f: app_settings = yaml.load(f) - + if not isinstance(app_settings, dict): + continue if 'domain' not in app_settings: continue + if user is not None: + if ('mode' not in app_settings \ + or ('mode' in app_settings \ + and app_settings['mode'] == 'private')) \ + and 'allowed_users' in app_settings \ + and user not in app_settings['allowed_users'].split(','): + continue + + domain = app_settings['domain'] + path = app_settings.get('path', '/') if raw: - if app_settings['domain'] not in result: - result[app_settings['domain']] = {} - result[app_settings['domain']][app_settings['path']] = { - 'label': app_settings['label'], - 'id': app_settings['id'] + if domain not in result: + result[domain] = {} + result[domain][path] = { + 'label': app_settings['label'], + 'id': app_settings['id'] } else: - result[app_settings['domain']+app_settings['path']] = app_settings['label'] + result[domain + path] = app_settings['label'] return result From da2b3691c76654455745e3c5d333f0dc09031ab8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Wed, 27 Jan 2016 22:31:04 +0100 Subject: [PATCH 161/170] [enh] Use a common function to retrieve app settings --- locales/en.json | 1 + src/yunohost/app.py | 53 +++++++++++++++++++++++++-------------------- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/locales/en.json b/locales/en.json index 8414a0ba9..7610b10e3 100644 --- a/locales/en.json +++ b/locales/en.json @@ -17,6 +17,7 @@ "app_unknown" : "Unknown app", "app_no_upgrade" : "No app to upgrade", "app_not_installed" : "{:s} is not installed", + "app_not_correctly_installed" : "{app:s} seems to be not correctly installed", "custom_app_url_required" : "You must provide an URL to upgrade your custom app {:s}", "app_recent_version_required" : "{:s} requires a more recent version of the moulinette", "app_upgraded" : "{:s} successfully upgraded", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 97cab2c1e..30ba491f8 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -218,8 +218,7 @@ def app_info(app, show_status=False, raw=False): m18n.n('app_not_installed', app)) if raw: ret = app_list(filter=app, raw=True)[app] - with open(apps_setting_path + app +'/settings.yml') as f: - ret['settings'] = yaml.load(f) + ret['settings'] = _get_app_settings(app) return ret app_setting_path = apps_setting_path + app @@ -265,9 +264,8 @@ def app_map(app=None, raw=False, user=None): apps = os.listdir(apps_setting_path) for app_id in apps: - with open(apps_setting_path + app_id +'/settings.yml') as f: - app_settings = yaml.load(f) - if not isinstance(app_settings, dict): + app_settings = _get_app_settings(app_id) + if not app_settings: continue if 'domain' not in app_settings: continue @@ -734,12 +732,7 @@ def app_makedefault(auth, app, domain=None): """ from yunohost.domain import domain_list - if not _is_installed(app): - raise MoulinetteError(errno.EINVAL, m18n.n('app_not_installed', app)) - - with open(apps_setting_path + app +'/settings.yml') as f: - app_settings = yaml.load(f) - + app_settings = _get_app_settings(app) app_domain = app_settings['domain'] app_path = app_settings['path'] @@ -782,17 +775,7 @@ def app_setting(app, key, value=None, delete=False): delete -- Delete the key """ - if not _is_installed(app): - raise MoulinetteError(errno.EINVAL, - m18n.n('app_not_installed', app)) - - settings_file = apps_setting_path + app +'/settings.yml' - try: - with open(settings_file) as f: - app_settings = yaml.load(f) - except IOError: - # Do not fail if setting file is not there - app_settings = {} + app_settings = _get_app_settings(app) if value is None and not delete: try: @@ -812,7 +795,8 @@ def app_setting(app, key, value=None, delete=False): value=yaml.load(value) app_settings[key] = value - with open(settings_file, 'w') as f: + with open(os.path.join( + apps_setting_path, app, 'settings.yml'), 'w') as f: yaml.safe_dump(app_settings, f, default_flow_style=False) @@ -1012,6 +996,29 @@ def app_ssowatconf(auth): msignals.display(m18n.n('ssowat_conf_generated'), 'success') +def _get_app_settings(app_id): + """ + Get settings of an installed app + + Keyword arguments: + app_id -- The app id + + """ + if not _is_installed(app_id): + raise MoulinetteError(errno.EINVAL, + m18n.n('app_not_installed', app_id)) + try: + with open(os.path.join( + apps_setting_path, app_id, 'settings.yml')) as f: + settings = yaml.load(f) + if app_id == settings['id']: + return settings + except (IOError, KeyError): + logger.exception(m18n.n('app_not_correctly_installed', + app=app_id)) + return {} + + def _get_app_status(app_id, format_date=False): """ Get app status or create it if needed From 6a64386127d2d80cec51b9da2420af4c01d1160a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Wed, 27 Jan 2016 22:45:29 +0100 Subject: [PATCH 162/170] [fix] Some improvements and fixes to actions related to app access --- src/yunohost/app.py | 96 ++++++++++++++++++++++----------------------- 1 file changed, 47 insertions(+), 49 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 30ba491f8..dd3810e1e 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -571,47 +571,50 @@ def app_addaccess(auth, apps, users=[]): from yunohost.user import user_list, user_info from yunohost.hook import hook_callback + result = {} + if not users: users = user_list(auth)['users'].keys() - - if not isinstance(users, list): users = [users] - if not isinstance(apps, list): apps = [apps] + elif not isinstance(users, list): + users = [users,] + if not isinstance(apps, list): + apps = [apps,] for app in apps: - if not _is_installed(app): - raise MoulinetteError(errno.EINVAL, - m18n.n('app_not_installed', app)) - - with open(apps_setting_path + app +'/settings.yml') as f: - app_settings = yaml.load(f) + app_settings = _get_app_settings(app) + if not app_settings: + continue if 'mode' not in app_settings: app_setting(app, 'mode', 'private') app_settings['mode'] = 'private' if app_settings['mode'] == 'private': + allowed_users = set() if 'allowed_users' in app_settings: - new_users = app_settings['allowed_users'] - else: - new_users = '' + allowed_users = set(app_settings['allowed_users'].split(',')) for allowed_user in users: - if allowed_user not in new_users.split(','): + if allowed_user not in allowed_users: try: user_info(auth, allowed_user) except MoulinetteError: + # FIXME: Add username keyword in user_unknown + logger.warning('{0}{1}'.format( + m18n.g('colon', m18n.n('user_unknown')), + allowed_user)) continue - if new_users == '': - new_users = allowed_user - else: - new_users = new_users +','+ allowed_user + allowed_users.add(allowed_user) - app_setting(app, 'allowed_users', new_users.strip()) + new_users = ','.join(allowed_users) + app_setting(app, 'allowed_users', new_users) hook_callback('post_app_addaccess', args=[app, new_users]) + result[app] = allowed_users + app_ssowatconf(auth) - return { 'allowed_users': new_users.split(',') } + return { 'allowed_users': result } def app_removeaccess(auth, apps, users=[]): @@ -626,45 +629,43 @@ def app_removeaccess(auth, apps, users=[]): from yunohost.user import user_list from yunohost.hook import hook_callback + result = {} + remove_all = False if not users: remove_all = True - if not isinstance(users, list): users = [users] - if not isinstance(apps, list): apps = [apps] + elif not isinstance(users, list): + users = [users,] + if not isinstance(apps, list): + apps = [apps,] + for app in apps: - new_users = '' + app_settings = _get_app_settings(app) + if not app_settings: + continue + allowed_users = set() - if not _is_installed(app): - raise MoulinetteError(errno.EINVAL, - m18n.n('app_not_installed', app)) - - with open(apps_setting_path + app +'/settings.yml') as f: - app_settings = yaml.load(f) - - if 'skipped_uris' not in app_settings or app_settings['skipped_uris'] != '/': + if app_settings.get('skipped_uris', '') != '/': if remove_all: - new_users = '' + pass elif 'allowed_users' in app_settings: for allowed_user in app_settings['allowed_users'].split(','): if allowed_user not in users: - if new_users == '': - new_users = allowed_user - else: - new_users = new_users +','+ allowed_user + allowed_users.add(allowed_user) else: - new_users = '' - for username in user_list(auth)['users'].keys(): - if username not in users: - if new_users == '': - new_users = username - new_users += ',' + username + for allowed_user in user_list(auth)['users'].keys(): + if allowed_user not in users: + allowed_users.add(allowed_user) - app_setting(app, 'allowed_users', new_users.strip()) + new_users = ','.join(allowed_users) + app_setting(app, 'allowed_users', new_users) hook_callback('post_app_removeaccess', args=[app, new_users]) + result[app] = allowed_users + app_ssowatconf(auth) - return { 'allowed_users': new_users.split(',') } + return { 'allowed_users': result } def app_clearaccess(auth, apps): @@ -680,12 +681,9 @@ def app_clearaccess(auth, apps): if not isinstance(apps, list): apps = [apps] for app in apps: - if not _is_installed(app): - raise MoulinetteError(errno.EINVAL, - m18n.n('app_not_installed', app)) - - with open(apps_setting_path + app +'/settings.yml') as f: - app_settings = yaml.load(f) + app_settings = _get_app_settings(app) + if not app_settings: + continue if 'mode' in app_settings: app_setting(app, 'mode', delete=True) From 82185b86d512d975a0c2cd12150855bf38af7a24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Wed, 27 Jan 2016 23:40:38 +0100 Subject: [PATCH 163/170] [fix] Remove udisks-glue from conf_regen and services udisks-glue has been removed from recommended packages by d6b0c357 since it's no longer maintained - and also out-dated; the conf_regen hook refers to an unavailable service. Note that even it's not really used in YunoHost yet, it has been replaced by udisks2 as suggested package. --- data/hooks/conf_regen/49-udisks-glue | 20 -------------------- data/templates/udisks-glue/udisks-glue.conf | 9 --------- data/templates/yunohost/services.yml | 2 +- 3 files changed, 1 insertion(+), 30 deletions(-) delete mode 100644 data/hooks/conf_regen/49-udisks-glue delete mode 100644 data/templates/udisks-glue/udisks-glue.conf diff --git a/data/hooks/conf_regen/49-udisks-glue b/data/hooks/conf_regen/49-udisks-glue deleted file mode 100644 index 03dbc75d4..000000000 --- a/data/hooks/conf_regen/49-udisks-glue +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -set -e - -force=$1 - -function safe_copy () { - if [[ "$force" == "True" ]]; then - sudo yunohost service safecopy \ - -s udisks-glue $1 $2 --force - else - sudo yunohost service safecopy \ - -s udisks-glue $1 $2 - fi -} - -cd /usr/share/yunohost/templates/udisks-glue - -if [[ "$(safe_copy udisks-glue.conf /etc/udisks-glue.conf | tail -n1)" == "True" ]]; then - sudo service udisks-glue restart -fi diff --git a/data/templates/udisks-glue/udisks-glue.conf b/data/templates/udisks-glue/udisks-glue.conf deleted file mode 100644 index f97de948f..000000000 --- a/data/templates/udisks-glue/udisks-glue.conf +++ /dev/null @@ -1,9 +0,0 @@ -filter disks { - optical = false - partition_table = false - usage = filesystem -} -match disks { - automount=true - automount_options= { sync, noatime, "dmask=0", "fmask=0" } -} diff --git a/data/templates/yunohost/services.yml b/data/templates/yunohost/services.yml index c015c8cdd..157a114ba 100644 --- a/data/templates/yunohost/services.yml +++ b/data/templates/yunohost/services.yml @@ -47,5 +47,5 @@ nsswitch: spamassassin: status: service log: /var/log/mail.log -udisks-glue: +udisks2: status: service From ab942600f6d087341e69dc8cb3a9fcbebc256c37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Wed, 27 Jan 2016 23:53:56 +0100 Subject: [PATCH 164/170] [fix] Remove old services and add rmilter/rspamd --- data/templates/yunohost/services.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/data/templates/yunohost/services.yml b/data/templates/yunohost/services.yml index 157a114ba..ed85738e7 100644 --- a/data/templates/yunohost/services.yml +++ b/data/templates/yunohost/services.yml @@ -13,6 +13,10 @@ dovecot: postfix: status: service log: [/var/log/mail.log,/var/log/mail.err] +rmilter: + status: systemctl status rmilter.socket +rspamd: + status: systemctl status rspamd.socket mysql: status: service log: [/var/log/mysql.log,/var/log/mysql.err] @@ -36,16 +40,10 @@ yunohost-api: postgrey: status: service log: /var/log/mail.log -amavis: - status: service - log: /var/log/mail.log nslcd: status: service log: /var/log/syslog nsswitch: status: service -spamassassin: - status: service - log: /var/log/mail.log udisks2: status: service From 94f81cc655d75b1246f2891fb726cd70291b6b2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Fri, 5 Feb 2016 11:38:34 +0100 Subject: [PATCH 165/170] [i18n] Use named variables in app category translations --- locales/en.json | 8 ++++---- src/yunohost/app.py | 21 ++++++++++++--------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/locales/en.json b/locales/en.json index 7610b10e3..46d2cf118 100644 --- a/locales/en.json +++ b/locales/en.json @@ -16,15 +16,15 @@ "appslist_removed" : "Apps list successfully removed", "app_unknown" : "Unknown app", "app_no_upgrade" : "No app to upgrade", - "app_not_installed" : "{:s} is not installed", + "app_not_installed" : "{app:s} is not installed", "app_not_correctly_installed" : "{app:s} seems to be not correctly installed", "custom_app_url_required" : "You must provide an URL to upgrade your custom app {:s}", - "app_recent_version_required" : "{:s} requires a more recent version of the moulinette", - "app_upgraded" : "{:s} successfully upgraded", + "app_recent_version_required" : "{app:s} requires a more recent version of YunoHost", + "app_upgraded" : "{app:s} successfully upgraded", "app_upgrade_failed" : "Unable to upgrade all apps", "app_id_invalid" : "Invalid app id", "app_already_installed" : "{:s} is already installed", - "app_removed" : "{:s} successfully removed", + "app_removed" : "{app:s} successfully removed", "app_location_already_used" : "An app is already installed on this location", "app_location_install_failed" : "Unable to install the app on this location", "app_extraction_failed" : "Unable to extract installation files", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index dd3810e1e..faa4a1615 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -215,7 +215,7 @@ def app_info(app, show_status=False, raw=False): """ if not _is_installed(app): raise MoulinetteError(errno.EINVAL, - m18n.n('app_not_installed', app)) + m18n.n('app_not_installed', app=app)) if raw: ret = app_list(filter=app, raw=True)[app] ret['settings'] = _get_app_settings(app) @@ -258,7 +258,7 @@ def app_map(app=None, raw=False, user=None): if app is not None: if not _is_installed(app): raise MoulinetteError(errno.EINVAL, - m18n.n('app_not_installed', app)) + m18n.n('app_not_installed', app=app)) apps = [app,] else: apps = os.listdir(apps_setting_path) @@ -323,7 +323,7 @@ def app_upgrade(auth, app=[], url=None, file=None): installed = _is_installed(app_id) if not installed: raise MoulinetteError(errno.ENOPKG, - m18n.n('app_not_installed', app_id)) + m18n.n('app_not_installed', app=app_id)) if app_id in upgraded_apps: continue @@ -351,7 +351,8 @@ def app_upgrade(auth, app=[], url=None, file=None): if 'min_version' in manifest \ and not has_min_version(manifest['min_version']): raise MoulinetteError(errno.EPERM, - m18n.n('app_recent_version_required', app_id)) + m18n.n('app_recent_version_required', + app=app_id)) app_setting_path = apps_setting_path +'/'+ app_id @@ -390,7 +391,7 @@ def app_upgrade(auth, app=[], url=None, file=None): # So much win upgraded_apps.append(app_id) - msignals.display(m18n.n('app_upgraded', app_id), 'success') + msignals.display(m18n.n('app_upgraded', app=app_id), 'success') if not upgraded_apps: raise MoulinetteError(errno.ENODATA, m18n.n('app_no_upgrade')) @@ -440,7 +441,8 @@ def app_install(auth, app, label=None, args=None): if 'min_version' in manifest \ and not has_min_version(manifest['min_version']): raise MoulinetteError(errno.EPERM, - m18n.n('app_recent_version_required', app_id)) + m18n.n('app_recent_version_required', + app=app_id)) # Check if app can be forked instance_number = _installed_instance_number(app_id, last=True) + 1 @@ -535,7 +537,8 @@ def app_remove(auth, app): from yunohost.hook import hook_exec, hook_remove if not _is_installed(app): - raise MoulinetteError(errno.EINVAL, m18n.n('app_not_installed', app)) + raise MoulinetteError(errno.EINVAL, + m18n.n('app_not_installed', app=app)) app_setting_path = apps_setting_path + app @@ -551,7 +554,7 @@ def app_remove(auth, app): args_list = [app] if hook_exec('/tmp/yunohost_remove/scripts/remove', args_list) == 0: - msignals.display(m18n.n('app_removed', app), 'success') + msignals.display(m18n.n('app_removed', app=app), 'success') if os.path.exists(app_setting_path): shutil.rmtree(app_setting_path) shutil.rmtree('/tmp/yunohost_remove') @@ -1004,7 +1007,7 @@ def _get_app_settings(app_id): """ if not _is_installed(app_id): raise MoulinetteError(errno.EINVAL, - m18n.n('app_not_installed', app_id)) + m18n.n('app_not_installed', app=app_id)) try: with open(os.path.join( apps_setting_path, app_id, 'settings.yml')) as f: From 86ee1a90276b9261af812dcc3b9a52afb399976c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Fri, 5 Feb 2016 13:28:10 +0100 Subject: [PATCH 166/170] [fix] Correct log file of yunohost-api in services.yml --- data/templates/yunohost/services.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/yunohost/services.yml b/data/templates/yunohost/services.yml index ed85738e7..1bdc2b702 100644 --- a/data/templates/yunohost/services.yml +++ b/data/templates/yunohost/services.yml @@ -36,7 +36,7 @@ php5-fpm: log: /var/log/php5-fpm.log yunohost-api: status: service - log: /var/log/yunohost.log + log: /var/log/yunohost/yunohost-api.log postgrey: status: service log: /var/log/mail.log From 7baa18e184f802e52c3718f7b36667ba9e6da18f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sun, 7 Feb 2016 18:56:32 +0100 Subject: [PATCH 167/170] Update changelog for 2.3.7 release --- debian/changelog | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/debian/changelog b/debian/changelog index 2badd9262..f0b594853 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,28 @@ +yunohost (2.3.7) testing; urgency=low + + [ Laurent Peuch ] + * [enh] new command to generate DNS configuration for a given domain name + + [ Jérôme Lebleu ] + * [fix] Save LDAP database when switching to MDB (bugfix #169) + * [fix] Review LDAP backup and restore hooks + * [enh] Replace msignals.display by logging in backup category + * [enh] Add a ynh_app_setting_delete helper + * [enh] Update rmilter hook and dependencies for 1.7 release + * [enh] Set minimum uid and ignore local users in nslcd.conf + * [enh] Use a common function to retrieve app settings + * [enh] Check the slapd config file at first in conf_regen + * [fix] Validate arguments and app settings in app_map (bugfix #168) + * [fix] Replace udisks-glue by udisks2 and only suggest it + * [fix] Correct condition syntax in metronome conf_regen hook + * [fix] Allow false and 0 as non-empty values for an app argument + * [fix] Some improvements and fixes to actions related to app access + * [fix] Remove old services and add rmilter/rspamd + * [fix] Correct log file of yunohost-api in services.yml + * [i18n] Use named variables in app category translations + + -- Jérôme Lebleu Sun, 07 Feb 2016 18:56:13 +0100 + yunohost (2.3.6) testing; urgency=low [ Jérôme Lebleu ] From c226c5b31170e9b938d143bb4896c2e817200778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sun, 7 Feb 2016 20:10:09 +0100 Subject: [PATCH 168/170] [fix] Add yunohost-firewall to services.yml --- data/templates/yunohost/services.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/templates/yunohost/services.yml b/data/templates/yunohost/services.yml index 1bdc2b702..f8dc324d3 100644 --- a/data/templates/yunohost/services.yml +++ b/data/templates/yunohost/services.yml @@ -37,6 +37,8 @@ php5-fpm: yunohost-api: status: service log: /var/log/yunohost/yunohost-api.log +yunohost-firewall: + status: service postgrey: status: service log: /var/log/mail.log From 50ba2668f541c25a3dc78091319f1d50fe3e2c82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Sun, 7 Feb 2016 20:29:12 +0100 Subject: [PATCH 169/170] [fix] Handle empty app settings error when it's not correctly installed --- src/yunohost/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index faa4a1615..6b40b3b55 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1014,7 +1014,7 @@ def _get_app_settings(app_id): settings = yaml.load(f) if app_id == settings['id']: return settings - except (IOError, KeyError): + except (IOError, TypeError, KeyError): logger.exception(m18n.n('app_not_correctly_installed', app=app_id)) return {} From 65721b41f37c5313e913c971d08a3c7227f06f2c Mon Sep 17 00:00:00 2001 From: opi Date: Mon, 8 Feb 2016 12:33:34 +0100 Subject: [PATCH 170/170] =?UTF-8?q?[fix]=C2=A0head=20opening=20tag=20may?= =?UTF-8?q?=20have=20attributes.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/templates/nginx/yunohost_panel.conf.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/nginx/yunohost_panel.conf.inc b/data/templates/nginx/yunohost_panel.conf.inc index 4d5b441d1..0ca8b02aa 100644 --- a/data/templates/nginx/yunohost_panel.conf.inc +++ b/data/templates/nginx/yunohost_panel.conf.inc @@ -1,2 +1,2 @@ -sub_filter ''; +sub_filter ''; sub_filter_once on;