42 Commits

Author SHA1 Message Date
endial 1830ca2a6d docs: 更新说明文档
ci/woodpecker/push/woodpecker Pipeline was successful
2026-01-20 09:54:52 +08:00
endial db02b9a63c feat: 删除Dockerfile中的架构参数 2026-01-20 09:54:44 +08:00
endial 7ff98e11f6 feat: 更新本地编译脚本 2026-01-20 09:54:38 +08:00
endial 7987b9f67e feat: 更新 Submodule 版本
ci/woodpecker/push/woodpecker Pipeline was successful
2026-01-13 16:09:01 +08:00
endial 8e8aa1b9cf fix: cmake 增加版本限制,规避错误 2026-01-13 16:08:49 +08:00
endial 3cea1c6f39 feat: 更新 Submodule 版本 2026-01-13 14:54:38 +08:00
endial b45a14a42d feat: 更新 Submodule 版本
ci/woodpecker/push/woodpecker Pipeline was successful
2026-01-13 14:43:29 +08:00
endial cc9861b622 feat: 更新 Submodule 版本
ci/woodpecker/push/woodpecker Pipeline was successful
2026-01-13 14:33:31 +08:00
endial c454fc624d fix: cmake 增加版本限制,规避错误 2026-01-13 14:33:03 +08:00
endial 78e8e5b2ca feat: 更新 Submodule 版本
ci/woodpecker/push/woodpecker Pipeline was successful
2026-01-13 14:20:56 +08:00
endial 963dbeb01d feat: 更新 Submodule 版本
ci/woodpecker/push/woodpecker Pipeline was successful
2026-01-13 14:10:46 +08:00
endial 0fe0fc68a0 feat: 更新 Submodule 版本
ci/woodpecker/push/woodpecker Pipeline failed
2026-01-13 14:06:36 +08:00
endial f75982bf58 fix: 修复流水线配置中触发条件错误[SKIP CI] 2026-01-13 10:51:40 +08:00
endial 0589d83b1e feat: 合并 main 分支最新版本更新
ci/woodpecker/push/woodpecker Pipeline was successful
2026-01-12 17:52:42 +08:00
endial 1b808759c6 feat: 更新 Submodule 版本
ci/woodpecker/push/woodpecker Pipeline was successful
2026-01-12 17:48:53 +08:00
endial f1ec16343e feat: 更新 Submodule 版本 2026-01-12 17:48:32 +08:00
endial 4db9b95113 feat: 更新为基于 Woodpecker 流水线的编译版本 2026-01-12 17:36:57 +08:00
endial bbeb952586 feat: 更新流水线触发定义方式 2026-01-12 17:36:31 +08:00
endial e44a049529 feat: 配置默认版本为 v3.23 2026-01-12 17:16:18 +08:00
endial 156085e074 feat: 切换系统源为3.23系统版本 2026-01-12 15:36:40 +08:00
endial cc3cbf139c feat: 切换使用本地路径的文件定义系统源仓库 2026-01-12 15:30:57 +08:00
endial e2c553d828 feat: 切换使用本地路径的文件定义系统源仓库 2026-01-12 15:28:31 +08:00
endial 3ce0cae8b4 feat: 删除 Gitlab 流水线配置文件 2026-01-12 12:18:15 +08:00
endial a1a138c3a2 feat: 更新 Submodule 版本
ci/woodpecker/push/woodpecker Pipeline was successful
2026-01-12 12:14:27 +08:00
endial d227386623 feat: 更新 Woodpecker 配置脚本触发配置
ci/woodpecker/push/woodpecker Pipeline was successful
2026-01-12 11:40:59 +08:00
endial 8c68522495 feat: 更新 Woodpecker 配置脚本触发配置 2026-01-12 11:39:25 +08:00
endial 54bd3cdd67 feat: 更新 Woodpecker 配置脚本触发配置
ci/woodpecker/push/woodpecker Pipeline was successful
2026-01-12 11:29:16 +08:00
endial e55bd147c3 feat: 增加流水线触发类型 2026-01-12 11:20:16 +08:00
endial 8350220735 feat: 增加 Woodpecker 配置脚本
ci/woodpecker/push/woodpecker Pipeline was successful
2026-01-09 18:16:18 +08:00
endial 9a8f109c6c chore: trigger rebuild 2026-01-09 18:11:40 +08:00
endial 0ff24f00e0 feat: 更新最新依赖版本为 3.23 2026-01-09 18:07:35 +08:00
endial 2347182afe feat: 更新 Dockerfile 2026-01-09 18:04:37 +08:00
endial b9f4e9cd5d chore: 更新本地编译脚本 2026-01-09 18:04:16 +08:00
endial 3553ac7cfa feat: 更新 Submodule 版本 2026-01-09 18:03:02 +08:00
endial e7b16b3230 chore: 更新 Docker 忽略配置 2026-01-09 18:01:07 +08:00
endial 9c28150ff5 feat: 更新 Submodule 版本 2026-01-08 15:28:38 +08:00
endial 896e69d4e9 feat: 删除本地脚本路径,使用 Submodule 2026-01-08 15:26:53 +08:00
endial 649ab2d52b chore: 工程环境增加 Submodule 2026-01-08 15:22:24 +08:00
endial 8bd1707361 chore: 更新 Docker 忽略配置 2026-01-08 15:10:10 +08:00
endial 14fc30d982 chore: 工程环境增加 Submodule 2026-01-08 11:20:02 +08:00
endial 5639541de3 docs: 更新3.20版本说明 2025-04-21 14:14:10 +08:00
endial 9f39af4f99 feat: 升级系统版本为3.20 2025-04-21 14:04:59 +08:00
28 changed files with 114 additions and 2535 deletions
Submodule
+1
Submodule .ci/common added at 99f494f83f
+1
View File
@@ -1,5 +1,6 @@
.git .git
.gitignore .gitignore
.gitmodules
./Makefile ./Makefile
-74
View File
@@ -1,74 +0,0 @@
# CI/CD 的阶段定义,按顺序执行各阶段;默认包含`.pre`(最先执行)/`.post`(最后执行)两个阶段,不用显示定义
stages:
- build
- test
- deploy
# 全局变量定义
variables:
IMG_URL: "$HARBOR_HOST/$HARBOR_PROJECT/$CI_PROJECT_NAME"
IMG_TAG: ":latest"
# 默认值信息配置
default:
# 各 stage 使用的默认镜像,如果不定义,则为 gitlab-runner 创建时指定的镜像;各 stage 可以覆盖该值以使用不同的镜像
image: docker.colovu.com/library/docker:20.10.16
# Gitlab-runner 配置的执行器为 Docker 时,需要 配置对应的 dind 服务(这里使用Runner中配置的Dind服务)
#services:
# - name: docker.colovu.com/library/docker:20.10.16-dind
# alias: docker
# 流水线中,各阶段都会执行的脚本命令,包括`before_script`(在各阶段 script 前执行)/`after_script`(在各阶段 script 后执行)
before_script:
- |
if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then
IMG_TAG=":latest"
else
IMG_TAG=":$CI_COMMIT_REF_NAME"
fi
- docker login -u "$HARBOR_USERNAME" -p "$HARBOR_PASSWORD" $HARBOR_URL
# 环境变量信息
env-variables:
stage: .pre
script:
- export
# 编译阶段任务
build-arm64:
stage: build
tags:
- arm64
script:
- docker buildx build --platform=linux/arm64 --pull -t "$IMG_URL$IMG_TAG-linux-arm64" . --push
- docker rmi "$IMG_URL$IMG_TAG-linux-arm64"
build-amd64:
stage: build
tags:
- amd64
script:
- docker buildx build --platform=linux/amd64 --pull -t "$IMG_URL$IMG_TAG-linux-amd64" . --push
- docker rmi "$IMG_URL$IMG_TAG-linux-amd64"
# 生成多架构制品,并在上传后删除本地文件
build-artifact:
stage: build
needs: [build-amd64, build-arm64]
script:
- docker manifest create "$IMG_URL$IMG_TAG" "$IMG_URL$IMG_TAG-linux-arm64" "$IMG_URL$IMG_TAG-linux-amd64"
- docker manifest push "$IMG_URL$IMG_TAG"
- docker manifest rm "$IMG_URL$IMG_TAG"
# 测试阶段任务
test:
stage: test
script:
- docker run --pull always --rm --platform=linux/arm64 "$IMG_URL$IMG_TAG" /bin/uname -a
- docker run --pull always --rm --platform=linux/amd64 "$IMG_URL$IMG_TAG" /bin/uname -a
- docker images -q "$IMG_URL" | sort -u | xargs docker rmi -f
# 部署阶段任务
deploy:
stage: deploy
script:
- echo "deploy stage"
+3
View File
@@ -0,0 +1,3 @@
[submodule ".ci/common"]
path = .ci/common
url = https://git.colovu.com/docker/common.git
+55
View File
@@ -0,0 +1,55 @@
# 注意:
# 1. git commit 信息中包含"[CI SKIP]"或"[SKIP CI]"则不触发工作流(注意大小写)
# 2. 工作步骤中包含 volumes 挂载时,需在 Woodpecker 配置中添加 volumes 挂载信任(Trust
# 3. lables 配置项,可配置多个;如果存在,则必须完全符合 Runner 创建时设置的 Lables 配置项
# 4. command 中,引用自定义变量不能使用`${VAR}`方式,需要使用`$VAR`方式;带花括号的变量,会在 Woodpecker 模板引擎解析阶段被替换(此时变量为空)
# 5. 多架构编译后推送至 SWR 报错,或单架构编译后必须在推送时明确指定架构信息才能推送;可通过在编译命令中增加参数`--provenance=false --sbom=false`解决
# 6. 使用 Git Submodule 管理通用脚本时,需手动更新 Submodule
when:
# 匹配 main
- event: push
branch: main
# 匹配 master
- event: push
branch: master
# 匹配数字开头的分支,如 1.0, 2, 3.2.1
- event: push
branch: "[0-9]*"
# 匹配 v 开头的版本分支,如 v1, v2.0
- event: push
branch: "v[0-9]*"
# 匹配 tag 事件
- event: tag
ref: "refs/tags/(v?[0-9].*)"
labels:
runtime: docker
arch: amd64
multiarch: "true"
steps:
- name: 初始化子模块
image: alpine/git
commands:
- git submodule update --init --recursive
- name: 编译并推送镜像
image: docker:cli
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
SWR_REGISTRY: "swr.cn-north-4.myhuaweicloud.com"
DOCKER_CLI_EXPERIMENTAL: enabled
SWR_USERNAME:
from_secret: swr_colovu_user
SWR_PASSWORD:
from_secret: swr_colovu_passwd
commands:
- |
# 直接执行 Submodule .ci/common 下的构建推送脚本
if [ ! -f ".ci/common/build_push.sh" ]; then
echo "错误: 未找到 .ci/common/build_push.sh 脚本"
exit 1
fi
- sh ./.ci/common/build_push.sh
+24 -14
View File
@@ -16,29 +16,38 @@
# 该部分变量,在编译命令中通过 `--build-arg` 传入;如果未设置,则使用下面对应的默认值 # 该部分变量,在编译命令中通过 `--build-arg` 传入;如果未设置,则使用下面对应的默认值
ARG APP_NAME=alpine ARG APP_NAME=alpine
ARG APP_VER=3.18 ARG APP_VER=3.23
ARG REGISTRY_URL="registry.colovu.com/docker-proxy/" ARG REGISTRY_URL="docker.io/"
ARG APT_SOURCE=ustc ARG APT_SOURCE=aliyun
ARG LOCAL_URL="http://pkgs.colovu.com/dist"
# 1. 生成镜像 ===================================================================== # 1. 生成镜像 =====================================================================
FROM --platform=${TARGETPLATFORM:-linux/amd64} ${REGISTRY_URL}alpine:${APP_VER} FROM ${REGISTRY_URL}alpine:${APP_VER}
# 声明需要使用的全局可变参数(ARG声明的变量仅编译打包阶段有效) # 声明需要使用的全局可变参数(ARG声明的变量仅编译打包阶段有效)
ARG APP_NAME ARG APP_NAME
ARG APP_VER ARG APP_VER
ARG APT_SOURCE ARG APT_SOURCE
LABEL \ # 镜像元数据标签 - 符合OCI镜像规范
"Version"="v${APP_VER}" \ LABEL org.opencontainers.image.title="${APP_NAME}" \
"Description"="Docker image for Alpine." \ org.opencontainers.image.version="${APP_VER}" \
"Github"="https://gitee.com/colovu/docker-${APP_NAME}" \ org.opencontainers.image.description="Docker image for Alpine." \
"Vendor"="Endial Fang (endial@126.com)" org.opencontainers.image.authors="Endial Fang <endial@126.com>" \
org.opencontainers.image.url="https://gitee.com/colovu/docker-${APP_NAME}" \
org.opencontainers.image.vendor="Endial Fang (colovu)" \
org.opencontainers.image.licenses="Apache-2.0" \
org.opencontainers.image.source="https://gitee.com/colovu/docker-${APP_NAME}" \
org.opencontainers.image.documentation="https://gitee.com/colovu/docker-${APP_NAME}/blob/main/README.md" \
maintainer="Endial Fang <endial@126.com>"
# 拷贝源仓库配置文件
COPY etc /etc/
# 拷贝默认的通用脚本文件 # 拷贝默认的通用脚本文件
COPY prebuilds / COPY .ci/common/alpine /
RUN \ RUN set -eux; \
\
# 选择软件包源,以加速后续软件包安装 # 选择软件包源,以加速后续软件包安装
select_source ${APT_SOURCE}; \ select_source ${APT_SOURCE}; \
\ \
@@ -52,11 +61,12 @@ RUN \
apk add --no-cache libintl; \ apk add --no-cache libintl; \
apk add --no-cache --virtual .locale_build git cmake make musl-dev gcc gettext-dev; \ apk add --no-cache --virtual .locale_build git cmake make musl-dev gcc gettext-dev; \
git clone https://gitlab.com/rilian-la-te/musl-locales; \ git clone https://gitlab.com/rilian-la-te/musl-locales; \
cd musl-locales && cmake -DLOCALE_PROFILE=OFF -DCMAKE_INSTALL_PREFIX:PATH=/usr . && make && make install; \ cd musl-locales && sed -i '1i cmake_minimum_required(VERSION 3.10)' CMakeLists.txt && cmake -Wno-dev -DLOCALE_PROFILE=OFF -DCMAKE_INSTALL_PREFIX:PATH=/usr . && make && make install; \
cd .. && rm -r musl-locales; \ cd .. && rm -r musl-locales; \
apk del .locale_build; \ apk del .locale_build; \
rm -rf /var/cache/apk/*; rm -rf /var/cache/apk/*;
# 在安装像相应软件包后,设置对应的环境变量
ENV LANG=en_US.UTF-8 \ ENV LANG=en_US.UTF-8 \
LANGUAGE=en_US.UTF-8 \ LANGUAGE=en_US.UTF-8 \
LC_ALL=en_US.UTF-8 LC_ALL=en_US.UTF-8
-68
View File
@@ -1,68 +0,0 @@
# Ver: 1.11 by Endial Fang (endial@126.com)
#
# 当前 Docker 镜像的编译脚本
# 定义镜像名称
image_name :=alpine
# 定义默认镜像仓库地址
REGISTRY_URL :=registry.colovu.com/docker-proxy/
# 定义系统默认使用的源服务器,包含:default / ustc / aliyun
# 当前 Alpine 3.18 阿里云缺少部分软件包
APT_SOURCE :=ustc
# 定义镜像TAG,类似:
# <镜像名>:<分支名>-<7位Git ID> # Git 仓库且无文件修改直接编译
# <镜像名>:<分支名>-<年月日>-<时分秒> # Git 仓库有文件修改后的编译
# <镜像名>:latest-<年月日>-<时分秒> # 非 Git 仓库编译
current_subversion:=$(shell if [ ! `git status >/dev/null 2>&1` ]; then git rev-parse --short HEAD; else date +%y%m%d-%H%M%S; fi)
image_tag:=$(shell if [ ! `git status >/dev/null 2>&1` ]; then git rev-parse --abbrev-ref HEAD | sed -e 's/master/latest/' | sed -e 's/main/latest/'; else echo "latest"; fi)-$(current_subversion)
build-arg:=--build-arg REGISTRY_URL=$(REGISTRY_URL)
build-arg+=--build-arg APT_SOURCE=$(APT_SOURCE)
# 设置本地下载服务器路径,加速调试时的本地编译速度
local_ip:=`echo "en0 eth0" | xargs -n1 ip addr show 2>/dev/null | grep inet | grep -v 127.0.0.1 | grep -v inet6 | tr "/" " " | awk '{print $$2}'`
build-arg+=--build-arg LOCAL_URL=http://pkgs.colovu.com/dist
.PHONY: build clean clearclean upgrade push
build:
@echo "Build $(image_name):$(image_tag)"
@podman build --progress plain --force-rm $(build-arg) -t $(image_name):$(image_tag) .
@echo "Add tag: $(image_name):latest"
@podman tag $(image_name):$(image_tag) $(image_name):latest
@echo "Build complete"
# 清理悬空的镜像(无TAG)及停止的容器
clearclean: clean
@echo "Clean untaged images and stoped containers..."
@podman ps -a | grep "Exited" | awk '{print $$1}' | sort -u | xargs -L 1 podman rm
@podman images | grep '<none>' | awk '{print $$3}' | sort -u | xargs -L 1 podman rmi -f
# 为了防止删除前缀名相同的镜像,在过滤条件中加入一个空格进行过滤
clean:
@echo "Clean all images for current application..."
@podman images | grep "$(image_name) " | awk '{print $$3}' | sort -u | xargs -L 1 podman rmi -f
# 更新所有 colovu 仓库的镜像
upgrade:
@echo "Upgrade all images..."
@podman images | grep 'colovu' | grep -v '<none>' | grep -v "latest-" | awk '{print $$1":"$$2}' | sort -u | xargs -L 1 podman pull
# 推送镜像至华为云 SWR 的 colovu 空间及 registry.colovu.com 的默认空间
push:
@echo "Push $(image_name):$(image_tag) to Huawei Cloud SWR colovu space"
@podman tag $(image_name):$(image_tag) swr.cn-north-4.myhuaweicloud.com/colovu/$(image_name):$(image_tag)
@podman push swr.cn-north-4.myhuaweicloud.com/colovu/$(image_name):$(image_tag)
@echo "Push $(image_name):$(image_tag) to registry.colovu.com"
@podman tag $(image_name):$(image_tag) registry.colovu.com/$(image_name):$(image_tag)
@podman push registry.colovu.com/$(image_name):$(image_tag)
@echo "Push $(image_name):latest to Huawei Cloud SWR colovu space"
@podman tag $(image_name):latest swr.cn-north-4.myhuaweicloud.com/colovu/$(image_name):latest
@podman push swr.cn-north-4.myhuaweicloud.com/colovu/$(image_name):latest
@echo "Push $(image_name):latest to registry.colovu.com"
@podman tag $(image_name):latest registry.colovu.com/$(image_name):latest
@podman push registry.colovu.com/$(image_name):latest
@echo "Push complete"
+6 -6
View File
@@ -7,14 +7,14 @@
**版本信息:** **版本信息:**
- 3.18 - 3.23
**镜像信息:** **镜像信息:**
* 镜像地址: * 镜像地址:
* 华为云: swr.cn-north-4.myhuaweicloud.com/colovu/alpine * 华为云: swr.cn-north-4.myhuaweicloud.com/colovu/alpine
* 依赖镜像:docker.io/library/alpine:3.18 * 依赖镜像:docker.io/library/alpine
registry.colovu.com/docker-proxy/alpine:3.18 registry.colovu.com/docker-proxy/alpine
> 后续相关命令行默认使用华为云 SWR 镜像服务器做说明。 > 后续相关命令行默认使用华为云 SWR 镜像服务器做说明。
@@ -45,10 +45,10 @@ docker run -it swr.cn-north-4.myhuaweicloud.com/colovu/alpine /bin/bash
**下载镜像:** **下载镜像:**
```shell ```shell
docker pull swr.cn-north-4.myhuaweicloud.com/colovu/alpine docker pull swr.cn-north-4.myhuaweicloud.com/colovu/alpine:latest
``` ```
- 3.18:为镜像的 TAG,可针对性选择不同的 TAG 进行下载;可使用`latest`选择最新的镜像 - latest:为镜像的 TAG,可针对性选择不同的 TAG 进行下载
- 不指定 TAG 时,默认下载`latest`镜像 - 不指定 TAG 时,默认下载`latest`镜像
**查看镜像:** **查看镜像:**
@@ -93,7 +93,7 @@ docker exec -it alpine /bin/bash
## 更新记录 ## 更新记录
- 3.18 - 20260115: 更新为 v3.23.2
---- ----
+18 -106
View File
@@ -1,110 +1,22 @@
#!/bin/bash #!/bin/bash
# Ver: 1.0 by Endial Fang (endial@126.com) # Ver: 3.0 by Endial Fang (endial@126.com)
# #
# Docker 镜像构建脚本 (仅linux/amd64) # Docker 镜像构建脚本 - 主入口
IMAGE_NAME="alpine" # 编译后镜像名称
REGISTRY_URL="registry.colovu.com/docker-proxy/" export IMAGE_NAME="alpine"
APT_SOURCE="ustc" # 依赖镜像的仓库地址(本镜像需要依赖原生 alpine 镜像)
export REGISTRY_URL="swr.cn-north-4.myhuaweicloud.com/img-sync/docker.io/"
# 源仓库地址(本地编译时,使用阿里云源仓库)
export APT_SOURCE="aliyun"
# 针对无法直接下载到软件包,本地变异时,使用缓存的软件包
export LOCAL_URL="http://pkgs.colovu.com/dist"
# 获取发布版本标签 # 引入本地构建脚本
get_release_tag() { if [ -f ".ci/common/build_local.sh" ]; then
if ! git rev-parse --git-dir >/dev/null 2>&1; then # 执行本地构建脚本并传递参数
echo "unknown" exec ".ci/common/build_local.sh" "$@"
elif [ -n "$(git status --porcelain)" ]; then else
echo "latest" echo "Error: .ci/common/build_local.sh script not found!"
else exit 1
# 尝试获取最近的git标签 fi
local tag=$(git describe --tags --abbrev=0 2>/dev/null)
if [ -n "$tag" ]; then
echo "$tag"
else
local branch=$(git rev-parse --abbrev-ref HEAD)
case "$branch" in
master|main) echo "latest" ;;
*) echo "$branch" ;;
esac
fi
fi
}
# 获取镜像完整TAG
get_image_tag() {
local CURRENT_SUBVERSION
if git rev-parse --git-dir >/dev/null 2>&1; then
if [ -z "$(git status --porcelain)" ]; then
CURRENT_SUBVERSION=$(git rev-parse --short=12 HEAD)
else
CURRENT_SUBVERSION=$(date +%Y%m%d-%H%M%S)
fi
local branch=$(git rev-parse --abbrev-ref HEAD)
branch=$(echo "$branch" | sed -e 's/master/latest/' -e 's/main/latest/')
echo "${branch}-${CURRENT_SUBVERSION}"
else
CURRENT_SUBVERSION=$(date +%Y%m%d-%H%M%S)
echo "latest-${CURRENT_SUBVERSION}"
fi
}
# 构建amd64架构镜像
build() {
local TAG=${1:-$(get_image_tag)}
echo "Building image ${IMAGE_NAME}:${TAG} (linux/amd64)"
podman build --platform linux/amd64 \
--progress plain --force-rm \
--build-arg REGISTRY_URL=${REGISTRY_URL} \
--build-arg APT_SOURCE=${APT_SOURCE} \
--build-arg LOCAL_URL=http://pkgs.colovu.com/dist \
-t ${IMAGE_NAME}:${TAG} \
-t ${IMAGE_NAME}:latest \
.
echo "Build complete"
}
# 推送镜像到colovu仓库
push_colovu() {
local TAG=${1:-$(get_image_tag)}
echo "Pushing ${IMAGE_NAME}:${TAG} to registry.colovu.com"
podman tag "${IMAGE_NAME}:${TAG}" "registry.colovu.com/library/${IMAGE_NAME}:${TAG}"
podman push "registry.colovu.com/library/${IMAGE_NAME}:${TAG}"
podman tag "${IMAGE_NAME}:latest" "registry.colovu.com/library/${IMAGE_NAME}:latest"
podman push "registry.colovu.com/library/${IMAGE_NAME}:latest"
}
# 推送镜像到华为云仓库
push_huawei() {
local TAG=${1:-$(get_release_tag)}
echo "Pushing ${IMAGE_NAME}:${TAG} to swr.cn-north-4.myhuaweicloud.com"
podman tag "${IMAGE_NAME}:${TAG}" "swr.cn-north-4.myhuaweicloud.com/colovu/${IMAGE_NAME}:${TAG}"
podman push "swr.cn-north-4.myhuaweicloud.com/colovu/${IMAGE_NAME}:${TAG}"
podman tag "${IMAGE_NAME}:latest" "swr.cn-north-4.myhuaweicloud.com/colovu/${IMAGE_NAME}:latest"
podman push "swr.cn-north-4.myhuaweicloud.com/colovu/${IMAGE_NAME}:latest"
}
# 清理工作空间
clean() {
echo "Cleaning workspace..."
podman images | grep "${IMAGE_NAME} " | awk '{print $3}' | xargs -L 1 podman rmi -f
podman ps -a | grep "Exited" | awk '{print $1}' | xargs -L 1 podman rm
podman images | grep '<none>' | awk '{print $3}' | xargs -L 1 podman rmi -f
}
# 主函数中更新使用说明
main() {
case "$1" in
build) build "$2" ;; # 传递第二个参数作为标签
clean) clean ;;
push-cv) push_colovu "$2" ;;
push-hw) push_huawei "$2" ;;
push) push_colovu "$2"; push_huawei "$2" ;;
*) echo "Usage: $0 {build [tag]|clean|push-cv [tag]|push-hw [tag]|push [tag]}"; exit 1 ;;
esac
}
main "$@"
+2
View File
@@ -0,0 +1,2 @@
http://mirrors.aliyun.com/alpine/v3.23/main
http://mirrors.aliyun.com/alpine/v3.23/community
+2
View File
@@ -0,0 +1,2 @@
http://dl-cdn.alpinelinux.org/alpine/v3.23/main
http://dl-cdn.alpinelinux.org/alpine/v3.23/community
+2
View File
@@ -0,0 +1,2 @@
http://mirrors.ustc.edu.cn/alpine/v3.23/main
http://mirrors.ustc.edu.cn/alpine/v3.23/community
-2
View File
@@ -1,2 +0,0 @@
http://mirrors.aliyun.com/alpine/v3.18/main
http://mirrors.aliyun.com/alpine/v3.18/community
-2
View File
@@ -1,2 +0,0 @@
http://dl-cdn.alpinelinux.org/alpine/v3.18/main
http://dl-cdn.alpinelinux.org/alpine/v3.18/community
-2
View File
@@ -1,2 +0,0 @@
http://mirrors.ustc.edu.cn/alpine/v3.18/main
http://mirrors.ustc.edu.cn/alpine/v3.18/community
-21
View File
@@ -1,21 +0,0 @@
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.
-101
View File
@@ -1,101 +0,0 @@
#!/bin/bash
# Ver: 1.6 by Endial Fang (endial@126.com)
#
# 通用函数库
# 加载依赖项
source "/usr/local/lib/liblog.sh"
# 函数列表
# 打印包含包含Logo的欢迎信息
print_welcome_info() {
[[ -n "${APP_NAME:-}" ]] && github_repo="https://gitee.com/colovu/docker-${APP_NAME}"
info " ____ _ "
info " / ___|___ | | _____ ___ _ "
info "| | / _ \| |/ _ \ \ / / | | | Application: ${APP_NAME:-undefined}"
info "| |__| (_) | | (_) \ V /| |_| | Version: ${APP_VER:-0.0.0}"
info " \____\___/|_|\___/ \_/ \__,_| PowerBy: Endial@126.com"
debug " Project Repo: ${github_repo:-}"
info ""
}
# 根据需要打印欢迎信息
print_image_welcome() {
if [[ -z "${DISABLE_WELCOME_MESSAGE:-}" ]]; then
if [[ ! "$(id -u)" = "0" ]]; then
print_welcome_info
fi
fi
}
# 检测可能导致容器执行后直接退出的命令,如"--help";如果存在,直接返回 0
# 参数:
# $1 - 待检测的参数表
print_command_help() {
local arg
for arg; do
if [[ "$arg" =~ ^(-[?HhVv]|--help|--version)$ ]]; then
exec "$@"
exit
fi
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}"
shift
local files=("$@")
debug "List to check: ${files[*]}"
for f in "${files[@]}"; do
debug "Processing: ${f}"
local dist="${base_path/$'\/etc'/$'\/srv\/conf'}/${f}"
if [[ -d "${base_path}/${f}" ]]; then
[[ ! -d "$dist" ]] && { debug "Creating directory: ${dist}"; mkdir -p "$dist"; }
[[ $(ls -A "${base_path}/${f}") ]] &&
ensure_config_file_exist "${base_path}/${f}" $(ls -A "${base_path}/${f}")
else
[[ ! -e "$dist" ]] && {
debug "Copying: ${base_path}/${f} => ${dist}"
cp "${base_path}/${f}" "$dist"
rm -f "/srv/conf/${APP_NAME}/.app_init_flag"
}
fi
done
}
# 根据脚本扩展名及权限,执行相应的初始化脚本
# 参数:
# $1 - 文件列表,支持路径通配符
# 使用:
# process_init_files [file [file [...]]]
# 例子:
# process_init_files /src/conf/${APP_NAME}/initdb.d/*
process_init_files() {
local f
while IFS= read -r -d '' f; do
case "$f" in
*.sh)
if [[ -x "$f" ]]; then
info "$0: executing $f"
"$f"
else
info "$0: sourcing $f"
source "$f"
fi
;;
*) warn "$0: ignoring $f" ;;
esac
done < <(find "$@" -type f -print0 2>/dev/null || true)
}
-145
View File
@@ -1,145 +0,0 @@
#!/bin/bash
# Ver: 1.2 by Endial Fang (endial@126.com)
#
# 文件操作函数库
# 加载依赖项
source "/usr/local/lib/libos.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
error "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' 方式操作
# 1) 部分系统兼容性问题,如从ConfigMap中加载的文件
# 2) 部分系统不支持"in-place" 方式操作
local -r del=$'\001' # Use a non-printable character as a 'sed' delimiter to avoid issues
if [[ $posix_regex = true ]]; then
result="$(sed -E "s${del}${match_regex}${del}${substitute_regex}${del}g" "$filename")"
else
result="$(sed "s${del}${match_regex}${del}${substitute_regex}${del}g" "$filename")"
fi
echo "$result" > "$filename"
}
# Replace a regex-matching multiline string in a file
# 使用规则表达式在文件中替换多行数据
# 参数:
# $1 - 文件名
# $2 - 正则表达式
# $3 - 替代数据表达式
replace_in_file_multiline() {
local filename="${1:?filename is required}"
local match_regex="${2:?match regex is required}"
local substitute_regex="${3:?substitute regex is required}"
local result
local -r del=$'\001' # Use a non-printable character as a 'sed' delimiter to avoid issues
result="$(perl -pe "BEGIN{undef $/;} s${del}${match_regex}${del}${substitute_regex}${del}sg" "$filename")"
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' 方式操作
# 1) 部分系统兼容性问题,如从ConfigMap中加载的文件
# 2) 部分系统不支持"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"
}
# 在符合条件的行后增加文本
# 参数:
# $1 - 文件名
# $2 - 正则表达式
# $3 - 待增加的文本
append_in_file() {
local file="${1:?missing file}"
local match_regex="${2:?missing pattern}"
local value="${3:?missing value}"
# We read the file in reverse, replace the first match (0,/pattern/s) and then reverse the results again
result="$(tac "$file" | sed -E "0,/($match_regex)/s||${value}\n\1|" | tac)"
echo "$result" > "$file"
}
# 等待日志文件中包含指定的条目
# 参数:
# $1 - 待查找的条目
# $2 - 日志文件
# $3 - 最大重试次数. Default: 12
# $4 - 重试间隔时间(秒). Default: 5
wait_for_log_entry() {
local -r entry="${1:-missing entry}"
local -r log_file="${2:-missing log file}"
local -r retries="${3:-12}"
local -r interval_time="${4:-5}"
local attempt=0
check_log_file_for_entry() {
if ! grep -qE "$entry" "$log_file"; then
debug "Entry \"${entry}\" still not present in ${log_file} (attempt $((++attempt))/${retries})"
return 1
fi
}
debug "Checking that ${log_file} log file contains entry \"${entry}\""
if retry_while check_log_file_for_entry "$retries" "$interval_time"; then
debug "Found entry \"${entry}\" in ${log_file}"
true
else
error "Could not find entry \"${entry}\" in ${log_file} after ${retries} retries"
debug_execute cat "$log_file"
return 1
fi
}
-170
View File
@@ -1,170 +0,0 @@
#!/bin/bash
# Ver: 1.3 by Endial Fang (endial@126.com)
#
# 文件管理函数库
# 加载依赖项
source "/usr/local/lib/liblog.sh"
# 函数列表
# 确保指定的 文件/路径 所属权为指定的 用户/组
# 参数:
# $1 - 文件路径
# $2 - 用户
# $3 - 用户组
ensure_owned_by() {
local path="${1:?path is missing}"
local owner="${2:?owner is missing}"
local group="${3:-}"
if [[ -n $group ]]; then
chown "$owner":"$group" "$path"
else
chown "$owner":"$owner" "$path"
fi
}
# 检测目录是否存在,如果不存在则创建,同时修改为指定的用户
# 参数:
# $1 - 目录路径
# $2 - 用户
# $3 - 用户组
ensure_dir_exists() {
local dir="${1:?directory is missing}"
local owner_user="${2:-}"
local owner_group="${3:-}"
[ -d "${dir}" ] || mkdir -p "${dir}"
if [[ -n $owner_user ]]; then
ensure_owned_by "$dir" "$owner_user" "$owner_group"
fi
}
# 检测目录是否存在或为空
# 参数:
# $1 - 目录路径
is_dir_empty() {
local dir="${1:?missing directory}"
# 计算真实路径,避免符号链接问题
local -r dir="$(realpath "$path")"
if [[ ! -e "$dir" ]] || [[ -z "$(ls -A "$dir")" ]]; then
true
else
false
fi
}
# 检测挂载的路径是否存在或为空
# 参数:
# $1 - 文件或路径
is_mounted_dir_empty() {
local dir="${1:?missing directory}"
if is_dir_empty "$dir" || find "$dir" -mindepth 1 -maxdepth 1 -not -name ".snapshot" -not -name "lost+found" -exec false {} +; then
true
else
false
fi
}
# 检测指定的路径当前用户是否可写入
# 参数:
# $1 - 文件或路径
is_writable() {
local file="${1:?missing file}"
local dir
dir="$(dirname "$file")"
if [[ (-f "$file" && -w "$file") || (! -f "$file" && -d "$dir" && -w "$dir") ]]; then
true
else
false
fi
}
# 生成相对路径
# 参数:
# $1 - 路径
# $2 - 基准路径
relativize() {
local -r path="${1:?missing path}"
local -r base="${2:?missing base}"
pushd "$base" >/dev/null || exit
realpath -q --no-symlinks --relative-base="$base" "$path" | sed -e 's|^/$|.|' -e 's|^/||'
popd >/dev/null || exit
}
# 循环设置目录中子目录及文件权限
# 参数:
# $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=""
# 参数有效性校验
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}"
;;
*)
error "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
debug "Check $p"
find -L "$p" -printf ""
if [[ -n ${dir_mode} ]]; then
debug "Change permissions to ${dir_mode} of directories in $p"
find -L "$p" -type d ! -perm "$dir_mode" -print0 | xargs -r -0 chmod "$dir_mode"
fi
if [[ -n ${file_mode} ]]; then
debug "Change permissions to ${file_mode} of files in $p"
find -L "$p" -type f ! -perm "$file_mode" -print0 | xargs -r -0 chmod "$file_mode"
fi
if [[ -n $user ]] && [[ -n ${group} ]]; then
debug "Change ownership to ${user}:${group} of files and directories in $p"
find -L "$p" -print0 | xargs -r -0 chown "${user}:${group}"
elif [[ -n $user ]] && [[ -z $group ]]; then
debug "Change user to ${user} of files and directories in $p"
find -L "$p" -print0 | xargs -r -0 chown "${user}"
elif [[ -z $user ]] && [[ -n $group ]]; then
debug "Change group to ${group} of files and directories in $p"
find -L "$p" -print0 | xargs -r -0 chgrp "${group}"
fi
else
error "$p does not exist"
fi
done
}
-97
View File
@@ -1,97 +0,0 @@
#!/bin/bash
# Ver: 1.2 by Endial Fang (endial@126.com)
#
# 日志输出函数库
#[[ ${ENV_DEBUG:-false} = true ]] && set -x
MODULE="$(basename "$0")"
# 定义颜色代码函数
get_color_code() {
local color="$1"
case "$color" in
"reset") echo '\033[0m' ;;
"red") echo '\033[38;5;1m' ;;
"green") echo '\033[38;5;2m' ;;
"yellow") echo '\033[38;5;3m' ;;
"blue") echo '\033[38;5;4m' ;;
"magenta") echo '\033[38;5;5m' ;;
"cyan") echo '\033[38;5;6m' ;;
*) echo "" ;;
esac
}
# 打印输出到 STDERR 设备
stderr_print() {
printf "%b\\n" "${*}" >&2
}
# 判断是否处于调试模式
is_debug_mode() {
local debug_var="${ENV_DEBUG:-false}"
shopt -s nocasematch
[[ "$debug_var" = 1 || "$debug_var" =~ ^(yes|true)$ ]]
}
# 输出实际日志信息
# 参数:
# $1 - 日志信息
log() {
local debug_info
if is_debug_mode; then
debug_info="$(get_color_code "cyan")${APP_NAME:-}:${MODULE:-}"
else
debug_info="$(get_color_code "cyan")${APP_NAME:-}"
fi
local timestamp="$(get_color_code "magenta")$(date "+%F %T.%3N")$(get_color_code "reset")"
stderr_print "$debug_info $timestamp $*"
}
# 输出调试类日志信息,尽量少使用
# 参数:
# $1 - 日志信息
debug() {
if is_debug_mode; then
log "$(get_color_code "blue")DBG$(get_color_code "reset"): $*"
fi
}
# 输出提示信息类日志信息
# 参数:
# $1 - 日志信息
info() {
log "$(get_color_code "green")INF$(get_color_code "reset"): $*"
}
# 输出警告类日志信息至sterr
# 参数:
# $1 - 日志信息
warn() {
log "$(get_color_code "yellow")WRN$(get_color_code "reset"): $*"
}
# 输出错误类日志信息至sterr,并退出脚本
# 参数:
# $1 - 日志信息
error() {
log "$(get_color_code "red")ERR$(get_color_code "reset"): $*"
}
# 缩进一个字符串
# 参数:
# $1 - 待缩进的字符串 (默认为空字符串)
# $2 - 缩进的字符数 (默认: 4)
# $3 - 缩进使用的字符 (默认: " ")
indent() {
local string="${1:-}"
local num="${2:?missing num}"
local char="${3:-" "}"
# 生成缩进填充的字符串
local indent_unit=""
for ((i = 0; i < num; i++)); do
indent_unit="${indent_unit}${char}"
done
echo "$string" | sed "s/^/${indent_unit}/"
}
-155
View File
@@ -1,155 +0,0 @@
#!/bin/bash
# Ver: 1.3 by Endial Fang (endial@126.com)
#
# 文件管理函数库
# 加载依赖项
source "/usr/local/lib/liblog.sh"
source "/usr/local/lib/libos.sh"
source "/usr/local/lib/libvalidations.sh"
# 函数列表
# 域名解析
# 参数:
# $1 - 需要解析的主机名
# $2 - IP 地址版本 v4/v6, 为空时解析所有版本
# 返回值:
# IP地址
dns_lookup() {
local host="${1:?host is missing}"
local ip_version="${2:-}"
getent "ahosts${ip_version}" "$host" | awk '/STREAM/ {print $1 }' | head -n 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() {
local -a ip_addresses
local hostname
hostname="$(hostname)"
read -r -a ip_addresses <<< "$(dns_lookup "$hostname" | xargs echo)"
if [[ "${#ip_addresses[@]}" -gt 1 ]]; then
warn "Found more than one IP address associated to hostname ${hostname}: ${ip_addresses[*]}, will use ${ip_addresses[0]}"
elif [[ "${#ip_addresses[@]}" -lt 1 ]]; then
error "Could not find any IP address associated to hostname ${hostname}"
exit 1
fi
# Check if the first IP address is IPv6 to add brackets
if validate_ipv6 "${ip_addresses[0]}" ; then
echo "[${ip_addresses[0]}]"
else
echo "${ip_addresses[0]}"
fi
}
# 检测指定的主机名是否可解析
# 参数:
# $1 - 待检测的主机名
# 返回值:
# true / false
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}]}"
}
# Wait for a HTTP connection to succeed
# 等待 HTTP 连接成功
# 参数:
# $1 - URL 地址
# $2 - 最大重试次数 (可选)
# $3 - 重试间隔时间 (可选)
wait_for_http_connection() {
local url="${1:?missing url}"
local retries="${2:-}"
local sleep_time="${3:-}"
if ! retry_while "debug_execute curl --silent ${url}" "$retries" "$sleep_time"; then
error "Could not connect to ${url}"
return 1
fi
}
-511
View File
@@ -1,511 +0,0 @@
#!/bin/bash
# Ver: 1.4 by Endial Fang (endial@126.com)
#
# 操作系统控制函数库
# 加载依赖项
source "/usr/local/lib/liblog.sh"
source "/usr/local/lib/libfs.sh"
source "/usr/local/lib/libvalidations.sh"
# 函数列表
# 检测指定用户账户是否存在
# 参数:
# $1 - 用户账户
# 返回值:
# 0 / 1
is_user_exists() {
local user="${1:?user is missing}"
id "$user" >/dev/null 2>&1
}
# 检测指定用户分组是否存在
# 参数:
# $1 - 用户组
# 返回值:
# 0 / 1
is_group_exists() {
local group="${1:?group is missing}"
getent group "$group" >/dev/null 2>&1
}
# 检测当前是否为 root 用户
# 返回值:
# true / false
is_root() {
if [[ "$(id -u)" = "0" ]]; then
debug "Run as root."
true
else
debug "Run as non-root: $(id -u)"
false
fi
}
# 确保指定用户组在系统中存在
# 参数:
# $1 - 用户组
# 标志位:
# -i|--gid - 用户组 ID
# -s|--system - 创建系统用户 (uid <= 999)
ensure_group_exists() {
local group="${1:?group is missing}"
local gid=""
local is_system_user=false
# 检测标志位
shift 1
while [ "$#" -gt 0 ]; do
case "$1" in
-i | --gid)
shift
gid="${1:?missing gid}"
;;
-s | --system)
is_system_user=true
;;
*)
echo "Invalid command line flag $1" >&2
return 1
;;
esac
shift
done
if ! group_exists "$group"; then
local -a args=("$group")
if [[ -n "$gid" ]]; then
if group_exists "$gid"; then
error "The GID $gid is already in use." >&2
return 1
fi
args+=("--gid" "$gid")
fi
$is_system_user && args+=("--system")
groupadd "${args[@]}" >/dev/null 2>&1
fi
}
# 确保指定用户在系统中存在
# 参数:
# $1 - 用户
# 标志位:
# -i|--uid - 用户 ID
# -a|--append-groups - 附加用户组 (逗号分隔)
# -g|--group - 用户组
# -h|--home - 用户家目录
# -s|--system - 创建系统用户 (uid <= 999)
ensure_user_exists() {
local user="${1:?user is missing}"
local uid=""
local group=""
local append_groups=""
local home=""
local is_system_user=false
# Validate arguments
shift 1
while [ "$#" -gt 0 ]; do
case "$1" in
-i | --uid)
shift
uid="${1:?missing uid}"
;;
-g | --group)
shift
group="${1:?missing group}"
;;
-a | --append-groups)
shift
append_groups="${1:?missing append_groups}"
;;
-h | --home)
shift
home="${1:?missing home directory}"
;;
-s | --system)
is_system_user=true
;;
*)
echo "Invalid command line flag $1" >&2
return 1
;;
esac
shift
done
if ! user_exists "$user"; then
local -a user_args=("-N" "$user")
if [[ -n "$uid" ]]; then
if user_exists "$uid"; then
error "The UID $uid is already in use."
return 1
fi
user_args+=("--uid" "$uid")
else
$is_system_user && user_args+=("--system")
fi
useradd "${user_args[@]}" >/dev/null 2>&1
fi
if [[ -n "$group" ]]; then
local -a group_args=("$group")
$is_system_user && group_args+=("--system")
ensure_group_exists "${group_args[@]}"
usermod -g "$group" "$user" >/dev/null 2>&1
fi
if [[ -n "$append_groups" ]]; then
local -a groups
read -ra groups <<<"$(tr ',;' ' ' <<<"$append_groups")"
for group in "${groups[@]}"; do
ensure_group_exists "$group"
usermod -aG "$group" "$user" >/dev/null 2>&1
done
fi
if [[ -n "$home" ]]; then
mkdir -p "$home"
usermod -d "$home" "$user" >/dev/null 2>&1
configure_permissions_ownership "$home" -d "775" -f "664" -u "$user" -g "$group"
fi
}
# 获取系统可用内存大小(MB)信息
# 返回值:
# 内存大小(兆字节)
get_total_memory() {
echo $(($(grep MemTotal /proc/meminfo | awk '{print $2}') / 1024))
}
# 获取以内存定量方式描述的机器类型
# 标志位:
# --memory - 内存大小 (MB,可选)
# 返回值:
# 类型名称
get_machine_size() {
local memory=""
# 检测标志位
while [[ "$#" -gt 0 ]]; do
case "$1" in
--memory)
shift
memory="${1:?missing memory}"
;;
*)
echo "Invalid command line flag $1" >&2
return 1
;;
esac
shift
done
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|M|g|G) ]]; then
size="${BASH_REMATCH[1]}"
unit="${BASH_REMATCH[2]}"
if [[ "$unit" = "g" || "$unit" = "G" ]]; then
amount="$((size * 1024))"
else
amount="$size"
fi
fi
echo "$amount"
}
# 如果禁用调试模式,将输出信息重定向至 /dev/null
# 参数:
# $@ - 待执行的命令
debug_execute() {
if is_boolean_yes "${ENV_DEBUG:-false}"; then
"$@"
else
"$@" >/dev/null 2>&1
fi
}
# 重试执行命令
# 参数:
# $1 - 命令 (字符串)
# $2 - 最大尝试次数. 默认值: 12
# $3 - 重试前等待时间(秒). 默认值: 5
# 返回值:
# 0 / 1
retry_while() {
local cmd="${1:?cmd is missing}"
local retries="${2:-12}"
local 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
}
# 生成随机字符串
# 标志位:
# -t|--type - 字符串类型 (ascii, alphanumeric, numeric). 默认值: ascii
# -c|--count - 字符串长度. 默认值: 32
# 返回值:
# 字符串
generate_random_string() {
local type="ascii"
local count="32"
local filter
local result
# 检测标志位
while [[ "$#" -gt 0 ]]; do
case "$1" in
-t | --type)
shift
type="$1"
;;
-c | --count)
shift
count="$1"
;;
*)
echo "Invalid command line flag $1" >&2
return 1
;;
esac
shift
done
# 检测类型
case "$type" in
ascii)
filter="[:print:]"
;;
numeric)
filter="0-9"
;;
alphanumeric)
filter="a-zA-Z0-9"
;;
alphanumeric+special|special+alphanumeric)
# Limit variety of special characters, so there is a higher chance of containing more alphanumeric characters
# Special characters are harder to write, and it could impact the overall UX if most passwords are too complex
filter='a-zA-Z0-9:@.,/+!='
;;
*)
echo "Invalid type ${type}" >&2
return 1
;;
esac
# Obtain count + 10 lines from /dev/urandom to ensure that the resulting string has the expected size
# Note there is a very small chance of strings starting with EOL character
# Therefore, the higher amount of lines read, this will happen less frequently
result="$(head -n "$((count + 10))" /dev/urandom | tr -dc "$filter" | head -c "$count")"
echo "$result"
}
# 为指定字符串生成 MD5 值
# 参数:
# $1 - 字符串
# 返回值:
# 字符串对应的 MD5
generate_md5_hash() {
local -r str="${1:?missing input string}"
echo -n "$str" | md5sum | awk '{print $1}'
}
# 为指定字符串生成 SHA1 值
# 参数:
# $1 - 字符串
# $2 - 算法 - 1 (default), 224, 256, 384, 512
# 返回值:
# 字符串对应的 SHA1
generate_sha_hash() {
local -r str="${1:?missing input string}"
local -r algorithm="${2:-1}"
echo -n "$str" | "sha${algorithm}sum" | awk '{print $1}'
}
# 将一个字符串转换为其十六进制表示形式
# 参数:
# $1 - 字符串
# 返回值:
# 十六进制表示形式
convert_to_hex() {
local -r str=${1:?missing input string}
local -i iterator
local char
for ((iterator = 0; iterator < ${#str}; iterator++)); do
char=${str:iterator:1}
printf '%x' "'${char}"
done
}
# 获取启动时间
# 返回值:
# 启动时间
get_boot_time() {
stat /proc --format=%Y
}
# 获取机器 ID
# 返回值:
# 机器 ID
get_machine_id() {
local machine_id
if [[ -f /etc/machine-id ]]; then
machine_id="$(cat /etc/machine-id)"
fi
if [[ -z "$machine_id" ]]; then
# Fallback to the boot-time, which will at least ensure a unique ID in the current session
machine_id="$(get_boot_time)"
fi
echo "$machine_id"
}
# 获取根分区的磁盘设备 ID(如: /dev/sda1
# 返回值:
# 根分区磁盘设备 ID
get_disk_device_id() {
local device_id=""
if grep -q ^/dev /proc/mounts; then
device_id="$(grep ^/dev /proc/mounts | awk '$2 == "/" { print $1 }' | tail -1)"
fi
# If it could not be autodetected, fallback to /dev/sda1 as a default
if [[ -z "$device_id" || ! -b "$device_id" ]]; then
device_id="/dev/sda1"
fi
echo "$device_id"
}
# 获取根磁盘设备 ID(如: /dev/sda
# 返回值:
# 根磁盘设备 ID
get_root_disk_device_id() {
get_disk_device_id | sed -E 's/p?[0-9]+$//'
}
# 获取根磁盘大小
# 返回值:
# 根磁盘大小(字节)
get_root_disk_size() {
fdisk -l "$(get_root_disk_device_id)" | grep 'Disk.*bytes' | sed -E 's/.*, ([0-9]+) bytes,.*/\1/' || true
}
# 以指定用户身份运行命令
# 注意:此函数将使用 chroot 命令来模拟指定用户身份运行命令,
# 参数:
# $1 - 用户(可选)
# $2..$n - 命令及命令参数
run_as_user() {
run_chroot "$@"
}
# 以指定用户身份执行命令
# 注意:此命令将替换当前进程映像,
# 参数:
# $1 - 用户(可选)
# $2..$n - 命令及命令参数
exec_as_user() {
run_chroot --replace-process "$@"
}
# 使用 chroot 命令以指定用户身份运行命令
# 参数:
# $1 - 用户(可选)
# $2..$n - 命令及命令参数
# 标志:
# -r | --replace-process - 替换当前进程映像
run_chroot() {
local userspec
local user
local homedir
local replace=false
local -r cwd="$(pwd)"
# 解析并验证标志位
while [[ "$#" -gt 0 ]]; do
case "$1" in
-r | --replace-process)
replace=true
;;
--)
shift
break
;;
-*)
stderr_print "unrecognized flag $1"
return 1
;;
*)
break
;;
esac
shift
done
# 解析并验证参数
if [[ "$#" -lt 2 ]]; then
echo "expected at least 2 arguments"
return 1
else
userspec=$1
shift
# 用户名可以包含组,因此我们解析用户
user=$(echo "$userspec" | cut -d':' -f1)
fi
if ! is_root; then
error "Could not switch to '${userspec}': Operation not permitted"
return 1
fi
# 获取用户的家目录,因为 chroot 不会正确更新此环境变量,并且一些脚本依赖于它
homedir=$(eval echo "~${user}")
if [[ ! -d $homedir ]]; then
homedir="${HOME:-/}"
fi
# 取得 "$@" 的值,以便正确支持 shell 参数扩展
if [[ "$replace" = true ]]; then
exec chroot --userspec="$userspec" / bash -c "cd ${cwd}; export HOME=${homedir}; exec \"\$@\"" -- "$@"
else
chroot --userspec="$userspec" / bash -c "cd ${cwd}; export HOME=${homedir}; exec \"\$@\"" -- "$@"
fi
}
-449
View File
@@ -1,449 +0,0 @@
#!/bin/bash
# Ver: 1.2 by Endial Fang (endial@126.com)
#
# 服务管理函数库
# shellcheck disable=SC1091
# 加载依赖项
source "/usr/local/lib/liblog.sh"
source "/usr/local/lib/libvalidations.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
# 返回值:
# 0 / 1
is_service_running() {
local pid="${1:?pid is missing}"
kill -0 "$pid" 2>/dev/null
}
# 通过发送信号停止一个指定 PID 的服务
# 参数:
# $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
}
# 启动一个 cron 守护进程
# 返回值:
# true / false
cron_start() {
if [[ -x "/usr/sbin/cron" ]]; then
/usr/sbin/cron
elif [[ -x "/usr/sbin/crond" ]]; then
/usr/sbin/crond
else
false
fi
}
# 为指定的服务生成 cron 配置文件
# 参数:
# $1 - 服务名称
# $2 - 命令
# 标志位:
# --run-as - 运行的用户. 默认值: root
# --schedule - Cron 周期配置. 默认值: * * * * *
generate_cron_conf() {
local service_name="${1:?service name is missing}"
local cmd="${2:?command is missing}"
local run_as="root"
local schedule="* * * * *"
local clean="true"
# 检测标志位
shift 2
while [[ "$#" -gt 0 ]]; do
case "$1" in
--run-as)
shift
run_as="$1"
;;
--schedule)
shift
schedule="$1"
;;
--no-clean)
clean="false"
;;
*)
echo "Invalid command line flag ${1}" >&2
return 1
;;
esac
shift
done
mkdir -p /etc/cron.d
if "$clean"; then
cat > "/etc/cron.d/${service_name}" <<EOF
${schedule} ${run_as} ${cmd}
EOF
else
echo "${schedule} ${run_as} ${cmd}" >> /etc/cron.d/"$service_name"
fi
}
# 删除指定服务的 cron 配置文件
# 参数:
# $1 - 服务名称
remove_cron_conf() {
local service_name="${1:?service name is missing}"
local cron_conf_dir="/etc/monit/conf.d"
rm -f "${cron_conf_dir}/${service_name}"
}
# 为指定的服务生成 monit 配置文件
# 参数:
# $1 - 服务名
# $2 - PID 文件
# $3 - 启动命令
# $4 - 停止命令
# 标志位:
# --disabled - 是否禁用. 默认值: no
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"
# 检测标志位
shift 4
while [[ "$#" -gt 0 ]]; do
case "$1" in
--disable)
disabled="yes"
;;
*)
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
}
# 删除指定服务的 monit 配置文件
# 参数:
# $1 - 服务名称
remove_monit_conf() {
local service_name="${1:?service name is missing}"
local monit_conf_dir="/etc/monit/conf.d"
rm -f "${monit_conf_dir}/${service_name}.conf"
}
# 为指定的服务生成 Logrotate 配置文件
# 参数:
# $1 - 应用名称
# $2 - 日志路径
# 标志位:
# --period - 周期
# --rotations - Rotations 存储的数量
# --extra - 扩展参数 (可选)
generate_logrotate_conf() {
local service_name="${1:?service name is missing}"
local log_path="${2:?log path is missing}"
local period="weekly"
local rotations="150"
local extra=""
local logrotate_conf_dir="/etc/logrotate.d"
local var_name
# 检测标志位
shift 2
while [[ "$#" -gt 0 ]]; do
case "$1" in
--period|--rotations|--extra)
var_name="$(echo "$1" | sed -e "s/^--//" -e "s/-/_/g")"
shift
declare "$var_name"="${1:?"$var_name" is missing}"
;;
*)
echo "Invalid command line flag ${1}" >&2
return 1
;;
esac
shift
done
mkdir -p "$logrotate_conf_dir"
cat <<EOF | sed '/^\s*$/d' > "${logrotate_conf_dir}/${service_name}"
${log_path} {
${period}
rotate ${rotations}
dateext
compress
copytruncate
missingok
$(indent "$extra" 2)
}
EOF
}
# 删除指定服务的 Logrotate 配置文件
# 参数:
# $1 - 服务名称
remove_logrotate_conf() {
local service_name="${1:?service name is missing}"
local logrotate_conf_dir="/etc/logrotate.d"
rm -f "${logrotate_conf_dir}/${service_name}"
}
# 为指定的服务生成 Systemd 配置文件
# 参数:
# $1 - 服务名称
# 标志位:
# --custom-service-content - 自定义内容
# --environment - 环境变量(可选)
# --environment-file - 环境变量文件(可选)
# --exec-start - 启动命令(必须)
# --exec-start-pre - 启动前命令(可选)
# --exec-start-post - 启动后命令(可选)
# --exec-stop - 停止命令(可选)
# --exec-reload - 重载命令(可选)
# --group - 系统组
# --name - 服务名称(默认为 $1)
# --restart - 重启策略
# --pid-file - PID 文件
# --standard-output - 标准输出文件
# --standard-error - 标准错误文件
# --success-exit-status - 成功退出状态码
# --type - 服务类型(默认为 Fork)
# --user - 系统用户
# --working-directory - 工作目录
generate_systemd_conf() {
local -r service_name="${1:?service name is missing}"
local -r systemd_units_dir="/etc/systemd/system"
local -r service_file="${systemd_units_dir}/bitnami.${service_name}.service"
# Default values
local name="$service_name"
local type="forking"
local user=""
local group=""
local environment=""
local environment_file=""
local exec_start=""
local exec_start_pre=""
local exec_start_post=""
local exec_stop=""
local exec_reload=""
local restart="always"
local pid_file=""
local standard_output="journal"
local standard_error=""
local limits_content=""
local success_exit_status=""
local custom_service_content=""
local working_directory=""
# Parse CLI flags
shift
while [[ "$#" -gt 0 ]]; do
case "$1" in
--name \
| --type \
| --user \
| --group \
| --exec-start \
| --exec-stop \
| --exec-reload \
| --restart \
| --pid-file \
| --standard-output \
| --standard-error \
| --success-exit-status \
| --custom-service-content \
| --working-directory \
)
var_name="$(echo "$1" | sed -e "s/^--//" -e "s/-/_/g")"
shift
declare "$var_name"="${1:?"${var_name} value is missing"}"
;;
--limit-*)
[[ -n "$limits_content" ]] && limits_content+=$'\n'
var_name="${1//--limit-}"
shift
limits_content+="Limit${var_name^^}=${1:?"--limit-${var_name} value is missing"}"
;;
--exec-start-pre)
shift
[[ -n "$exec_start_pre" ]] && exec_start_pre+=$'\n'
exec_start_pre+="ExecStartPre=${1:?"--exec-start-pre value is missing"}"
;;
--exec-start-post)
shift
[[ -n "$exec_start_post" ]] && exec_start_post+=$'\n'
exec_start_post+="ExecStartPost=${1:?"--exec-start-post value is missing"}"
;;
--environment)
shift
# It is possible to add multiple environment lines
[[ -n "$environment" ]] && environment+=$'\n'
environment+="Environment=${1:?"--environment value is missing"}"
;;
--environment-file)
shift
# It is possible to add multiple environment-file lines
[[ -n "$environment_file" ]] && environment_file+=$'\n'
environment_file+="EnvironmentFile=${1:?"--environment-file value is missing"}"
;;
*)
echo "Invalid command line flag ${1}" >&2
return 1
;;
esac
shift
done
# Validate inputs
local error="no"
if [[ -z "$exec_start" ]]; then
error "The --exec-start option is required"
error="yes"
fi
if [[ "$error" != "no" ]]; then
return 1
fi
# Generate the Systemd unit
cat > "$service_file" <<EOF
# Copyright Broadcom, Inc. All Rights Reserved.
# SPDX-License-Identifier: APACHE-2.0
[Unit]
Description=Bitnami service for ${name}
# Starting/stopping the main bitnami service should cause the same effect for this service
PartOf=bitnami.service
[Service]
Type=${type}
EOF
if [[ -n "$working_directory" ]]; then
cat >> "$service_file" <<< "WorkingDirectory=${working_directory}"
fi
if [[ -n "$exec_start_pre" ]]; then
# This variable may contain multiple ExecStartPre= directives
cat >> "$service_file" <<< "$exec_start_pre"
fi
if [[ -n "$exec_start" ]]; then
cat >> "$service_file" <<< "ExecStart=${exec_start}"
fi
if [[ -n "$exec_start_post" ]]; then
# This variable may contain multiple ExecStartPost= directives
cat >> "$service_file" <<< "$exec_start_post"
fi
# Optional stop and reload commands
if [[ -n "$exec_stop" ]]; then
cat >> "$service_file" <<< "ExecStop=${exec_stop}"
fi
if [[ -n "$exec_reload" ]]; then
cat >> "$service_file" <<< "ExecReload=${exec_reload}"
fi
# User and group
if [[ -n "$user" ]]; then
cat >> "$service_file" <<< "User=${user}"
fi
if [[ -n "$group" ]]; then
cat >> "$service_file" <<< "Group=${group}"
fi
# PID file allows to determine if the main process is running properly (for Restart=always)
if [[ -n "$pid_file" ]]; then
cat >> "$service_file" <<< "PIDFile=${pid_file}"
fi
if [[ -n "$restart" ]]; then
cat >> "$service_file" <<< "Restart=${restart}"
fi
# Environment flags
if [[ -n "$environment" ]]; then
# This variable may contain multiple Environment= directives
cat >> "$service_file" <<< "$environment"
fi
if [[ -n "$environment_file" ]]; then
# This variable may contain multiple EnvironmentFile= directives
cat >> "$service_file" <<< "$environment_file"
fi
# Logging
if [[ -n "$standard_output" ]]; then
cat >> "$service_file" <<< "StandardOutput=${standard_output}"
fi
if [[ -n "$standard_error" ]]; then
cat >> "$service_file" <<< "StandardError=${standard_error}"
fi
if [[ -n "$custom_service_content" ]]; then
# This variable may contain multiple miscellaneous directives
cat >> "$service_file" <<< "$custom_service_content"
fi
if [[ -n "$success_exit_status" ]]; then
cat >> "$service_file" <<EOF
# When the process receives a SIGTERM signal, it exits with code ${success_exit_status}
SuccessExitStatus=${success_exit_status}
EOF
fi
cat >> "$service_file" <<EOF
# Optimizations
TimeoutStartSec=2min
TimeoutStopSec=30s
IgnoreSIGPIPE=no
KillMode=mixed
EOF
if [[ -n "$limits_content" ]]; then
cat >> "$service_file" <<EOF
# Limits
${limits_content}
EOF
fi
cat >> "$service_file" <<EOF
[Install]
# Enabling/disabling the main bitnami service should cause the same effect for this service
WantedBy=bitnami.service
EOF
}
-285
View File
@@ -1,285 +0,0 @@
#!/bin/bash
# Ver: 1.2 by Endial Fang (endial@126.com)
#
# 数据有效性校验函数库
# 加载依赖项
source "/usr/local/lib/liblog.sh"
# 函数列表
# 检测数据是否为整数
# 参数:
# $1 - 待检测的数据
# 返回值:
# true / false
is_int() {
local -r int="${1:?missing value}"
if [[ "$int" =~ ^-?[0-9]+ ]]; then
true
else
false
fi
}
# 检测数据是否为正整数
# 参数:
# $1 - 待检测的数据
# 返回值:
# true / false
is_positive_int() {
local -r int="${1:?missing value}"
if is_int "$int" && (( "${int}" >= 0 )); then
true
else
false
fi
}
# 检测数据是否为布尔值 '1' 或字符串 'yes/true'
# 参数:
# $1 - 待检测的数据
# 返回值:
# true / false
is_boolean_yes() {
local -r bool="${1:-}"
# 忽略字母大小写
shopt -s nocasematch
if [[ "$bool" = 1 || "$bool" =~ ^(yes|true)$ ]]; then
true
else
false
fi
}
# 检测数据是否为字符串 'yes/no'
# 参数:
# $1 - 待检测的数据
# 返回值:
# true / false
is_yes_no_value() {
local -r bool="${1:-}"
if [[ "$bool" =~ ^(yes|no)$ ]]; then
true
else
false
fi
}
# 检测数据是否为字符串 'true/false'
# 参数:
# $1 - 待检测的数据
# 返回值:
# true / false
is_true_false_value() {
local -r bool="${1:-}"
if [[ "$bool" =~ ^(true|false)$ ]]; 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/0
# 参数:
# $1 - 待检测的数据
is_1_0_value() {
local -r bool="${1:-}"
if [[ "$bool" =~ ^[10]$ ]]; then
true
else
false
fi
}
# 检测提供的参数是否为空字符串或未定义
# 参数:
# $1 - 待检测的数据
# 返回值:
# true / false
is_empty_value() {
local -r val="${1:-}"
if [[ -z "$val" ]]; then
true
else
false
fi
}
# 检测数据是否为有效的端口号
# 标志位:
# -unprivileged - 没有特权的端口
# 参数:
# $1 - 待检测的数据
# 返回值:
# true / false 或 错误消息
validate_port() {
local value
local unprivileged=0
# Parse flags
while [[ "$#" -gt 0 ]]; do
case "$1" in
-unprivileged)
unprivileged=1
;;
--)
shift
break
;;
-*)
error "unrecognized flag $1"
return 1
;;
*)
break
;;
esac
shift
done
if [[ "$#" -gt 1 ]]; then
error "too many arguments provided"
return 2
elif [[ "$#" -eq 0 ]]; then
error "missing port argument"
return 1
else
value=$1
fi
if [[ -z "$value" ]]; then
error "the value is empty"
return 1
else
if ! is_int "$value"; then
warn "value is not an integer"
return 2
elif [[ "$value" -lt 0 ]]; then
warn "negative value provided"
return 2
elif [[ "$value" -gt 65535 ]]; then
warn "requested port is greater than 65535"
return 2
elif [[ "$unprivileged" = 1 && "$value" -lt 1024 ]]; then
warn "privileged port requested"
return 3
fi
fi
}
# 检测数据是否为有效的IPv6地址
# 参数:
# $1 - 待检测的数据
validate_ipv6() {
local ip="${1:?ip is missing}"
local stat=1
local full_address_regex='^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$'
local short_address_regex='^((([0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4}){0,6}::(([0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4}){0,6})$'
if [[ $ip =~ $full_address_regex || $ip =~ $short_address_regex || $ip == "::" ]]; then
stat=0
fi
return $stat
}
# 检测数据是否为有效的IPv4地址
# 参数:
# $1 - 待检测的数据
# 返回值:
# 0 / 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
}
# 检测数据是否为有效的IP地址
# 参数:
# $1 - 待检测的数据
validate_ip() {
local ip="${1:?ip is missing}"
local stat=1
if validate_ipv4 "$ip"; then
stat=0
else
stat=$(validate_ipv6 "$ip")
fi
return $stat
}
# 校验字符串格式
# 标志位:
# -min-length - 最小长度
# -max-length - 最大长度
# 参数:
# $1 - 待检测的数据
# 返回值:
# 0 / 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
;;
-*)
error "unrecognized flag $1"
return 1
;;
*)
string="$1"
;;
esac
shift
done
if [[ "$min_length" -ge 0 ]] && [[ "${#string}" -lt "$min_length" ]]; then
info "string length is less than $min_length"
return 1
fi
if [[ "$max_length" -ge 0 ]] && [[ "${#string}" -gt "$max_length" ]]; then
info "string length is great than $max_length"
return 1
fi
}
-47
View File
@@ -1,47 +0,0 @@
#!/bin/bash
# Ver: 1.0 by Endial Fang (endial@126.com)
#
# 版本管理函数库
# 加载依赖项
source "/usr/local/lib/liblog.sh"
# 函数列表
# 获取语义化版本
# 参数:
# $1 - 版本字符串
# $2 - 版本部分,1 为 major2 为 minor3 为 patch
# 返回值:
# 版本数组,包含 majorminor 和 patch
get_sematic_version () {
local version="${1:?version is required}"
local section="${2:?section is required}"
local -a version_sections
# 用于解析版本的正则表达式:x.y.z
local -r regex='([0-9]+)(\.([0-9]+)(\.([0-9]+))?)?'
if [[ "$version" =~ $regex ]]; then
local i=1
local j=1
local n=${#BASH_REMATCH[*]}
while [[ $i -lt $n ]]; do
if [[ -n "${BASH_REMATCH[$i]}" ]] && [[ "${BASH_REMATCH[$i]:0:1}" != '.' ]]; then
version_sections[j]="${BASH_REMATCH[$i]}"
((j++))
fi
((i++))
done
local number_regex='^[0-9]+$'
if [[ "$section" =~ $number_regex ]] && (( section > 0 )) && (( section <= 3 )); then
echo "${version_sections[$section]}"
return
else
error "Section allowed values are: 1, 2, and 3"
return 1
fi
fi
}
-183
View File
@@ -1,183 +0,0 @@
#!/bin/bash
# Ver: 1.3 by Endial Fang (endial@126.com)
#
# shell 执行参数,分别为 -e(命令执行错误则退出脚本) -u(变量未定义则报错) -x(打印实际待执行的命令行)
set -eu
# 定义错误处理函数,添加错误位置信息
error() {
local error_location="$1"
local error_message="$2"
echo "Error at $error_location: $error_message" >&2
exit 1
}
print_usage() {
echo "Usage: download_pkg <COMMAND> <PACKAGE-NAME> \"<URLS>\" [OPTIONS]"
echo ""
echo "Download and install Third-Part packages"
echo ""
echo "Commands:"
echo " download Download a package."
echo " install Download and install a package."
echo " unpack Download and unpack a package."
echo ""
echo "Options:"
echo " -g, --checkpgp Package release bucket."
echo " -s, --checksum SHA256 verification checksum."
echo " -h, --help Show this help message and exit."
echo ""
echo "PACKAGE-NAME: Name with extern name"
echo "URLS: String with URL list"
echo ""
echo "Examples:"
echo " - Unpack package"
echo " \$ download_pkg unpack redis-5.0.8.tar.gz \"http://download.redis.io/releases\""
echo ""
echo " - Verify and Install package"
echo " \$ download_pkg install redis-5.0.8.tar.gz \"http://download.redis.io/releases\" -s 42cf86a114d2a451b898fcda96acd4d01062a7dbaaad2801d9164a36f898f596"
echo ""
}
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)"
if which gpg >/dev/null 2>&1; then
local key_servers=("pgp.mit.edu" "keys.gnupg.net" "keyserver.pgp.com" "ha.pool.sks-keyservers.net")
for key in $keys; do
for server in "${key_servers[@]}"; do
if gpg --batch --keyserver "$server" --recv-keys "${key}" --timeout 10; then
break
fi
done
done
gpg --batch --verify "$name_asc" "$name" || error "PGP verification" "PGP verification failed"
command -v gpgconf > /dev/null && gpgconf --kill all
fi
}
# 获取并解析参数
parsed_args=$(getopt -o g:s:h -l "checkpgp:,checksum:,help" -n "download-pkg" -- "$@")
if [ $? -ne 0 ]; then
error "Parameter parsing" "Failed to parse command line arguments."
print_usage
exit 1
fi
eval set -- "$parsed_args";
while true; do
case "$1" in
-g|--checkpgp)
shift
if [ -n "$1" ]; then
package_keys=$1
shift
fi
;;
-s|--checksum)
shift
if [ -n "$1" ]; then
package_sha256=$1
shift
fi
;;
-h|--help)
print_usage
exit 0
;;
--)
shift
break
;;
esac
done
# 检测输入的命令是否合法
case "$1" in
download|install|unpack) ;;
*)
error "Command validation" "Unrecognized command: $1"
print_usage
exit 1
;;
esac
# 检测输入参数是否足够,需要至少提供软件包名称 及 下载路径
if [ $# -lt 3 ]; then
error "Parameter validation" "Insufficient parameters. Please provide package name and URLs."
print_usage
exit 1
fi
install_root=/usr/local
cache_root=/tmp
package="$2"
package_urls=$3
# 检查缓存目录中是否已存在该软件包
if [ -e "$cache_root/$package" ]; then
echo "Package already exists in cache: $cache_root/$package"
else
cd $install_root
echo "Downloading $package package"
for url in $package_urls; do
echo "Try $url/$package"
if wget -T 10 -O "$cache_root/$package" "$url/$package" && [ -s "$cache_root/$package" ]; then
if [ -n "${package_keys:-}" ]; then
wget -T 10 -O "$cache_root/$package.asc" "$url/$package.asc" || wget -T 10 -O "$cache_root/$package.asc" "$url/$package.sign" || :
if [ ! -e "$cache_root/$package.asc" ]; then
error "PGP signature download" "Failed to download PGP signature file."
exit 1
fi
fi
break
else
echo "Failed to download from $url/$package"
fi
done
fi
if [ -n "${package_sha256:-}" ]; then
echo "Verifying package integrity"
if ! echo "$package_sha256 *$cache_root/$package" | sha256sum -c -; then
error "SHA256 verification" "SHA256 verification failed"
fi
fi
if [ -e "$cache_root/$package.asc" ]; then
echo "Verifying package with PGP"
check_pgp "$cache_root/$package.asc" "$cache_root/$package" "$package_keys"
fi
# If the tarball has too many files, it can trigger a bug
# in overlayfs when using tar. Install bsdtar in the container image
# to workaround it. As the overhead is too big (~40 MB), it is not added by
# default. Source: https://github.com/coreos/bugs/issues/1095
# 安装或解压软件
case "$1" in
download)
echo "Download success: $cache_root/$package"
;;
install)
echo "Installing $package"
cp $cache_root/$package /usr/local/sbin/
;;
unpack)
if ! tar -taf $cache_root/$package >/dev/null 2>&1; then
error "Package integrity check" "Invalid or corrupt '$package' package."
exit 1
fi
echo "Unpacking $package to $cache_root"
cd $cache_root
if which bsdtar >/dev/null 2>&1; then
bsdtar -xf $cache_root/$package
else
tar --no-same-owner -xaf $cache_root/$package
fi
;;
esac
-66
View File
@@ -1,66 +0,0 @@
#!/bin/sh
# Ver: 1.3 by Endial Fang (endial@126.com)
#
# shell 执行参数,分别为 -e(命令执行错误则退出脚本) -u(变量未定义则报错) -x(打印实际待执行的命令行)
set -eu
# 检查用户权限
if [ "$(id -u)" -ne 0 ]; then
echo "Error: This script must be run as root."
exit 1
fi
print_usage() {
echo "Usage: install_pkg <PACKAGE-NAME>"
echo ""
echo "Download and install packages"
echo ""
echo "Options:"
echo " -h, --help Show this help message and exit."
echo ""
echo "Examples:"
echo " - Install bash & curl"
echo " \$ install_pkg bash curl"
echo ""
}
if [ $# -lt 1 ]; then
print_usage
exit 1
fi
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
print_usage
exit 0
;;
*)
break
;;
esac
done
retry=0
max=2
until [ $retry -gt $max ]; do
set +e
(
echo "Update and install packages..."
apk update --no-cache
apk add --no-cache "$@"
)
CODE=$?
set -e
if [ $CODE -eq 0 ]; then
break
fi
if [ $retry -eq $max ]; then
echo "Failed to install packages after $max retries."
exit $CODE
fi
echo "APK failed, retrying"
retry=$(($retry + 1))
done
# rm -r /var/cache/apk/* /root/.cache /tmp/* || :
-31
View File
@@ -1,31 +0,0 @@
#!/bin/sh
# Ver: 1.3 by Endial Fang (endial@126.com)
#
# 此脚本用于根据传入的参数选择对应的 apk 源配置文件并复制到指定目录
# shell 执行参数,分别为 -e(命令执行错误则退出脚本) -u(变量未定义则报错) -x(打印实际待执行的命令行)
set -eu
# 检查是否有足够的权限
if [ "$(id -u)" -ne 0 ]; then
echo "Error: This script must be run as root."
exit 1
fi
# 获取用户传入的参数,若未传入则使用默认值 "default"
source_name=${1:-default}
# 定义源文件路径
source_file="/etc/apk/repositories.${source_name}"
# 检查源文件是否存在
if [ ! -f "$source_file" ]; then
echo "Error: Source file $source_file does not exist."
exit 1
fi
# 定义目标文件路径
target_file="/etc/apk/repositories"
# 复制源文件到目标文件
cp "$source_file" "$target_file"
echo "Successfully selected apt source: $source_name"