#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
LANG=en_US.UTF-8

Btapi_Url='https://bt.kajiejie.com'
UPGRADE_WARN=0          # 自检/预准备出现异常时置 1，用于最后的完成横幅


#==============================================================
# 预检模式（只读，不改动任何东西）
#   curl -sSL https://bt.kajiejie.com/install/update6.sh | bash -s -- --check
# 用途：在真正升级前，先确认这台机器能不能跑这个升级。
#
# 降级保护：若镜像上的目标版本低于本机当前面板版本，脚本会中止并 exit 1
#   （避免把新版面板静默降级成镜像里的旧版）。确需降级时显式放行：
#        curl -sSL <镜像>/install/update6.sh | bash -s -- --force
#   也可以显式指定目标版本号（视为明确意图）：
#        curl -sSL <镜像>/install/update6.sh | bash -s -- 13.0.0
#==============================================================
if [ "${1}" = "--check" ]; then
	CHK_FAIL=0
	ok()   { echo -e "  \033[32m✓\033[0m $*"; }
	bad()  { echo -e "  \033[31m✗\033[0m $*"; CHK_FAIL=$((CHK_FAIL+1)); }
	warn() { echo -e "  \033[33m!\033[0m $*"; }

	echo "=============================================================="
	echo " 升级前预检（只读，不会改动任何东西）"
	echo " 镜像: 自持云端"
	echo "=============================================================="
	echo
	echo "【1】本机环境"
	ARCH=$(uname -m)
	echo "     架构: ${ARCH}"
	case "${ARCH}" in
		arm*|aarch64)
			bad "ARM 架构：脚本在 ARM 上会强制请求 LinuxPanel-7.7.0.zip，本镜像只托管 x86_64 版本 → 升级会失败"
			;;
		x86_64|amd64)
			ok "x86_64，本镜像支持"
			;;
		*)
			warn "未知架构 ${ARCH}，请自行确认"
			;;
	esac

	# 权限：升级要写 /www/server/panel、/etc/init.d/bt，必须是 root
	if [ "$(id -u 2>/dev/null)" = "0" ]; then
		ok "以 root 运行"
		for d in /www/server /www/server/panel /tmp; do
			if [ -d "$d" ]; then
				if [ -w "$d" ]; then ok "$d 可写"; else bad "$d 不可写（只读挂载？升级会失败）"; fi
			else
				warn "$d 不存在"
			fi
		done
	else
		bad "当前不是 root（uid=$(id -u 2>/dev/null)）：升级要写 /www/server/panel 和 /etc/init.d/bt，必须用 root 或 sudo 执行"
	fi

	# 位数：安装/升级脚本都只支持 64 位
	if [ "$(getconf LONG_BIT 2>/dev/null)" != "64" ]; then
		bad "系统是 32 位：脚本会直接拒绝（需要 64 位系统）"
	else
		ok "64 位系统"
	fi

	# 系统类型：与 install.sh 里那条「拒绝名单」保持一致
	CHK_ID=""; CHK_OSV=""; CHK_OS_DESC="未知"; CHK_OS_REJECT=""
	if [ -f /etc/os-release ]; then
		. /etc/os-release
		CHK_ID="${ID}"; CHK_OSV="${VERSION_ID%%.*}"
		CHK_OS_DESC="${PRETTY_NAME:-${ID} ${CHK_OSV}}"
		case "${CHK_ID}" in
			centos|rhel|redhat)
				[ "${CHK_OSV}" = "6" ] && CHK_OS_REJECT="CentOS/RedHat 6 已停止支持（建议 7/8/9）" ;;
			ubuntu)
				if [ "${CHK_OSV}" -lt 16 ] 2>/dev/null; then
					CHK_OS_REJECT="Ubuntu ${CHK_OSV} 过旧（建议 22/24）"
				elif [ "${CHK_OSV}" = 19 ] || [ "${CHK_OSV}" = 21 ] || [ "${CHK_OSV}" = 23 ] || [ "${CHK_OSV}" = 25 ]; then
					CHK_OS_REJECT="Ubuntu ${CHK_OSV} 是非 LTS 版本，脚本会拒绝"
				fi ;;
			debian)
				case "${CHK_OSV}" in
					7|8|9) CHK_OS_REJECT="Debian ${CHK_OSV} 已停止支持（建议 11/12/13）" ;;
				esac ;;
		esac
	fi
	echo "     系统: ${CHK_OS_DESC}"
	if [ -n "${CHK_OS_REJECT}" ]; then
		bad "系统不受支持：${CHK_OS_REJECT}"
	else
		case "${CHK_ID}" in
			debian|ubuntu|centos|opencloudos|tencentos|hce|almalinux|rocky|alinux|anolis)
				ok "系统在支持列表内（${CHK_ID} ${CHK_OSV}）" ;;
			"")
				warn "读不到 /etc/os-release，无法判断系统类型" ;;
			*)
				warn "系统 ${CHK_ID} ${CHK_OSV} 不在已知支持列表，通常可用但未验证" ;;
		esac
	fi
	if [ "$(id -u 2>/dev/null)" = "0" ]; then
		[ -d /www/server/panel/BTPanel ] && ok "面板目录 /www/server/panel/BTPanel 存在" || bad "找不到 /www/server/panel/BTPanel"
		[ -x /www/server/panel/pyenv/bin/python3 ] && ok "pyenv 环境可用" || bad "缺 pyenv（无法升级到新版）"
		CURV=$(grep -m1 -oE "g\.version *= *['\"][0-9.]+" /www/server/panel/class/common.py 2>/dev/null | grep -oE "[0-9.]+$")
		echo "     当前面板版本: ${CURV:-未知}"
	else
		# /www/server/panel/pyenv 是 drw------- (700 root)，非 root 读不到，会误判成"缺 pyenv"
		warn "面板目录 / pyenv / 当前版本：需要 root 才能确认（当前非 root，已跳过）"
	fi
	for c in curl wget unzip du awk; do
		command -v "$c" >/dev/null 2>&1 && ok "命令 $c 可用" || bad "缺少命令 $c"
	done
	FREE=$(df -Pk /www 2>/dev/null | awk 'NR==2{print $4}')
	if [ -n "${FREE}" ]; then
		if [ "${FREE}" -gt 512000 ]; then ok "根分区可用空间 $((FREE/1024)) MB"
		else bad "可用空间仅 $((FREE/1024)) MB，建议 > 500MB"; fi
	fi
	echo
	echo "【2】镜像连通性"
	if curl -sS --connect-timeout 8 -m 20 -o /dev/null "${Btapi_Url}/api/panel/get_version" 2>/dev/null; then
		ok "云端可达"
	else
		bad "云端不可达（脚本会直接中止）"
	fi
	V=$(curl -Ss --connect-timeout 8 -m 20 "${Btapi_Url}/api/panel/get_version" 2>/dev/null)
	if [ -n "${V}" ]; then ok "镜像报告的目标版本: ${V}"; else bad "取不到目标版本号"; fi
	[ -z "${V}" ] && V='13.0.0'
	echo
	echo "【3】升级包与校验信息"
	ZIP="${Btapi_Url}/install/update/LinuxPanel-${V}.zip"
	ZSZ=$(curl -sSI --max-time 30 "${ZIP}" 2>/dev/null | awk 'tolower($1)=="content-length:"{print $2}' | tr -d '\r')
	ZCD=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 30 -I "${ZIP}" 2>/dev/null)
	if [ "${ZCD}" = "200" ] && [ -n "${ZSZ}" ] && [ "${ZSZ}" -gt 10240 ]; then
		ok "LinuxPanel-${V}.zip 可取（$((ZSZ/1048576)) MB）"
	elif [ "${ZCD}" = "200" ]; then
		bad "升级包过小（${ZSZ:-0} 字节），脚本会判定失败"
	else
		bad "升级包不可取（HTTP ${ZCD}）→ 脚本会报「获取更新包失败」"
	fi
	PL="${Btapi_Url}/install/update/LinuxPanel-${V}.pl"
	PLBODY=$(curl -sS --max-time 30 -o /tmp/.bt_chk_pl.$$ -w '%{http_code}' "${PL}" 2>/dev/null)
	if [ "${PLBODY}" = "200" ] && [ -s /tmp/.bt_chk_pl.$$ ]; then
		PLHASH=$(/www/server/panel/pyenv/bin/python3 -c "import json,sys;print(json.load(open(sys.argv[1])).get('hash',''))" /tmp/.bt_chk_pl.$$ 2>/dev/null)
		if [ -n "${PLHASH}" ]; then ok "校验信息 .pl 可取（hash ${PLHASH:0:16}…）"
		else warn ".pl 可取但解析不出 hash 字段（面板 UI 的升级会跳过校验）"; fi
	else
		warn "校验信息 .pl 不可取（HTTP ${PLBODY}）—— 命令行升级不依赖它，面板 UI 升级会放弃校验"
	fi
	rm -f /tmp/.bt_chk_pl.$$
	for f in "/install/update/update_prep_script.sh" "/tools/bt_selfhost_patch.sh" "/tools/check_btpanel.sh"; do
		c=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 25 "${Btapi_Url}${f}" 2>/dev/null)
		[ "${c}" = "200" ] && ok "${f}" || warn "${f} 不可取（HTTP ${c}）—— 对应步骤会被跳过，不影响升级"
	done
	echo
	echo "【4】辅助依赖（走 bt.cn，条件触发、失败不致命）"
	for f in "/install/src/bt7.init" "/install/pyenv/pip-3.7.16.txt" "/site_total/install.sh"; do
		c=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 20 -I "https://download.bt.cn${f}" 2>/dev/null)
		[ "${c}" = "200" ] && ok "download.bt.cn${f}" || warn "download.bt.cn${f} 不可取（${c}）"
	done
	echo
	echo "=============================================================="
	if [ "${CHK_FAIL}" = "0" ]; then
		echo -e " \033[32m预检通过：这台机器可以升级。\033[0m"
		echo " 正式升级： 重新执行本升级命令（去掉末尾的 --check）"
		echo
		echo " 注意：升级会把面板目录整体替换为镜像里的 ${V} 版本；"
		echo "       若本机当前是官方宝塔，升级后会变成镜像所带的版本。"
	else
		echo -e " \033[31m预检发现 ${CHK_FAIL} 个问题，不建议直接升级。\033[0m"
		echo " 先解决上面标 ✗ 的项；标 ! 的只是提示，通常不影响。"
	fi
	echo "=============================================================="
	exit 0
