#!/bin/bash # Get a value from heterogeneous file (yaml, json, php, python...) # # usage: ynh_value_get --file=PATH --key=KEY # | arg: -f, --file= - the path to the file # | arg: -k, --key= - the key to get # # 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_value_get() { # Declare an array to define the options of this helper. local legacy_args=fk local -A args_array=( [f]=file= [k]=key= ) local file local key # Manage arguments with getopts ynh_handle_getopts_args "$@" local var_part='^[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*' local crazy_value="$((grep -i -o -P '^[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*\K.*(?=[ \t,\n;]*$)' ${file} || echo YNH_NULL) | head -n1)" #" local first_char="${crazy_value:0:1}" if [[ "$first_char" == '"' ]] ; then echo "$crazy_value" | grep -m1 -o -P '"\K([^"](\\")?)*[^\\](?=")' | head -n1 | sed 's/\\"/"/g' elif [[ "$first_char" == "'" ]] ; then echo "$crazy_value" | grep -m1 -o -P "'\K([^'](\\\\')?)*[^\\\\](?=')" | head -n1 | sed "s/\\\\'/'/g" else echo "$crazy_value" fi } # Set a value into heterogeneous file (yaml, json, php, python...) # # usage: ynh_value_set --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 # # Requires YunoHost version 4.3 or higher. ynh_value_set() { # Declare an array to define the options of this helper. local legacy_args=fkv local -A args_array=( [f]=file= [k]=key= [v]=value=) local file local key local value # Manage arguments with getopts ynh_handle_getopts_args "$@" local var_part='[ \t]*\$?(\w*\[)?[ \t]*["'"']?${key}['"'"]?[ \t]*\]?[ \t]*[:=]>?[ \t]*' local crazy_value="$(grep -i -o -P "^${var_part}\K.*(?=[ \t,\n;]*\$)" ${file} | head -n1)" local first_char="${crazy_value:0:1}" if [[ "$first_char" == '"' ]] ; then value="$(echo "$value" | sed 's/"/\"/g')" sed -ri 's%^('"${var_part}"'")[^"]*("[ \t;,]*)$%\1'"${value}"'\3%i' ${file} elif [[ "$first_char" == "'" ]] ; then value="$(echo "$value" | sed "s/'/"'\'"'/g")" sed -ri "s%^(${var_part}')[^']*('"'[ \t,;]*)$%\1'"${value}"'\3%i' ${file} else if [[ "$value" == *"'"* ]] || [[ "$value" == *'"'* ]] ; then value='\"'"$(echo "$value" | sed 's/"/\"/g')"'\"' fi sed -ri "s%^(${var_part}).*"'$%\1'"${value}"'%i' ${file} fi } _ynh_panel_get() { # From settings local lines lines=`python3 << EOL import toml from collections import OrderedDict with open("../config_panel.toml", "r") as f: file_content = f.read() loaded_toml = toml.loads(file_content, _dict=OrderedDict) for panel_name, panel in loaded_toml.items(): if not isinstance(panel, dict): continue for section_name, section in panel.items(): if not isinstance(section, dict): continue for name, param in section.items(): if not isinstance(param, dict): continue print(';'.join([ name, param.get('type', 'string'), param.get('source', 'settings' if param.get('type', 'string') != 'file' else '') ])) EOL ` for line in $lines do IFS=';' read short_setting type source <<< "$line" local getter="get__${short_setting}" sources[${short_setting}]="$source" types[${short_setting}]="$type" file_hash[${short_setting}]="" formats[${short_setting}]="" # Get value from getter if exists if type -t $getter 2>/dev/null | grep -q '^function$' 2>/dev/null; then old[$short_setting]="$($getter)" formats[${short_setting}]="yaml" elif [[ "$source" == "" ]] ; then old[$short_setting]="YNH_NULL" # Get value from app settings or from another file elif [[ "$type" == "file" ]] ; then if [[ "$source" == "settings" ]] ; then ynh_die "File '${short_setting}' can't be stored in settings" fi old[$short_setting]="$(ls $(echo $source | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/) 2> /dev/null || echo YNH_NULL)" file_hash[$short_setting]="true" # Get multiline text from settings or from a full file elif [[ "$type" == "text" ]] ; then if [[ "$source" == "settings" ]] ; then old[$short_setting]="$(ynh_app_setting_get $app $short_setting)" elif [[ "$source" == *":"* ]] ; then ynh_die "For technical reasons, multiline text '${short_setting}' can't be stored automatically in a variable file, you have to create custom getter/setter" else old[$short_setting]="$(cat $(echo $source | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/) 2> /dev/null || echo YNH_NULL)" fi # Get value from a kind of key/value file else if [[ "$source" == "settings" ]] ; then source=":/etc/yunohost/apps/$app/settings.yml" fi local source_key="$(echo "$source" | cut -d: -f1)" source_key=${source_key:-$short_setting} local source_file="$(echo "$source" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" old[$short_setting]="$(ynh_value_get --file="${source_file}" --key="${source_key}")" fi done } _ynh_panel_apply() { for short_setting in "${!old[@]}" do local setter="set__${short_setting}" local source="${sources[$short_setting]}" local type="${types[$short_setting]}" if [ "${changed[$short_setting]}" == "true" ] ; then # Apply setter if exists if type -t $setter 2>/dev/null | grep -q '^function$' 2>/dev/null; then $setter elif [[ "$source" == "" ]] ; then continue # Save in a file elif [[ "$type" == "file" ]] ; then if [[ "$source" == "settings" ]] ; then ynh_die "File '${short_setting}' can't be stored in settings" fi local source_file="$(echo "$source" | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" if [[ "${!short_setting}" == "" ]] ; then rm -f "$source_file" else cp "${!short_setting}" "$source_file" fi # Save value in app settings elif [[ "$source" == "settings" ]] ; then ynh_app_setting_set $app $short_setting "${!short_setting}" # Save multiline text in a file elif [[ "$type" == "text" ]] ; then if [[ "$source" == *":"* ]] ; then ynh_die "For technical reasons, multiline text '${short_setting}' can't be stored automatically in a variable file, you have to create custom getter/setter" fi local source_file="$(echo "$source" | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" echo "${!short_setting}" > "$source_file" # Set value into a kind of key/value file else local source_key="$(echo "$source" | cut -d: -f1)" source_key=${source_key:-$short_setting} local source_file="$(echo "$source" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" ynh_value_set --file="${source_file}" --key="${source_key}" --value="${!short_setting}" fi fi done } _ynh_panel_show() { for short_setting in "${!old[@]}" do if [[ "${old[$short_setting]}" != YNH_NULL ]] ; then if [[ "${formats[$short_setting]}" == "yaml" ]] ; then ynh_return "${short_setting}:" ynh_return "$(echo "${old[$short_setting]}" | sed 's/^/ /g')" else ynh_return "${short_setting}: "'"'"$(echo "${old[$short_setting]}" | sed 's/"/\\"/g' | sed ':a;N;$!ba;s/\n/\n\n/g')"'"' fi fi done } _ynh_panel_validate() { # Change detection ynh_script_progression --message="Checking what changed in the new configuration..." --weight=1 local is_error=true #for changed_status in "${!changed[@]}" for short_setting in "${!old[@]}" do changed[$short_setting]=false if [ -z ${!short_setting+x} ]; then # Assign the var with the old value in order to allows multiple # args validation declare "$short_setting"="${old[$short_setting]}" continue fi if [ ! -z "${file_hash[${short_setting}]}" ] ; then file_hash[old__$short_setting]="" file_hash[new__$short_setting]="" if [ -f "${old[$short_setting]}" ] ; then file_hash[old__$short_setting]=$(sha256sum "${old[$short_setting]}" | cut -d' ' -f1) if [ -z "${!short_setting}" ] ; then changed[$short_setting]=true is_error=false fi fi if [ -f "${!short_setting}" ] ; then file_hash[new__$short_setting]=$(sha256sum "${!short_setting}" | cut -d' ' -f1) if [[ "${file_hash[old__$short_setting]}" != "${file_hash[new__$short_setting]}" ]] then changed[$short_setting]=true is_error=false fi fi else if [[ "${!short_setting}" != "${old[$short_setting]}" ]] then changed[$short_setting]=true is_error=false fi fi done if [[ "$is_error" == "true" ]] then ynh_print_info "Nothing has changed" exit 0 fi # Run validation if something is changed ynh_script_progression --message="Validating the new configuration..." --weight=1 for short_setting in "${!old[@]}" do [[ "${changed[$short_setting]}" == "false" ]] && continue local result="" if type -t validate__$short_setting | grep -q '^function$' 2>/dev/null; then result="$(validate__$short_setting)" fi if [ -n "$result" ] then local key="YNH_ERROR_${short_setting}" ynh_return "$key: \"$result\"" is_error=true fi done if [[ "$is_error" == "true" ]] then exit 0 fi } ynh_panel_get() { _ynh_panel_get } ynh_panel_show() { _ynh_panel_show } ynh_panel_validate() { _ynh_panel_validate } ynh_panel_apply() { _ynh_panel_apply } ynh_panel_run() { declare -Ag old=() declare -Ag changed=() declare -Ag file_hash=() declare -Ag sources=() declare -Ag types=() declare -Ag formats=() case $1 in show) ynh_panel_get ynh_panel_show ;; apply) max_progression=4 ynh_script_progression --message="Reading config panel description and current configuration..." ynh_panel_get ynh_panel_validate ynh_script_progression --message="Applying the new configuration..." ynh_panel_apply ynh_script_progression --message="Configuration of $app completed" --last ;; esac }