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,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
|
||||
}
|
||||
Reference in New Issue
Block a user