fi

#==============================================================
# 权限检查（快速失败）
# update6.sh 要写 /www/server/panel（unzip -o 覆盖）、/etc/init.d/bt，
# 非 root 会在中途失败并可能留下半个面板，所以这里先拦住。
# 官方原始脚本没有这一步，是本镜像补的。
#==============================================================
if [ "$(id -u 2>/dev/null)" != "0" ]; then
	echo "============================================="
	echo "错误: 请使用 root 权限执行升级命令（当前 uid=$(id -u 2>/dev/null)）"
	echo "例如: 用 sudo 重新执行本升级命令"
	echo "     或先 sudo -i 再执行"
	echo "============================================="
	exit 1
fi

if [ -d "/www/server" ] && [ ! -w "/www/server" ]; then
	echo "============================================="
	echo "错误: /www/server 不可写（是否只读挂载？）无法升级"
	echo "============================================="
	exit 1
fi

#==============================================================
# 版本迁移预处理脚本（与「面板 UI 升级」那条路径等价）
#
# 官方 UI 升级会先跑 panel/script/update_prep_script.sh，它会：
#   * 补装新版面板需要的 Python 包（asn1crypto/cbor2/numpy/openai/pydantic）
#     —— 官方面板的 pyenv 里没有这几个，而下面那段「修复面板依赖」只在
#        环境过旧（Flask<=2 且包数<=118）时才触发，官方面板通常不满足 → 会漏装
#   * 执行各版本的一次性迁移（如清理旧技能数据）
#
# 命令行升级的官方原版**不跑**这个脚本，所以本镜像补上，使
# 「官方宝塔 → 本升级脚本」与官方 UI 升级等价。
# 失败只提示，不中断升级（与官方 run_pre_script 的处理一致）。
#==============================================================
version_prep() {
	_vp_opp="$1"
	_vp_ver="$2"
	_vp_sh="/www/server/panel/script/update_prep_script.sh"
	_vp_tmp="${_vp_sh}.new.$$"

	# 每次都取最新（下载失败则退回本机已有的那份）
	wget -T 15 -t 2 -O "$_vp_tmp" "${Btapi_Url}/install/update/update_prep_script.sh" >/dev/null 2>&1
	if [ -s "$_vp_tmp" ] && head -1 "$_vp_tmp" | grep -q '#!/bin/bash'; then
		mv -f "$_vp_tmp" "$_vp_sh"
		chmod 700 "$_vp_sh" 2>/dev/null
	else
		rm -f "$_vp_tmp"
		if [ ! -s "$_vp_sh" ]; then
			echo -e "\033[33m预准备脚本不可用，跳过 ${_vp_opp} 阶段\033[0m"
			return 0
		fi
	fi

	# 主版本号为偶数才是 LTS（与面板 script/upgrade_panel_optimized.py 的 is_lts 一致）
	_vp_major="${_vp_ver%%.*}"
	if [ $(( _vp_major % 2 )) -eq 0 ]; then _vp_lts="True"; else _vp_lts="False"; fi

	echo "正在执行 ${_vp_opp} 阶段预处理（版本 ${_vp_ver} / LTS=${_vp_lts}）"
	_vp_log="/tmp/bt_update_prep_${_vp_opp}.log"
	_vp_out=$(bash "$_vp_sh" "$_vp_ver" "$_vp_lts" "$_vp_opp" 2>&1)
	echo "$_vp_out" > "$_vp_log"

	# pip 的输出太吵（notice/WARNING/Requirement already satisfied…），
	# 屏上只留预处理脚本自己的进度行与关键信息，完整日志留文件备查
	echo "$_vp_out" | grep -E '^\[prepare|^\[after|^>>>|^<<<|BT-Panel Update Ready|阶段完成|阶段处理|当前版本:|无需|清理完成|跳过|失败|Error' | tail -15

	if echo "$_vp_out" | grep -q 'BT-Panel Update Ready'; then
		echo -e "\033[32m${_vp_opp} 阶段预处理完成。\033[0m"
	else
		echo -e "\033[33m${_vp_opp} 阶段预处理未报告成功，完整日志: ${_vp_log}\033[0m"
		# 预处理失败会跳过必要的迁移动作（如 after_13_0 同步 /etc/init.d/bt），
		# 这里置位以便结尾不再打印全绿的"升级成功"（与官方 run_pre_script 一样不中止）
		UPGRADE_WARN=1
	fi
	return 0
}


