diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..9642e92f6 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,22 @@ +## The problem + +... + +## Solution + +... + +## PR Status + +... + +## How to test + +... + +## Validation + +- [ ] Principle agreement 0/2 : +- [ ] Quick review 0/1 : +- [ ] Simple test 0/1 : +- [ ] Deep review 0/1 : diff --git a/data/helpers.d/string b/data/helpers.d/string index 80fae8cf7..43c4aab1a 100644 --- a/data/helpers.d/string +++ b/data/helpers.d/string @@ -24,19 +24,36 @@ ynh_replace_string () { local delimit=@ local match_string=$1 local replace_string=$2 - local workfile=$3 - - # Escape any backslash to preserve them as simple backslash. - match_string=${match_string//\\/"\\\\"} - replace_string=${replace_string//\\/"\\\\"} - - # Escape the & character, who has a special function in sed. - match_string=${match_string//&/"\&"} - replace_string=${replace_string//&/"\&"} - + local workfile=$ + # Escape the delimiter if it's in the string. match_string=${match_string//${delimit}/"\\${delimit}"} replace_string=${replace_string//${delimit}/"\\${delimit}"} sudo sed --in-place "s${delimit}${match_string}${delimit}${replace_string}${delimit}g" "$workfile" } + +# Substitute/replace a special string by another in a file +# +# usage: ynh_replace_special_string match_string replace_string target_file +# | arg: match_string - String to be searched and replaced in the file +# | arg: replace_string - String that will replace matches +# | arg: target_file - File in which the string will be replaced. +# +# This helper will use ynh_replace_string, but as you can use special +# characters, you can't use some regular expressions and sub-expressions. +ynh_replace_special_string () { + local match_string=$1 + local replace_string=$2 + local workfile=$3 + + # Escape any backslash to preserve them as simple backslash. + match_string=${match_string//\\/"\\\\"} + replace_string=${replace_string//\\/"\\\\"} + + # Escape the & character, who has a special function in sed. + match_string=${match_string//&/"\&"} + replace_string=${replace_string//&/"\&"} + + ynh_replace_string "$match_string" "$replace_string" "$workfile" +} diff --git a/data/templates/ssh/sshd_config b/data/templates/ssh/sshd_config index 695ea0d36..8c5a7fb95 100644 --- a/data/templates/ssh/sshd_config +++ b/data/templates/ssh/sshd_config @@ -66,6 +66,9 @@ PrintLastLog yes TCPKeepAlive yes #UseLogin no +# keep ssh sessions fresh +ClientAliveInterval 60 + #MaxStartups 10:30:60 Banner /etc/issue.net diff --git a/locales/en.json b/locales/en.json index 8dac6e799..0aa8798fd 100644 --- a/locales/en.json +++ b/locales/en.json @@ -16,24 +16,26 @@ "app_change_url_success": "Successfully changed {app:s} url to {domain:s}{path:s}", "app_extraction_failed": "Unable to extract installation files", "app_id_invalid": "Invalid app id", - "app_incompatible": "The app is incompatible with your YunoHost version", + "app_incompatible": "The app {app} is incompatible with your YunoHost version", "app_install_files_invalid": "Invalid installation files", - "app_location_already_used": "An app is already installed in this location", - "app_location_install_failed": "Unable to install the app in this location", + "app_location_already_used": "The app '{app}' is already installed on that location ({path})", + "app_make_default_location_already_used": "Can't make the app '{app}' the default on the domain {domain} is already used by the other app '{other_app}'", + "app_location_install_failed": "Unable to install the app in this location because it conflit with the app '{other_app}' already installed on '{other_path}'", "app_location_unavailable": "This url is not available or conflicts with an already installed app", "app_manifest_invalid": "Invalid app manifest: {error}", "app_no_upgrade": "No app to upgrade", "app_not_correctly_installed": "{app:s} seems to be incorrectly installed", "app_not_installed": "{app:s} is not installed", "app_not_properly_removed": "{app:s} has not been properly removed", - "app_package_need_update": "The app package needs to be updated to follow YunoHost changes", + "app_package_need_update": "The app {app} package needs to be updated to follow YunoHost changes", "app_removed": "{app:s} has been removed", - "app_requirements_checking": "Checking required packages...", - "app_requirements_failed": "Unable to meet requirements: {error}", - "app_requirements_unmeet": "Requirements are not met, the package {pkgname} ({version}) must be {spec}", + "app_requirements_checking": "Checking required packages for {app}...", + "app_requirements_failed": "Unable to meet requirements for {app}: {error}", + "app_requirements_unmeet": "Requirements are not met for {app}, the package {pkgname} ({version}) must be {spec}", "app_sources_fetch_failed": "Unable to fetch sources files", "app_unknown": "Unknown app", "app_unsupported_remote_type": "Unsupported remote type used for the app", + "app_upgrade_app_name": "Upgrading app {app}...", "app_upgrade_failed": "Unable to upgrade {app:s}", "app_upgrade_some_app_failed": "Unable to upgrade some applications", "app_upgraded": "{app:s} has been upgraded", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 403e76cc4..15855753c 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -330,6 +330,9 @@ def app_info(app, show_status=False, raw=False): if not _is_installed(app): raise MoulinetteError(errno.EINVAL, m18n.n('app_not_installed', app=app)) + + app_setting_path = APPS_SETTING_PATH + app + if raw: ret = app_list(filter=app, raw=True)[app] ret['settings'] = _get_app_settings(app) @@ -345,11 +348,10 @@ def app_info(app, show_status=False, raw=False): upgradable = "no" ret['upgradable'] = upgradable + ret['change_url'] = os.path.exists(os.path.join(app_setting_path, "scripts", "change_url")) return ret - app_setting_path = APPS_SETTING_PATH + app - # Retrieve manifest and status with open(app_setting_path + '/manifest.json') as f: manifest = json.loads(str(f.read())) @@ -555,6 +557,7 @@ def app_upgrade(auth, app=[], url=None, file=None): logger.info("Upgrading apps %s", ", ".join(app)) for app_instance_name in apps: + logger.warning(m18n.n('app_upgrade_app_name', app=app_instance_name)) installed = _is_installed(app_instance_name) if not installed: raise MoulinetteError(errno.ENOPKG, @@ -580,7 +583,7 @@ def app_upgrade(auth, app=[], url=None, file=None): continue # Check requirements - _check_manifest_requirements(manifest) + _check_manifest_requirements(manifest, app_instance_name=app_instance_name) app_setting_path = APPS_SETTING_PATH + '/' + app_instance_name @@ -621,10 +624,13 @@ def app_upgrade(auth, app=[], url=None, file=None): with open(app_setting_path + '/status.json', 'w+') as f: json.dump(status, f) - # Replace scripts and manifest - os.system('rm -rf "%s/scripts" "%s/manifest.json"' % (app_setting_path, app_setting_path)) + # Replace scripts and manifest and conf (if exists) + os.system('rm -rf "%s/scripts" "%s/manifest.json %s/conf"' % (app_setting_path, app_setting_path, app_setting_path)) os.system('mv "%s/manifest.json" "%s/scripts" %s' % (extracted_app_folder, extracted_app_folder, app_setting_path)) + if os.path.exists(os.path.join(extracted_app_folder, "conf")): + os.system('cp -R %s/conf %s' % (extracted_app_folder, app_setting_path)) + # So much win upgraded_apps.append(app_instance_name) logger.success(m18n.n('app_upgraded', app=app_instance_name)) @@ -683,7 +689,7 @@ def app_install(auth, app, label=None, args=None, no_remove_on_failure=False): app_id = manifest['id'] # Check requirements - _check_manifest_requirements(manifest) + _check_manifest_requirements(manifest, app_id) # Check if app can be forked instance_number = _installed_instance_number(app_id, last=True) + 1 @@ -733,6 +739,9 @@ def app_install(auth, app, label=None, args=None, no_remove_on_failure=False): os.system('cp %s/manifest.json %s' % (extracted_app_folder, app_setting_path)) os.system('cp -R %s/scripts %s' % (extracted_app_folder, app_setting_path)) + if os.path.exists(os.path.join(extracted_app_folder, "conf")): + os.system('cp -R %s/conf %s' % (extracted_app_folder, app_setting_path)) + # Execute the app install script install_retcode = 1 try: @@ -1016,7 +1025,9 @@ def app_makedefault(auth, app, domain=None): if '/' in app_map(raw=True)[domain]: raise MoulinetteError(errno.EEXIST, - m18n.n('app_location_already_used')) + m18n.n('app_make_default_location_already_used', + app=app, domain=app_domain, + other_app=app_map(raw=True)[domain]["/"]["id"])) try: with open('/etc/ssowat/conf.json.persistent') as json_conf: @@ -1169,10 +1180,13 @@ def app_checkurl(auth, url, app=None): continue if path == p: raise MoulinetteError(errno.EINVAL, - m18n.n('app_location_already_used')) + m18n.n('app_location_already_used', + app=a["id"], path=path)) + # can't install "/a/b/" if "/a/" exists elif path.startswith(p) or p.startswith(path): raise MoulinetteError(errno.EPERM, - m18n.n('app_location_install_failed')) + m18n.n('app_location_install_failed', + other_path=p, other_app=a['id'])) if app is not None and not installed: app_setting(app, 'domain', value=domain) @@ -1689,7 +1703,7 @@ def _encode_string(value): return value -def _check_manifest_requirements(manifest): +def _check_manifest_requirements(manifest, app_instance_name): """Check if required packages are met from the manifest""" requirements = manifest.get('requirements', dict()) @@ -1707,12 +1721,12 @@ def _check_manifest_requirements(manifest): if (not yunohost_req or not packages.SpecifierSet(yunohost_req) & '>= 2.3.6'): raise MoulinetteError(errno.EINVAL, '{0}{1}'.format( - m18n.g('colon', m18n.n('app_incompatible')), - m18n.n('app_package_need_update'))) + m18n.g('colon', m18n.n('app_incompatible'), app=app_instance_name), + m18n.n('app_package_need_update', app=app_instance_name))) elif not requirements: return - logger.info(m18n.n('app_requirements_checking')) + logger.info(m18n.n('app_requirements_checking', app=app_instance_name)) # Retrieve versions of each required package try: @@ -1721,7 +1735,7 @@ def _check_manifest_requirements(manifest): except packages.PackageException as e: raise MoulinetteError(errno.EINVAL, m18n.n('app_requirements_failed', - error=str(e))) + error=str(e), app=app_instance_name)) # Iterate over requirements for pkgname, spec in requirements.items(): @@ -1730,7 +1744,7 @@ def _check_manifest_requirements(manifest): raise MoulinetteError( errno.EINVAL, m18n.n('app_requirements_unmeet', pkgname=pkgname, version=version, - spec=spec)) + spec=spec, app=app_instance_name)) def _parse_args_from_manifest(manifest, action, args={}, auth=None): diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index f828b0973..727a63df3 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -188,17 +188,17 @@ def domain_dns_conf(domain, ttl=None): result = "" - result += "# Basic ipv4/ipv6 records" + result += "; Basic ipv4/ipv6 records" for record in dns_conf["basic"]: result += "\n{name} {ttl} IN {type} {value}".format(**record) result += "\n\n" - result += "# XMPP" + result += "; XMPP" for record in dns_conf["xmpp"]: result += "\n{name} {ttl} IN {type} {value}".format(**record) result += "\n\n" - result += "# Mail" + result += "; Mail" for record in dns_conf["mail"]: result += "\n{name} {ttl} IN {type} {value}".format(**record)