diff --git a/.ci/common b/.ci/common new file mode 160000 index 0000000..73c5b20 --- /dev/null +++ b/.ci/common @@ -0,0 +1 @@ +Subproject commit 73c5b2003482ae5c2af289e2e85e8927f69aa0a1 diff --git a/.dockerignore b/.dockerignore index 4df4001..85be1e7 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,6 @@ .git .gitignore +.gitmodules ./Makefile diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 24bc8a8..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -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" diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..a2aba7e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule ".ci/common"] + path = .ci/common + url = https://git.colovu.com/docker/common.git diff --git a/.woodpecker.yml b/.woodpecker.yml new file mode 100644 index 0000000..1afc706 --- /dev/null +++ b/.woodpecker.yml @@ -0,0 +1,50 @@ +# 注意: +# 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: + - event: push + branch: [ "main", "master" ] + - event: push + branch: "v[0-9]*.[0-9]*.[0-9]*" + - event: push + branch: "[0-9]*.[0-9]*.[0-9]*" + - event: tag + ref: "refs/tags/v[0-9]*.[0-9]*.[0-9]*" + - event: tag + ref: "refs/tags/[0-9]*.[0-9]*.[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 diff --git a/Dockerfile b/Dockerfile index b1106db..1b44ea6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,10 +16,9 @@ # 该部分变量,在编译命令中通过 `--build-arg` 传入;如果未设置,则使用下面对应的默认值 ARG APP_NAME=alpine -ARG APP_VER=3.20 -ARG REGISTRY_URL="registry.colovu.com/docker-proxy/" -ARG APT_SOURCE=ustc -ARG LOCAL_URL="http://pkgs.colovu.com/dist" +ARG APP_VER=3.22 +ARG REGISTRY_URL="docker.io/" +ARG APT_SOURCE=default # 1. 生成镜像 ===================================================================== FROM --platform=${TARGETPLATFORM:-linux/amd64} ${REGISTRY_URL}alpine:${APP_VER} @@ -29,16 +28,26 @@ ARG APP_NAME ARG APP_VER ARG APT_SOURCE -LABEL \ - "Version"="v${APP_VER}" \ - "Description"="Docker image for Alpine." \ - "Github"="https://gitee.com/colovu/docker-${APP_NAME}" \ - "Vendor"="Endial Fang (endial@126.com)" +# 镜像元数据标签 - 符合OCI镜像规范 +LABEL org.opencontainers.image.title="${APP_NAME}" \ + org.opencontainers.image.version="${APP_VER}" \ + org.opencontainers.image.description="Docker image for Alpine." \ + org.opencontainers.image.authors="Endial Fang " \ + 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 " + +# 拷贝源仓库配置文件 +COPY etc /etc/ # 拷贝默认的通用脚本文件 -COPY prebuilds / +COPY .ci/common/alpine / -RUN \ +RUN set -eux; \ + \ # 选择软件包源,以加速后续软件包安装 select_source ${APT_SOURCE}; \ \ @@ -56,7 +65,8 @@ RUN \ cd .. && rm -r musl-locales; \ apk del .locale_build; \ rm -rf /var/cache/apk/*; - + +# 在安装像相应软件包后,设置对应的环境变量 ENV LANG=en_US.UTF-8 \ LANGUAGE=en_US.UTF-8 \ LC_ALL=en_US.UTF-8 diff --git a/Makefile b/Makefile deleted file mode 100644 index 9e20b22..0000000 --- a/Makefile +++ /dev/null @@ -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 '' | 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 '' | 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" diff --git a/build.sh b/build.sh index 6c10d3d..423d8e9 100755 --- a/build.sh +++ b/build.sh @@ -3,9 +3,13 @@ # # Docker 镜像构建脚本 (仅linux/amd64) +# 编译后镜像名称 IMAGE_NAME="alpine" -REGISTRY_URL="registry.colovu.com/docker-proxy/" -APT_SOURCE="ustc" +# 依赖镜像的仓库地址(本镜像需要依赖原生 alpine 镜像) +REGISTRY_URL="swr.cn-north-4.myhuaweicloud.com/img-sync/docker.io/" +# 源仓库地址(本地编译时,使用阿里云源仓库) +APT_SOURCE="aliyun" +APP_VER="3.22" # 获取发布版本标签 get_release_tag() { @@ -46,16 +50,57 @@ get_image_tag() { fi } -# 构建amd64架构镜像 +# 根据当前系统架构构建镜像 build() { local TAG=${1:-$(get_image_tag)} - echo "Building image ${IMAGE_NAME}:${TAG} (linux/amd64)" + + # 获取当前系统的架构 + local ARCH=$(uname -m) + local PLATFORM="" + case "$ARCH" in + x86_64) + PLATFORM="linux/amd64" + ;; + aarch64|arm64) + PLATFORM="linux/arm64" + ;; + armv7l) + PLATFORM="linux/arm/v7" + ;; + *) + PLATFORM="linux/amd64" # 默认为 amd64 + ;; + esac - podman build --platform linux/amd64 \ + # 如果在命令中指定了TAG,则使用相同的TAG作为APP_VER + if [ -n "$1" ]; then + APP_VER="$1" + else + # 获取当前分支名并判断是否为主分支 + local branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) + if [ "$branch" != "master" ] && [ "$branch" != "main" ]; then + # 如果不是主分支,则使用分支名或标签名 + local tag=$(git describe --tags --abbrev=0 2>/dev/null) + if [ -n "$tag" ] && [ "$(git rev-list -n 1 $tag 2>/dev/null)" = "$(git rev-parse HEAD)" ]; then + # 如果当前提交正好是标签,则使用标签名 + APP_VER="$tag" + else + # 否则使用分支名 + APP_VER="$branch" + fi + fi + fi + + # 去除APP_VER中的v前缀,仅保留数字分段部分 + APP_VER=$(echo "$APP_VER" | sed 's/^v//') + + echo "Building image ${IMAGE_NAME}:${TAG} (${PLATFORM}) with APP_VER=${APP_VER}" + + podman build --platform ${PLATFORM} \ --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 \ + --build-arg APP_VER=${APP_VER} \ -t ${IMAGE_NAME}:${TAG} \ -t ${IMAGE_NAME}:latest \ . @@ -63,47 +108,65 @@ build() { 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 '' | awk '{print $3}' | xargs -L 1 podman rmi -f + # 删除所有与 IMAGE_NAME 相关的镜像标签(包括带 localhost 前缀的) + podman images --format "{{.Repository}}:{{.Tag}}" | grep -E "^(${IMAGE_NAME}|localhost/${IMAGE_NAME}):" | xargs -r podman rmi -f + # 删除所有已退出的容器 + podman ps -a --format "{{.ID}}" --filter status=exited | xargs -r podman rm + # 删除所有悬空镜像(dangling images) + podman images --filter "dangling=true" -q | xargs -r podman rmi -f + # 清理构建缓存 + podman system prune -f } -# 主函数中更新使用说明 +# 显示使用帮助信息 +show_help() { + cat << EOF +Usage: $0 [COMMAND] [TAG] + +Docker 镜像构建和清理脚本 + +COMMANDS: + build [tag] 构建镜像,可选择性指定标签,如未指定则自动生成 + clean 清理工作空间,删除相关镜像和无用容器 + help 显示此帮助信息 + +EXAMPLES: + $0 build # 构建镜像,使用自动生成的标签 + $0 build mytag # 构建镜像,使用指定标签 + $0 clean # 清理工作空间 + $0 help # 显示帮助信息 + +DESCRIPTION: + 该脚本用于构建 alpine 镜像,支持自动生成标签,包含清理功能。 + 构建的镜像名称为 ${IMAGE_NAME},默认会同时生成 latest 标签。 + 脚本会根据当前系统架构自动构建对应架构的镜像,并设置 APP_VER 参数(自动去除v前缀): + - 根据当前系统架构自动选择平台(linux/amd64, linux/arm64, linux/arm/v7 等) + - 当在命令中指定TAG时,APP_VER使用相同的TAG值(去除v前缀) + - 主分支(master/main)时使用 latest + - 其他分支时使用分支名 + - 标签提交时使用标签名(去除v前缀) +EOF +} + +# 主函数 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 ;; + help|-h|--help) show_help ;; + *) + if [ -z "$1" ]; then + show_help + else + echo "Error: Unknown command '$1'" + echo "" + show_help + exit 1 + fi + ;; esac } diff --git a/etc/apk/repositories.aliyun b/etc/apk/repositories.aliyun new file mode 100644 index 0000000..9ebf653 --- /dev/null +++ b/etc/apk/repositories.aliyun @@ -0,0 +1,2 @@ +http://mirrors.aliyun.com/alpine/v3.22/main +http://mirrors.aliyun.com/alpine/v3.22/community diff --git a/etc/apk/repositories.default b/etc/apk/repositories.default new file mode 100644 index 0000000..fcb0827 --- /dev/null +++ b/etc/apk/repositories.default @@ -0,0 +1,2 @@ +http://dl-cdn.alpinelinux.org/alpine/v3.22/main +http://dl-cdn.alpinelinux.org/alpine/v3.22/community diff --git a/etc/apk/repositories.ustc b/etc/apk/repositories.ustc new file mode 100644 index 0000000..1b49677 --- /dev/null +++ b/etc/apk/repositories.ustc @@ -0,0 +1,2 @@ +http://mirrors.ustc.edu.cn/alpine/v3.22/main +http://mirrors.ustc.edu.cn/alpine/v3.22/community diff --git a/prebuilds/etc/apk/repositories.aliyun b/prebuilds/etc/apk/repositories.aliyun deleted file mode 100644 index 75159ea..0000000 --- a/prebuilds/etc/apk/repositories.aliyun +++ /dev/null @@ -1,2 +0,0 @@ -http://mirrors.aliyun.com/alpine/v3.18/main -http://mirrors.aliyun.com/alpine/v3.18/community diff --git a/prebuilds/etc/apk/repositories.default b/prebuilds/etc/apk/repositories.default deleted file mode 100644 index d55be9c..0000000 --- a/prebuilds/etc/apk/repositories.default +++ /dev/null @@ -1,2 +0,0 @@ -http://dl-cdn.alpinelinux.org/alpine/v3.18/main -http://dl-cdn.alpinelinux.org/alpine/v3.18/community diff --git a/prebuilds/etc/apk/repositories.ustc b/prebuilds/etc/apk/repositories.ustc deleted file mode 100644 index f07899b..0000000 --- a/prebuilds/etc/apk/repositories.ustc +++ /dev/null @@ -1,2 +0,0 @@ -http://mirrors.ustc.edu.cn/alpine/v3.18/main -http://mirrors.ustc.edu.cn/alpine/v3.18/community diff --git a/prebuilds/usr/local/LICENSE b/prebuilds/usr/local/LICENSE deleted file mode 100644 index 80e3bb7..0000000 --- a/prebuilds/usr/local/LICENSE +++ /dev/null @@ -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. diff --git a/prebuilds/usr/local/lib/libcommon.sh b/prebuilds/usr/local/lib/libcommon.sh deleted file mode 100644 index 5c1ce1b..0000000 --- a/prebuilds/usr/local/lib/libcommon.sh +++ /dev/null @@ -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) -} diff --git a/prebuilds/usr/local/lib/libfile.sh b/prebuilds/usr/local/lib/libfile.sh deleted file mode 100644 index 908e209..0000000 --- a/prebuilds/usr/local/lib/libfile.sh +++ /dev/null @@ -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 -} diff --git a/prebuilds/usr/local/lib/libfs.sh b/prebuilds/usr/local/lib/libfs.sh deleted file mode 100644 index 83c2e44..0000000 --- a/prebuilds/usr/local/lib/libfs.sh +++ /dev/null @@ -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 -} diff --git a/prebuilds/usr/local/lib/liblog.sh b/prebuilds/usr/local/lib/liblog.sh deleted file mode 100644 index b55ec2b..0000000 --- a/prebuilds/usr/local/lib/liblog.sh +++ /dev/null @@ -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}/" -} diff --git a/prebuilds/usr/local/lib/libnet.sh b/prebuilds/usr/local/lib/libnet.sh deleted file mode 100644 index 69d674b..0000000 --- a/prebuilds/usr/local/lib/libnet.sh +++ /dev/null @@ -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 -} diff --git a/prebuilds/usr/local/lib/libos.sh b/prebuilds/usr/local/lib/libos.sh deleted file mode 100644 index e84c812..0000000 --- a/prebuilds/usr/local/lib/libos.sh +++ /dev/null @@ -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 -} diff --git a/prebuilds/usr/local/lib/libservice.sh b/prebuilds/usr/local/lib/libservice.sh deleted file mode 100644 index a28a8ba..0000000 --- a/prebuilds/usr/local/lib/libservice.sh +++ /dev/null @@ -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}" <> /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:-}" <&2 - return 1 - ;; - esac - shift - done - - mkdir -p "$logrotate_conf_dir" - cat < "${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" <> "$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" <> "$service_file" <> "$service_file" <> "$service_file" <= 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 -} diff --git a/prebuilds/usr/local/lib/libversion.sh b/prebuilds/usr/local/lib/libversion.sh deleted file mode 100644 index 190d991..0000000 --- a/prebuilds/usr/local/lib/libversion.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash -# Ver: 1.0 by Endial Fang (endial@126.com) -# -# 版本管理函数库 - -# 加载依赖项 -source "/usr/local/lib/liblog.sh" - -# 函数列表 - -# 获取语义化版本 -# 参数: -# $1 - 版本字符串 -# $2 - 版本部分,1 为 major,2 为 minor,3 为 patch -# 返回值: -# 版本数组,包含 major,minor 和 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 -} diff --git a/prebuilds/usr/local/sbin/download_pkg b/prebuilds/usr/local/sbin/download_pkg deleted file mode 100755 index 3717706..0000000 --- a/prebuilds/usr/local/sbin/download_pkg +++ /dev/null @@ -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 \"\" [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 diff --git a/prebuilds/usr/local/sbin/install_pkg b/prebuilds/usr/local/sbin/install_pkg deleted file mode 100755 index 20a86ff..0000000 --- a/prebuilds/usr/local/sbin/install_pkg +++ /dev/null @@ -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 " - 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/* || : diff --git a/prebuilds/usr/local/sbin/select_source b/prebuilds/usr/local/sbin/select_source deleted file mode 100755 index 7c47df1..0000000 --- a/prebuilds/usr/local/sbin/select_source +++ /dev/null @@ -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"