if [ ! -d /www/server/panel/BTPanel ];then
	echo "============================================="
	echo "错误, 5.x不可以使用此命令升级!"
	echo "5.9平滑升级到6.0的命令：curl http://download.bt.cn/install/update_to_6.sh|bash"
	exit 0;
fi

if [ ! -f "/www/server/panel/pyenv/bin/python3" ];then
	echo "============================================="
	echo "错误, 当前面板过旧/py-2.7/无pyenv环境，无法升级至最新版面板"
	echo "请截图发帖至论坛www.bt.cn/bbs求助"
	exit 0;
fi



if [ -f "/etc/redhat-release" ];then
	Centos6Check=$(cat /etc/redhat-release | grep ' 6.' | grep -iE 'centos|Red Hat')
	if [ "${Centos6Check}" ];then
		echo "Centos6不支持升级宝塔面板，建议备份数据重装更换Centos7/8安装宝塔面板"
		exit 1
	fi 
	Centos8Check=$(cat /etc/redhat-release | grep ' 8.' | grep -iE 'centos|Red Hat')
	if [ "${Centos8Check}" ];then
		if [ ! -f "/usr/bin/python" ] && [ -f "/usr/bin/python3" ] && [ ! -d "/www/server/panel/pyenv" ]; then
			ln -sf /usr/bin/python3 /usr/bin/python
		fi
	fi
fi

mypip="pip"
env_path=/www/server/panel/pyenv/bin/activate
if [ -f $env_path ];then
	mypip="/www/server/panel/pyenv/bin/pip"
fi

if [ -f "/www/server/panel/data/down_url.pl" ];then
	D_NODE_URL=$(cat /www/server/panel/data/down_url.pl|grep bt.cn)
fi

if [ -z "${D_NODE_URL}" ];then
	D_NODE_URL="download.bt.cn"
fi

download_Url=$D_NODE_URL


