You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ublinux-init/ublinux/rc.local.d/98-ubpile

321 lines
20 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/usr/bin/env bash
#
# Author: Dmitry Razumov <asmeron@ublinux.com>
# Copyright (c) 2021-2025 UBLinux <support@ublinux.com>
#
ENABLED=yes
[[ ${ENABLED} == "yes" ]] || exit 0
DEBUGMODE=no
SOURCE=/usr/lib/ublinux/functions; [[ -f ${SOURCE} ]] && . ${SOURCE} 2>/dev/null || exit 0
SOURCE=/usr/lib/ublinux/default; [[ -f ${SOURCE} ]] && . ${SOURCE} 2>/dev/null || exit 0
SOURCE=${SYSCONF}/config; [[ -f ${SOURCE} ]] && . ${SOURCE} 2>/dev/null
SOURCE=${SYSCONF}/server; [[ -f ${SOURCE} ]] && . ${SOURCE} 2>/dev/null
debug_mode "$0" "$@"
if [[ -d "/opt/ubpile" ]]; then
APP_UBPILE="ubpile"
PATH_UBPILE="/opt/${APP_UBPILE}"
UBPILE_CONF_JSON_TEMPLATE="${PATH_UBPILE}/sample_conf/config.json"
fi
if [[ -d "/opt/ubpile-workflow" ]]; then
APP_UBPILE_WORKFLOW="ubpile-workflow"
PATH_UBPILE="/opt/${APP_UBPILE_WORKFLOW}"
PATH_CONF_JSON_TAMPLATE="/usr/share/${APP_UBPILE_WORKFLOW}"
UBPILE_CONF_JSON_TEMPLATE="${PATH_CONF_JSON_TAMPLATE}/sample_conf/config.json"
PATH_STORAGE_DB_TAMPLATE="${PATH_CONF_JSON_TAMPLATE}/storage"
fi
UBPILE_DATA="${PATH_UBPILE}/data"
UBPILE_CONF="${PATH_UBPILE}/conf"
UBPILE_CONF_JSON="${UBPILE_CONF}/config.json"
UBPILE_STORAGE_CONF_JSON="${UBPILE_CONF}/storage.json"
UBPILE_SECRET_KEY=${UBPILE_CONF}/secret_key
PATH_HAPROXY_UBPILE_CONF="/etc/haproxy/haproxy-ubpile.cfg"
## Настройка база данных UBPile, файл настроек /opt/ubpile-workflow/conf/storage.json имеет приоритет над /opt/ubpile-workflow/conf/config.json
## По умолчанию *fs (filesystem)
## UBPILE_DB=*fs|sqlite|level|lmdb|mssql|mysql|oracle|postgres|redis|s3|sftp
## UBPILE_DB[SQL.connection.filename|...]=<value>
## SQL.connection.filename # Для UBPILE_DB=sqlite местоположение файла БД, по умолчанию корень приложения /opt/ubpile-workflow
## UBPILE_DB[SQL.connection.filename]=ubpile.db
exec_01_ubpile_db(){
[[ -n ${APP_UBPILE} || -n ${APP_UBPILE_WORKFLOW} ]] || return 0
[[ $1 == @("set="|"set+="|"set++="|"set-="|"set--="|"remove") ]] && COMMAND=$1 && shift
[[ -n ${COMMAND} ]] || COMMAND="set="
local PARAM="$@"
if [[ -n ${PARAM} ]]; then
local UBPILE=
declare -A UBPILE
[[ ${PARAM%%=*} =~ [!\$%\&()*+,/\;\<\=\>?\^\{|\}~] ]] || eval "${PARAM%%=*}=\${PARAM#*=}"
fi
[[ -n ${UBPILE_DB[0]} ]] && case ${UBPILE_DB[0]} in
fs) FILE_STORAGE_DB_TAMPLATE=${PATH_STORAGE_DB_TAMPLATE}/storage.fs.json ;;
level) FILE_STORAGE_DB_TAMPLATE=${PATH_STORAGE_DB_TAMPLATE}/storage.level.json ;;
lmdb) FILE_STORAGE_DB_TAMPLATE=${PATH_STORAGE_DB_TAMPLATE}/storage.lmdb.json ;;
mssql) FILE_STORAGE_DB_TAMPLATE=${PATH_STORAGE_DB_TAMPLATE}/storage.mssql.json ;;
mysql) FILE_STORAGE_DB_TAMPLATE=${PATH_STORAGE_DB_TAMPLATE}/storage.mysql.json ;;
oracle) FILE_STORAGE_DB_TAMPLATE=${PATH_STORAGE_DB_TAMPLATE}/storage.oracle.json ;;
postgres) FILE_STORAGE_DB_TAMPLATE=${PATH_STORAGE_DB_TAMPLATE}/storage.postgres.json ;;
redis) FILE_STORAGE_DB_TAMPLATE=${PATH_STORAGE_DB_TAMPLATE}/storage.redis.json ;;
s3) FILE_STORAGE_DB_TAMPLATE=${PATH_STORAGE_DB_TAMPLATE}/storage.s3.json ;;
sftp) FILE_STORAGE_DB_TAMPLATE=${PATH_STORAGE_DB_TAMPLATE}/storage.sftp.json ;;
sqlite) FILE_STORAGE_DB_TAMPLATE=${PATH_STORAGE_DB_TAMPLATE}/storage.sqlite.json ;;
*) true ;;
esac
if [[ ${COMMAND} == @("set="|"set+="|"set++=") ]] && [[ ${#UBPILE_DB[@]} != 0 ]]; then
[[ -n ${UBPILE_DB[0]} ]] && cp -f ${FILE_STORAGE_DB_TAMPLATE} ${UBPILE_STORAGE_CONF_JSON}
local STRING_ARG_CONF=
if [[ -f ${UBPILE_STORAGE_CONF_JSON} ]]; then
for NAME_ARG_CONF in "${!UBPILE_DB[@]}"; do
if [[ ${NAME_ARG_CONF} != 0 ]] && [[ ${UBPILE_DB[${NAME_ARG_CONF}]} =~ ^([[:digit:]]*|true|false)$ ]]; then
STRING_ARG_CONF+=".${NAME_ARG_CONF} = ${UBPILE_DB[${NAME_ARG_CONF}]} | "
elif [[ ${NAME_ARG_CONF} != 0 ]]; then
STRING_ARG_CONF+=".${NAME_ARG_CONF} = \"${UBPILE_DB[${NAME_ARG_CONF}]}\" | "
fi
done
[[ -z ${STRING_ARG_CONF} ]] || echo -E "$(jq "${STRING_ARG_CONF%|*}" ${UBPILE_STORAGE_CONF_JSON})" > ${UBPILE_STORAGE_CONF_JSON}
else
true
fi
elif [[ ${COMMAND} == @("set-="|"set--="|"remove") ]]; then
if [[ ${PARAM%%=*} =~ ^.*'['(.*)']' ]]; then
NAME_VAR=${BASH_REMATCH[1]}
TEMPLATE_VALUE=$(jq ".${NAME_VAR}" ${UBPILE_CONF_JSON_TEMPLATE})
[[ -f ${UBPILE_STORAGE_CONF_JSON} ]] && echo -E "$(jq ".${NAME_VAR} = ${TEMPLATE_VALUE}" ${UBPILE_STORAGE_CONF_JSON})" > ${UBPILE_STORAGE_CONF_JSON}
echo "INFO: you need restart systemd service UBPile"
#systemctl --quiet is-active ubpile.service &>/dev/null && systemctl --quiet restart ubpile.service &>/dev/null
else
rm -f ${UBPILE_STORAGE_CONF_JSON} || true
fi
fi
}
## Настройки UBPile
## UBPILE=manager|admin|primary|worker|disable|clean
## manager|admin|primary # Выступать в роли сервера администратора/менеджера управляющего всеми исполнителями 'worker'
## worker # Исполнитель заданий приходящих от сервера администратора
## disable # Принудительно отключить
## clean # Очистить БД и вернуть настройки по умолчанию. После процедуры очистки установится UBPILE=disable
##
## UBPILE[secret_key|base_app_url|...]=<value>
## secret_key # При настройке нескольких серверов все ваши серверы должны иметь один и тот же секретный ключ. Любая случайно сгенерированная строка подойдет
## # Если указать <value>=random, будет сгенерирована случайная строка
## base_app_url # Полный URL-адрес, включая http порт, если он нестандартный. Это используется в электронных письмах для создания URL-адресов с самостоятельными ссылками
## email_from # Адрес электронной почты, который будет использоваться в качестве адреса "От" при отправке уведомлений
## smtp_hostname # Имя хоста вашего SMTP-сервера для отправки почты. Так-же это может быть `127.0.0.1` или `localhost`
## debug_level # Уровень детализации в журналах отладки. Он варьируется от 1 (очень тихо) до 10 (очень громко). Значение по умолчанию: 1
## job_memory_max # Максимальное ограничение памяти по умолчанию для каждого задания, по умолчанию: 1073741824 (1Гб)
##
## server_comm_use_hostnames # Разрешить серверам подключаться друг к другу по именам хостов, полезно с меняющимися IP адресами. Значения: *false, true
## web_direct_connect # Разрешить веб-интерфейсу прямое подключение к главному серверу, полезно при использовании нескольких резервных серверов
## # или балансировщик нагрузки или обратного прокси. Значения: *false, true
## web_socket_use_hostnames # Разрешить веб-интерфейсу подключаться к внутренним серверам, используя их имена хостов, а не IP-адреса. Значения: *false, true
## # Это свойство вступает в силу только в том случае, если web_direct_connect=true
## WebServer.http_port # Номер порта http веб-сервера для пользовательского интерфейса. По умолчанию: 9012
## WebServer.https # Включить HTTPS, чтобы пользовательский интерфейс был зашифрован SSL. Значения: *false, true
## WebServer.https_port # Номер порта https веб-сервера для пользовательского интерфейса. По умолчанию: 9013
## ... # Могут использоваться любые переменные конфигурационного файла config.json
## UBPILE[secret_key]="4a6ed27e0434490028ff63b11f12fb7c"
## UBPILE[base_app_url]="http://localhost:80"
## UBPILE[web_direct_connect]="true"
exec_02_ubpile(){
clean_db(){
[[ -n ${UBPILE_DB[0]} ]] || local UBPILE_DB="fs"
case ${UBPILE_DB[0]} in
fs)
[[ -d ${PATH_UBPILE}/data ]] && rm -rdf ${PATH_UBPILE}/data
;;
sqlite)
cd ${PATH_UBPILE}
# Получить имя из storage.json и его удалить
[[ -f ${UBPILE_STORAGE_CONF_JSON} ]] && FILE_SQLITE_DB=$(jq ".SQL.connection.filename" ${UBPILE_STORAGE_CONF_JSON} | tr -d '"' 2>/dev/null)
[[ -n ${FILE_SQLITE_DB} ]] || FILE_SQLITE_DB=${PATH_UBPILE}/ubpile.db
[[ -f ${FILE_SQLITE_DB} ]] && rm -f ${FILE_SQLITE_DB}
;;
*) true
;;
esac
}
ubpile_start(){
[[ -d ${UBPILE_DATA} ]] && chmod go-rwx ${UBPILE_DATA}
[[ -d ${UBPILE_CONF} ]] && chmod go-rwx ${UBPILE_CONF}
[[ -n ${APP_UBPILE} ]] && { systemctl --quiet enable ubpile.service &>/dev/null; systemctl --quiet restart ubpile.service &>/dev/null; }
[[ -n ${APP_UBPILE_WORKFLOW} ]] && { systemctl --quiet enable ubpile-workflow.service &>/dev/null; systemctl --quiet restart ubpile-workflow.service &>/dev/null; }
}
ubpile_stop(){
[[ -n ${APP_UBPILE} ]] && systemctl --quiet stop ubpile.service &>/dev/null
[[ -n ${APP_UBPILE_WORKFLOW} ]] && systemctl --quiet stop ubpile-workflow.service &>/dev/null
[[ -n ${APP_UBPILE} ]] && systemctl --quiet disable ubpile.service &>/dev/null
[[ -n ${APP_UBPILE_WORKFLOW} ]] && systemctl --quiet disable ubpile-workflow.service &>/dev/null
}
[[ -n ${APP_UBPILE} || -n ${APP_UBPILE_WORKFLOW} ]] || return 0
[[ ! -f ${UBPILE_CONF_JSON} || $(stat --printf="%s" ${UBPILE_CONF_JSON} 2>/dev/null) -lt 10 ]] && cp -f ${UBPILE_CONF_JSON_TEMPLATE} ${UBPILE_CONF_JSON}
[[ $1 == @("set="|"set+="|"set++="|"set-="|"set--="|"remove") ]] && COMMAND=$1 && shift
[[ -n ${COMMAND} ]] || COMMAND="set="
local PARAM="$@"
if [[ -n ${PARAM} ]]; then
local UBPILE=
declare -A UBPILE
[[ ${PARAM%%=*} =~ [!\$%\&()*+,/\;\<\=\>?\^\{|\}~] ]] || eval "${PARAM%%=*}=\${PARAM#*=}"
fi
if [[ ${COMMAND} == @("set="|"set+="|"set++=") ]] && [[ ${#UBPILE[@]} != 0 ]]; then
local STRING_ARG_CONF=
if [[ -f ${UBPILE_CONF_JSON} ]]; then
for NAME_ARG_CONF in "${!UBPILE[@]}"; do
if [[ ${NAME_ARG_CONF} != 0 && ${NAME_ARG_CONF} == "secret_key" ]]; then
[[ ${UBPILE[${NAME_ARG_CONF}]} == "secret" ]] && continue
if [[ ${UBPILE[${NAME_ARG_CONF}]} == "random" ]]; then
UBPILE[${NAME_ARG_CONF}]="$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1)"
ubconfig --target global set [server] UBPILE[${NAME_ARG_CONF}]="${UBPILE[${NAME_ARG_CONF}]}"
fi
[[ -n ${APP_UBPILE} ]] && STRING_ARG_CONF+=".${NAME_ARG_CONF} = \"${UBPILE[${NAME_ARG_CONF}]}\" | "
[[ -n ${APP_UBPILE_WORKFLOW} ]] && echo "${UBPILE[${NAME_ARG_CONF}]}" > ${UBPILE_SECRET_KEY} && chmod 400 ${UBPILE_SECRET_KEY}
elif [[ ${NAME_ARG_CONF} != 0 ]] && [[ ${UBPILE[${NAME_ARG_CONF}]} =~ ^([[:digit:]]*|true|false)$ ]]; then
STRING_ARG_CONF+=".${NAME_ARG_CONF} = ${UBPILE[${NAME_ARG_CONF}]} | "
elif [[ ${NAME_ARG_CONF} != 0 ]]; then
STRING_ARG_CONF+=".${NAME_ARG_CONF} = \"${UBPILE[${NAME_ARG_CONF}]}\" | "
fi
done
[[ -n ${STRING_ARG_CONF} ]] && echo -E "$(jq "${STRING_ARG_CONF%|*}" ${UBPILE_CONF_JSON})" > ${UBPILE_CONF_JSON}
fi
# Удалить ключ из системной конфигурации
[[ -n ${UBPILE[secret_key]} && -f ${SYSCONF}/server ]] && sed -E "s/(UBPILE\[secret_key\]=).*/\1=secret/" -i ${SYSCONF}/server
if [[ ${UBPILE[0]} == "clean" ]]; then
ubconfig set [server] UBPILE=disable
clean_db
elif [[ ${UBPILE[0]} == @("manager"|"admin"|"master"|"primary") ]]; then
ubpile_stop
${PATH_UBPILE}/bin/control.sh setup &>/dev/null
#node ${PATH_UBPILE}/bin/storage-cli.js setup
ubpile_start
elif [[ ${UBPILE[0]} == "worker" ]]; then
ubpile_stop
clean_db
ubpile_start
elif [[ ${UBPILE[0]} == "disable" ]]; then
ubpile_stop
else
echo "INFO: you need restart systemd service UBPile"
#systemctl --quiet is-active ubpile.service &>/dev/null && systemctl --quiet restart ubpile.service &>/dev/null
fi
elif [[ ${COMMAND} == @("set-="|"set--="|"remove") ]]; then
if [[ ${PARAM%%=*} =~ ^.*'['(.*)']' ]]; then
NAME_VAR=${BASH_REMATCH[1]}
if [[ -n ${APP_UBPILE_WORKFLOW} && ${NAME_VAR} == "secret_key" ]]; then
# Если удаляется ключ, то установить из default по умолчанию
SOURCE=/usr/lib/ublinux/default; [[ -f ${SOURCE} ]] && . ${SOURCE} 2>/dev/null
echo "${UBPILE[secret_key]}" > ${UBPILE_SECRET_KEY}
# Если удаляется ключ, то сгенирировать рандомный
#cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1 > ${UBPILE_SECRET_KEY}
#chmod 400 ${UBPILE_SECRET_KEY}
else
TEMPLATE_VALUE=$(jq ".${NAME_VAR}" ${UBPILE_CONF_JSON_TEMPLATE})
echo -E "$(jq ".${NAME_VAR} = ${TEMPLATE_VALUE}" ${UBPILE_CONF_JSON})" > ${UBPILE_CONF_JSON}
echo "INFO: you need restart systemd service UBPile"
#systemctl --quiet is-active ubpile.service &>/dev/null && systemctl --quiet restart ubpile.service &>/dev/null
fi
else
ubpile_stop || true
fi
fi
# Только для простой версии UBPILE
[[ -z ${APP_UBPILE_WORKFLOW} ]] || message_motd
}
## Использовать для UBPile преднастроенный обратный прокси, разместив приложение на указанном порту. Должен быть установлен: haproxy
## UBPILE_REVERSE_PROXY=enable|disable|<http_port>
## enable # Только включить автозапуск и запустить сервис haproxy-ubpile.service
## disable # Выключить и отключить автозапуск сервис haproxy-ubpile.service
## <http_port> # Настроить http порт, включить автозапуск и запустить сервис haproxy-ubpile.service
## # Внимание, обатить внимание на настройки UBPILE[server_comm_use_hostnames] и UBPILE[web_socket_use_hostnames]
## # Если требуется настройка SSL, то необходимо полученный сертификат вручную прописать в файле конфигурации /etc/haproxy/haproxy-ubpile.cfg
## UBPILE_REVERSE_PROXY=80
exec_03_ubpile_reverse_proxy(){
start_haproxy(){
systemctl --quiet enable haproxy-ubpile.service &>/dev/null
systemctl --quiet restart haproxy-ubpile.service &>/dev/null
}
stop_haproxy(){
systemctl --quiet stop haproxy-ubpile.service &>/dev/null
systemctl --quiet disable haproxy-ubpile.service &>/dev/null
}
[[ -n ${APP_UBPILE} || -n ${APP_UBPILE_WORKFLOW} ]] || return 0
[[ -f /usr/bin/haproxy ]] || return 0
local STRING_ARG_CONF=
[[ $1 == @("set="|"set+="|"set++="|"set-="|"set--="|"remove") ]] && COMMAND=$1 && shift
[[ -n ${COMMAND} ]] || COMMAND="set="
local PARAM="$@"
if [[ -n ${PARAM} ]]; then
local UBPILE_REVERSE_PROXY=
[[ ${PARAM%%=*} =~ [!\$%\&()*+,/\;\<\=\>?\^\{|\}~] ]] || eval "${PARAM%%=*}=\${PARAM#*=}"
fi
if [[ ${COMMAND} == @("set="|"set+="|"set++=") ]] && [[ -n ${UBPILE_REVERSE_PROXY} ]]; then
if [[ ${UBPILE_REVERSE_PROXY} == @(disable|no) ]]; then
stop_haproxy
elif [[ ${UBPILE_REVERSE_PROXY} == @(enable|yes) ]]; then
UBPILE_PORT="$(sed -En '/^\s*frontend http/,/^\s*backend/{s/^\s*bind.*:([[:digit:]]*).*/\1/p}' ${PATH_HAPROXY_UBPILE_CONF})"
#[[ $(declare -p UBPILE 2>/dev/null) =~ "declare -A" && -n ${UBPILE[web_direct_connect]} ]] || { ubconfig set [server] UBPILE[web_direct_connect]="true"; RESTART_UBPILE=yes; }
#[[ $(declare -p UBPILE 2>/dev/null) =~ "declare -A" && -n ${UBPILE[base_app_url]} ]] || { ubconfig set [server] UBPILE[base_app_url]="http://localhost:${UBPILE_PORT:-80}"; RESTART_UBPILE=yes; }
RESTART_HAPROXY=yes
elif [[ ${UBPILE_REVERSE_PROXY} =~ ^[[:digit:]]*$ ]]; then
# Пропишем порт в конфиге haproxy
sed -E "/^\s*frontend http/,/^\s*backend/s/^\s*bind.*/ bind \*:${UBPILE_REVERSE_PROXY}/" -i ${PATH_HAPROXY_UBPILE_CONF}
# Если параметры не заданы в конфиге, то зададим
#ubconfig set [server] UBPILE[web_direct_connect]="true"
#ubconfig set [server] UBPILE[base_app_url]="http://localhost:${UBPILE_REVERSE_PROXY:-3012}"
RESTART_UBPILE=yes
RESTART_HAPROXY=yes
fi
elif [[ ${COMMAND} == @("set-="|"set--="|"remove") ]]; then
[[ -f ${UBPILE_CONF_JSON} ]] && UBPILE_PORT=$(jq '.WebServer.http_port' ${UBPILE_CONF_JSON})
[[ $(declare -p UBPILE 2>/dev/null) =~ "declare -A" && -n ${UBPILE[web_direct_connect]} ]] && { ubconfig remove [server] UBPILE[web_direct_connect]; RESTART_UBPILE=yes; }
[[ $(declare -p UBPILE 2>/dev/null) =~ "declare -A" && -n ${UBPILE[base_app_url]} ]] && { ubconfig remove [server] UBPILE[base_app_url]; RESTART_UBPILE=yes; }
stop_haproxy
fi
[[ -n ${RESTART_UBPILE} && -n ${APP_UBPILE} ]] && { systemctl --quiet is-active ubpile.service &>/dev/null && systemctl --quiet restart ubpile.service &>/dev/null; message_motd; }
[[ -n ${RESTART_UBPILE} && -n ${APP_UBPILE_WORKFLOW} ]] && systemctl --quiet is-active ubpile-workflow.service &>/dev/null && systemctl --quiet restart ubpile-workflow.service &>/dev/null
[[ -z ${RESTART_HAPROXY} ]] || start_haproxy
}
message_motd(){
[[ -d ${PATH_UBPILE} ]] || return 0
local UBPILE_RUN="/run/ubpile"
PC_HOSTNAME="$(hostname -f)"
PC_IP="$(ip route get 1 2>/dev/null | sed -n 's/^.*src \([0-9.]*\) .*$/\1/p')"
install -dm0755 ${UBPILE_RUN}
[[ -f ${UBPILE_CONF_JSON} ]] && UBPILE_PORT=$(jq '.WebServer.http_port' ${UBPILE_CONF_JSON})
systemctl --quiet is-active haproxy-ubpile.service &>/dev/null && UBPILE_PORT="$(sed -En '/^\s*frontend http/,/^\s*backend/{s/^\s*bind.*:([[:digit:]]*).*/\1/p}' ${PATH_HAPROXY_UBPILE_CONF})"
systemctl --quiet is-active ubpile.service &>/dev/null && ln -snf active.motd ${UBPILE_RUN}/motd || ln -snf inactive.motd ${UBPILE_RUN}/motd
echo -e "Веб-консоль UBPile: http://${PC_HOSTNAME}:${UBPILE_PORT:-3012}/ или http://${PC_IP:-127.0.0.1}:${UBPILE_PORT:-3012}/\n" > ${UBPILE_RUN}/active.motd
echo -e "Запустить веб-консоль UBPile: systemctl enable --now ubpile.service\n" > ${UBPILE_RUN}/inactive.motd
}
################
##### MAIN #####
################
[[ -d ${PATH_UBPILE} ]] || exit 0
# Если файл подключен как ресурс с функциями, то выйти
return 0 2>/dev/null && return 0
if [[ -z $@ ]]; then
while read -ru3 FUNCTION; do
$"${FUNCTION##* }"
done 3< <(declare -F | grep "declare -f exec_")
else
FUNCTION=
while [[ $# -gt 0 ]]; do
[[ -z ${1} ]] || { declare -f ${1} &>/dev/null && FUNCTION+="; ${1}" || FUNCTION+=" '${1}'"; }
shift
done
eval ${FUNCTION#*; }
fi