#!/usr/bin/env bash # Plan B:整目录重装 /opt/crypto_monitor(备份 .env → 移走旧目录 → git clone → setup_env → 恢复配置 → PM2) # # 与 deploy/setup_env.sh 分工: # setup_env.sh — 首次 / 日常:建 venv、装依赖、复制 .env.example(一键安装,不变) # reinstall.sh — 生产清库重装:保留密钥与 hub 配置,丢弃旧代码/旧库/脏 PM2 # # 用法(在现有安装目录以 root 执行): # cd /opt/crypto_monitor # bash deploy/reinstall.sh # 交互确认 # bash deploy/reinstall.sh --yes # 跳过确认 # bash deploy/reinstall.sh --dry-run # 仅打印步骤 # # 可选环境变量: # INSTALL_ROOT=/opt/crypto_monitor # GIT_URL=https://git.bz121.com/dekun/crypto_monitor.git # GIT_BRANCH=main # BACKUP_ROOT=/root/backups # set -e set -u if [ -n "${BASH_VERSION:-}" ]; then set -o pipefail fi DEPLOY_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_SOURCE="${DEPLOY_DIR}/reinstall.sh" REPO_ROOT="$(cd "${DEPLOY_DIR}/.." && pwd)" 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="${REINSTALL_TZ:-Asia/Shanghai}" ASSUME_YES=0 DRY_RUN=0 INSTALL_BACKUP_CRON=1 CONFIG_PATHS=( "crypto_monitor_binance/.env" "crypto_monitor_okx/.env" "crypto_monitor_gate/.env" "manual_trading_hub/.env" "manual_trading_hub/hub_settings.json" ) usage() { sed -n '2,18p' "$0" | sed 's/^# \?//' exit "${1:-0}" } while [[ $# -gt 0 ]]; do case "$1" in --yes|-y) ASSUME_YES=1; shift ;; --dry-run) DRY_RUN=1; shift ;; --no-backup-cron) INSTALL_BACKUP_CRON=0; shift ;; -h|--help) usage 0 ;; *) echo "未知参数: $1" >&2; usage 1 ;; esac done log() { printf '[%s] %s\n' "$(TZ="${TZ_NAME}" date '+%Y-%m-%d %H:%M:%S')" "$*"; } step() { echo ""; log "==> $*"; } run() { if [[ "${DRY_RUN}" -eq 1 ]]; then log "[dry-run] $*" return 0 fi log "+ $*" "$@" } confirm() { if [[ "${ASSUME_YES}" -eq 1 || "${DRY_RUN}" -eq 1 ]]; then return 0 fi local msg="$1" read -r -p "${msg} [y/N] " ans [[ "${ans}" == [yY] || "${ans}" == [yY][eE][sS] ]] } resolve_path() { local base="$1" local rel="$2" printf '%s/%s' "${base}" "${rel}" } backup_configs() { local src_root="$1" local dest="$2" mkdir -p "${dest}" local rel copied=0 for rel in "${CONFIG_PATHS[@]}"; do local src src="$(resolve_path "${src_root}" "${rel}")" if [[ -f "${src}" ]]; then mkdir -p "${dest}/$(dirname "${rel}")" if [[ "${DRY_RUN}" -eq 1 ]]; then log "[dry-run] backup ${src} -> ${dest}/${rel}" else cp -a "${src}" "${dest}/${rel}" log "backup ${rel}" fi copied=$((copied + 1)) else log "skip (missing): ${rel}" fi done if [[ "${copied}" -eq 0 ]]; then echo "错误: 未备份到任何配置文件,请检查 ${src_root}" >&2 exit 1 fi if [[ -f "${src_root}/scripts/one_shot_backup_config_before_cleanup.py" ]]; then if [[ "${DRY_RUN}" -eq 1 ]]; then log "[dry-run] python3 scripts/one_shot_backup_config_before_cleanup.py (in ${src_root})" else (cd "${src_root}" && python3 scripts/one_shot_backup_config_before_cleanup.py) || true if compgen -G "${src_root}/backups/one-shot-*" >/dev/null; then cp -a "${src_root}"/backups/one-shot-* "${dest}/" 2>/dev/null || true fi fi fi if [[ "${DRY_RUN}" -eq 0 ]]; then { echo "created_at=${STAMP}" echo "install_root=${INSTALL_ROOT}" echo "old_dir=${OLD_DIR}" echo "git_url=${GIT_URL}" echo "git_branch=${GIT_BRANCH}" echo "script=${SCRIPT_SOURCE}" } >"${dest}/reinstall.manifest" fi } restore_configs() { local backup_dir="$1" local dest_root="$2" local rel for rel in "${CONFIG_PATHS[@]}"; do local src dest src="${backup_dir}/${rel}" dest="$(resolve_path "${dest_root}" "${rel}")" if [[ -f "${src}" ]]; then mkdir -p "$(dirname "${dest}")" if [[ "${DRY_RUN}" -eq 1 ]]; then log "[dry-run] restore ${src} -> ${dest}" else cp -a "${src}" "${dest}" log "restore ${rel}" fi fi done local hub_settings hub_settings="$(resolve_path "${dest_root}" "manual_trading_hub/hub_settings.json")" if [[ -f "${hub_settings}" && "${DRY_RUN}" -eq 0 ]]; then python3 "${dest_root}/deploy/sanitize_hub_settings.py" "${hub_settings}" || true fi } install_instance_backup_cron() { local dest_root="$1" local dir for dir in crypto_monitor_binance crypto_monitor_gate crypto_monitor_okx; do local proj="${dest_root}/${dir}" local inst="${proj}/scripts/install_backup_cron.sh" local data="${proj}/scripts/backup_data.sh" if [[ -f "${inst}" && -f "${data}" ]]; then chmod +x "${inst}" "${data}" run bash "${inst}" fi done } verify_pm2() { log "预期 PM2 进程(7 个): crypto_binance crypto_gate crypto_okx manual-trading-hub manual-agent-*" if [[ "${DRY_RUN}" -eq 1 ]]; then return 0 fi pm2 list || true if pm2 list 2>/dev/null | grep -qiE 'gate_bot|15203'; then log "警告: PM2 列表仍含 gate_bot 相关进程,请 pm2 delete 后 pm2 save" fi } # --- 前置检查 --- if [[ "$(id -u)" -ne 0 ]]; then echo "请使用 root 执行(推荐路径 ${INSTALL_ROOT})" >&2 exit 1 fi if [[ ! -f "${REPO_ROOT}/deploy/setup_env.sh" ]]; then echo "当前脚本不在有效仓库内: ${REPO_ROOT}" >&2 exit 1 fi if [[ "${REPO_ROOT}" != "${INSTALL_ROOT}" ]]; then log "提示: 当前仓库 ${REPO_ROOT} 与 INSTALL_ROOT=${INSTALL_ROOT} 不一致;将备份当前仓库并克隆到 INSTALL_ROOT" fi STAMP="$(TZ="${TZ_NAME}" date +%Y%m%d-%H%M%S)" BACKUP_DIR="${BACKUP_ROOT}/pre-reinstall-${STAMP}" OLD_DIR="${INSTALL_ROOT}.old.${STAMP}" SRC_ROOT="${REPO_ROOT}" if [[ -d "${INSTALL_ROOT}" && "${REPO_ROOT}" != "${INSTALL_ROOT}" ]]; then SRC_ROOT="${INSTALL_ROOT}" fi step "计划" echo " 备份目录: ${BACKUP_DIR}" echo " 配置来源: ${SRC_ROOT}" echo " 旧目录移走: ${OLD_DIR}" echo " 新克隆: ${GIT_URL} (${GIT_BRANCH}) -> ${INSTALL_ROOT}" echo " 环境: deploy/setup_env.sh --skip-env-copy --recreate-venv --skip-pm2" echo "" echo " 将停止并 delete 全部 PM2 进程;不备份 crypto.db / hub data / 图片。" if ! confirm "确认执行 Plan B 整目录重装?"; then log "已取消" exit 0 fi # --- 1. 备份 --- step "备份配置到 ${BACKUP_DIR}" backup_configs "${SRC_ROOT}" "${BACKUP_DIR}" # --- 2. 停 PM2 --- step "停止并清空 PM2" if command -v pm2 >/dev/null 2>&1; then run pm2 stop all || true run pm2 delete all || true else log "未安装 pm2,跳过" fi # --- 3. 移走旧目录 --- step "移走旧安装 ${INSTALL_ROOT} -> ${OLD_DIR}" if [[ -d "${INSTALL_ROOT}" ]]; then if [[ "${DRY_RUN}" -eq 1 ]]; then log "[dry-run] mv ${INSTALL_ROOT} ${OLD_DIR}" else mv "${INSTALL_ROOT}" "${OLD_DIR}" fi else log "目标目录不存在,跳过 mv" fi # --- 4. 克隆 --- step "git clone" if [[ "${DRY_RUN}" -eq 1 ]]; then log "[dry-run] git clone -b ${GIT_BRANCH} ${GIT_URL} ${INSTALL_ROOT}" else git clone -b "${GIT_BRANCH}" "${GIT_URL}" "${INSTALL_ROOT}" fi # --- 5. setup_env(一键安装逻辑,不复制 .env)--- step "重建 Python 虚拟环境 (setup_env.sh)" if [[ "${DRY_RUN}" -eq 1 ]]; then log "[dry-run] bash ${INSTALL_ROOT}/deploy/setup_env.sh --skip-env-copy --recreate-venv --skip-pm2" else bash "${INSTALL_ROOT}/deploy/setup_env.sh" --skip-env-copy --recreate-venv --skip-pm2 fi # --- 6. 恢复配置 --- step "恢复 .env 与 hub_settings.json" restore_configs "${BACKUP_DIR}" "${INSTALL_ROOT}" # --- 7. PM2 启动 --- step "PM2 启动全部进程" if command -v pm2 >/dev/null 2>&1; then run bash "${INSTALL_ROOT}/deploy/pm2_start_all.sh" run pm2 save else log "未安装 pm2;请手动: bash ${INSTALL_ROOT}/deploy/pm2_start_all.sh" fi # --- 8. 定时备份 cron(可选)--- if [[ "${INSTALL_BACKUP_CRON}" -eq 1 ]]; then step "安装三所每日备份 cron" install_instance_backup_cron "${INSTALL_ROOT}" fi # --- 完成 --- step "完成" verify_pm2 echo "" echo "备份: ${BACKUP_DIR}" echo "旧目录(确认无误后可删): ${OLD_DIR}" echo "" echo "验收建议:" echo " pm2 list" echo " curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:5100/" echo " 浏览器打开中控 /monitor,确认三所 LINK 正常" echo "" echo "回滚(未删旧目录时):" echo " pm2 delete all" echo " rm -rf ${INSTALL_ROOT}" echo " mv ${OLD_DIR} ${INSTALL_ROOT}" echo " cp -a ${BACKUP_DIR}/*/ ${INSTALL_ROOT}/ # 若需恢复配置" echo " bash ${INSTALL_ROOT}/deploy/pm2_start_all.sh"