Set_Centos7_Repo(){
    if [ -f "/etc/yum.repos.d/docker-ce.repo" ];then
        mv /etc/yum.repos.d/docker-ce.repo /etc/yum.repos.d/docker-ce.repo_backup
    fi
	MIRROR_CHECK=$(cat /etc/yum.repos.d/CentOS-Base.repo |grep "[^#]mirror.centos.org")
	if [ "${MIRROR_CHECK}" ] && [ "${is64bit}" == "64" ];then
		\cp -rpa /etc/yum.repos.d/ /etc/yumBak
		sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*.repo
		sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.epel.cloud|g' /etc/yum.repos.d/CentOS-*.repo
	fi

	TSU_MIRROR_CHECK=$(cat /etc/yum.repos.d/CentOS-Base.repo |grep "tuna.tsinghua.edu.cn")
	if [ "${TSU_MIRROR_CHECK}" ];then
		\cp -rpa /etc/yum.repos.d/ /etc/yumBak
		sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*.repo
		sed -i 's|#baseurl=https://mirrors.tuna.tsinghua.edu.cn|baseurl=http://vault.epel.cloud|g' /etc/yum.repos.d/CentOS-*.repo
		sed -i 's|#baseurl=http://mirrors.tuna.tsinghua.edu.cn|baseurl=http://vault.epel.cloud|g' /etc/yum.repos.d/CentOS-*.repo
	fi
	
	ALI_CLOUD_CHECK=$(grep Alibaba /etc/motd)
	Tencent_Cloud=$(cat /etc/hostname |grep -E VM-[0-9]+-[0-9]+)
	if [ "${ALI_CLOUD_CHECK}" ] || [ "${Tencent_Cloud}" ];then
		return
	fi
	
	yum install tree -y
	if [ "$?" != "0" ] ;then
		TAR_CHECK=$(which tree)
		if [ "$?" == "0" ] ;then
			\cp -rpa /etc/yum.repos.d/ /etc/yumBak
			if [ -z "${download_Url}" ];then
				download_Url="http://download.bt.cn"
			fi
			curl -Ss --connect-timeout 5 -m 60 -O ${download_Url}/src/el7repo.tar.gz
			rm -f /etc/yum.repos.d/*.repo
			tar -xvzf el7repo.tar.gz -C /etc/yum.repos.d/
		fi
	fi

	yum install tree -y
	if [ "$?" != "0" ] ;then
		sed -i "s/vault.epel.cloud/mirrors.cloud.tencent.com/g" /etc/yum.repos.d/*.repo
	fi
}
if [ -f "/etc/redhat-release" ];then
	Centos7Check=$(cat /etc/redhat-release | grep ' 7.' | grep -iE 'centos|Red Hat')
	if [ "${Centos7Check}" ];then
		Set_Centos7_Repo
	fi
fi
setup_path=/www
version=$(curl -Ss --connect-timeout 5 -m 10 $Btapi_Url/api/panel/get_version)
if [ -z "$version" ];then
	version='13.0.0'
fi
armCheck=$(uname -m|grep arm)
if [ "${armCheck}" ];then
	version='7.7.0'
fi

if [ "$1" = "--force" ]; then
	# 显式放行降级（见下方降级保护）
	FORCE_UPGRADE=1
elif [ -n "$1" ]; then
	# 显式指定目标版本号，视为用户明确意图
	version=$1
	VER_ARG="$1"
fi

#==============================================================
# 降级保护：镜像上只托管有限版本，若目标版本低于本机当前版本，
# 直接覆盖会把面板静默降级（且 unzip -o 只覆盖不清理，会留下新版独有文件）。
# 默认中止；--force 或显式指定版本号才放行。
# 注意：本脚本常被 `curl | bash` 管道执行，stdin 不是终端，不能用 read 交互。
#==============================================================
_version_gt() {
	# $1 是否大于 $2（按版本号语义比较，不用字典序）
	[ "$1" = "$2" ] && return 1
	[ "$(printf '%s\n%s\n' "$1" "$2" | sort -V 2>/dev/null | tail -n1)" = "$1" ]
}
CUR_VER=$(grep -m1 -oE "g\.version *= *['\"][0-9.]+" /www/server/panel/class/common.py 2>/dev/null | grep -oE "[0-9.]+$")
if [ -z "$CUR_VER" ]; then
	echo -e "\033[33m警告: 读不到当前面板版本（/www/server/panel/class/common.py），已跳过降级保护。\033[0m"
	UPGRADE_WARN=1
elif _version_gt "$CUR_VER" "$version"; then
	echo "============================================="
	echo -e "\033[33m警告: 本次升级会把面板降级！\033[0m"
	echo "  当前版本: ${CUR_VER}"
	echo "  目标版本: ${version}"
	echo "  升级后版本会回退；且升级是整体覆盖，可能留下新版独有文件。"
	if [ "$FORCE_UPGRADE" = "1" ]; then
		echo -e "\033[33m  已指定 --force，已跳过降级保护，继续升级。\033[0m"
		UPGRADE_WARN=1
	elif [ -n "$VER_ARG" ]; then
		echo -e "\033[33m  你已显式指定目标版本 ${VER_ARG}，视为明确意图，继续升级。\033[0m"
		UPGRADE_WARN=1
	else
		echo "  若确认要降级，请重试并加 --force："
		echo "      curl -sSL <镜像>/install/update6.sh | bash -s -- --force"
		echo "  已中止，未做任何改动。"
		echo "============================================="
		exit 1
	fi
	echo "============================================="
else
	echo "当前面板版本: ${CUR_VER} → 目标版本: ${version}"
fi

# prepare 阶段：在下载/替换面板文件之前，补齐依赖、执行前置迁移
version_prep prepare "$version"

echo "正在下载面板升级包（约 37MB），请稍等..."
wget -q -T 5 -O /tmp/panel.zip $Btapi_Url/install/update/LinuxPanel-${version}.zip
dsize=$(du -b /tmp/panel.zip|awk '{print $1}')
if [ $dsize -lt 10240 ];then
	echo "获取更新包失败，请稍后更新或联系宝塔运维"
	exit;
fi

#--------------------------------------------------------------
# 升级包完整性校验
# 镜像同目录的 LinuxPanel-<ver>.pl 内容是 {"hash": "<zip 的 sha256>", ...}
# hash 存在且不匹配 → 删除升级包并中止（绝不能拿被篡改/损坏的包覆盖面板）
# 取不到或解析不出 hash → 只警告并继续（镜像缺 .pl 不该把所有客户端卡死）
#--------------------------------------------------------------
PL_TMP="/tmp/panel.pl.$$"
wget -q -T 5 -t 2 -O "$PL_TMP" $Btapi_Url/install/update/LinuxPanel-${version}.pl >/dev/null 2>&1
EXP_HASH=""
if [ -s "$PL_TMP" ]; then
	EXP_HASH=$(/www/server/panel/pyenv/bin/python3 -c "import json,sys;print(json.load(open(sys.argv[1])).get('hash',''))" "$PL_TMP" 2>/dev/null)
fi
rm -f "$PL_TMP"
EXP_HASH=$(echo "$EXP_HASH" | tr 'A-Z' 'a-z' | tr -d '[:space:]')
if [ -n "$EXP_HASH" ]; then
	ACT_HASH=$(sha256sum /tmp/panel.zip 2>/dev/null | awk '{print $1}' | tr 'A-Z' 'a-z')
	if [ "$ACT_HASH" != "$EXP_HASH" ]; then
		echo "============================================="
		echo "错误: 升级包校验失败（下载损坏或已被篡改）"
		echo "  期望 sha256: ${EXP_HASH}"
		echo "  实际 sha256: ${ACT_HASH}"
		echo "  已删除升级包，面板未被改动。"
		echo "============================================="
		rm -f /tmp/panel.zip
		exit 1
	fi
	echo "升级包 sha256 校验通过"
else
	echo -e "\033[33m警告: 无法校验升级包完整性（取不到或解析不出校验信息），将继续升级。\033[0m"
	UPGRADE_WARN=1
fi

#--------------------------------------------------------------
# 备份 → 解压 → 关键文件检查，任一步失败即回滚到升级前面板
# 注意：失败路径下保留 /tmp/panel.zip 便于排查
#--------------------------------------------------------------
bt_panel_dir="$setup_path/server/panel"
PANEL_BAK="$setup_path/server/panel_bak_$(date +%Y%m%d%H%M%S)"
while [ -e "$PANEL_BAK" ]; do
	PANEL_BAK="${PANEL_BAK}_1"
done

_panel_rollback() {
	echo -e "\033[33m正在回滚面板文件...\033[0m"
	rm -rf "$bt_panel_dir"
	if cp -a "$PANEL_BAK" "$bt_panel_dir" && [ -f "$bt_panel_dir/BT-Panel" ]; then
		echo -e "\033[32m面板已恢复到升级前状态。\033[0m"
	else
		echo -e "\033[31m回滚失败！请手动恢复：\033[0m"
		echo -e "\033[31m  rm -rf ${bt_panel_dir} && cp -a ${PANEL_BAK} ${bt_panel_dir}\033[0m"
	fi
	echo -e "\033[33m升级前备份保留在: ${PANEL_BAK}（本次升级未自动删除）\033[0m"
}

# 空间检查放最前：备份做到一半空间不足会留下半个面板
_panel_kb=$(du -sk "$bt_panel_dir" 2>/dev/null | awk '{print $1}')
_free_kb=$(df -Pk "$bt_panel_dir" 2>/dev/null | awk 'NR==2{print $4}')
if [ -n "$_panel_kb" ] && [ -n "$_free_kb" ] && [ "$_free_kb" -lt $(( _panel_kb + 51200 )) ]; then
	echo "============================================="
	echo "错误: 磁盘可用空间不足，无法安全升级"
	echo "  面板目录约 $(( _panel_kb/1024 )) MB，备份需同等空间（另留 50MB 余量）"
	echo "  当前可用 $(( _free_kb/1024 )) MB"
	echo "  请清理磁盘后重试。未做任何改动。"
	echo "============================================="
	exit 1
fi

echo "正在备份当前面板目录（升级失败时用于回滚）..."
if ! cp -a "$bt_panel_dir" "$PANEL_BAK"; then
	rm -rf "$PANEL_BAK"
	echo "============================================="
	echo "错误: 面板目录备份失败，已中止升级（面板未被改动）"
	echo "============================================="
	exit 1
fi
echo -e "\033[32m面板已备份到: ${PANEL_BAK}\033[0m"

if ! unzip -o /tmp/panel.zip -d $setup_path/server/ > /dev/null; then
	echo "============================================="
	echo "错误: 升级包解压失败"
	_panel_rollback
	echo "升级包已保留在 /tmp/panel.zip，便于排查。"
	echo "============================================="
	exit 1
fi

# 关键文件存在性检查：解压不完整时不能继续，否则面板会处于半残状态
_missing=""
for _f in "$bt_panel_dir/BT-Panel" "$bt_panel_dir/class/common.py" "$bt_panel_dir/tools.py"; do
	[ -e "$_f" ] || _missing="${_missing} ${_f}"
done
if [ -n "$_missing" ]; then
	echo "============================================="
	echo "错误: 解压后关键文件缺失，判定为解压不完整："
	for _f in $_missing; do
		echo "  缺失: ${_f}"
	done
	_panel_rollback
	echo "升级包已保留在 /tmp/panel.zip，便于排查。"
	echo "============================================="
	exit 1
fi

rm -f /tmp/panel.zip
cd $setup_path/server/panel/
check_bt=`cat /etc/init.d/bt`
if [ "${check_bt}" = "" ];then
	rm -f /etc/init.d/bt
	wget -O /etc/init.d/bt $download_Url/install/src/bt7.init -T 20
	chmod +x /etc/init.d/bt
fi
echo "=============================================================="
echo "正在修复面板依赖问题"
rm -f /www/server/panel/*.pyc
rm -f /www/server/panel/class/*.pyc
#pip install flask_sqlalchemy
#pip install itsdangerous==0.24

if [ -f "/www/server/panel/pyenv/bin/pip3" ];then
	btpip_path="/www/server/panel/pyenv/bin/pip3"
	FlaskV=$($btpip_path list 2>/dev/null|grep "Flask " |awk '{print $2}'|cut -f 1 -d .)
	piplist_count=$($btpip_path list 2>/dev/null|wc -l)
	if [ "${FlaskV}" -le "2" ] && [ "${piplist_count}" -le "118" ];then
		echo "检测到面板运行环境过老，正常尝试修复面板依赖"
		pyenv_path="/www/server/panel"
		wget -O $pyenv_path/pyenv/pip.txt $download_Url/install/pyenv/pip-3.7.16.txt -T 5
		$pyenv_path/pyenv/bin/pip install -U pip
		$pyenv_path/pyenv/bin/pip install -U setuptools==65.5.0
		$pyenv_path/pyenv/bin/pip install -U wheel==0.34.2 
		$pyenv_path/pyenv/bin/pip install -r $pyenv_path/pyenv/pip.txt
		echo "依赖修复完成，如面板仍无法正常访问，请联系宝塔官方人员进行求助"
	fi

	#wget -O pip-packs.txt $download_Url/install/pyenv/pip-packs.txt
	#PIP_PACKS=$(cat pip-packs.txt)
	#for P_PACK in ${PIP_PACKS};
	#do
	#	btpip show ${P_PACK} > /dev/null 2>&1
	#	if [ "$?" == "1" ];then
	#		btpip install ${P_PACK}
	#	fi 
	#done
fi


pip_list=$($mypip list 2>&1)
request_v=$(btpip list 2>/dev/null|grep "requests "|awk '{print $2}'|cut -d '.' -f 2)
if [ "$request_v" = "" ] || [ "${request_v}" -gt "28" ];then
	$mypip install requests==2.27.1
	$mypip install chardet==4.0.0
fi

NATSORT_C=$(echo $pip_list|grep natsort)
if [ -z "${NATSORT_C}" ];then
	btpip install natsort
fi

openssl_v=$(echo "$pip_list"|grep pyOpenSSL)
if [ "$openssl_v" = "" ];then
	$mypip install pyOpenSSL
fi

#cffi_v=$(echo "$pip_list"|grep cffi|grep 1.12.)
#if [ "$cffi_v" = "" ];then
#	$mypip install cffi==1.12.3
#fi

pymysql=$(echo "$pip_list"|grep pymysql)
if [ "$pymysql" = "" ];then
	$mypip install pymysql
fi
GEVENT_V=$(btpip list 2> /dev/null|grep "gevent "|awk '{print $2}'|cut -f 1 -d '.')
if [ "${GEVENT_V}" -le "1" ];then
    /www/server/panel/pyenv/bin/pip3 install -I gevent
fi

BROTLI_C=$(btpip list 2> /dev/null |grep Brotli)
if [ -z "$BROTLI_C" ]; then
    btpip install brotli
fi

PYMYSQL_C=$(btpip list 2> /dev/null |grep PyMySQL)
if [ -z "$PYMYSQL_C" ]; then
    btpip install PyMySQL
fi


PY_CRPYT=$(btpip list 2> /dev/null |grep cryptography|awk '{print $2}'|cut -f 1 -d '.')
if [ "${PY_CRPYT}" -le "10" ];then
    btpip install pyOpenSSL==24.1.0
    btpip install cryptography==42.0.5
fi

PYMYSQL_SSL_CHECK=$(btpython -c "import pymysql" 2>&1|grep "AttributeError: module 'cryptography.hazmat.bindings._rust.openssl'")
if [ "${PYMYSQL_SSL_CHECK}" ];then
    btpip uninstall pyopenssl cryptography -y
    btpip install pyopenssl cryptography
fi

btpip uninstall enum34 -y

btpip install asn1crypto==1.5.1 cbor2==5.4.6
# openai 与 numpy 必须分开装：numpy==1.21.6 只支持 Python<3.11，
# 在 Python 3.13 的面板上必然 No matching distribution，而 pip 对同一条
# 命令行做统一解析 → 会把 openai 一并拖垮（两个都装不上）。
btpip install openai==1.39.0
if [ "$?" != "0" ]; then
	echo -e "\033[33m警告: openai==1.39.0 安装失败，面板 AI 功能可能不可用，继续升级。\033[0m"
	UPGRADE_WARN=1
fi

# numpy==1.21.6 要求 Python<3.11（面板代码无任何文件 import numpy，仅个别场景用）
if /www/server/panel/pyenv/bin/python3 --version 2>&1 | grep -q "^Python 3\.13"; then
	echo -e "\033[33m检测到 Python 3.13：跳过 numpy==1.21.6（该版本要求 Python<3.11）。\033[0m"
elif btpip show numpy >/dev/null 2>&1; then
	:
else
	btpip install numpy==1.21.6
	if [ "$?" != "0" ]; then
		echo -e "\033[33m警告: numpy==1.21.6 安装失败，继续升级。\033[0m"
		UPGRADE_WARN=1
	fi
fi

GEOIP_C=$(echo $pip_list|grep geoip2)
if [ -z "${GEOIP_C}" ];then
	btpip install geoip2==4.7.0
fi

# PANDAS_C=$(echo $pip_list|grep pandas)
# if [ -z "${PANDAS_C}" ];then
# 	btpip install pandas
# fi

pymysql=$(echo "$pip_list"|grep pycryptodome)
if [ "$pymysql" = "" ];then
	$mypip install pycryptodome
fi

echo "修复面板依赖完成！"

# after 阶段：文件已替换，重启面板之前执行各版本迁移（与面板 UI 升级一致）
version_prep after "$version"

#==============================================================
# 把客户端 install/public.sh 统一为自持版
#
# 升级包自带的 panel/install/public.sh 是换皮版（md5 04c5c65a…）：
# 第 142 行把版本遥测指向 install.btweb.co，且比官方版少了一段逻辑。
# 而面板 install/*.sh（nginx/mysql/php 安装脚本）里钉死校验的 md5 已被
# bt_selfhost_patch.sh 换成**自持版** public.sh 的 742857589a…，客户端
# public.sh 的 md5 一旦与之不符，这些脚本就会去外部重下 public.sh 覆盖回去。
# 这里把镜像上那份自持版 public.sh 直接落地到客户端：既消除那次外部下载，
# 也去掉换皮遥测。
# 失败只提示，不影响升级。
#==============================================================
pub_tmp=/tmp/.bt_public.$$
PUB_MD5="742857589a6e1678316387065a61cc09"
if wget -T 15 -t 2 -O ${pub_tmp} ${Btapi_Url}/install/public.sh >/dev/null 2>&1 && [ -s ${pub_tmp} ]; then
	if [ "$(md5sum ${pub_tmp} | awk '{print $1}')" = "${PUB_MD5}" ]; then
		if [ "$(md5sum /www/server/panel/install/public.sh 2>/dev/null | awk '{print $1}')" != "${PUB_MD5}" ]; then
			cp -f ${pub_tmp} /www/server/panel/install/public.sh
			chmod 644 /www/server/panel/install/public.sh
			echo -e "\033[32minstall/public.sh 已换为自持版（md5 与 install/*.sh 钉死值一致）\033[0m"
		fi
	else
		echo -e "\033[33m镜像上的 public.sh md5=$(md5sum ${pub_tmp}|awk '{print $1}') 非预期，跳过替换\033[0m"
	fi
	rm -f ${pub_tmp}
else
	rm -f ${pub_tmp}
	echo -e "\033[33mpublic.sh 下载失败，跳过替换（不影响升级）\033[0m"
fi
echo "==========================================="

# echo "安装基础网站流量统计程序..."
wget -O site_new_total.sh ${download_Url}/site_total/install.sh &> /dev/null 
bash site_new_total.sh &> /dev/null
rm -f site_new_total.sh
# echo "安装基础网站流量统计程序完成"


RE_UPDATE=$(cat /www/server/panel/data/db/update)
if [ "$RE_UPDATE" -ge "4" ];then
    echo "2" > /www/server/panel/data/db/update
fi

#psutil=$(echo "$pip_list"|grep psutil|awk '{print $2}'|grep '5.7.')
#if [ "$psutil" = "" ];then
#	$mypip install -U psutil
#fi

if [ -d /www/server/panel/class/BTPanel ];then
	rm -rf /www/server/panel/class/BTPanel
fi
rm -f /www/server/panel/class/*.so
if [ ! -f /www/server/panel/data/not_workorder.pl ]; then
	echo "True" > /www/server/panel/data/not_workorder.pl
fi
if [ ! -f /www/server/panel/data/not_panelai.pl ]; then
	echo "True" > /www/server/panel/data/not_panelai.pl
fi
if [ ! -f /www/server/panel/data/not_evaluate.pl ]; then
	echo "True" > /www/server/panel/data/not_evaluate.pl
fi
if [ ! -f /www/server/panel/data/userInfo.json ]; then
	echo "{\"uid\":1,\"username\":\"Administrator\",\"address\":\"127.0.0.1\",\"access_key\":\"test\",\"secret_key\":\"123456\",\"ukey\":\"123456\",\"state\":1}" > /www/server/panel/data/userInfo.json
fi
if [ ! -f /www/server/panel/data/panel_nps.pl ]; then
	echo "" > /www/server/panel/data/panel_nps.pl
fi
if [ ! -f /www/server/panel/data/btwaf_nps.pl ]; then
	echo "" > /www/server/panel/data/btwaf_nps.pl
fi
if [ ! -f /www/server/panel/data/tamper_proof_nps.pl ]; then
	echo "" > /www/server/panel/data/tamper_proof_nps.pl
fi
if [ ! -f /www/server/panel/data/total_nps.pl ]; then
	echo "" > /www/server/panel/data/total_nps.pl
fi

echo "==========================================="
echo "正在更新面板文件..............."
sleep 1
echo "更新完成！"
echo "如在终端中执行此命令，请手动刷新面板页面"
echo "==========================================="

chattr -i /etc/init.d/bt
chmod +x /etc/init.d/bt
echo "====================================="
rm -f /dev/shm/bt_sql_tips.pl
kill $(ps aux|grep -E "task.pyc|main.py"|grep -v grep|awk '{print $2}')
/etc/init.d/bt restart
echo 'True' > /www/server/panel/data/restart.pl
pkill -9 gunicorn &
echo "已成功升级到[$version]${Ver}";

#==============================================================
# 面板自持补丁：把面板的「云端」重新指向本镜像
# 升级会用新包覆盖面板代码，所以每次升级后都要重打一次（失败不影响面板使用）
#   * 软件商店的插件列表      class/PluginLoader.py
#   * 插件包的下载            class/panelPlugin.py
#   * UI 里的版本检测与升级   class/system.py、script/upgrade_panel_optimized.py
#==============================================================
echo -e ""
echo -e "正在应用自持镜像补丁..."
self_tmp=/tmp/.bt_selfhost_patch.$$
if wget -T 15 -t 2 -O ${self_tmp} ${Btapi_Url}/tools/bt_selfhost_patch.sh >/dev/null 2>&1 \
   && [ -s ${self_tmp} ] && head -1 ${self_tmp} | grep -q '#!/bin/bash'; then
    if MIRROR=${Btapi_Url} bash ${self_tmp}; then
        echo -e "\033[32m面板的云端已指向自持镜像（软件商店 / 插件安装 / 版本更新）。\033[0m"
    else
        echo -e "\033[33m自持补丁执行失败，已跳过（面板可用，但仍连厂商云端）。\033[0m"
    fi
    rm -f ${self_tmp}
else
    rm -f ${self_tmp}
    echo -e "\033[33m自持补丁脚本下载失败，已跳过。\033[0m"
fi
# 补丁改的是面板代码，需重启面板才会加载
if [ -f /etc/init.d/bt ]; then
    /etc/init.d/bt restart >/dev/null 2>&1 && echo -e "\033[32m面板已重启，补丁生效。\033[0m"
fi

#==============================================================
# 面板安全自检（升级后复查；失败不影响升级结果）
#
# 注意：升级本身会改动面板文件，所以这里【不】自动重建基线，
# 否则会抹掉漂移检测。确认本次升级无误后，请手动执行：
#     bash /root/security-check/check_btpanel.sh --init-baseline
#==============================================================
echo -e ""
echo -e "=================================================================="
echo -e "正在复查面板安全自检..."
sel_dir=/root/security-check
sel_tmp=/tmp/.bt_sel_check.$$
mkdir -p ${sel_dir} 2>/dev/null
# 先下载到临时文件、校验通过后才替换 —— 直接 wget -O 到目标文件时，
# 若下载失败会把已有脚本截断成 0 字节。
if wget -T 15 -t 2 -O ${sel_tmp} ${Btapi_Url}/tools/check_btpanel.sh >/dev/null 2>&1 \
   && [ -s ${sel_tmp} ] && head -1 ${sel_tmp} | grep -q '#!/bin/bash'; then
    mv -f ${sel_tmp} ${sel_dir}/check_btpanel.sh
    chmod 700 ${sel_dir}/check_btpanel.sh 2>/dev/null
    wget -T 15 -t 2 -O ${sel_tmp} ${Btapi_Url}/tools/install-notes.txt >/dev/null 2>&1
    [ -s ${sel_tmp} ] && mv -f ${sel_tmp} ${sel_dir}/install-notes.txt
    rm -f ${sel_tmp}
    sel_ok=1
elif [ -s ${sel_dir}/check_btpanel.sh ]; then
    rm -f ${sel_tmp}
    echo -e "\033[33m自检脚本更新失败，改用本机已有版本。\033[0m"
    sel_ok=1
else
    rm -f ${sel_tmp}
    sel_ok=0
fi
if [ "${sel_ok}" = "1" ]; then
    if [ ! -f ${sel_dir}/baseline.sha256 ]; then
        bash ${sel_dir}/check_btpanel.sh --init-baseline >/dev/null 2>&1
        echo -e "\033[32m首次在此机器建立安全基线。\033[0m"
    fi
    if bash ${sel_dir}/check_btpanel.sh; then
        echo -e "\033[32m升级后安全自检通过。\033[0m"
    else
        echo -e "\033[31m升级后发现可疑项，请查看上方输出并人工检查！\033[0m"
        UPGRADE_WARN=1
    fi
    if [ ! -f /etc/cron.d/btpanel-security-check ]; then
        printf 'SHELL=/bin/bash\nPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n17 4 * * * root /bin/bash %s/check_btpanel.sh --quiet >> /var/log/btpanel-security-check.log 2>&1\n' "${sel_dir}" > /etc/cron.d/btpanel-security-check 2>/dev/null
        chmod 644 /etc/cron.d/btpanel-security-check 2>/dev/null
        echo -e "\033[32m已安装每日定时巡检（04:17）。\033[0m"
    fi
    echo -e "\033[33m提示：升级会改动面板文件，上方若有『文件已变更』属预期。\033[0m"
    echo -e "\033[33m      确认本次升级正常后，请重建基线：\033[0m"
    echo -e "\033[33m      bash ${sel_dir}/check_btpanel.sh --init-baseline\033[0m"
else
    echo -e "\033[33m安全自检脚本不可用，已跳过（不影响面板使用）\033[0m"
    UPGRADE_WARN=1
fi
echo -e "=================================================================="

#==============================================================
# 完成横幅：给出明确的成功状态与版本号
#==============================================================
echo
if [ "${UPGRADE_WARN}" = "1" ]; then
	echo "=================================================="
	echo -e "\033[33m  升级完成\033[0m"
	echo -e "\033[33m  当前面板版本: ${version}\033[0m"
	echo -e "\033[33m  （上方有提示项，建议查看后再确认）\033[0m"
	echo "=================================================="
else
	echo "=================================================="
	echo -e "\033[32m  升级成功\033[0m"
	echo -e "\033[32m  当前面板版本: ${version}\033[0m"
	echo -e "\033[32m  面板已重启，请刷新页面访问\033[0m"
	echo "=================================================="
fi
if [ -n "${PANEL_BAK}" ] && [ -d "${PANEL_BAK}" ]; then
	echo -e "\033[33m  升级前备份: ${PANEL_BAK}\033[0m"
	echo -e "\033[33m  确认本次升级无误后，可自行删除该目录以释放空间\033[0m"
fi
echo


