#!/usr/bin/env bash # deploy/lib/common.sh — crypto_okx_hub 部署公共函数 (Ubuntu / root / PM2) set -e set -u if [ -n "${BASH_VERSION:-}" ]; then set -o pipefail fi INSTALL_ROOT="${INSTALL_ROOT:-/opt/crypto_okx_hub}" GIT_URL="${GIT_URL:-https://git.bz121.com/dekun/crypto_okx_hub.git}" GIT_BRANCH="${GIT_BRANCH:-main}" BACKUP_ROOT="${BACKUP_ROOT:-/root/backups}" TZ_NAME="${CM_TZ:-Asia/Shanghai}" NODE_MAJOR="${NODE_MAJOR:-20}" APP_PORT="${APP_PORT:-5100}" PM2_APP_NAME="${PM2_APP_NAME:-crypto_okx_hub}" LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DEPLOY_DIR="$(cd "${LIB_DIR}/.." && pwd)" REPO_ROOT="$(cd "${DEPLOY_DIR}/.." && pwd)" PM2_APPS=( crypto_okx_hub ) PM2_APPS_LEGACY=() CONFIG_PATHS=( .env hub.db ) log() { printf '[%s] %s\n' "$(TZ="${TZ_NAME}" date '+%Y-%m-%d %H:%M:%S')" "$*"; } step() { echo ""; log "==> $*"; } die() { echo "错误: $*" >&2 exit 1 } require_root() { if [[ "$(id -u)" -ne 0 ]]; then die "请使用 root 执行(推荐: sudo -i 后运行)" fi } require_ubuntu() { if [[ ! -f /etc/os-release ]]; then log "警告: 未检测到 /etc/os-release,跳过 Ubuntu 版本检查" return 0 fi # shellcheck source=/dev/null source /etc/os-release if [[ "${ID:-}" != "ubuntu" ]]; then log "警告: 当前系统为 ${ID:-unknown},官方仅测试 Ubuntu 22.04/24.04" return 0 fi local ver="${VERSION_ID:-}" if [[ "${ver}" != "22.04" && "${ver}" != "24.04" ]]; then log "警告: Ubuntu ${ver} 未在文档中明确测试,继续执行" fi } detect_server_ip() { local ip="" if command -v hostname >/dev/null 2>&1; then ip="$(hostname -I 2>/dev/null | awk '{print $1}')" fi if [[ -z "${ip}" ]]; then ip="127.0.0.1" fi echo "${ip}" } cm_read() { local __var="$1" local __prompt="$2" local __val="" if [[ -r /dev/tty ]]; then IFS= read -r -p "${__prompt}" __val /dev/null 2>&1; then die "未检测到 apt-get,请手动安装 python3-venv git curl" fi export DEBIAN_FRONTEND=noninteractive apt-get update -qq apt-get install -y python3 python3-pip python3-venv curl git ca-certificates local pyver="" if command -v python3 >/dev/null 2>&1; then pyver="$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')" apt-get install -y "python${pyver}-venv" 2>/dev/null || apt-get install -y python3-venv fi } install_node_pm2() { step "检查 Node.js 与 PM2" if command -v pm2 >/dev/null 2>&1; then log "PM2 已安装: $(pm2 -v)" return 0 fi if ! command -v node >/dev/null 2>&1; then log "安装 Node.js ${NODE_MAJOR}.x ..." curl -fsSL "https://deb.nodesource.com/setup_${NODE_MAJOR}.x" | bash - apt-get install -y nodejs fi log "安装 PM2 ..." npm install -g pm2 log "PM2: $(pm2 -v)" } pip_progress_bar_arg() { echo "on" } pip_upgrade_tools() { local pip_bin="$1" local bar bar="$(pip_progress_bar_arg)" echo " 升级 pip ..." "${pip_bin}" install -U pip setuptools wheel \ --disable-pip-version-check \ --progress-bar "${bar}" } pip_install_requirements() { local pip_bin="$1" local req_file="$2" local label="${3:-依赖}" local bar bar="$(pip_progress_bar_arg)" echo " 安装${label} (下方为 pip 进度) ..." [[ -f "${req_file}" ]] || die "缺少依赖文件: ${req_file}" "${pip_bin}" install -r "${req_file}" \ --disable-pip-version-check \ --retries 5 \ --progress-bar "${bar}" echo " ${label}安装完成" } pm2_app_exists() { local name="$1" if ! command -v pm2 >/dev/null 2>&1; then return 1 fi if ! command -v python3 >/dev/null 2>&1; then pm2 describe "${name}" 2>/dev/null | grep -q "status" return $? fi pm2 jlist 2>/dev/null | python3 -c ' import json,sys name=sys.argv[1] try: apps=json.load(sys.stdin) except Exception: sys.exit(1) sys.exit(0 if any(isinstance(a,dict) and a.get("name")==name for a in (apps or [])) else 1) ' "${name}" } pm2_stop_project_apps() { if ! command -v pm2 >/dev/null 2>&1; then log "未安装 pm2,跳过" return 0 fi local name for name in "${PM2_APPS[@]}" "${PM2_APPS_LEGACY[@]}"; do if pm2_app_exists "${name}"; then log "pm2 stop ${name}" pm2 stop "${name}" 2>/dev/null || true fi done } pm2_delete_project_apps() { if ! command -v pm2 >/dev/null 2>&1; then return 0 fi local name deleted=0 for name in "${PM2_APPS[@]}" "${PM2_APPS_LEGACY[@]}"; do if pm2_app_exists "${name}"; then log "pm2 delete ${name}" pm2 delete "${name}" 2>/dev/null || true deleted=$((deleted + 1)) fi done if [[ "${deleted}" -gt 0 ]]; then pm2 save 2>/dev/null || true fi } pm2_start_app() { step "启动 PM2: ${PM2_APP_NAME}" cd "${REPO_ROOT}" [[ -f "${REPO_ROOT}/ecosystem.config.cjs" ]] || die "缺少 ecosystem.config.cjs" [[ -x "${REPO_ROOT}/.venv/bin/python" ]] || die "缺少 .venv,请先运行 setup_env.sh" if pm2_app_exists "${PM2_APP_NAME}"; then log "已存在,执行 restart --update-env" if ! pm2 restart "${PM2_APP_NAME}" --update-env; then pm2 start ecosystem.config.cjs fi else log "首次启动: pm2 start ecosystem.config.cjs" pm2 start ecosystem.config.cjs fi } pm2_save_startup() { step "PM2 save & startup" pm2 save 2>/dev/null || true if pm2 startup systemd -u root --hp /root 2>/dev/null | grep -q "sudo"; then pm2 startup systemd -u root --hp /root 2>/dev/null | grep "^sudo" | bash || true else pm2 startup 2>/dev/null || true fi } read_app_port() { local env_file="${REPO_ROOT}/.env" local port="${APP_PORT}" if [[ -f "${env_file}" ]]; then local line line="$(grep -E '^[[:space:]]*APP_PORT=' "${env_file}" | tail -n1 || true)" if [[ -n "${line}" ]]; then port="${line#*=}" port="$(echo "${port}" | tr -d '"' | tr -d "'" | xargs)" fi fi echo "${port:-5100}" } verify_deployment() { local ip="${1:-$(detect_server_ip)}" local port port="$(read_app_port)" step "验收" if command -v pm2 >/dev/null 2>&1 && pm2_app_exists "${PM2_APP_NAME}"; then log "PM2: ${PM2_APP_NAME} 已注册" else log "警告: 未检测到 PM2 进程 ${PM2_APP_NAME}" return 1 fi local code code="$(curl -sS -o /dev/null -w '%{http_code}' --connect-timeout 2 --max-time 3 "http://127.0.0.1:${port}/login" 2>/dev/null || echo "000")" log "HTTP /login -> ${code}" log "访问: http://${ip}:${port}/login" return 0 } print_post_install_guide() { local ip="${1:-$(detect_server_ip)}" local port port="$(read_app_port)" echo "" echo "======== 部署完成 ========" echo " 中控: http://${ip}:${port}/login" echo " 账号: 见 .env 的 HUB_USERNAME / HUB_PASSWORD" echo " 路径: ${REPO_ROOT}" echo " 日志: pm2 logs ${PM2_APP_NAME} --lines 100" echo " 菜单: bash ${REPO_ROOT}/deploy/manage.sh" echo " 客户端: https://git.bz121.com/dekun/crypto_okx.git" echo "==========================" echo "" } doctor_report() { step "环境检测" require_ubuntu || true log "用户: $(id -un) uid=$(id -u)" log "路径候选: INSTALL_ROOT=${INSTALL_ROOT}" if command -v python3 >/dev/null 2>&1; then log "Python: $(python3 --version 2>&1)" else log "Python: 未安装" fi if command -v node >/dev/null 2>&1; then log "Node: $(node -v)" else log "Node: 未安装" fi if command -v pm2 >/dev/null 2>&1; then log "PM2: $(pm2 -v)" else log "PM2: 未安装" fi local root="" if root="$(resolve_repo_root 2>/dev/null)"; then log "仓库: ${root} (就绪)" REPO_ROOT="${root}" if [[ -x "${root}/.venv/bin/python" ]]; then log "venv: 存在" else log "venv: 缺失" fi if [[ -f "${root}/.env" ]]; then log ".env: 存在" else log ".env: 缺失" fi if command -v pm2 >/dev/null 2>&1 && pm2_app_exists "${PM2_APP_NAME}"; then log "PM2 进程: ${PM2_APP_NAME} 已注册" else log "PM2 进程: 未注册 ${PM2_APP_NAME}" fi else log "仓库: 未安装到 ${INSTALL_ROOT}" fi }