mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
407 lines
14 KiB
Bash
407 lines
14 KiB
Bash
#!/bin/bash
|
|
|
|
# Create a dedicated config file from a template
|
|
#
|
|
# usage: ynh_add_config --template="template" --destination="destination"
|
|
# | arg: -t, --template= - Template config file to use
|
|
# | arg: -d, --destination= - Destination of the config file
|
|
# | arg: -j, --jinja - Use jinja template instead of legacy __MY_VAR__
|
|
#
|
|
# examples:
|
|
# ynh_add_config --template=".env" --destination="$install_dir/.env" use the template file "../conf/.env"
|
|
# ynh_add_config --jinja --template="config.j2" --destination="$install_dir/config" use the template file "../conf/config.j2"
|
|
# ynh_add_config --template="/etc/nginx/sites-available/default" --destination="etc/nginx/sites-available/mydomain.conf"
|
|
#
|
|
##
|
|
## How it works in "legacy" mode
|
|
##
|
|
# The template can be by default the name of a file in the conf directory
|
|
# of a YunoHost Package, a relative path or an absolute path.
|
|
#
|
|
# The helper will use the template `template` to generate a config file
|
|
# `destination` by replacing the following keywords with global variables
|
|
# that should be defined before calling this helper :
|
|
# ```
|
|
# __PATH__ by $path_url
|
|
# __USER__ by $app
|
|
# __YNH_NODE_LOAD_PATH__ by $ynh_node_load_PATH
|
|
# ```
|
|
# And any dynamic variables that should be defined before calling this helper like:
|
|
# ```
|
|
# __DOMAIN__ by $domain
|
|
# __APP__ by $app
|
|
# __VAR_1__ by $var_1
|
|
# __VAR_2__ by $var_2
|
|
# ```
|
|
#
|
|
##
|
|
## When --jinja is enabled
|
|
##
|
|
# For a full documentation of the template you can refer to: https://jinja.palletsprojects.com/en/3.1.x/templates/
|
|
# In Yunohost context there are no really some specificity except that all variable passed are of type string.
|
|
# So here are some example of recommended usage:
|
|
#
|
|
# If you need a conditional block
|
|
#
|
|
# {% if should_my_block_be_shown == 'true' %}
|
|
# ...
|
|
# {% endif %}
|
|
#
|
|
# or
|
|
#
|
|
# {% if should_my_block_be_shown == '1' %}
|
|
# ...
|
|
# {% endif %}
|
|
#
|
|
# If you need to iterate with loop:
|
|
#
|
|
# {% for yolo in var_with_multiline_value.splitlines() %}
|
|
# ...
|
|
# {% endfor %}
|
|
#
|
|
# or
|
|
#
|
|
# {% for jail in my_var_with_coma.split(',') %}
|
|
# ...
|
|
# {% endfor %}
|
|
#
|
|
# The helper will verify the checksum and backup the destination file
|
|
# if it's different before applying the new template.
|
|
#
|
|
# And it will calculate and store the destination file checksum
|
|
# into the app settings when configuration is done.
|
|
#
|
|
# Requires YunoHost version 4.1.0 or higher.
|
|
ynh_add_config() {
|
|
# ============ Argument parsing =============
|
|
local -A args_array=([t]=template= [d]=destination= [j]=jinja)
|
|
local template
|
|
local destination
|
|
local jinja
|
|
ynh_handle_getopts_args "$@"
|
|
jinja="${jinja:-0}"
|
|
# ===========================================
|
|
|
|
local template_path
|
|
if [ -f "$YNH_APP_BASEDIR/conf/$template" ]; then
|
|
template_path="$YNH_APP_BASEDIR/conf/$template"
|
|
elif [ -f "$template" ]; then
|
|
template_path=$template
|
|
else
|
|
ynh_die --message="The provided template $template doesn't exist"
|
|
fi
|
|
|
|
ynh_backup_if_checksum_is_different --file="$destination"
|
|
|
|
# Make sure to set the permissions before we copy the file
|
|
# This is to cover a case where an attacker could have
|
|
# created a file beforehand to have control over it
|
|
# (cp won't overwrite ownership / modes by default...)
|
|
touch $destination
|
|
chmod 640 $destination
|
|
_ynh_apply_default_permissions $destination
|
|
|
|
if [[ "$jinja" == 1 ]]
|
|
then
|
|
# This is ran in a subshell such that the "export" does not "contaminate" the main process
|
|
(
|
|
export $(compgen -v)
|
|
j2 "$template_path" -f env -o $destination
|
|
)
|
|
else
|
|
cp -f "$template_path" "$destination"
|
|
ynh_replace_vars --file="$destination"
|
|
fi
|
|
|
|
ynh_store_file_checksum --file="$destination"
|
|
}
|
|
|
|
# Replace variables in a file
|
|
#
|
|
# [internal]
|
|
#
|
|
# usage: ynh_replace_vars --file="file"
|
|
# | arg: -f, --file= - File where to replace variables
|
|
#
|
|
# The helper will replace the following keywords with global variables
|
|
# that should be defined before calling this helper :
|
|
# __PATH__ by $path_url
|
|
# __USER__ by $app
|
|
# __YNH_NODE_LOAD_PATH__ by $ynh_node_load_PATH
|
|
#
|
|
# And any dynamic variables that should be defined before calling this helper like:
|
|
# __DOMAIN__ by $domain
|
|
# __APP__ by $app
|
|
# __VAR_1__ by $var_1
|
|
# __VAR_2__ by $var_2
|
|
#
|
|
# Requires YunoHost version 4.1.0 or higher.
|
|
ynh_replace_vars() {
|
|
# ============ Argument parsing =============
|
|
local -A args_array=([f]=file=)
|
|
local file
|
|
ynh_handle_getopts_args "$@"
|
|
# ===========================================
|
|
|
|
# Replace specific YunoHost variables
|
|
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="$file"
|
|
ynh_replace_string --match_string="__PATH__" --replace_string="$path_url" --target_file="$file"
|
|
fi
|
|
if test -n "${app:-}"; then
|
|
ynh_replace_string --match_string="__USER__" --replace_string="$app" --target_file="$file"
|
|
fi
|
|
if test -n "${ynh_node_load_PATH:-}"; then
|
|
ynh_replace_string --match_string="__YNH_NODE_LOAD_PATH__" --replace_string="$ynh_node_load_PATH" --target_file="$file"
|
|
fi
|
|
|
|
# Replace others variables
|
|
|
|
# List other unique (__ __) variables in $file
|
|
local uniques_vars=($(grep -oP '__[A-Z0-9]+?[A-Z0-9_]*?[A-Z0-9]*?__' $file | sort --unique | sed "s@__\([^.]*\)__@\L\1@g"))
|
|
|
|
set +o xtrace # set +x
|
|
|
|
# Do the replacement
|
|
local delimit=@
|
|
for one_var in "${uniques_vars[@]}"; do
|
|
# Validate that one_var is indeed defined
|
|
# -v checks if the variable is defined, for example:
|
|
# -v FOO tests if $FOO is defined
|
|
# -v $FOO tests if ${!FOO} is defined
|
|
# More info: https://stackoverflow.com/questions/3601515/how-to-check-if-a-variable-is-set-in-bash/17538964#comment96392525_17538964
|
|
[[ -v "${one_var:-}" ]] || ynh_die --message="Variable \$$one_var wasn't initialized when trying to replace __${one_var^^}__ in $file"
|
|
|
|
# Escape delimiter in match/replace string
|
|
match_string="__${one_var^^}__"
|
|
match_string=${match_string//${delimit}/"\\${delimit}"}
|
|
replace_string="${!one_var}"
|
|
replace_string=${replace_string//\\/\\\\}
|
|
replace_string=${replace_string//${delimit}/"\\${delimit}"}
|
|
|
|
# Actually replace (sed is used instead of ynh_replace_string to avoid triggering an epic amount of debug logs)
|
|
sed --in-place "s${delimit}${match_string}${delimit}${replace_string}${delimit}g" "$file"
|
|
done
|
|
set -o xtrace # set -x
|
|
}
|
|
|
|
# Get a value from heterogeneous file (yaml, json, php, python...)
|
|
#
|
|
# usage: ynh_read_var_in_file --file=PATH --key=KEY
|
|
# | arg: -f, --file= - the path to the file
|
|
# | arg: -k, --key= - the key to get
|
|
# | arg: -a, --after= - the line just before the key (in case of multiple lines with the name of the key in the file)
|
|
#
|
|
# This helpers match several var affectation use case in several languages
|
|
# We don't use jq or equivalent to keep comments and blank space in files
|
|
# This helpers work line by line, it is not able to work correctly
|
|
# if you have several identical keys in your files
|
|
#
|
|
# Example of line this helpers can managed correctly
|
|
# .yml
|
|
# title: YunoHost documentation
|
|
# email: 'yunohost@yunohost.org'
|
|
# .json
|
|
# "theme": "colib'ris",
|
|
# "port": 8102
|
|
# "some_boolean": false,
|
|
# "user": null
|
|
# .ini
|
|
# some_boolean = On
|
|
# action = "Clear"
|
|
# port = 20
|
|
# .php
|
|
# $user=
|
|
# user => 20
|
|
# .py
|
|
# USER = 8102
|
|
# user = 'https://donate.local'
|
|
# CUSTOM['user'] = 'YunoHost'
|
|
#
|
|
# Requires YunoHost version 4.3 or higher.
|
|
ynh_read_var_in_file() {
|
|
# ============ Argument parsing =============
|
|
local -A args_array=([f]=file= [k]=key= [a]=after=)
|
|
local file
|
|
local key
|
|
local after
|
|
ynh_handle_getopts_args "$@"
|
|
after="${after:-}"
|
|
# ===========================================
|
|
|
|
[[ -f $file ]] || ynh_die --message="File $file does not exists"
|
|
|
|
set +o xtrace # set +x
|
|
|
|
# Get the line number after which we search for the variable
|
|
local line_number=1
|
|
if [[ -n "$after" ]]; then
|
|
line_number=$(grep -m1 -n $after $file | cut -d: -f1)
|
|
if [[ -z "$line_number" ]]; then
|
|
set -o xtrace # set -x
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
local filename="$(basename -- "$file")"
|
|
local ext="${filename##*.}"
|
|
local endline=',;'
|
|
local assign="=>|:|="
|
|
local comments="#"
|
|
local string="\"'"
|
|
if [[ "$ext" =~ ^ini|env|toml|yml|yaml$ ]]; then
|
|
endline='#'
|
|
fi
|
|
if [[ "$ext" =~ ^ini|env$ ]]; then
|
|
comments="[;#]"
|
|
fi
|
|
if [[ "php" == "$ext" ]] || [[ "$ext" == "js" ]]; then
|
|
comments="//"
|
|
fi
|
|
local list='\[\s*['$string']?\w+['$string']?\]'
|
|
local var_part='^\s*((const|var|let)\s+)?\$?(\w+('$list')*(->|\.|\[))*\s*'
|
|
var_part+="[$string]?${key}[$string]?"
|
|
var_part+='\s*\]?\s*'
|
|
var_part+="($assign)"
|
|
var_part+='\s*'
|
|
|
|
# Extract the part after assignation sign
|
|
local expression_with_comment="$((tail +$line_number ${file} | grep -i -o -P $var_part'\K.*$' || echo YNH_NULL) | head -n1)"
|
|
if [[ "$expression_with_comment" == "YNH_NULL" ]]; then
|
|
set -o xtrace # set -x
|
|
echo YNH_NULL
|
|
return 0
|
|
fi
|
|
|
|
# Remove comments if needed
|
|
local expression="$(echo "$expression_with_comment" | sed "s@${comments}[^$string]*\$@@g" | sed "s@\s*[$endline]*\s*]*\$@@")"
|
|
|
|
local first_char="${expression:0:1}"
|
|
if [[ "$first_char" == '"' ]]; then
|
|
echo "$expression" | grep -m1 -o -P '"\K([^"](\\")?)*[^\\](?=")' | head -n1 | sed 's/\\"/"/g'
|
|
elif [[ "$first_char" == "'" ]]; then
|
|
echo "$expression" | grep -m1 -o -P "'\K([^'](\\\\')?)*[^\\\\](?=')" | head -n1 | sed "s/\\\\'/'/g"
|
|
else
|
|
echo "$expression"
|
|
fi
|
|
set -o xtrace # set -x
|
|
}
|
|
|
|
# Set a value into heterogeneous file (yaml, json, php, python...)
|
|
#
|
|
# usage: ynh_write_var_in_file --file=PATH --key=KEY --value=VALUE
|
|
# | arg: -f, --file= - the path to the file
|
|
# | arg: -k, --key= - the key to set
|
|
# | arg: -v, --value= - the value to set
|
|
# | arg: -a, --after= - the line just before the key (in case of multiple lines with the name of the key in the file)
|
|
#
|
|
# Requires YunoHost version 4.3 or higher.
|
|
ynh_write_var_in_file() {
|
|
# ============ Argument parsing =============
|
|
local -A args_array=([f]=file= [k]=key= [v]=value= [a]=after=)
|
|
local file
|
|
local key
|
|
local value
|
|
local after
|
|
ynh_handle_getopts_args "$@"
|
|
after="${after:-}"
|
|
# ===========================================
|
|
|
|
[[ -f $file ]] || ynh_die --message="File $file does not exists"
|
|
|
|
set +o xtrace # set +x
|
|
|
|
# Get the line number after which we search for the variable
|
|
local after_line_number=1
|
|
if [[ -n "$after" ]]; then
|
|
after_line_number=$(grep -m1 -n $after $file | cut -d: -f1)
|
|
if [[ -z "$after_line_number" ]]; then
|
|
set -o xtrace # set -x
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
local filename="$(basename -- "$file")"
|
|
local ext="${filename##*.}"
|
|
local endline=',;'
|
|
local assign="=>|:|="
|
|
local comments="#"
|
|
local string="\"'"
|
|
if [[ "$ext" =~ ^ini|env|toml|yml|yaml$ ]]; then
|
|
endline='#'
|
|
fi
|
|
if [[ "$ext" =~ ^ini|env$ ]]; then
|
|
comments="[;#]"
|
|
fi
|
|
if [[ "php" == "$ext" ]] || [[ "$ext" == "js" ]]; then
|
|
comments="//"
|
|
fi
|
|
local list='\[\s*['$string']?\w+['$string']?\]'
|
|
local var_part='^\s*((const|var|let)\s+)?\$?(\w+('$list')*(->|\.|\[))*\s*'
|
|
var_part+="[$string]?${key}[$string]?"
|
|
var_part+='\s*\]?\s*'
|
|
var_part+="($assign)"
|
|
var_part+='\s*'
|
|
|
|
# Extract the part after assignation sign
|
|
local expression_with_comment="$((tail +$after_line_number ${file} | grep -i -o -P $var_part'\K.*$' || echo YNH_NULL) | head -n1)"
|
|
if [[ "$expression_with_comment" == "YNH_NULL" ]]; then
|
|
set -o xtrace # set -x
|
|
return 1
|
|
fi
|
|
local value_line_number="$(tail +$after_line_number ${file} | grep -m1 -n -i -P $var_part'\K.*$' | cut -d: -f1)"
|
|
value_line_number=$((after_line_number + value_line_number))
|
|
local range="${after_line_number},${value_line_number} "
|
|
|
|
# Remove comments if needed
|
|
local expression="$(echo "$expression_with_comment" | sed "s@${comments}[^$string]*\$@@g" | sed "s@\s*[$endline]*\s*]*\$@@")"
|
|
endline=${expression_with_comment#"$expression"}
|
|
endline="$(echo "$endline" | sed 's/\\/\\\\/g')"
|
|
value="$(echo "$value" | sed 's/\\/\\\\/g')"
|
|
value=${value//&/"\&"}
|
|
local first_char="${expression:0:1}"
|
|
delimiter=$'\001'
|
|
if [[ "$first_char" == '"' ]]; then
|
|
# \ and sed is quite complex you need 2 \\ to get one in a sed
|
|
# So we need \\\\ to go through 2 sed
|
|
value="$(echo "$value" | sed 's/"/\\\\"/g')"
|
|
sed -ri "${range}s$delimiter"'(^'"${var_part}"'")([^"]|\\")*("[\s;,]*)(\s*'$comments'.*)?$'$delimiter'\1'"${value}"'"'"${endline}${delimiter}i" ${file}
|
|
elif [[ "$first_char" == "'" ]]; then
|
|
# \ and sed is quite complex you need 2 \\ to get one in a sed
|
|
# However double quotes implies to double \\ to
|
|
# So we need \\\\\\\\ to go through 2 sed and 1 double quotes str
|
|
value="$(echo "$value" | sed "s/'/\\\\\\\\'/g")"
|
|
sed -ri "${range}s$delimiter(^${var_part}')([^']|\\')*('"'[\s,;]*)(\s*'$comments'.*)?$'$delimiter'\1'"${value}'${endline}${delimiter}i" ${file}
|
|
else
|
|
if [[ "$value" == *"'"* ]] || [[ "$value" == *'"'* ]] || [[ "$ext" =~ ^php|py|json|js$ ]]; then
|
|
value='\"'"$(echo "$value" | sed 's/"/\\\\"/g')"'\"'
|
|
fi
|
|
if [[ "$ext" =~ ^yaml|yml$ ]]; then
|
|
value=" $value"
|
|
fi
|
|
sed -ri "${range}s$delimiter(^${var_part}).*\$$delimiter\1${value}${endline}${delimiter}i" ${file}
|
|
fi
|
|
set -o xtrace # set -x
|
|
}
|
|
|
|
# Render templates with Jinja2
|
|
#
|
|
# [internal]
|
|
#
|
|
# Attention : Variables should be exported before calling this helper to be
|
|
# accessible inside templates.
|
|
#
|
|
# usage: ynh_render_template some_template output_path
|
|
# | arg: some_template - Template file to be rendered
|
|
# | arg: output_path - The path where the output will be redirected to
|
|
ynh_render_template() {
|
|
local template_path=$1
|
|
local output_path=$2
|
|
mkdir -p "$(dirname $output_path)"
|
|
# Taken from https://stackoverflow.com/a/35009576
|
|
python3 -c 'import os, sys, jinja2; sys.stdout.write(
|
|
jinja2.Template(sys.stdin.read()
|
|
).render(os.environ));' <$template_path >$output_path
|
|
}
|