[fix:1.16]修改Dockerfile构建方式;增加Makefile;增加docker-compose文件

This commit is contained in:
2020-08-07 15:44:35 +08:00
parent c01edf0e4b
commit 9240ad9b3d
25 changed files with 2183 additions and 454 deletions
+12
View File
@@ -0,0 +1,12 @@
.git
.gitignore
./alpine
./Makefile
*.yml
*.yaml
./LICENSE
./README.md
./img
+1 -1
View File
@@ -1,4 +1,4 @@
*.DS_Store
.DS_Store
.AppleDouble
.LSOverride
-4
View File
@@ -1,4 +0,0 @@
.git
.gitignore
README.md
LICENSE
-210
View File
@@ -1,210 +0,0 @@
# Ver: 1.0 by Endial Fang (endial@126.com)
#
# 指定原始系统镜像,常用镜像为 colovu/ubuntu:18.04、colovu/debian:10、colovu/alpine:3.12、colovu/openjdk:8u252-jre
FROM colovu/debian:10
# ARG参数使用"--build-arg"指定,如 "--build-arg apt_source=tencent"
# sources.list 可使用版本:default / tencent / ustc / aliyun / huawei
ARG apt_source=default
# 外部指定应用版本信息,如 "--build-arg app_ver=6.0.0"
ARG app_ver=1.16.1
# 编译镜像时指定本地服务器地址,如 "--build-arg local_url=http://172.29.14.108/dist-files/"
ARG local_url=""
# 定义应用基础常量信息,该常量在容器内可使用
ENV APP_MAJOR=1.16.1 \
PCRE_VERSION=8.43 \
OPENSSL_VERSION=1.1.1e \
HTTP_FLV_VERSION=1.2.7 \
NGINX_KEYS='0xB0F4253373F8F6F510D42178520A9993A1C052F8'
LABEL \
"Version"="v${APP_MAJOR}" \
"Description"="Docker image for Nginx ${APP_MAJOR} based on Ubuntu 18.04." \
"Dockerfile"="https://github.com/colovu/docker-nginx" \
"Vendor"="Endial Fang (endial@126.com)"
# 镜像内应用安装脚本
# 以下脚本可按照不同需求拆分为多个段,但需要注意各个段在结束前需要清空缓存
# set -eux: 设置 shell 执行参数,分别为 -e(命令执行错误则退出脚本) -u(变量未定义则报错) -x(打印实际待执行的命令行)
RUN set -eux; \
\
# 设置程序使用静默安装,而非交互模式;类似tzdata等程序需要使用静默安装
export DEBIAN_FRONTEND=noninteractive; \
groupadd -r nginx; \
useradd -r -g nginx -s /usr/sbin/nologin -d /usr/cache/nginx nginx; \
\
mkdir -p /etc/nginx /srv/conf/nginx /var/log/nginx /var/run/nginx /var/cache/nginx; \
\
NGINX_CONFIG=" \
--prefix=/etc/nginx \
--user=nginx \
--group=nginx \
--sbin-path=/usr/local/sbin/nginx \
--conf-path=/etc/nginx/nginx.conf \
--http-log-path=/var/log/nginx/access.log \
--error-log-path=/var/log/nginx/error.log \
--modules-path=/usr/lib/nginx/modules \
--pid-path=/var/run/nginx/nginx.pid \
--lock-path=/var/run/nginx/nginx.lock \
--http-client-body-temp-path=/var/cache/nginx/client_temp \
--http-proxy-temp-path=/var/cache/nginx/proxy_temp \
--http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \
--http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \
--http-scgi-temp-path=/var/cache/nginx/scgi_temp \
\
--with-pcre=./pcre-$PCRE_VERSION \
--with-pcre-jit \
--add-module=./nginx-http-flv-module-$HTTP_FLV_VERSION \
--with-http_flv_module \
--with-openssl=./openssl-$OPENSSL_VERSION \
--with-http_ssl_module \
--with-http_v2_module \
--with-http_realip_module \
--with-http_xslt_module \
--with-http_image_filter_module \
--with-http_geoip_module \
--with-http_sub_module \
--with-http_dav_module \
--with-http_mp4_module \
--with-http_gunzip_module \
--with-http_auth_request_module \
--with-http_slice_module \
\
--with-stream \
--with-stream_geoip_module \
--with-stream_realip_module \
--with-stream_ssl_module \
--with-threads \
--with-poll_module \
--with-mail \
"; \
\
# 更新源,并安装临时使用的软件包(使用完后可删除)
fetchDeps=" \
autoconf \
automake \
gcc \
g++ \
gcc-multilib \
make \
ca-certificates \
wget \
gpg \
gpg-agent \
dirmngr \
zlib1g-dev \
libxml2-dev \
libxslt-dev \
libgd-dev \
libc6-dev \
libgeoip-dev \
libterm-readkey-perl \
"; \
savedAptMark="$(apt-mark showmanual)"; \
apt-get update; \
apt-get install -y ${fetchDeps}; \
\
# 安装应用程序及需要依赖的软件包
apt install -y --no-install-recommends \
zlib1g \
libxml2 \
libxslt1.1 \
geoip-bin \
geoip-database \
libgd3 \
libc6 \
; \
\
wget -O nginx.tar.gz "http://nginx.org/download/nginx-${APP_MAJOR}.tar.gz"; \
wget -O nginx.tar.gz.asc "http://nginx.org/download/nginx-${APP_MAJOR}.tar.gz.asc"; \
\
wget -O openssl.tar.gz --no-check-certificate "https://www.openssl.org/source/old/1.1.1/openssl-${OPENSSL_VERSION}.tar.gz"; \
wget -O openssl.tar.gz.asc --no-check-certificate "https://www.openssl.org/source/old/1.1.1/openssl-${OPENSSL_VERSION}.tar.gz.asc"; \
\
wget -O pcre.tar.gz --no-check-certificate "https://nchc.dl.sourceforge.net/project/pcre/pcre/${PCRE_VERSION}/pcre-${PCRE_VERSION}.tar.gz"; \
wget -O pcre.tar.gz.sig --no-check-certificate "https://nchc.dl.sourceforge.net/project/pcre/pcre/${PCRE_VERSION}/pcre-${PCRE_VERSION}.tar.gz.sig"; \
\
wget -O nginx-http-flv.tar.gz --no-check-certificate "https://github.com/winshining/nginx-http-flv-module/archive/v${HTTP_FLV_VERSION}.tar.gz"; \
\
# 安装软件包需要使用的GPG证书
export GNUPGHOME="$(mktemp -d)"; \
for key in ${NGINX_KEYS}; do \
gpg --batch --keyserver ha.pool.sks-keyservers.net --recv-keys "${key}"|| \
gpg --batch --keyserver pgp.mit.edu --recv-keys "${key}" || \
gpg --batch --keyserver keys.gnupg.net --recv-keys "${key}" || \
gpg --batch --keyserver keyserver.pgp.com --recv-keys "${key}"; \
done; \
gpg --batch --verify nginx.tar.gz.asc nginx.tar.gz; \
\
command -v gpgconf > /dev/null && gpgconf --kill all; \
rm -rf "$GNUPGHOME"; \
\
mkdir -p nginx-${APP_MAJOR}; \
tar -xzvf nginx.tar.gz -C nginx-${APP_MAJOR} --strip-components 1; \
rm -rf nginx.tar.gz nginx.tar.gz.asc nginx_signing.key; \
mkdir -p nginx-${APP_MAJOR}/pcre-${PCRE_VERSION}; \
tar -xzvf pcre.tar.gz -C nginx-${APP_MAJOR}/pcre-${PCRE_VERSION} --strip-components 1; \
rm -rf pcre.tar.gz pcre.tar.gz.sig; \
mkdir -p nginx-${APP_MAJOR}/openssl-${OPENSSL_VERSION}; \
tar -xzvf openssl.tar.gz -C nginx-${APP_MAJOR}/openssl-${OPENSSL_VERSION} --strip-components 1; \
rm -rf openssl.tar.gz openssl.tar.gz.asc; \
mkdir -p nginx-${APP_MAJOR}/nginx-http-flv-module-${HTTP_FLV_VERSION}; \
tar -xzvf nginx-http-flv.tar.gz -C nginx-${APP_MAJOR}/nginx-http-flv-module-${HTTP_FLV_VERSION} --strip-components 1; \
rm -rf nginx-http-flv.tar.gz; \
\
cd nginx-${APP_MAJOR}; \
./configure ${NGINX_CONFIG}; \
make -j "$(nproc)"; \
make install; \
\
echo "<?php" >/etc/nginx/html/index.php; \
echo "phpinfo();" >>/etc/nginx/html/index.php; \
echo "?>" >>/etc/nginx/html/index.php; \
\
strip $(which nginx); \
\
cd /; \
rm -rf /nginx-${APP_MAJOR}; \
ln -sf /srv/conf/nginx/nginx.conf /etc/nginx/nginx.conf; \
\
# 设置临时目录的权限信息,设置为777是为了保证后续使用`--user`或`gosu`时,可以更改目录对应的用户属性信息;运行时会被更改为700或755
chown -Rf nginx:nginx /etc/nginx /srv/conf/nginx /var/log/nginx /var/run/nginx /var/cache/nginx; \
chmod 777 /etc/nginx /srv/conf/nginx /var/log/nginx /var/run/nginx /var/cache/nginx; \
\
# 查找新安装的应用及应用依赖软件包,并标识为'manual',防止后续自动清理时被删除
apt-mark auto '.*' > /dev/null; \
{ [ -z "$savedAptMark" ] || apt-mark manual $savedAptMark; }; \
find /usr/local -type f -executable -exec ldd '{}' ';' \
| awk '/=>/ { print $(NF-1) }' \
| sort -u \
| xargs -r dpkg-query --search \
| cut -d: -f1 \
| sort -u \
| xargs -r apt-mark manual; \
\
# 删除安装的临时依赖软件包,清理缓存
apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false ${fetchDeps}; \
apt-get autoclean -y; \
rm -rf /var/lib/apt/lists/*; \
\
# 验证安装的软件是否可以正常运行,常规情况下放置在命令行的最后
: ;
COPY entrypoint.sh /usr/local/bin/
COPY ./nginx /etc/nginx
VOLUME ["/srv/www", "/srv/conf", "/srv/cert", "/var/log", "/var/run"]
# 解决使用gosu后,nginx: [emerg] bind() to 0.0.0.0:80 failed (13: Permission denied)
# 默认使用gosu切换为新建用户启动,必须保证端口在1024之上
EXPOSE 8080 8443
STOPSIGNAL SIGTERM
# 容器初始化命令,默认存放在:/usr/local/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
CMD ["nginx"]
-108
View File
@@ -1,108 +0,0 @@
#!/bin/bash
# docker entrypoint script
# 以下变量已在 Dockerfile 中定义,不需要修改
# APP_NAME: 应用名称,nginx
# APP_EXEC: 应用可执行二进制文件,nginx
# APP_USER: 应用对应的用户名,nginx
# APP_GROUP: 应用对应的用户组名,nginx
set -Eeo pipefail
LOG_RAW() {
local type="$1"; shift
printf '%s [%s] Entrypoint: %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$type" "$*"
}
LOG_I() {
LOG_RAW Note "$@"
}
LOG_W() {
LOG_RAW Warn "$@" >&2
}
LOG_E() {
LOG_RAW Error "$@" >&2
exit 1
}
LOG_I "Initial container for nginx"
# 检测当前脚本是被直接执行的,还是从其他脚本中使用 "source" 调用的
_is_sourced() {
[ "${#FUNCNAME[@]}" -ge 2 ] \
&& [ "${FUNCNAME[0]}" = '_is_sourced' ] \
&& [ "${FUNCNAME[1]}" = 'source' ]
}
# 使用root用户运行时,创建默认的数据目录,并拷贝所必须的默认配置文件及初始化文件
# 修改对应目录所属用户为应用对应的用户
# 使用--user指定用户时,挂载的数据卷及子目录下文件默认会映射会对应的用户,不用修改
docker_create_user_directories() {
local user_id; user_id="$(id -u)"
# 如果设置了'--user',这里 user_id 不为 0
# 如果没有设置'--user',这里 user_id 为 0,需要使用默认用户名设置相关目录权限
LOG_I "Check directories used by nginx"
mkdir -p "/var/log/nginx"
mkdir -p "/var/run/nginx"
mkdir -p "/var/cache/nginx"
mkdir -p "/srv/conf/nginx/conf.d"
[ ! -e /srv/conf/nginx/nginx.conf ] && cp /etc/nginx/nginx.conf.default /srv/conf/nginx/nginx.conf
[ ! -e /srv/conf/nginx/mime.types ] && cp /etc/nginx/mime.types /srv/conf/nginx/
[ ! -e /srv/conf/nginx/conf.d/default.conf ] && cp /etc/nginx/conf.d/default.conf /srv/conf/nginx/conf.d/
# 允许容器使用`--user`参数启动,修改相应目录的所属用户信息
if [ "$user_id" = '0' ]; then
LOG_I "Chang owner of resources to: nginx by root"
find /var/run/nginx \! -user nginx -exec chown nginx '{}' +
find /var/log/nginx \! -user nginx -exec chown nginx '{}' +
find /var/cache/nginx \! -user nginx -exec chown nginx '{}' +
find /srv/conf/nginx \! -user nginx -exec chown nginx '{}' +
chmod 755 /etc/nginx /var/log/nginx /var/cache/nginx /var/run/nginx /srv/conf/nginx
# 解决使用gosu后,nginx: [emerg] open() "/dev/stdout" failed (13: Permission denied)
chmod 0622 /dev/stdout /dev/stderr
fi
}
# 检测可能导致容器执行后直接退出的命令,如"--help";如果存在,直接返回 0
docker_app_want_help() {
local arg
for arg; do
case "$arg" in
-'?'|-h|-V|-v|-t|-T)
return 0
;;
esac
done
return 1
}
_main() {
# 如果命令行参数是以配置参数("-")开始,修改执行命令,确保使用可执行应用命令启动服务器
if [ "${1:0:1}" = '-' ]; then
LOG_I "Add nginx at the begin of command line"
set -- nginx "$@"
fi
# 命令行参数以可执行应用命令起始,且不包含直接返回的命令(如:-V、--version、--help)时,执行初始化操作
if [ "$1" = "nginx" ] && ! docker_app_want_help "$@"; then
# 以root用户运行时,设置数据存储目录与权限;设置完成后,会使用gosu重新以"postgres"用户运行当前脚本
docker_create_user_directories
if [ "$(id -u)" = '0' ]; then
LOG_I "Restart container with default user: nginx"
LOG_I ""
exec gosu nginx "$0" "$@"
fi
fi
LOG_I "Start container with: $@"
# 执行命令行
exec "$@"
}
if ! _is_sourced; then
LOG_I "Run shell script with: $@"
_main "$@"
fi
-80
View File
@@ -1,80 +0,0 @@
server {
listen 8080;
server_name localhost;
# charset utf-8; ## DO NOT need, set in nginx.conf
access_log /var/log/nginx/default.access.log main;
location / {
root /etc/nginx/html;
index index.html index.htm index.php;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
# Docker镜像,尽量避免使用UNIX Domain Socket方式
location ~ \.php$ {
root /etc/nginx/html;
fastcgi_pass 0.0.0.0:9000;
# fastcgi_pass unix:/var/run/php5/php-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include /etc/nginx/fastcgi_params;
}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;
# location / {
# root html;
# index index.html index.htm;
# }
#}
# HTTPS server
#
#server {
# listen 8443 ssl;
# server_name localhost;
# ssl_certificate /srv/cert/nginx/cert.pem;
# ssl_certificate_key /srv/cert/nginx/cert.key;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# location / {
# root html;
# index index.html index.htm;
# }
#}
+279
View File
@@ -0,0 +1,279 @@
# Ver: 1.0 by Endial Fang (endial@126.com)
#
# 指定原始系统镜像,常用镜像为 colovu/ubuntu:18.04、colovu/debian:10、colovu/alpine:3.12、colovu/openjdk:8u252-jre
FROM colovu/debian:10
# ARG参数使用"--build-arg"指定,如 "--build-arg apt_source=tencent"
# sources.list 可使用版本:default / tencent / ustc / aliyun / huawei
ARG apt_source=default
# 外部指定应用版本信息,如 "--build-arg app_ver=6.0.0"
ARG app_ver=1.16.1
# 编译镜像时指定本地服务器地址,如 "--build-arg local_url=http://172.29.14.108/dist-files/"
ARG local_url=""
# 定义应用基础常量信息,该常量在容器内可使用
ENV APP_NAME=nginx \
APP_EXEC=nginx \
APP_VERSION=${app_ver}
# 定义应用基础目录信息,该常量在容器内可使用
ENV APP_HOME_DIR=/usr/local/${APP_NAME} \
APP_DEF_DIR=/etc/${APP_NAME} \
APP_CONF_DIR=/srv/conf/${APP_NAME} \
APP_DATA_DIR=/srv/data/${APP_NAME} \
APP_DATA_LOG_DIR=/srv/datalog/${APP_NAME} \
APP_CACHE_DIR=/var/cache/${APP_NAME} \
APP_RUN_DIR=/var/run/${APP_NAME} \
APP_LOG_DIR=/var/log/${APP_NAME} \
APP_CERT_DIR=/srv/cert/${APP_NAME}
LABEL \
"Version"="v${app_ver}" \
"Description"="Docker image for ${APP_NAME}(v${app_ver})." \
"Dockerfile"="https://github.com/colovu/docker-${APP_NAME}" \
"Vendor"="Endial Fang (endial@126.com)"
# 拷贝默认 Shell 脚本至容器相关目录中
COPY prebuilds /
# 镜像内相应应用及依赖软件包的安装脚本;以下脚本可按照不同需求拆分为多个段,但需要注意各个段在结束前需要清空缓存
RUN \
# 设置程序使用静默安装,而非交互模式;默认情况下,类似 tzdata/gnupg/ca-certificates 等程序配置需要交互
export DEBIAN_FRONTEND=noninteractive; \
\
# 设置 shell 执行参数,分别为 -e(命令执行错误则退出脚本) -u(变量未定义则报错) -x(打印实际待执行的命令行)
set -eux; \
\
# 更改源为当次编译指定的源
cp /etc/apt/sources.list.${apt_source} /etc/apt/sources.list; \
\
# 为应用创建对应的组、用户、相关目录
export OPENSSL_VERSION=1.1.1e; \
export PCRE_VERSION=8.43; \
export HTTP_FLV_VERSION=1.2.7; \
export APP_DIRS="${APP_DEF_DIR:-} ${APP_CONF_DIR:-} ${APP_DATA_DIR:-} ${APP_CACHE_DIR:-} ${APP_RUN_DIR:-} ${APP_LOG_DIR:-} ${APP_CERT_DIR:-} ${APP_DATA_LOG_DIR:-} ${APP_HOME_DIR:-${APP_DATA_DIR}}"; \
mkdir -p ${APP_DIRS}; \
groupadd -r -g 998 ${APP_NAME}; \
useradd -r -g ${APP_NAME} -u 999 -s /usr/sbin/nologin -d ${APP_DATA_DIR} ${APP_NAME}; \
\
# 应用软件包及依赖项。相关软件包在镜像创建完成时,不会被清理
appDeps=" \
curl \
ca-certificates \
\
zlib1g \
libxml2 \
libxslt1.1 \
geoip-bin \
geoip-database \
libgd3 \
libc6 \
"; \
savedAptMark="$(apt-mark showmanual) ${appDeps}"; \
\
NGINX_CONFIG=" \
--prefix=/etc/nginx \
--user=nginx \
--group=nginx \
--sbin-path=/usr/local/sbin/nginx \
--conf-path=/etc/nginx/nginx.conf \
--http-log-path=/var/log/nginx/access.log \
--error-log-path=/var/log/nginx/error.log \
--modules-path=/usr/lib/nginx/modules \
--pid-path=/var/run/nginx/nginx.pid \
--lock-path=/var/run/nginx/nginx.lock \
--http-client-body-temp-path=/var/cache/nginx/client_temp \
--http-proxy-temp-path=/var/cache/nginx/proxy_temp \
--http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \
--http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \
--http-scgi-temp-path=/var/cache/nginx/scgi_temp \
\
--with-pcre=./pcre-$PCRE_VERSION \
--with-pcre-jit \
--add-module=./nginx-http-flv-module-$HTTP_FLV_VERSION \
--with-http_flv_module \
--with-openssl=./openssl-$OPENSSL_VERSION \
--with-http_ssl_module \
--with-http_v2_module \
--with-http_realip_module \
--with-http_xslt_module \
--with-http_image_filter_module \
--with-http_geoip_module \
--with-http_sub_module \
--with-http_dav_module \
--with-http_mp4_module \
--with-http_gunzip_module \
--with-http_auth_request_module \
--with-http_slice_module \
\
--with-stream \
--with-stream_geoip_module \
--with-stream_realip_module \
--with-stream_ssl_module \
--with-threads \
--with-poll_module \
--with-mail \
"; \
\
# 安装临时使用的软件包及依赖项。相关软件包在镜像创建完后时,会被清理
fetchDeps=" \
wget \
ca-certificates \
\
apt-transport-https \
lsb-release \
\
autoconf \
automake \
gcc \
g++ \
gcc-multilib \
make \
\
dirmngr \
gnupg \
\
zlib1g-dev \
libxml2-dev \
libxslt-dev \
libgd-dev \
libc6-dev \
libgeoip-dev \
libterm-readkey-perl \
"; \
apt update; \
apt upgrade -y; \
apt install -y --no-install-recommends ${fetchDeps}; \
\
\
\
# 下载需要的软件包资源。可使用 不校验、签名校验、SHA256 校验 三种方式
DIST_NAME="${APP_NAME}-${APP_VERSION}.tar.gz"; \
DIST_KEYIDS="0xB0F4253373F8F6F510D42178520A9993A1C052F8"; \
DIST_URLS=" \
${local_url}${APP_NAME}/ \
http://nginx.org/download/ \
"; \
. /usr/local/scripts/libdownload.sh && download_dist "${DIST_NAME}" "${DIST_URLS}" --pgpkey "${DIST_KEYIDS}"; \
\
APP_SRC=/usr/local/src/nginx-${APP_VERSION}; \
mkdir -p ${APP_SRC}; \
tar --extract --file "${DIST_NAME}" --directory "${APP_SRC}" --strip-components 1; \
rm -rf "${DIST_NAME}"; \
\
\
\
# 下载需要的软件包资源。可使用 不校验、签名校验、SHA256 校验 三种方式
DIST_NAME="openssl-${OPENSSL_VERSION}.tar.gz"; \
DIST_URLS=" \
${local_url}openssl/ \
https://www.openssl.org/source/old/1.1.1/ \
"; \
. /usr/local/scripts/libdownload.sh && download_dist "${DIST_NAME}" "${DIST_URLS}"; \
\
APP_SRC=/usr/local/src/nginx-${APP_VERSION}/openssl-${OPENSSL_VERSION}; \
mkdir -p ${APP_SRC}; \
tar --extract --file "${DIST_NAME}" --directory "${APP_SRC}" --strip-components 1; \
rm -rf "${DIST_NAME}"; \
\
\
\
# 下载需要的软件包资源。可使用 不校验、签名校验、SHA256 校验 三种方式
DIST_NAME="pcre-${PCRE_VERSION}.tar.gz"; \
DIST_URLS=" \
${local_url}pcre/ \
https://nchc.dl.sourceforge.net/project/pcre/pcre/${PCRE_VERSION}/ \
"; \
. /usr/local/scripts/libdownload.sh && download_dist "${DIST_NAME}" "${DIST_URLS}"; \
\
APP_SRC=/usr/local/src/nginx-${APP_VERSION}/pcre-${PCRE_VERSION}; \
mkdir -p ${APP_SRC}; \
tar --extract --file "${DIST_NAME}" --directory "${APP_SRC}" --strip-components 1; \
rm -rf "${DIST_NAME}"; \
\
\
\
# 下载需要的软件包资源。可使用 不校验、签名校验、SHA256 校验 三种方式
DIST_NAME="v${HTTP_FLV_VERSION}.tar.gz"; \
DIST_URLS=" \
${local_url}nginx-http-flv/ \
https://github.com/winshining/nginx-http-flv-module/archive/ \
"; \
. /usr/local/scripts/libdownload.sh && download_dist "${DIST_NAME}" "${DIST_URLS}"; \
\
APP_SRC=/usr/local/src/nginx-${APP_VERSION}/nginx-http-flv-module-${HTTP_FLV_VERSION}; \
mkdir -p ${APP_SRC}; \
tar --extract --file "${DIST_NAME}" --directory "${APP_SRC}" --strip-components 1; \
rm -rf "${DIST_NAME}"; \
\
\
\
# 源码编译方式安装: 编译后将原始配置文件拷贝至 ${APP_DEF_DIR} 中
cd /usr/local/src/nginx-${APP_VERSION}; \
./configure ${NGINX_CONFIG}; \
make -j "$(nproc)"; \
make install; \
\
echo "<?php" >/etc/nginx/html/index.php; \
echo "phpinfo();" >>/etc/nginx/html/index.php; \
echo "?>" >>/etc/nginx/html/index.php; \
\
strip $(which nginx); \
\
cd /; \
rm -rf /usr/local/src/nginx-${APP_VERSION}; \
# ln -sf /srv/conf/nginx/nginx.conf /etc/nginx/nginx.conf; \
\
# 设置应用关联目录的权限信息
chown -Rf ${APP_NAME}:${APP_NAME} ${APP_DIRS}; \
\
# 查找新安装的应用及应用依赖软件包,并标识为'manual',防止后续自动清理时被删除
apt-mark auto '.*' > /dev/null; \
{ [ -z "$savedAptMark" ] || apt-mark manual $savedAptMark; }; \
find /usr/local -type f -executable -exec ldd '{}' ';' \
| awk '/=>/ { print $(NF-1) }' \
| sort -u \
| xargs -r dpkg-query --search \
| cut -d: -f1 \
| sort -u \
| xargs -r apt-mark manual; \
\
# 删除安装的临时依赖软件包,清理缓存
apt purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false ${fetchDeps}; \
apt autoclean -y; \
rm -rf /var/lib/apt/lists/*; \
:;
# 拷贝应用专用 Shell 脚本至容器相关目录中
COPY customer /
RUN set -eux; \
# 设置容器入口脚本的可执行权限
chmod +x /usr/local/bin/entrypoint.sh; \
\
# 检测是否存在对应版本的 overrides 脚本文件;如果存在,执行
{ [ ! -e "/usr/local/overrides/overrides-${app_ver}.sh" ] || /bin/bash "/usr/local/overrides/overrides-${app_ver}.sh"; }; \
\
# 验证安装的软件是否可以正常运行,常规情况下放置在命令行的最后
gosu ${APP_NAME} ${APP_EXEC} -V ; \
:;
COPY ./nginx /etc/nginx
# 默认提供的数据卷
VOLUME ["/srv/conf", "/srv/data", "/srv/cert", "/srv/datalog", "/var/log"]
# 默认使用gosu切换为新建用户启动,必须保证端口在1024之上
EXPOSE 8080 8443
STOPSIGNAL SIGTERM
# 容器初始化命令,默认存放在:/usr/local/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
WORKDIR ${APP_DATA_DIR}
# 应用程序的服务命令,必须使用非守护进程方式运行。如果使用变量,则该变量必须在运行环境中存在(ENV可以获取)
CMD ["${APP_EXEC}"]
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2017 endial
Copyright (c) 2020 Endial Fang (endial@126.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+17
View File
@@ -0,0 +1,17 @@
# Ver: 1.0 by Endial Fang (endial@126.com)
#
# 当前 Docker 镜像的编译脚本
app_name := colovu/nginx
current_branch := $(shell git rev-parse --abbrev-ref HEAD)
# Sources List: default / tencent / ustc / aliyun / huawei
build-arg := --build-arg apt_source=tencent
# 设置本地下载服务器路径,加速调试时的本地编译速度
local_ip=`ifconfig -a|grep inet|grep -v 127.0.0.1|grep -v inet6|awk '{print $$2}'|tr -d "addr:"`
build-arg += --build-arg local_url=http://$(local_ip)/dist-files/
build:
docker rmi $(app_name):$(current_branch) || true
docker build --force-rm $(build-arg) -t $(app_name):$(if $(current_branch),$(current_branch),latest) .
+442
View File
@@ -0,0 +1,442 @@
#!/bin/bash
# Ver: 1.0 by Endial Fang (endial@126.com)
#
# 应用通用业务处理函数
# 加载依赖脚本
. /usr/local/scripts/libcommon.sh # 通用函数库
. /usr/local/scripts/libfile.sh
. /usr/local/scripts/libfs.sh
. /usr/local/scripts/libos.sh
. /usr/local/scripts/libservice.sh
. /usr/local/scripts/libvalidations.sh
. /usr/local/scripts/libnet.sh
# 函数列表
# 加载应用使用的环境变量初始值,该函数在相关脚本中以 eval 方式调用
# 全局变量:
# ENV_* : 容器使用的全局变量
# APP_* : 在镜像创建时定义的全局变量
# *_* : 应用配置文件使用的全局变量,变量名根据配置项定义
# 返回值:
# 可以被 'eval' 使用的序列化输出
docker_app_env() {
cat <<"EOF"
# Common Settings
export ENV_DEBUG=${ENV_DEBUG:-false}
# Paths
# Application settings
# Application Cluster configuration
# Application TLS Settings
# JVM settings
# Application Authentication
EOF
# 利用 *_FILE 设置密码,不在配置命令中设置密码,增强安全性
# if [[ -f "${ZOO_CLIENT_PASSWORD_FILE:-}" ]]; then
# cat <<"EOF"
#export ZOO_CLIENT_PASSWORD="$(< "${ZOO_CLIENT_PASSWORD_FILE}")"
#EOF
# fi
}
# 使用环境变量中以 "ZOO_CFG_" 开头的的全局变量更新配置文件中对应项(全小写,以"."分隔)
# 全局变量:
# ZOO_CFG_*
# 举例:
# ZOO_CFG_LOG_DIRS 对应配置文件中的配置项:log.dirs
app_configure_from_environment_variables() {
# Map environment variables to config properties
for var in "${!ZOO_CFG_@}"; do
key="$(echo "$var" | sed -e 's/^ZOO_CFG_//g' -e 's/_/\./g' | tr '[:upper:]' '[:lower:]')"
value="${!var}"
zoo_conf_set "$key" "$value"
done
}
# 将变量配置更新至配置文件
# 参数:
# $1 - 文件
# $2 - 变量
# $3 - 值(列表)
zoo_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
zoo_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
}
# 更新 server.properties 配置文件中指定变量值
# 全局变量:
# APP_CONF_DIR
# 变量:
# $1 - 变量
# $2 - 值(列表)
zoo_conf_set() {
zoo_common_conf_set "$APP_CONF_DIR/zoo.cfg" "$@"
}
# 更新 log4j.properties 配置文件中指定变量值
# 全局变量:
# APP_CONF_DIR
# 变量:
# $1 - 变量
# $2 - 值(列表)
zoo_log4j_set() {
zoo_common_conf_set "$APP_CONF_DIR/log4j.properties" "$@"
}
# 生成默认配置文件
# 全局变量:
# ZOO_*
zoo_generate_conf() {
# 准备原始默认配置文件或生成空文件
cp "${APP_CONF_DIR}/zoo_sample.cfg" "$APP_CONF_FILE"
echo "">> "$APP_CONF_FILE"
# 根据容器参数,设置配置文件
zoo_log4j_set "zookeeper.console.threshold" "$ZOO_LOG_LEVEL"
zoo_log4j_set "zookeeper.log.dir" "${APP_LOG_DIR}"
}
# 设置环境变量 JVMFLAGS
# 全局变量:
# JVMFLAGS
# 参数:
# $1 - value
zoo_export_jvmflags() {
local -r value="${1:?value is required}"
export JVMFLAGS="${JVMFLAGS} ${value}"
echo "export JVMFLAGS=\"${JVMFLAGS}\"" > "${APP_CONF_DIR}/java.env"
}
# 配置 HEAP 大小
# 全局变量:
# JVMFLAGS
# 参数:
# $1 - HEAP 大小
zoo_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..."
zoo_export_jvmflags "-Xmx${heap_size}m -Xms${heap_size}m"
fi
}
# 检测用户参数信息是否满足条件; 针对部分权限过于开放情况,打印提示信息
# 全局变量:
# ZOO_*
app_verify_minimum_env() {
local error_code=0
LOG_D "Validating settings in ZOO_* env vars..."
print_validation_error() {
LOG_E "$1"
error_code=1
}
# 检测认证设置。如果不允许匿名登录,检测登录用户名及密码是否设置
# if is_boolean_yes "$ALLOW_ANONYMOUS_LOGIN"; then
# LOG_W "You have set the environment variable ALLOW_ANONYMOUS_LOGIN=${ALLOW_ANONYMOUS_LOGIN}. For safety reasons, do not use this flag in a production environment."
# elif ! is_boolean_yes "$ZOO_ENABLE_AUTH"; then
# print_validation_error "The ZOO_ENABLE_AUTH environment variable does not configure authentication. Set the environment variable ALLOW_ANONYMOUS_LOGIN=yes to allow unauthenticated users to connect to ZooKeeper."
# fi
# TODO: 其他参数检测
[[ "$error_code" -eq 0 ]] || exit "$error_code"
}
# 更改默认监听地址为 "*" 或 "0.0.0.0",以对容器外提供服务;默认配置文件应当为仅监听 localhost(127.0.0.1)
app_enable_remote_connections() {
LOG_D "Modify default config to enable all IP access"
}
# 检测依赖的服务端口是否就绪;该脚本依赖系统工具 'netcat'
# 参数:
# $1 - host:port
app_wait_service() {
local serviceport=${1:?Missing server info}
local service=${serviceport%%:*}
local port=${serviceport#*:}
local retry_seconds=5
local max_try=100
let i=1
if [[ -z "$(which nc)" ]]; then
LOG_E "Nedd nc installed before, command: apt-get install netcat."
exit 1
fi
LOG_I "[0/${max_try}] check for ${service}:${port}..."
set +e
nc -z ${service} ${port}
result=$?
until [ $result -eq 0 ]; do
LOG_D " [$i/${max_try}] not available yet"
if (( $i == ${max_try} )); then
LOG_E "${service}:${port} is still not available; giving up after ${max_try} tries."
exit 1
fi
LOG_I "[$i/${max_try}] try in ${retry_seconds}s once again ..."
let "i++"
sleep ${retry_seconds}
nc -z ${service} ${port}
result=$?
done
set -e
LOG_I "[$i/${max_try}] ${service}:${port} is available."
}
# 以后台方式启动应用服务,并等待启动就绪
# 全局变量:
# ZOO_*
app_start_server_bg() {
is_app_server_running && return
LOG_I "Starting ${APP_NAME} in background..."
# 使用内置脚本启动服务
#local start_command="zkServer.sh start"
#if is_boolean_yes "${ENV_DEBUG}"; then
# $start_command &
#else
# $start_command >/dev/null 2>&1 &
#fi
# 使用内置命令启动服务
# if [[ "${ENV_DEBUG:-false}" = true ]]; then
# debug_execute "rabbitmq-server" &
#else
# debug_execute "rabbitmq-server" >/dev/null 2>&1 &
#fi
# 通过命令或特定端口检测应用是否就绪
LOG_I "Checking ${APP_NAME} ready status..."
# wait-for-port --timeout 60 "$ZOO_PORT_NUMBER"
LOG_D "${APP_NAME} is ready for service..."
}
# 停止应用服务
# 全局变量:
# APP_*
app_stop_server() {
is_app_server_running || return
LOG_I "Stopping ${APP_NAME}..."
# 使用 PID 文件 kill 进程
stop_service_using_pid "$APP_PID_FILE"
# 使用内置命令停止服务
#debug_execute "rabbitmqctl" stop
# 使用内置脚本关闭服务
#if [[ "$ENV_DEBUG" = true ]]; then
# "zkServer.sh" stop
#else
# "zkServer.sh" stop >/dev/null 2>&1
#fi
# 检测停止是否完成
local counter=10
while [[ "$counter" -ne 0 ]] && is_app_server_running; do
LOG_D "Waiting for ${APP_NAME} to stop..."
sleep 1
counter=$((counter - 1))
done
}
# 检测应用服务是否在后台运行中
# 全局变量:
# ZOO_*
# 返回值:
# 布尔值
is_app_server_running() {
LOG_D "Check if ${APP_NAME} is running..."
local pid
pid="$(get_pid_from_file '/var/run/${APP_NAME}/${APP_NAME}.pid')"
if [[ -z "${pid}" ]]; then
false
else
is_service_running "${pid}"
fi
}
# 清理初始化应用时生成的临时文件
app_clean_tmp_file() {
LOG_D "Clean ${APP_NAME} tmp files for init..."
}
# 在重新启动容器时,删除标志文件及必须删除的临时文件 (容器重新启动)
# 全局变量:
# APP_*
app_clean_from_restart() {
LOG_D "Clean ${APP_NAME} tmp files for restart..."
local -r -a files=(
"/var/run/${APP_NAME}/${APP_NAME}.pid"
)
for file in ${files[@]}; do
if [[ -f "$file" ]]; then
LOG_I "Cleaning stale $file file"
rm "$file"
fi
done
}
# 应用默认初始化操作
# 执行完毕后,生成文件 ${APP_CONF_DIR}/.app_init_flag 及 ${APP_DATA_DIR}/.data_init_flag 文件
docker_app_init() {
app_clean_from_restart
LOG_D "Check init status of ${APP_NAME}..."
# 检测配置文件是否存在
if [[ ! -f "${APP_CONF_DIR}/.app_init_flag" ]]; then
LOG_I "No injected configuration file found, creating default config files..."
# TODO: 生成配置文件,并按照容器运行参数进行相应修改
# 修改默认 index.html 文件,增加限制主机名信息,用于集群测试验证
HostInfo="<h1>Welcome to nginx!</h1></br><p>Served by host: ${HOSTNAME}</p>"
sed -i -e "s#^<h1>Welcome to nginx!</h1>*#<h1>Welcome to nginx!</h1></br><p>Served by host: ${HOSTNAME}</p>#g" "/srv/conf/nginx/html/index.html"
touch ${APP_CONF_DIR}/.app_init_flag
echo "$(date '+%Y-%m-%d %H:%M:%S') : Init success." >> ${APP_CONF_DIR}/.app_init_flag
else
LOG_I "User injected custom configuration detected!"
fi
if [[ ! -f "${APP_DATA_DIR}/.data_init_flag" ]]; then
LOG_I "Deploying ${APP_NAME} from scratch..."
# 检测服务是否运行中如果未运行,则启动后台服务
is_app_server_running || app_start_server_bg
# TODO: 根据需要生成相应初始化数据
touch ${APP_DATA_DIR}/.data_init_flag
echo "$(date '+%Y-%m-%d %H:%M:%S') : Init success." >> ${APP_DATA_DIR}/.data_init_flag
else
LOG_I "Deploying ${APP_NAME} with persisted data..."
fi
}
# 用户自定义的前置初始化操作,依次执行目录 preinitdb.d 中的初始化脚本
# 执行完毕后,生成文件 ${APP_DATA_DIR}/.custom_preinit_flag
docker_custom_preinit() {
LOG_D "Check custom pre-init status of ${APP_NAME}..."
# 检测用户配置文件目录是否存在 preinitdb.d 文件夹,如果存在,尝试执行目录中的初始化脚本
if [ -d "/srv/conf/${APP_NAME}/preinitdb.d" ]; then
# 检测数据存储目录是否存在已初始化标志文件;如果不存在,检索可执行脚本文件并进行初始化操作
if [[ -n $(find "/srv/conf/${APP_NAME}/preinitdb.d/" -type f -regex ".*\.\(sh\)") ]] && \
[[ ! -f "${APP_DATA_DIR}/.custom_preinit_flag" ]]; then
LOG_I "Process custom pre-init scripts from /srv/conf/${APP_NAME}/preinitdb.d..."
# 检索所有可执行脚本,排序后执行
find "/srv/conf/${APP_NAME}/preinitdb.d/" -type f -regex ".*\.\(sh\)" | sort | docker_process_init_files
touch ${APP_DATA_DIR}/.custom_preinit_flag
echo "$(date '+%Y-%m-%d %H:%M:%S') : Init success." >> ${APP_DATA_DIR}/.custom_preinit_flag
LOG_I "Custom preinit for ${APP_NAME} complete."
else
LOG_I "Custom preinit for ${APP_NAME} already done before, skipping initialization."
fi
fi
# 检测依赖的服务是否就绪
#for i in ${SERVICE_PRECONDITION[@]}; do
# app_wait_service "${i}"
#done
}
# 用户自定义的应用初始化操作,依次执行目录initdb.d中的初始化脚本
# 执行完毕后,生成文件 ${APP_DATA_DIR}/.custom_init_flag
docker_custom_init() {
LOG_D "Check custom init status of ${APP_NAME}..."
# 检测用户配置文件目录是否存在 initdb.d 文件夹,如果存在,尝试执行目录中的初始化脚本
if [ -d "/srv/conf/${APP_NAME}/initdb.d" ]; then
# 检测数据存储目录是否存在已初始化标志文件;如果不存在,检索可执行脚本文件并进行初始化操作
if [[ -n $(find "/srv/conf/${APP_NAME}/initdb.d/" -type f -regex ".*\.\(sh\|sql\|sql.gz\)") ]] && \
[[ ! -f "${APP_DATA_DIR}/.custom_init_flag" ]]; then
LOG_I "Process custom init scripts from /srv/conf/${APP_NAME}/initdb.d..."
# 检测服务是否运行中;如果未运行,则启动后台服务
is_app_server_running || app_start_server_bg
# 检索所有可执行脚本,排序后执行
find "/srv/conf/${APP_NAME}/initdb.d/" -type f -regex ".*\.\(sh\|sql\|sql.gz\)" | sort | while read -r f; do
case "$f" in
*.sh)
if [[ -x "$f" ]]; then
LOG_D "Executing $f"; "$f"
else
LOG_D "Sourcing $f"; . "$f"
fi
;;
*.sql) LOG_D "Executing $f"; postgresql_execute "$PG_DATABASE" "$PG_INITSCRIPTS_USERNAME" "$PG_INITSCRIPTS_PASSWORD" < "$f";;
*.sql.gz) LOG_D "Executing $f"; gunzip -c "$f" | postgresql_execute "$PG_DATABASE" "$PG_INITSCRIPTS_USERNAME" "$PG_INITSCRIPTS_PASSWORD";;
*) LOG_D "Ignoring $f" ;;
esac
done
touch ${APP_DATA_DIR}/.custom_init_flag
echo "$(date '+%Y-%m-%d %H:%M:%S') : Init success." >> ${APP_DATA_DIR}/.custom_init_flag
LOG_I "Custom init for ${APP_NAME} complete."
else
LOG_I "Custom init for ${APP_NAME} already done before, skipping initialization."
fi
fi
# 检测服务是否运行中;如果运行,则停止后台服务
is_app_server_running && app_stop_server
# 删除第一次运行生成的临时文件
app_clean_tmp_file
# 绑定所有 IP ,启用远程访问
app_enable_remote_connections
}
+106
View File
@@ -0,0 +1,106 @@
#!/bin/bash
# Ver: 1.0 by Endial Fang (endial@126.com)
#
# 容器入口脚本
# 设置遇到执行错误则退出执行
set -o errexit
# 设置脚本遇到未初始化或声明的变量退出执行
set -o nounset
# 设置遇到管道命令失败则退出执行
set -o pipefail
# 设置调试信息输出,及命令在执行前先打印命令执行前的命令内容
# set -o xtrace
# 加载依赖脚本
#. /usr/local/scripts/liblog.sh # 日志输出函数库
#. /usr/local/scripts/libcommon.sh # 通用函数库
. /usr/local/bin/appcommon.sh # 应用专用函数库
LOG_D "Process entrypoint.sh..."
# 初始化环境变量。 docker_app_env()函数在文件 appcommon.sh 中定义
eval "$(docker_app_env)"
# 定义容器中使用的默认目录(未定义时设置默认值为空"")
APP_DIRS="${APP_CONF_DIR:-} ${APP_DATA_DIR:-} ${APP_LOG_DIR:-} ${APP_CERT_DIR:-} ${APP_DATA_LOG_DIR:-}"
# 打印镜像欢迎信息
docker_print_welcome
# 检测数据卷中相关目录,创建默认的关联目录,并拷贝所必须的默认配置文件及初始化文件
# 全局变量:
# APP_*
docker_ensure_dir_and_configs() {
_is_restart && return
local user_id; user_id="$(id -u)"
LOG_D "Check directories..."
for dir in ${APP_DIRS}; do
LOG_D " Check $dir"
ensure_dir_exists "$dir"
done
# 检测指定文件是否在配置文件存储目录存在,如果不存在则拷贝(新挂载数据卷、手动删除都会导致不存在)
LOG_D "Check config files..."
if [[ ! -z "$(ls -A "${APP_DEF_DIR}")" ]]; then
ensure_config_file_exist "${APP_DEF_DIR}" $(ls -A "${APP_DEF_DIR}")
fi
}
_main() {
# 替换命令行中的变量
set -- $(eval echo "$@")
# 如果命令行参数是以配置参数("-")开始,修改执行命令,确保使用可执行应用命令启动服务器
if [ "${1:0:1}" = '-' ]; then
set -- "${APP_EXEC}" "$@"
fi
# 命令行参数以可执行应用命令起始,且不包含直接返回的命令(如:-V、--version、--help)时,执行初始化操作
if [ "$1" = "${APP_EXEC}" ] && ! docker_command_help "$@"; then
# 检测启动容器时设置的环境变量是否有效
app_verify_minimum_env
# 检测应用需要使用的目录及配置文件是否存在
docker_ensure_dir_and_configs
# 以root用户运行时,会使用gosu重新以"APP_NAME"用户运行当前脚本
if _is_run_as_root; then
LOG_D "Change permissions when run as root"
# 以root用户启动时,修改相应目录的所属用户信息为 APP_NAME ,确保切换用户时,权限正常
for dir in ${APP_DIRS}; do
chmod 755 ${dir}
configure_permissions_ownership "$dir" -u "${APP_NAME}" -g "${APP_NAME}"
:
done
# 解决使用gosu后,nginx: [emerg] open() "/dev/stdout" failed (13: Permission denied)
LOG_D "Change permissions of stdout/stderr to 0622"
chmod 0622 /dev/stdout /dev/stderr
LOG_I ""
LOG_I "Restart container with default user: ${APP_NAME}"
LOG_I " command: $@"
export RESTART_FLAG=1
exec gosu "${APP_NAME}" "$0" "$@"
fi
# 执行应用初始化操作
docker_app_init
# 执行用户自定义初始化脚本
docker_custom_init
fi
LOG_I "Start container with command: $@"
# 执行命令行。
exec "$@"
}
# 脚本入口命令
if ! _is_sourced; then
_main "$@"
fi
@@ -0,0 +1,10 @@
#!/bin/bash -e
#
# 在安装完应用后,使用该脚本修改默认配置文件中部分配置项; 如果相应的配置项已经定义为容器环境变量,则不需要在这里修改
# 定义要修改的文件
CONF_FILE="${APP_DEF_DIR}/nginx.conf"
echo "Process overrides for: ${CONF_FILE}"
#sed -i -E 's/^listeners=/d' "${CONF_FILE}"
#sed -i -E 's/^log.dirs=\/tmp\/kafka-logs*/log.dirs=\/var\/log\/kafka/g' "${CONF_FILE}"
+9
View File
@@ -0,0 +1,9 @@
version: '3.6'
# Docker-Compose 单容器使用参考 YAML 配置文件
# 更多配置参数请参考镜像 README.md 文档中说明
services:
nginx:
image: 'colovu/nginx:latest'
ports:
- '80:8000'
+75
View File
@@ -0,0 +1,75 @@
server {
listen 8080;
server_name localhost;
access_log /var/log/nginx/default.access.log main;
error_log /var/log/nginx/default.access.log warn;
location / {
root /srv/conf/nginx/html;
index index.html index.htm index.php;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
# 将 PHP 脚本解析请求转发至提供 FastCGI 服务的容器中,如 php-fpm:9000
# Docker 镜像,尽量避免使用 UNIX Domain Socket 方式
#location ~ \.php$ {
# root /srv/conf/nginx/html;
# fastcgi_pass php-fpm:9000;
# #fastcgi_pass unix:/var/run/php5/php-fpm.sock;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# include /srv/conf/nginx/fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#location ~ /\.ht {
# deny all;
#}
}
# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;
#
# location / {
# root /srv/data/nginx/;
# index index.html index.htm;
# }
#}
# HTTPS server
#
#server {
# listen 8443 ssl;
# server_name localhost;
#
# ssl_certificate /srv/cert/nginx/cert.pem;
# ssl_certificate_key /srv/cert/nginx/cert.key;
#
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
#
# ssl_protocols SSLv2 SSLv3 TLSv1.2;
# ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256:AES128-SHA:AES256-SHA:RC4-SHA:DES-CBC3-SHA:RC4-MD5;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
#
# location / {
# root /srv/data/nginx/;
# index index.html index.htm;
# }
#}
@@ -1,22 +1,28 @@
# /etc/nginx/nginx.conf
# 针对Docker镜像使用,请不要修改为其他用户
# user nginx;
# 针对 Docker 镜像使用,不能修改为其他用户
user nginx;
# 关闭后台模式,防止默认设置为后台模式时导致容器直接退出
# 关闭守护进程模式。如果设置为后台守护进程模式,容器在启动应用后会退出
daemon off;
# Set number of worker processes automatically based on number of CPU cores.
# 根据 CPU 核心数设置进程数量
worker_processes auto;
# 手动设置进程数量。子进程个数最好跟CPU的核心数一样
#worker_processes 8;
# 手动绑定子进程与 CPU 核心,避免进程切换造成性能损失
#worker_cpu_affinity 0001 0010 0100 1000 0011 0110 1100 1001;
# Enables the use of JIT for regular expressions to speed-up their processing.
pcre_jit on;
# Configures default error logger.
# error_log /var/log/nginx/error.log warn;
# 配置默认的日志输出方式。可以为日志文件或标准输出设备,日志文件路径固定不可修改
# 输出级别:notice / info / warn / error / 为空
#error_log /var/log/nginx/error.log warn;
#error_log /dev/stdout warn;
# Includes files with directives to load dynamic modules.
# 包含配置文件,以加载动态模块
include /etc/nginx/modules/*.conf;
# 设置PID文件路径为对应的子目录
@@ -27,39 +33,20 @@ worker_rlimit_nofile 32767;
events {
use epoll;
# The maximum number of simultaneous connections that can be opened by
# a worker process.
worker_connections 32767;
# 设置一个进程可以打开的最大并发链接数量
worker_connections 10240;
}
http {
# 如果启用Ruby支持,需要编译支持Ruby的版本,在这里配置启用对应版本的Passenger
# passenger_root /usr/local/rvm/gems/ruby-2.1.3/gems/passenger-4.0.57;
# passenger_ruby /usr/local/rvm/gems/ruby-2.1.3/wrappers/ruby;
# 如果启用Ruby支持,需要编译支持Ruby的版本,在这里配置启用对应版本的Passenger
#passenger_root /usr/local/rvm/gems/ruby-2.1.3/gems/passenger-4.0.57;
#passenger_ruby /usr/local/rvm/gems/ruby-2.1.3/wrappers/ruby;
# Includes mapping of file name extensions to MIME types of responses
# and defines the default type.
# 包含扩展名与类型映射定义 MIME 文件,并定义默认使用的类型
include /srv/conf/nginx/mime.types;
default_type application/octet-stream;
charset UTF-8;
# Name servers used to resolve names of upstream servers into addresses.
# It's also needed when using tcpsocket and udpsocket in Lua modules.
#resolver 208.67.222.222 208.67.220.220;
# Don't tell nginx version to clients.
server_tokens off;
# Specifies the maximum accepted body size of a client request, as
# indicated by the request header Content-Length. If the stated content
# length is greater than this size, then the client receives the HTTP
# error code 413. Set to 0 to disable.
client_max_body_size 100m;
# Timeout for keep-alive connections. Server will close connections after
# this time.
keepalive_timeout 65;
# Sendfile copies data between one FD and other from within the kernel,
# which is more efficient than read() + write().
sendfile on;
@@ -70,8 +57,33 @@ http {
# Causes nginx to attempt to send its HTTP response head in one packet,
# instead of using partial frames.
tcp_nopush on;
#tcp_nopush on;
# Don't tell nginx version to clients.
server_tokens off;
# Specifies the maximum accepted body size of a client request, as
# indicated by the request header Content-Length. If the stated content
# length is greater than this size, then the client receives the HTTP
# error code 413. Set to 0 to disable.
client_max_body_size 1024m;
# Name servers used to resolve names of upstream servers into addresses.
# It's also needed when using tcpsocket and udpsocket in Lua modules.
#resolver 208.67.222.222 208.67.220.220;
# Timeout for keep-alive connections. Server will close connections after
# this time.
keepalive_timeout 65;
proxy_buffer_size 128k;
proxy_buffering on;
proxy_buffers 4 64k;
proxy_busy_buffers_size 128k;
proxy_max_temp_file_size 1024m;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Path of the file with Diffie-Hellman parameters for EDH ciphers.
#ssl_dhparam /etc/ssl/nginx/dh2048.pem;
@@ -83,40 +95,41 @@ http {
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 5m;
# Enable gzipping of responses.
gzip on;
# 启用或禁用应答信息的压缩传输
gzip off;
# Set the Vary HTTP header as defined in the RFC 2616.
gzip_vary on;
gzip_disable "msie6";
gzip_proxied any;
gzip_min_length 1000;
gzip_comp_level 6;
gzip_buffers 8 16k;
gzip_http_version 1.1;
# 压缩类型,添加一个类型 application/javascript,默认就已经包含text/html,所以下面就不用再写了
gzip_types text/plain text/css text/xml application/javascript application/json application/rss+xml;
gzip_proxied any;
gzip_min_length 1000;
gzip_comp_level 6;
gzip_buffers 8 16k;
gzip_http_version 1.1;
gzip_types text/plain text/css text/xml application/javascript application/json application/rss+xml;
# Enable checking the existence of precompressed files.
#gzip_static on;
# Specifies the main log format.
# 定义日志格式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# Sets the path, format, and configuration for a buffered log write.
# access_log /var/log/nginx/access.log main;
# error_log /var/log/nginx/error.log warn;
# 设置日志输出的路径、格式
#access_log /var/log/nginx/access.log main;
#error_log /var/log/nginx/error.log warn;
access_log /dev/stdout main;
error_log /dev/stdout warn;
# Turn off log output
# access_log /dev/null;
# error_log /dev/null;
# 关闭日志输出,提升性能
#access_log /dev/null;
#error_log /dev/null;
# Includes virtual hosts configs.
# 包含虚拟服务器定义配置文件
include /srv/conf/nginx/conf.d/*.conf;
}
# 包含其它类型服务配置文件,如 RTMP
include /etc/nginx/services/*.conf;
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Endial Fang (endial@126.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+146
View File
@@ -0,0 +1,146 @@
#!/bin/bash
# Ver: 1.1 by Endial Fang (endial@126.com)
#
# shellcheck disable=SC1091
BOLD='\033[1m'
# 加载依赖项
. /usr/local/scripts/liblog.sh # 日志输出函数库
# 函数列表
# 打印包含包含Logo的欢迎信息
# 全局变量:
# APP_NAME
print_image_welcome_page() {
_is_restart && return
local github_url="https://github.com/colovu/docker-${APP_NAME}"
LOG_I ""
LOG_I " ######## ######## ### ######## ### ## ### ##"
LOG_I " ### ## ### ## ### ### ## ### ## ### ##"
LOG_I " ### ### ## ### ### ## ### ## ### ##"
LOG_I " ### ### ## ### ### ## ### ## ### ##"
LOG_I " ### ### ## ### ### ## ### ## ### ##"
LOG_I " ### ## ### ## ### ### ## #### ### ##"
LOG_I "######## ######## ######## ######## ## ########"
LOG_I ""
LOG_I "Welcome to the ${BOLD}${APP_NAME}${RESET} container"
LOG_I "Project on Github: ${BOLD}${github_url}${RESET}"
LOG_I "Send us your feedback at ${BOLD}endial@126.com${RESET}"
LOG_I ""
}
# 根据需要打印欢迎信息
# 全局变量:
# ENV_DISABLE_WELCOME_MESSAGE
# APP_NAME
docker_print_welcome() {
if [[ -z "${ENV_DISABLE_WELCOME_MESSAGE:-}" ]]; then
if [[ -n "$APP_NAME" ]]; then
print_image_welcome_page
fi
fi
}
# 检测可能导致容器执行后直接退出的命令,如"--help";如果存在,直接返回 0
# 参数:
# $1 - 待检测的参数表
docker_command_help() {
local arg
for arg; do
case "$arg" in
-'?'|--help|-V|--version)
return 0
;;
esac
done
return 1
}
# 根据脚本扩展名及权限,执行相应的初始化脚本
# 参数:
# $1 - 文件列表,支持路径通配符
# 使用:
# docker_process_init_files [file [file [...]]]
# 例子:
# docker_process_init_files /src/conf/${APP_NAME}/initdb.d/*
docker_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
}
# 检测应用相应的配置文件是否存在,如果不存在,则从默认配置文件目录拷贝一份
# 默认配置文件路径:/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_I "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_I "Copy: ${base_path}/${f} ===> ${dist}" && cp "${base_path}/${f}" "${dist}" && rm -rf "/srv/conf/${APP_NAME}/.app_init_flag"
fi
shift
done
}
# 检测当前用户是否为 root
# 返回值:
# 布尔值
_is_run_as_root() {
if [[ "$(id -u)" = "0" ]]; then
LOG_D "Check if run as root: Yes"
true
else
LOG_D "Check if run as root: No (ID $(id -u))"
false
fi
}
_is_restart() {
if [ x"${RESTART_FLAG:-}" = "x" ]; then
false
else
true
fi
}
# 检测当前脚本是被直接执行的,还是从其他脚本中使用 "source" 调用的
_is_sourced() {
[ "${#FUNCNAME[@]}" -ge 2 ] \
&& [ "${FUNCNAME[0]}" = '_is_sourced' ] \
&& [ "${FUNCNAME[1]}" = 'source' ]
}
@@ -0,0 +1,97 @@
#!/bin/bash
# Ver: 1.0 by Endial Fang (endial@126.com)
#
# 从服务器(列表)下载相应软件包
# Constants
#CV_BASE="http://archive.colovu.com/dist-files/"
#CV_BASE="http://10.37.129.2/dist-files/"
CV_BASE=""
# 检测软件包签名是否正确
# 参数:
# $1 - 软件包签名文件
# $2 - 软件包文件
# $3 - PGPKEY
check_pgp() {
local name_asc=${1:?missing asc file name}
local name=${2:?missing file name}
local keys="${3:?missing key id}"
GNUPGHOME="$(mktemp -d)"
for key in $keys; do
gpg --batch --keyserver ha.pool.sks-keyservers.net --recv-keys "${key}" ||
gpg --batch --keyserver pgp.mit.edu --recv-keys "${key}" ||
gpg --batch --keyserver keys.gnupg.net --recv-keys "${key}" ||
gpg --batch --keyserver keyserver.pgp.com --recv-keys "${key}";
done
gpg --batch --verify "$name_asc" "$name"
command -v gpgconf > /dev/null && gpgconf --kill all
rm -rf "$GNUPGHOME" "$name_asc"
}
# 从私有服务器下载软件包,如果不存在,则从官网服务器下载
# 参数:
# $1 - 软件包全名(字符串)
# $2 - 官网路径(字符串)
# $3 - "-c"/"--checksum"
# $4 - 软件包SHA256值
# $3 - "-g"/"--pgpkey"
# $4 - 用于软件包签名的KEY ID
# 例子:
# . /usr/local/scripts/libdownload.sh && download_dist "java" "11.0.7-0" --checksum 02a1fc9b79b11617ad39221667f6a34209f5c45ca908268f8ba6c264a2577ee2
download_dist() {
local name="${1:?name is required}"
local base_urls="${2:?url is required}"
local package_sha256=""
local pgp_key=""
local success=""
# 获取SHA256或PGP KEY
shift 2
while [ "$#" -gt 0 ]; do
case "$1" in
-c|--checksum)
shift
package_sha256="${1:?missing package checksum}"
;;
-g|--pgpkey)
shift
pgp_key="${1:?missing package PGP key}"
;;
*)
echo "Invalid command line flag $1" >&2
return 1
;;
esac
shift
done
echo "Downloading $name package"
for url in $CV_BASE $base_urls; do
if wget -O "$name" "$url$name" && [ -s "$name" ]; then
if [ -n "$pgp_key" ]; then
wget -O "$name.asc" "$url$name.asc"
if [ ! -e "$name.asc" ]; then
wget -O "$name.asc" "$url$name.sig"
fi
fi
success=1
break
fi
done
if [ -n "$success" ]; then
if [ -n "$package_sha256" ]; then
echo "Verifying package whith sha256"
echo "$package_sha256 *${name}" | sha256sum --check -
fi
if [ -n "$pgp_key" ]; then
echo "Verifying package with PGP"
check_pgp "$name.asc" "$name" "$pgp_key"
fi
else
[ -n "$success" ]
fi
}
+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"
}
+120
View File
@@ -0,0 +1,120 @@
#!/bin/bash
# Ver: 1.1 by Endial Fang (endial@126.com)
#
# 文件管理函数库
# 加载依赖项
. /usr/local/scripts/liblog.sh # 日志输出函数库
# 函数列表
# Ensure a file/directory is owned (user and group) but the given user
# Arguments:
# $1 - filepath
# $2 - owner
ensure_owned_by() {
local path="${1:?path is missing}"
local owner="${2:?owner is missing}"
chown "$owner":"$owner" "$path"
}
# 检测目录是否存在,如果不存在则创建,同时修改为指定的用户
# Arguments:
# $1 - directory
# $2 - owner
ensure_dir_exists() {
local dir="${1:?directory is missing}"
local owner="${2:-}"
mkdir -p "${dir}"
if [[ -n $owner ]]; then
ensure_owned_by "$dir" "$owner"
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
}
+66
View File
@@ -0,0 +1,66 @@
#!/bin/bash
# Ver: 1.0 by Endial Fang (endial@126.com)
#
# 日志处理函数库
# 定义颜色信息
RESET='\033[0m'
RED='\033[31;1m'
GREEN='\033[32;2m'
YELLOW='\033[33;1m'
MAGENTA='\033[36;2m'
CYAN='\033[35;2m'
BLUE='\033[34;2m'
# 函数列表
# 输出实际日志信息
# 参数:
# $1 - 日志类型
# $2 - 日志信息
LOG_RAW() {
local type="$1"; shift
case "${type}" in
x) printf "${CYAN}${APP_NAME:-} ${MAGENTA}%s ${RESET}${BLUE}DEBUG${RESET} %b\n" "$(date "+%T.%2N")" "${*}" ;;
I) printf "${CYAN}${APP_NAME:-} ${MAGENTA}%s ${RESET}${GREEN}INFO ${RESET} %b\n" "$(date "+%T.%2N")" "${*}";;
W) printf "${CYAN}${APP_NAME:-} ${MAGENTA}%s ${RESET}${YELLOW}WARN ${RESET} %b\n" "$(date "+%T.%2N")" "${*}";;
E) printf "${CYAN}${APP_NAME:-} ${MAGENTA}%s ${RESET}${RED}ERROR${RESET} %b\n" "$(date "+%T.%2N")" "${*}";;
esac
}
# 输出调试类日志信息,尽量少使用
# 参数:
# $1 - 日志类型
# $2 - 日志信息
LOG_D() {
local -r bool="${ENV_DEBUG:-false}"
shopt -s nocasematch
if [[ "$bool" = 1 || "$bool" =~ ^(yes|true)$ ]]; then
LOG_RAW x "$@"
fi
}
# 输出提示信息类日志信息
# 参数:
# $1 - 日志类型
# $2 - 日志信息
LOG_I() {
shopt -s nocasematch
LOG_RAW I "$@"
}
# 输出警告类日志信息至sterr
# 参数:
# $1 - 日志类型
# $2 - 日志信息
LOG_W() {
LOG_RAW W "$@" >&2
}
# 输出错误类日志信息至sterr,并退出脚本
# 参数:
# $1 - 日志类型
# $2 - 日志信息
LOG_E() {
LOG_RAW E "$@" >&2
}
+120
View File
@@ -0,0 +1,120 @@
#!/bin/bash
# Ver: 1.0 by Endial Fang (endial@126.com)
#
# 网络管理函数库
# shellcheck disable=SC1091
# 加载依赖项
. /usr/local/scripts/liblog.sh # 日志输出函数库
# 函数列表
# 解析主机名为 IP
# 参数:
# $1 - 待解析的主机名
# 返回值:
# IP 地址
#########################
dns_lookup() {
local host="${1:?host is missing}"
getent ahosts "$host" | awk '/STREAM/ {print $1 }'
}
# 等待主机名解析,并返回 IP
# 参数:
# $1 - 主机名
# $2 - 重试次数
# $3 - 重试间隔(秒)
# 返回值:
# - IP 地址
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
# 返回值:
# - IP 地址
get_machine_ip() {
dns_lookup "$(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}]}"
}
+159
View File
@@ -0,0 +1,159 @@
#!/bin/bash
# Ver: 1.0 by Endial Fang (endial@126.com)
#
# 操作系统控制函数库
# shellcheck disable=SC1091
# 加载依赖项
. /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
}
# 确保用户组存在,如果不存在则创建相应用户组
# 参数:
# $1 - 用户组
ensure_group_exists() {
local group="${1:?group is missing}"
if ! group_exists "$group"; then
groupadd "$group" >/dev/null 2>&1
fi
}
# 确保用户组及用户账户存在,如果不存在则创建相应用户组及账户
# 参数:
# $1 - 用户
# $2 - 用户组
ensure_user_exists() {
local user="${1:?user is missing}"
local group="${2:-}"
if ! user_exists "$user"; then
useradd "$user" >/dev/null 2>&1
fi
if [[ -n "$group" ]]; then
ensure_group_exists "$group"
fi
usermod -a -G "$group" "$user" >/dev/null 2>&1
}
# 获取系统可用内存
# 返回值:
# 内存大小(MB)
get_total_memory() {
echo $(($(grep MemTotal /proc/meminfo | awk '{print $2}') / 1024))
}
# 获取以定量方式描述的内存大小
# 参数:
# $1 - 内存大小 (可选)
# 返回值:
# 基于定量内存大小的内存大小描述
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 - 内存大小
# 返回值:
# 内存大小值(以MB为单位)
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
# 全局变量:
# ENV_DEBUG
# 参数:
# $@ - 待执行的命令
debug_execute() {
local -r bool="${ENV_DEBUG:-false}"
shopt -s nocasematch
if [[ "$bool" = 1 || "$bool" =~ ^(yes|true)$ ]]; then
"$@" >/dev/null 2>&1
else
"$@"
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
}
+132
View File
@@ -0,0 +1,132 @@
#!/bin/bash
# Ver: 1.0 by Endial Fang (endial@126.com)
#
# 服务管理函数库
# shellcheck disable=SC1091
# 加载依赖项
. /usr/local/scripts/liblog.sh # 日志输出函数库
# 函数列表
# 获取并返回服务 PID
# 参数:
# $1 - PID 文件
# 返回值:
# 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
# 返回值:
# Boolean
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
}
# 为指定的服务生成一个监控配置文件
# Arguments:
# $1 - 服务名
# $2 - PID 文件
# $3 - 启动命令
# $4 - 停止命令
# Flags:
# --disabled - Whether to disable the monit configuration
generate_monit_conf() {
local service_name="${1:?service name is missing}"
local pid_file="${2:?pid file is missing}"
local start_command="${3:?start command is missing}"
local stop_command="${4:?stop command is missing}"
local monit_conf_dir="/etc/monit/conf.d"
local disabled="no"
# Parse optional CLI flags
shift 4
while [[ "$#" -gt 0 ]]; do
case "$1" in
--disabled)
shift
disabled="$1"
;;
*)
echo "Invalid command line flag ${1}" >&2
return 1
;;
esac
shift
done
is_boolean_yes "$disabled" && conf_suffix=".disabled"
mkdir -p "$monit_conf_dir"
cat >"${monit_conf_dir}/${service_name}.conf${conf_suffix:-}" <<EOF
check process ${service_name}
with pidfile "${pid_file}"
start program = "${start_command}" with timeout 90 seconds
stop program = "${stop_command}" with timeout 90 seconds
EOF
}
# 生成一个 Logrotate 配置文件
# Arguments:
# $1 - 日志路径
# $2 - Period
# $3 - Rotations 存储的数量
# $4 - 其他参数 (可选)
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,229 @@
#!/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
;;
-*)
stderr_print "unrecognized flag $1"
return 1
;;
*)
break
;;
esac
shift
done
if [[ "$#" -gt 1 ]]; then
echo "too many arguments provided"
return 2
elif [[ "$#" -eq 0 ]]; then
stderr_print "missing port argument"
return 1
else
value=$1
fi
if [[ -z "$value" ]]; then
echo "the value is empty"
return 1
else
if ! is_int "$value"; then
echo "value is not an integer"
return 2
elif [[ "$value" -lt 0 ]]; then
echo "negative value provided"
return 2
elif [[ "$value" -gt 65535 ]]; then
echo "requested port is greater than 65535"
return 2
elif [[ "$unprivileged" = 1 && "$value" -lt 1024 ]]; then
echo "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
;;
-*)
stderr_print "unrecognized flag $1"
return 1
;;
*)
break
;;
esac
shift
done
if [ "$#" -gt 1 ]; then
stderr_print "too many arguments provided"
return 2
elif [ "$#" -eq 0 ]; then
stderr_print "missing string"
return 1
else
string=$1
fi
if [[ "$min_length" -ge 0 ]] && [[ "${#string}" -lt "$min_length" ]]; then
echo "string length is less than $min_length"
return 1
fi
if [[ "$max_length" -ge 0 ]] && [[ "${#string}" -gt "$max_length" ]]; then
echo "string length is great than $max_length"
return 1
fi
}