diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index ea8339e24..d12557b90 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1840,6 +1840,32 @@ tools: full: --force action: store_true + ### tools_regen_conf() + regen-conf: + action_help: Regenerate the configuration file(s) + api: PUT /tools/regenconf + arguments: + names: + help: Categories to regenerate configuration of (all by default) + nargs: "*" + metavar: NAME + -d: + full: --with-diff + help: Show differences in case of configuration changes + action: store_true + -f: + full: --force + help: Override all manual modifications in configuration files + action: store_true + -n: + full: --dry-run + help: Show what would have been regenerated + action: store_true + -p: + full: --list-pending + help: List pending configuration files and exit + action: store_true + subcategories: migrations: @@ -2000,6 +2026,10 @@ log: full: --limit help: Maximum number of logs type: int + -d: + full: --with-details + help: Show additional infos (e.g. operation success) but may significantly increase command time. Consider using --limit in combination with this. + action: store_true ### log_display() display: diff --git a/data/actionsmap/yunohost_completion.py b/data/actionsmap/yunohost_completion.py new file mode 100644 index 000000000..a4c17c4d6 --- /dev/null +++ b/data/actionsmap/yunohost_completion.py @@ -0,0 +1,86 @@ +""" +Simple automated generation of a bash_completion file +for yunohost command from the actionsmap. + +Generates a bash completion file assuming the structure +`yunohost domain action` +adds `--help` at the end if one presses [tab] again. + +author: Christophe Vuillot +""" +import os +import yaml + +THIS_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +ACTIONSMAP_FILE = THIS_SCRIPT_DIR + '/yunohost.yml' +BASH_COMPLETION_FILE = THIS_SCRIPT_DIR + '/../bash-completion.d/yunohost' + +with open(ACTIONSMAP_FILE, 'r') as stream: + + # Getting the dictionary containning what actions are possible per domain + OPTION_TREE = yaml.load(stream) + DOMAINS = [str for str in OPTION_TREE.keys() if not str.startswith('_')] + DOMAINS_STR = '"{}"'.format(' '.join(DOMAINS)) + ACTIONS_DICT = {} + for domain in DOMAINS: + ACTIONS = [str for str in OPTION_TREE[domain]['actions'].keys() + if not str.startswith('_')] + ACTIONS_STR = '"{}"'.format(' '.join(ACTIONS)) + ACTIONS_DICT[domain] = ACTIONS_STR + + with open(BASH_COMPLETION_FILE, 'w') as generated_file: + + # header of the file + generated_file.write('#\n') + generated_file.write('# completion for yunohost\n') + generated_file.write('# automatically generated from the actionsmap\n') + generated_file.write('#\n\n') + + # Start of the completion function + generated_file.write('_yunohost()\n') + generated_file.write('{\n') + + # Defining local variable for previously and currently typed words + generated_file.write('\tlocal cur prev opts narg\n') + generated_file.write('\tCOMPREPLY=()\n\n') + generated_file.write('\t# the number of words already typed\n') + generated_file.write('\tnarg=${#COMP_WORDS[@]}\n\n') + generated_file.write('\t# the current word being typed\n') + generated_file.write('\tcur="${COMP_WORDS[COMP_CWORD]}"\n\n') + generated_file.write('\t# the last typed word\n') + generated_file.write('\tprev="${COMP_WORDS[COMP_CWORD-1]}"\n\n') + + # If one is currently typing a domain then match with the domain list + generated_file.write('\t# If one is currently typing a domain,\n') + generated_file.write('\t# match with domains\n') + generated_file.write('\tif [[ $narg == 2 ]]; then\n') + generated_file.write('\t\topts={}\n'.format(DOMAINS_STR)) + generated_file.write('\tfi\n\n') + + # If one is currently typing an action then match with the action list + # of the previously typed domain + generated_file.write('\t# If one already typed a domain,\n') + generated_file.write('\t# match the actions of that domain\n') + generated_file.write('\tif [[ $narg == 3 ]]; then\n') + for domain in DOMAINS: + generated_file.write('\t\tif [[ $prev == "{}" ]]; then\n'.format(domain)) + generated_file.write('\t\t\topts={}\n'.format(ACTIONS_DICT[domain])) + generated_file.write('\t\tfi\n') + generated_file.write('\tfi\n\n') + + # If both domain and action have been typed or the domain + # was not recognized propose --help (only once) + generated_file.write('\t# If no options were found propose --help\n') + generated_file.write('\tif [ -z "$opts" ]; then\n') + generated_file.write('\t\tif [[ $prev != "--help" ]]; then\n') + generated_file.write('\t\t\topts=( --help )\n') + generated_file.write('\t\tfi\n') + generated_file.write('\tfi\n') + + # generate the completion list from the possible options + generated_file.write('\tCOMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )\n') + generated_file.write('\treturn 0\n') + generated_file.write('}\n\n') + + # Add the function to bash completion + generated_file.write('complete -F _yunohost yunohost') diff --git a/data/bash-completion.d/yunohost b/data/bash-completion.d/yunohost index 106f8fbdf..2572a391d 100644 --- a/data/bash-completion.d/yunohost +++ b/data/bash-completion.d/yunohost @@ -1,12 +1,3 @@ -# -# 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" +# This file is automatically generated +# during Debian's package build by the script +# data/actionsmap/yunohost_completion.py diff --git a/data/helpers.d/package b/data/helpers.d/apt similarity index 91% rename from data/helpers.d/package rename to data/helpers.d/apt index 000b0ee74..9d5ad3ac2 100644 --- a/data/helpers.d/package +++ b/data/helpers.d/apt @@ -5,6 +5,8 @@ # [internal] # # usage: ynh_wait_dpkg_free +# +# Requires YunoHost version 3.3.1 or higher. ynh_wait_dpkg_free() { local try # With seq 1 17, timeout will be almost 30 minutes @@ -44,6 +46,8 @@ ynh_wait_dpkg_free() { # # usage: ynh_package_is_installed --package=name # | arg: -p, --package - the package name to check +# +# Requires YunoHost version 2.2.4 or higher. ynh_package_is_installed() { # Declare an array to define the options of this helper. local legacy_args=p @@ -64,6 +68,8 @@ ynh_package_is_installed() { # usage: ynh_package_version --package=name # | arg: -p, --package - the package name to get version # | ret: the version or an empty string +# +# Requires YunoHost version 2.2.4 or higher. ynh_package_version() { # Declare an array to define the options of this helper. local legacy_args=p @@ -84,6 +90,8 @@ ynh_package_version() { # [internal] # # usage: ynh_apt update +# +# Requires YunoHost version 2.4.0.3 or higher. ynh_apt() { ynh_wait_dpkg_free DEBIAN_FRONTEND=noninteractive apt-get -y $@ @@ -92,6 +100,8 @@ ynh_apt() { # Update package index files # # usage: ynh_package_update +# +# Requires YunoHost version 2.2.4 or higher. ynh_package_update() { ynh_apt update } @@ -100,6 +110,8 @@ ynh_package_update() { # # usage: ynh_package_install name [name [...]] # | arg: name - the package name to install +# +# Requires YunoHost version 2.2.4 or higher. ynh_package_install() { ynh_apt --no-remove -o Dpkg::Options::=--force-confdef \ -o Dpkg::Options::=--force-confold install $@ @@ -109,6 +121,8 @@ ynh_package_install() { # # usage: ynh_package_remove name [name [...]] # | arg: name - the package name to remove +# +# Requires YunoHost version 2.2.4 or higher. ynh_package_remove() { ynh_apt remove $@ } @@ -117,6 +131,8 @@ ynh_package_remove() { # # usage: ynh_package_autoremove name [name [...]] # | arg: name - the package name to remove +# +# Requires YunoHost version 2.2.4 or higher. ynh_package_autoremove() { ynh_apt autoremove $@ } @@ -125,6 +141,8 @@ ynh_package_autoremove() { # # usage: ynh_package_autopurge name [name [...]] # | arg: name - the package name to autoremove and purge +# +# Requires YunoHost version 2.7.2 or higher. ynh_package_autopurge() { ynh_apt autoremove --purge $@ } @@ -139,6 +157,8 @@ ynh_package_autopurge() { # # usage: ynh_package_install_from_equivs controlfile # | arg: controlfile - path of the equivs control file +# +# Requires YunoHost version 2.2.4 or higher. ynh_package_install_from_equivs () { local controlfile=$1 @@ -146,7 +166,7 @@ ynh_package_install_from_equivs () { local pkgname=$(grep '^Package: ' $controlfile | cut -d' ' -f 2) # Retrieve the name of the debian package local pkgversion=$(grep '^Version: ' $controlfile | cut -d' ' -f 2) # And its version number [[ -z "$pkgname" || -z "$pkgversion" ]] \ - && echo "Invalid control file" && exit 1 # Check if this 2 variables aren't empty. + && ynh_die --message="Invalid control file" # Check if this 2 variables aren't empty. # Update packages cache ynh_package_update @@ -181,6 +201,8 @@ ynh_package_install_from_equivs () { # You can give a choice between some package with this syntax : "dep1|dep2" # Example : ynh_install_app_dependencies dep1 dep2 "dep3|dep4|dep5" # This mean in the dependence tree : dep1 & dep2 & (dep3 | dep4 | dep5) +# +# Requires YunoHost version 2.6.4 or higher. ynh_install_app_dependencies () { local dependencies=$@ local dependencies=${dependencies// /, } @@ -217,6 +239,8 @@ EOF # Dependencies will removed only if no other package need them. # # usage: ynh_remove_app_dependencies +# +# Requires YunoHost version 2.6.4 or higher. ynh_remove_app_dependencies () { local dep_app=${app//_/-} # Replace all '_' by '-' ynh_package_autopurge ${dep_app}-ynh-deps # Remove the fake package and its dependencies if they not still used. diff --git a/data/helpers.d/backend b/data/helpers.d/backend deleted file mode 100644 index 1532601a8..000000000 --- a/data/helpers.d/backend +++ /dev/null @@ -1,437 +0,0 @@ -#!/bin/bash - -# Use logrotate to manage the logfile -# -# usage: ynh_use_logrotate [--logfile=/log/file] [--nonappend] [--specific_user=user/group] -# | arg: -l, --logfile= - absolute path of logfile -# | arg: -n, --nonappend - (Option) Replace the config file instead of appending this new config. -# | arg: -u, --specific_user : run logrotate as the specified user and group. If not specified logrotate is runned as root. -# -# If no argument provided, a standard directory will be use. /var/log/${app} -# You can provide a path with the directory only or with the logfile. -# /parentdir/logdir -# /parentdir/logdir/logfile.log -# -# It's possible to use this helper several times, each config will be added to the same logrotate config file. -# Unless you use the option --non-append -ynh_use_logrotate () { - # Declare an array to define the options of this helper. - local legacy_args=lnuya - declare -Ar args_array=( [l]=logfile= [n]=nonappend [u]=specific_user= [y]=non [a]=append ) - # [y]=non [a]=append are only for legacy purpose, to not fail on the old option '--non-append' - local logfile - local nonappend - local specific_user - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - local logfile="${logfile:-}" - local nonappend="${nonappend:-0}" - local specific_user="${specific_user:-}" - - # LEGACY CODE - PRE GETOPTS - if [ $# -gt 0 ] && [ "$1" == "--non-append" ]; then - nonappend=1 - # Destroy this argument for the next command. - shift - elif [ $# -gt 1 ] && [ "$2" == "--non-append" ]; then - nonappend=1 - fi - - if [ $# -gt 0 ] && [ "$(echo ${1:0:1})" != "-" ]; then - if [ "$(echo ${1##*.})" == "log" ]; then # Keep only the extension to check if it's a logfile - local logfile=$1 # In this case, focus logrotate on the logfile - else - local logfile=$1/*.log # Else, uses the directory and all logfile into it. - fi - fi - # LEGACY CODE - - local customtee="tee -a" - if [ "$nonappend" -eq 1 ]; then - customtee="tee" - fi - if [ -n "$logfile" ] - then - if [ "$(echo ${logfile##*.})" != "log" ]; then # Keep only the extension to check if it's a logfile - local logfile="$1/*.log" # Else, uses the directory and all logfile into it. - fi - else - logfile="/var/log/${app}/*.log" # Without argument, use a defaut directory in /var/log - fi - local su_directive="" - if [[ -n $specific_user ]]; then - su_directive=" # Run logorotate as specific user - group - su ${specific_user%/*} ${specific_user#*/}" - fi - - cat > ./${app}-logrotate << EOF # Build a config file for logrotate -$logfile { - # Rotate if the logfile exceeds 100Mo - size 100M - # Keep 12 old log maximum - rotate 12 - # Compress the logs with gzip - compress - # Compress the log at the next cycle. So keep always 2 non compressed logs - delaycompress - # Copy and truncate the log to allow to continue write on it. Instead of move the log. - copytruncate - # Do not do an error if the log is missing - missingok - # Not rotate if the log is empty - notifempty - # Keep old logs in the same dir - noolddir - $su_directive -} -EOF - sudo mkdir -p $(dirname "$logfile") # Create the log directory, if not exist - cat ${app}-logrotate | sudo $customtee /etc/logrotate.d/$app > /dev/null # Append this config to the existing config file, or replace the whole config file (depending on $customtee) -} - -# Remove the app's logrotate config. -# -# usage: ynh_remove_logrotate -ynh_remove_logrotate () { - if [ -e "/etc/logrotate.d/$app" ]; then - sudo rm "/etc/logrotate.d/$app" - fi -} - -# Create a dedicated systemd config -# -# usage: ynh_add_systemd_config [--service=service] [--template=template] -# | arg: -s, --service - Service name (optionnal, $app by default) -# | arg: -t, --template - Name of template file (optionnal, this is 'systemd' by default, meaning ./conf/systemd.service will be used as template) -# -# This will use the template ../conf/.service -# to generate a systemd config, by replacing the following keywords -# with global variables that should be defined before calling -# this helper : -# -# __APP__ by $app -# __FINALPATH__ by $final_path -# -ynh_add_systemd_config () { - # Declare an array to define the options of this helper. - local legacy_args=st - declare -Ar args_array=( [s]=service= [t]=template= ) - local service - local template - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - local service="${service:-$app}" - local template="${template:-systemd.service}" - - finalsystemdconf="/etc/systemd/system/$service.service" - ynh_backup_if_checksum_is_different --file="$finalsystemdconf" - sudo cp ../conf/$template "$finalsystemdconf" - - # To avoid a break by set -u, use a void substitution ${var:-}. If the variable is not set, it's simply set with an empty variable. - # Substitute in a nginx config file only if the variable is not empty - if test -n "${final_path:-}"; then - ynh_replace_string --match_string="__FINALPATH__" --replace_string="$final_path" --target_file="$finalsystemdconf" - fi - if test -n "${app:-}"; then - ynh_replace_string --match_string="__APP__" --replace_string="$app" --target_file="$finalsystemdconf" - fi - ynh_store_file_checksum --file="$finalsystemdconf" - - sudo chown root: "$finalsystemdconf" - sudo systemctl enable $service - sudo systemctl daemon-reload -} - -# Remove the dedicated systemd config -# -# usage: ynh_remove_systemd_config [--service=service] -# | arg: -s, --service - Service name (optionnal, $app by default) -# -ynh_remove_systemd_config () { - # Declare an array to define the options of this helper. - local legacy_args=s - declare -Ar args_array=( [s]=service= ) - local service - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - local service="${service:-$app}" - - local finalsystemdconf="/etc/systemd/system/$service.service" - if [ -e "$finalsystemdconf" ]; then - sudo systemctl stop $service - sudo systemctl disable $service - ynh_secure_remove --file="$finalsystemdconf" - sudo systemctl daemon-reload - fi -} - -# Create a dedicated nginx config -# -# usage: ynh_add_nginx_config "list of others variables to replace" -# -# | arg: list of others variables to replace separeted by a space -# | for example : 'path_2 port_2 ...' -# -# This will use a template in ../conf/nginx.conf -# __PATH__ by $path_url -# __DOMAIN__ by $domain -# __PORT__ by $port -# __NAME__ by $app -# __FINALPATH__ by $final_path -# -# And dynamic variables (from the last example) : -# __PATH_2__ by $path_2 -# __PORT_2__ by $port_2 -# -ynh_add_nginx_config () { - finalnginxconf="/etc/nginx/conf.d/$domain.d/$app.conf" - local others_var=${1:-} - ynh_backup_if_checksum_is_different --file="$finalnginxconf" - sudo cp ../conf/nginx.conf "$finalnginxconf" - - # To avoid a break by set -u, use a void substitution ${var:-}. If the variable is not set, it's simply set with an empty variable. - # Substitute in a nginx config file only if the variable is not empty - if test -n "${path_url:-}"; then - # path_url_slash_less is path_url, or a blank value if path_url is only '/' - local path_url_slash_less=${path_url%/} - ynh_replace_string --match_string="__PATH__/" --replace_string="$path_url_slash_less/" --target_file="$finalnginxconf" - ynh_replace_string --match_string="__PATH__" --replace_string="$path_url" --target_file="$finalnginxconf" - fi - if test -n "${domain:-}"; then - ynh_replace_string --match_string="__DOMAIN__" --replace_string="$domain" --target_file="$finalnginxconf" - fi - if test -n "${port:-}"; then - ynh_replace_string --match_string="__PORT__" --replace_string="$port" --target_file="$finalnginxconf" - fi - if test -n "${app:-}"; then - ynh_replace_string --match_string="__NAME__" --replace_string="$app" --target_file="$finalnginxconf" - fi - if test -n "${final_path:-}"; then - ynh_replace_string --match_string="__FINALPATH__" --replace_string="$final_path" --target_file="$finalnginxconf" - fi - - # Replace all other variable given as arguments - for var_to_replace in $others_var - do - # ${var_to_replace^^} make the content of the variable on upper-cases - # ${!var_to_replace} get the content of the variable named $var_to_replace - ynh_replace_string --match_string="__${var_to_replace^^}__" --replace_string="${!var_to_replace}" --target_file="$finalnginxconf" - done - - if [ "${path_url:-}" != "/" ] - then - ynh_replace_string --match_string="^#sub_path_only" --replace_string="" --target_file="$finalnginxconf" - else - ynh_replace_string --match_string="^#root_path_only" --replace_string="" --target_file="$finalnginxconf" - fi - - ynh_store_file_checksum --file="$finalnginxconf" - - sudo systemctl reload nginx -} - -# Remove the dedicated nginx config -# -# usage: ynh_remove_nginx_config -ynh_remove_nginx_config () { - ynh_secure_remove --file="/etc/nginx/conf.d/$domain.d/$app.conf" - sudo systemctl reload nginx -} - -# Create a dedicated php-fpm config -# -# usage: ynh_add_fpm_config -ynh_add_fpm_config () { - # Configure PHP-FPM 7.0 by default - local fpm_config_dir="/etc/php/7.0/fpm" - local fpm_service="php7.0-fpm" - # Configure PHP-FPM 5 on Debian Jessie - if [ "$(ynh_get_debian_release)" == "jessie" ]; then - fpm_config_dir="/etc/php5/fpm" - fpm_service="php5-fpm" - fi - ynh_app_setting_set --app=$app --key=fpm_config_dir --value="$fpm_config_dir" - ynh_app_setting_set --app=$app --key=fpm_service --value="$fpm_service" - finalphpconf="$fpm_config_dir/pool.d/$app.conf" - ynh_backup_if_checksum_is_different --file="$finalphpconf" - sudo cp ../conf/php-fpm.conf "$finalphpconf" - ynh_replace_string --match_string="__NAMETOCHANGE__" --replace_string="$app" --target_file="$finalphpconf" - ynh_replace_string --match_string="__FINALPATH__" --replace_string="$final_path" --target_file="$finalphpconf" - ynh_replace_string --match_string="__USER__" --replace_string="$app" --target_file="$finalphpconf" - sudo chown root: "$finalphpconf" - ynh_store_file_checksum --file="$finalphpconf" - - if [ -e "../conf/php-fpm.ini" ] - then - echo "Packagers ! Please do not use a separate php ini file, merge your directives in the pool file instead." >&2 - finalphpini="$fpm_config_dir/conf.d/20-$app.ini" - ynh_backup_if_checksum_is_different "$finalphpini" - sudo cp ../conf/php-fpm.ini "$finalphpini" - sudo chown root: "$finalphpini" - ynh_store_file_checksum "$finalphpini" - fi - sudo systemctl reload $fpm_service -} - -# Remove the dedicated php-fpm config -# -# usage: ynh_remove_fpm_config -ynh_remove_fpm_config () { - local fpm_config_dir=$(ynh_app_setting_get --app=$app --key=fpm_config_dir) - local fpm_service=$(ynh_app_setting_get --app=$app --key=fpm_service) - # Assume php version 7 if not set - if [ -z "$fpm_config_dir" ]; then - fpm_config_dir="/etc/php/7.0/fpm" - fpm_service="php7.0-fpm" - fi - ynh_secure_remove --file="$fpm_config_dir/pool.d/$app.conf" - ynh_secure_remove --file="$fpm_config_dir/conf.d/20-$app.ini" 2>&1 - sudo systemctl reload $fpm_service -} - -# Create a dedicated fail2ban config (jail and filter conf files) -# -# usage 1: ynh_add_fail2ban_config --logpath=log_file --failregex=filter [--max_retry=max_retry] [--ports=ports] -# | arg: -l, --logpath= - Log file to be checked by fail2ban -# | arg: -r, --failregex= - Failregex to be looked for by fail2ban -# | arg: -m, --max_retry= - Maximum number of retries allowed before banning IP address - default: 3 -# | arg: -p, --ports= - Ports blocked for a banned IP address - default: http,https -# -# ----------------------------------------------------------------------------- -# -# usage 2: ynh_add_fail2ban_config --use_template [--others_var="list of others variables to replace"] -# | arg: -t, --use_template - Use this helper in template mode -# | arg: -v, --others_var= - List of others variables to replace separeted by a space -# | for example : 'var_1 var_2 ...' -# -# This will use a template in ../conf/f2b_jail.conf and ../conf/f2b_filter.conf -# __APP__ by $app -# -# You can dynamically replace others variables by example : -# __VAR_1__ by $var_1 -# __VAR_2__ by $var_2 -# -# Generally your template will look like that by example (for synapse): -# -# f2b_jail.conf: -# [__APP__] -# enabled = true -# port = http,https -# filter = __APP__ -# logpath = /var/log/__APP__/logfile.log -# maxretry = 3 -# -# f2b_filter.conf: -# [INCLUDES] -# before = common.conf -# [Definition] -# -# # Part of regex definition (just used to make more easy to make the global regex) -# __synapse_start_line = .? \- synapse\..+ \- -# -# # Regex definition. -# failregex = ^%(__synapse_start_line)s INFO \- POST\-(\d+)\- \- \d+ \- Received request\: POST /_matrix/client/r0/login\??%(__synapse_start_line)s INFO \- POST\-\1\- Got login request with identifier: \{u'type': u'm.id.user', u'user'\: u'(.+?)'\}, medium\: None, address: None, user\: u'\5'%(__synapse_start_line)s WARNING \- \- (Attempted to login as @\5\:.+ but they do not exist|Failed password login for user @\5\:.+)$ -# -# ignoreregex = -# -# ----------------------------------------------------------------------------- -# -# Note about the "failregex" option: -# regex to match the password failure messages in the logfile. The -# host must be matched by a group named "host". The tag "" can -# be used for standard IP/hostname matching and is only an alias for -# (?:::f{4,6}:)?(?P[\w\-.^_]+) -# -# You can find some more explainations about how to make a regex here : -# https://www.fail2ban.org/wiki/index.php/MANUAL_0_8#Filters -# -# Note that the logfile need to exist before to call this helper !! -# -# To validate your regex you can test with this command: -# fail2ban-regex /var/log/YOUR_LOG_FILE_PATH /etc/fail2ban/filter.d/YOUR_APP.conf -# -ynh_add_fail2ban_config () { - # Declare an array to define the options of this helper. - local legacy_args=lrmptv - declare -Ar args_array=( [l]=logpath= [r]=failregex= [m]=max_retry= [p]=ports= [t]=use_template [v]=others_var=) - local logpath - local failregex - local max_retry - local ports - local others_var - local use_template - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - use_template="${use_template:-0}" - max_retry=${max_retry:-3} - ports=${ports:-http,https} - - finalfail2banjailconf="/etc/fail2ban/jail.d/$app.conf" - finalfail2banfilterconf="/etc/fail2ban/filter.d/$app.conf" - ynh_backup_if_checksum_is_different "$finalfail2banjailconf" - ynh_backup_if_checksum_is_different "$finalfail2banfilterconf" - - if [ $use_template -eq 1 ] - then - # Usage 2, templates - cp ../conf/f2b_jail.conf $finalfail2banjailconf - cp ../conf/f2b_filter.conf $finalfail2banfilterconf - - if [ -n "${app:-}" ] - then - ynh_replace_string "__APP__" "$app" "$finalfail2banjailconf" - ynh_replace_string "__APP__" "$app" "$finalfail2banfilterconf" - fi - - # Replace all other variable given as arguments - for var_to_replace in ${others_var:-}; do - # ${var_to_replace^^} make the content of the variable on upper-cases - # ${!var_to_replace} get the content of the variable named $var_to_replace - ynh_replace_string --match_string="__${var_to_replace^^}__" --replace_string="${!var_to_replace}" --target_file="$finalfail2banjailconf" - ynh_replace_string --match_string="__${var_to_replace^^}__" --replace_string="${!var_to_replace}" --target_file="$finalfail2banfilterconf" - done - - else - # Usage 1, no template. Build a config file from scratch. - test -n "$logpath" || ynh_die "ynh_add_fail2ban_config expects a logfile path as first argument and received nothing." - test -n "$failregex" || ynh_die "ynh_add_fail2ban_config expects a failure regex as second argument and received nothing." - - tee $finalfail2banjailconf < This line will be added into CSV file @@ -35,7 +30,7 @@ CAN_BIND=${CAN_BIND:-1} # # ynh_backup "/etc/nginx/conf.d/$domain.d/$app.conf" "conf/" # # => "/etc/nginx/conf.d/$domain.d/$app.conf","apps/wordpress/conf/$app.conf" -# +# # ynh_backup "/etc/nginx/conf.d/$domain.d/$app.conf" "conf" # # => "/etc/nginx/conf.d/$domain.d/$app.conf","apps/wordpress/conf" # @@ -46,6 +41,7 @@ CAN_BIND=${CAN_BIND:-1} # ynh_backup "/etc/nginx/conf.d/$domain.d/$app.conf" "/conf/" # # => "/etc/nginx/conf.d/$domain.d/$app.conf","apps/wordpress/conf/$app.conf" # +# Requires YunoHost version 2.4.0 or higher. ynh_backup() { # TODO find a way to avoid injection by file strange naming ! @@ -67,25 +63,23 @@ ynh_backup() { # If backing up core only (used by ynh_backup_before_upgrade), # don't backup big data items if [ "$is_big" == "1" ] && [ "$BACKUP_CORE_ONLY" == "1" ] ; then - echo "$src_path will not be saved, because backup_core_only is set." >&2 + ynh_print_info --message="$src_path will not be saved, because backup_core_only is set." return 0 fi - + # ============================================================================== # Format correctly source and destination paths # ============================================================================== # Be sure the source path is not empty [[ -e "${src_path}" ]] || { - echo "Source path '${src_path}' does not exist" >&2 + ynh_print_warn --message="Source path '${src_path}' does not exist" if [ "$not_mandatory" == "0" ] then - echo "Source path '${SRC_PATH}' does not exist" >&2 - # This is a temporary fix for fail2ban config files missing after the migration to stretch. if echo "${src_path}" | grep --quiet "/etc/fail2ban" then touch "${src_path}" - echo "The missing file will be replaced by a dummy one for the backup !!!" >&2 + ynh_print_info --message="The missing file will be replaced by a dummy one for the backup !!!" else return 1 fi @@ -129,7 +123,7 @@ ynh_backup() { # Check if dest_path already exists in tmp archive [[ ! -e "${dest_path}" ]] || { - echo "Destination path '${dest_path}' already exist" >&2 + ynh_print_err --message="Destination path '${dest_path}' already exist" return 1 } @@ -158,6 +152,7 @@ ynh_backup() { # # usage: ynh_restore # +# Requires YunoHost version 2.6.4 or higher. ynh_restore () { # Deduce the relative path of $YNH_CWD local REL_DIR="${YNH_CWD#$YNH_BACKUP_DIR/}" @@ -193,32 +188,30 @@ with open(sys.argv[1], 'r') as backup_file: return $? } -# Restore a file or a directory +# Restore a file or a directory # # Use the registered path in backup_list by ynh_backup to restore the file at -# the good place. +# the right place. # # usage: ynh_restore_file --origin_path=origin_path [--dest_path=dest_path] [--not_mandatory] -# | arg: -o, --origin_path - Path where was located the file or the directory before -# to be backuped or relative path to $YNH_CWD where it is located in the backup archive -# | arg: -d, --dest_path - Path where restore the file or the dir, if unspecified, -# the destination will be ORIGIN_PATH or if the ORIGIN_PATH doesn't exist in -# the archive, the destination will be searched into backup.csv +# | arg: -o, --origin_path - Path where was located the file or the directory before to be backuped or relative path to $YNH_CWD where it is located in the backup archive +# | arg: -d, --dest_path - Path where restore the file or the dir, if unspecified, the destination will be ORIGIN_PATH or if the ORIGIN_PATH doesn't exist in the archive, the destination will be searched into backup.csv # | arg: -m, --not_mandatory - Indicate that if the file is missing, the restore process can ignore it. # -# If DEST_PATH already exists and is lighter than 500 Mo, a backup will be made in +# examples: +# ynh_restore_file "/etc/nginx/conf.d/$domain.d/$app.conf" +# # You can also use relative paths: +# ynh_restore_file "conf/nginx.conf" +# +# If DEST_PATH already exists and is lighter than 500 Mo, a backup will be made in # /home/yunohost.conf/backup/. Otherwise, the existing file is removed. # -# examples: -# ynh_restore_file "/etc/nginx/conf.d/$domain.d/$app.conf" -# # if apps/wordpress/etc/nginx/conf.d/$domain.d/$app.conf exists, restore it into -# # /etc/nginx/conf.d/$domain.d/$app.conf -# # if no, search a correspondance in the csv (eg: conf/nginx.conf) and restore it into -# # /etc/nginx/conf.d/$domain.d/$app.conf -# -# # DON'T GIVE THE ARCHIVE PATH: -# ynh_restore_file "conf/nginx.conf" +# if apps/wordpress/etc/nginx/conf.d/$domain.d/$app.conf exists, restore it into +# /etc/nginx/conf.d/$domain.d/$app.conf +# if no, search for a match in the csv (eg: conf/nginx.conf) and restore it into +# /etc/nginx/conf.d/$domain.d/$app.conf # +# Requires YunoHost version 2.6.4 or higher. ynh_restore_file () { # Declare an array to define the options of this helper. local legacy_args=odm @@ -253,7 +246,7 @@ ynh_restore_file () { then local backup_file="/home/yunohost.conf/backup/${dest_path}.backup.$(date '+%Y%m%d.%H%M%S')" mkdir -p "$(dirname "$backup_file")" - mv "${dest_path}" "$backup_file" # Move the current file or directory + mv "${dest_path}" "$backup_file" # Move the current file or directory else ynh_secure_remove --file=${dest_path} fi @@ -283,91 +276,74 @@ ynh_bind_or_cp() { local AS_ROOT=${3:-0} local NO_ROOT=0 [[ "${AS_ROOT}" = "1" ]] || NO_ROOT=1 - echo "This helper is deprecated, you should use ynh_backup instead" >&2 + ynh_print_warn --message="This helper is deprecated, you should use ynh_backup instead" ynh_backup "$1" "$2" 1 } -# Create a directory under /tmp -# -# [internal] -# -# Deprecated helper -# -# usage: ynh_mkdir_tmp -# | ret: the created directory path -ynh_mkdir_tmp() { - echo "The helper ynh_mkdir_tmp is deprecated." >&2 - echo "You should use 'mktemp -d' instead and manage permissions \ -properly with chmod/chown." >&2 - local TMP_DIR=$(mktemp -d) - - # Give rights to other users could be a security risk. - # But for retrocompatibility we need it. (This helpers is deprecated) - chmod 755 $TMP_DIR - echo $TMP_DIR -} - # Calculate and store a file checksum into the app settings # # $app should be defined when calling this helper # # usage: ynh_store_file_checksum --file=file # | arg: -f, --file - The file on which the checksum will performed, then stored. +# +# Requires YunoHost version 2.6.4 or higher. ynh_store_file_checksum () { - # Declare an array to define the options of this helper. - local legacy_args=f - declare -Ar args_array=( [f]=file= ) - local file - # Manage arguments with getopts - ynh_handle_getopts_args "$@" + # Declare an array to define the options of this helper. + local legacy_args=f + declare -Ar args_array=( [f]=file= ) + local file + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_' - ynh_app_setting_set --app=$app --key=$checksum_setting_name --value=$(sudo md5sum "$file" | cut -d' ' -f1) + local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_' + ynh_app_setting_set --app=$app --key=$checksum_setting_name --value=$(sudo md5sum "$file" | cut -d' ' -f1) - # If backup_file_checksum isn't empty, ynh_backup_if_checksum_is_different has made a backup - if [ -n "${backup_file_checksum-}" ] - then - # Print the diff between the previous file and the new one. - # diff return 1 if the files are different, so the || true - diff --report-identical-files --unified --color=always $backup_file_checksum $file >&2 || true - fi - # Unset the variable, so it wouldn't trig a ynh_store_file_checksum without a ynh_backup_if_checksum_is_different before it. - unset backup_file_checksum + # If backup_file_checksum isn't empty, ynh_backup_if_checksum_is_different has made a backup + if [ -n "${backup_file_checksum-}" ] + then + # Print the diff between the previous file and the new one. + # diff return 1 if the files are different, so the || true + diff --report-identical-files --unified --color=always $backup_file_checksum $file >&2 || true + fi + # Unset the variable, so it wouldn't trig a ynh_store_file_checksum without a ynh_backup_if_checksum_is_different before it. + unset backup_file_checksum } # Verify the checksum and backup the file if it's different -# This helper is primarily meant to allow to easily backup personalised/manually +# This helper is primarily meant to allow to easily backup personalised/manually # modified config files. # # $app should be defined when calling this helper # # usage: ynh_backup_if_checksum_is_different --file=file # | arg: -f, --file - The file on which the checksum test will be perfomed. +# | ret: the name of a backup file, or nothing # -# | ret: Return the name a the backup file, or nothing +# Requires YunoHost version 2.6.4 or higher. ynh_backup_if_checksum_is_different () { - # Declare an array to define the options of this helper. - local legacy_args=f - declare -Ar args_array=( [f]=file= ) - local file - # Manage arguments with getopts - ynh_handle_getopts_args "$@" + # Declare an array to define the options of this helper. + local legacy_args=f + declare -Ar args_array=( [f]=file= ) + local file + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_' - local checksum_value=$(ynh_app_setting_get --app=$app --key=$checksum_setting_name) - # backup_file_checksum isn't declare as local, so it can be reuse by ynh_store_file_checksum - backup_file_checksum="" - if [ -n "$checksum_value" ] - then # Proceed only if a value was stored into the app settings - if ! echo "$checksum_value $file" | sudo md5sum -c --status - then # If the checksum is now different - backup_file_checksum="/home/yunohost.conf/backup/$file.backup.$(date '+%Y%m%d.%H%M%S')" - sudo mkdir -p "$(dirname "$backup_file_checksum")" - sudo cp -a "$file" "$backup_file_checksum" # Backup the current file - ynh_print_warn "File $file has been manually modified since the installation or last upgrade. So it has been duplicated in $backup_file_checksum" - echo "$backup_file_checksum" # Return the name of the backup file - fi - fi + local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_' + local checksum_value=$(ynh_app_setting_get --app=$app --key=$checksum_setting_name) + # backup_file_checksum isn't declare as local, so it can be reuse by ynh_store_file_checksum + backup_file_checksum="" + if [ -n "$checksum_value" ] + then # Proceed only if a value was stored into the app settings + if ! echo "$checksum_value $file" | sudo md5sum -c --status + then # If the checksum is now different + backup_file_checksum="/home/yunohost.conf/backup/$file.backup.$(date '+%Y%m%d.%H%M%S')" + sudo mkdir -p "$(dirname "$backup_file_checksum")" + sudo cp -a "$file" "$backup_file_checksum" # Backup the current file + ynh_print_warn "File $file has been manually modified since the installation or last upgrade. So it has been duplicated in $backup_file_checksum" + echo "$backup_file_checksum" # Return the name of the backup file + fi + fi } # Delete a file checksum from the app settings @@ -376,53 +352,97 @@ ynh_backup_if_checksum_is_different () { # # usage: ynh_remove_file_checksum file # | arg: -f, --file= - The file for which the checksum will be deleted -ynh_delete_file_checksum () { - # Declare an array to define the options of this helper. - local legacy_args=f - declare -Ar args_array=( [f]=file= ) - local file - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - - local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_' - ynh_app_setting_delete --app=$app --key=$checksum_setting_name -} - -# Remove a file or a directory securely # -# usage: ynh_secure_remove --file=path_to_remove -# | arg: -f, --file - File or directory to remove -ynh_secure_remove () { - # Declare an array to define the options of this helper. - local legacy_args=f - declare -Ar args_array=( [f]=file= ) - local file - # Manage arguments with getopts - ynh_handle_getopts_args "$@" +# Requires YunoHost version 3.3.1 or higher. +ynh_delete_file_checksum () { + # Declare an array to define the options of this helper. + local legacy_args=f + declare -Ar args_array=( [f]=file= ) + local file + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - local forbidden_path=" \ - /var/www \ - /home/yunohost.app" - - if [ $# -ge 2 ] - then - echo "/!\ Packager ! You provided more than one argument to ynh_secure_remove but it will be ignored... Use this helper with one argument at time." >&2 - fi - - if [[ "$forbidden_path" =~ "$file" \ - # Match all paths or subpaths in $forbidden_path - || "$file" =~ ^/[[:alnum:]]+$ \ - # Match all first level paths from / (Like /var, /root, etc...) - || "${file:${#file}-1}" = "/" ]] - # Match if the path finishes by /. Because it seems there is an empty variable - then - echo "Avoid deleting $file." >&2 - else - if [ -e "$file" ] - then - sudo rm -R "$file" - else - echo "$file wasn't deleted because it doesn't exist." >&2 - fi - fi + local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_' + ynh_app_setting_delete --app=$app --key=$checksum_setting_name +} + +# Make a backup in case of failed upgrade +# +# usage: +# ynh_backup_before_upgrade +# ynh_clean_setup () { +# ynh_restore_upgradebackup +# } +# ynh_abort_if_errors +# +# Requires YunoHost version 2.7.2 or higher. +ynh_backup_before_upgrade () { + if [ ! -e "/etc/yunohost/apps/$app/scripts/backup" ] + then + ynh_print_warn --message="This app doesn't have any backup script." + return + fi + backup_number=1 + local old_backup_number=2 + local app_bck=${app//_/-} # Replace all '_' by '-' + NO_BACKUP_UPGRADE=${NO_BACKUP_UPGRADE:-0} + + if [ "$NO_BACKUP_UPGRADE" -eq 0 ] + then + # Check if a backup already exists with the prefix 1 + if sudo yunohost backup list | grep -q $app_bck-pre-upgrade1 + then + # Prefix becomes 2 to preserve the previous backup + backup_number=2 + old_backup_number=1 + fi + + # Create backup + sudo BACKUP_CORE_ONLY=1 yunohost backup create --apps $app --name $app_bck-pre-upgrade$backup_number --debug + if [ "$?" -eq 0 ] + then + # If the backup succeeded, remove the previous backup + if sudo yunohost backup list | grep -q $app_bck-pre-upgrade$old_backup_number + then + # Remove the previous backup only if it exists + sudo yunohost backup delete $app_bck-pre-upgrade$old_backup_number > /dev/null + fi + else + ynh_die --message="Backup failed, the upgrade process was aborted." + fi + else + ynh_print_warn --message="\$NO_BACKUP_UPGRADE is set, backup will be avoided. Be careful, this upgrade is going to be operated without a security backup" + fi +} + +# Restore a previous backup if the upgrade process failed +# +# usage: +# ynh_backup_before_upgrade +# ynh_clean_setup () { +# ynh_restore_upgradebackup +# } +# ynh_abort_if_errors +# +# Requires YunoHost version 2.7.2 or higher. +ynh_restore_upgradebackup () { + ynh_print_err --message="Upgrade failed." + local app_bck=${app//_/-} # Replace all '_' by '-' + + NO_BACKUP_UPGRADE=${NO_BACKUP_UPGRADE:-0} + + if [ "$NO_BACKUP_UPGRADE" -eq 0 ] + then + # Check if an existing backup can be found before removing and restoring the application. + if sudo yunohost backup list | grep -q $app_bck-pre-upgrade$backup_number + then + # Remove the application then restore it + sudo yunohost app remove $app + # Restore the backup + sudo yunohost backup restore $app_bck-pre-upgrade$backup_number --apps $app --force --debug + ynh_die --message="The app was restored to the way it was before the failed upgrade." + fi + else + ynh_print_warn --message="\$NO_BACKUP_UPGRADE is set, that means there's no backup to restore. You have to fix this upgrade by yourself !" + fi } diff --git a/data/helpers.d/debug b/data/helpers.d/debug deleted file mode 100644 index a8b7c8d69..000000000 --- a/data/helpers.d/debug +++ /dev/null @@ -1,59 +0,0 @@ -#!/bin/bash - -# Debugger for app packagers -# -# usage: ynh_debug [--message=message] [--trace=1/0] -# | arg: -m, --message= - The text to print -# | arg: -t, --trace= - Turn on or off the trace of the script. Usefull to trace nonly a small part of a script. -ynh_debug () { - # Disable set xtrace for the helper itself, to not pollute the debug log - set +x - # Declare an array to define the options of this helper. - local legacy_args=mt - declare -Ar args_array=( [m]=message= [t]=trace= ) - local message - local trace - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - # Redisable xtrace, ynh_handle_getopts_args set it back - set +x - message=${message:-} - trace=${trace:-} - - if [ -n "$message" ] - then - ynh_print_log "\e[34m\e[1m[DEBUG]\e[0m ${message}" >&2 - fi - - if [ "$trace" == "1" ] - then - ynh_debug --message="Enable debugging" - set +x - # Get the current file descriptor of xtrace - old_bash_xtracefd=$BASH_XTRACEFD - # Add the current file name and the line number of any command currently running while tracing. - PS4='$(basename ${BASH_SOURCE[0]})-L${LINENO}: ' - # Force xtrace to stderr - BASH_XTRACEFD=2 - fi - if [ "$trace" == "0" ] - then - ynh_debug --message="Disable debugging" - set +x - # Put xtrace back to its original fild descriptor - BASH_XTRACEFD=$old_bash_xtracefd - fi - # Renable set xtrace - set -x -} - -# Execute a command and print the result as debug -# -# usage: ynh_debug_exec command to execute -# usage: ynh_debug_exec "command to execute | following command" -# In case of use of pipes, you have to use double quotes. Otherwise, this helper will be executed with the first command, then be sent to the next pipe. -# -# | arg: command - command to execute -ynh_debug_exec () { - ynh_debug --message="$(eval $@)" -} diff --git a/data/helpers.d/fail2ban b/data/helpers.d/fail2ban new file mode 100644 index 000000000..85f568520 --- /dev/null +++ b/data/helpers.d/fail2ban @@ -0,0 +1,151 @@ +#!/bin/bash + +# Create a dedicated fail2ban config (jail and filter conf files) +# +# usage 1: ynh_add_fail2ban_config --logpath=log_file --failregex=filter [--max_retry=max_retry] [--ports=ports] +# | arg: -l, --logpath= - Log file to be checked by fail2ban +# | arg: -r, --failregex= - Failregex to be looked for by fail2ban +# | arg: -m, --max_retry= - Maximum number of retries allowed before banning IP address - default: 3 +# | arg: -p, --ports= - Ports blocked for a banned IP address - default: http,https +# +# ----------------------------------------------------------------------------- +# +# usage 2: ynh_add_fail2ban_config --use_template [--others_var="list of others variables to replace"] +# | arg: -t, --use_template - Use this helper in template mode +# | arg: -v, --others_var= - List of others variables to replace separeted by a space +# | for example : 'var_1 var_2 ...' +# +# This will use a template in ../conf/f2b_jail.conf and ../conf/f2b_filter.conf +# __APP__ by $app +# +# You can dynamically replace others variables by example : +# __VAR_1__ by $var_1 +# __VAR_2__ by $var_2 +# +# Generally your template will look like that by example (for synapse): +# +# f2b_jail.conf: +# [__APP__] +# enabled = true +# port = http,https +# filter = __APP__ +# logpath = /var/log/__APP__/logfile.log +# maxretry = 3 +# +# f2b_filter.conf: +# [INCLUDES] +# before = common.conf +# [Definition] +# +# # Part of regex definition (just used to make more easy to make the global regex) +# __synapse_start_line = .? \- synapse\..+ \- +# +# # Regex definition. +# failregex = ^%(__synapse_start_line)s INFO \- POST\-(\d+)\- \- \d+ \- Received request\: POST /_matrix/client/r0/login\??%(__synapse_start_line)s INFO \- POST\-\1\- Got login request with identifier: \{u'type': u'm.id.user', u'user'\: u'(.+?)'\}, medium\: None, address: None, user\: u'\5'%(__synapse_start_line)s WARNING \- \- (Attempted to login as @\5\:.+ but they do not exist|Failed password login for user @\5\:.+)$ +# +# ignoreregex = +# +# ----------------------------------------------------------------------------- +# +# Note about the "failregex" option: +# regex to match the password failure messages in the logfile. The +# host must be matched by a group named "host". The tag "" can +# be used for standard IP/hostname matching and is only an alias for +# (?:::f{4,6}:)?(?P[\w\-.^_]+) +# +# You can find some more explainations about how to make a regex here : +# https://www.fail2ban.org/wiki/index.php/MANUAL_0_8#Filters +# +# Note that the logfile need to exist before to call this helper !! +# +# To validate your regex you can test with this command: +# fail2ban-regex /var/log/YOUR_LOG_FILE_PATH /etc/fail2ban/filter.d/YOUR_APP.conf +# +# Requires YunoHost version 3.?.? or higher. +ynh_add_fail2ban_config () { + # Declare an array to define the options of this helper. + local legacy_args=lrmptv + declare -Ar args_array=( [l]=logpath= [r]=failregex= [m]=max_retry= [p]=ports= [t]=use_template [v]=others_var=) + local logpath + local failregex + local max_retry + local ports + local others_var + local use_template + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + use_template="${use_template:-0}" + max_retry=${max_retry:-3} + ports=${ports:-http,https} + + finalfail2banjailconf="/etc/fail2ban/jail.d/$app.conf" + finalfail2banfilterconf="/etc/fail2ban/filter.d/$app.conf" + ynh_backup_if_checksum_is_different "$finalfail2banjailconf" + ynh_backup_if_checksum_is_different "$finalfail2banfilterconf" + + if [ $use_template -eq 1 ] + then + # Usage 2, templates + cp ../conf/f2b_jail.conf $finalfail2banjailconf + cp ../conf/f2b_filter.conf $finalfail2banfilterconf + + if [ -n "${app:-}" ] + then + ynh_replace_string "__APP__" "$app" "$finalfail2banjailconf" + ynh_replace_string "__APP__" "$app" "$finalfail2banfilterconf" + fi + + # Replace all other variable given as arguments + for var_to_replace in ${others_var:-}; do + # ${var_to_replace^^} make the content of the variable on upper-cases + # ${!var_to_replace} get the content of the variable named $var_to_replace + ynh_replace_string --match_string="__${var_to_replace^^}__" --replace_string="${!var_to_replace}" --target_file="$finalfail2banjailconf" + ynh_replace_string --match_string="__${var_to_replace^^}__" --replace_string="${!var_to_replace}" --target_file="$finalfail2banfilterconf" + done + + else + # Usage 1, no template. Build a config file from scratch. + test -n "$logpath" || ynh_die "ynh_add_fail2ban_config expects a logfile path as first argument and received nothing." + test -n "$failregex" || ynh_die "ynh_add_fail2ban_config expects a failure regex as second argument and received nothing." + + tee $finalfail2banjailconf <&1 } @@ -128,6 +145,8 @@ ynh_exec_warn_less () { # If the command to execute uses double quotes, they have to be escaped or they will be interpreted and removed. # # | arg: command - command to execute +# +# Requires YunoHost version 3.2.0 or higher. ynh_exec_quiet () { eval $@ > /dev/null } @@ -140,6 +159,8 @@ ynh_exec_quiet () { # If the command to execute uses double quotes, they have to be escaped or they will be interpreted and removed. # # | arg: command - command to execute +# +# Requires YunoHost version 3.2.0 or higher. ynh_exec_fully_quiet () { eval $@ > /dev/null 2>&1 } @@ -148,6 +169,8 @@ ynh_exec_fully_quiet () { # # usage: ynh_print_OFF # WARNING: You should be careful with this helper, and never forget to use ynh_print_ON as soon as possible to restore the logging. +# +# Requires YunoHost version 3.2.0 or higher. ynh_print_OFF () { set +x } @@ -155,6 +178,8 @@ ynh_print_OFF () { # Restore the logging after ynh_print_OFF # # usage: ynh_print_ON +# +# Requires YunoHost version 3.2.0 or higher. ynh_print_ON () { set -x # Print an echo only for the log, to be able to know that ynh_print_ON has been called. @@ -167,13 +192,17 @@ ynh_print_ON () { # | arg: -m, --message= - The text to print # | arg: -w, --weight= - The weight for this progression. This value is 1 by default. Use a bigger value for a longer part of the script. # | arg: -t, --time= - Print the execution time since the last call to this helper. Especially usefull to define weights. +# The execution time is given for the duration since the previous call. So the weight should be applied to this previous call. # | arg: -l, --last= - Use for the last call of the helper, to fill te progression bar. +# +# Requires YunoHost version 3.?.? or higher. increment_progression=0 previous_weight=0 # Define base_time when the file is sourced base_time=$(date +%s) ynh_script_progression () { # Declare an array to define the options of this helper. + local legacy_args=mwtl declare -Ar args_array=( [m]=message= [w]=weight= [t]=time [l]=last ) local message local weight @@ -195,9 +224,9 @@ ynh_script_progression () { local weight_calls=$(grep --perl-regexp --count "^[^#]*ynh_script_progression.*(--weight|-w )" $0) # Get the weight of each occurrences of 'ynh_script_progression' in the script using --weight - local weight_valuesA="$(grep --perl-regexp "^[^#]*ynh_script_progression.*--weight" $0 | sed 's/.*--weight[= ]\([[:digit:]].*\)/\1/g')" - # Get the weight of each occurrences of 'ynh_script_progression' in the script using -w - local weight_valuesB="$(grep --perl-regexp "^[^#]*ynh_script_progression.*-w " $0 | sed 's/.*-w[= ]\([[:digit:]].*\)/\1/g')" + local weight_valuesA="$(grep --perl-regexp "^[^#]*ynh_script_progression.*--weight" $0 | sed 's/.*--weight[= ]\([[:digit:]]*\).*/\1/g')" + # Get the weight of each occurrences of 'ynh_script_progression' in the script using -w + local weight_valuesB="$(grep --perl-regexp "^[^#]*ynh_script_progression.*-w " $0 | sed 's/.*-w[= ]\([[:digit:]]*\).*/\1/g')" # Each value will be on a different line. # Remove each 'end of line' and replace it by a '+' to sum the values. local weight_values=$(( $(echo "$weight_valuesA" | tr '\n' '+') + $(echo "$weight_valuesB" | tr '\n' '+') 0 )) @@ -214,8 +243,9 @@ ynh_script_progression () { # Set the scale of the progression bar local scale=20 - # progress_string(1,2) should have the size of the scale. - local progress_string1="####################" + # progress_string(0,1,2) should have the size of the scale. + local progress_string2="####################" + local progress_string1="++++++++++++++++++++" local progress_string0="...................." # Reduce $increment_progression to the size of the scale @@ -227,8 +257,17 @@ ynh_script_progression () { local effective_progression=$scale fi - # Build $progression_bar from progress_string(1,2) according to $effective_progression - local progression_bar="${progress_string1:0:$effective_progression}${progress_string0:0:$(( $scale - $effective_progression ))}" + # Build $progression_bar from progress_string(0,1,2) according to $effective_progression and the weight of the current task + # expected_progression is the progression expected after the current task + local expected_progression="$(( ( $increment_progression + $weight ) * $scale / $max_progression - $effective_progression ))" + if [ $last -eq 1 ] + then + expected_progression=0 + fi + # left_progression is the progression not yet done + local left_progression="$(( $scale - $effective_progression - $expected_progression ))" + # Build the progression bar with $effective_progression, work done, $expected_progression, current work and $left_progression, work to be done. + local progression_bar="${progress_string2:0:$effective_progression}${progress_string1:0:$expected_progression}${progress_string0:0:$left_progression}" local print_exec_time="" if [ $time -eq 1 ] @@ -238,3 +277,69 @@ ynh_script_progression () { ynh_print_info "[$progression_bar] > ${message}${print_exec_time}" } + +# Debugger for app packagers +# +# usage: ynh_debug [--message=message] [--trace=1/0] +# | arg: -m, --message= - The text to print +# | arg: -t, --trace= - Turn on or off the trace of the script. Usefull to trace nonly a small part of a script. +# +# Requires YunoHost version 3.?.? or higher. +ynh_debug () { + # Disable set xtrace for the helper itself, to not pollute the debug log + set +x + # Declare an array to define the options of this helper. + local legacy_args=mt + declare -Ar args_array=( [m]=message= [t]=trace= ) + local message + local trace + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + # Redisable xtrace, ynh_handle_getopts_args set it back + set +x + message=${message:-} + trace=${trace:-} + + if [ -n "$message" ] + then + ynh_print_log "\e[34m\e[1m[DEBUG]\e[0m ${message}" >&2 + fi + + if [ "$trace" == "1" ] + then + ynh_debug --message="Enable debugging" + set +x + # Get the current file descriptor of xtrace + old_bash_xtracefd=$BASH_XTRACEFD + # Add the current file name and the line number of any command currently running while tracing. + PS4='$(basename ${BASH_SOURCE[0]})-L${LINENO}: ' + # Force xtrace to stderr + BASH_XTRACEFD=2 + # Force stdout to stderr + exec 1>&2 + fi + if [ "$trace" == "0" ] + then + ynh_debug --message="Disable debugging" + set +x + # Put xtrace back to its original fild descriptor + BASH_XTRACEFD=$old_bash_xtracefd + # Restore stdout + exec 1>&1 + fi + # Renable set xtrace + set -x +} + +# Execute a command and print the result as debug +# +# usage: ynh_debug_exec command to execute +# usage: ynh_debug_exec "command to execute | following command" +# In case of use of pipes, you have to use double quotes. Otherwise, this helper will be executed with the first command, then be sent to the next pipe. +# +# | arg: command - command to execute +# +# Requires YunoHost version 3.?.? or higher. +ynh_debug_exec () { + ynh_debug --message="$(eval $@)" +} diff --git a/data/helpers.d/logrotate b/data/helpers.d/logrotate new file mode 100644 index 000000000..47ce46cf6 --- /dev/null +++ b/data/helpers.d/logrotate @@ -0,0 +1,103 @@ +#!/bin/bash + +# Use logrotate to manage the logfile +# +# usage: ynh_use_logrotate [--logfile=/log/file] [--nonappend] [--specific_user=user/group] +# | arg: -l, --logfile - absolute path of logfile +# | arg: -n, --nonappend - (optional) Replace the config file instead of appending this new config. +# | arg: -u, --specific_user : run logrotate as the specified user and group. If not specified logrotate is runned as root. +# +# If no --logfile is provided, /var/log/${app} will be used as default. +# logfile can be just a directory, or a full path to a logfile : +# /parentdir/logdir +# /parentdir/logdir/logfile.log +# +# It's possible to use this helper multiple times, each config will be added to +# the same logrotate config file. Unless you use the option --non-append +# +# Requires YunoHost version 2.6.4 or higher. +ynh_use_logrotate () { + # Declare an array to define the options of this helper. + local legacy_args=lnuya + declare -Ar args_array=( [l]=logfile= [n]=nonappend [u]=specific_user= [y]=non [a]=append ) + # [y]=non [a]=append are only for legacy purpose, to not fail on the old option '--non-append' + local logfile + local nonappend + local specific_user + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + local logfile="${logfile:-}" + local nonappend="${nonappend:-0}" + local specific_user="${specific_user:-}" + + # LEGACY CODE - PRE GETOPTS + if [ $# -gt 0 ] && [ "$1" == "--non-append" ]; then + nonappend=1 + # Destroy this argument for the next command. + shift + elif [ $# -gt 1 ] && [ "$2" == "--non-append" ]; then + nonappend=1 + fi + + if [ $# -gt 0 ] && [ "$(echo ${1:0:1})" != "-" ]; then + if [ "$(echo ${1##*.})" == "log" ]; then # Keep only the extension to check if it's a logfile + local logfile=$1 # In this case, focus logrotate on the logfile + else + local logfile=$1/*.log # Else, uses the directory and all logfile into it. + fi + fi + # LEGACY CODE + + local customtee="tee -a" + if [ "$nonappend" -eq 1 ]; then + customtee="tee" + fi + if [ -n "$logfile" ] + then + if [ "$(echo ${logfile##*.})" != "log" ]; then # Keep only the extension to check if it's a logfile + local logfile="$logfile/*.log" # Else, uses the directory and all logfile into it. + fi + else + logfile="/var/log/${app}/*.log" # Without argument, use a defaut directory in /var/log + fi + local su_directive="" + if [[ -n $specific_user ]]; then + su_directive=" # Run logorotate as specific user - group + su ${specific_user%/*} ${specific_user#*/}" + fi + + cat > ./${app}-logrotate << EOF # Build a config file for logrotate +$logfile { + # Rotate if the logfile exceeds 100Mo + size 100M + # Keep 12 old log maximum + rotate 12 + # Compress the logs with gzip + compress + # Compress the log at the next cycle. So keep always 2 non compressed logs + delaycompress + # Copy and truncate the log to allow to continue write on it. Instead of move the log. + copytruncate + # Do not do an error if the log is missing + missingok + # Not rotate if the log is empty + notifempty + # Keep old logs in the same dir + noolddir + $su_directive +} +EOF + sudo mkdir -p $(dirname "$logfile") # Create the log directory, if not exist + cat ${app}-logrotate | sudo $customtee /etc/logrotate.d/$app > /dev/null # Append this config to the existing config file, or replace the whole config file (depending on $customtee) +} + +# Remove the app's logrotate config. +# +# usage: ynh_remove_logrotate +# +# Requires YunoHost version 2.6.4 or higher. +ynh_remove_logrotate () { + if [ -e "/etc/logrotate.d/$app" ]; then + sudo rm "/etc/logrotate.d/$app" + fi +} diff --git a/data/helpers.d/mysql b/data/helpers.d/mysql index fa1a61dab..39f93891c 100644 --- a/data/helpers.d/mysql +++ b/data/helpers.d/mysql @@ -11,6 +11,8 @@ MYSQL_ROOT_PWD_FILE=/etc/yunohost/mysql # | arg: -u, --user - the user name to connect as # | arg: -p, --password - the user password # | arg: -d, --database - the database to connect to +# +# Requires YunoHost version 2.2.4 or higher. ynh_mysql_connect_as() { # Declare an array to define the options of this helper. local legacy_args=upd @@ -30,6 +32,8 @@ ynh_mysql_connect_as() { # usage: ynh_mysql_execute_as_root --sql=sql [--database=database] # | arg: -s, --sql - the SQL command to execute # | arg: -d, --database - the database to connect to +# +# Requires YunoHost version 2.2.4 or higher. ynh_mysql_execute_as_root() { # Declare an array to define the options of this helper. local legacy_args=sd @@ -49,6 +53,8 @@ ynh_mysql_execute_as_root() { # usage: ynh_mysql_execute_file_as_root --file=file [--database=database] # | arg: -f, --file - the file containing SQL commands # | arg: -d, --database - the database to connect to +# +# Requires YunoHost version 2.2.4 or higher. ynh_mysql_execute_file_as_root() { # Declare an array to define the options of this helper. local legacy_args=fd @@ -71,6 +77,8 @@ ynh_mysql_execute_file_as_root() { # | arg: db - the database name to create # | arg: user - the user to grant privilegies # | arg: pwd - the password to identify user by +# +# Requires YunoHost version 2.2.4 or higher. ynh_mysql_create_db() { local db=$1 @@ -95,6 +103,8 @@ ynh_mysql_create_db() { # # usage: ynh_mysql_drop_db db # | arg: db - the database name to drop +# +# Requires YunoHost version 2.2.4 or higher. ynh_mysql_drop_db() { ynh_mysql_execute_as_root --sql="DROP DATABASE ${1};" } @@ -106,6 +116,8 @@ ynh_mysql_drop_db() { # usage: ynh_mysql_dump_db --database=database # | arg: -d, --database - the database name to dump # | ret: the mysqldump output +# +# Requires YunoHost version 2.2.4 or higher. ynh_mysql_dump_db() { # Declare an array to define the options of this helper. local legacy_args=d @@ -124,6 +136,8 @@ ynh_mysql_dump_db() { # usage: ynh_mysql_create_user user pwd [host] # | arg: user - the user name to create # | arg: pwd - the password to identify user by +# +# Requires YunoHost version 2.2.4 or higher. ynh_mysql_create_user() { ynh_mysql_execute_as_root \ --sql="CREATE USER '${1}'@'localhost' IDENTIFIED BY '${2}';" @@ -133,6 +147,8 @@ ynh_mysql_create_user() { # # usage: ynh_mysql_user_exists --user=user # | arg: -u, --user - the user for which to check existence +# +# Requires YunoHost version 2.2.4 or higher. ynh_mysql_user_exists() { # Declare an array to define the options of this helper. @@ -156,6 +172,8 @@ ynh_mysql_user_exists() # # usage: ynh_mysql_drop_user user # | arg: user - the user name to drop +# +# Requires YunoHost version 2.2.4 or higher. ynh_mysql_drop_user() { ynh_mysql_execute_as_root --sql="DROP USER '${1}'@'localhost';" } @@ -168,7 +186,9 @@ ynh_mysql_drop_user() { # usage: ynh_mysql_setup_db --db_user=user --db_name=name [--db_pwd=pwd] # | arg: -u, --db_user - Owner of the database # | arg: -n, --db_name - Name of the database -# | arg: -p, --db_pwd - Password of the database. If not given, a password will be generated +# | arg: -p, --db_pwd - Password of the database. If not provided, a password will be generated +# +# Requires YunoHost version 2.6.4 or higher. ynh_mysql_setup_db () { # Declare an array to define the options of this helper. local legacy_args=unp @@ -180,7 +200,7 @@ ynh_mysql_setup_db () { ynh_handle_getopts_args "$@" local new_db_pwd=$(ynh_string_random) # Generate a random password - # If $db_pwd is not given, use new_db_pwd instead for db_pwd + # If $db_pwd is not provided, use new_db_pwd instead for db_pwd db_pwd="${db_pwd:-$new_db_pwd}" ynh_mysql_create_db "$db_name" "$db_user" "$db_pwd" # Create the database @@ -192,6 +212,8 @@ ynh_mysql_setup_db () { # usage: ynh_mysql_remove_db --db_user=user --db_name=name # | arg: -u, --db_user - Owner of the database # | arg: -n, --db_name - Name of the database +# +# Requires YunoHost version 2.6.4 or higher. ynh_mysql_remove_db () { # Declare an array to define the options of this helper. local legacy_args=un @@ -203,10 +225,10 @@ ynh_mysql_remove_db () { local mysql_root_password=$(sudo cat $MYSQL_ROOT_PWD_FILE) if mysqlshow -u root -p$mysql_root_password | grep -q "^| $db_name"; then # Check if the database exists - echo "Removing database $db_name" >&2 + ynh_print_info --message="Removing database $db_name" ynh_mysql_drop_db $db_name # Remove the database else - echo "Database $db_name not found" >&2 + ynh_print_warn --message="Database $db_name not found" fi # Remove mysql user if it exists @@ -215,22 +237,3 @@ ynh_mysql_remove_db () { fi } -# Sanitize a string intended to be the name of a database -# (More specifically : replace - and . by _) -# -# example: dbname=$(ynh_sanitize_dbid $app) -# -# usage: ynh_sanitize_dbid --db_name=name -# | arg: -n, --db_name - name to correct/sanitize -# | ret: the corrected name -ynh_sanitize_dbid () { - # Declare an array to define the options of this helper. - local legacy_args=n - declare -Ar args_array=( [n]=db_name= ) - local db_name - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - - # We should avoid having - and . in the name of databases. They are replaced by _ - echo ${db_name//[-.]/_} -} diff --git a/data/helpers.d/network b/data/helpers.d/network index a765d6346..0f75cb165 100644 --- a/data/helpers.d/network +++ b/data/helpers.d/network @@ -1,41 +1,13 @@ #!/bin/bash -# Normalize the url path syntax -# Handle the slash at the beginning of path and its absence at ending -# Return a normalized url path -# -# example: url_path=$(ynh_normalize_url_path $url_path) -# ynh_normalize_url_path example -> /example -# ynh_normalize_url_path /example -> /example -# ynh_normalize_url_path /example/ -> /example -# ynh_normalize_url_path / -> / -# -# usage: ynh_normalize_url_path --path_url=path_to_normalize -# | arg: -p, --path_url - URL path to normalize before using it -ynh_normalize_url_path () { - # Declare an array to define the options of this helper. - local legacy_args=p - declare -Ar args_array=( [p]=path_url= ) - local path_url - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - - test -n "$path_url" || ynh_die --message="ynh_normalize_url_path expect a URL path as first argument and received nothing." - if [ "${path_url:0:1}" != "/" ]; then # If the first character is not a / - path_url="/$path_url" # Add / at begin of path variable - fi - if [ "${path_url:${#path_url}-1}" == "/" ] && [ ${#path_url} -gt 1 ]; then # If the last character is a / and that not the only character. - path_url="${path_url:0:${#path_url}-1}" # Delete the last character - fi - echo $path_url -} - # Find a free port and return it # # example: port=$(ynh_find_port --port=8080) # # usage: ynh_find_port --port=begin_port # | arg: -p, --port - port to start to search +# +# Requires YunoHost version 2.6.4 or higher. ynh_find_port () { # Declare an array to define the options of this helper. local legacy_args=p @@ -52,42 +24,77 @@ ynh_find_port () { echo $port } -# Check availability of a web path +# Validate an IP address # -# example: ynh_webpath_available --domain=some.domain.tld --path_url=/coffee +# usage: ynh_validate_ip --family=family --ip_address=ip_address +# | ret: 0 for valid ip addresses, 1 otherwise # -# usage: ynh_webpath_available --domain=domain --path_url=path -# | arg: -d, --domain - the domain/host of the url -# | arg: -p, --path_url - the web path to check the availability of -ynh_webpath_available () { - # Declare an array to define the options of this helper. - local legacy_args=dp - declare -Ar args_array=( [d]=domain= [p]=path_url= ) - local domain - local path_url - # Manage arguments with getopts - ynh_handle_getopts_args "$@" +# example: ynh_validate_ip 4 111.222.333.444 +# +# Requires YunoHost version 2.2.4 or higher. +ynh_validate_ip() +{ + # http://stackoverflow.com/questions/319279/how-to-validate-ip-address-in-python#319298 - sudo yunohost domain url-available $domain $path_url + # Declare an array to define the options of this helper. + local legacy_args=fi + declare -Ar args_array=( [f]=family= [i]=ip_address= ) + local family + local ip_address + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + [ "$family" == "4" ] || [ "$family" == "6" ] || return 1 + + python /dev/stdin << EOF +import socket +import sys +family = { "4" : socket.AF_INET, "6" : socket.AF_INET6 } +try: + socket.inet_pton(family["$family"], "$ip_address") +except socket.error: + sys.exit(1) +sys.exit(0) +EOF } -# Register/book a web path for an app +# Validate an IPv4 address # -# example: ynh_webpath_register --app=wordpress --domain=some.domain.tld --path_url=/coffee +# example: ynh_validate_ip4 111.222.333.444 # -# usage: ynh_webpath_register --app=app --domain=domain --path_url=path -# | arg: -a, --app - the app for which the domain should be registered -# | arg: -d, --domain - the domain/host of the web path -# | arg: -p, --path_url - the web path to be registered -ynh_webpath_register () { - # Declare an array to define the options of this helper. - local legacy_args=adp - declare -Ar args_array=( [a]=app= [d]=domain= [p]=path_url= ) - local app - local domain - local path_url - # Manage arguments with getopts - ynh_handle_getopts_args "$@" +# usage: ynh_validate_ip4 --ip_address=ip_address +# | ret: 0 for valid ipv4 addresses, 1 otherwise +# +# Requires YunoHost version 2.2.4 or higher. +ynh_validate_ip4() +{ + # Declare an array to define the options of this helper. + local legacy_args=i + declare -Ar args_array=( [i]=ip_address= ) + local ip_address + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - sudo yunohost app register-url $app $domain $path_url + ynh_validate_ip 4 $ip_address +} + + +# Validate an IPv6 address +# +# example: ynh_validate_ip6 2000:dead:beef::1 +# +# usage: ynh_validate_ip6 --ip_address=ip_address +# | ret: 0 for valid ipv6 addresses, 1 otherwise +# +# Requires YunoHost version 2.2.4 or higher. +ynh_validate_ip6() +{ + # Declare an array to define the options of this helper. + local legacy_args=i + declare -Ar args_array=( [i]=ip_address= ) + local ip_address + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + ynh_validate_ip 6 $ip_address } diff --git a/data/helpers.d/nginx b/data/helpers.d/nginx new file mode 100644 index 000000000..ce6b61d3c --- /dev/null +++ b/data/helpers.d/nginx @@ -0,0 +1,76 @@ +#!/bin/bash + +# Create a dedicated nginx config +# +# usage: ynh_add_nginx_config "list of others variables to replace" +# +# | arg: list - (Optional) list of others variables to replace separated by spaces. For example : 'path_2 port_2 ...' +# +# This will use a template in ../conf/nginx.conf +# __PATH__ by $path_url +# __DOMAIN__ by $domain +# __PORT__ by $port +# __NAME__ by $app +# __FINALPATH__ by $final_path +# +# And dynamic variables (from the last example) : +# __PATH_2__ by $path_2 +# __PORT_2__ by $port_2 +# +# Requires YunoHost version 2.7.2 or higher. +ynh_add_nginx_config () { + finalnginxconf="/etc/nginx/conf.d/$domain.d/$app.conf" + local others_var=${1:-} + ynh_backup_if_checksum_is_different --file="$finalnginxconf" + sudo cp ../conf/nginx.conf "$finalnginxconf" + + # To avoid a break by set -u, use a void substitution ${var:-}. If the variable is not set, it's simply set with an empty variable. + # Substitute in a nginx config file only if the variable is not empty + if test -n "${path_url:-}"; then + # path_url_slash_less is path_url, or a blank value if path_url is only '/' + local path_url_slash_less=${path_url%/} + ynh_replace_string --match_string="__PATH__/" --replace_string="$path_url_slash_less/" --target_file="$finalnginxconf" + ynh_replace_string --match_string="__PATH__" --replace_string="$path_url" --target_file="$finalnginxconf" + fi + if test -n "${domain:-}"; then + ynh_replace_string --match_string="__DOMAIN__" --replace_string="$domain" --target_file="$finalnginxconf" + fi + if test -n "${port:-}"; then + ynh_replace_string --match_string="__PORT__" --replace_string="$port" --target_file="$finalnginxconf" + fi + if test -n "${app:-}"; then + ynh_replace_string --match_string="__NAME__" --replace_string="$app" --target_file="$finalnginxconf" + fi + if test -n "${final_path:-}"; then + ynh_replace_string --match_string="__FINALPATH__" --replace_string="$final_path" --target_file="$finalnginxconf" + fi + + # Replace all other variable given as arguments + for var_to_replace in $others_var + do + # ${var_to_replace^^} make the content of the variable on upper-cases + # ${!var_to_replace} get the content of the variable named $var_to_replace + ynh_replace_string --match_string="__${var_to_replace^^}__" --replace_string="${!var_to_replace}" --target_file="$finalnginxconf" + done + + if [ "${path_url:-}" != "/" ] + then + ynh_replace_string --match_string="^#sub_path_only" --replace_string="" --target_file="$finalnginxconf" + else + ynh_replace_string --match_string="^#root_path_only" --replace_string="" --target_file="$finalnginxconf" + fi + + ynh_store_file_checksum --file="$finalnginxconf" + + ynh_systemd_action --service_name=nginx --action=reload +} + +# Remove the dedicated nginx config +# +# usage: ynh_remove_nginx_config +# +# Requires YunoHost version 2.7.2 or higher. +ynh_remove_nginx_config () { + ynh_secure_remove --file="/etc/nginx/conf.d/$domain.d/$app.conf" + ynh_systemd_action --service_name=nginx --action=reload +} diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index 098ed4410..9295c4348 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -10,8 +10,10 @@ export N_PREFIX="$n_install_dir" # [internal] # # usage: ynh_install_n +# +# Requires YunoHost version 2.7.12 or higher. ynh_install_n () { - echo "Installation of N - Node.js version management" >&2 + ynh_print_info --message="Installation of N - Node.js version management" # Build an app.src for n mkdir -p "../conf" echo "SOURCE_URL=https://github.com/tj/n/archive/v2.1.7.tar.gz @@ -36,6 +38,8 @@ SOURCE_SUM=2ba3c9d4dd3c7e38885b37e02337906a1ee91febe6d5c9159d89a9050f2eea8f" > " # That's means it has to be added to any systemd script. # # usage: ynh_use_nodejs +# +# Requires YunoHost version 2.7.12 or higher. ynh_use_nodejs () { nodejs_version=$(ynh_app_setting_get --app=$app --key=nodejs_version) @@ -59,6 +63,8 @@ ynh_use_nodejs () { # | arg: -n, --nodejs_version - Version of node to install. # If possible, prefer to use major version number (e.g. 8 instead of 8.10.0). # The crontab will handle the update of minor versions when needed. +# +# Requires YunoHost version 2.7.12 or higher. ynh_install_nodejs () { # Use n, https://github.com/tj/n to manage the nodejs versions @@ -117,7 +123,7 @@ ynh_install_nodejs () { fi # Store the ID of this app and the version of node requested for it - echo "$YNH_APP_ID:$nodejs_version" | tee --append "$n_install_dir/ynh_app_version" + echo "$YNH_APP_INSTANCE_NAME:$nodejs_version" | tee --append "$n_install_dir/ynh_app_version" # Store nodejs_version into the config of this app ynh_app_setting_set --app=$app --key=nodejs_version --value=$nodejs_version @@ -135,11 +141,13 @@ ynh_install_nodejs () { # If no other app uses node, n will be also removed. # # usage: ynh_remove_nodejs +# +# Requires YunoHost version 2.7.12 or higher. ynh_remove_nodejs () { nodejs_version=$(ynh_app_setting_get --app=$app --key=nodejs_version) # Remove the line for this app - sed --in-place "/$YNH_APP_ID:$nodejs_version/d" "$n_install_dir/ynh_app_version" + sed --in-place "/$YNH_APP_INSTANCE_NAME:$nodejs_version/d" "$n_install_dir/ynh_app_version" # If no other app uses this version of nodejs, remove it. if ! grep --quiet "$nodejs_version" "$n_install_dir/ynh_app_version" @@ -164,6 +172,8 @@ ynh_remove_nodejs () { # This cron will check and update all minor node versions used by your apps. # # usage: ynh_cron_upgrade_node +# +# Requires YunoHost version 2.7.12 or higher. ynh_cron_upgrade_node () { # Build the update script cat > "$n_install_dir/node_update.sh" << EOF diff --git a/data/helpers.d/php b/data/helpers.d/php new file mode 100644 index 000000000..c9e3ba9ed --- /dev/null +++ b/data/helpers.d/php @@ -0,0 +1,67 @@ +#!/bin/bash + +# Create a dedicated php-fpm config +# +# usage: ynh_add_fpm_config [--phpversion=7.X] +# | arg: -v, --phpversion - Version of php to use. +# +# Requires YunoHost version 2.7.2 or higher. +ynh_add_fpm_config () { + # Declare an array to define the options of this helper. + local legacy_args=v + declare -Ar args_array=( [v]=phpversion= ) + local phpversion + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + # Configure PHP-FPM 7.0 by default + phpversion="${phpversion:-7.0}" + + local fpm_config_dir="/etc/php/$phpversion/fpm" + local fpm_service="php${phpversion}-fpm" + # Configure PHP-FPM 5 on Debian Jessie + if [ "$(ynh_get_debian_release)" == "jessie" ]; then + fpm_config_dir="/etc/php5/fpm" + fpm_service="php5-fpm" + fi + ynh_app_setting_set --app=$app --key=fpm_config_dir --value="$fpm_config_dir" + ynh_app_setting_set --app=$app --key=fpm_service --value="$fpm_service" + finalphpconf="$fpm_config_dir/pool.d/$app.conf" + ynh_backup_if_checksum_is_different --file="$finalphpconf" + sudo cp ../conf/php-fpm.conf "$finalphpconf" + ynh_replace_string --match_string="__NAMETOCHANGE__" --replace_string="$app" --target_file="$finalphpconf" + ynh_replace_string --match_string="__FINALPATH__" --replace_string="$final_path" --target_file="$finalphpconf" + ynh_replace_string --match_string="__USER__" --replace_string="$app" --target_file="$finalphpconf" + ynh_replace_string --match_string="__PHPVERSION__" --replace_string="$phpversion" --target_file="$finalphpconf" + sudo chown root: "$finalphpconf" + ynh_store_file_checksum --file="$finalphpconf" + + if [ -e "../conf/php-fpm.ini" ] + then + echo "Packagers ! Please do not use a separate php ini file, merge your directives in the pool file instead." >&2 + finalphpini="$fpm_config_dir/conf.d/20-$app.ini" + ynh_backup_if_checksum_is_different "$finalphpini" + sudo cp ../conf/php-fpm.ini "$finalphpini" + sudo chown root: "$finalphpini" + ynh_store_file_checksum "$finalphpini" + fi + ynh_systemd_action --service_name=$fpm_service --action=reload +} + +# Remove the dedicated php-fpm config +# +# usage: ynh_remove_fpm_config +# +# Requires YunoHost version 2.7.2 or higher. +ynh_remove_fpm_config () { + local fpm_config_dir=$(ynh_app_setting_get --app=$app --key=fpm_config_dir) + local fpm_service=$(ynh_app_setting_get --app=$app --key=fpm_service) + # Assume php version 7 if not set + if [ -z "$fpm_config_dir" ]; then + fpm_config_dir="/etc/php/7.0/fpm" + fpm_service="php7.0-fpm" + fi + ynh_secure_remove --file="$fpm_config_dir/pool.d/$app.conf" + ynh_secure_remove --file="$fpm_config_dir/conf.d/20-$app.ini" 2>&1 + ynh_systemd_action --service_name=$fpm_service --action=reload +} diff --git a/data/helpers.d/postgresql b/data/helpers.d/postgresql new file mode 100644 index 000000000..8e3297458 --- /dev/null +++ b/data/helpers.d/postgresql @@ -0,0 +1,299 @@ +#!/bin/bash + +PSQL_ROOT_PWD_FILE=/etc/yunohost/psql + +# Open a connection as a user +# +# example: ynh_psql_connect_as 'user' 'pass' <<< "UPDATE ...;" +# example: ynh_psql_connect_as 'user' 'pass' < /path/to/file.sql +# +# usage: ynh_psql_connect_as --user=user --password=password [--database=database] +# | arg: -u, --user - the user name to connect as +# | arg: -p, --password - the user password +# | arg: -d, --database - the database to connect to +# +# Requires YunoHost version 3.?.? or higher. +ynh_psql_connect_as() { + # Declare an array to define the options of this helper. + local legacy_args=upd + declare -Ar args_array=([u]=user= [p]=password= [d]=database=) + local user + local password + local database + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + database="${database:-}" + + sudo --login --user=postgres PGUSER="$user" PGPASSWORD="$password" psql "$database" +} + +# Execute a command as root user +# +# usage: ynh_psql_execute_as_root --sql=sql [--database=database] +# | arg: -s, --sql - the SQL command to execute +# | arg: -d, --database - the database to connect to +# +# Requires YunoHost version 3.?.? or higher. +ynh_psql_execute_as_root() { + # Declare an array to define the options of this helper. + local legacy_args=sd + declare -Ar args_array=([s]=sql= [d]=database=) + local sql + local database + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + database="${database:-}" + + ynh_psql_connect_as --user="postgres" --password="$(sudo cat $PSQL_ROOT_PWD_FILE)" \ + --database="$database" <<<"$sql" +} + +# Execute a command from a file as root user +# +# usage: ynh_psql_execute_file_as_root --file=file [--database=database] +# | arg: -f, --file - the file containing SQL commands +# | arg: -d, --database - the database to connect to +# +# Requires YunoHost version 3.?.? or higher. +ynh_psql_execute_file_as_root() { + # Declare an array to define the options of this helper. + local legacy_args=fd + declare -Ar args_array=([f]=file= [d]=database=) + local file + local database + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + database="${database:-}" + + ynh_psql_connect_as --user="postgres" --password="$(sudo cat $PSQL_ROOT_PWD_FILE)" \ + --database="$database" <"$file" +} + +# Create a database and grant optionnaly privilegies to a user +# +# [internal] +# +# usage: ynh_psql_create_db db [user] +# | arg: db - the database name to create +# | arg: user - the user to grant privilegies +# +# Requires YunoHost version 3.?.? or higher. +ynh_psql_create_db() { + local db=$1 + local user=${2:-} + + local sql="CREATE DATABASE ${db};" + + # grant all privilegies to user + if [ -n "$user" ]; then + sql+="GRANT ALL PRIVILEGES ON DATABASE ${db} TO ${user} WITH GRANT OPTION;" + fi + + ynh_psql_execute_as_root --sql="$sql" +} + +# Drop a database +# +# [internal] +# +# If you intend to drop the database *and* the associated user, +# consider using ynh_psql_remove_db instead. +# +# usage: ynh_psql_drop_db db +# | arg: db - the database name to drop +# +# Requires YunoHost version 3.?.? or higher. +ynh_psql_drop_db() { + local db=$1 + # First, force disconnection of all clients connected to the database + # https://stackoverflow.com/questions/5408156/how-to-drop-a-postgresql-database-if-there-are-active-connections-to-it + # https://dba.stackexchange.com/questions/16426/how-to-drop-all-connections-to-a-specific-database-without-stopping-the-server + ynh_psql_execute_as_root --sql="SELECT pg_terminate_backend (pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = '$db';" --database="$db" + sudo --login --user=postgres dropdb $db +} + +# Dump a database +# +# example: ynh_psql_dump_db 'roundcube' > ./dump.sql +# +# usage: ynh_psql_dump_db --database=database +# | arg: -d, --database - the database name to dump +# | ret: the psqldump output +# +# Requires YunoHost version 3.?.? or higher. +ynh_psql_dump_db() { + # Declare an array to define the options of this helper. + local legacy_args=d + declare -Ar args_array=([d]=database=) + local database + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + sudo --login --user=postgres pg_dump "$database" +} + +# Create a user +# +# [internal] +# +# usage: ynh_psql_create_user user pwd +# | arg: user - the user name to create +# | arg: pwd - the password to identify user by +# +# Requires YunoHost version 3.?.? or higher. +ynh_psql_create_user() { + local user=$1 + local pwd=$2 + ynh_psql_execute_as_root --sql="CREATE USER $user WITH ENCRYPTED PASSWORD '$pwd'" +} + +# Check if a psql user exists +# +# usage: ynh_psql_user_exists --user=user +# | arg: -u, --user - the user for which to check existence +ynh_psql_user_exists() { + # Declare an array to define the options of this helper. + local legacy_args=u + declare -Ar args_array=([u]=user=) + local user + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + if ! sudo --login --user=postgres PGUSER="postgres" PGPASSWORD="$(sudo cat $PSQL_ROOT_PWD_FILE)" psql -tAc "SELECT rolname FROM pg_roles WHERE rolname='$user';" | grep --quiet "$user" ; then + return 1 + else + return 0 + fi +} + +# Check if a psql database exists +# +# usage: ynh_psql_database_exists --database=database +# | arg: -d, --database - the database for which to check existence +ynh_psql_database_exists() { + # Declare an array to define the options of this helper. + local legacy_args=d + declare -Ar args_array=([d]=database=) + local database + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + if ! sudo --login --user=postgres PGUSER="postgres" PGPASSWORD="$(sudo cat $PSQL_ROOT_PWD_FILE)" psql -tAc "SELECT datname FROM pg_database WHERE datname='$database';" | grep --quiet "$database"; then + return 1 + else + return 0 + fi +} + +# Drop a user +# +# [internal] +# +# usage: ynh_psql_drop_user user +# | arg: user - the user name to drop +# +# Requires YunoHost version 3.?.? or higher. +ynh_psql_drop_user() { + ynh_psql_execute_as_root --sql="DROP USER ${1};" +} + +# Create a database, an user and its password. Then store the password in the app's config +# +# After executing this helper, the password of the created database will be available in $db_pwd +# It will also be stored as "psqlpwd" into the app settings. +# +# usage: ynh_psql_setup_db --db_user=user --db_name=name [--db_pwd=pwd] +# | arg: -u, --db_user - Owner of the database +# | arg: -n, --db_name - Name of the database +# | arg: -p, --db_pwd - Password of the database. If not given, a password will be generated +ynh_psql_setup_db() { + # Declare an array to define the options of this helper. + local legacy_args=unp + declare -Ar args_array=([u]=db_user= [n]=db_name= [p]=db_pwd=) + local db_user + local db_name + db_pwd="" + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + local new_db_pwd=$(ynh_string_random) # Generate a random password + # If $db_pwd is not given, use new_db_pwd instead for db_pwd + db_pwd="${db_pwd:-$new_db_pwd}" + + if ! ynh_psql_user_exists --user=$db_user; then + ynh_psql_create_user "$db_user" "$db_pwd" + fi + + ynh_psql_create_db "$db_name" "$db_user" # Create the database + ynh_app_setting_set --app=$app --key=psqlpwd --value=$db_pwd # Store the password in the app's config +} + +# Remove a database if it exists, and the associated user +# +# usage: ynh_psql_remove_db --db_user=user --db_name=name +# | arg: -u, --db_user - Owner of the database +# | arg: -n, --db_name - Name of the database +ynh_psql_remove_db() { + # Declare an array to define the options of this helper. + local legacy_args=un + declare -Ar args_array=([u]=db_user= [n]=db_name=) + local db_user + local db_name + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + local psql_root_password=$(sudo cat $PSQL_ROOT_PWD_FILE) + if ynh_psql_database_exists --database=$db_name; then # Check if the database exists + ynh_print_info --message="Removing database $db_name" + ynh_psql_drop_db $db_name # Remove the database + else + ynh_print_warn --message="Database $db_name not found" + fi + + # Remove psql user if it exists + if ynh_psql_user_exists --user=$db_user; then + ynh_print_info --message="Removing user $db_user" + ynh_psql_drop_user $db_user + else + ynh_print_warn --message="User $db_user not found" + fi +} + +# Create a master password and set up global settings +# Please always call this script in install and restore scripts +# +# usage: ynh_psql_test_if_first_run +ynh_psql_test_if_first_run() { + if [ -f "$PSQL_ROOT_PWD_FILE" ]; then + echo "PostgreSQL is already installed, no need to create master password" + else + local pgsql="$(ynh_string_random)" + echo "$pgsql" >/etc/yunohost/psql + + if [ -e /etc/postgresql/9.4/ ]; then + local pg_hba=/etc/postgresql/9.4/main/pg_hba.conf + local logfile=/var/log/postgresql/postgresql-9.4-main.log + elif [ -e /etc/postgresql/9.6/ ]; then + local pg_hba=/etc/postgresql/9.6/main/pg_hba.conf + local logfile=/var/log/postgresql/postgresql-9.6-main.log + else + ynh_die "postgresql shoud be 9.4 or 9.6" + fi + + ynh_systemd_action --service_name=postgresql --action=start + + sudo --login --user=postgres psql -c"ALTER user postgres WITH PASSWORD '$pgsql'" postgres + + # force all user to connect to local database using passwords + # https://www.postgresql.org/docs/current/static/auth-pg-hba-conf.html#EXAMPLE-PG-HBA.CONF + # Note: we can't use peer since YunoHost create users with nologin + # See: https://github.com/YunoHost/yunohost/blob/unstable/data/helpers.d/user + ynh_replace_string --match_string="local\(\s*\)all\(\s*\)all\(\s*\)peer" --replace_string="local\1all\2all\3password" --target_file="$pg_hba" + + # Advertise service in admin panel + yunohost service add postgresql --log "$logfile" + + systemctl enable postgresql + ynh_systemd_action --service_name=postgresql --action=reload + fi +} diff --git a/data/helpers.d/psql b/data/helpers.d/psql deleted file mode 100644 index 2ef13482a..000000000 --- a/data/helpers.d/psql +++ /dev/null @@ -1,148 +0,0 @@ -# Create a master password and set up global settings -# Please always call this script in install and restore scripts -# -# usage: ynh_psql_test_if_first_run -ynh_psql_test_if_first_run() { - if [ -f /etc/yunohost/psql ]; - then - echo "PostgreSQL is already installed, no need to create master password" - else - local pgsql="$(ynh_string_random)" - echo "$pgsql" > /etc/yunohost/psql - - if [ -e /etc/postgresql/9.4/ ] - then - local pg_hba=/etc/postgresql/9.4/main/pg_hba.conf - elif [ -e /etc/postgresql/9.6/ ] - then - local pg_hba=/etc/postgresql/9.6/main/pg_hba.conf - else - ynh_die "postgresql shoud be 9.4 or 9.6" - fi - - systemctl start postgresql - sudo --login --user=postgres psql -c"ALTER user postgres WITH PASSWORD '$pgsql'" postgres - - # force all user to connect to local database using passwords - # https://www.postgresql.org/docs/current/static/auth-pg-hba-conf.html#EXAMPLE-PG-HBA.CONF - # Note: we can't use peer since YunoHost create users with nologin - # See: https://github.com/YunoHost/yunohost/blob/unstable/data/helpers.d/user - sed -i '/local\s*all\s*all\s*peer/i \ - local all all password' "$pg_hba" - systemctl enable postgresql - systemctl reload postgresql - fi -} - -# Open a connection as a user -# -# example: ynh_psql_connect_as 'user' 'pass' <<< "UPDATE ...;" -# example: ynh_psql_connect_as 'user' 'pass' < /path/to/file.sql -# -# usage: ynh_psql_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_psql_connect_as() { - local user="$1" - local pwd="$2" - local db="$3" - sudo --login --user=postgres PGUSER="$user" PGPASSWORD="$pwd" psql "$db" -} - -# # Execute a command as root user -# -# usage: ynh_psql_execute_as_root sql [db] -# | arg: sql - the SQL command to execute -ynh_psql_execute_as_root () { - local sql="$1" - sudo --login --user=postgres psql <<< "$sql" -} - -# Execute a command from a file as root user -# -# usage: ynh_psql_execute_file_as_root file [db] -# | arg: file - the file containing SQL commands -# | arg: db - the database to connect to -ynh_psql_execute_file_as_root() { - local file="$1" - local db="$2" - sudo --login --user=postgres psql "$db" < "$file" -} - -# Create a database, an user and its password. Then store the password in the app's config -# -# After executing this helper, the password of the created database will be available in $db_pwd -# It will also be stored as "psqlpwd" into the app settings. -# -# usage: ynh_psql_setup_db user name [pwd] -# | arg: user - Owner of the database -# | arg: name - Name of the database -# | arg: pwd - Password of the database. If not given, a password will be generated -ynh_psql_setup_db () { - local db_user="$1" - local db_name="$2" - local new_db_pwd=$(ynh_string_random) # Generate a random password - # If $3 is not given, use new_db_pwd instead for db_pwd. - local db_pwd="${3:-$new_db_pwd}" - ynh_psql_create_db "$db_name" "$db_user" "$db_pwd" # Create the database - ynh_app_setting_set "$app" psqlpwd "$db_pwd" # Store the password in the app's config -} - -# Create a database and grant privilegies to a user -# -# usage: ynh_psql_create_db db [user [pwd]] -# | arg: db - the database name to create -# | arg: user - the user to grant privilegies -# | arg: pwd - the user password -ynh_psql_create_db() { - local db="$1" - local user="$2" - local pwd="$3" - ynh_psql_create_user "$user" "$pwd" - sudo --login --user=postgres createdb --owner="$user" "$db" -} - -# Drop a database -# -# usage: ynh_psql_drop_db db -# | arg: db - the database name to drop -# | arg: user - the user to drop -ynh_psql_remove_db() { - local db="$1" - local user="$2" - sudo --login --user=postgres dropdb "$db" - ynh_psql_drop_user "$user" -} - -# Dump a database -# -# example: ynh_psql_dump_db 'roundcube' > ./dump.sql -# -# usage: ynh_psql_dump_db db -# | arg: db - the database name to dump -# | ret: the psqldump output -ynh_psql_dump_db() { - local db="$1" - sudo --login --user=postgres pg_dump "$db" -} - - -# Create a user -# -# usage: ynh_psql_create_user user pwd [host] -# | arg: user - the user name to create -ynh_psql_create_user() { - local user="$1" - local pwd="$2" - sudo --login --user=postgres psql -c"CREATE USER $user WITH PASSWORD '$pwd'" postgres -} - -# Drop a user -# -# usage: ynh_psql_drop_user user -# | arg: user - the user name to drop -ynh_psql_drop_user() { - local user="$1" - sudo --login --user=postgres dropuser "$user" -} diff --git a/data/helpers.d/setting b/data/helpers.d/setting index c9334c60a..9ac7efb23 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -5,6 +5,8 @@ # usage: ynh_app_setting_get --app=app --key=key # | arg: -a, --app - the application id # | arg: -k, --key - the setting to get +# +# Requires YunoHost version 2.2.4 or higher. ynh_app_setting_get() { # Declare an array to define the options of this helper. local legacy_args=ak @@ -14,7 +16,7 @@ ynh_app_setting_get() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - sudo yunohost app setting "$app" "$key" --output-as plain --quiet + ynh_app_setting "get" "$app" "$key" } # Set an application setting @@ -23,6 +25,8 @@ ynh_app_setting_get() { # | arg: -a, --app - the application id # | arg: -k, --key - the setting name to set # | arg: -v, --value - the setting value to set +# +# Requires YunoHost version 2.2.4 or higher. ynh_app_setting_set() { # Declare an array to define the options of this helper. local legacy_args=akv @@ -33,7 +37,7 @@ ynh_app_setting_set() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - sudo yunohost app setting "$app" "$key" --value="$value" --quiet + ynh_app_setting "set" "$app" "$key" "$value" } # Delete an application setting @@ -41,6 +45,8 @@ ynh_app_setting_set() { # usage: ynh_app_setting_delete --app=app --key=key # | arg: -a, --app - the application id # | arg: -k, --key - the setting to delete +# +# Requires YunoHost version 2.2.4 or higher. ynh_app_setting_delete() { # Declare an array to define the options of this helper. local legacy_args=ak @@ -50,7 +56,84 @@ ynh_app_setting_delete() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - sudo yunohost app setting -d "$app" "$key" --quiet + ynh_app_setting "delete" "$app" "$key" +} + +# Small "hard-coded" interface to avoid calling "yunohost app" directly each +# time dealing with a setting is needed (which may be so slow on ARM boards) +# +# [internal] +# +ynh_app_setting() +{ + ACTION="$1" APP="$2" KEY="$3" VALUE="${4:-}" python - < /example +# ynh_normalize_url_path /example # -> /example +# ynh_normalize_url_path /example/ # -> /example +# ynh_normalize_url_path / # -> / +# +# usage: ynh_normalize_url_path --path_url=path_to_normalize +# | arg: -p, --path_url - URL path to normalize before using it +# +# Requires YunoHost version 2.6.4 or higher. +ynh_normalize_url_path () { + # Declare an array to define the options of this helper. + local legacy_args=p + declare -Ar args_array=( [p]=path_url= ) + local path_url + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + test -n "$path_url" || ynh_die --message="ynh_normalize_url_path expect a URL path as first argument and received nothing." + if [ "${path_url:0:1}" != "/" ]; then # If the first character is not a / + path_url="/$path_url" # Add / at begin of path variable + fi + if [ "${path_url:${#path_url}-1}" == "/" ] && [ ${#path_url} -gt 1 ]; then # If the last character is a / and that not the only character. + path_url="${path_url:0:${#path_url}-1}" # Delete the last character + fi + echo $path_url +} diff --git a/data/helpers.d/system b/data/helpers.d/system deleted file mode 100644 index 9a4219e11..000000000 --- a/data/helpers.d/system +++ /dev/null @@ -1,161 +0,0 @@ -#!/bin/bash - -# Manage a fail of the script -# -# [internal] -# -# usage: -# ynh_exit_properly is used only by the helper ynh_abort_if_errors. -# You should not use it directly. -# Instead, add to your script: -# ynh_clean_setup () { -# instructions... -# } -# -# This function provide a way to clean some residual of installation that not managed by remove script. -# -# It prints a warning to inform that the script was failed, and execute the ynh_clean_setup function if used in the app script -# -ynh_exit_properly () { - local exit_code=$? - if [ "$exit_code" -eq 0 ]; then - exit 0 # Exit without error if the script ended correctly - fi - - trap '' EXIT # Ignore new exit signals - set +eu # Do not exit anymore if a command fail or if a variable is empty - - echo -e "!!\n $app's script has encountered an error. Its execution was cancelled.\n!!" >&2 - - if type -t ynh_clean_setup > /dev/null; then # Check if the function exist in the app script. - ynh_clean_setup # Call the function to do specific cleaning for the app. - fi - - ynh_die # Exit with error status -} - -# Exits if an error occurs during the execution of the script. -# -# usage: ynh_abort_if_errors -# -# This configure the rest of the script execution such that, if an error occurs -# or if an empty variable is used, the execution of the script stops -# immediately and a call to `ynh_clean_setup` is triggered if it has been -# defined by your script. -# -ynh_abort_if_errors () { - set -eu # Exit if a command fail, and if a variable is used unset. - trap ynh_exit_properly EXIT # Capturing exit signals on shell script -} - -# Fetch the Debian release codename -# -# usage: ynh_get_debian_release -# | ret: The Debian release codename (i.e. jessie, stretch, ...) -ynh_get_debian_release () { - echo $(lsb_release --codename --short) -} - -# Read the value of a key in a ynh manifest file -# -# usage: ynh_read_manifest manifest key -# | arg: -m, --manifest= - Path of the manifest to read -# | arg: -k, --key= - Name of the key to find -ynh_read_manifest () { - # Declare an array to define the options of this helper. - declare -Ar args_array=( [m]=manifest= [k]=manifest_key= ) - local manifest - local manifest_key - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - - if [ ! -e "$manifest" ]; then - # If the manifest isn't found, try the common place for backup and restore script. - manifest="../settings/manifest.json" - fi - - jq ".$manifest_key" "$manifest" --raw-output -} - -# Read the upstream version from the manifest -# The version number in the manifest is defined by ~ynh -# For example : 4.3-2~ynh3 -# This include the number before ~ynh -# In the last example it return 4.3-2 -# -# usage: ynh_app_upstream_version [-m manifest] -# | arg: -m, --manifest= - Path of the manifest to read -ynh_app_upstream_version () { - declare -Ar args_array=( [m]=manifest= ) - local manifest - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - - manifest="${manifest:-../manifest.json}" - version_key=$(ynh_read_manifest --manifest="$manifest" --manifest_key="version") - echo "${version_key/~ynh*/}" -} - -# Read package version from the manifest -# The version number in the manifest is defined by ~ynh -# For example : 4.3-2~ynh3 -# This include the number after ~ynh -# In the last example it return 3 -# -# usage: ynh_app_package_version [-m manifest] -# | arg: -m, --manifest= - Path of the manifest to read -ynh_app_package_version () { - declare -Ar args_array=( [m]=manifest= ) - local manifest - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - - manifest="${manifest:-../manifest.json}" - version_key=$(ynh_read_manifest --manifest="$manifest" --manifest_key="version") - echo "${version_key/*~ynh/}" -} - -# Checks the app version to upgrade with the existing app version and returns: -# - UPGRADE_APP if the upstream app version has changed -# - UPGRADE_PACKAGE if only the YunoHost package has changed -# -## It stops the current script without error if the package is up-to-date -# -# This helper should be used to avoid an upgrade of an app, or the upstream part -# of it, when it's not needed -# -# To force an upgrade, even if the package is up to date, -# you have to set the variable YNH_FORCE_UPGRADE before. -# example: sudo YNH_FORCE_UPGRADE=1 yunohost app upgrade MyApp -# -# usage: ynh_check_app_version_changed -ynh_check_app_version_changed () { - local force_upgrade=${YNH_FORCE_UPGRADE:-0} - local package_check=${PACKAGE_CHECK_EXEC:-0} - - # By default, upstream app version has changed - local return_value="UPGRADE_APP" - - local current_version=$(ynh_read_manifest --manifest="/etc/yunohost/apps/$YNH_APP_INSTANCE_NAME/manifest.json" --manifest_key="version" || echo 1.0) - local current_upstream_version="$(ynh_app_upstream_version --manifest="/etc/yunohost/apps/$YNH_APP_INSTANCE_NAME/manifest.json")" - local update_version=$(ynh_read_manifest --manifest="../manifest.json" --manifest_key="version" || echo 1.0) - local update_upstream_version="$(ynh_app_upstream_version)" - - if [ "$current_version" == "$update_version" ] ; then - # Complete versions are the same - if [ "$force_upgrade" != "0" ] - then - echo "Upgrade forced by YNH_FORCE_UPGRADE." >&2 - unset YNH_FORCE_UPGRADE - elif [ "$package_check" != "0" ] - then - echo "Upgrade forced for package check." >&2 - else - ynh_die "Up-to-date, nothing to do" 0 - fi - elif [ "$current_upstream_version" == "$update_upstream_version" ] ; then - # Upstream versions are the same, only YunoHost package versions differ - return_value="UPGRADE_PACKAGE" - fi - echo $return_value -} diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd new file mode 100644 index 000000000..c4100bf8a --- /dev/null +++ b/data/helpers.d/systemd @@ -0,0 +1,179 @@ +#!/bin/bash + +# Create a dedicated systemd config +# +# usage: ynh_add_systemd_config [--service=service] [--template=template] +# | arg: -s, --service - Service name (optionnal, $app by default) +# | arg: -t, --template - Name of template file (optionnal, this is 'systemd' by default, meaning ./conf/systemd.service will be used as template) +# +# This will use the template ../conf/.service +# to generate a systemd config, by replacing the following keywords +# with global variables that should be defined before calling +# this helper : +# +# __APP__ by $app +# __FINALPATH__ by $final_path +# +# Requires YunoHost version 2.7.2 or higher. +ynh_add_systemd_config () { + # Declare an array to define the options of this helper. + local legacy_args=st + declare -Ar args_array=( [s]=service= [t]=template= ) + local service + local template + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + local service="${service:-$app}" + local template="${template:-systemd.service}" + + finalsystemdconf="/etc/systemd/system/$service.service" + ynh_backup_if_checksum_is_different --file="$finalsystemdconf" + sudo cp ../conf/$template "$finalsystemdconf" + + # To avoid a break by set -u, use a void substitution ${var:-}. If the variable is not set, it's simply set with an empty variable. + # Substitute in a nginx config file only if the variable is not empty + if test -n "${final_path:-}"; then + ynh_replace_string --match_string="__FINALPATH__" --replace_string="$final_path" --target_file="$finalsystemdconf" + fi + if test -n "${app:-}"; then + ynh_replace_string --match_string="__APP__" --replace_string="$app" --target_file="$finalsystemdconf" + fi + ynh_store_file_checksum --file="$finalsystemdconf" + + sudo chown root: "$finalsystemdconf" + sudo systemctl enable $service + sudo systemctl daemon-reload +} + +# Remove the dedicated systemd config +# +# usage: ynh_remove_systemd_config [--service=service] +# | arg: -s, --service - Service name (optionnal, $app by default) +# +# Requires YunoHost version 2.7.2 or higher. +ynh_remove_systemd_config () { + # Declare an array to define the options of this helper. + local legacy_args=s + declare -Ar args_array=( [s]=service= ) + local service + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + local service="${service:-$app}" + + local finalsystemdconf="/etc/systemd/system/$service.service" + if [ -e "$finalsystemdconf" ]; then + ynh_systemd_action --service_name=$service --action=stop + systemctl disable $service + ynh_secure_remove --file="$finalsystemdconf" + systemctl daemon-reload + fi +} + +# Start (or other actions) a service, print a log in case of failure and optionnaly wait until the service is completely started +# +# usage: ynh_systemd_action [-n service_name] [-a action] [ [-l "line to match"] [-p log_path] [-t timeout] [-e length] ] +# | arg: -n, --service_name= - Name of the service to start. Default : $app +# | arg: -a, --action= - Action to perform with systemctl. Default: start +# | arg: -l, --line_match= - Line to match - The line to find in the log to attest the service have finished to boot. +# If not defined it don't wait until the service is completely started. +# WARNING: When using --line_match, you should always add `ynh_clean_check_starting` into your +# `ynh_clean_setup` at the beginning of the script. Otherwise, tail will not stop in case of failure +# of the script. The script will then hang forever. +# | arg: -p, --log_path= - Log file - Path to the log file. Default : /var/log/$app/$app.log +# | arg: -t, --timeout= - Timeout - The maximum time to wait before ending the watching. Default : 300 seconds. +# | arg: -e, --length= - Length of the error log : Default : 20 +ynh_systemd_action() { + # Declare an array to define the options of this helper. + local legacy_args=nalpte + declare -Ar args_array=( [n]=service_name= [a]=action= [l]=line_match= [p]=log_path= [t]=timeout= [e]=length= ) + local service_name + local action + local line_match + local length + local log_path + local timeout + + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + local service_name="${service_name:-$app}" + local action=${action:-start} + local log_path="${log_path:-/var/log/$service_name/$service_name.log}" + local length=${length:-20} + local timeout=${timeout:-300} + + # Start to read the log + if [[ -n "${line_match:-}" ]] + then + local templog="$(mktemp)" + # Following the starting of the app in its log + if [ "$log_path" == "systemd" ] ; then + # Read the systemd journal + journalctl --unit=$service_name --follow --since=-0 --quiet > "$templog" & + # Get the PID of the journalctl command + local pid_tail=$! + else + # Read the specified log file + tail -F -n0 "$log_path" > "$templog" 2>&1 & + # Get the PID of the tail command + local pid_tail=$! + fi + fi + + ynh_print_info --message="${action^} the service $service_name" + + # Use reload-or-restart instead of reload. So it wouldn't fail if the service isn't running. + if [ "$action" == "reload" ]; then + action="reload-or-restart" + fi + + systemctl $action $service_name \ + || ( journalctl --no-pager --lines=$length -u $service_name >&2 \ + ; test -e "$log_path" && echo "--" >&2 && tail --lines=$length "$log_path" >&2 \ + ; false ) + + # Start the timeout and try to find line_match + if [[ -n "${line_match:-}" ]] + then + local i=0 + for i in $(seq 1 $timeout) + do + # Read the log until the sentence is found, that means the app finished to start. Or run until the timeout + if grep --quiet "$line_match" "$templog" + then + ynh_print_info --message="The service $service_name has correctly started." + break + fi + if [ $i -eq 3 ]; then + echo -n "Please wait, the service $service_name is ${action}ing" >&2 + fi + if [ $i -ge 3 ]; then + echo -n "." >&2 + fi + sleep 1 + done + if [ $i -ge 3 ]; then + echo "" >&2 + fi + if [ $i -eq $timeout ] + then + ynh_print_warn --message="The service $service_name didn't fully started before the timeout." + ynh_print_warn --message="Please find here an extract of the end of the log of the service $service_name:" + journalctl --no-pager --lines=$length -u $service_name >&2 + test -e "$log_path" && echo "--" >&2 && tail --lines=$length "$log_path" >&2 + fi + ynh_clean_check_starting + fi +} + +# Clean temporary process and file used by ynh_check_starting +# (usually used in ynh_clean_setup scripts) +# +# usage: ynh_clean_check_starting +ynh_clean_check_starting () { + # Stop the execution of tail. + kill -s 15 $pid_tail 2>&1 + ynh_secure_remove "$templog" 2>&1 +} + + diff --git a/data/helpers.d/user b/data/helpers.d/user index d716bf03b..0c4591dcd 100644 --- a/data/helpers.d/user +++ b/data/helpers.d/user @@ -6,6 +6,8 @@ # # usage: ynh_user_exists --username=username # | arg: -u, --username - the username to check +# +# Requires YunoHost version 2.2.4 or higher. ynh_user_exists() { # Declare an array to define the options of this helper. local legacy_args=u @@ -25,6 +27,8 @@ ynh_user_exists() { # | arg: -u, --username - the username to retrieve info from # | arg: -k, --key - the key to retrieve # | ret: string - the key's value +# +# Requires YunoHost version 2.2.4 or higher. ynh_user_get_info() { # Declare an array to define the options of this helper. local legacy_args=uk @@ -43,6 +47,8 @@ ynh_user_get_info() { # # usage: ynh_user_list # | ret: string - one username per line +# +# Requires YunoHost version 2.4.0 or higher. ynh_user_list() { sudo yunohost user list --output-as plain --quiet \ | awk '/^##username$/{getline; print}' @@ -52,6 +58,8 @@ ynh_user_list() { # # usage: ynh_system_user_exists --username=username # | arg: -u, --username - the username to check +# +# Requires YunoHost version 2.2.4 or higher. ynh_system_user_exists() { # Declare an array to define the options of this helper. local legacy_args=u @@ -63,19 +71,35 @@ ynh_system_user_exists() { getent passwd "$username" &>/dev/null } +# Check if a group exists on the system +# +# usage: ynh_system_group_exists --group=group +# | arg: -g, --group - the group to check +ynh_system_group_exists() { + # Declare an array to define the options of this helper. + local legacy_args=g + declare -Ar args_array=( [g]=group= ) + local group + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + getent group "$group" &>/dev/null +} + # Create a system user # # examples: -# - ynh_system_user_create --username=nextcloud -> creates a nextcloud user with -# no home directory and /usr/sbin/nologin login shell (hence no login capability) -# - ynh_system_user_create --username=discourse --home_dir=/var/www/discourse --use_shell --> creates a -# discourse user using /var/www/discourse as home directory and the default login shell +# # Create a nextcloud user with no home directory and /usr/sbin/nologin login shell (hence no login capability) +# ynh_system_user_create --username=nextcloud +# # Create a discourse user using /var/www/discourse as home directory and the default login shell +# ynh_system_user_create --username=discourse --home_dir=/var/www/discourse --use_shell # # usage: ynh_system_user_create --username=user_name [--home_dir=home_dir] [--use_shell] # | arg: -u, --username - Name of the system user that will be create # | arg: -h, --home_dir - Path of the home dir for the user. Usually the final path of the app. If this argument is omitted, the user will be created without home -# | arg: -s, --use_shell - Create a user using the default login shell if present. -# If this argument is omitted, the user will be created with /usr/sbin/nologin shell +# | arg: -s, --use_shell - Create a user using the default login shell if present. If this argument is omitted, the user will be created with /usr/sbin/nologin shell +# +# Requires YunoHost version 2.6.4 or higher. ynh_system_user_create () { # Declare an array to define the options of this helper. local legacy_args=uhs @@ -108,6 +132,8 @@ ynh_system_user_create () { # # usage: ynh_system_user_delete --username=user_name # | arg: -u, --username - Name of the system user that will be create +# +# Requires YunoHost version 2.6.4 or higher. ynh_system_user_delete () { # Declare an array to define the options of this helper. local legacy_args=u @@ -116,11 +142,19 @@ ynh_system_user_delete () { # Manage arguments with getopts ynh_handle_getopts_args "$@" - if ynh_system_user_exists "$username" # Check if the user exists on the system + # Check if the user exists on the system + if ynh_system_user_exists "$username" then - echo "Remove the user $username" >&2 - sudo userdel $username + ynh_print_info --message="Remove the user $username" + deluser $username else - echo "The user $username was not found" >&2 + ynh_print_warn --message="The user $username was not found" + fi + + # Check if the group exists on the system + if ynh_system_group_exists "$username" + then + ynh_print_info --message="Remove the group $username" + delgroup $username fi } diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 5ba2946a2..1dd83c0e2 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -1,109 +1,53 @@ #!/bin/bash -# Extract a key from a plain command output +# Manage a fail of the script # -# example: yunohost user info tata --output-as plain | ynh_get_plain_key mail -# -# usage: ynh_get_plain_key key [subkey [subsubkey ...]] -# | ret: string - the key's value -ynh_get_plain_key() { - local prefix="#" - local founded=0 - local key=$1 - shift - while read line; do - if [[ "$founded" == "1" ]] ; then - [[ "$line" =~ ^${prefix}[^#] ]] && return - echo $line - elif [[ "$line" =~ ^${prefix}${key}$ ]]; then - if [[ -n "${1:-}" ]]; then - prefix+="#" - key=$1 - shift - else - founded=1 - fi - fi - done -} - -# Restore a previous backup if the upgrade process failed +# [internal] # # usage: -# ynh_backup_before_upgrade +# ynh_exit_properly is used only by the helper ynh_abort_if_errors. +# You should not use it directly. +# Instead, add to your script: # ynh_clean_setup () { -# ynh_restore_upgradebackup +# instructions... # } -# ynh_abort_if_errors # -ynh_restore_upgradebackup () { - echo "Upgrade failed." >&2 - local app_bck=${app//_/-} # Replace all '_' by '-' - - NO_BACKUP_UPGRADE=${NO_BACKUP_UPGRADE:-0} - - if [ "$NO_BACKUP_UPGRADE" -eq 0 ] - then - # Check if an existing backup can be found before removing and restoring the application. - if sudo yunohost backup list | grep -q $app_bck-pre-upgrade$backup_number - then - # Remove the application then restore it - sudo yunohost app remove $app - # Restore the backup - sudo yunohost backup restore $app_bck-pre-upgrade$backup_number --apps $app --force --debug - ynh_die --message="The app was restored to the way it was before the failed upgrade." - fi - else - echo "\$NO_BACKUP_UPGRADE is set, that means there's no backup to restore. You have to fix this upgrade by yourself !" >&2 - fi -} - -# Make a backup in case of failed upgrade +# This function provide a way to clean some residual of installation that not managed by remove script. # -# usage: -# ynh_backup_before_upgrade -# ynh_clean_setup () { -# ynh_restore_upgradebackup -# } -# ynh_abort_if_errors +# It prints a warning to inform that the script was failed, and execute the ynh_clean_setup function if used in the app script # -ynh_backup_before_upgrade () { - if [ ! -e "/etc/yunohost/apps/$app/scripts/backup" ] - then - echo "This app doesn't have any backup script." >&2 - return +# Requires YunoHost version 2.6.4 or higher. +ynh_exit_properly () { + local exit_code=$? + if [ "$exit_code" -eq 0 ]; then + exit 0 # Exit without error if the script ended correctly fi - backup_number=1 - local old_backup_number=2 - local app_bck=${app//_/-} # Replace all '_' by '-' - NO_BACKUP_UPGRADE=${NO_BACKUP_UPGRADE:-0} - if [ "$NO_BACKUP_UPGRADE" -eq 0 ] - then - # Check if a backup already exists with the prefix 1 - if sudo yunohost backup list | grep -q $app_bck-pre-upgrade1 - then - # Prefix becomes 2 to preserve the previous backup - backup_number=2 - old_backup_number=1 - fi + trap '' EXIT # Ignore new exit signals + set +eu # Do not exit anymore if a command fail or if a variable is empty - # Create backup - sudo BACKUP_CORE_ONLY=1 yunohost backup create --apps $app --name $app_bck-pre-upgrade$backup_number --debug - if [ "$?" -eq 0 ] - then - # If the backup succeeded, remove the previous backup - if sudo yunohost backup list | grep -q $app_bck-pre-upgrade$old_backup_number - then - # Remove the previous backup only if it exists - sudo yunohost backup delete $app_bck-pre-upgrade$old_backup_number > /dev/null - fi - else - ynh_die --message="Backup failed, the upgrade process was aborted." - fi - else - echo "\$NO_BACKUP_UPGRADE is set, backup will be avoided. Be careful, this upgrade is going to be operated without a security backup" - fi + ynh_print_err --message="!!\n $app's script has encountered an error. Its execution was cancelled.\n!!" + + if type -t ynh_clean_setup > /dev/null; then # Check if the function exist in the app script. + ynh_clean_setup # Call the function to do specific cleaning for the app. + fi + + ynh_die # Exit with error status +} + +# Exits if an error occurs during the execution of the script. +# +# usage: ynh_abort_if_errors +# +# This configure the rest of the script execution such that, if an error occurs +# or if an empty variable is used, the execution of the script stops +# immediately and a call to `ynh_clean_setup` is triggered if it has been +# defined by your script. +# +# Requires YunoHost version 2.6.4 or higher. +ynh_abort_if_errors () { + set -eu # Exit if a command fail, and if a variable is used unset. + trap ynh_exit_properly EXIT # Capturing exit signals on shell script } # Download, check integrity, uncompress and patch the source from app.src @@ -125,7 +69,7 @@ ynh_backup_before_upgrade () { # SOURCE_IN_SUBDIR=false # # (Optionnal) Name of the local archive (offline setup support) # # default: ${src_id}.${src_format} -# SOURCE_FILENAME=example.tar.gz +# SOURCE_FILENAME=example.tar.gz # # (Optional) If it set as false don't extract the source. # # (Useful to get a debian package or a python wheel.) # # default: true @@ -150,6 +94,8 @@ ynh_backup_before_upgrade () { # usage: ynh_setup_source --dest_dir=dest_dir [--source_id=source_id] # | arg: -d, --dest_dir - Directory where to setup sources # | arg: -s, --source_id - Name of the app, if the package contains more than one app +# +# Requires YunoHost version 2.6.4 or higher. ynh_setup_source () { # Declare an array to define the options of this helper. local legacy_args=ds @@ -160,15 +106,22 @@ ynh_setup_source () { ynh_handle_getopts_args "$@" source_id="${source_id:-app}" # If the argument is not given, source_id equals "app" + local src_file_path="$YNH_CWD/../conf/${source_id}.src" + # In case of restore script the src file is in an other path. + # So try to use the restore path if the general path point to no file. + if [ ! -e "$src_file_path" ]; then + src_file_path="$YNH_CWD/../settings/conf/${source_id}.src" + fi + # Load value from configuration file (see above for a small doc about this file # format) - local src_url=$(grep 'SOURCE_URL=' "$YNH_CWD/../conf/${source_id}.src" | cut -d= -f2-) - local src_sum=$(grep 'SOURCE_SUM=' "$YNH_CWD/../conf/${source_id}.src" | cut -d= -f2-) - local src_sumprg=$(grep 'SOURCE_SUM_PRG=' "$YNH_CWD/../conf/${source_id}.src" | cut -d= -f2-) - local src_format=$(grep 'SOURCE_FORMAT=' "$YNH_CWD/../conf/${source_id}.src" | cut -d= -f2-) - local src_extract=$(grep 'SOURCE_EXTRACT=' "$YNH_CWD/../conf/${source_id}.src" | cut -d= -f2-) - local src_in_subdir=$(grep 'SOURCE_IN_SUBDIR=' "$YNH_CWD/../conf/${source_id}.src" | cut -d= -f2-) - local src_filename=$(grep 'SOURCE_FILENAME=' "$YNH_CWD/../conf/${source_id}.src" | cut -d= -f2-) + local src_url=$(grep 'SOURCE_URL=' "$src_file_path" | cut -d= -f2-) + local src_sum=$(grep 'SOURCE_SUM=' "$src_file_path" | cut -d= -f2-) + local src_sumprg=$(grep 'SOURCE_SUM_PRG=' "$src_file_path" | cut -d= -f2-) + local src_format=$(grep 'SOURCE_FORMAT=' "$src_file_path" | cut -d= -f2-) + local src_extract=$(grep 'SOURCE_EXTRACT=' "$src_file_path" | cut -d= -f2-) + local src_in_subdir=$(grep 'SOURCE_IN_SUBDIR=' "$src_file_path" | cut -d= -f2-) + local src_filename=$(grep 'SOURCE_FILENAME=' "$src_file_path" | cut -d= -f2-) # Default value src_sumprg=${src_sumprg:-sha256sum} @@ -199,7 +152,7 @@ ynh_setup_source () { then mv $src_filename $dest_dir elif [ "$src_format" = "zip" ] - then + then # Zip format # Using of a temp directory, because unzip doesn't manage --strip-components if $src_in_subdir ; then @@ -249,45 +202,47 @@ ynh_setup_source () { # $domain and $path_url should be defined externally (and correspond to the domain.tld and the /path (of the app?)) # # example: ynh_local_curl "/install.php?installButton" "foo=$var1" "bar=$var2" -# +# # usage: ynh_local_curl "page_uri" "key1=value1" "key2=value2" ... # | arg: page_uri - Path (relative to $path_url) of the page where POST data will be sent # | arg: key1=value1 - (Optionnal) POST key and corresponding value # | arg: key2=value2 - (Optionnal) Another POST key and corresponding value # | arg: ... - (Optionnal) More POST keys and values +# +# Requires YunoHost version 2.6.4 or higher. ynh_local_curl () { - # Define url of page to curl - local local_page=$(ynh_normalize_url_path $1) - local full_path=$path_url$local_page - - if [ "${path_url}" == "/" ]; then - full_path=$local_page - fi - - local full_page_url=https://localhost$full_path + # Define url of page to curl + local local_page=$(ynh_normalize_url_path $1) + local full_path=$path_url$local_page - # Concatenate all other arguments with '&' to prepare POST data - local POST_data="" - local arg="" - for arg in "${@:2}" - do - POST_data="${POST_data}${arg}&" - done - if [ -n "$POST_data" ] - then - # Add --data arg and remove the last character, which is an unecessary '&' - POST_data="--data ${POST_data::-1}" - fi - - # Wait untils nginx has fully reloaded (avoid curl fail with http2) - sleep 2 + if [ "${path_url}" == "/" ]; then + full_path=$local_page + fi - # Curl the URL - curl --silent --show-error -kL -H "Host: $domain" --resolve $domain:443:127.0.0.1 $POST_data "$full_page_url" + local full_page_url=https://localhost$full_path + + # Concatenate all other arguments with '&' to prepare POST data + local POST_data="" + local arg="" + for arg in "${@:2}" + do + POST_data="${POST_data}${arg}&" + done + if [ -n "$POST_data" ] + then + # Add --data arg and remove the last character, which is an unecessary '&' + POST_data="--data ${POST_data::-1}" + fi + + # Wait untils nginx has fully reloaded (avoid curl fail with http2) + sleep 2 + + # Curl the URL + curl --silent --show-error -kL -H "Host: $domain" --resolve $domain:443:127.0.0.1 $POST_data "$full_page_url" } # Render templates with Jinja2 -# +# # Attention : Variables should be exported before calling this helper to be # accessible inside templates. # @@ -303,3 +258,220 @@ ynh_render_template() { jinja2.Template(sys.stdin.read() ).render(os.environ));' < $template_path > $output_path } + +# Fetch the Debian release codename +# +# usage: ynh_get_debian_release +# | ret: The Debian release codename (i.e. jessie, stretch, ...) +# +# Requires YunoHost version 2.7.12 or higher. +ynh_get_debian_release () { + echo $(lsb_release --codename --short) +} + +# Create a directory under /tmp +# +# [internal] +# +# Deprecated helper +# +# usage: ynh_mkdir_tmp +# | ret: the created directory path +ynh_mkdir_tmp() { + ynh_print_warn --message="The helper ynh_mkdir_tmp is deprecated." + ynh_print_warn --message="You should use 'mktemp -d' instead and manage permissions \ +properly with chmod/chown." + local TMP_DIR=$(mktemp -d) + + # Give rights to other users could be a security risk. + # But for retrocompatibility we need it. (This helpers is deprecated) + chmod 755 $TMP_DIR + echo $TMP_DIR +} + +# Remove a file or a directory securely +# +# usage: ynh_secure_remove --file=path_to_remove +# | arg: -f, --file - File or directory to remove +# +# Requires YunoHost version 2.6.4 or higher. +ynh_secure_remove () { + # Declare an array to define the options of this helper. + local legacy_args=f + declare -Ar args_array=( [f]=file= ) + local file + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + local forbidden_path=" \ + /var/www \ + /home/yunohost.app" + + if [ $# -ge 2 ] + then + ynh_print_warn --message="/!\ Packager ! You provided more than one argument to ynh_secure_remove but it will be ignored... Use this helper with one argument at time." + fi + + if [[ "$forbidden_path" =~ "$file" \ + # Match all paths or subpaths in $forbidden_path + || "$file" =~ ^/[[:alnum:]]+$ \ + # Match all first level paths from / (Like /var, /root, etc...) + || "${file:${#file}-1}" = "/" ]] + # Match if the path finishes by /. Because it seems there is an empty variable + then + ynh_print_warn --message="Avoid deleting $file." + else + if [ -e "$file" ] + then + sudo rm -R "$file" + else + ynh_print_info --message="$file wasn't deleted because it doesn't exist." + fi + fi +} + +# Extract a key from a plain command output +# +# example: yunohost user info tata --output-as plain | ynh_get_plain_key mail +# +# usage: ynh_get_plain_key key [subkey [subsubkey ...]] +# | ret: string - the key's value +# +# Requires YunoHost version 2.2.4 or higher. +ynh_get_plain_key() { + local prefix="#" + local founded=0 + local key=$1 + shift + while read line; do + if [[ "$founded" == "1" ]] ; then + [[ "$line" =~ ^${prefix}[^#] ]] && return + echo $line + elif [[ "$line" =~ ^${prefix}${key}$ ]]; then + if [[ -n "${1:-}" ]]; then + prefix+="#" + key=$1 + shift + else + founded=1 + fi + fi + done +} + +# Read the value of a key in a ynh manifest file +# +# usage: ynh_read_manifest manifest key +# | arg: -m, --manifest= - Path of the manifest to read +# | arg: -k, --key= - Name of the key to find +# +# Requires YunoHost version 3.?.? or higher. +ynh_read_manifest () { + # Declare an array to define the options of this helper. + local legacy_args=mk + declare -Ar args_array=( [m]=manifest= [k]=manifest_key= ) + local manifest + local manifest_key + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + if [ ! -e "$manifest" ]; then + # If the manifest isn't found, try the common place for backup and restore script. + manifest="../settings/manifest.json" + fi + + jq ".$manifest_key" "$manifest" --raw-output +} + +# Read the upstream version from the manifest +# The version number in the manifest is defined by ~ynh +# For example : 4.3-2~ynh3 +# This include the number before ~ynh +# In the last example it return 4.3-2 +# +# usage: ynh_app_upstream_version [-m manifest] +# | arg: -m, --manifest= - Path of the manifest to read +# +# Requires YunoHost version 3.?.? or higher. +ynh_app_upstream_version () { + # Declare an array to define the options of this helper. + local legacy_args=m + declare -Ar args_array=( [m]=manifest= ) + local manifest + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + manifest="${manifest:-../manifest.json}" + version_key=$(ynh_read_manifest --manifest="$manifest" --manifest_key="version") + echo "${version_key/~ynh*/}" +} + +# Read package version from the manifest +# The version number in the manifest is defined by ~ynh +# For example : 4.3-2~ynh3 +# This include the number after ~ynh +# In the last example it return 3 +# +# usage: ynh_app_package_version [-m manifest] +# | arg: -m, --manifest= - Path of the manifest to read +# +# Requires YunoHost version 3.?.? or higher. +ynh_app_package_version () { + # Declare an array to define the options of this helper. + local legacy_args=m + declare -Ar args_array=( [m]=manifest= ) + local manifest + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + manifest="${manifest:-../manifest.json}" + version_key=$(ynh_read_manifest --manifest="$manifest" --manifest_key="version") + echo "${version_key/*~ynh/}" +} + +# Checks the app version to upgrade with the existing app version and returns: +# - UPGRADE_APP if the upstream app version has changed +# - UPGRADE_PACKAGE if only the YunoHost package has changed +# +# It stops the current script without error if the package is up-to-date +# +# This helper should be used to avoid an upgrade of an app, or the upstream part +# of it, when it's not needed +# +# To force an upgrade, even if the package is up to date, +# you have to set the variable YNH_FORCE_UPGRADE before. +# example: sudo YNH_FORCE_UPGRADE=1 yunohost app upgrade MyApp +# +# usage: ynh_check_app_version_changed +# +# Requires YunoHost version 3.?.? or higher. +ynh_check_app_version_changed () { + local force_upgrade=${YNH_FORCE_UPGRADE:-0} + local package_check=${PACKAGE_CHECK_EXEC:-0} + + # By default, upstream app version has changed + local return_value="UPGRADE_APP" + + local current_version=$(ynh_read_manifest --manifest="/etc/yunohost/apps/$YNH_APP_INSTANCE_NAME/manifest.json" --manifest_key="version" || echo 1.0) + local current_upstream_version="$(ynh_app_upstream_version --manifest="/etc/yunohost/apps/$YNH_APP_INSTANCE_NAME/manifest.json")" + local update_version=$(ynh_read_manifest --manifest="../manifest.json" --manifest_key="version" || echo 1.0) + local update_upstream_version="$(ynh_app_upstream_version)" + + if [ "$current_version" == "$update_version" ] ; then + # Complete versions are the same + if [ "$force_upgrade" != "0" ] + then + ynh_print_info --message="Upgrade forced by YNH_FORCE_UPGRADE." + unset YNH_FORCE_UPGRADE + elif [ "$package_check" != "0" ] + then + ynh_print_info --message="Upgrade forced for package check." + else + ynh_die "Up-to-date, nothing to do" 0 + fi + elif [ "$current_upstream_version" == "$update_upstream_version" ] ; then + # Upstream versions are the same, only YunoHost package versions differ + return_value="UPGRADE_PACKAGE" + fi + echo $return_value +} diff --git a/data/hooks/backup/32-conf_cron b/data/hooks/backup/32-conf_cron index 063ec1a3f..acbd009ab 100755 --- a/data/hooks/backup/32-conf_cron +++ b/data/hooks/backup/32-conf_cron @@ -10,6 +10,6 @@ source /usr/share/yunohost/helpers backup_dir="${1}/conf/cron" # Backup the configuration -for f in $(ls -1B /etc/cron.d/yunohost*); do +for f in $(ls -1B /etc/cron.d/yunohost* 2> /dev/null); do ynh_backup "$f" "${backup_dir}/${f##*/}" done diff --git a/data/hooks/conf_regen/03-ssh b/data/hooks/conf_regen/03-ssh index 5bb9cf916..54b7c55b7 100755 --- a/data/hooks/conf_regen/03-ssh +++ b/data/hooks/conf_regen/03-ssh @@ -12,7 +12,7 @@ do_pre_regen() { [[ ! -f /etc/yunohost/from_script ]] || return 0 cd /usr/share/yunohost/templates/ssh - + # do not listen to IPv6 if unavailable [[ -f /proc/net/if_inet6 ]] && ipv6_enabled=true || ipv6_enabled=false @@ -23,6 +23,9 @@ do_pre_regen() { ssh_keys="$ssh_keys $(ls /etc/ssh/ssh_host_dsa_key 2>/dev/null || true)" fi + # Support different strategy for security configurations + export compatibility="$(yunohost settings get 'security.ssh.compatibility')" + export ssh_keys export ipv6_enabled ynh_render_template "sshd_config" "${pending_dir}/etc/ssh/sshd_config" diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx index 7ca63c003..59654a771 100755 --- a/data/hooks/conf_regen/15-nginx +++ b/data/hooks/conf_regen/15-nginx @@ -10,7 +10,25 @@ do_init_regen() { exit 1 fi - do_pre_regen "" + cd /usr/share/yunohost/templates/nginx + + nginx_dir="/etc/nginx" + nginx_conf_dir="${nginx_dir}/conf.d" + mkdir -p "$nginx_conf_dir" + + # install plain conf files + cp plain/* "$nginx_conf_dir" + + # probably run with init: just disable default site, restart NGINX and exit + rm -f "${nginx_dir}/sites-enabled/default" + + export compatibility="intermediate" + ynh_render_template "yunohost_admin.conf" "${nginx_conf_dir}/yunohost_admin.conf" + + # Restart nginx if conf looks good, otherwise display error and exit unhappy + nginx -t 2>/dev/null && service nginx restart || (nginx -t && exit 1) + + exit 0 } do_pre_regen() { @@ -22,20 +40,16 @@ do_pre_regen() { nginx_conf_dir="${nginx_dir}/conf.d" mkdir -p "$nginx_conf_dir" - # install plain conf files + # install / update plain conf files cp plain/* "$nginx_conf_dir" - # probably run with init: just disable default site, restart NGINX and exit - if [[ -z "$pending_dir" ]]; then - rm -f "${nginx_dir}/sites-enabled/default" - service nginx restart - exit 0 - fi - # retrieve variables main_domain=$(cat /etc/yunohost/current_host) domain_list=$(sudo yunohost domain list --output-as plain --quiet) + # Support different strategy for security configurations + export compatibility="$(yunohost settings get 'security.nginx.compatibility')" + # add domain conf files for domain in $domain_list; do domain_conf_dir="${nginx_conf_dir}/${domain}.d" @@ -58,6 +72,8 @@ do_pre_regen() { done + ynh_render_template "yunohost_admin.conf" "${nginx_conf_dir}/yunohost_admin.conf" + # remove old domain conf files conf_files=$(ls -1 /etc/nginx/conf.d \ | awk '/^[^\.]+\.[^\.]+.*\.conf$/ { print $1 }') diff --git a/data/templates/nginx/plain/global.conf b/data/templates/nginx/plain/global.conf index ca8721afb..b3a5f356a 100644 --- a/data/templates/nginx/plain/global.conf +++ b/data/templates/nginx/plain/global.conf @@ -1,2 +1 @@ server_tokens off; -gzip_types text/css text/javascript application/javascript; diff --git a/data/templates/nginx/plain/yunohost_panel.conf.inc b/data/templates/nginx/plain/yunohost_panel.conf.inc index 34afe136d..1c5a2d656 100644 --- a/data/templates/nginx/plain/yunohost_panel.conf.inc +++ b/data/templates/nginx/plain/yunohost_panel.conf.inc @@ -1,8 +1,8 @@ -# Insert YunoHost panel -sub_filter ''; +# Insert YunoHost button + portal overlay +sub_filter ''; sub_filter_once on; # Apply to other mime types than text/html sub_filter_types application/xhtml+xml; # Prevent YunoHost panel files from being blocked by specific app rules -location ~ ynhpanel\.(js|json|css) { +location ~ (ynh_portal.js|ynh_overlay.css|ynh_userinfo.json) { } diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index 43d38ca98..4a5e91557 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -1,3 +1,8 @@ +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + server { listen 80; listen [::]:80; @@ -29,6 +34,14 @@ server { ssl_session_timeout 5m; ssl_session_cache shared:SSL:50m; + {% if compatibility == "modern" %} + # Ciphers with modern compatibility + # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=modern + # The following configuration use modern ciphers, but remove compatibility with some old clients (android < 5.0, Internet Explorer < 10, ...) + ssl_protocols TLSv1.2; + ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; + ssl_prefer_server_ciphers on; + {% else %} # As suggested by Mozilla : https://wiki.mozilla.org/Security/Server_Side_TLS and https://en.wikipedia.org/wiki/Curve25519 ssl_ecdh_curve secp521r1:secp384r1:prime256v1; ssl_prefer_server_ciphers on; @@ -38,15 +51,10 @@ server { ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS'; - # Ciphers with modern compatibility - # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=modern - # Uncomment the following to use modern ciphers, but remove compatibility with some old clients (android < 5.0, Internet Explorer < 10, ...) - #ssl_protocols TLSv1.2; - #ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; - # Uncomment the following directive after DH generation # > openssl dhparam -out /etc/ssl/private/dh2048.pem -outform PEM -2 2048 #ssl_dhparam /etc/ssl/private/dh2048.pem; + {% endif %} # Follows the Web Security Directives from the Mozilla Dev Lab and the Mozilla Obervatory + Partners # https://wiki.mozilla.org/Security/Guidelines/Web_Security @@ -71,6 +79,10 @@ server { resolver_timeout 5s; {% endif %} + # Disable gzip to protect against BREACH + # Read https://trac.nginx.org/nginx/ticket/1720 (text/html cannot be disabled!) + gzip off; + access_by_lua_file /usr/share/ssowat/access.lua; include /etc/nginx/conf.d/{{ domain }}.d/*.conf; diff --git a/data/templates/nginx/plain/yunohost_admin.conf b/data/templates/nginx/yunohost_admin.conf similarity index 84% rename from data/templates/nginx/plain/yunohost_admin.conf rename to data/templates/nginx/yunohost_admin.conf index 2493e4033..e0d9f6bb1 100644 --- a/data/templates/nginx/plain/yunohost_admin.conf +++ b/data/templates/nginx/yunohost_admin.conf @@ -20,6 +20,14 @@ server { ssl_session_timeout 5m; ssl_session_cache shared:SSL:50m; + {% if compatibility == "modern" %} + # Ciphers with modern compatibility + # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=modern + # Uncomment the following to use modern ciphers, but remove compatibility with some old clients (android < 5.0, Internet Explorer < 10, ...) + ssl_protocols TLSv1.2; + ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; + ssl_prefer_server_ciphers on; + {% else %} # As suggested by Mozilla : https://wiki.mozilla.org/Security/Server_Side_TLS and https://en.wikipedia.org/wiki/Curve25519 ssl_ecdh_curve secp521r1:secp384r1:prime256v1; ssl_prefer_server_ciphers on; @@ -29,20 +37,15 @@ server { ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS'; - # Ciphers with modern compatibility - # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=modern - # Uncomment the following to use modern ciphers, but remove compatibility with some old clients (android < 5.0, Internet Explorer < 10, ...) - #ssl_protocols TLSv1.2; - #ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; - # Uncomment the following directive after DH generation # > openssl dhparam -out /etc/ssl/private/dh2048.pem -outform PEM -2 2048 #ssl_dhparam /etc/ssl/private/dh2048.pem; - + {% endif %} + # Follows the Web Security Directives from the Mozilla Dev Lab and the Mozilla Obervatory + Partners # https://wiki.mozilla.org/Security/Guidelines/Web_Security - # https://observatory.mozilla.org/ - more_set_headers "Strict-Transport-Security : max-age=63072000; includeSubDomains; preload"; + # https://observatory.mozilla.org/ + more_set_headers "Strict-Transport-Security : max-age=63072000; includeSubDomains; preload"; more_set_headers "Referrer-Policy : 'same-origin'"; more_set_headers "Content-Security-Policy : upgrade-insecure-requests; object-src 'none'; script-src https: 'unsafe-eval'"; more_set_headers "X-Content-Type-Options : nosniff"; @@ -51,6 +54,10 @@ server { more_set_headers "X-Permitted-Cross-Domain-Policies : none"; more_set_headers "X-Frame-Options : SAMEORIGIN"; + # Disable gzip to protect against BREACH + # Read https://trac.nginx.org/nginx/ticket/1720 (text/html cannot be disabled!) + gzip off; + location / { return 302 https://$http_host/yunohost/admin; } diff --git a/data/templates/postfix/plain/master.cf b/data/templates/postfix/plain/master.cf index 2d8712604..377a90971 100644 --- a/data/templates/postfix/plain/master.cf +++ b/data/templates/postfix/plain/master.cf @@ -122,6 +122,6 @@ mailman unix - n n - - pipe flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py ${nexthop} ${user} -# Dovecot LDA -dovecot unix - n n - - pipe - flags=DRhu user=vmail:mail argv=/usr/lib/dovecot/deliver -f ${sender} -d ${user}@${nexthop} -m ${extension} +# Dovecot LDA +dovecot unix - n n - - pipe + flags=DRhu user=vmail:mail argv=/usr/lib/dovecot/deliver -f ${sender} -d ${user}@${nexthop} -m ${extension} -a ${recipient} diff --git a/data/templates/slapd/slapd.default b/data/templates/slapd/slapd.default index 372b8f4ab..0041b30c5 100644 --- a/data/templates/slapd/slapd.default +++ b/data/templates/slapd/slapd.default @@ -21,7 +21,7 @@ SLAPD_PIDFILE= # sockets. # Example usage: # SLAPD_SERVICES="ldap://127.0.0.1:389/ ldaps:/// ldapi:///" -SLAPD_SERVICES="ldap:/// ldapi:///" +SLAPD_SERVICES="ldap://127.0.0.1:389/ ldap://[::1]:389/ ldapi:///" # If SLAPD_NO_START is set, the init script will not start or restart # slapd (but stop will still work). Uncomment this if you are diff --git a/data/templates/ssh/sshd_config b/data/templates/ssh/sshd_config index ed870e5dc..8dc0e8dfc 100644 --- a/data/templates/ssh/sshd_config +++ b/data/templates/ssh/sshd_config @@ -15,10 +15,17 @@ HostKey {{ key }}{% endfor %} # https://infosec.mozilla.org/guidelines/openssh # ############################################## -# Keys, ciphers and MACS -KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha256 -Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr -MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,umac-128@openssh.com +{% if compatibility == "intermediate" %} + KexAlgorithms diffie-hellman-group-exchange-sha256 + Ciphers aes256-ctr,aes192-ctr,aes128-ctr + MACs hmac-sha2-512,hmac-sha2-256 +{% else %} + # By default use "modern" Mozilla configuration + # Keys, ciphers and MACS + KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha256 + Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr + MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,umac-128@openssh.com +{% endif %} # Use kernel sandbox mechanisms where possible in unprivileged processes UsePrivilegeSeparation sandbox diff --git a/data/templates/yunohost/services.yml b/data/templates/yunohost/services.yml index 62509e1e9..0d79b182f 100644 --- a/data/templates/yunohost/services.yml +++ b/data/templates/yunohost/services.yml @@ -20,8 +20,6 @@ mysql: glances: {} ssh: log: /var/log/auth.log -ssl: - status: null metronome: log: [/var/log/metronome/metronome.log,/var/log/metronome/metronome.err] slapd: @@ -34,10 +32,9 @@ yunohost-firewall: need_lock: true nslcd: log: /var/log/syslog -nsswitch: - status: null -yunohost: - status: null +nsswitch: null +ssl: null +yunohost: null bind9: null tahoe-lafs: null memcached: null diff --git a/debian/changelog b/debian/changelog index 7be4212fe..a22959899 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,119 @@ +yunohost (3.5.2.2) stable; urgency=low + + - Hotfix for ynh_psql_remove_db (from ljf) + + -- Alexandre Aubin Thu, 18 Apr 2019 17:32:00 +0000 + +yunohost (3.5.2.1) stable; urgency=low + + - [fix] Fresh install was broken because of yunohost_admin.conf initialization + + -- Alexandre Aubin Thu, 11 Apr 2019 14:38:00 +0000 + +yunohost (3.5.2) stable; urgency=low + + - Release as stable ! + - [doc] Update script to automatically generate helper doc + - [i18n] Update translations for Catalan, Arabic, Italian + + Thanks to all contributors: Aleks, xaloc, BoF, silkevicious ! <3 + + -- Alexandre Aubin Wed, 10 Apr 2019 01:53:00 +0000 + +yunohost (3.5.1.1) testing; urgency=low + + - [fix] enabled/disabled status for sysv services + - [fix] Nodejs helpers : use YNH_APP_INSTANCE_NAME instead of YNH_APP_ID (#700) + - [fix] nginx diagnosis when there's an error throwing a huge useless traceback. Use Popen instead to display the real error + - [fix] service_status returns different type of data if you ask for one or multiple services + + -- Alexandre Aubin Wed, 03 Apr 2019 17:28:00 +0000 + +yunohost (3.5.1) testing; urgency=low + + - [fix] Fix the dbus interface to get info for services (#698) + - [mod] Use ask key for display_text instead and support i18n (#697) + - [fix] Rework tools update (#695) + - [enh] Nginx conf tweaks for theme (#689) + - [fix] Fix argument escaping in getopts (#685, #683) + - [enh] Support php versions in ynh_add_fpm_config (#674) + - [enh] Check that required services are up before running app install and upgrade (#670) + - [doc] Add min version for all helpers (#664) + - [enh] Add a setting to control compatibility/security tradeoff for nginx and ssh configurations (#640) + - [enh] Hooks to allow apps to extend the recommended DNS configuration (#517) + - Misc technical fixes / improvements (0bd781b, fad3edf, 1268872, 847ceca, 26e77b7, b6cff68) + - [i18n] Update translation for French, Catalan, Esperanto, Occitan + + Thanks to all contributors: Aleks, Bram, Gabriel Corona, Jibec, Josue, Maniack C, Mélanie C., Quentí, Romuald du Song, ljf, ppr, Xaloc ! <3 + + -- Alexandre Aubin Wed, 03 Apr 2019 02:13:00 +0000 + +yunohost (3.5.0.2) testing; urgency=low + + - [fix] Make sure that `ynh_system_user_delete` also deletes the group (#680) + - [enh] `ynh_systemd_action` : reload-or-restart instead of just reload (#681) + + Last minute fixes by Maniack ;) + + -- Alexandre Aubin Thu, 14 Mar 2019 03:45:00 +0000 + +yunohost (3.5.0.1) testing; urgency=low + + - [fix] #675 introduced a bug in nginx conf ... + + -- Alexandre Aubin Wed, 13 Mar 2019 19:23:00 +0000 + +yunohost (3.5.0) testing; urgency=low + + Core + ---- + + - [fix] Disable gzip entirely to avoid BREACH attacks (#675) + - [fix] Backup tests were broken (#673) + - [fix] Backup fails because output directory not empty (#672) + - [fix] Reject app password if they contains { or } (#671) + - [enh] Allow `display_text` 'fake' argument in manifest.json (#669) + - [fix] Optimize dyndns requests (#662) + - [enh] Don't add Strict-Transport-Security header in nginx conf if using a selfsigned cert (#661) + - [enh] Add apt-transport-https to dependencies (#658) + - [enh] Cache results from meltdown vulnerability checker (#656) + - [enh] Ensure the tar file is closed during the backup (#655) + - [enh] Be able to define hook to trigger when changing a setting (#654) + - [enh] Assert dpkg is not broken before app install (#652) + - [fix] Loading only one helper file leads to errors because missing getopts (#651) + - [enh] Improve / add some messages to improve UX (#650) + - [enh] Reload fail2ban instead of restart (#649) + - [enh] Add IPv6 resolvers from diyisp.org to resolv.dnsmasq.conf (#639) + - [fix] Remove old SMTP port (465) from fail2ban jail.conf (#637) + - [enh] Improve protection against indexation from the robots. (#622) + - [enh] Allow hooks to return data (#526) + - [fix] Do not make version number available from web API to unauthenticated users (#291) + - [i18n] Improve Russian and Chinese (Mandarin) translations + + App helpers + ----------- + + - [enh] Optimize app setting helpers (#663, #676) + - [enh] Handle `ynh_install_nodejs` for arm64 / aarch64 (#660) + - [enh] Update postgresql helpers (#657) + - [enh] Print diff of files when backup by `ynh_backup_if_checksum_is_different` (#648) + - [enh] Add app debugger helper (#647) + - [fix] Escape double quote before eval in getopts (#646) + - [fix] `ynh_local_curl` not using the right url in some cases (#644) + - [fix] Get rid of annoying 'unable to initialize frontend' messages (#643) + - [enh] Check if dpkg is not broken when calling `ynh_wait_dpkg_free` (#638) + - [enh] Warn the packager that `ynh_secure_remove` should be used with only one arg… (#635, #642) + - [enh] Add `ynh_script_progression` helper (#634) + - [enh] Add `ynh_systemd_action` helper (#633) + - [enh] Allow to dig deeper into an archive with `ynh_setup_source` (#630) + - [enh] Use getops (#561) + - [enh] Add `ynh_check_app_version_changed` helper (#521) + - [enh] Add fail2ban helpers (#364) + + Contributors: Alexandre Aubin, Jimmy Monin, Josué Tille, Kayou, Laurent Peuch, Lukas Fülling, Maniack Crudelis, Taekiro, frju365, ljf, opi, yalh76, Алексей + + -- Alexandre Aubin Wed, 13 Mar 2019 16:10:00 +0000 + yunohost (3.4.2.4) stable; urgency=low - [fix] Meltdown vulnerability checker something outputing trash instead of pure json diff --git a/debian/control b/debian/control index 685c194ba..bb697d074 100644 --- a/debian/control +++ b/debian/control @@ -8,6 +8,7 @@ X-Python-Version: >= 2.7 Homepage: https://yunohost.org/ Package: yunohost +Essential: yes Architecture: all Depends: ${python:Depends}, ${misc:Depends} , moulinette (>= 2.7.1), ssowat (>= 2.7.1) diff --git a/debian/postinst b/debian/postinst index df7112b9d..83220ae0b 100644 --- a/debian/postinst +++ b/debian/postinst @@ -12,7 +12,7 @@ do_configure() { bash /usr/share/yunohost/hooks/conf_regen/15-nginx init else echo "Regenerating configuration, this might take a while..." - yunohost service regen-conf --output-as none + yunohost tools regen-conf --output-as none echo "Launching migrations.." yunohost tools migrations migrate --auto diff --git a/debian/rules b/debian/rules index ce03d0e31..d012c73f3 100755 --- a/debian/rules +++ b/debian/rules @@ -7,6 +7,10 @@ %: dh ${@} --with=python2,systemd +override_dh_auto_build: + # Generate bash completion file + python data/actionsmap/yunohost_completion.py + override_dh_installinit: dh_installinit -pyunohost --name=yunohost-api --restart-after-upgrade dh_installinit -pyunohost --name=yunohost-firewall --noscripts diff --git a/doc/generate_helper_doc.py b/doc/generate_helper_doc.py index 7d8c489b7..5b51dda02 100644 --- a/doc/generate_helper_doc.py +++ b/doc/generate_helper_doc.py @@ -4,7 +4,12 @@ import os import glob import datetime -def render(data): +def render(helpers): + + data = { "helpers": helpers, + "date": datetime.datetime.now().strftime("%m/%d/%Y"), + "version": open("../debian/changelog").readlines()[0].split()[1].strip("()") + } from jinja2 import Template from ansi2html import Ansi2HTMLConverter @@ -43,7 +48,7 @@ class Parser(): "code": [] } for i, line in enumerate(self.file): - + if line.startswith("#!/bin/bash"): continue @@ -103,7 +108,6 @@ class Parser(): b["usage"] = "" b["args"] = [] b["ret"] = "" - b["example"] = "" subblocks = '\n'.join(b["comments"]).split("\n\n") @@ -114,17 +118,29 @@ class Parser(): b["brief"] = subblock continue - elif subblock.startswith("example"): + elif subblock.startswith("example:"): b["example"] = " ".join(subblock.split()[1:]) continue + elif subblock.startswith("examples:"): + b["examples"] = subblock.split("\n")[1:] + continue + elif subblock.startswith("usage"): for line in subblock.split("\n"): if line.startswith("| arg"): - argname = line.split()[2] - argdescr = " ".join(line.split()[4:]) - b["args"].append((argname, argdescr)) + linesplit = line.split() + argname = linesplit[2] + # Detect that there's a long argument version (-f, --foo - Some description) + if argname.endswith(",") and linesplit[3].startswith("--"): + argname = argname.strip(",") + arglongname = linesplit[3] + argdescr = " ".join(linesplit[5:]) + b["args"].append((argname, arglongname, argdescr)) + else: + argdescr = " ".join(linesplit[4:]) + b["args"].append((argname, argdescr)) elif line.startswith("| ret"): b["ret"] = " ".join(line.split()[2:]) else: @@ -136,9 +152,17 @@ class Parser(): elif subblock.startswith("| arg"): for line in subblock.split("\n"): if line.startswith("| arg"): - argname = line.split()[2] - argdescr = line.split()[4:] - b["args"].append((argname, argdescr)) + linesplit = line.split() + argname = linesplit[2] + # Detect that there's a long argument version (-f, --foo - Some description) + if argname.endswith(",") and linesplit[3].startswith("--"): + argname = argname.strip(",") + arglongname = linesplit[3] + argdescr = " ".join(linesplit[5:]) + b["args"].append((argname, arglongname, argdescr)) + else: + argdescr = " ".join(linesplit[4:]) + b["args"].append((argname, argdescr)) continue else: diff --git a/doc/helper_doc_template.html b/doc/helper_doc_template.html index 1fa1f68ad..92611c737 100644 --- a/doc/helper_doc_template.html +++ b/doc/helper_doc_template.html @@ -2,7 +2,7 @@

App helpers

-{% for category, helpers in data %} +{% for category, helpers in data.helpers %}

{{ category }}

@@ -27,8 +27,12 @@

Arguments:

    - {% for name, descr in h.args %} -
  • {{ name }} : {{ descr }}
  • + {% for infos in h.args %} + {% if infos|length == 2 %} +
  • {{ infos[0] }} : {{ infos[1] }}
  • + {% else %} +
  • {{ infos[0] }}, {{ infos[1] }} : {{ infos[2] }}
  • + {% endif %} {% endfor %}

@@ -38,11 +42,25 @@ Returns: {{ h.ret }}

{% endif %} - {% if h.example %} + {% if "example" in h.keys() %}

Example: {{ h.example }}

{% endif %} + {% if "examples" in h.keys() %} +

+ Examples:

    + {% for example in h.examples %} + {% if not example.strip().startswith("# ") %} + {{ example }} + {% else %} + {{ example.strip("# ") }} + {% endif %} +
    + {% endfor %} +
+

+ {% endif %} {% if h.details %}

Details: @@ -63,6 +81,8 @@ {% endfor %} {% endfor %} +

Generated by this script on {{data.date}} (Yunohost version {{data.version}})

+