From 4923b32bbecbb1bbdbc14633572c31e8e2344ed4 Mon Sep 17 00:00:00 2001 From: dekun Date: Sat, 4 Jul 2026 22:07:59 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20Plan=20B=20?= =?UTF-8?q?=E6=95=B4=E7=9B=AE=E5=BD=95=E9=87=8D=E8=A3=85=E8=84=9A=E6=9C=AC?= =?UTF-8?q?=EF=BC=8C=E4=B8=8D=E5=BD=B1=E5=93=8D=20setup=5Fenv=20=E4=B8=80?= =?UTF-8?q?=E9=94=AE=E5=AE=89=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加 deploy/reinstall.sh 备份 env、克隆、调 setup_env、恢复配置并 PM2 启动; 附带 pm2_start_all.sh 与 hub_settings 清理工具。 Co-authored-by: Cursor --- deploy/README.md | 4 + deploy/pm2_start_all.sh | 41 ++++ deploy/reinstall-plan-b.md | 112 ++++++++++ deploy/reinstall.sh | 312 ++++++++++++++++++++++++++++ deploy/sanitize_hub_settings.py | 100 +++++++++ docs/ubuntu-server.md | 17 +- tests/test_sanitize_hub_settings.py | 41 ++++ 7 files changed, 624 insertions(+), 3 deletions(-) create mode 100644 deploy/pm2_start_all.sh create mode 100644 deploy/reinstall-plan-b.md create mode 100644 deploy/reinstall.sh create mode 100644 deploy/sanitize_hub_settings.py create mode 100644 tests/test_sanitize_hub_settings.py diff --git a/deploy/README.md b/deploy/README.md index dadcdbe..579aaf4 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -35,6 +35,8 @@ bash deploy/setup_env.sh --skip-pm2 # 不尝试安装 pm2 bash deploy/setup_env.sh --skip-env-copy # 不复制 .env.example ``` +**整目录重装**(保留 `.env`、清库、去脏 PM2)见 **[reinstall-plan-b.md](./reinstall-plan-b.md)**,执行 `bash deploy/reinstall.sh`。与 `setup_env.sh` 独立,不影响首次一键安装。 + 若在其它环境编辑过脚本后报 `pipefail` 错误,先转 LF: ```bash @@ -68,6 +70,8 @@ sed -i 's/\r$//' deploy/setup_env.sh pm2 save ``` + 或一条命令:`bash deploy/pm2_start_all.sh` + 3. 三所 `.env` 同步脚本见 **[docs/env-sync-scripts.md](../docs/env-sync-scripts.md)**。 --- diff --git a/deploy/pm2_start_all.sh b/deploy/pm2_start_all.sh new file mode 100644 index 0000000..0a98e6e --- /dev/null +++ b/deploy/pm2_start_all.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# 按推荐顺序启动三所 Flask + 中控 hub/三 agent(PM2)。 +# 用法(仓库根或任意目录): +# bash deploy/pm2_start_all.sh +# +# 与 deploy/setup_env.sh 独立:setup_env 只建 venv;本脚本负责 PM2 启动。 +set -e +set -u +if [ -n "${BASH_VERSION:-}" ]; then + set -o pipefail +fi + +DEPLOY_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${DEPLOY_DIR}/.." && pwd)" + +start_one() { + local dir_name="$1" + local proj="${REPO_ROOT}/${dir_name}" + local eco="${proj}/ecosystem.config.cjs" + if [[ ! -f "${eco}" ]]; then + echo "skip (no ecosystem): ${dir_name}" >&2 + return 0 + fi + echo "==> pm2 start ${dir_name}" + (cd "${proj}" && pm2 start ecosystem.config.cjs) +} + +if ! command -v pm2 >/dev/null 2>&1; then + echo "未找到 pm2,请先安装 Node.js 与 pm2(见 docs/ubuntu-server.md)" >&2 + exit 1 +fi + +start_one crypto_monitor_binance +start_one crypto_monitor_gate +start_one crypto_monitor_okx +start_one manual_trading_hub + +pm2 save 2>/dev/null || true +echo "" +echo "PM2 进程:" +pm2 list diff --git a/deploy/reinstall-plan-b.md b/deploy/reinstall-plan-b.md new file mode 100644 index 0000000..d7ea9d7 --- /dev/null +++ b/deploy/reinstall-plan-b.md @@ -0,0 +1,112 @@ +# Plan B:整目录重装(生产清库) + +适用于:**保留三所 `.env` 与中控配置,丢弃旧代码、旧 SQLite、脏 PM2 名单**(例如移除 `gate_bot` 后偶发重启)。 + +与 **[setup_env.sh](./setup_env.sh)** 的关系: + +| 脚本 | 用途 | +|------|------| +| `setup_env.sh` | **首次安装 / 日常**:建 venv、装依赖、从 `.env.example` 复制(**不变**) | +| `reinstall.sh` | **整目录重装**:备份 → 移走旧目录 → `git clone` → 调 `setup_env.sh` → 恢复配置 → PM2 | + +--- + +## 一键执行(推荐) + +在现有服务器安装上以 **root** 执行: + +```bash +cd /opt/crypto_monitor +bash deploy/reinstall.sh --yes +``` + +交互确认(不加 `--yes`): + +```bash +bash deploy/reinstall.sh +``` + +仅预览步骤: + +```bash +bash deploy/reinstall.sh --dry-run +``` + +--- + +## 脚本会做什么 + +1. 备份到 **`/root/backups/pre-reinstall-YYYYMMDD-HHMMSS/`** + - 三所 `crypto_monitor_*/.env` + - `manual_trading_hub/.env` + - `manual_trading_hub/hub_settings.json`(若有) + - 可选:仓库内 `one_shot` 备份目录 +2. **`pm2 stop all` + `pm2 delete all`** +3. **`mv /opt/crypto_monitor /opt/crypto_monitor.old.时间戳`** +4. **`git clone`** 到 `/opt/crypto_monitor`(默认 `main`) +5. **`bash deploy/setup_env.sh --skip-env-copy --recreate-venv --skip-pm2`** +6. 从备份 **恢复 `.env` / `hub_settings.json`** +7. **`deploy/sanitize_hub_settings.py`** 去掉 `gate_bot` / 第四账户 +8. **`deploy/pm2_start_all.sh`** + `pm2 save` +9. 为三所重装 **每日 0 点备份 cron**(可用 `--no-backup-cron` 跳过) + +**不会备份/恢复**:`crypto.db`、hub `data/*.db`、`static/images`(符合「全新启动」)。 + +**不会动**:宝塔/Nginx 反代、SSH SOCKS 隧道(tmux 内)。 + +--- + +## 环境变量 + +```bash +export INSTALL_ROOT=/opt/crypto_monitor +export GIT_URL=https://git.bz121.com/dekun/crypto_monitor.git +export GIT_BRANCH=main +export BACKUP_ROOT=/root/backups +bash deploy/reinstall.sh --yes +``` + +--- + +## 验收 + +```bash +pm2 list +# 应有 7 个: crypto_binance crypto_gate crypto_okx manual-trading-hub manual-agent-* + +curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:5100/ +``` + +浏览器:中控 `/monitor` 登录,三所 LINK 绿,监控区为空库。 + +--- + +## 回滚 + +旧目录默认保留为 `/opt/crypto_monitor.old.时间戳`,配置在 `/root/backups/pre-reinstall-*`: + +```bash +pm2 delete all +rm -rf /opt/crypto_monitor +mv /opt/crypto_monitor.old.XXXXXXXX /opt/crypto_monitor +bash /opt/crypto_monitor/deploy/pm2_start_all.sh +``` + +确认新环境稳定后再删 `.old.*` 目录。 + +--- + +## 辅助脚本 + +| 文件 | 说明 | +|------|------| +| [pm2_start_all.sh](./pm2_start_all.sh) | 按顺序 PM2 启动三所 + hub(setup_env 之后手动用) | +| [sanitize_hub_settings.py](./sanitize_hub_settings.py) | 清理 `hub_settings.json` 中 gate_bot 条目 | + +--- + +## 相关文档 + +- [deploy/README.md](./README.md) — 首次一键安装 +- [docs/ubuntu-server.md](../docs/ubuntu-server.md) — Python / PM2 版本 +- [备份与恢复.md](../备份与恢复.md) — 日常 DB 备份 cron diff --git a/deploy/reinstall.sh b/deploy/reinstall.sh new file mode 100644 index 0000000..5ac8738 --- /dev/null +++ b/deploy/reinstall.sh @@ -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" diff --git a/deploy/sanitize_hub_settings.py b/deploy/sanitize_hub_settings.py new file mode 100644 index 0000000..557ae91 --- /dev/null +++ b/deploy/sanitize_hub_settings.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +"""重装后清理 hub_settings.json 中已废弃的 gate_bot / 第四账户条目。""" +from __future__ import annotations + +import json +import sys +from pathlib import Path + +DROP_KEYS = frozenset({"gate_bot", "gate-bot"}) +DROP_MARKERS = ( + "gate_bot", + "crypto_monitor_gate_bot", + "15203", + ":5002", +) + + +def _text(*parts: object) -> str: + return " ".join(str(p) for p in parts if p is not None).lower() + + +def should_drop(ex: dict) -> bool: + key = str(ex.get("key") or "").strip().lower() + if key in DROP_KEYS: + return True + blob = _text( + ex.get("name"), + ex.get("flask_url"), + ex.get("agent_url"), + ex.get("review_url"), + ) + if any(m in blob for m in DROP_MARKERS): + return True + ex_id = str(ex.get("id") or "").strip() + if ex_id == "3" and key not in ("gate", ""): + return True + return False + + +def sanitize_settings(data: dict) -> tuple[dict, list[str]]: + removed: list[str] = [] + exchanges = data.get("exchanges") + if not isinstance(exchanges, list): + return data, removed + + kept: list[dict] = [] + seen_keys: set[str] = set() + for ex in exchanges: + if not isinstance(ex, dict): + continue + key = str(ex.get("key") or "").strip().lower() + label = f"id={ex.get('id')} key={key} name={ex.get('name')}" + if should_drop(ex): + removed.append(label) + continue + if key and key in seen_keys: + removed.append(f"duplicate {label}") + continue + if key: + seen_keys.add(key) + kept.append(ex) + + out = dict(data) + out["exchanges"] = kept + return out, removed + + +def main(argv: list[str] | None = None) -> int: + args = argv if argv is not None else sys.argv[1:] + if len(args) != 1: + print("用法: python deploy/sanitize_hub_settings.py ", file=sys.stderr) + return 2 + + path = Path(args[0]) + if not path.is_file(): + print(f"文件不存在: {path}", file=sys.stderr) + return 1 + + try: + data = json.loads(path.read_text(encoding="utf-8")) + except json.JSONDecodeError as e: + print(f"JSON 解析失败: {e}", file=sys.stderr) + return 1 + if not isinstance(data, dict): + print("hub_settings.json 根节点必须是 object", file=sys.stderr) + return 1 + + cleaned, removed = sanitize_settings(data) + if removed: + path.write_text(json.dumps(cleaned, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + print("已移除条目:") + for line in removed: + print(f" - {line}") + else: + print("无需修改(未发现 gate_bot / 第四账户)") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/docs/ubuntu-server.md b/docs/ubuntu-server.md index c7285f5..016e900 100644 --- a/docs/ubuntu-server.md +++ b/docs/ubuntu-server.md @@ -102,13 +102,24 @@ pm2 restart all # 或按进程名 restart | 目录 | ecosystem 内典型名称 | |------|---------------------| -| `crypto_monitor_binance` | `crypto-monitor-binance` | -| `crypto_monitor_gate` | `crypto-monitor-gate` | -| `crypto_monitor_okx` | `crypto-monitor-okx` | +| `crypto_monitor_binance` | `crypto_binance` | +| `crypto_monitor_gate` | `crypto_gate` | +| `crypto_monitor_okx` | `crypto_okx` | | `manual_trading_hub` | `manual-trading-hub`、`manual-agent-*` | 以各目录 **`ecosystem.config.cjs`** 为准。 +### 3.4 整目录重装(清库 / 去脏 PM2) + +保留 `.env`、丢弃旧库与旧 PM2 名单时,见 **[deploy/reinstall-plan-b.md](../deploy/reinstall-plan-b.md)**: + +```bash +cd /opt/crypto_monitor +bash deploy/reinstall.sh --yes +``` + +首次安装仍只用 `deploy/setup_env.sh`,二者互不影响。 + --- ## 4. 目录与权限 diff --git a/tests/test_sanitize_hub_settings.py b/tests/test_sanitize_hub_settings.py new file mode 100644 index 0000000..a0e108e --- /dev/null +++ b/tests/test_sanitize_hub_settings.py @@ -0,0 +1,41 @@ +"""deploy/sanitize_hub_settings.py 单元测试。""" +from __future__ import annotations + +import json +import sys +from pathlib import Path + +REPO = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(REPO / "deploy")) + +from sanitize_hub_settings import sanitize_settings # noqa: E402 + + +def test_drops_gate_bot_and_keeps_gate(): + raw = { + "exchanges": [ + {"id": "0", "key": "binance", "name": "币安", "agent_url": "http://127.0.0.1:15200"}, + {"id": "3", "key": "gate_bot", "name": "Gate bot", "agent_url": "http://127.0.0.1:15203"}, + {"id": "2", "key": "gate", "name": "Gate", "flask_url": "http://127.0.0.1:5000"}, + ] + } + cleaned, removed = sanitize_settings(raw) + keys = [x["key"] for x in cleaned["exchanges"]] + assert keys == ["binance", "gate"] + assert len(removed) == 1 + + +def test_drops_port_5002_legacy(): + raw = { + "exchanges": [ + { + "id": "3", + "key": "legacy", + "name": "crypto_monitor_gate_bot", + "flask_url": "http://127.0.0.1:5002", + }, + ] + } + cleaned, removed = sanitize_settings(raw) + assert cleaned["exchanges"] == [] + assert removed