362 lines
11 KiB
Bash
362 lines
11 KiB
Bash
#!/usr/bin/env bash
|
||
# 抖音客服系统 - Linux 一键安装(兼容宝塔 Python 项目 + 原生 CentOS/Ubuntu)
|
||
# 用法:
|
||
# ./install.sh # 自动探测可用 Python 并安装
|
||
# ./install.sh --diagnose # 仅列出本机 Python 探测结果
|
||
# AUTO_BUILD_PYTHON=1 ./install.sh # 无可用 Python 时自动编译 3.11
|
||
# KEFU_PYTHON=/path/to/python3 ./install.sh # 手动指定
|
||
set -euo pipefail
|
||
|
||
ROOT="$(cd "$(dirname "$0")" && pwd)"
|
||
cd "$ROOT"
|
||
|
||
MIN_PY_MAJOR=3
|
||
MIN_PY_MINOR=10
|
||
DIAGNOSE=0
|
||
AUTO_BUILD="${AUTO_BUILD_PYTHON:-0}"
|
||
|
||
for arg in "$@"; do
|
||
case "$arg" in
|
||
--diagnose) DIAGNOSE=1 ;;
|
||
--build-python) AUTO_BUILD=1 ;;
|
||
esac
|
||
done
|
||
|
||
echo "========================================"
|
||
echo " 抖音客服系统 - 依赖安装"
|
||
echo "========================================"
|
||
echo
|
||
|
||
# ---------- Python 检测工具 ----------
|
||
check_python_ssl() {
|
||
"$1" -c "import ssl" 2>/dev/null
|
||
}
|
||
|
||
check_python_version() {
|
||
"$1" -c "import sys; sys.exit(0 if sys.version_info >= ($MIN_PY_MAJOR, $MIN_PY_MINOR) else 1)" 2>/dev/null
|
||
}
|
||
|
||
python_brief() {
|
||
local py="$1"
|
||
if [ ! -x "$py" ]; then
|
||
echo "不存在"
|
||
return
|
||
fi
|
||
local ver ssl
|
||
ver="$("$py" -V 2>&1 | awk '{print $2}')" || ver="?"
|
||
if check_python_ssl "$py"; then
|
||
ssl="$("$py" -c "import ssl; print(ssl.OPENSSL_VERSION)" 2>/dev/null)"
|
||
else
|
||
ssl="无SSL"
|
||
fi
|
||
echo "${ver} | ${ssl}"
|
||
}
|
||
|
||
try_python() {
|
||
local py="$1"
|
||
[ -n "$py" ] && [ -x "$py" ] || return 1
|
||
check_python_ssl "$py" || return 1
|
||
check_python_version "$py" || return 1
|
||
return 0
|
||
}
|
||
|
||
# 收集候选 Python(去重,高版本优先)
|
||
declare -a CANDIDATES=()
|
||
declare -A SEEN=()
|
||
|
||
add_candidate() {
|
||
local py="$1"
|
||
[ -n "$py" ] || return 0
|
||
if [ -x "$py" ] && [ -z "${SEEN[$py]:-}" ]; then
|
||
SEEN[$py]=1
|
||
CANDIDATES+=("$py")
|
||
fi
|
||
}
|
||
|
||
collect_python_candidates() {
|
||
CANDIDATES=()
|
||
SEEN=()
|
||
|
||
# 1) 用户指定
|
||
[ -n "${KEFU_PYTHON:-}" ] && add_candidate "$KEFU_PYTHON"
|
||
|
||
# 2) 当前 shell(宝塔 source 项目环境后 which python 会指向项目 Python)
|
||
add_candidate "$(command -v python 2>/dev/null || true)"
|
||
add_candidate "$(command -v python3 2>/dev/null || true)"
|
||
add_candidate "$(command -v python3.11 2>/dev/null || true)"
|
||
add_candidate "$(command -v python3.10 2>/dev/null || true)"
|
||
|
||
# 3) 虚拟环境
|
||
[ -n "${VIRTUAL_ENV:-}" ] && add_candidate "${VIRTUAL_ENV}/bin/python"
|
||
|
||
# 4) 宝塔 Python 项目环境(多版本目录名,动态扫描,不写死单一版本)
|
||
local base py ver_dir
|
||
for base in \
|
||
/www/server/pyporject_evn/versions \
|
||
/home/www/server/pyporject_evn/versions \
|
||
/www/server/panel/pyenv/versions \
|
||
/www/server/python_project/versions; do
|
||
[ -d "$base" ] || continue
|
||
for ver_dir in "$base"/*; do
|
||
[ -d "$ver_dir" ] || continue
|
||
for py in "$ver_dir"/bin/python3.* "$ver_dir"/bin/python3 "$ver_dir"/bin/python; do
|
||
add_candidate "$py"
|
||
done
|
||
done
|
||
done
|
||
|
||
# 5) 源码/包管理器安装路径
|
||
for py in \
|
||
/usr/local/python3.11/bin/python3.11 \
|
||
/usr/local/python3.10/bin/python3.10 \
|
||
/usr/local/bin/python3.11 /usr/local/bin/python3.10 \
|
||
/usr/bin/python3.11 /usr/bin/python3.10; do
|
||
add_candidate "$py"
|
||
done
|
||
|
||
# 6) conda
|
||
add_candidate "${CONDA_PREFIX:-}/bin/python"
|
||
for py in /opt/miniconda3/envs/*/bin/python /root/miniconda3/envs/*/bin/python; do
|
||
[ -e "$py" ] && add_candidate "$py"
|
||
done
|
||
}
|
||
|
||
sort_candidates_by_version() {
|
||
local -a sorted=()
|
||
local py ver
|
||
while IFS= read -r line; do
|
||
sorted+=("$line")
|
||
done < <(
|
||
for py in "${CANDIDATES[@]}"; do
|
||
ver="$("$py" -V 2>&1 | awk '{print $2}' || echo 0.0.0)"
|
||
echo "$ver $py"
|
||
done | sort -t. -k1,1nr -k2,2nr -k3,3nr | awk '{print $2}'
|
||
)
|
||
CANDIDATES=("${sorted[@]}")
|
||
}
|
||
|
||
select_python() {
|
||
local py
|
||
collect_python_candidates
|
||
if [ "${#CANDIDATES[@]}" -eq 0 ]; then
|
||
return 1
|
||
fi
|
||
sort_candidates_by_version
|
||
for py in "${CANDIDATES[@]}"; do
|
||
if try_python "$py"; then
|
||
echo "$py"
|
||
return 0
|
||
fi
|
||
done
|
||
return 1
|
||
}
|
||
|
||
print_diagnose() {
|
||
local py tag
|
||
echo "[诊断] 本机 Python 探测结果(需要 3.10+ 且带 SSL):"
|
||
echo
|
||
collect_python_candidates
|
||
if [ "${#CANDIDATES[@]}" -eq 0 ]; then
|
||
echo " (未找到任何 Python 可执行文件)"
|
||
return
|
||
fi
|
||
sort_candidates_by_version
|
||
for py in "${CANDIDATES[@]}"; do
|
||
if try_python "$py"; then tag="可用"; else tag="不可用"; fi
|
||
printf " [%s] %s\n %s\n" "$tag" "$py" "$(python_brief "$py")"
|
||
done
|
||
echo
|
||
if [ -d /www/server/panel ] || [ -d /www/server/pyporject_evn ] || [ -d /home/www/server/pyporject_evn ]; then
|
||
echo " 环境: 检测到宝塔面板"
|
||
echo " 提示: 宝塔 Python 项目需先 source 项目环境,或确保 pyporject_evn 下 Python 带 _ssl"
|
||
else
|
||
echo " 环境: 原生 Linux"
|
||
fi
|
||
}
|
||
|
||
build_python311() {
|
||
local PYVER="3.11.9" PY_SHORT="3.11"
|
||
local PREFIX="/usr/local/python${PY_SHORT}"
|
||
local PY_BIN="${PREFIX}/bin/python${PY_SHORT}"
|
||
local SRC="/usr/src/Python-${PYVER}"
|
||
|
||
if [ "$(id -u)" -ne 0 ]; then
|
||
echo "[错误] 自动编译 Python 需要 root 权限"
|
||
return 1
|
||
fi
|
||
if [ -x "$PY_BIN" ] && try_python "$PY_BIN"; then
|
||
echo "[OK] 已存在可用 Python: $PY_BIN"
|
||
echo "$PY_BIN"
|
||
return 0
|
||
fi
|
||
|
||
echo "[编译] 未找到可用 Python,开始编译 ${PYVER} -> ${PREFIX}(约 10 分钟)"
|
||
if command -v yum &>/dev/null; then
|
||
yum groupinstall -y "Development Tools" 2>/dev/null || yum install -y gcc make
|
||
yum install -y openssl openssl-devel libffi-devel zlib-devel bzip2-devel \
|
||
readline-devel sqlite-devel xz-devel wget tar
|
||
elif command -v apt-get &>/dev/null; then
|
||
apt-get update
|
||
apt-get install -y build-essential libssl-dev zlib1g-dev libbz2-dev \
|
||
libreadline-dev libsqlite3-dev libffi-dev wget tar
|
||
else
|
||
echo "[错误] 不支持的包管理器"
|
||
return 1
|
||
fi
|
||
|
||
mkdir -p /usr/src && cd /usr/src
|
||
if [ ! -f "Python-${PYVER}.tgz" ]; then
|
||
wget -q "https://www.python.org/ftp/python/${PYVER}/Python-${PYVER}.tgz" \
|
||
|| wget -q "https://registry.npmmirror.com/-/binary/python/${PYVER}/Python-${PYVER}.tgz"
|
||
fi
|
||
rm -rf "$SRC" && tar xzf "Python-${PYVER}.tgz" && cd "$SRC"
|
||
./configure --prefix="$PREFIX" --enable-optimizations --with-openssl=/usr
|
||
make -j"$(nproc 2>/dev/null || echo 2)"
|
||
make altinstall
|
||
try_python "$PY_BIN" || return 1
|
||
echo "[OK] 编译完成: $PY_BIN"
|
||
echo "$PY_BIN"
|
||
}
|
||
|
||
# ---------- 选择 Python ----------
|
||
if [ "$DIAGNOSE" -eq 1 ]; then
|
||
print_diagnose
|
||
exit 0
|
||
fi
|
||
|
||
PYTHON=""
|
||
if [ -n "${KEFU_PYTHON:-}" ]; then
|
||
if [ ! -x "$KEFU_PYTHON" ]; then
|
||
echo "[错误] KEFU_PYTHON 不存在: $KEFU_PYTHON"
|
||
echo " 运行 ./install.sh --diagnose 查看本机可用 Python"
|
||
exit 1
|
||
fi
|
||
if ! try_python "$KEFU_PYTHON"; then
|
||
echo "[错误] KEFU_PYTHON 不可用(需 3.10+ 且带 SSL): $KEFU_PYTHON"
|
||
echo " 详情: $(python_brief "$KEFU_PYTHON")"
|
||
echo " 运行 ./install.sh --diagnose 查看全部候选"
|
||
exit 1
|
||
fi
|
||
PYTHON="$KEFU_PYTHON"
|
||
else
|
||
if ! PYTHON="$(select_python)"; then
|
||
print_diagnose
|
||
echo "[错误] 未找到可用的 Python 3.10+(需带 SSL 模块)。"
|
||
echo
|
||
if [ "$AUTO_BUILD" = "1" ] || [ "$AUTO_BUILD" = "yes" ]; then
|
||
PYTHON="$(build_python311)" || exit 1
|
||
else
|
||
echo " 宝塔用户常见原因: pyporject_evn 里的 Python 编译时缺少 openssl-devel。"
|
||
echo
|
||
echo " 解决方式(任选):"
|
||
echo " 1) 自动编译 Python 3.11:"
|
||
echo " AUTO_BUILD_PYTHON=1 ./install.sh"
|
||
echo " 2) 宝塔面板卸载 Python 3.11 后执行:"
|
||
echo " yum install -y openssl openssl-devel && 在面板重装 Python 3.11"
|
||
echo " 3) 手动指定已验证可用的 Python:"
|
||
echo " KEFU_PYTHON=/path/to/python3.11 ./install.sh"
|
||
echo " 4) 查看探测详情:"
|
||
echo " ./install.sh --diagnose"
|
||
exit 1
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
echo "[OK] $("$PYTHON" --version) @ $PYTHON"
|
||
"$PYTHON" -c "import ssl; print('[OK] OpenSSL:', ssl.OPENSSL_VERSION)"
|
||
|
||
# ---------- Node.js ----------
|
||
if ! command -v node &>/dev/null; then
|
||
echo "[警告] 未找到 Node.js,私信签名将无法工作"
|
||
echo " 宝塔: 软件商店 -> Node 版本管理器 -> 安装 Node 18+"
|
||
else
|
||
echo "[OK] Node $(node --version)"
|
||
fi
|
||
echo
|
||
|
||
# ---------- 1. Python venv ----------
|
||
echo "[1/6] Python 虚拟环境与 pip 包..."
|
||
cd "$ROOT/backend"
|
||
|
||
VENV_PY=".venv/bin/python"
|
||
need_new_venv=0
|
||
if [ ! -x "$VENV_PY" ]; then
|
||
need_new_venv=1
|
||
elif ! try_python "$VENV_PY"; then
|
||
echo " 现有 .venv 不符合要求,删除并重建..."
|
||
rm -rf .venv
|
||
need_new_venv=1
|
||
fi
|
||
|
||
if [ "$need_new_venv" -eq 1 ]; then
|
||
echo " 创建 .venv <- $PYTHON"
|
||
"$PYTHON" -m venv .venv
|
||
fi
|
||
|
||
try_python "$VENV_PY" || { echo "[错误] .venv 创建失败"; exit 1; }
|
||
|
||
chmod +x .venv/bin/python .venv/bin/pip 2>/dev/null || true
|
||
.venv/bin/python -m pip install -U pip -q
|
||
.venv/bin/pip install -r requirements.txt
|
||
echo " Python 依赖完成"
|
||
echo
|
||
|
||
# ---------- 2. Playwright ----------
|
||
echo "[2/6] Playwright Chromium..."
|
||
export PLAYWRIGHT_BROWSERS_PATH="${PLAYWRIGHT_BROWSERS_PATH:-$ROOT/playwright-browsers}"
|
||
.venv/bin/python -m playwright install chromium
|
||
if [ "$(uname -s)" = "Linux" ]; then
|
||
.venv/bin/python -m playwright install-deps chromium 2>/dev/null \
|
||
|| echo "[警告] install-deps 失败,可手动以 root 执行"
|
||
fi
|
||
echo " 路径: $PLAYWRIGHT_BROWSERS_PATH"
|
||
echo
|
||
|
||
# ---------- 3. IM Node 依赖 ----------
|
||
echo "[3/6] IM 签名依赖..."
|
||
cd "$ROOT/backend/rpa_engine/douyin_im/static"
|
||
if [ -f package.json ]; then
|
||
npm install --no-fund --no-audit
|
||
echo " jsrsasign 完成"
|
||
fi
|
||
echo
|
||
|
||
# ---------- 4. 前端依赖 ----------
|
||
echo "[4/6] 前端依赖..."
|
||
cd "$ROOT/frontend"
|
||
if [ -f package.json ]; then
|
||
npm install --no-fund --no-audit
|
||
echo " 前端 npm 完成"
|
||
fi
|
||
echo
|
||
|
||
# ---------- 5. 构建前端 ----------
|
||
echo "[5/6] 构建前端..."
|
||
if [ -f "$ROOT/frontend/package.json" ]; then
|
||
cd "$ROOT/frontend" && npm run build
|
||
echo " -> frontend/dist"
|
||
fi
|
||
echo
|
||
|
||
# ---------- 6. 校验 ----------
|
||
echo "[6/6] 校验..."
|
||
cd "$ROOT/backend"
|
||
.venv/bin/python -c "import fastapi, uvicorn, playwright, execjs; print(' 核心包 OK')"
|
||
[ -f "$ROOT/frontend/dist/index.html" ] && echo " 前端静态 OK" \
|
||
|| echo "[警告] 未找到 frontend/dist/index.html"
|
||
|
||
echo
|
||
echo "========================================"
|
||
echo " 安装完成"
|
||
echo
|
||
echo " 启动: ./start_web.sh"
|
||
echo " API: ./start_backend.sh"
|
||
echo " 地址: http://服务器IP:8000"
|
||
echo
|
||
echo " 宝塔 Python 项目:"
|
||
echo " 项目路径: $ROOT/backend"
|
||
echo " 启动命令: bash $ROOT/backend/baota_start.sh"
|
||
echo " 初始化: bash $ROOT/backend/baota_init.sh"
|
||
echo " 详见: deploy/baota.md"
|
||
echo "========================================"
|