[fix]重命名customer脚本;优化readme.md文档

This commit is contained in:
2021-07-08 14:13:02 +08:00
parent b94340296b
commit c9c80279c2
16 changed files with 968 additions and 46 deletions
+35 -33
View File
@@ -28,18 +28,17 @@ ARG apt_source
ARG local_url
# 选择软件包源(Optional),以加速后续软件包安装
#RUN select_source ${apt_source};
RUN select_source ${apt_source};
# 安装依赖的软件包及库(Optional)
#RUN install_pkg xz-utils
# 设置工作目录
WORKDIR /usr/local
WORKDIR /tmp
# 下载并解压软件包
#RUN set -eux; \
# appVersion=1.12; \
# appName=gosu-"$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
# appName=${app_name}-${app_version}.tgz; \
# appKeys="0xB42F6819007F00F88E364FD4036A9C25BF357DD4"; \
# sha256="04fa1fddc39bd1aecb6739dd5dd73858a3515b427acd1e2947a66dadce868d68"; \
# [ ! -z ${local_url} ] && localURL=${local_url}/${app_name}; \
@@ -50,33 +49,37 @@ WORKDIR /usr/local
# download_pkg unpack ${appName} "${appUrls}"; \
# chmod +x /usr/local/bin/${appName};
# 源码编译: 编译后将配置文件模板拷贝至 /usr/local/${app_name}/share/${app_name} 中
# 源码编译
#RUN set -eux; \
# APP_SRC="/usr/local/${app_name}-${app_version}"; \
# APP_SRC="/tmp/${app_name}-${app_version}"; \
# cd ${APP_SRC}; \
# ./configure \
# --prefix=/usr/local/${app_name} \
# --prefix=/usr/local \
# CPPFLAGS="-I/usr/local/include -D_GNU_SOURCE" \
# LDFLAGS="-L/usr/local/lib" \
# ; \
# make -j "$(nproc)"; \
# make PREFIX=/usr/local/${app_name} install;
# make install;
# 删除编译生成的多余文件
RUN set -eux; \
find /usr/local -name '*.a' -delete; \
rm -rf /usr/local/${app_name}/include;
rm -rf /usr/local/share; \
rm -rf /usr/local/include; \
rm -rf /usr/local/docs;
# 检测并生成依赖文件记录
RUN set -eux; \
find /usr/local/${app_name} -type f -executable -exec ldd '{}' ';' | \
find /usr/local -type f -executable -exec ldd '{}' ';' | \
awk '/=>/ { print $(NF-1) }' | \
sort -u | \
xargs -r dpkg-query --search | \
xargs -r dpkg-query --search 2>/dev/null | \
cut -d: -f1 | \
sort -u >/usr/local/${APP_NAME}/runDeps;
sort -u >/usr/local/runDeps;
# 1. 生成镜像 =====================================================================
FROM ${registry_url}/colovu/debian:10
FROM ${registry_url}/colovu/debian:buster
FROM ${registry_url}/colovu/openjre:8
# 声明需要使用的全局可变参数
@@ -88,14 +91,13 @@ ARG local_url
# 镜像所包含应用的基础信息,定义环境变量,供后续脚本使用
ENV APP_NAME=${app_name} \
APP_USER=builder \
APP_EXEC=run.sh \
APP_EXEC=${app_name} \
APP_VERSION=${app_version}
ENV APP_HOME_DIR=/usr/local/${APP_NAME} \
APP_DEF_DIR=/etc/${APP_NAME}
ENV APP_HOME_DIR=/usr/local \
APP_DEF_DIR=/usr/local/etc/${APP_NAME}
ENV PATH="${APP_HOME_DIR}/bin:${APP_HOME_DIR}/sbin:${PATH}" \
ENV PATH="${APP_HOME_DIR}/libexec:${PATH}" \
LD_LIBRARY_PATH="${APP_HOME_DIR}/lib"
LABEL \
@@ -106,25 +108,22 @@ LABEL \
# 拷贝应用使用的客制化脚本,并创建对应的用户及数据存储目录
COPY customer /
RUN create_user && prepare_env
# 从预处理过程中拷贝软件包(Optional),可以使用阶段编号或阶段命名定义来源
#COPY --from=0 /usr/local/${APP_NAME}/ /usr/local/${APP_NAME}
#COPY --from=builder /usr/local/${APP_NAME}/ /usr/local/${APP_NAME}
COPY --from=0 /usr/local/ /usr/local
# 选择软件包源(Optional),以加速后续软件包安装
#RUN select_source ${apt_source}
RUN select_source ${apt_source}
# 安装依赖的软件包及库(Optional)
#RUN install_pkg `cat /usr/local/${APP_NAME}/runDeps`;
#RUN install_pkg bash tini sudo libssl1.1
RUN install_pkg `cat /usr/local/runDeps`;
#RUN install_pkg bash sudo libssl1.1
# 执行预处理脚本,并验证安装的软件包
RUN set -eux; \
override_file="/usr/local/overrides/overrides-${APP_VERSION}.sh"; \
[ -e "${override_file}" ] && /bin/bash "${override_file}"; \
gosu ${APP_USER} ${APP_EXEC} --version ; \
gosu --version;
${APP_EXEC} --version ;
# 默认提供的数据卷
VOLUME ["/srv/conf", "/srv/data", "/srv/datalog", "/srv/cert", "/var/log"]
@@ -136,13 +135,16 @@ EXPOSE 8080
#HEALTHCHECK NONE
# 应用健康状态检查
#HEALTHCHECK --interval=30 --timeout=30 --retries=3 \
# CMD curl -fs http://localhost:9864/ || exit 1
#HEALTHCHECK --interval=0 --timeout=0 --retries=0 \
# CMD netstat -ltun | grep 10514
#HEALTHCHECK --interval=30s --timeout=30s --retries=3 \
# CMD curl -fs http://localhost:8080/ || exit 1
#HEALTHCHECK --interval=10s --timeout=10s --retries=3 \
# CMD netstat -ltun | grep 8080
# 容器初始化命令,默认存放在:/usr/local/bin/entry.sh
# 使用 non-root 用户运行后续的命令
USER 1001
# 容器初始化命令,默认存放在:/usr/local/bin
ENTRYPOINT ["entry.sh"]
# 应用程序的服务命令,必须使用非守护进程方式运行。如果使用变量,则该变量必须在运行环境中存在(ENV可以获取)
CMD ["${APP_EXEC}"]
# 应用程序的启动命令,必须使用非守护进程方式运行。默认存放在:/usr/local/bin
CMD ["run.sh"]
+1 -1
View File
@@ -135,7 +135,7 @@ max_wal_size = '400MB'
如果没有必要,可选配置参数可以不用定义,直接使用对应的默认值,主要包括:
- `ENV_DEBUG`:默认值:**false**。设置是否输出容器调试信息。可选值:1、true、yes
- `ENV_DEBUG`:默认值:**false**。设置是否输出容器调试信息。可选值:no、true、yes
+4 -4
View File
@@ -8,9 +8,9 @@
set -eu
set -o pipefail
. /usr/local/bin/comm-${APP_NAME}.sh # 应用专用函数库
. /usr/local/bin/common.sh # 应用专用函数库
. /usr/local/bin/comm-env.sh # 设置环境变量
. /usr/local/bin/environment.sh # 设置环境变量
LOG_I "** Processing entry.sh **"
@@ -27,11 +27,11 @@ if ! is_sourced; then
/usr/local/bin/setup.sh
LOG_I "Restart with non-root user: ${APP_USER}\n"
exec gosu "${APP_USER}" "$0" "$@"
exec "$0" "$@"
fi
[ "$1" = "${APP_EXEC}" ] && /usr/local/bin/init.sh
LOG_I "Start container with command: $@"
exec tini -- "$@"
exec "$@"
fi
+2 -2
View File
@@ -8,9 +8,9 @@
set -eu
set -o pipefail
. /usr/local/bin/comm-${APP_NAME}.sh # 应用专用函数库
. /usr/local/bin/common.sh # 应用专用函数库
. /usr/local/bin/comm-env.sh # 设置环境变量
. /usr/local/bin/environment.sh # 设置环境变量
LOG_I "** Processing init.sh **"
+8 -4
View File
@@ -8,15 +8,19 @@
set -eu
set -o pipefail
. /usr/local/bin/comm-${APP_NAME}.sh # 应用专用函数库
. /usr/local/bin/common.sh # 应用专用函数库
. /usr/local/bin/comm-env.sh # 设置环境变量
. /usr/local/bin/environment.sh # 设置环境变量
LOG_I "** Processing run.sh **"
flags=("${APP_CONF_FILE:-}")
readonly START_COMMAND="$(command -v ${APP_EXEC})"
# 确保应用运行在前台
flags=("-f" "${APP_CONF_FILE:-}")
[[ -z "${APP_EXTRA_FLAGS:-}" ]] || flags=("${flags[@]}" "${APP_EXTRA_FLAGS[@]}")
START_COMMAND=("${APP_EXEC:-/bin/bash}")
LOG_I "** Starting ${APP_NAME} **"
LOG_D "Command: ${START_COMMAND[@]} ${flags[@]}"
+2 -2
View File
@@ -8,9 +8,9 @@
set -eu
set -o pipefail
. /usr/local/bin/comm-${APP_NAME}.sh # 应用专用函数库
. /usr/local/bin/common.sh # 应用专用函数库
. /usr/local/bin/comm-env.sh # 设置环境变量
. /usr/local/bin/environment.sh # 设置环境变量
LOG_I "** Processing setup.sh **"
+119
View File
@@ -0,0 +1,119 @@
#!/bin/bash
# Ver: 1.3 by Endial Fang (endial@126.com)
#
# 通用函数库
# 加载依赖项
. /usr/local/scripts/liblog.sh # 日志输出函数库
# 函数列表
# 打印包含包含Logo的欢迎信息
print_welcome_info() {
[[ -n "${APP_NAME}" ]] && github_url="/docker-${APP_NAME}"
LOG_I ' ____ _ '
LOG_I ' / ___|___ | | _____ ___ _ '
LOG_I '| | / _ \| |/ _ \ \ / / | | | '"Docker : ${BOLD}${APP_NAME:-undefined}${RESET}"
LOG_I '| |__| (_) | | (_) \ V /| |_| | '"Version: ${BOLD}${APP_VERSION:-0.0}${RESET}"
LOG_I ' \____\___/|_|\___/ \_/ \__,_| '"PowerBy: ${BOLD}Endial@126.com${RESET}"
LOG_D " Project Repo: https://github.com/colovu/${github_url:-}"
LOG_I ""
}
# 根据需要打印欢迎信息
print_image_welcome() {
if [[ "$(id -u)" = "0" ]]; then
print_welcome_info
fi
}
# 检测可能导致容器执行后直接退出的命令,如"--help";如果存在,直接返回 0
# 参数:
# $1 - 待检测的参数表
print_command_help() {
local arg
for arg; do
case "$arg" in
-'?'|--help|-V|--version|-version)
exec "$@"
exit
;;
esac
done
}
# 检测应用相应的配置文件是否存在,如果不存在,则从默认配置文件目录拷贝一份
# 默认配置文件路径:/etc/${APP_NAME}
# 目标配置文件路径:/srv/conf/${APP_NAME}
# 参数:
# $1 - 基础路径
# $* - 基础路径下的文件及目录列表,以" "分割
# 例子:
# ensure_config_file_exist /etc/${APP_NAME} conf.d server.conf
ensure_config_file_exist() {
local -r base_path="${1:?paths is missing}"
local f=""
local dist=""
shift 1
LOG_D "List to check: $@"
while [ "$#" -gt 0 ]; do
f="${1}"
LOG_D " Process \"${f}\""
if [ -d "${base_path}/${f}" ]; then
dist="$(echo ${base_path}/${f} | sed -e 's/\/etc/\/srv\/conf/g')"
[[ ! -d "${dist}" ]] && LOG_D " Create directory: ${dist}" && mkdir -p "${dist}"
[[ ! -z $(ls -A "${base_path}/${f}") ]] && ensure_config_file_exist "${base_path}/${f}" $(ls -A "${base_path}/${f}")
else
dist="$(echo ${base_path}/${f} | sed -e 's/\/etc/\/srv\/conf/g')"
[[ ! -e "${dist}" ]] && LOG_D " Copy: ${base_path}/${f} ===> ${dist}" && cp "${base_path}/${f}" "${dist}" && rm -rf "/srv/conf/${APP_NAME}/.app_init_flag"
fi
shift
done
}
# 根据脚本扩展名及权限,执行相应的初始化脚本
# 参数:
# $1 - 文件列表,支持路径通配符
# 使用:
# process_init_files [file [file [...]]]
# 例子:
# process_init_files /src/conf/${APP_NAME}/initdb.d/*
process_init_files() {
echo
local f
for f; do
case "$f" in
*.sh)
if [ -x "$f" ]; then
LOG_I "$0: running $f"
"$f"
else
LOG_I "$0: sourcing $f"
. "$f"
fi
;;
*) LOG_W "$0: ignoring $f" ;;
esac
echo
done
}
# 检测当前是否为 root 用户
is_root() {
if [[ "$(id -u)" = "0" ]]; then
LOG_D "Run as root."
true
else
LOG_D "Run as non-root: $(id -u)"
false
fi
}
# 检测当前脚本是被直接执行的,还是从其他脚本中使用 "source" 调用的
is_sourced() {
[ "${#FUNCNAME[@]}" -ge 2 ] \
&& [ "${FUNCNAME[0]}" = 'is_sourced' ] \
&& [ "${FUNCNAME[1]}" = 'source' ]
}
+78
View File
@@ -0,0 +1,78 @@
#!/bin/bash
# Ver: 1.0 by Endial Fang (endial@126.com)
#
# 文件操作函数库
# 加载依赖项
. /usr/local/scripts/liblog.sh # 日志输出函数库
# 函数列表
# 检测"*_FILE"文件,并从文件中读取信息作为参数值;环境变量不允许 VAR 与 VAR_FILE 方式并存
# 变量:
# $1 - 需要设置的环境变量名称
# $2 - 该变量对应的默认值(Option)
#
# 使用: file_env ENV_VAR [DEFAULT]
file_env() {
local var="$1"
local fileVar="${var}_FILE"
local def="${2:-}"
if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then
LOG_E "Both $var and $fileVar are set (but are exclusive)"
exit 1
fi
local val="$def"
if [ "${!var:-}" ]; then
val="${!var}"
elif [ "${!fileVar:-}" ]; then
val="$(< "${!fileVar}")"
fi
export "$var"="$val"
unset "$fileVar"
}
# 使用规则表达式在文件中替换数据
# 参数:
# $1 - 文件名
# $2 - 正则表达式
# $3 - 替代数据表达式
# $4 - 是否使用POSIX表达式. Default: true
replace_in_file() {
local filename="${1:?filename is required}"
local match_regex="${2:?match regex is required}"
local substitute_regex="${3:?substitute regex is required}"
local posix_regex=${4:-true}
local result
# 因部分系统兼容性问题,需要防止使用 'sed in-place' 方式操作
if [[ $posix_regex = true ]]; then
result="$(sed -E "s@$match_regex@$substitute_regex@g" "$filename")"
else
result="$(sed "s@$match_regex@$substitute_regex@g" "$filename")"
fi
echo "$result" > "$filename"
}
# 使用规则表达式在文件中删除数据
# 参数:
# $1 - 文件名
# $2 - 正则表达式
# $3 - 是否使用POSIX表达式. Default: true
remove_in_file() {
local filename="${1:?filename is required}"
local match_regex="${2:?match regex is required}"
local posix_regex=${3:-true}
local result
# 因部分系统兼容性问题,需要防止使用 'sed in-place' 方式操作
if [[ $posix_regex = true ]]; then
result="$(sed -E "/$match_regex/d" "$filename")"
else
result="$(sed "/$match_regex/d" "$filename")"
fi
echo "$result" > "$filename"
}
+107
View File
@@ -0,0 +1,107 @@
#!/bin/bash
# Ver: 1.1 by Endial Fang (endial@126.com)
#
# 文件管理函数库
# 加载依赖项
. /usr/local/scripts/liblog.sh # 日志输出函数库
# 函数列表
# 检测目录是否存在,如果不存在则创建,同时修改为指定的用户
# 参数:
# $1 - 目录路径
# $2 - 用户
ensure_dir_exists() {
local dir="${1:?directory is missing}"
local owner="${2:-}"
mkdir -p "${dir}"
if [[ -n $owner ]]; then
chown "$owner":"$owner" "$dir"
fi
}
# 检测目录是否存在或为空
# 参数:
# $1 - 目录路径
is_dir_empty() {
local dir="${1:?missing directory}"
if [[ ! -e "$dir" ]] || [[ -z "$(ls -A "$dir")" ]]; then
true
else
false
fi
}
# 循环设置目录中子目录及文件权限
# 参数:
# $1 - paths (as a string).
# Flags:
# -f|--file-mode - 文件权限模式
# -d|--dir-mode - 目录权限模式
# -u|--user - 用户
# -g|--group - 用户组
configure_permissions_ownership() {
local -r paths="${1:?paths is missing}"
local dir_mode=""
local file_mode=""
local user=""
local group=""
# Validate arguments
shift 1
while [ "$#" -gt 0 ]; do
case "$1" in
-f|--file-mode)
shift
file_mode="${1:?missing mode for files}"
;;
-d|--dir-mode)
shift
dir_mode="${1:?missing mode for directories}"
;;
-u|--user)
shift
user="${1:?missing user}"
;;
-g|--group)
shift
group="${1:?missing group}"
;;
*)
LOG_E "Invalid command line flag $1" >&2
return 1
;;
esac
shift
done
read -r -a filepaths <<< "$paths"
for p in "${filepaths[@]}"; do
if [[ -e "$p" ]]; then
LOG_D "Check $p"
if [[ -n ${dir_mode} ]]; then
LOG_D "Change permissions to ${dir_mode} of directories in $p"
find -L "$p" -type d -print | xargs -i chmod "${dir_mode}" '{}'
fi
if [[ -n ${file_mode} ]]; then
LOG_D "Change permissions to ${file_mode} of files in $p"
find -L "$p" -type f -print | xargs -i chmod "${file_mode}" '{}'
fi
if [[ -n $user ]] && [[ -n ${group} ]]; then
LOG_D "Change ownership to ${user}:${group} of files and directories in $p"
find -L "$p" \( \! -user ${user} -or \! -group ${group} \) -print | xargs -i chown -L "${user}":"${group}" '{}'
elif [[ -n $user ]] && [[ -z $group ]]; then
LOG_D "Change user to ${user} of files and directories in $p"
find -L "$p" \! -user ${user} -print | xargs -i chown -L "${user}" '{}'
elif [[ -z $user ]] && [[ -n $group ]]; then
LOG_D "Change group to ${group} of files and directories in $p"
find -L "$p" \! -group ${group} -print | xargs -i chgrp -L "${group}" '{}'
fi
else
LOG_E "$p does not exist"
fi
done
}
+83
View File
@@ -0,0 +1,83 @@
#!/bin/bash
# Ver: 1.1 by Endial Fang (endial@126.com)
#
# 日志输出函数库
#[[ ${ENV_DEBUG:-false} = true ]] && set -x
MODULE="$(basename "$0")"
RESET='\033[0m'
BOLD='\033[1m'
# 前景色
BLACK='\033[38;5;0m'
RED='\033[38;5;1m'
GREEN='\033[38;5;2m'
YELLOW='\033[38;5;3m'
BLUE='\033[38;5;4m'
MAGENTA='\033[38;5;5m'
CYAN='\033[38;5;6m'
WHITE='\033[38;5;7m'
# 背景色
ON_BLACK='\033[48;5;0m'
ON_RED='\033[48;5;1m'
ON_GREEN='\033[48;5;2m'
ON_YELLOW='\033[48;5;3m'
ON_BLUE='\033[48;5;4m'
ON_MAGENTA='\033[48;5;5m'
ON_CYAN='\033[48;5;6m'
ON_WHITE='\033[48;5;7m'
# 函数列表
# 打印输出到 STDERR 设备
stderr_print() {
printf "%b\\n" "${*}" >&2
}
# 输出实际日志信息
# 参数:
# $1 - 日志信息
LOG() {
local -r bool="${ENV_DEBUG:-false}"
shopt -s nocasematch
if [[ "$bool" = 1 || "$bool" =~ ^(yes|true)$ ]]; then
debugInfo="${CYAN}${APP_NAME:-}:${MODULE:-}"
else
debugInfo="${CYAN}${APP_NAME:-}"
fi
stderr_print "${debugInfo} ${MAGENTA}$(date "+%F %T.%3N")${RESET} ${*}"
}
# 输出调试类日志信息,尽量少使用
# 参数:
# $1 - 日志信息
LOG_D() {
local -r bool="${ENV_DEBUG:-false}"
shopt -s nocasematch
if [[ "$bool" = 1 || "$bool" =~ ^(yes|true)$ ]]; then
LOG "${BLUE}DBG${RESET}: ${*}"
fi
}
# 输出提示信息类日志信息
# 参数:
# $1 - 日志信息
LOG_I() {
LOG "${GREEN}INF${RESET}: ${*}"
}
# 输出警告类日志信息至sterr
# 参数:
# $1 - 日志信息
LOG_W() {
LOG "${YELLOW}WRN${RESET}: ${*}"
}
# 输出错误类日志信息至sterr,并退出脚本
# 参数:
# $1 - 日志信息
LOG_E() {
LOG "${RED}ERR${RESET}: ${*}"
}
+120
View File
@@ -0,0 +1,120 @@
#!/bin/bash
# Ver: 1.1 by Endial Fang (endial@126.com)
#
# 文件管理函数库
# 加载依赖项
. /usr/local/scripts/liblog.sh # 日志输出函数库
# 函数列表
# 域名解析
# 参数:
# $1 - 需要解析的主机名
dns_lookup() {
local host="${1:?host is missing}"
getent ahosts "$host" | awk '/STREAM/ {print $1 }'
}
# 尝试解析域名并返回对应的 IP
# 参数:
# $1 - 主机名
# $2 - 尝试次数
# $3 - 重试间隔时间(秒)
wait_for_dns_lookup() {
local hostname="${1:?hostname is missing}"
local retries="${2:-5}"
local seconds="${3:-1}"
check_host() {
if [[ $(dns_lookup "$hostname") == "" ]]; then
false
else
true
fi
}
# Wait for the host to be ready
retry_while "check_host ${hostname}" "$retries" "$seconds"
dns_lookup "$hostname"
}
# 获取当前主机 IP
get_machine_ip() {
local -a ip_addresses
local hostname
hostname="$(hostname)"
read -r -a ip_addresses <<< "$(dns_lookup "$hostname" | xargs echo)"
if [[ "${#ip_addresses[@]}" -gt 1 ]]; then
LOG_W "Found more than one IP address associated to hostname ${hostname}: ${ip_addresses[*]}, will use ${ip_addresses[0]}"
elif [[ "${#ip_addresses[@]}" -lt 1 ]]; then
LOG_E "Could not find any IP address associated to hostname ${hostname}"
exit 1
fi
echo "${ip_addresses[0]}"
}
# Check if the provided argument is a resolved hostname
# 参数:
# $1 - 待检测的主机名
# 返回值:
# 布尔值
is_hostname_resolved() {
local -r host="${1:?missing value}"
if [[ -n "$(dns_lookup "$host")" ]]; then
true
else
false
fi
}
# 解析 URL
# 参数:
# $1 - URI 字符串
# $2 - 类型字符串. 有效值 (scheme, authority, userinfo, host, port, path, query or fragment)
# 返回值:
# 字符串
parse_uri() {
local uri="${1:?uri is missing}"
local component="${2:?component is missing}"
# Solution based on https://tools.ietf.org/html/rfc3986#appendix-B with
# additional sub-expressions to split authority into userinfo, host and port
# Credits to Patryk Obara (see https://stackoverflow.com/a/45977232/6694969)
local -r URI_REGEX='^(([^:/?#]+):)?(//((([^@/?#]+)@)?([^:/?#]+)(:([0-9]+))?))?(/([^?#]*))?(\?([^#]*))?(#(.*))?'
# || | ||| | | | | | | | | |
# |2 scheme | ||6 userinfo 7 host | 9 port | 11 rpath | 13 query | 15 fragment
# 1 scheme: | |5 userinfo@ 8 :... 10 path 12 ?... 14 #...
# | 4 authority
# 3 //...
local index=0
case "$component" in
scheme)
index=2
;;
authority)
index=4
;;
userinfo)
index=6
;;
host)
index=7
;;
port)
index=9
;;
path)
index=10
;;
query)
index=13
;;
fragment)
index=14
;;
*)
stderr_print "unrecognized component $component"
return 1
;;
esac
[[ "$uri" =~ $URI_REGEX ]] && echo "${BASH_REMATCH[${index}]}"
}
+109
View File
@@ -0,0 +1,109 @@
#!/bin/bash
# Ver: 1.2 by Endial Fang (endial@126.com)
#
# 操作系统控制函数库
# 加载依赖项
. /usr/local/scripts/liblog.sh # 日志输出函数库
# 函数列表
# 检测指定用户账户是否存在
# 参数:
# $1 - 用户账户
user_exists() {
local user="${1:?user is missing}"
id "$user" >/dev/null 2>&1
}
# 检测指定用户分组是否存在
# 参数:
# $1 - 用户组
group_exists() {
local group="${1:?group is missing}"
getent group "$group" >/dev/null 2>&1
}
# 获取系统可用内存大小(MB)信息
get_total_memory() {
echo $(($(grep MemTotal /proc/meminfo | awk '{print $2}') / 1024))
}
# 获取以定量方式描述的内存大小
# 参数:
# $1 - 内存大小 (MB,可选)
get_machine_size() {
local memory="${1:-}"
if [[ -z "$memory" ]]; then
debug "Memory was not specified, detecting available memory automatically"
memory="$(get_total_memory)"
fi
sanitized_memory=$(convert_to_mb "$memory")
if [[ "$sanitized_memory" -gt 26000 ]]; then
echo 2xlarge
elif [[ "$sanitized_memory" -gt 13000 ]]; then
echo xlarge
elif [[ "$sanitized_memory" -gt 6000 ]]; then
echo large
elif [[ "$sanitized_memory" -gt 3000 ]]; then
echo medium
elif [[ "$sanitized_memory" -gt 1500 ]]; then
echo small
else
echo micro
fi
}
# 获取已定义的所有内存大小描述
get_supported_machine_sizes() {
echo micro small medium large xlarge 2xlarge
}
# 将以字符串表示的内存大小转换为以MB为单位的内存大小值 (i.e. 2G -> 2048)
# 参数:
# $1 - 内存大小
convert_to_mb() {
local amount="${1:-}"
if [[ $amount =~ ^([0-9]+)(M|G) ]]; then
size="${BASH_REMATCH[1]}"
unit="${BASH_REMATCH[2]}"
if [[ "$unit" = "G" ]]; then
amount="$((size * 1024))"
else
amount="$size"
fi
fi
echo "$amount"
}
# 如果禁用调试模式,将输出信息重定向至 /dev/null
# 参数:
# $@ - 待执行的命令
debug_execute() {
local -r bool="${ENV_DEBUG:-false}"
shopt -s nocasematch
if [[ "$bool" = 1 || "$bool" =~ ^(yes|true)$ ]]; then
"$@"
else
"$@" >/dev/null 2>&1
fi
}
# 重试执行命令
# 参数:
# $1 - cmd (as a string)
# $2 - 最大尝试次数. Default: 12
# $3 - 重试前等待时间(秒). Default: 5
retry_while() {
local -r cmd="${1:?cmd is missing}"
local -r retries="${2:-12}"
local -r sleep_time="${3:-5}"
local return_value=1
read -r -a command <<< "$cmd"
for ((i = 1 ; i <= retries ; i+=1 )); do
"${command[@]}" && return_value=0 && break
sleep "$sleep_time"
done
return $return_value
}
+87
View File
@@ -0,0 +1,87 @@
#!/bin/bash
# Ver: 1.0 by Endial Fang (endial@126.com)
#
# 服务管理函数库
# shellcheck disable=SC1091
# 加载依赖项
. /usr/local/scripts/liblog.sh # 日志输出函数库
# 函数列表
# 获取并返回服务 PID
# 参数:
# $1 - PID 文件
get_pid_from_file() {
local pid_file="${1:?pid file is missing}"
if [[ -f "$pid_file" ]]; then
if [[ -n "$(< "$pid_file")" ]] && [[ "$(< "$pid_file")" -gt 0 ]]; then
echo "$(< "$pid_file")"
fi
fi
}
# 检测 PID 对应的服务是否在运行中
# 参数:
# $1 - PID
is_service_running() {
local pid="${1:?pid is missing}"
kill -0 "$pid" 2>/dev/null
}
# 通过发送信号停止一个指定的服务
# 参数:
# $1 - PID 文件
# $2 - 信号 (可选)
stop_service_using_pid() {
local pid_file="${1:?pid file is missing}"
local signal="${2:-}"
local pid
pid="$(get_pid_from_file "$pid_file")"
[[ -z "$pid" ]] || ! is_service_running "$pid" && return
if [[ -n "$signal" ]]; then
kill "-${signal}" "$pid"
else
kill "$pid"
fi
local counter=10
while [[ "$counter" -ne 0 ]] && is_service_running "$pid"; do
sleep 1
counter=$((counter - 1))
done
}
# 生成一个 Logrotate 配置文件
# 参数:
# $1 - 应用名称
# $2 - 日志路径及日志文件名
# $3 - 周期
# $4 - Rotations 存储的数量
# $5 - 其他参数 (可选)
generate_logrotate_conf() {
local service_name="${1:?service name is missing}"
local log_path="${2:?log path is missing}"
local period="${3:-weekly}"
local rotations="${4:-150}"
local extra_options="${5:-}"
local logrotate_conf_dir="/etc/logrotate.d"
mkdir -p "$logrotate_conf_dir"
cat >"${logrotate_conf_dir}/${service_name}" <<-'EOF'
${log_path} {
${period}
rotate ${rotations}
dateext
compress
copytruncate
missingok
${extra_options}
}
EOF
}
@@ -0,0 +1,213 @@
#!/bin/bash
# Ver: 1.0 by Endial Fang (endial@126.com)
#
# 数据有效性校验函数库
# 加载依赖项
. /usr/local/scripts/liblog.sh # 日志输出函数库
# 函数列表
# 检测数据是否为整数
# 参数:
# $1 - 待检测的数据
is_int() {
local -r int="${1:?missing value}"
if [[ "$int" =~ ^-?[0-9]+ ]]; then
true
else
false
fi
}
# 检测数据是否为正整数
# 参数:
# $1 - 待检测的数据
is_positive_int() {
local -r int="${1:?missing value}"
if is_int "$int" && (( "${int}" >= 0 )); then
true
else
false
fi
}
# 检测数据是否为布尔值 '1' 或字符串 'yes/true'
# 参数:
# $1 - 待检测的数据
is_boolean_yes() {
local -r bool="${1:-}"
# comparison is performed without regard to the case of alphabetic characters
shopt -s nocasematch
if [[ "$bool" = 1 || "$bool" =~ ^(yes|true)$ ]]; then
true
else
false
fi
}
# 检测数据是否为字符串 'yes/no'
# 参数:
# $1 - 待检测的数据
is_yes_no_value() {
local -r bool="${1:-}"
if [[ "$bool" =~ ^(yes|no)$ ]]; then
true
else
false
fi
}
# 检测数据是否为字符串 'true/false'
# 参数:
# $1 - 待检测的数据
is_true_false_value() {
local -r bool="${1:-}"
if [[ "$bool" =~ ^(true|false)$ ]]; then
true
else
false
fi
}
# 检测提供的参数是否为空字符串或未定义
# 参数:
# $1 - 待检测的数据
is_empty_value() {
local -r val="${1:-}"
if [[ -z "$val" ]]; then
true
else
false
fi
}
# 检测数据是否为有效的端口号
# 参数:
# $1 - 待检测的数据
# 返回值:
# 布尔值 或 错误消息
validate_port() {
local value
local unprivileged=0
# Parse flags
while [[ "$#" -gt 0 ]]; do
case "$1" in
-unprivileged)
unprivileged=1
;;
--)
shift
break
;;
-*)
LOG_E "unrecognized flag $1"
return 1
;;
*)
break
;;
esac
shift
done
if [[ "$#" -gt 1 ]]; then
LOG_E "too many arguments provided"
return 2
elif [[ "$#" -eq 0 ]]; then
LOG_E "missing port argument"
return 1
else
value=$1
fi
if [[ -z "$value" ]]; then
LOG_E "the value is empty"
return 1
else
if ! is_int "$value"; then
LOG_W "value is not an integer"
return 2
elif [[ "$value" -lt 0 ]]; then
LOG_W "negative value provided"
return 2
elif [[ "$value" -gt 65535 ]]; then
LOG_W "requested port is greater than 65535"
return 2
elif [[ "$unprivileged" = 1 && "$value" -lt 1024 ]]; then
LOG_W "privileged port requested"
return 3
fi
fi
}
# 检测数据是否为有效的IPv4地址
# 参数:
# $1 - 待检测的数据
validate_ipv4() {
local ip="${1:?ip is missing}"
local stat=1
if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
read -r -a ip_array <<< "$(tr '.' ' ' <<< "$ip")"
[[ ${ip_array[0]} -le 255 && ${ip_array[1]} -le 255 \
&& ${ip_array[2]} -le 255 && ${ip_array[3]} -le 255 ]]
stat=$?
fi
return $stat
}
# 校验字符串格式
# 参数:
# $1 - 待检测的数据
validate_string() {
local string
local min_length=-1
local max_length=-1
# Parse flags
while [ "$#" -gt 0 ]; do
case "$1" in
-min-length)
shift
min_length=${1:-}
;;
-max-length)
shift
max_length=${1:-}
;;
--)
shift
break
;;
-*)
LOG_E "unrecognized flag $1"
return 1
;;
*)
break
;;
esac
shift
done
if [ "$#" -gt 1 ]; then
LOG_E "too many arguments provided"
return 2
elif [ "$#" -eq 0 ]; then
LOG_W "missing string"
return 1
else
string=$1
fi
if [[ "$min_length" -ge 0 ]] && [[ "${#string}" -lt "$min_length" ]]; then
LOG_I "string length is less than $min_length"
return 1
fi
if [[ "$max_length" -ge 0 ]] && [[ "${#string}" -gt "$max_length" ]]; then
LOG_I "string length is great than $max_length"
return 1
fi
}