feat: 新增 Plan B 整目录重装脚本,不影响 setup_env 一键安装

添加 deploy/reinstall.sh 备份 env、克隆、调 setup_env、恢复配置并 PM2 启动;
附带 pm2_start_all.sh 与 hub_settings 清理工具。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-04 22:07:59 +08:00
parent 9f67de3677
commit 4923b32bbe
7 changed files with 624 additions and 3 deletions
+312
View File
@@ -0,0 +1,312 @@
#!/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"