#!/bin/bash
#===============================================================================
# 从自持镜像安装宝塔/BTweb 插件（专业版应用 / 企业版应用 / 收费插件）
#
# 用法：
#   curl -sSL https://bt.kajiejie.com/tools/install_plugin.sh | bash -s -- --list
#   curl -sSL https://bt.kajiejie.com/tools/install_plugin.sh | bash -s -- btwaf bt_security
#   curl -sSL https://bt.kajiejie.com/tools/install_plugin.sh | bash -s -- --pro
#   curl -sSL https://bt.kajiejie.com/tools/install_plugin.sh | bash -s -- --ent
#
# 也可先落地再跑：
#   curl -sSLO https://bt.kajiejie.com/tools/install_plugin.sh
#   bash install_plugin.sh --pro
#
# 特点：
#   * 全部包来自自持镜像，不依赖厂商 CDN
#   * 安装前备份旧目录，install.sh 失败自动回滚
#   * 会清除遮蔽新版 .py 的旧 .so / __pycache__
#===============================================================================
MIRROR="${MIRROR:-https://bt.kajiejie.com}"
PKG_BASE="$MIRROR/install/plugin-pkg"
PANEL_DIR="${PANEL_DIR:-/www/server/panel}"
PLUG_DIR="$PANEL_DIR/plugin"
PYENV_BIN="$PANEL_DIR/pyenv/bin"
BAK_DIR="${BAK_DIR:-/root/plugin-backup}"
TMPD="$(mktemp -d /tmp/.btplugin.XXXXXX)"

RED=$'\033[31m'; GRN=$'\033[32m'; YEL=$'\033[33m'; NC=$'\033[0m'

cleanup() { rm -rf "$TMPD"; }
trap cleanup EXIT

die() { echo "${RED}$*${NC}" >&2; exit 1; }

[ "$(id -u)" = "0" ] || die "需要 root 权限"
[ -d "$PLUG_DIR" ] || die "找不到 $PLUG_DIR —— 这台机器上装了宝塔/BTweb 面板吗？"

# 依赖检查
for c in curl unzip python3; do
    command -v "$c" >/dev/null 2>&1 || die "缺少命令: $c"
done

echo "正在获取插件索引 $PKG_BASE/index.json ..."
if ! curl -sS --max-time 60 -o "$TMPD/index.json" "$PKG_BASE/index.json"; then
    die "索引下载失败（镜像不可达？）"
fi
[ -s "$TMPD/index.json" ] || die "索引为空"

# ---------- 参数处理 ----------
if [ $# -eq 0 ]; then
    cat <<EOF
用法:
  bash $0 --list                 列出镜像里可装的插件
  bash $0 <插件名> [插件名...]     安装指定插件
  bash $0 --pro                  安装全部「专业版应用」
  bash $0 --ent                  安装全部「企业版应用」
  bash $0 --all                  安装以上全部
环境变量:
  MIRROR=$MIRROR   (可覆盖镜像地址)
EOF
    exit 1
fi

NAMES=()
DRY=0
for a in "$@"; do
    case "$a" in
        --list)
            python3 - "$TMPD/index.json" <<'PY'
import json, sys
d = json.load(open(sys.argv[1]))
p = d['plugins']
CATE = {'8': '专业版', '12': '企业版', '7': '免费', '10': '第三方', '13': 'Docker', '5': '运行环境', '6': '安全'}
print('镜像索引更新时间: %s   共 %d 个插件' % (d.get('updated'), d.get('count')))
print()
for k in sorted(p, key=lambda x: (str(p[x].get('type')), x)):
    v = p[k]
    print('  %-24s %-10s v%-10s ¥%-8s %8.2f MB' % (
        k, CATE.get(str(v.get('type')), '其他'), v.get('version', ''),
        v.get('price') or 0, (v.get('size') or 0) / 1048576))
