#!/usr/bin/env bash set -Eeuo pipefail REPO_URL=${PLUGIN_REPO_URL:-https://git.awaioi.com/awaioi/sub2api-add.git} SOURCE_DIR=${PLUGIN_SOURCE_DIR:-/opt/sub2api-add} REF=${PLUGIN_REF:-v1.1.1} EXPECTED_SHA=${PLUGIN_COMMIT_SHA:-} ALLOW_MUTABLE_REF=${PLUGIN_ALLOW_MUTABLE_REF:-false} DEPLOY_ENV=${PLUGIN_ENV:-production} ALLOW_UNPINNED_TAG=${PLUGIN_ALLOW_UNPINNED_TAG:-false} die() { printf '错误:%s\n' "$*" >&2; exit 1; } validate_path_components() { local value=$1 label=$2 component current="" [[ -n "$value" && "$value" = /* && "$value" != "/" ]] || die "$label 必须是非根绝对路径" [[ "$value" != *$'\n'* && "$value" != *$'\r'* ]] || die "$label 包含非法换行" local parts=() IFS='/' read -r -a parts <<< "${value#/}" for component in "${parts[@]}"; do [[ -z "$component" ]] && continue [[ "$component" != "." && "$component" != ".." ]] || die "$label 不能包含 . 或 .. 路径组件" current="$current/$component" [[ ! -L "$current" ]] || die "$label 的路径组件不能是符号链接:$current" [[ ! -e "$current" || -d "$current" ]] || die "$label 的路径组件不是目录:$current" done } ensure_real_parent() { local path=$1 label=$2 parent parent=$(dirname -- "$path") [[ -d "$parent" && ! -L "$parent" ]] || die "$label 的父目录必须是已存在的真实目录:$parent" } validate_path_components "$SOURCE_DIR" PLUGIN_SOURCE_DIR ensure_real_parent "$SOURCE_DIR" PLUGIN_SOURCE_DIR if [[ "$SOURCE_DIR" != */sub2api-add && "${PLUGIN_ALLOW_CUSTOM_PATHS:-false}" != "true" ]]; then die "PLUGIN_SOURCE_DIR 必须以 sub2api-add 结尾;如确需自定义路径请显式设置 PLUGIN_ALLOW_CUSTOM_PATHS=true" fi [[ $EUID -eq 0 ]] || die "请使用 root 或 sudo 运行" command -v git >/dev/null 2>&1 || die "缺少 git" is_sha() { [[ "$1" =~ ^[0-9a-fA-F]{40}$ ]]; } is_release_tag() { [[ "$1" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]]; } [[ "$DEPLOY_ENV" == "development" || "$DEPLOY_ENV" == "production" ]] || die "PLUGIN_ENV 必须是 development 或 production" if [[ -n "$EXPECTED_SHA" ]] && ! is_sha "$EXPECTED_SHA"; then die "PLUGIN_COMMIT_SHA 必须是 40 位 commit SHA" fi if [[ "$ALLOW_MUTABLE_REF" == "true" && "$DEPLOY_ENV" != "development" ]]; then die "PLUGIN_ALLOW_MUTABLE_REF=true 仅允许在 PLUGIN_ENV=development 使用" fi if ! is_sha "$REF" && ! is_release_tag "$REF" && [[ "$ALLOW_MUTABLE_REF" != "true" ]]; then die "默认只接受不可变 commit SHA 或版本 tag;开发环境显式设置 PLUGIN_ALLOW_MUTABLE_REF=true 才能使用分支" fi if [[ -z "$EXPECTED_SHA" ]] && is_release_tag "$REF"; then if [[ "$DEPLOY_ENV" != "development" || "$ALLOW_UNPINNED_TAG" != "true" ]]; then die "使用版本 tag 时必须设置 PLUGIN_COMMIT_SHA;仅开发环境显式设置 PLUGIN_ALLOW_UNPINNED_TAG=true 才能跳过 pin" fi fi if [[ "$ALLOW_UNPINNED_TAG" == "true" && "$DEPLOY_ENV" != "development" ]]; then die "PLUGIN_ALLOW_UNPINNED_TAG=true 仅允许在 PLUGIN_ENV=development 使用" fi if [[ -e "$SOURCE_DIR" && ! -d "$SOURCE_DIR/.git" ]]; then die "$SOURCE_DIR 已存在但不是本插件仓库;请设置 PLUGIN_SOURCE_DIR" fi if [[ ! -e "$SOURCE_DIR" ]]; then ensure_real_parent "$SOURCE_DIR" PLUGIN_SOURCE_DIR fi if [[ -d "$SOURCE_DIR/.git" ]]; then if [[ -n "$(git -C "$SOURCE_DIR" status --porcelain)" ]]; then die "$SOURCE_DIR 有未提交修改,先清理后再升级" fi git -C "$SOURCE_DIR" fetch --depth 1 --no-tags origin "$REF" git -C "$SOURCE_DIR" checkout --force FETCH_HEAD else if is_sha "$REF"; then git clone --depth 1 --no-tags "$REPO_URL" "$SOURCE_DIR" git -C "$SOURCE_DIR" fetch --depth 1 --no-tags origin "$REF" git -C "$SOURCE_DIR" checkout --force FETCH_HEAD else git clone --depth 1 --no-tags --branch "$REF" "$REPO_URL" "$SOURCE_DIR" fi fi RESOLVED_SHA=$(git -C "$SOURCE_DIR" rev-parse HEAD) NORMALIZED_RESOLVED_SHA=$(printf '%s' "$RESOLVED_SHA" | tr '[:upper:]' '[:lower:]') NORMALIZED_EXPECTED_SHA=$(printf '%s' "$EXPECTED_SHA" | tr '[:upper:]' '[:lower:]') if [[ -n "$EXPECTED_SHA" && "$NORMALIZED_RESOLVED_SHA" != "$NORMALIZED_EXPECTED_SHA" ]]; then die "检出的 commit 与 PLUGIN_COMMIT_SHA 不一致:$RESOLVED_SHA" fi if ! is_sha "$REF" && ! is_release_tag "$REF" && [[ "$ALLOW_MUTABLE_REF" == "true" ]]; then printf '%s\n' '警告:当前使用可变 Git ref,仅限开发环境。' >&2 fi exec "$SOURCE_DIR/scripts/install-local.sh" "$@"