Files
template/customer/usr/local/bin/common.sh
T
2023-09-22 09:04:45 +08:00

409 lines
13 KiB
Bash

#!/bin/bash
# Ver: 1.5 by Endial Fang (endial@126.com)
#
# 应用通用业务处理函数
. /colovu/lib/libcommon.sh # 通用函数库
. /colovu/lib/libfile.sh
. /colovu/lib/libfs.sh
. /colovu/lib/liblog.sh
. /colovu/lib/libos.sh
. /colovu/lib/libservice.sh
. /colovu/lib/libvalidations.sh
# 检测应用相应的配置文件是否存在,如果不存在,则从默认配置文件目录拷贝一份
# 默认配置文件路径:/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
LOG_D "List to check in ${base_path}: $@"
while [ "$#" -gt 0 ]; do
f="${1}"
LOG_D " Process \"${f}\""
if [ -d "${base_path}/${f}" ]; then
[[ ! -d "${dist_path}/${f}" ]] && LOG_D " 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}" ]] && LOG_D " 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}"
LOG_D "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}"
LOG_D " ${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}"
LOG_D " ${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
LOG_E "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() {
LOG_I "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_LOG_DIR}"
app_update_conf
}
# 检测用户参数信息是否满足条件; 针对部分权限过于开放情况,打印提示信息
app_verify_minimum_env() {
local error_code=0
LOG_D "Validating settings in ENV vars..."
print_validation_error() {
LOG_E "$1"
error_code=1
}
# 检测认证设置。如果不允许匿名登录,检测登录用户名及密码是否设置
if is_boolean_yes "${ALLOW_ANONYMOUS}"; then
LOG_W "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() {
LOG_I "Modify default config to ENABLE external IP access"
}
# 更改默认监听地址为 "localhost" 或 "127.0.0.1",以禁止对容器外提供服务
app_disable_remote_connections() {
LOG_I "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
LOG_I "[0/${max_try}] check for ${service}:${port}..."
set +e
nc -z ${service} ${port}
result=$?
until [ $result -eq 0 ]; do
if (( $i == ${max_try} )); then
LOG_E "${service}:${port} is still not available; giving up after ${max_try} tries."
exit 1
fi
LOG_D " [$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
LOG_I "[$i/${max_try}] ${service}:${port} is available."
}
# 以后台方式启动应用服务,并等待启动就绪
app_start_server_bg() {
app_is_server_running && return
LOG_I "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
LOG_E "${APP_NAME} is not ready after 30 seconds"
exit 1
fi
LOG_D "Waiting for ${APP_NAME} to ready ... $counter"
done
}
# 停止应用服务
app_stop_server() {
app_is_server_not_running && return
LOG_I "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
LOG_E "${APP_NAME} is not stoped after 30 seconds"
exit 1
fi
LOG_D "Waiting for ${APP_NAME} to stop ... $counter"
done
}
# 检测应用服务是否在后台运行中
app_is_server_running() {
local pid
pid="$(get_pid_from_file ${APP_PID_FILE})"
LOG_D "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() {
LOG_D "Clean ${APP_NAME} tmp files ..."
local -r -a files=(
"${APP_PID_FILE}"
)
for file in ${files[@]}; do
if [[ -f "$file" ]]; then
LOG_D " Remove $file"
rm -rf "$file"
fi
done
}
# 用户自定义的前置初始化操作,依次执行目录 preinit.d 中的初始化脚本
# 执行完毕后,生成以当前时间命名的'.tar'的文件,包含执行记录、已执行的脚本,同时删除已执行的脚本
app_custom_preinit() {
LOG_I "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
LOG_D " Process: ${f}"
process_init_files "${f}"
tar --remove-files -f "${tarFile}" -r "${f}"
done
fi
}
# 应用默认初始化操作
app_default_init() {
LOG_I "Process default init for ${APP_NAME}..."
# 特殊情况下,生成默认配置文件(如默认不存在或仅存在 sample 配置文件)
app_generate_conf
# 更新配置文件
app_update_conf
# 启用远程访问
app_enable_remote_connections
}
# 用户自定义的应用初始化操作,依次执行目录 initdb.d 中的初始化脚本
# 执行完毕后,生成以当前时间命名的'.tar'的文件,包含执行记录、已执行的脚本,同时删除已执行的脚本
app_custom_init() {
LOG_I "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)
LOG_D " Process Shell: ${f}"
process_init_files "$f"
;;
*.sql)
LOG_D " Process SQL: ${f}";
postgresql_execute "${PG_DATABASE}" "${PG_INITSCRIPTS_USERNAME}" "${PG_INITSCRIPTS_PASSWORD}" < "$f"
;;
*.sql.gz)
LOG_D " Process SQLs in ${f}";
gunzip -c "$f" | postgresql_execute "${PG_DATABASE}" "${PG_INITSCRIPTS_USERNAME}" "${PG_INITSCRIPTS_PASSWORD}" <
;;
*)
LOG_D " 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
LOG_D "Using specified values (JVMFLAGS=${JVMFLAGS})"
else
LOG_D "Setting '-Xmx${heap_size}m -Xms${heap_size}m' heap options..."
app_export_jvmflags "-Xmx${heap_size}m -Xms${heap_size}m"
fi
}