PY
            exit 0
            ;;
        --pro)  NAMES+=( $(python3 -c "
import json;d=json.load(open('$TMPD/index.json'))['plugins']
print(' '.join(k for k,v in d.items() if str(v.get('type'))=='8'))") ) ;;
        --dry-run) DRY=1 ;;
        --ent)  NAMES+=( $(python3 -c "
import json;d=json.load(open('$TMPD/index.json'))['plugins']
print(' '.join(k for k,v in d.items() if str(v.get('type'))=='12'))") ) ;;
        --all)  NAMES+=( $(python3 -c "
import json;d=json.load(open('$TMPD/index.json'))['plugins']
print(' '.join(k for k,v in d.items() if str(v.get('type')) in ('8','12')))") ) ;;
        -*)     die "未知参数: $a" ;;
        *)      NAMES+=("$a") ;;
    esac
done

[ ${#NAMES[@]} -gt 0 ] || die "没有要安装的插件"

ok=0; bad=0; skip=0
FAILED=()

for NAME in "${NAMES[@]}"; do
    echo
    echo "──────────────────────────────────────────────────────────"
    INFO=$(python3 -c "
import json,sys
d=json.load(open('$TMPD/index.json'))['plugins']
v=d.get('$NAME')
if not v: sys.exit(1)
print('%s|%s|%s' % (v['version'], v['file'], v.get('title') or ''))
" 2>/dev/null) || { echo "${YEL}跳过 $NAME：镜像索引里没有${NC}"; skip=$((skip+1)); continue; }
    VER="${INFO%%|*}"; REST="${INFO#*|}"; FILE="${REST%%|*}"; TITLE="${REST#*|}"

    echo "插件: $NAME  ($TITLE)  版本: $VER"
    ZIP="$TMPD/$NAME-$VER.zip"
    if ! curl -sS --max-time 900 -o "$ZIP" "$MIRROR/$FILE"; then
        echo "${RED}✗ 下载失败: $FILE${NC}"; bad=$((bad+1)); FAILED+=("$NAME"); continue
    fi
    if ! unzip -t "$ZIP" >/dev/null 2>&1; then
        echo "${RED}✗ 包损坏${NC}"; bad=$((bad+1)); FAILED+=("$NAME"); continue
    fi
    echo "  已下载 $(du -h "$ZIP" | cut -f1)"

    # 备份
    B=""
    if [ -d "$PLUG_DIR/$NAME" ]; then
        mkdir -p "$BAK_DIR"
        B="$BAK_DIR/$NAME.$(date +%Y%m%d-%H%M%S)"
        cp -a "$PLUG_DIR/$NAME" "$B" 2>/dev/null && echo "  已备份旧版 → $B"
    fi

    # 清除会遮蔽 .py 的旧编译产物
    if [ -d "$PLUG_DIR/$NAME" ]; then
        n_so=$(ls -1 "$PLUG_DIR/$NAME"/*.so 2>/dev/null | wc -l)
        [ "$n_so" != "0" ] && { rm -f "$PLUG_DIR/$NAME"/*.so; echo "  已清除 $n_so 个旧 .so"; }
        rm -rf "$PLUG_DIR/$NAME/__pycache__"
    fi

    # 解压前先判断包结构：
    #   多数包是「<插件名>/...」结构（直接解到 plugin/ 即可）
    #   少数第三方包是「平铺」结构（info.json / install.sh 在 zip 根，如 mfftpdev），
    #   这类必须解到 plugin/<插件名>/，否则文件会散落到 plugin/ 根目录
    if unzip -Z1 "$ZIP" 2>/dev/null | awk -v p="$NAME/" 'index($0,p)==1{f=1;exit} END{exit !f}'; then
        UNZIP_DEST="$PLUG_DIR/"
    else
        echo "  包为平铺结构，解压到 plugin/$NAME/"
        mkdir -p "$PLUG_DIR/$NAME"
        UNZIP_DEST="$PLUG_DIR/$NAME/"
    fi

    if ! unzip -oq "$ZIP" -d "$UNZIP_DEST"; then
        echo "${RED}✗ 解压失败${NC}"
        [ -n "$B" ] && { rm -rf "$PLUG_DIR/$NAME"; cp -a "$B" "$PLUG_DIR/$NAME"; echo "  已回滚"; }
        bad=$((bad+1)); FAILED+=("$NAME"); continue
    fi

    SH="$PLUG_DIR/$NAME/install.sh"
    if [ "$DRY" = "1" ]; then
        echo "  ${YEL}--dry-run：已解压到 $PLUG_DIR/$NAME，跳过 install.sh${NC}"
        ok=$((ok+1)); continue
    fi
    if [ ! -f "$SH" ]; then
        echo "${GRN}✓ $NAME 已解压（无 install.sh）${NC}"; ok=$((ok+1)); continue
    fi

    # 复现面板的 set_pyenv：把 pyenv/bin 前置到 PATH
    [ -d "$PYENV_BIN" ] && sed -i "s|^PATH=/bin:/sbin|PATH=${PYENV_BIN}:/bin:/sbin|" "$SH"
    chmod +x "$SH" 2>/dev/null
    # 让插件内部 wget 走自持镜像（镜像里已备好它们引用的 /install/src、/src 等依赖）
    export download_Url="$MIRROR"
    export NODE_URL="$MIRROR"

    if ( cd "$PLUG_DIR/$NAME" && bash install.sh install ); then
        echo "${GRN}✓ $NAME v$VER 安装完成${NC}"; ok=$((ok+1))
    else
        rc=$?
        echo "${RED}✗ $NAME 的 install.sh 失败（退出码 $rc）${NC}"
        if [ -n "$B" ]; then
            rm -rf "$PLUG_DIR/$NAME"; cp -a "$B" "$PLUG_DIR/$NAME"
            echo "  已回滚到旧版"
        fi
        bad=$((bad+1)); FAILED+=("$NAME")
    fi
done

echo
echo "══════════════════════════════════════════════════════════"
echo "  成功 $ok   失败 $bad   跳过 $skip"
[ ${#FAILED[@]} -gt 0 ] && echo "  失败清单: ${FAILED[*]}"
echo "  备份目录: $BAK_DIR"
echo "══════════════════════════════════════════════════════════"
[ "$bad" -eq 0 ]
