release: harden plugin deployment and recovery
Business Plugins CI / check (plugin-admin) (push) Successful in 1m37s
Business Plugins CI / check (subscription-admin) (push) Successful in 1m31s

This commit is contained in:
Qiufeng
2026-08-30 13:14:22 +08:00
parent ada4ab3c21
commit d3ff9be315
16 changed files with 259 additions and 50 deletions
+47 -5
View File
@@ -22,12 +22,40 @@ validate_managed_root() {
[[ "$component" != "." && "$component" != ".." ]] || die "$label 不能包含 . 或 .. 路径组件"
current="$current/$component"
[[ ! -L "$current" ]] || die "$label 的路径组件不能是符号链接:$current"
[[ ! -e "$current" || -d "$current" ]] || die "$label 的路径组件不是目录:$current"
done
if [[ "$value" != */sub2api-add && "${PLUGIN_ALLOW_CUSTOM_PATHS:-false}" != "true" ]]; then
die "$label 必须位于受管的 sub2api-add 目录;如确需自定义路径请显式设置 PLUGIN_ALLOW_CUSTOM_PATHS=true"
fi
}
ensure_real_parent() {
local path=$1 label=$2 parent component current="" parts=()
parent=$(dirname -- "$path")
IFS='/' read -r -a parts <<< "${parent#/}"
for component in "${parts[@]}"; do
[[ -z "$component" ]] && continue
current="$current/$component"
[[ -d "$current" && ! -L "$current" ]] || die "$label 的父目录必须是已存在的真实目录:$current"
done
}
ensure_real_dir() {
local path=$1 label=$2 component current="" parts=()
[[ -n "$path" && "$path" = /* ]] || die "$label 路径无效"
IFS='/' read -r -a parts <<< "${path#/}"
for component in "${parts[@]}"; do
[[ -z "$component" ]] && continue
current="$current/$component"
[[ -d "$current" && ! -L "$current" ]] || die "$label 必须是已存在的真实目录:$current"
done
}
ensure_regular_target() {
local path=$1 label=$2
[[ ! -L "$path" ]] || die "$label 不能是符号链接:$path"
}
die() { printf '错误:%s\n' "$*" >&2; exit 1; }
[[ $EUID -eq 0 ]] || die "请使用 root 或 sudo 运行"
command -v systemctl >/dev/null 2>&1 || die "缺少 systemd/systemctl"
@@ -52,6 +80,11 @@ validate_managed_root "$ETC_DIR" PLUGIN_ETC_DIR
validate_managed_root "$VAR_DIR" PLUGIN_VAR_DIR
validate_managed_root "$PREFIX" PLUGIN_INSTALL_PREFIX
validate_managed_root "$SOURCE_DIR" PLUGIN_SOURCE_DIR
ensure_real_parent "$ETC_DIR" PLUGIN_ETC_DIR
ensure_real_parent "$VAR_DIR" PLUGIN_VAR_DIR
ensure_real_parent "$PREFIX" PLUGIN_INSTALL_PREFIX
ensure_real_parent "$SOURCE_DIR" PLUGIN_SOURCE_DIR
ensure_real_dir /etc/systemd/system SYSTEMD_UNIT_DIR
if $PURGE && [[ "${PLUGIN_PURGE_SOURCE:-true}" == "true" && "$SOURCE_DIR" != "$PREFIX" && "${PLUGIN_ALLOW_CUSTOM_PATHS:-false}" != "true" ]]; then
die "为避免误删,--purge 默认要求 PLUGIN_SOURCE_DIR 与 PLUGIN_INSTALL_PREFIX 相同"
fi
@@ -64,28 +97,37 @@ fi
for plugin in "${PLUGINS[@]}"; do
unit="sub2api-$plugin.service"
unit_path="/etc/systemd/system/$unit"
if [[ -e "$unit_path" || -L "$unit_path" ]]; then
ensure_regular_target "$unit_path" SYSTEMD_UNIT
fi
systemctl disable --now "$unit" 2>/dev/null || true
rm -f -- "/etc/systemd/system/$unit"
rm -f -- "$unit_path"
# Keep the checked-out source clean so a later install can fast-forward it.
# Only generated binaries are removed unless --purge is explicitly used.
rm -f -- "$PREFIX/$plugin/bin/$plugin"
if [[ -e "$PREFIX/$plugin/bin/$plugin" || -L "$PREFIX/$plugin/bin/$plugin" ]]; then
ensure_real_parent "$PREFIX/$plugin/bin/$plugin" PLUGIN_INSTALL_PREFIX
rm -f -- "$PREFIX/$plugin/bin/$plugin"
fi
if $PURGE; then
plugin_root="$PREFIX/$plugin"
if [[ -d "$plugin_root" && ! -L "$plugin_root" ]]; then
ensure_real_parent "$plugin_root" PLUGIN_INSTALL_PREFIX
rm -rf -- "${plugin_root:?}"
fi
rm -f -- "$ETC_DIR/$plugin.env"
plugin_data="$VAR_DIR/$plugin"
if [[ -d "$plugin_data" && ! -L "$plugin_data" ]]; then
ensure_real_parent "$plugin_data" PLUGIN_VAR_DIR
rm -rf -- "${plugin_data:?}"
fi
fi
done
systemctl daemon-reload
if $PURGE && [[ -d "$ETC_DIR" ]]; then rmdir "$ETC_DIR" 2>/dev/null || true; fi
if $PURGE && [[ -d "$VAR_DIR" ]]; then rmdir "$VAR_DIR" 2>/dev/null || true; fi
if $PURGE && [[ "${PLUGIN_PURGE_SOURCE:-true}" == "true" && "$SOURCE_DIR" == "$PREFIX" && -d "$SOURCE_DIR" && ! -L "$SOURCE_DIR" ]]; then rm -rf -- "$SOURCE_DIR"; fi
if $PURGE && [[ -d "$ETC_DIR" && ! -L "$ETC_DIR" ]]; then rmdir "$ETC_DIR" 2>/dev/null || true; fi
if $PURGE && [[ -d "$VAR_DIR" && ! -L "$VAR_DIR" ]]; then rmdir "$VAR_DIR" 2>/dev/null || true; fi
if $PURGE && [[ "${PLUGIN_PURGE_SOURCE:-true}" == "true" && "$SOURCE_DIR" == "$PREFIX" && -d "$SOURCE_DIR" && ! -L "$SOURCE_DIR" ]]; then ensure_real_parent "$SOURCE_DIR" PLUGIN_SOURCE_DIR; rm -rf -- "$SOURCE_DIR"; fi
if $PURGE; then
userdel "$RUN_USER" 2>/dev/null || true