#!/usr/bin/env bash # deploy/lib/common.sh — 部署管理器公共函数 set -e set -u if [ -n "${BASH_VERSION:-}" ]; then set -o pipefail fi INSTALL_ROOT="${INSTALL_ROOT:-/opt/crypto_monitor}" GIT_URL="${GIT_URL:-https://git.bz121.com/dekun/crypto_monitor.git}" GIT_BRANCH="${GIT_BRANCH:-main}" BACKUP_ROOT="${BACKUP_ROOT:-/root/backups}" TZ_NAME="${CM_TZ:-Asia/Shanghai}" NODE_MAJOR="${NODE_MAJOR:-20}" LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DEPLOY_DIR="$(cd "${LIB_DIR}/.." && pwd)" REPO_ROOT="$(cd "${DEPLOY_DIR}/.." && pwd)" PM2_APPS=( crypto_binance crypto_gate crypto_okx manual-trading-hub manual-agent-binance manual-agent-okx manual-agent-gate ) CONFIG_PATHS=( crypto_monitor_binance/.env crypto_monitor_okx/.env crypto_monitor_gate/.env manual_trading_hub/.env manual_trading_hub/hub_settings.json ) INSTANCE_DIRS=( crypto_monitor_binance crypto_monitor_gate crypto_monitor_okx manual_trading_hub ) 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}" } confirm_yes() { local msg="$1" local ans="" read -r -p "${msg} [y/N] " ans [[ "${ans}" == [yY] || "${ans}" == [yY][eE][sS] ]] } confirm_uninstall() { local ans="" echo "此操作将停止全部 PM2 进程并移走安装目录." read -r -p "输入 UNINSTALL 确认卸载: " ans [[ "${ans}" == "UNINSTALL" ]] } repo_ready() { [[ -f "${1}/deploy/setup_env.sh" && -f "${1}/deploy/manage.sh" ]] } resolve_repo_root() { if repo_ready "${INSTALL_ROOT}"; then echo "${INSTALL_ROOT}" return 0 fi if repo_ready "${REPO_ROOT}"; then echo "${REPO_ROOT}" return 0 fi return 1 } install_log_file() { local stamp stamp="$(TZ="${TZ_NAME}" date +%Y%m%d-%H%M%S)" mkdir -p "${BACKUP_ROOT}/deploy-logs" echo "${BACKUP_ROOT}/deploy-logs/manage-${stamp}.log" } backup_configs_to() { local src_root="$1" local dest="$2" mkdir -p "${dest}" local rel copied=0 for rel in "${CONFIG_PATHS[@]}"; do local src="${src_root}/${rel}" if [[ -f "${src}" ]]; then mkdir -p "${dest}/$(dirname "${rel}")" cp -a "${src}" "${dest}/${rel}" log "backup ${rel}" copied=$((copied + 1)) fi done if [[ "${copied}" -eq 0 ]]; then log "提示: 未找到可备份的配置文件" fi } install_system_packages() { step "安装系统依赖" if ! command -v apt-get >/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)" } install_backup_cron_all() { step "安装三所每日备份 cron" local dir for dir in crypto_monitor_binance crypto_monitor_gate crypto_monitor_okx; do local inst="${REPO_ROOT}/${dir}/scripts/install_backup_cron.sh" if [[ -f "${inst}" ]]; then chmod +x "${inst}" bash "${inst}" || log "警告: ${dir} cron 安装失败" fi done } remove_backup_cron_all() { step "移除三所备份 cron" local tmp removed=0 tmp="$(mktemp)" if ! crontab -l 2>/dev/null >"${tmp}"; then rm -f "${tmp}" return 0 fi local filtered filtered="$(grep -vF "backup_data.sh" "${tmp}" || true)" if [[ "${filtered}" != "$(cat "${tmp}")" ]]; then printf '%s\n' "${filtered}" | awk ' BEGIN { tz = 0 } /^CRON_TZ=Asia\/Shanghai$/ { if (tz++) next } { print } ' | crontab - removed=1 fi rm -f "${tmp}" if [[ "${removed}" -eq 1 ]]; then log "已移除 backup_data.sh 相关 cron" 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 } pm2_count_online() { local name online=0 if ! command -v pm2 >/dev/null 2>&1; then echo "0" return 0 fi for name in "${PM2_APPS[@]}"; do if pm2 pid "${name}" >/dev/null 2>&1; then online=$((online + 1)) fi done echo "${online}" } is_deployed() { local root="$1" [[ -x "${root}/crypto_monitor_binance/.venv/bin/python" ]] \ || [[ -x "${root}/manual_trading_hub/.venv/bin/python" ]] } check_http() { local url="$1" local code code="$(curl -sS -o /dev/null -w '%{http_code}' --connect-timeout 5 "${url}" 2>/dev/null || echo "000")" [[ "${code}" == "200" || "${code}" == "302" || "${code}" == "301" ]] } verify_deployment() { local ip="${1:-$(detect_server_ip)}" local ok=1 local online total step "部署验收" total="${#PM2_APPS[@]}" online="$(pm2_count_online)" if [[ "${online}" -eq "${total}" ]]; then echo " [✓] PM2 进程 ${online}/${total} online" else echo " [✗] PM2 进程 ${online}/${total} online" ok=0 pm2 list 2>/dev/null || true fi local checks=( "中控:http://127.0.0.1:5100/" "Binance:http://127.0.0.1:5001/" "Gate:http://127.0.0.1:5000/" "OKX:http://127.0.0.1:5004/" ) local item label url for item in "${checks[@]}"; do label="${item%%:*}" url="${item#*:}" if check_http "${url}"; then echo " [✓] ${label} 可访问" else echo " [✗] ${label} 不可访问 (${url})" ok=0 fi done if [[ "${ok}" -eq 1 ]]; then echo "" log "验收通过: 进程都在 + 页面可打开" return 0 fi echo "" log "验收未完全通过,可执行 pm2 logs <进程名> --lines 30 排查" return 1 } print_post_install_guide() { local ip="${1:-$(detect_server_ip)}" cat <