Files
template/customer/usr/local/lib/libapplication.sh
T
2025-04-21 17:09:10 +08:00

439 lines
14 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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
}