#!/bin/bash
# ==============================================
# 宝塔面板 13.x 更新脚本
# 面板运行时已切换到 Python3.13.14，
# 已是新运行环境时不再执行旧版(3.7)的依赖修复操作
# ==============================================
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
LANG=en_US.UTF-8

if [ $(whoami) != "root" ];then
	echo "====================================================="
	echo "检查到当前非root权限进行面板升级操作"
	echo "请执行su root命令切换为root账户后再次尝试"
	echo "-----------------------------------------------------"
	exit 1
fi

# 配置变量
PANEL_PATH="/www/server/panel"
SETUP_PATH="/www"
PYENV_PATH="${PANEL_PATH}/pyenv"
PYTHON_PATH="${PYENV_PATH}/bin/python3"
NODE_FILE="${PANEL_PATH}/data/node.json"
PANEL_VERSION="13.1.0"
DOWNLOAD_URL="download.bt.cn"
MYPIP=""  # 由 init_pip 初始化
SO_BAK="false"
is64bit=$(getconf LONG_BIT)

# ==============================================
# 函数定义区域
# ==============================================

# 检查并处理节点文件
check_node_file() {
	local node_check=$(cat "${NODE_FILE}" 2>/dev/null | grep "125.88.182.172")
	if [ "${node_check}" ];then
		rm -f "${NODE_FILE}"
		echo "已清理包含特定IP的节点文件"
	fi
}

