feat: add one-command plugin deployment and removal
This commit is contained in:
@@ -16,6 +16,20 @@ HTTP API、管理员鉴权和 `custom_menu_items` 接入 Core;插件不导入
|
||||
两个插件都是独立 Go module,可以分别构建和发布。生产环境应使用独立的
|
||||
低权限服务账号、HTTPS 反向代理、签名包和稳定的 Core API 兼容基线。
|
||||
|
||||
## 推荐部署顺序
|
||||
|
||||
先单独部署官方 Sub2API Core,再部署本仓库的插件。官方 Core 的全新 Docker
|
||||
部署命令(官方仓库和官方镜像):
|
||||
|
||||
```sh
|
||||
mkdir -p /opt/sub2api-core && cd /opt/sub2api-core
|
||||
curl -fsSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/docker-deploy.sh | bash
|
||||
docker compose -f docker-compose.local.yml up -d
|
||||
```
|
||||
|
||||
Core 和插件使用不同的目录、服务、端口及数据边界;不要把插件源码复制到
|
||||
Core 仓库,也不要让插件连接 Core PostgreSQL/Redis。
|
||||
|
||||
## 快速验证
|
||||
|
||||
```sh
|
||||
@@ -42,3 +56,22 @@ HTTP API、管理员鉴权和 `custom_menu_items` 接入 Core;插件不导入
|
||||
|
||||
Core 继续作为用户、余额、订阅、计费和用量账本的权威来源。插件浏览器端
|
||||
不持有 Core JWT、Admin Key 或其他服务密钥。
|
||||
|
||||
## 一键部署与卸载
|
||||
|
||||
Linux + systemd 环境可直接使用仓库内的安装脚本:
|
||||
|
||||
```sh
|
||||
curl -fsSL https://git.awaioi.com/awaioi/sub2api-add/raw/branch/main/deploy/install.sh \
|
||||
| sudo bash -s -- --plugin all
|
||||
```
|
||||
|
||||
默认卸载并保留配置/数据:
|
||||
|
||||
```sh
|
||||
curl -fsSL https://git.awaioi.com/awaioi/sub2api-add/raw/branch/main/deploy/uninstall.sh \
|
||||
| sudo bash -s -- --plugin all
|
||||
```
|
||||
|
||||
需要清除插件配置、数据和源码时,显式使用 `--purge --yes`。完整部署、反向
|
||||
代理、菜单应用和升级说明见 [`deploy/README.md`](deploy/README.md)。
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
# 部署与卸载
|
||||
|
||||
本仓库的插件是独立服务,不需要把源码复制进 Sub2API Core。安装脚本会构建
|
||||
插件二进制、创建低权限 systemd 服务、生成插件配置,并让服务监听各自的
|
||||
端口。Core 数据库、Redis 和 Core `data/` 目录始终由 Core 自己管理。
|
||||
|
||||
## 先部署官方 Core
|
||||
|
||||
建议先使用官方仓库的全新部署脚本,单独放在自己的目录:
|
||||
|
||||
```sh
|
||||
mkdir -p /opt/sub2api-core && cd /opt/sub2api-core
|
||||
curl -fsSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/docker-deploy.sh | bash
|
||||
docker compose -f docker-compose.local.yml up -d
|
||||
```
|
||||
|
||||
官方 Core 默认通过 `http://服务器IP:8080` 提供页面。确认 Core 登录和
|
||||
数据库初始化完成后,再安装下面的独立插件。插件不会修改 Core 数据库迁移。
|
||||
|
||||
## 一键安装
|
||||
|
||||
在一台已安装 Go 1.23+、Git 和 systemd 的 Linux 主机上:
|
||||
|
||||
```sh
|
||||
curl -fsSL https://git.awaioi.com/awaioi/sub2api-add/raw/branch/main/deploy/install.sh \
|
||||
| sudo bash -s -- --plugin all
|
||||
```
|
||||
|
||||
只安装控制面:
|
||||
|
||||
```sh
|
||||
curl -fsSL https://git.awaioi.com/awaioi/sub2api-add/raw/branch/main/deploy/install.sh \
|
||||
| sudo bash -s -- --plugin plugin-admin
|
||||
```
|
||||
|
||||
安装订阅插件:
|
||||
|
||||
```sh
|
||||
curl -fsSL https://git.awaioi.com/awaioi/sub2api-add/raw/branch/main/deploy/install.sh \
|
||||
| sudo bash -s -- --plugin subscription-admin
|
||||
```
|
||||
|
||||
默认安装位置:
|
||||
|
||||
| 内容 | 路径 |
|
||||
| --- | --- |
|
||||
| 源码 | `/opt/sub2api-add` |
|
||||
| 二进制 | `/opt/sub2api-add/<plugin>/bin/` |
|
||||
| 配置 | `/etc/sub2api-add/<plugin>.env` |
|
||||
| 插件数据 | `/var/lib/sub2api-add/<plugin>/` |
|
||||
| systemd | `sub2api-plugin-admin.service`、`sub2api-subscription-admin.service` |
|
||||
|
||||
安装后先编辑配置:
|
||||
|
||||
```sh
|
||||
sudoedit /etc/sub2api-add/plugin-admin.env
|
||||
sudoedit /etc/sub2api-add/subscription-admin.env
|
||||
sudo systemctl restart sub2api-plugin-admin sub2api-subscription-admin
|
||||
```
|
||||
|
||||
至少确认 `CORE_BASE_URL`、插件端口、反向代理路径和 HTTPS Cookie 设置。生产
|
||||
环境应启用 `PLUGIN_COOKIE_SECURE=true`,并为 plugin-admin 配置稳定的
|
||||
`PLUGIN_CONFIG_KEY` 和受信发布者公钥。
|
||||
|
||||
## 菜单接入
|
||||
|
||||
1. 让反向代理把 `plugin-admin` 和业务插件分别转发到 `127.0.0.1:8090`
|
||||
与 `127.0.0.1:8091`。
|
||||
2. 登录 plugin-admin,上传业务插件包并完成签名/哈希/兼容性校验。
|
||||
3. 配置业务插件的 loopback `service_url`。
|
||||
4. 启用、健康检查、菜单预览,然后应用菜单。
|
||||
|
||||
订阅插件是可选项;未安装或未应用菜单时,Core 管理员菜单不会出现“订阅管理”。
|
||||
|
||||
## 一键卸载
|
||||
|
||||
默认卸载服务和二进制,但保留配置与插件数据,便于重新安装:
|
||||
|
||||
```sh
|
||||
curl -fsSL https://git.awaioi.com/awaioi/sub2api-add/raw/branch/main/deploy/uninstall.sh \
|
||||
| sudo bash -s -- --plugin all
|
||||
```
|
||||
|
||||
确认同时删除插件配置、插件数据和源码:
|
||||
|
||||
```sh
|
||||
curl -fsSL https://git.awaioi.com/awaioi/sub2api-add/raw/branch/main/deploy/uninstall.sh \
|
||||
| sudo bash -s -- --plugin all --purge --yes
|
||||
```
|
||||
|
||||
卸载脚本不会删除 PostgreSQL、Redis、Sub2API Core 数据或 Core 配置。若只删除
|
||||
订阅插件,使用 `--plugin subscription-admin`;控制面和其他插件保持不变。
|
||||
|
||||
## 本地安装与升级
|
||||
|
||||
从已克隆的仓库执行:
|
||||
|
||||
```sh
|
||||
sudo ./scripts/install-local.sh --plugin all
|
||||
sudo ./deploy/uninstall.sh --plugin subscription-admin
|
||||
```
|
||||
|
||||
再次运行安装脚本会重新构建并重启选定插件。安装脚本发现源码有未提交修改
|
||||
时会中止,避免升级覆盖本地改动。
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/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:-main}
|
||||
|
||||
die() { printf '错误:%s\n' "$*" >&2; exit 1; }
|
||||
[[ $EUID -eq 0 ]] || die "请使用 root 或 sudo 运行"
|
||||
command -v git >/dev/null 2>&1 || die "缺少 git"
|
||||
|
||||
if [[ -e "$SOURCE_DIR" && ! -d "$SOURCE_DIR/.git" ]]; then
|
||||
die "$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 origin "$REF"
|
||||
git -C "$SOURCE_DIR" checkout --force FETCH_HEAD
|
||||
else
|
||||
git clone --depth 1 --branch "$REF" "$REPO_URL" "$SOURCE_DIR"
|
||||
fi
|
||||
|
||||
exec "$SOURCE_DIR/scripts/install-local.sh" "$@"
|
||||
Executable
+59
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
ETC_DIR=${PLUGIN_ETC_DIR:-/etc/sub2api-add}
|
||||
VAR_DIR=${PLUGIN_VAR_DIR:-/var/lib/sub2api-add}
|
||||
PREFIX=${PLUGIN_INSTALL_PREFIX:-/opt/sub2api-add}
|
||||
SOURCE_DIR=${PLUGIN_SOURCE_DIR:-/opt/sub2api-add}
|
||||
RUN_USER=${PLUGIN_SYSTEM_USER:-sub2api-plugin}
|
||||
SELECTION=all
|
||||
PURGE=false
|
||||
YES=false
|
||||
|
||||
die() { printf '错误:%s\n' "$*" >&2; exit 1; }
|
||||
[[ $EUID -eq 0 ]] || die "请使用 root 或 sudo 运行"
|
||||
|
||||
while (($#)); do
|
||||
case "$1" in
|
||||
--plugin) (($# >= 2)) || die "--plugin 需要参数"; SELECTION=$2; shift 2 ;;
|
||||
--purge) PURGE=true; shift ;;
|
||||
--yes) YES=true; shift ;;
|
||||
-h|--help) printf '%s\n' '用法:deploy/uninstall.sh [--plugin all|plugin-admin|subscription-admin] [--purge --yes]'; exit 0 ;;
|
||||
*) die "未知参数:$1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
case "$SELECTION" in
|
||||
all) PLUGINS=(plugin-admin subscription-admin) ;;
|
||||
plugin-admin|subscription-admin) PLUGINS=("$SELECTION") ;;
|
||||
*) die "插件必须是 all、plugin-admin 或 subscription-admin" ;;
|
||||
esac
|
||||
|
||||
if $PURGE && ! $YES; then
|
||||
[[ -t 0 ]] || die "非交互环境执行 --purge 时必须同时提供 --yes"
|
||||
read -r -p "将删除插件配置和数据,继续?[y/N] " answer
|
||||
[[ "$answer" == [yY] ]] || { printf '%s\n' '已取消'; exit 0; }
|
||||
fi
|
||||
|
||||
for plugin in "${PLUGINS[@]}"; do
|
||||
unit="sub2api-$plugin.service"
|
||||
systemctl disable --now "$unit" 2>/dev/null || true
|
||||
rm -f "/etc/systemd/system/$unit"
|
||||
rm -rf "$PREFIX/$plugin"
|
||||
if $PURGE; then
|
||||
rm -f "$ETC_DIR/$plugin.env"
|
||||
rm -rf "$VAR_DIR/$plugin"
|
||||
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 && [[ -d "$SOURCE_DIR" ]]; then rm -rf "$SOURCE_DIR"; fi
|
||||
|
||||
if $PURGE; then
|
||||
userdel "$RUN_USER" 2>/dev/null || true
|
||||
printf '%s\n' '插件、配置、数据和源码已删除;Core 数据库未被触碰。'
|
||||
else
|
||||
printf '%s\n' "插件服务已卸载;配置和数据仍保留在 $ETC_DIR 与 $VAR_DIR。"
|
||||
fi
|
||||
Executable
+140
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
ROOT=$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
PREFIX=${PLUGIN_INSTALL_PREFIX:-/opt/sub2api-add}
|
||||
ETC_DIR=${PLUGIN_ETC_DIR:-/etc/sub2api-add}
|
||||
VAR_DIR=${PLUGIN_VAR_DIR:-/var/lib/sub2api-add}
|
||||
RUN_USER=${PLUGIN_SYSTEM_USER:-sub2api-plugin}
|
||||
SELECTION=all
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
用法:scripts/install-local.sh [--plugin all|plugin-admin|subscription-admin]
|
||||
|
||||
环境变量:
|
||||
PLUGIN_INSTALL_PREFIX 二进制目录,默认 /opt/sub2api-add
|
||||
PLUGIN_ETC_DIR 环境文件目录,默认 /etc/sub2api-add
|
||||
PLUGIN_VAR_DIR 插件数据目录,默认 /var/lib/sub2api-add
|
||||
PLUGIN_SYSTEM_USER systemd 用户,默认 sub2api-plugin
|
||||
EOF
|
||||
}
|
||||
|
||||
die() { printf '错误:%s\n' "$*" >&2; exit 1; }
|
||||
info() { printf '[sub2api-add] %s\n' "$*"; }
|
||||
|
||||
while (($#)); do
|
||||
case "$1" in
|
||||
--plugin)
|
||||
(($# >= 2)) || die "--plugin 需要参数"
|
||||
SELECTION=$2
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*) die "未知参数:$1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ $EUID -eq 0 ]] || die "请使用 root 或 sudo 运行"
|
||||
command -v go >/dev/null 2>&1 || die "缺少 Go;请安装 Go 1.23 或更高版本"
|
||||
command -v systemctl >/dev/null 2>&1 || die "缺少 systemd/systemctl"
|
||||
command -v install >/dev/null 2>&1 || die "缺少 install 命令"
|
||||
|
||||
case "$SELECTION" in
|
||||
all) PLUGINS=(plugin-admin subscription-admin) ;;
|
||||
plugin-admin|subscription-admin) PLUGINS=("$SELECTION") ;;
|
||||
*) die "插件必须是 all、plugin-admin 或 subscription-admin" ;;
|
||||
esac
|
||||
|
||||
if ! id -u "$RUN_USER" >/dev/null 2>&1; then
|
||||
useradd --system --user-group --home-dir "$VAR_DIR" --create-home --shell /usr/sbin/nologin "$RUN_USER"
|
||||
fi
|
||||
|
||||
ensure_env_value() {
|
||||
local file=$1 key=$2 value=$3
|
||||
if grep -q "^${key}=" "$file"; then
|
||||
if command sed --version >/dev/null 2>&1; then
|
||||
sed -i "s#^${key}=.*#${key}=${value}#" "$file"
|
||||
else
|
||||
sed -i '' "s#^${key}=.*#${key}=${value}#" "$file"
|
||||
fi
|
||||
else
|
||||
printf '%s=%s\n' "$key" "$value" >> "$file"
|
||||
fi
|
||||
}
|
||||
|
||||
random_secret() {
|
||||
if command -v openssl >/dev/null 2>&1; then
|
||||
openssl rand -hex 32
|
||||
else
|
||||
od -An -N32 -tx1 /dev/urandom | tr -d ' \n'
|
||||
fi
|
||||
}
|
||||
|
||||
install_one() {
|
||||
local name=$1 source="$ROOT/plugins/$1" env_file="$ETC_DIR/$1.env"
|
||||
local binary_dir="$PREFIX/$1/bin" data_dir="$VAR_DIR/$1"
|
||||
[[ -d "$source" ]] || die "插件目录不存在:$source"
|
||||
install -d -m 0755 "$binary_dir" "$data_dir" "$ETC_DIR"
|
||||
if [[ ! -f "$env_file" ]]; then
|
||||
install -m 0600 "$source/.env.example" "$env_file"
|
||||
fi
|
||||
|
||||
if [[ "$name" == plugin-admin ]]; then
|
||||
ensure_env_value "$env_file" PLUGIN_REGISTRY_DIR "$data_dir"
|
||||
if grep -q '^PLUGIN_CONFIG_KEY=generate-and-replace-with-a-random-32-byte-secret$' "$env_file" ||
|
||||
! grep -q '^PLUGIN_CONFIG_KEY=.' "$env_file"; then
|
||||
ensure_env_value "$env_file" PLUGIN_CONFIG_KEY "$(random_secret)"
|
||||
fi
|
||||
fi
|
||||
|
||||
local tmp="$binary_dir/.${name}.tmp"
|
||||
info "构建 $name"
|
||||
(cd "$source" && CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o "$tmp" .)
|
||||
chmod 0755 "$tmp"
|
||||
mv -f "$tmp" "$binary_dir/$name"
|
||||
chown -R "$RUN_USER:$RUN_USER" "$data_dir"
|
||||
chown "$RUN_USER:$RUN_USER" "$binary_dir/$name"
|
||||
chmod 0600 "$env_file"
|
||||
|
||||
cat > "/etc/systemd/system/sub2api-$name.service" <<EOF
|
||||
[Unit]
|
||||
Description=Sub2API ${name} business plugin
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=${RUN_USER}
|
||||
Group=${RUN_USER}
|
||||
WorkingDirectory=${data_dir}
|
||||
EnvironmentFile=${env_file}
|
||||
ExecStart=${binary_dir}/${name}
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ReadWritePaths=${data_dir}
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
}
|
||||
|
||||
for plugin in "${PLUGINS[@]}"; do
|
||||
install_one "$plugin"
|
||||
done
|
||||
|
||||
systemctl daemon-reload
|
||||
for plugin in "${PLUGINS[@]}"; do
|
||||
systemctl enable --now "sub2api-$plugin.service"
|
||||
info "$plugin 已启动:$(systemctl is-active "sub2api-$plugin.service")"
|
||||
done
|
||||
|
||||
info "安装完成。配置文件位于 $ETC_DIR;数据位于 $VAR_DIR。"
|
||||
info "查看日志:journalctl -u sub2api-plugin-admin -u sub2api-subscription-admin -f"
|
||||
Reference in New Issue
Block a user