439 lines
14 KiB
Bash
439 lines
14 KiB
Bash
#!/bin/bash
|
||
# Ver: 1.5 by Endial Fang (endial@126.com)
|
||
#
|
||
# 应用通用业务处理函数
|
||
|
||
. /usr/local/lib/libcommon.sh # 通用函数库
|
||
. /usr/local/lib/libfile.sh
|
||
. /usr/local/lib/libfs.sh
|
||
. /usr/local/lib/liblog.sh
|
||
. /usr/local/lib/libos.sh
|
||
. /usr/local/lib/libservice.sh
|
||
. /usr/local/lib/libvalidations.sh
|
||
|
||
# 应用环境变量初始化
|
||
# 环境变量格式:
|
||
# APP_<key>=<value>
|
||
app_env() {
|
||
cat << "EOF"
|
||
export ALLOW_ANONYMOUS="${ALLOW_ANONYMOUS:-no}"
|
||
|
||
# 应用路径参数(Dockerfile 已定义:APP_NAME、APP_VER,可能定义 APP_USER、APP_EXEC)
|
||
export APP_EXEC="${APP_EXEC:-${APP_NAME}}"
|
||
export APP_USER="${APP_USER:-${APP_NAME}}"
|
||
export APP_GROUP="${APP_GROUP:-${APP_USER}}"
|
||
export APP_HOME="${APP_HOME:-/srv/${APP_NAME}}"
|
||
export APP_BASE="${APP_BASE:-/usr/local/${APP_NAME}}"
|
||
|
||
export APP_DEF_DIR="${APP_BASE}/etc/${APP_NAME}"
|
||
export APP_CONF_DIR="/srv/${APP_NAME}/conf"
|
||
export APP_DATA_DIR="/srv/${APP_NAME}/data"
|
||
export APP_CERT_DIR="/srv/${APP_NAME}/cert"
|
||
export APP_debugIR="/srv/${APP_NAME}/log"
|
||
export APP_CACHE_DIR="/var/cache/${APP_NAME}"
|
||
export APP_RUN_DIR="/var/run/${APP_NAME}"
|
||
|
||
# 应用配置参数
|
||
export APP_CONF_FILE="${APP_CONF_FILE:-${APP_CONF_DIR}/default.conf}"
|
||
export APP_PID_FILE="${APP_PID_FILE:-${APP_RUN_DIR}/${APP_NAME}.pid}"
|
||
|
||
# 个性化变量
|
||
EOF
|
||
}
|
||
|
||
# 检测应用相应的配置文件是否存在,如果不存在,则从默认配置文件目录拷贝一份
|
||
# 默认配置文件路径:/etc/${APP_NAME}
|
||
# 目标配置文件路径:/srv/conf/${APP_NAME}
|
||
# 参数:
|
||
# $1 - 目标路径
|
||
# $2 - 源路径
|
||
# $* - 基础路径下的文件及目录列表,以" "分割
|
||
# 例子:
|
||
# ensure_config_file_exist /etc/${APP_NAME} conf.d server.conf
|
||
app_ensure_config_file_exist() {
|
||
local -r dist_path="${1:?dist paths is missing}"
|
||
local -r base_path="${2:?source paths is missing}"
|
||
local f=""
|
||
|
||
shift 2
|
||
debug "List to check in ${base_path}: $@"
|
||
while [ "$#" -gt 0 ]; do
|
||
f="${1}"
|
||
debug " Process \"${f}\""
|
||
if [ -d "${base_path}/${f}" ]; then
|
||
[[ ! -d "${dist_path}/${f}" ]] && debug " Create directory: ${dist_path}/${f}" && mkdir -p "${dist_path}/${f}"
|
||
[[ ! -z $(ls -A "${base_path}/${f}") ]] && app_ensure_config_file_exist "${dist_path}/${f}" "${base_path}/${f}" $(ls -A "${base_path}/${f}")
|
||
else
|
||
[[ ! -e "${dist_path}/${f}" ]] && debug " Copy: ${base_path}/${f} to ${dist_path}" && cp "${base_path}/${f}" "${dist_path}"
|
||
fi
|
||
shift
|
||
done
|
||
}
|
||
|
||
# 检测以 "<PREFIX>" 开头的环境变量,并更新指定配置文件中对应配置项的值
|
||
# 如果需要全部转换为小写,可使用命令: tr '[:upper:]' '[:lower:]'
|
||
# 环境变量与配置项替换规则 : 环境变量中下划线 ==> 配置参数中特殊字符
|
||
# - "_" ==> "_"(下划线)
|
||
# - "__" ==> "."(半角点)
|
||
# - "___" ==> "-"(中划线)
|
||
#
|
||
# 变量:
|
||
# $1 - 配置文件
|
||
# $2 - <PREFIX>前缀(不含结束的"_")
|
||
app_configure_from_environment() {
|
||
local confFile="${1:?missing file}"
|
||
local envPrefix="${2:-APP_CFG}"
|
||
|
||
debug "Configuration File: ${confFile}"
|
||
|
||
if [[ ${file#*.} != "xml" ]]; then
|
||
# 更新普通key-value配置文件,转换为小写后写入文件
|
||
for var in $(eval echo \${!${envPrefix}_@}); do
|
||
key="$(echo "$var" | sed -e 's/^'${envPrefix}'_//g' -e 's/___/-/g' -e 's/__/./g' | tr '[:upper:]' '[:lower:]')"
|
||
value="${!var}"
|
||
debug " ${key}: ${value}"
|
||
app_common_conf_set "$confFile" "$key" "$value"
|
||
done
|
||
else
|
||
# 更新xml配置文件,大小写不变写入文件
|
||
for var in $(eval echo \${!${envPrefix}_@}); do
|
||
key=$(echo "$var" | sed -e 's/^'${envPrefix}'_//g' -e 's/___/-/g' -e 's/__/./g')
|
||
value="${!var}"
|
||
debug " ${key}: ${value}"
|
||
app_common_xml_set "${confFile}" "${key}" "${value}"
|
||
done
|
||
fi
|
||
}
|
||
|
||
# 将变量配置更新至配置文件
|
||
# 参数:
|
||
# $1 - 文件
|
||
# $2 - 变量
|
||
# $3 - 值(列表)
|
||
app_common_conf_set() {
|
||
local file="${1:?missing file}"
|
||
local key="${2:?missing key}"
|
||
shift
|
||
shift
|
||
local values=("$@")
|
||
|
||
if [[ "${#values[@]}" -eq 0 ]]; then
|
||
error "missing value"
|
||
return 1
|
||
elif [[ "${#values[@]}" -ne 1 ]]; then
|
||
for i in "${!values[@]}"; do
|
||
app_common_conf_set "$file" "${key[$i]}" "${values[$i]}"
|
||
done
|
||
else
|
||
value="${values[0]}"
|
||
# Check if the value was set before
|
||
if grep -q "^[#\\s]*$key\s*=.*" "$file"; then
|
||
# Update the existing key
|
||
replace_in_file "$file" "^[#\\s]*${key}\s*=.*" "${key}=${value}" false
|
||
else
|
||
# 增加一个新的配置项;如果在其他位置有类似操作,需要注意换行
|
||
printf "%s=%s" "$key" "$value" >>"$file"
|
||
fi
|
||
fi
|
||
}
|
||
|
||
# 使用环境变量中配置,更新配置文件
|
||
app_update_conf() {
|
||
info "Update configure files from APP_CFG_*..."
|
||
app_configure_from_environment "${APP_CONF_FILE}" "APP_CFG"
|
||
}
|
||
|
||
# 生成默认配置文件
|
||
app_generate_conf() {
|
||
# 准备原始默认配置文件或生成空文件
|
||
cp "${APP_CONF_DIR}/app_sample.cfg" "${APP_CONF_FILE}"
|
||
|
||
echo "">> "${APP_CONF_FILE}"
|
||
|
||
# 根据容器参数,设置配置文件
|
||
app_common_conf_set "${APP_CONF_DIR}/log4j.properties" "zookeeper.console.threshold" "${ZOO_LOG_LEVEL}"
|
||
app_common_conf_set "${APP_CONF_DIR}/log4j.properties" "zookeeper.log.dir" "${APP_debugIR}"
|
||
|
||
app_update_conf
|
||
}
|
||
|
||
# 检测用户参数信息是否满足条件; 针对部分权限过于开放情况,打印提示信息
|
||
app_env_validate() {
|
||
local error_code=0
|
||
|
||
debug "Validating settings in ENV vars..."
|
||
|
||
print_validation_error() {
|
||
error "$1"
|
||
error_code=1
|
||
}
|
||
|
||
# 检测认证设置。如果不允许匿名登录,检测登录用户名及密码是否设置
|
||
if is_boolean_yes "${ALLOW_ANONYMOUS}"; then
|
||
warn "You have set the environment variable ALLOW_ANONYMOUS=${ALLOW_ANONYMOUS}. For safety reasons, do not use this flag in a production environment."
|
||
else
|
||
# 检测相应密码是否设置
|
||
# print_validation_error "The ZOO_ENABLE_AUTH environment variable does not configure authentication. Set the environment variable ALLOW_ANONYMOUS=yes to allow unauthenticated users to connect to ZooKeeper."
|
||
fi
|
||
|
||
# TODO: 其他参数检测
|
||
|
||
[[ "$error_code" -eq 0 ]] || exit "$error_code"
|
||
}
|
||
|
||
# 更改默认监听地址为 "*" 或 "0.0.0.0",以对容器外提供服务
|
||
app_enable_remote_connections() {
|
||
info "Modify default config to ENABLE external IP access"
|
||
|
||
}
|
||
|
||
# 更改默认监听地址为 "localhost" 或 "127.0.0.1",以禁止对容器外提供服务
|
||
app_disable_remote_connections() {
|
||
info "Modify default config to DISABLE external IP access"
|
||
|
||
}
|
||
|
||
# 检测依赖的服务端口是否就绪;该脚本依赖系统工具 'netcat'
|
||
# 参数:
|
||
# $1 - host:port
|
||
app_wait_service() {
|
||
local serviceInfo=${1:?Missing server info}
|
||
local service=${serviceInfo%%:*}
|
||
local port=${serviceInfo#*:}
|
||
local retry_seconds=5
|
||
local max_try=6
|
||
let i=1
|
||
|
||
if [[ -z "$(which nc)" ]]; then
|
||
install_pkg ncat
|
||
fi
|
||
|
||
info "[0/${max_try}] check for ${service}:${port}..."
|
||
|
||
set +e
|
||
nc -z ${service} ${port}
|
||
result=$?
|
||
|
||
until [ $result -eq 0 ]; do
|
||
if (( $i == ${max_try} )); then
|
||
error "${service}:${port} is still not available; giving up after ${max_try} tries."
|
||
exit 1
|
||
fi
|
||
debug " [$i/${max_try}] not available yet, try in ${retry_seconds}'s latter ..."
|
||
|
||
let "i++"
|
||
sleep ${retry_seconds}
|
||
|
||
nc -z ${service} ${port}
|
||
result=$?
|
||
done
|
||
|
||
set -e
|
||
info "[$i/${max_try}] ${service}:${port} is available."
|
||
}
|
||
|
||
# 以后台方式启动应用服务,并等待启动就绪
|
||
app_start_server_bg() {
|
||
app_is_server_running && return
|
||
|
||
info "Starting ${APP_NAME} in background..."
|
||
|
||
# 使用 debug_execute 并以守护进程方式启动服务
|
||
# debug_execute "zkServer.sh" start
|
||
# debug_execute "redis-server" "--daemonize" "yes"
|
||
|
||
# 直接使用应用命令并以守护进程方式启动服务
|
||
# 使用内置命令启动服务
|
||
# if is_boolean_yes ${ENV_DEBUG}; then
|
||
# "rabbitmq-server" &
|
||
# else
|
||
# "rabbitmq-server" >/dev/null 2>&1 &
|
||
# fi
|
||
|
||
# 使用端口检测方式
|
||
# app_wait_service "${APP_HOST}:${APP_PORT}"
|
||
|
||
# 使用 PID 文件检测方式
|
||
local counter=30
|
||
while app_is_server_not_running ; do
|
||
sleep 1
|
||
counter=$(( counter - 1 ))
|
||
if (( counter <= 0 )); then
|
||
error "${APP_NAME} is not ready after 30 seconds"
|
||
exit 1
|
||
fi
|
||
debug "Waiting for ${APP_NAME} to ready ... $counter"
|
||
done
|
||
}
|
||
|
||
# 停止应用服务
|
||
app_stop_server() {
|
||
app_is_server_not_running && return
|
||
|
||
info "Stopping ${APP_NAME} background service..."
|
||
|
||
# 使用 debug_execute 关闭服务
|
||
# debug_execute "zkServer.sh" stop
|
||
# debug_execute "redis-cli" shutdown
|
||
|
||
# 直接使用内置命令停止服务
|
||
# if is_boolean_yes ${ENV_DEBUG}; then
|
||
# rabbitmqctl stop
|
||
#else
|
||
# rabbitmqctl stop >/dev/null 2>&1
|
||
#fi
|
||
|
||
# 使用 PID 文件 kill 进程(不建议)
|
||
# stop_service_using_pid "$APP_PID_FILE"
|
||
|
||
# 检测停止是否完成
|
||
local counter=30
|
||
while app_is_server_running ; do
|
||
sleep 1
|
||
counter=$(( counter - 1 ))
|
||
if (( counter <= 0 )); then
|
||
error "${APP_NAME} is not stoped after 30 seconds"
|
||
exit 1
|
||
fi
|
||
debug "Waiting for ${APP_NAME} to stop ... $counter"
|
||
done
|
||
}
|
||
|
||
# 检测应用服务是否在后台运行中
|
||
app_is_server_running() {
|
||
local pid
|
||
pid="$(get_pid_from_file ${APP_PID_FILE})"
|
||
debug "Checking ${APP_NAME} running status with PID: ${pid}"
|
||
|
||
if [[ -n "${pid}" ]]; then
|
||
is_service_running "${pid}"
|
||
else
|
||
false
|
||
fi
|
||
}
|
||
|
||
app_is_server_not_running() {
|
||
if [[ app_is_server_running == false ]]; then
|
||
true
|
||
else
|
||
false
|
||
fi
|
||
}
|
||
|
||
# 清理初始化应用时生成的临时文件
|
||
app_clean_tmp_file() {
|
||
debug "Clean ${APP_NAME} tmp files ..."
|
||
|
||
local -r -a files=(
|
||
"${APP_PID_FILE}"
|
||
)
|
||
|
||
for file in ${files[@]}; do
|
||
if [[ -f "$file" ]]; then
|
||
debug " Remove $file"
|
||
rm -rf "$file"
|
||
fi
|
||
done
|
||
}
|
||
|
||
# 用户自定义的前置初始化操作,依次执行目录 preinit.d 中的初始化脚本
|
||
# 执行完毕后,生成以当前时间命名的'.tar'的文件,包含执行记录、已执行的脚本,同时删除已执行的脚本
|
||
app_custom_preinit() {
|
||
info "Process pre-init for ${APP_NAME}..."
|
||
|
||
# 检测用户配置文件目录是否存在 preinit.d 文件夹,如果存在,尝试执行目录中的初始化脚本
|
||
if [[ -d "${APP_CONF_DIR}/preinit.d" ]] && [[ -n $(find "${APP_CONF_DIR}/preinit.d/" -type f -regex ".*\.\(sh\)") ]]; then
|
||
echo "$(date '+%Y-%m-%d %H:%M:%S') : Init success." > "${APP_CONF_DIR}/preinit.d/preinit_info"
|
||
local tarFile="preinit-$(date '+%Y-%m-%d-%H%M%S').tar"
|
||
tar --remove-files -cf "${tarFile}" "${APP_CONF_DIR}/preinit.d/preinit_info"
|
||
|
||
for f in $(find "${APP_CONF_DIR}/preinit.d/" -type f -regex ".*\.\(sh\)" | sort); do
|
||
debug " Process: ${f}"
|
||
process_init_files "${f}"
|
||
tar --remove-files -f "${tarFile}" -r "${f}"
|
||
done
|
||
fi
|
||
}
|
||
|
||
# 应用默认初始化操作
|
||
app_default_init() {
|
||
info "Process default init for ${APP_NAME}..."
|
||
|
||
# 特殊情况下,生成默认配置文件(如默认不存在或仅存在 sample 配置文件)
|
||
app_generate_conf
|
||
|
||
# 更新配置文件
|
||
app_update_conf
|
||
|
||
# 启用远程访问
|
||
app_enable_remote_connections
|
||
}
|
||
|
||
# 用户自定义的应用初始化操作,依次执行目录 initdb.d 中的初始化脚本
|
||
# 执行完毕后,生成以当前时间命名的'.tar'的文件,包含执行记录、已执行的脚本,同时删除已执行的脚本
|
||
app_custom_init() {
|
||
info "Process DB-init for ${APP_NAME}..."
|
||
|
||
# 检测用户配置文件目录是否存在 preinit.d 文件夹,如果存在,尝试执行目录中的初始化脚本
|
||
if [[ -d "${APP_CONF_DIR}/initdb.d" ]] && [[ -n $(find "${APP_CONF_DIR}/initdb.d/" -type f -regex ".*\.\(sh\|sql\|sql.gz\)") ]]; then
|
||
echo "$(date '+%Y-%m-%d %H:%M:%S') : Init success." > "${APP_CONF_DIR}/initdb.d/dbinit_info"
|
||
local tarFile="initdb-$(date '+%Y-%m-%d-%H%M%S').tar"
|
||
tar --remove-files -cf "${tarFile}" "${APP_CONF_DIR}/initdb.d/initdb_info"
|
||
|
||
# 禁用远程访问,并启动后台服务
|
||
app_disable_remote_connections
|
||
app_start_server_bg
|
||
|
||
for f in $(find "${APP_CONF_DIR}/initdb.d/" -type f -regex ".*\.\(sh\|sql\|sql.gz\)" | sort); do
|
||
case "$f" in
|
||
*.sh)
|
||
debug " Process Shell: ${f}"
|
||
process_init_files "$f"
|
||
;;
|
||
*.sql)
|
||
debug " Process SQL: ${f}";
|
||
postgresql_execute "${PG_DATABASE}" "${PG_INITSCRIPTS_USERNAME}" "${PG_INITSCRIPTS_PASSWORD}" < "$f"
|
||
;;
|
||
*.sql.gz)
|
||
debug " Process SQLs in ${f}";
|
||
gunzip -c "$f" | postgresql_execute "${PG_DATABASE}" "${PG_INITSCRIPTS_USERNAME}" "${PG_INITSCRIPTS_PASSWORD}" <
|
||
;;
|
||
*)
|
||
debug " Ignoring $f"
|
||
;;
|
||
esac
|
||
|
||
tar --remove-files -f "${tarFile}" -r "${f}"
|
||
done
|
||
|
||
# 停止后台服务,并启用远程访问
|
||
app_stop_server
|
||
app_clean_tmp_file
|
||
app_enable_remote_connections
|
||
fi
|
||
}
|
||
|
||
# 设置环境变量 JVMFLAGS
|
||
# 参数:
|
||
# $1 - value
|
||
app_export_jvmflags() {
|
||
local -r value="${1:?value is required}"
|
||
|
||
export JVMFLAGS="${JVMFLAGS} ${value}"
|
||
echo "export JVMFLAGS=\"${JVMFLAGS}\"" > "${APP_CONF_DIR}/java.env"
|
||
}
|
||
|
||
# 配置 HEAP 大小
|
||
# 参数:
|
||
# $1 - HEAP 大小
|
||
app_configure_heap_size() {
|
||
local -r heap_size="${1:?heap_size is required}"
|
||
|
||
if [[ "${JVMFLAGS}" =~ -Xm[xs].*-Xm[xs] ]]; then
|
||
debug "Using specified values (JVMFLAGS=${JVMFLAGS})"
|
||
else
|
||
debug "Setting '-Xmx${heap_size}m -Xms${heap_size}m' heap options..."
|
||
app_export_jvmflags "-Xmx${heap_size}m -Xms${heap_size}m"
|
||
fi
|
||
}
|