# 检查面板版本是否支持升级
check_panel_version() {
	if [ ! -d "${PANEL_PATH}/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
}

# 检查Python环境
check_python_env() {
	if [ ! -f "${PYTHON_PATH}" ];then
		echo "============================================="
		echo "错误, 当前面板过旧/py-2.7/无pyenv环境，无法升级至最新版面板"
		echo "请截图发帖至论坛www.bt.cn/bbs求助"
		exit 0;
	fi
}

# 检查当前面板运行环境是否已是新的 Python3.13
check_python_version() {
	${PYTHON_PATH} --version 2>&1 | grep -q "^Python 3\.13"
	return $?
}

# 检查系统版本兼容性
check_system_version() {
	if [ ! -f "/etc/redhat-release" ];then
		return
	fi

	# 检查CentOS 6
	local centos6_check=$(cat /etc/redhat-release | grep ' 6.' | grep -iE 'centos|Red Hat')
	if [ "${centos6_check}" ];then
		echo "Centos6不支持升级宝塔面板，建议备份数据重装更换Centos7/8安装宝塔面板"
		exit 1
	fi

	# 处理CentOS 8特殊逻辑
	local centos8_check=$(cat /etc/redhat-release | grep ' 8.' | grep -iE 'centos|Red Hat')
	if [ "${centos8_check}" ];then
		if [ ! -f "/usr/bin/python" ] && [ -f "/usr/bin/python3" ] && [ ! -d "${PYENV_PATH}" ]; then
			ln -sf /usr/bin/python3 /usr/bin/python
		fi
	fi
}

# CentOS7 更换可用的镜像源
set_centos7_repo() {
	if [ ! -f "/etc/redhat-release" ];then
		return
	fi

	local centos7_check=$(cat /etc/redhat-release | grep ' 7.' | grep -iE 'centos|Red Hat')
	if [ -z "${centos7_check}" ];then
		return
	fi

	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

	local mirror_check=$(cat /etc/yum.repos.d/CentOS-Base.repo 2>/dev/null |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

	local tsu_mirror_check=$(cat /etc/yum.repos.d/CentOS-Base.repo 2>/dev/null |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

	local ali_cloud_check=$(grep Alibaba /etc/motd 2>/dev/null)
	local tencent_cloud=$(cat /etc/hostname 2>/dev/null |grep -E VM-[0-9]+-[0-9]+)
	if [ "${ali_cloud_check}" ] || [ "${tencent_cloud}" ];then
		return
	fi

	yum install tree -y
	if [ "$?" != "0" ] ;then
		local tar_check=$(which tree)
		if [ "$?" == "0" ] ;then
			\cp -rpa /etc/yum.repos.d/ /etc/yumBak
			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
}

# 获取下载节点URL
get_download_url() {
	local node_url_file="${PANEL_PATH}/data/down_url.pl"
	if [ -f "${node_url_file}" ];then
		local url=$(cat "${node_url_file}" | grep bt.cn)
		if [ "${url}" ];then
			DOWNLOAD_URL="${url}"
		fi
	fi
}

# 下载并执行修复脚本
download_fix_script() {
	wget -O fix_install.sh "${DOWNLOAD_URL}/tools/fix_install.sh"
	nohup bash fix_install.sh > "${PANEL_PATH}/install/fix.log" 2>&1 &
}

# 处理命令行参数
# $1: 指定更新到的面板版本号, $2: 传入则备份SO文件
process_arguments() {
	if [ "$1" ];then
		PANEL_VERSION="$1"
	fi

	if [ "$2" ];then
		SO_BAK="true"
		/etc/init.d/bt stop
	fi
}

# 备份SO文件
backup_so_files() {
	if [ "${SO_BAK}" = "true" ];then
		mkdir -p "${PANEL_PATH}so"
		\cp -rpa "${PANEL_PATH}/class"/*.so "${PANEL_PATH}so"
		echo "已备份SO文件到 ${PANEL_PATH}so"
	fi
}

# 下载面板更新包
download_panel_package() {
	local panel_zip="/tmp/panel.zip"

	wget -T 20 -O "${panel_zip}" "${DOWNLOAD_URL}/install/update/LinuxPanel-${PANEL_VERSION}.zip"

	local file_size=$(du -b "${panel_zip}" | awk '{print $1}')
	if [ "${file_size}" -lt 10240 ];then
		echo "获取更新包失败，请稍后更新或联系宝塔运维"
		exit 1
	fi

	unzip -o "${panel_zip}" -d "${SETUP_PATH}/server/" > /dev/null
	rm -f "${panel_zip}"
}

# 修复BT启动脚本
fix_bt_script() {
	local bt_script="/etc/init.d/bt"

	local check_bt=$(cat "${bt_script}" 2>/dev/null)
	if [ "${check_bt}" = "" ];then
		rm -f "${bt_script}"
		wget -O "${bt_script}" "${DOWNLOAD_URL}/install/src/bt7.init" -T 20
		chmod +x "${bt_script}"
		echo "已修复BT启动脚本"
	fi
}

# 初始化pip命令
init_pip() {
	MYPIP="pip"
	if [ -f "${PYENV_PATH}/bin/activate" ];then
		MYPIP="${PYENV_PATH}/bin/pip"
	fi
}

# 修复面板依赖 (仅旧版 Python3.7 运行环境需要执行)
fix_panel_dependencies() {
	echo "=============================================================="
	echo "正在修复面板依赖问题"
	rm -f "${PANEL_PATH}"/*.pyc
	rm -f "${PANEL_PATH}/class"/*.pyc

	if [ -f "${PYENV_PATH}/bin/pip3" ];then
		local btpip_path="${PYENV_PATH}/bin/pip3"
		local flask_v=$(${btpip_path} list 2>/dev/null|grep "Flask " |awk '{print $2}'|cut -f 1 -d .)
		local piplist_count=$(${btpip_path} list 2>/dev/null|wc -l)

		# 检测运行环境过老时重新拉取旧版运行环境的依赖清单
		if [ "${flask_v}" -le "2" ] && [ "${piplist_count}" -le "118" ];then
			echo "检测到面板运行环境过老，正常尝试修复面板依赖"
			wget -O "${PYENV_PATH}/pip.txt" "${DOWNLOAD_URL}/install/pyenv/pip-3.7.16.txt" -T 5
			${PYENV_PATH}/bin/pip install -U pip
			${PYENV_PATH}/bin/pip install -U setuptools==65.5.0
			${PYENV_PATH}/bin/pip install -U wheel==0.34.2
			${PYENV_PATH}/bin/pip install -r "${PYENV_PATH}/pip.txt"
			echo "依赖修复完成，如面板仍无法正常访问，请联系宝塔官方人员进行求助"
		fi

		wget -O /tmp/pip-packs.txt "${DOWNLOAD_URL}/install/pyenv/pip-packs.txt"
		local pip_packs=$(cat /tmp/pip-packs.txt)
		local p_pack=""
		for p_pack in ${pip_packs};
		do
			btpip show ${p_pack} > /dev/null 2>&1
			if [ "$?" == "1" ];then
				btpip install ${p_pack}
			fi
		done
		rm -f /tmp/pip-packs.txt
	fi

	local pip_list=$(${MYPIP} list 2>&1)

	local 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

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

	local openssl_v=$(echo "${pip_list}"|grep pyOpenSSL)
	if [ "$openssl_v" = "" ];then
		${MYPIP} install pyOpenSSL
	fi

	local pymysql=$(echo "${pip_list}"|grep pymysql)
	if [ "$pymysql" = "" ];then
		${MYPIP} install pymysql
	fi

	local gevent_v=$(btpip list 2> /dev/null|grep "gevent "|awk '{print $2}'|cut -f 1 -d '.')
	if [ "${gevent_v}" -le "1" ];then
		${PYENV_PATH}/bin/pip3 install -I gevent
	fi

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

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

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

	local 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

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

	local pycryptodome_c=$(echo "${pip_list}"|grep pycryptodome)
	if [ "$pycryptodome_c" = "" ];then
		${MYPIP} install pycryptodome
	fi

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

# 重置数据库更新标记
reset_db_update() {
	local re_update=$(cat "${PANEL_PATH}/data/db/update" 2>/dev/null)
	if [ "${re_update}" -ge "4" ] 2>/dev/null;then
		echo "2" > "${PANEL_PATH}/data/db/update"
	fi
}

# 清理旧文件并恢复SO文件
cleanup_and_restore() {
	# 清理旧BTPanel目录
	if [ -d "${PANEL_PATH}/class/BTPanel" ];then
		rm -rf "${PANEL_PATH}/class/BTPanel"
	fi

	# 恢复SO文件
	if [ "${SO_BAK}" = "true" ];then
		\cp -rpa "${PANEL_PATH}so"/*.so "${PANEL_PATH}/class"
	fi

	echo "==========================================="
	echo "正在更新面板文件..............."
	sleep 1
	echo "更新完成！"
	echo "==========================================="
}

# 启动面板服务
start_panel_service() {
	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 start
	echo 'True' > "${PANEL_PATH}/data/restart.pl"
	pkill -9 gunicorn &
}

# 安装基础网站流量统计程序
install_site_total() {
	if [ -f "/www/server/nginx/sbin/nginx" ] || [ -f "/www/server/apache/bin/httpd" ];then
		# echo "安装基础网站流量统计程序..."
		( cd "${PANEL_PATH}" && 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 "安装基础网站流量统计程序完成"
	fi
}

# ==============================================
# 主函数
# ==============================================
main() {
	echo "=========================================="
	echo "宝塔面板升级脚本"
	echo "开始时间: $(date)"
	echo "=========================================="

	# 1. 检查并处理节点文件
	check_node_file

	# 2. 检查面板版本
	check_panel_version

	# 3. 检查Python环境
	check_python_env

	# 4. 检查系统版本
	check_system_version

	# 5. CentOS7更换可用镜像源
	set_centos7_repo

	# 6. 获取下载URL (赋值给全局 DOWNLOAD_URL)
	get_download_url

	# 7. 下载并执行修复脚本
	download_fix_script

	# 8. 处理命令行参数 (可能会覆盖全局 PANEL_VERSION)
	process_arguments "$@"

	# 9. 备份SO文件
	backup_so_files

	# 10. 下载面板更新包
	download_panel_package

	# 11. 修复BT启动脚本
	fix_bt_script

	# 12. 修复面板依赖
	if check_python_version; then
		# 已经是Python3.13运行环境，无需再执行旧版环境修复流程
		echo "当前面板运行环境为Python3.13，跳过旧版依赖修复"
		rm -f "${PANEL_PATH}"/*.pyc
		rm -f "${PANEL_PATH}/class"/*.pyc
	else
		init_pip
		fix_panel_dependencies
	fi

	# 13. 重置数据库更新标记
	reset_db_update

	# 14. 清理旧文件并恢复SO文件
	cleanup_and_restore

	# 15. 启动面板服务
	start_panel_service

	# 16. 安装基础网站流量统计程序
	install_site_total

	echo "已成功升级到[${PANEL_VERSION}]"
	echo "结束时间: $(date)"
	echo "=========================================="
}

# ==============================================
# 脚本执行入口
# ==============================================

# 检查是否直接运行
if [ "${BASH_SOURCE[0]}" = "${0}" ];then
	main "$@"
fi
