Initialize crypto_monitor_user (user edition) from monitor codebase.
Retarget git remote, install path, and deploy docs from crypto_monitor to crypto_monitor_user. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
# 环境一键部署(Ubuntu / root /opt)
|
||||
|
||||
在 **`/opt/crypto_monitor_user`** 下以 **root** 部署三所 Flask + 中控 hub/agent,使用 **PM2** 常驻.
|
||||
|
||||
完整系统要求见 **[docs/ubuntu-server.md](../docs/ubuntu-server.md)**.
|
||||
|
||||
---
|
||||
|
||||
## 一键部署管理器(推荐)
|
||||
|
||||
新服务器**无需先 clone**,一条命令进入菜单:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://git.bz121.com/dekun/crypto_monitor_user/raw/branch/main/deploy/manage.sh | bash
|
||||
```
|
||||
|
||||
已安装机器:
|
||||
|
||||
```bash
|
||||
bash /opt/crypto_monitor_user/deploy/manage.sh
|
||||
```
|
||||
|
||||
### 菜单
|
||||
|
||||
| 选项 | 功能 |
|
||||
|------|------|
|
||||
| **1) 一键部署(全套)** | 装系统依赖 + Node/PM2 + venv + 自动生成密钥 + 启动 7 进程(三所+中控) |
|
||||
| **4) 仅 OKX 实例** | 只部署/启动 `crypto_okx`(不含中控/agent) |
|
||||
| **5) 仅 Binance 实例** | 只部署/启动 `crypto_binance` |
|
||||
| **6) 仅 Gate 实例** | 只部署/启动 `crypto_gate` |
|
||||
| **2) 一键卸载** | 备份 `.env` / `hub_settings.json` → 停 PM2 → 移走目录 |
|
||||
| **3) 更新** | 快速更新 / 依赖更新 / 深度重装 |
|
||||
| **0) 退出** | |
|
||||
|
||||
单所模式仍 clone 整仓到 `/opt/crypto_monitor_user`(共用 `lib/`),但 `setup_env.sh --only <所>` 只建该所 venv,且 PM2 只起对应 Flask.
|
||||
|
||||
### 部署完成后
|
||||
|
||||
脚本自动验收(PM2 7 进程 + 页面可访问),并提示:
|
||||
|
||||
- 登录账号: **admin**
|
||||
- 登录密码: **admin123**
|
||||
- 浏览器配置: 各所 **env 配置**(API,风控) + 中控 **系统设置**
|
||||
|
||||
**无需 SSH 编辑 `.env` 填 API**;密钥由 `bootstrap_deploy_secrets.py` 自动生成.
|
||||
|
||||
| 地址 | 端口 |
|
||||
|------|------|
|
||||
| 中控 | 5100 |
|
||||
| Binance | 5001 |
|
||||
| Gate | 5000 |
|
||||
| OKX | 5004 |
|
||||
|
||||
agent 默认 `127.0.0.1:15200/15201/15202`,由代码内置,一般无需改.
|
||||
|
||||
---
|
||||
|
||||
## 脚本结构
|
||||
|
||||
```
|
||||
deploy/
|
||||
├── manage.sh # 入口(自举 + 菜单)
|
||||
├── lib/
|
||||
│ ├── common.sh # 公共函数,验收
|
||||
│ ├── install.sh # 一键部署
|
||||
│ ├── uninstall.sh # 一键卸载
|
||||
│ └── update.sh # 更新子菜单
|
||||
├── setup_env.sh # venv + 依赖(被 install 调用)
|
||||
├── pm2_start_all.sh # 启动 7 进程
|
||||
├── pull_and_restart.sh # 快速更新(被 update 调用)
|
||||
└── reinstall.sh # 深度重装(被 update 调用)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 前置条件
|
||||
|
||||
- **Ubuntu 22.04 / 24.04**,用户 **root**
|
||||
- 能 `git clone` 仓库到 `/opt/crypto_monitor_user`
|
||||
|
||||
---
|
||||
|
||||
## 分步安装(仍可用)
|
||||
|
||||
若不使用 `manage.sh`,可手动:
|
||||
|
||||
```bash
|
||||
cd /opt
|
||||
git clone https://git.bz121.com/dekun/crypto_monitor_user.git crypto_monitor_user
|
||||
cd /opt/crypto_monitor_user
|
||||
bash deploy/setup_env.sh --install-system-deps
|
||||
bash deploy/pm2_start_all.sh
|
||||
pm2 save && pm2 startup
|
||||
```
|
||||
|
||||
`setup_env.sh` 常用参数:
|
||||
|
||||
```bash
|
||||
bash deploy/setup_env.sh --only binance,gate # 仅部分子项目
|
||||
bash deploy/setup_env.sh --recreate-venv # 重建虚拟环境
|
||||
bash deploy/setup_env.sh --skip-pm2 # 不尝试安装 pm2
|
||||
bash deploy/setup_env.sh --skip-env-copy # 不复制 .env.example
|
||||
```
|
||||
|
||||
**整目录重装**(保留 `.env`,清库)见 **[reinstall-plan-b.md](./reinstall-plan-b.md)**:
|
||||
|
||||
```bash
|
||||
bash deploy/reinstall.sh --yes
|
||||
```
|
||||
|
||||
若在其它环境编辑过脚本后报 `pipefail` 错误,先转 LF:
|
||||
|
||||
```bash
|
||||
sed -i 's/\r$//' deploy/manage.sh deploy/lib/*.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## setup_env.sh 会做什么
|
||||
|
||||
| 步骤 | 说明 |
|
||||
|------|------|
|
||||
| 检查 Python | 需要 **3.10+** |
|
||||
| `crypto_monitor_*` | 各目录 `.venv` + `pip install -r ../requirements.txt` |
|
||||
| `manual_trading_hub` | 独立 `requirements.txt` |
|
||||
| `.env` | 不存在则从 `.env.example` 复制 |
|
||||
| 部署密钥 | `python3 scripts/bootstrap_deploy_secrets.py`(不覆盖已有值) |
|
||||
| 目录 | `static/images`,`static/images/order_charts` |
|
||||
| PM2 | 已装 Node 时 `npm install -g pm2` |
|
||||
|
||||
---
|
||||
|
||||
## 环境变量(可选)
|
||||
|
||||
```bash
|
||||
INSTALL_ROOT=/opt/crypto_monitor_user
|
||||
GIT_URL=https://git.bz121.com/dekun/crypto_monitor_user.git
|
||||
GIT_BRANCH=main
|
||||
BACKUP_ROOT=/root/backups
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 依赖说明
|
||||
|
||||
- 三个监控子项目共用根目录 **[requirements.txt](../requirements.txt)**.
|
||||
- 走 SOCKS 须 **PySocks**(已包含在 requirements 中).
|
||||
@@ -0,0 +1,509 @@
|
||||
#!/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_user}"
|
||||
GIT_URL="${GIT_URL:-https://git.bz121.com/dekun/crypto_monitor_user.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
|
||||
)
|
||||
|
||||
# 历史进程名(仅 stop/delete 时兼容,不计入验收)
|
||||
PM2_APPS_LEGACY=(
|
||||
crypto-monitor-binance
|
||||
crypto-monitor-gate
|
||||
crypto-monitor-okx
|
||||
)
|
||||
|
||||
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=""
|
||||
cm_read ans "${msg} [y/N] "
|
||||
[[ "${ans}" == [yY] || "${ans}" == [yY][eE][sS] ]]
|
||||
}
|
||||
|
||||
confirm_uninstall() {
|
||||
local ans=""
|
||||
echo "此操作将停止全部 PM2 进程并移走安装目录."
|
||||
cm_read ans "输入 UNINSTALL 确认卸载: "
|
||||
[[ "${ans}" == "UNINSTALL" ]]
|
||||
}
|
||||
|
||||
# curl | bash 时 stdin 是管道,须从 /dev/tty 读取用户输入
|
||||
cm_read() {
|
||||
local __var="$1"
|
||||
local __prompt="$2"
|
||||
local __val=""
|
||||
if [[ -r /dev/tty ]]; then
|
||||
IFS= read -r -p "${__prompt}" __val </dev/tty || true
|
||||
else
|
||||
IFS= read -r -p "${__prompt}" __val || true
|
||||
fi
|
||||
printf -v "${__var}" '%s' "${__val}"
|
||||
}
|
||||
|
||||
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)"
|
||||
}
|
||||
|
||||
# pip 安装时显示进度条(避免 -q 静默导致用户以为卡死)
|
||||
# pip>=26 已移除 ascii,仅支持 auto|on|off|raw
|
||||
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}安装完成"
|
||||
}
|
||||
|
||||
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_app_exists() {
|
||||
local name="$1"
|
||||
pm2 pid "${name}" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# 仅操作本项目 PM2 进程,不影响服务器上其它应用
|
||||
pm2_stop_project_apps() {
|
||||
if ! command -v pm2 >/dev/null 2>&1; then
|
||||
log "未安装 pm2,跳过"
|
||||
return 0
|
||||
fi
|
||||
local name stopped=0
|
||||
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
|
||||
stopped=$((stopped + 1))
|
||||
fi
|
||||
done
|
||||
if [[ "${stopped}" -eq 0 ]]; then
|
||||
log "未发现本项目 PM2 进程(其它 PM2 不受影响)"
|
||||
fi
|
||||
}
|
||||
|
||||
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_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}/crypto_monitor_gate/.venv/bin/python" ]] \
|
||||
|| [[ -x "${root}/crypto_monitor_okx/.venv/bin/python" ]] \
|
||||
|| [[ -x "${root}/manual_trading_hub/.venv/bin/python" ]]
|
||||
}
|
||||
|
||||
# 单所实例: key -> 目录 / PM2 名 / 端口 / 展示名
|
||||
exchange_dir() {
|
||||
case "$1" in
|
||||
okx) echo "crypto_monitor_okx" ;;
|
||||
binance) echo "crypto_monitor_binance" ;;
|
||||
gate) echo "crypto_monitor_gate" ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
exchange_pm2_name() {
|
||||
case "$1" in
|
||||
okx) echo "crypto_okx" ;;
|
||||
binance) echo "crypto_binance" ;;
|
||||
gate) echo "crypto_gate" ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
exchange_http_port() {
|
||||
case "$1" in
|
||||
okx) echo "5004" ;;
|
||||
binance) echo "5001" ;;
|
||||
gate) echo "5000" ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
exchange_label() {
|
||||
case "$1" in
|
||||
okx) echo "OKX" ;;
|
||||
binance) echo "Binance" ;;
|
||||
gate) echo "Gate" ;;
|
||||
*) echo "$1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
normalize_exchange_key() {
|
||||
local k
|
||||
k="$(echo "${1:-}" | tr '[:upper:]' '[:lower:]' | xargs)"
|
||||
case "${k}" in
|
||||
okx|binance|gate) echo "${k}" ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
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 mode="${2:-all}"
|
||||
local ok=1
|
||||
local online total
|
||||
local ex_key=""
|
||||
|
||||
step "部署验收"
|
||||
if [[ "${mode}" == "all" || -z "${mode}" ]]; then
|
||||
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
|
||||
else
|
||||
if ! ex_key="$(normalize_exchange_key "${mode}")"; then
|
||||
echo " [✗] 未知验收模式: ${mode}"
|
||||
return 1
|
||||
fi
|
||||
local pm2_name port label
|
||||
pm2_name="$(exchange_pm2_name "${ex_key}")"
|
||||
port="$(exchange_http_port "${ex_key}")"
|
||||
label="$(exchange_label "${ex_key}")"
|
||||
if pm2_app_exists "${pm2_name}"; then
|
||||
echo " [✓] PM2 ${pm2_name} 已注册"
|
||||
else
|
||||
echo " [✗] PM2 缺少进程 ${pm2_name}"
|
||||
ok=0
|
||||
pm2 list 2>/dev/null || true
|
||||
fi
|
||||
if check_http "http://127.0.0.1:${port}/"; then
|
||||
echo " [✓] ${label} 可访问 (http://127.0.0.1:${port}/)"
|
||||
else
|
||||
echo " [✗] ${label} 不可访问 (http://127.0.0.1:${port}/)"
|
||||
ok=0
|
||||
fi
|
||||
fi
|
||||
|
||||
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 <<EOF
|
||||
|
||||
═══════════════════════════════════════════
|
||||
✅ 部署完成
|
||||
|
||||
中控: http://${ip}:5100
|
||||
Binance: http://${ip}:5001
|
||||
Gate: http://${ip}:5000
|
||||
OKX: http://${ip}:5004
|
||||
|
||||
登录账号: admin
|
||||
登录密码: admin123
|
||||
(请尽快在「系统设置」中修改)
|
||||
|
||||
下一步(浏览器):
|
||||
1. 中控 → 系统设置 → 确认账户 URL(有域名时改 flask_url)
|
||||
2. 各所 → env 配置 → 填 API、风控、企业微信
|
||||
3. 中控 → AI 配置(如需复盘)
|
||||
4. 各所 env 配置 → 开启实盘下单 → 保存并重启
|
||||
|
||||
agent_url 默认本机 127.0.0.1:15200/15201/15202,一般无需修改
|
||||
═══════════════════════════════════════════
|
||||
EOF
|
||||
}
|
||||
|
||||
print_post_install_guide_instance() {
|
||||
local ex_key="$1"
|
||||
local ip="${2:-$(detect_server_ip)}"
|
||||
local label port
|
||||
label="$(exchange_label "${ex_key}")"
|
||||
port="$(exchange_http_port "${ex_key}")"
|
||||
cat <<EOF
|
||||
|
||||
═══════════════════════════════════════════
|
||||
✅ 单所实例部署完成(不含中控)
|
||||
|
||||
${label}: http://${ip}:${port}
|
||||
|
||||
登录账号: admin
|
||||
登录密码: admin123
|
||||
(请尽快在 env 配置中修改)
|
||||
|
||||
下一步(浏览器):
|
||||
1. ${label} → env 配置 → 填 API、风控、企业微信
|
||||
2. 开启实盘下单 → 保存并重启该实例进程
|
||||
|
||||
说明: 本模式不起中控 / agent;整仓仍在 ${INSTALL_ROOT},仅启动本所 Flask.
|
||||
═══════════════════════════════════════════
|
||||
EOF
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
#!/usr/bin/env bash
|
||||
# deploy/lib/install.sh — 一键部署(全套) / 单所仅实例
|
||||
set -e
|
||||
set -u
|
||||
if [ -n "${BASH_VERSION:-}" ]; then
|
||||
set -o pipefail
|
||||
fi
|
||||
|
||||
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=common.sh
|
||||
source "${LIB_DIR}/common.sh"
|
||||
|
||||
EXCHANGE=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--exchange)
|
||||
EXCHANGE="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
echo "用法: bash deploy/lib/install.sh [--exchange okx|binance|gate]"
|
||||
echo " 无 --exchange: 全套(三所+中控)"
|
||||
echo " 有 --exchange: 仅该所 Flask 实例(不含中控/agent)"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "未知参数: $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
install_fresh() {
|
||||
step "克隆仓库"
|
||||
if [[ -d "${INSTALL_ROOT}" ]]; then
|
||||
die "目录已存在: ${INSTALL_ROOT},请选修复环境或深度重装"
|
||||
fi
|
||||
mkdir -p "$(dirname "${INSTALL_ROOT}")"
|
||||
git clone -b "${GIT_BRANCH}" "${GIT_URL}" "${INSTALL_ROOT}"
|
||||
}
|
||||
|
||||
install_repair() {
|
||||
step "修复环境(保留数据与配置)"
|
||||
bash "${REPO_ROOT}/deploy/setup_env.sh" --install-system-deps
|
||||
}
|
||||
|
||||
install_deep() {
|
||||
step "深度重装(保留 .env,清库)"
|
||||
bash "${REPO_ROOT}/deploy/reinstall.sh" --yes
|
||||
verify_deployment "$(detect_server_ip)" || true
|
||||
print_post_install_guide "$(detect_server_ip)"
|
||||
}
|
||||
|
||||
run_install_pipeline() {
|
||||
step "环境部署 setup_env.sh"
|
||||
bash "${REPO_ROOT}/deploy/setup_env.sh" --install-system-deps
|
||||
|
||||
step "启动 PM2 全部进程"
|
||||
bash "${REPO_ROOT}/deploy/pm2_start_all.sh"
|
||||
|
||||
pm2_save_startup
|
||||
install_backup_cron_all
|
||||
|
||||
verify_deployment "$(detect_server_ip)" || true
|
||||
print_post_install_guide "$(detect_server_ip)"
|
||||
}
|
||||
|
||||
run_instance_pipeline() {
|
||||
local ex_key="$1"
|
||||
local dir_name
|
||||
dir_name="$(exchange_dir "${ex_key}")"
|
||||
|
||||
step "单所环境部署 setup_env.sh --only ${ex_key}(不含中控)"
|
||||
bash "${REPO_ROOT}/deploy/setup_env.sh" --only "${ex_key}" --install-system-deps
|
||||
|
||||
step "启动 PM2 仅 ${dir_name}"
|
||||
bash "${REPO_ROOT}/deploy/pm2_start_all.sh" --only "${ex_key}"
|
||||
|
||||
pm2_save_startup
|
||||
local cron_script="${REPO_ROOT}/${dir_name}/scripts/install_backup_cron.sh"
|
||||
if [[ -x "${cron_script}" ]]; then
|
||||
step "安装 ${ex_key} 备份 cron"
|
||||
bash "${cron_script}" || true
|
||||
fi
|
||||
|
||||
verify_deployment "$(detect_server_ip)" "${ex_key}" || true
|
||||
print_post_install_guide_instance "${ex_key}" "$(detect_server_ip)"
|
||||
}
|
||||
|
||||
handle_existing_install() {
|
||||
echo ""
|
||||
echo "检测到已部署安装: ${INSTALL_ROOT}"
|
||||
echo " a) 取消"
|
||||
echo " b) 修复环境(重建 venv,保留 .env 与数据库)"
|
||||
echo " c) 深度重装(保留 .env,清库,见 reinstall.sh)"
|
||||
local choice=""
|
||||
cm_read choice "请选择 [a/b/c]: "
|
||||
case "${choice}" in
|
||||
b|B)
|
||||
install_repair
|
||||
if command -v pm2 >/dev/null 2>&1; then
|
||||
bash "${REPO_ROOT}/deploy/pm2_start_all.sh" 2>/dev/null || pm2 restart all 2>/dev/null || true
|
||||
pm2_save_startup
|
||||
fi
|
||||
verify_deployment "$(detect_server_ip)" || true
|
||||
print_post_install_guide "$(detect_server_ip)"
|
||||
;;
|
||||
c|C)
|
||||
install_deep
|
||||
;;
|
||||
*)
|
||||
log "已取消"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
ensure_repo_ready() {
|
||||
if repo_ready "${INSTALL_ROOT}"; then
|
||||
REPO_ROOT="${INSTALL_ROOT}"
|
||||
elif [[ -n "${REPO_ROOT:-}" ]] && repo_ready "${REPO_ROOT}"; then
|
||||
:
|
||||
else
|
||||
REPO_ROOT=""
|
||||
fi
|
||||
|
||||
if [[ -z "${REPO_ROOT}" ]]; then
|
||||
install_system_packages
|
||||
install_node_pm2
|
||||
install_fresh
|
||||
REPO_ROOT="${INSTALL_ROOT}"
|
||||
else
|
||||
install_system_packages
|
||||
install_node_pm2
|
||||
fi
|
||||
}
|
||||
|
||||
main_install_instance() {
|
||||
local ex_key="$1"
|
||||
require_root
|
||||
require_ubuntu
|
||||
ensure_repo_ready
|
||||
run_instance_pipeline "${ex_key}"
|
||||
}
|
||||
|
||||
main_install() {
|
||||
require_root
|
||||
require_ubuntu
|
||||
|
||||
if [[ -n "${EXCHANGE}" ]]; then
|
||||
local ex_key=""
|
||||
if ! ex_key="$(normalize_exchange_key "${EXCHANGE}")"; then
|
||||
die "无效 --exchange: ${EXCHANGE} (期望 okx|binance|gate)"
|
||||
fi
|
||||
main_install_instance "${ex_key}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if repo_ready "${INSTALL_ROOT}"; then
|
||||
REPO_ROOT="${INSTALL_ROOT}"
|
||||
elif repo_ready "${REPO_ROOT}"; then
|
||||
:
|
||||
else
|
||||
REPO_ROOT=""
|
||||
fi
|
||||
|
||||
if [[ -n "${REPO_ROOT}" ]] && is_deployed "${REPO_ROOT}"; then
|
||||
handle_existing_install
|
||||
return 0
|
||||
fi
|
||||
|
||||
ensure_repo_ready
|
||||
run_install_pipeline
|
||||
}
|
||||
|
||||
main_install "$@"
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bash
|
||||
# deploy/lib/uninstall.sh — 一键卸载
|
||||
set -e
|
||||
set -u
|
||||
if [ -n "${BASH_VERSION:-}" ]; then
|
||||
set -o pipefail
|
||||
fi
|
||||
|
||||
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=common.sh
|
||||
source "${LIB_DIR}/common.sh"
|
||||
|
||||
main_uninstall() {
|
||||
require_root
|
||||
|
||||
local root=""
|
||||
if ! root="$(resolve_repo_root)"; then
|
||||
if [[ -d "${INSTALL_ROOT}" ]]; then
|
||||
root="${INSTALL_ROOT}"
|
||||
else
|
||||
die "未找到安装目录 ${INSTALL_ROOT}"
|
||||
fi
|
||||
fi
|
||||
REPO_ROOT="${root}"
|
||||
|
||||
if ! confirm_uninstall; then
|
||||
log "已取消卸载"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local stamp backup_dir removed_dir
|
||||
stamp="$(TZ="${TZ_NAME}" date +%Y%m%d-%H%M%S)"
|
||||
backup_dir="${BACKUP_ROOT}/pre-uninstall-${stamp}"
|
||||
removed_dir="${INSTALL_ROOT}.removed.${stamp}"
|
||||
|
||||
step "备份配置到 ${backup_dir}"
|
||||
backup_configs_to "${REPO_ROOT}" "${backup_dir}"
|
||||
{
|
||||
echo "created_at=${stamp}"
|
||||
echo "install_root=${INSTALL_ROOT}"
|
||||
echo "removed_dir=${removed_dir}"
|
||||
} >"${backup_dir}/uninstall.manifest"
|
||||
|
||||
step "停止并移除本项目 PM2 进程(不影响其它 PM2)"
|
||||
pm2_stop_project_apps
|
||||
pm2_delete_project_apps
|
||||
|
||||
remove_backup_cron_all
|
||||
|
||||
step "移走安装目录"
|
||||
if [[ -d "${INSTALL_ROOT}" ]]; then
|
||||
mv "${INSTALL_ROOT}" "${removed_dir}"
|
||||
log "已移动: ${INSTALL_ROOT} -> ${removed_dir}"
|
||||
else
|
||||
log "安装目录不存在,跳过"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "卸载完成."
|
||||
echo " 配置备份: ${backup_dir}"
|
||||
echo " 旧目录: ${removed_dir} (确认无误后可手动删除)"
|
||||
echo ""
|
||||
echo "回滚示例:"
|
||||
echo " mv ${removed_dir} ${INSTALL_ROOT}"
|
||||
echo " bash ${INSTALL_ROOT}/deploy/manage.sh # 选 1 修复环境"
|
||||
}
|
||||
|
||||
main_uninstall "$@"
|
||||
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env bash
|
||||
# deploy/lib/update.sh — 更新
|
||||
set -e
|
||||
set -u
|
||||
if [ -n "${BASH_VERSION:-}" ]; then
|
||||
set -o pipefail
|
||||
fi
|
||||
|
||||
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=common.sh
|
||||
source "${LIB_DIR}/common.sh"
|
||||
|
||||
require_installed() {
|
||||
if ! repo_ready "${REPO_ROOT}"; then
|
||||
die "未找到安装,请先执行「1) 一键部署」"
|
||||
fi
|
||||
}
|
||||
|
||||
update_quick() {
|
||||
step "快速更新"
|
||||
bash "${REPO_ROOT}/deploy/pull_and_restart.sh"
|
||||
verify_deployment "$(detect_server_ip)" || true
|
||||
}
|
||||
|
||||
update_deps() {
|
||||
step "依赖更新"
|
||||
local dir
|
||||
for dir in crypto_monitor_binance crypto_monitor_gate crypto_monitor_okx; do
|
||||
local proj="${REPO_ROOT}/${dir}"
|
||||
if [[ -x "${proj}/.venv/bin/pip" ]]; then
|
||||
step "${dir}"
|
||||
pip_install_requirements "${proj}/.venv/bin/pip" "${REPO_ROOT}/requirements.txt" "交易所共用依赖"
|
||||
fi
|
||||
done
|
||||
local hub="${REPO_ROOT}/manual_trading_hub"
|
||||
if [[ -x "${hub}/.venv/bin/pip" && -f "${hub}/requirements.txt" ]]; then
|
||||
step "manual_trading_hub"
|
||||
pip_install_requirements "${hub}/.venv/bin/pip" "${hub}/requirements.txt" "中控依赖"
|
||||
fi
|
||||
update_quick
|
||||
}
|
||||
|
||||
update_deep() {
|
||||
step "深度重装"
|
||||
if ! confirm_yes "深度重装将清库并保留 .env,确认继续?"; then
|
||||
log "已取消"
|
||||
return 0
|
||||
fi
|
||||
bash "${REPO_ROOT}/deploy/reinstall.sh" --yes
|
||||
verify_deployment "$(detect_server_ip)" || true
|
||||
}
|
||||
|
||||
show_update_menu() {
|
||||
while true; do
|
||||
echo ""
|
||||
echo " 更新选项:"
|
||||
echo " 3-1) 快速更新(git pull + pm2 restart)"
|
||||
echo " 3-2) 依赖更新(含 pip install)"
|
||||
echo " 3-3) 深度重装(保留 .env,清库)"
|
||||
echo " 0) 返回主菜单"
|
||||
local choice=""
|
||||
cm_read choice "请选择 [0/3-1/3-2/3-3]: "
|
||||
case "${choice}" in
|
||||
3-1|31|1) update_quick; break ;;
|
||||
3-2|32|2) update_deps; break ;;
|
||||
3-3|33|3) update_deep; break ;;
|
||||
0) break ;;
|
||||
*) echo "无效选项" ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
main_update() {
|
||||
require_root
|
||||
if ! REPO_ROOT="$(resolve_repo_root)"; then
|
||||
die "未找到安装目录 ${INSTALL_ROOT},请先执行「1) 一键部署」"
|
||||
fi
|
||||
require_installed
|
||||
show_update_menu
|
||||
}
|
||||
|
||||
main_update "$@"
|
||||
@@ -0,0 +1,161 @@
|
||||
#!/usr/bin/env bash
|
||||
# crypto_monitor_user 部署管理器 — 一键部署 / 卸载 / 更新
|
||||
#
|
||||
# 新服务器(免克隆):
|
||||
# curl -fsSL https://git.bz121.com/dekun/crypto_monitor_user/raw/branch/main/deploy/manage.sh | bash
|
||||
#
|
||||
# 已安装:
|
||||
# bash /opt/crypto_monitor_user/deploy/manage.sh
|
||||
#
|
||||
set -e
|
||||
if [ -n "${BASH_VERSION:-}" ]; then
|
||||
set -o pipefail
|
||||
fi
|
||||
|
||||
INSTALL_ROOT="${INSTALL_ROOT:-/opt/crypto_monitor_user}"
|
||||
GIT_URL="${GIT_URL:-https://git.bz121.com/dekun/crypto_monitor_user.git}"
|
||||
GIT_BRANCH="${GIT_BRANCH:-main}"
|
||||
|
||||
# curl | bash 时脚本从 stdin 执行,BASH_SOURCE[0] 为空;须先安全探测再 set -u
|
||||
_script_src="${BASH_SOURCE[0]:-}"
|
||||
if [[ -n "${_script_src}" && -f "${_script_src}" ]]; then
|
||||
DEPLOY_DIR="$(cd "$(dirname "${_script_src}")" && pwd)"
|
||||
REPO_ROOT="$(cd "${DEPLOY_DIR}/.." && pwd)"
|
||||
LIB_DIR="${DEPLOY_DIR}/lib"
|
||||
else
|
||||
DEPLOY_DIR=""
|
||||
REPO_ROOT=""
|
||||
LIB_DIR=""
|
||||
fi
|
||||
unset _script_src
|
||||
|
||||
set -u
|
||||
|
||||
repo_ready() {
|
||||
[[ -f "${1}/deploy/setup_env.sh" && -f "${1}/deploy/manage.sh" ]]
|
||||
}
|
||||
|
||||
sync_repo_if_present() {
|
||||
local root="$1"
|
||||
if [[ -d "${root}/.git" ]] && command -v git >/dev/null 2>&1; then
|
||||
git -C "${root}" pull -q --ff-only 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
bootstrap_repo() {
|
||||
if repo_ready "${INSTALL_ROOT}"; then
|
||||
REPO_ROOT="${INSTALL_ROOT}"
|
||||
DEPLOY_DIR="${REPO_ROOT}/deploy"
|
||||
LIB_DIR="${DEPLOY_DIR}/lib"
|
||||
sync_repo_if_present "${REPO_ROOT}"
|
||||
return 0
|
||||
fi
|
||||
if [[ -n "${REPO_ROOT}" ]] && repo_ready "${REPO_ROOT}"; then
|
||||
DEPLOY_DIR="${REPO_ROOT}/deploy"
|
||||
LIB_DIR="${DEPLOY_DIR}/lib"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "crypto_monitor_user 部署管理器 — 首次自举"
|
||||
echo "将克隆到: ${INSTALL_ROOT}"
|
||||
if [[ "$(id -u)" -ne 0 ]]; then
|
||||
echo "错误: 请使用 root 执行" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v git >/dev/null 2>&1; then
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -qq
|
||||
apt-get install -y git ca-certificates curl
|
||||
else
|
||||
echo "错误: 未找到 git" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
if [[ -d "${INSTALL_ROOT}" ]]; then
|
||||
echo "错误: ${INSTALL_ROOT} 已存在但不是有效仓库" >&2
|
||||
echo "请手动处理后再运行,或设置 INSTALL_ROOT 指向其它路径" >&2
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p "$(dirname "${INSTALL_ROOT}")"
|
||||
git clone -b "${GIT_BRANCH}" "${GIT_URL}" "${INSTALL_ROOT}"
|
||||
# clone 后用仓库内脚本 + 终端 stdin(管道已 EOF)
|
||||
exec bash "${INSTALL_ROOT}/deploy/manage.sh" "$@" </dev/tty
|
||||
}
|
||||
|
||||
show_banner() {
|
||||
local ip path
|
||||
path="${INSTALL_ROOT}"
|
||||
if repo_ready "${REPO_ROOT}"; then
|
||||
path="${REPO_ROOT}"
|
||||
fi
|
||||
# shellcheck source=/dev/null
|
||||
if [[ -f "${LIB_DIR}/common.sh" ]]; then
|
||||
source "${LIB_DIR}/common.sh"
|
||||
ip="$(detect_server_ip)"
|
||||
else
|
||||
ip="?"
|
||||
fi
|
||||
echo ""
|
||||
echo "╔══════════════════════════════════════╗"
|
||||
echo "║ crypto_monitor_user 部署管理器 ║"
|
||||
echo "║ 路径: ${path}"
|
||||
echo "╚══════════════════════════════════════╝"
|
||||
echo ""
|
||||
}
|
||||
|
||||
show_menu() {
|
||||
echo " 1) 一键部署(全套:三所+中控)"
|
||||
echo " 4) 仅 OKX 实例(不含中控)"
|
||||
echo " 5) 仅 Binance 实例(不含中控)"
|
||||
echo " 6) 仅 Gate 实例(不含中控)"
|
||||
echo " 2) 一键卸载"
|
||||
echo " 3) 更新"
|
||||
echo " 0) 退出"
|
||||
echo ""
|
||||
}
|
||||
|
||||
main_menu() {
|
||||
bootstrap_repo
|
||||
# shellcheck source=lib/common.sh
|
||||
source "${LIB_DIR}/common.sh"
|
||||
REPO_ROOT="$(resolve_repo_root || echo "${INSTALL_ROOT}")"
|
||||
|
||||
while true; do
|
||||
show_banner
|
||||
show_menu
|
||||
local choice=""
|
||||
cm_read choice "请选择 [0-6]: "
|
||||
case "${choice}" in
|
||||
1)
|
||||
bash "${LIB_DIR}/install.sh"
|
||||
;;
|
||||
4)
|
||||
bash "${LIB_DIR}/install.sh" --exchange okx
|
||||
;;
|
||||
5)
|
||||
bash "${LIB_DIR}/install.sh" --exchange binance
|
||||
;;
|
||||
6)
|
||||
bash "${LIB_DIR}/install.sh" --exchange gate
|
||||
;;
|
||||
2)
|
||||
bash "${LIB_DIR}/uninstall.sh"
|
||||
;;
|
||||
3)
|
||||
bash "${LIB_DIR}/update.sh"
|
||||
;;
|
||||
0)
|
||||
echo "再见."
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "无效选项,请输入 0-6"
|
||||
;;
|
||||
esac
|
||||
echo ""
|
||||
cm_read _ "按 Enter 返回菜单..."
|
||||
done
|
||||
}
|
||||
|
||||
main_menu "$@"
|
||||
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env bash
|
||||
# 拉长宝塔 Nginx 反代读/写超时,避免 AI 复盘(1~3 分钟)被 60s 默认切断返回 504.
|
||||
# 用法: bash deploy/nginx_ai_review_timeouts.sh
|
||||
# 可选: TIMEOUT_SEC=360 bash deploy/nginx_ai_review_timeouts.sh
|
||||
set -euo pipefail
|
||||
|
||||
TIMEOUT_SEC="${TIMEOUT_SEC:-360}"
|
||||
PROXY_ROOT="${PROXY_ROOT:-/www/server/panel/vhost/nginx/proxy}"
|
||||
SITES=(okx.hyf2.cc bn.hyf2.cc gt1.hyf2.cc zk.hyf2.cc)
|
||||
|
||||
if [[ ! -d "$PROXY_ROOT" ]]; then
|
||||
echo "skip: no BT nginx proxy dir at $PROXY_ROOT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
patched=0
|
||||
for site in "${SITES[@]}"; do
|
||||
dir="$PROXY_ROOT/$site"
|
||||
[[ -d "$dir" ]] || continue
|
||||
shopt -s nullglob
|
||||
for conf in "$dir"/*.conf; do
|
||||
# 已有同秒数则跳过
|
||||
if grep -qE "proxy_read_timeout[[:space:]]+${TIMEOUT_SEC}" "$conf"; then
|
||||
echo "ok (unchanged): $conf"
|
||||
continue
|
||||
fi
|
||||
# 去掉旧的超时行,再在 proxy_http_version 后插入
|
||||
tmp="$(mktemp)"
|
||||
grep -vE 'proxy_(connect|send|read)_timeout' "$conf" >"$tmp" || true
|
||||
if grep -q 'proxy_http_version' "$tmp"; then
|
||||
awk -v t="$TIMEOUT_SEC" '
|
||||
{print}
|
||||
/proxy_http_version/ && !done {
|
||||
print " proxy_connect_timeout " t "s;"
|
||||
print " proxy_send_timeout " t "s;"
|
||||
print " proxy_read_timeout " t "s;"
|
||||
done=1
|
||||
}
|
||||
' "$tmp" >"$conf"
|
||||
else
|
||||
# 兜底:插到 proxy_pass 后
|
||||
awk -v t="$TIMEOUT_SEC" '
|
||||
{print}
|
||||
/proxy_pass/ && !done {
|
||||
print " proxy_connect_timeout " t "s;"
|
||||
print " proxy_send_timeout " t "s;"
|
||||
print " proxy_read_timeout " t "s;"
|
||||
done=1
|
||||
}
|
||||
' "$tmp" >"$conf"
|
||||
fi
|
||||
rm -f "$tmp"
|
||||
echo "patched: $conf -> ${TIMEOUT_SEC}s"
|
||||
patched=$((patched + 1))
|
||||
done
|
||||
done
|
||||
|
||||
# 同步抬高全局默认,防止其它 location 仍用 60s
|
||||
GLOBAL_PROXY="${GLOBAL_PROXY:-/www/server/nginx/conf/proxy.conf}"
|
||||
if [[ -f "$GLOBAL_PROXY" ]]; then
|
||||
if grep -qE "proxy_read_timeout[[:space:]]+${TIMEOUT_SEC}" "$GLOBAL_PROXY"; then
|
||||
echo "ok (unchanged): $GLOBAL_PROXY"
|
||||
else
|
||||
cp -a "$GLOBAL_PROXY" "${GLOBAL_PROXY}.bak.ai_timeout"
|
||||
sed -i -E \
|
||||
-e "s/proxy_connect_timeout[[:space:]]+[0-9]+;/proxy_connect_timeout ${TIMEOUT_SEC};/" \
|
||||
-e "s/proxy_read_timeout[[:space:]]+[0-9]+;/proxy_read_timeout ${TIMEOUT_SEC};/" \
|
||||
-e "s/proxy_send_timeout[[:space:]]+[0-9]+;/proxy_send_timeout ${TIMEOUT_SEC};/" \
|
||||
"$GLOBAL_PROXY"
|
||||
echo "patched: $GLOBAL_PROXY -> ${TIMEOUT_SEC}s"
|
||||
patched=$((patched + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$patched" -eq 0 ]]; then
|
||||
echo "done (nothing to patch)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if command -v nginx >/dev/null 2>&1; then
|
||||
nginx -t
|
||||
# 宝塔常用 reload
|
||||
if [[ -x /etc/init.d/nginx ]]; then
|
||||
/etc/init.d/nginx reload
|
||||
else
|
||||
nginx -s reload
|
||||
fi
|
||||
echo "nginx reloaded"
|
||||
else
|
||||
echo "warn: nginx binary not found; configs patched but not reloaded"
|
||||
fi
|
||||
echo "done"
|
||||
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env bash
|
||||
# PM2 日志策略:关灌屏后的兜底轮转 — 按天保留 3 天,单文件过大也切分.
|
||||
# 用法(root):
|
||||
# bash deploy/pm2_log_policy.sh
|
||||
# bash deploy/pm2_log_policy.sh --clean-only
|
||||
set -euo pipefail
|
||||
|
||||
PM2_LOG_DIR="${PM2_LOG_DIR:-/root/.pm2/logs}"
|
||||
LOGROTATE_CONF="${LOGROTATE_CONF:-/etc/logrotate.d/crypto_monitor_user_pm2}"
|
||||
RETENTION_DAYS="${RETENTION_DAYS:-3}"
|
||||
MAXSIZE="${MAXSIZE:-20M}"
|
||||
CLEAN_ONLY=0
|
||||
if [[ "${1:-}" == "--clean-only" ]]; then
|
||||
CLEAN_ONLY=1
|
||||
fi
|
||||
|
||||
echo ">>> PM2 log dir: ${PM2_LOG_DIR}"
|
||||
if [[ ! -d "${PM2_LOG_DIR}" ]]; then
|
||||
echo "目录不存在,跳过"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo ">>> truncate oversized active logs (>20MB) and drop stale files older than ${RETENTION_DAYS}d"
|
||||
# 当前正在写的大文件用 truncate 清空内容(copytruncate/空写兼容 PM2 仍持有 fd)
|
||||
find "${PM2_LOG_DIR}" -type f -name '*.log' -size +20M -print -exec truncate -s 0 {} \;
|
||||
# 过期归档/旧编号日志直接删
|
||||
find "${PM2_LOG_DIR}" -type f \( -name '*.log' -o -name '*.log.gz' -o -name '*.log.[0-9]*' \) -mtime +"${RETENTION_DAYS}" -print -delete || true
|
||||
|
||||
if [[ "${CLEAN_ONLY}" -eq 1 ]]; then
|
||||
echo ">>> --clean-only done"
|
||||
du -sh "${PM2_LOG_DIR}" || true
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo ">>> install logrotate: ${LOGROTATE_CONF} (daily, rotate ${RETENTION_DAYS}, maxsize ${MAXSIZE})"
|
||||
cat > "${LOGROTATE_CONF}" <<EOF
|
||||
${PM2_LOG_DIR}/*.log {
|
||||
daily
|
||||
rotate ${RETENTION_DAYS}
|
||||
maxsize ${MAXSIZE}
|
||||
missingok
|
||||
notifempty
|
||||
compress
|
||||
delaycompress
|
||||
copytruncate
|
||||
dateext
|
||||
dateformat -%Y%m%d
|
||||
}
|
||||
EOF
|
||||
|
||||
# 立即跑一次(不强制等到 cron)
|
||||
if command -v logrotate >/dev/null 2>&1; then
|
||||
logrotate -f "${LOGROTATE_CONF}" || true
|
||||
else
|
||||
echo "warn: logrotate 未安装,仅完成清理;请 apt install logrotate"
|
||||
fi
|
||||
|
||||
echo ">>> size after"
|
||||
du -sh "${PM2_LOG_DIR}" || true
|
||||
ls -lah "${PM2_LOG_DIR}" | head -30 || true
|
||||
echo "done"
|
||||
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env bash
|
||||
# 按推荐顺序启动三所 Flask + 中控 hub/三 agent(PM2).
|
||||
# 用法(仓库根或任意目录):
|
||||
# bash deploy/pm2_start_all.sh
|
||||
# bash deploy/pm2_start_all.sh --only okx
|
||||
# bash deploy/pm2_start_all.sh --only binance
|
||||
# bash deploy/pm2_start_all.sh --only gate
|
||||
#
|
||||
# 与 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)"
|
||||
# shellcheck source=lib/common.sh
|
||||
source "${DEPLOY_DIR}/lib/common.sh"
|
||||
|
||||
ONLY="all"
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--only)
|
||||
ONLY="${2:-all}"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
sed -n '2,10p' "$0" | sed 's/^# \?//'
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "未知参数: $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
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}"
|
||||
# 已存在则 restart,避免重复 start 导致 set -e 退出
|
||||
if (cd "${proj}" && pm2 start ecosystem.config.cjs); then
|
||||
return 0
|
||||
fi
|
||||
echo " (已存在,改为 restart)"
|
||||
(cd "${proj}" && pm2 startOrReload ecosystem.config.cjs --update-env) \
|
||||
|| (cd "${proj}" && pm2 reload ecosystem.config.cjs --update-env) \
|
||||
|| true
|
||||
}
|
||||
|
||||
if ! command -v pm2 >/dev/null 2>&1; then
|
||||
echo "未找到 pm2,请先安装 Node.js 与 pm2(见 docs/ubuntu-server.md)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${ONLY}" == "all" ]]; then
|
||||
start_one crypto_monitor_binance
|
||||
start_one crypto_monitor_gate
|
||||
start_one crypto_monitor_okx
|
||||
start_one manual_trading_hub
|
||||
else
|
||||
ex_key="$(normalize_exchange_key "${ONLY}" || true)"
|
||||
if [[ -z "${ex_key}" ]]; then
|
||||
echo "未知 --only 值: ${ONLY} (期望 okx|binance|gate|all)" >&2
|
||||
exit 1
|
||||
fi
|
||||
dir_name="$(exchange_dir "${ex_key}")"
|
||||
start_one "${dir_name}"
|
||||
fi
|
||||
|
||||
pm2 save 2>/dev/null || true
|
||||
echo ""
|
||||
echo "PM2 进程:"
|
||||
pm2 list
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env bash
|
||||
# 服务器上拉代码,同步 env,应用强制清仓策略并重启 PM2.
|
||||
# 用法(/opt/crypto_monitor_user 下 root):
|
||||
# bash deploy/pull_and_restart.sh
|
||||
# bash deploy/pull_and_restart.sh --dry-run
|
||||
set -euo pipefail
|
||||
|
||||
REPO="${REPO:-/opt/crypto_monitor_user}"
|
||||
DRY=()
|
||||
if [[ "${1:-}" == "--dry-run" ]]; then
|
||||
DRY=(--dry-run)
|
||||
echo "(dry-run mode)"
|
||||
fi
|
||||
|
||||
cd "$REPO"
|
||||
echo ">>> git pull"
|
||||
git pull
|
||||
|
||||
echo ">>> sync common trading env (binance + okx missing keys)"
|
||||
python3 scripts/sync_common_trading_env.py "${DRY[@]}"
|
||||
|
||||
echo ">>> force-close defaults only if missing (never overwrite manual)"
|
||||
python3 scripts/sync_common_trading_env.py --apply-force-close-policy "${DRY[@]}"
|
||||
|
||||
if [[ ${#DRY[@]} -gt 0 ]]; then
|
||||
echo "(dry-run, skip pm2 log policy + restart)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo ">>> pm2 log policy (silence leftover + 3-day retain)"
|
||||
sed -i 's/\r$//' deploy/pm2_log_policy.sh 2>/dev/null || true
|
||||
bash deploy/pm2_log_policy.sh
|
||||
|
||||
echo ">>> nginx AI review proxy timeouts (avoid 504 on /ai_*_review)"
|
||||
sed -i 's/\r$//' deploy/nginx_ai_review_timeouts.sh 2>/dev/null || true
|
||||
bash deploy/nginx_ai_review_timeouts.sh || echo "warn: nginx timeout patch skipped"
|
||||
|
||||
echo ">>> pm2 restart --update-env"
|
||||
# --update-env:避免 PM2 dump 里残留的跨实例环境变量(如 EXCHANGE_DISPLAY_NAME)继续污染
|
||||
pm2 restart crypto_gate crypto_binance crypto_okx manual-trading-hub manual-agent-gate --update-env 2>/dev/null \
|
||||
|| pm2 restart crypto-monitor-gate crypto-monitor-binance crypto-monitor-okx manual-trading-hub --update-env 2>/dev/null \
|
||||
|| pm2 restart all --update-env
|
||||
|
||||
echo ">>> FORCE_CLOSE settings"
|
||||
grep -E '^FORCE_CLOSE_' crypto_monitor_gate/.env crypto_monitor_binance/.env crypto_monitor_okx/.env || true
|
||||
|
||||
echo "done"
|
||||
@@ -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_user
|
||||
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_user /opt/crypto_monitor_user.old.时间戳`**
|
||||
4. **`git clone`** 到 `/opt/crypto_monitor_user`(默认 `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_user
|
||||
export GIT_URL=https://git.bz121.com/dekun/crypto_monitor_user.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_user.old.时间戳`,配置在 `/root/backups/pre-reinstall-*`:
|
||||
|
||||
```bash
|
||||
pm2 delete all
|
||||
rm -rf /opt/crypto_monitor_user
|
||||
mv /opt/crypto_monitor_user.old.XXXXXXXX /opt/crypto_monitor_user
|
||||
bash /opt/crypto_monitor_user/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
|
||||
@@ -0,0 +1,318 @@
|
||||
#!/usr/bin/env bash
|
||||
# Plan B:整目录重装 /opt/crypto_monitor_user(备份 .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_user
|
||||
# bash deploy/reinstall.sh # 交互确认
|
||||
# bash deploy/reinstall.sh --yes # 跳过确认
|
||||
# bash deploy/reinstall.sh --dry-run # 仅打印步骤
|
||||
#
|
||||
# 可选环境变量:
|
||||
# INSTALL_ROOT=/opt/crypto_monitor_user
|
||||
# GIT_URL=https://git.bz121.com/dekun/crypto_monitor_user.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)"
|
||||
# shellcheck source=lib/common.sh
|
||||
source "${DEPLOY_DIR}/lib/common.sh"
|
||||
|
||||
INSTALL_ROOT="${INSTALL_ROOT:-/opt/crypto_monitor_user}"
|
||||
GIT_URL="${GIT_URL:-https://git.bz121.com/dekun/crypto_monitor_user.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 进程(不影响其它 PM2)"
|
||||
if command -v pm2 >/dev/null 2>&1; then
|
||||
if [[ "${DRY_RUN}" -eq 1 ]]; then
|
||||
log "[dry-run] pm2 stop/delete 仅: ${PM2_APPS[*]} (及历史别名)"
|
||||
else
|
||||
pm2_stop_project_apps
|
||||
pm2_delete_project_apps
|
||||
fi
|
||||
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"
|
||||
@@ -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 <hub_settings.json>", 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())
|
||||
@@ -0,0 +1,263 @@
|
||||
#!/usr/bin/env bash
|
||||
# crypto_monitor_user 一键环境部署(Ubuntu / root /opt/crypto_monitor_user)
|
||||
#
|
||||
# 用法:
|
||||
# bash deploy/setup_env.sh
|
||||
# bash deploy/setup_env.sh --only binance,gate
|
||||
# bash deploy/setup_env.sh --skip-pm2
|
||||
# bash deploy/setup_env.sh --recreate-venv
|
||||
# bash deploy/setup_env.sh --install-system-deps # root + apt 时安装 python*-venv
|
||||
#
|
||||
set -e
|
||||
set -u
|
||||
# 避免 Windows CRLF 导致 set -euo pipefail 一行报错;pipefail 仅 bash 支持
|
||||
if [ -n "${BASH_VERSION:-}" ]; then
|
||||
set -o pipefail
|
||||
fi
|
||||
|
||||
DEPLOY_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "${DEPLOY_DIR}/.." && pwd)"
|
||||
REQ_FILE="${REPO_ROOT}/requirements.txt"
|
||||
HUB_REQ="${REPO_ROOT}/manual_trading_hub/requirements.txt"
|
||||
# shellcheck source=lib/common.sh
|
||||
source "${DEPLOY_DIR}/lib/common.sh"
|
||||
|
||||
ONLY="all"
|
||||
SKIP_PM2=0
|
||||
SKIP_ENV_COPY=0
|
||||
RECREATE_VENV=0
|
||||
INSTALL_APT_DEPS=0
|
||||
PY=""
|
||||
|
||||
usage() {
|
||||
sed -n '2,12p' "$0" | sed 's/^# \?//'
|
||||
exit "${1:-0}"
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--only) ONLY="${2:-all}"; shift 2 ;;
|
||||
--skip-pm2) SKIP_PM2=1; shift ;;
|
||||
--skip-env-copy) SKIP_ENV_COPY=1; shift ;;
|
||||
--recreate-venv) RECREATE_VENV=1; shift ;;
|
||||
--install-system-deps) INSTALL_APT_DEPS=1; shift ;;
|
||||
-h|--help) usage 0 ;;
|
||||
*) echo "未知参数: $1" >&2; usage 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
step() { echo ""; echo "==> $*"; }
|
||||
|
||||
should_include() {
|
||||
local key="$1"
|
||||
if [[ "${ONLY}" == "all" ]]; then
|
||||
return 0
|
||||
fi
|
||||
local item
|
||||
IFS=',' read -ra PARTS <<< "${ONLY}"
|
||||
for item in "${PARTS[@]}"; do
|
||||
item="$(echo "${item}" | tr '[:upper:]' '[:lower:]' | xargs)"
|
||||
[[ "${item}" == "${key}" ]] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
find_python() {
|
||||
if command -v python3 >/dev/null 2>&1; then
|
||||
echo python3
|
||||
return
|
||||
fi
|
||||
if command -v python >/dev/null 2>&1; then
|
||||
echo python
|
||||
return
|
||||
fi
|
||||
echo "未找到 python3/python,请先安装 Python 3.10+" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
check_python_version() {
|
||||
local py="$1"
|
||||
local ver
|
||||
ver="$("${py}" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')"
|
||||
local major minor
|
||||
major="${ver%%.*}"
|
||||
minor="${ver#*.}"
|
||||
if [[ "${major}" -lt 3 ]] || [[ "${major}" -eq 3 && "${minor}" -lt 10 ]]; then
|
||||
echo "需要 Python 3.10+,当前: ${ver}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Python: $("${py}" --version 2>&1)"
|
||||
}
|
||||
|
||||
python_minor_version() {
|
||||
local py="$1"
|
||||
"${py}" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")'
|
||||
}
|
||||
|
||||
check_venv_available() {
|
||||
local py="$1"
|
||||
local tmp
|
||||
tmp="$(mktemp -d 2>/dev/null || mktemp -d -t cmvenv)"
|
||||
if "${py}" -m venv "${tmp}" >/dev/null 2>&1 && [[ -x "${tmp}/bin/python" ]]; then
|
||||
rm -rf "${tmp}"
|
||||
return 0
|
||||
fi
|
||||
rm -rf "${tmp}" 2>/dev/null || true
|
||||
return 1
|
||||
}
|
||||
|
||||
install_debian_venv_packages() {
|
||||
local py="$1"
|
||||
local ver
|
||||
ver="$(python_minor_version "${py}")"
|
||||
if ! command -v apt-get >/dev/null 2>&1; then
|
||||
echo " 未检测到 apt-get,请手动安装 python${ver}-venv 与 python3-pip" >&2
|
||||
return 1
|
||||
fi
|
||||
if [[ "$(id -u)" -ne 0 ]]; then
|
||||
echo " 需要 root 安装系统包,请执行:" >&2
|
||||
echo " sudo apt update && sudo apt install -y python${ver}-venv python3-pip curl" >&2
|
||||
echo " 或: sudo bash deploy/setup_env.sh --install-system-deps" >&2
|
||||
return 1
|
||||
fi
|
||||
step "安装系统依赖 (python${ver}-venv) ..."
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -qq
|
||||
if ! apt-get install -y "python${ver}-venv" python3-pip curl ca-certificates; then
|
||||
apt-get install -y python3-venv python3-pip curl ca-certificates
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_venv_prereqs() {
|
||||
local py="$1"
|
||||
if check_venv_available "${py}"; then
|
||||
return 0
|
||||
fi
|
||||
echo " 当前 Python 无法创建 venv(缺少 ensurepip,常见于未安装 python*-venv)" >&2
|
||||
if [[ "${INSTALL_APT_DEPS}" -eq 1 ]] || [[ "$(id -u)" -eq 0 ]]; then
|
||||
install_debian_venv_packages "${py}" || exit 1
|
||||
if check_venv_available "${py}"; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
local ver
|
||||
ver="$(python_minor_version "${py}")"
|
||||
echo "请安装后重试:" >&2
|
||||
echo " apt update && apt install -y python${ver}-venv python3-pip" >&2
|
||||
echo " bash deploy/setup_env.sh" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
create_project_venv() {
|
||||
local py="$1"
|
||||
if [[ "${RECREATE_VENV}" -eq 1 && -d .venv ]]; then
|
||||
echo " 删除旧 venv ..."
|
||||
rm -rf .venv
|
||||
fi
|
||||
if [[ -d .venv && ! -x .venv/bin/python ]]; then
|
||||
echo " 清理未完成的 venv ..."
|
||||
rm -rf .venv
|
||||
fi
|
||||
if [[ -x .venv/bin/python ]]; then
|
||||
return 0
|
||||
fi
|
||||
echo " 创建 venv ..."
|
||||
if ! "${py}" -m venv .venv; then
|
||||
rm -rf .venv 2>/dev/null || true
|
||||
echo " venv 创建失败" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
setup_monitor() {
|
||||
local dir_name="$1"
|
||||
local proj="${REPO_ROOT}/${dir_name}"
|
||||
if [[ ! -d "${proj}" ]]; then
|
||||
echo " 跳过(目录不存在): ${dir_name}"
|
||||
return
|
||||
fi
|
||||
step "${dir_name}"
|
||||
cd "${proj}"
|
||||
create_project_venv "${PY}"
|
||||
pip_upgrade_tools ".venv/bin/pip"
|
||||
pip_install_requirements ".venv/bin/pip" "${REQ_FILE}" "交易所共用依赖"
|
||||
if [[ "${SKIP_ENV_COPY}" -eq 0 ]]; then
|
||||
if [[ -f .env.example && ! -f .env ]]; then
|
||||
cp -n .env.example .env 2>/dev/null || cp .env.example .env
|
||||
echo " 已复制 .env.example -> .env"
|
||||
elif [[ -f .env ]]; then
|
||||
echo " 保留已有 .env"
|
||||
else
|
||||
echo " 无 .env.example,请手动配置 .env"
|
||||
fi
|
||||
fi
|
||||
mkdir -p static/images/order_charts
|
||||
echo " 完成: ${proj}/.venv/bin/python"
|
||||
}
|
||||
|
||||
setup_hub() {
|
||||
local proj="${REPO_ROOT}/manual_trading_hub"
|
||||
if [[ ! -d "${proj}" ]]; then
|
||||
echo " 跳过 hub(目录不存在)"
|
||||
return
|
||||
fi
|
||||
step "manual_trading_hub"
|
||||
cd "${proj}"
|
||||
create_project_venv "${PY}"
|
||||
pip_upgrade_tools ".venv/bin/pip"
|
||||
if [[ -f "${HUB_REQ}" ]]; then
|
||||
pip_install_requirements ".venv/bin/pip" "${HUB_REQ}" "中控依赖"
|
||||
fi
|
||||
if [[ "${SKIP_ENV_COPY}" -eq 0 && -f .env.example && ! -f .env ]]; then
|
||||
cp -n .env.example .env 2>/dev/null || cp .env.example .env
|
||||
echo " 已复制 .env.example -> .env"
|
||||
fi
|
||||
echo " 完成: ${proj}/.venv/bin/python"
|
||||
}
|
||||
|
||||
install_pm2() {
|
||||
if [[ "${SKIP_PM2}" -eq 1 ]]; then
|
||||
return
|
||||
fi
|
||||
step "PM2(可选)"
|
||||
if ! command -v node >/dev/null 2>&1; then
|
||||
echo " 未检测到 Node.js,跳过.安装后执行: npm install -g pm2"
|
||||
return
|
||||
fi
|
||||
if command -v pm2 >/dev/null 2>&1; then
|
||||
echo " PM2 已安装: $(pm2 -v)"
|
||||
return
|
||||
fi
|
||||
echo " 正在安装 pm2 ..."
|
||||
npm install -g pm2
|
||||
echo " 各子目录: pm2 start ecosystem.config.cjs"
|
||||
}
|
||||
|
||||
echo "crypto_monitor_user 环境部署"
|
||||
echo "仓库根目录: ${REPO_ROOT}"
|
||||
|
||||
[[ -f "${REQ_FILE}" ]] || { echo "缺少 ${REQ_FILE}" >&2; exit 1; }
|
||||
|
||||
PY="$(find_python)"
|
||||
check_python_version "${PY}"
|
||||
ensure_venv_prereqs "${PY}"
|
||||
|
||||
should_include binance && setup_monitor crypto_monitor_binance
|
||||
should_include gate && setup_monitor crypto_monitor_gate
|
||||
should_include okx && setup_monitor crypto_monitor_okx
|
||||
should_include hub && setup_hub
|
||||
|
||||
install_pm2
|
||||
|
||||
step "部署密钥(首次自动生成,不覆盖已有)"
|
||||
if command -v python3 >/dev/null 2>&1; then
|
||||
python3 "${REPO_ROOT}/scripts/bootstrap_deploy_secrets.py" || true
|
||||
else
|
||||
echo " 跳过 bootstrap_deploy_secrets(未找到 python3)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "部署完成.下一步:"
|
||||
echo " 1. 编辑各子目录 .env(交易所 API 等;AI 请在中控系统设置配置)"
|
||||
echo " 2. 编辑各目录 .env 后使用 PM2: pm2 start ecosystem.config.cjs(见 docs/ubuntu-server.md)"
|
||||
echo ""
|
||||
Reference in New Issue
Block a user