Support up to 3 strategy slots on one host with auto ports.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-01 23:33:41 +08:00
parent 9c515644a3
commit aab0b9bdf6
8 changed files with 503 additions and 200 deletions
+164
View File
@@ -15,6 +15,7 @@ TZ_NAME="${EHS_TZ:-Asia/Shanghai}"
NODE_MAJOR="${NODE_MAJOR:-20}"
PM2_APP_NAME="${PM2_APP_NAME:-eth-hedge-api}"
API_PORT="${API_PORT:-5155}"
INSTANCE_SLOT="${INSTANCE_SLOT:-1}"
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEPLOY_DIR="$(cd "${LIB_DIR}/.." && pwd)"
@@ -28,6 +29,167 @@ die() {
exit 1
}
# ---------- 同机多套槽位(1/2/3 ----------
infer_slot_from_root() {
local root="${1%/}"
case "${root}" in
*/eth_hedge_sim_2) echo 2 ;;
*/eth_hedge_sim_3) echo 3 ;;
*) echo 1 ;;
esac
}
# 设置 INSTANCE_SLOT / INSTALL_ROOT / API_PORT / PM2_APP_NAME
resolve_slot() {
local slot="${1:-${INSTANCE_SLOT:-1}}"
case "${slot}" in
1|2|3) ;;
*) die "无效槽位: ${slot}(仅支持 1/2/3" ;;
esac
INSTANCE_SLOT="${slot}"
case "${slot}" in
1)
INSTALL_ROOT="/opt/eth_hedge_sim"
API_PORT="5155"
PM2_APP_NAME="eth-hedge-api"
;;
2)
INSTALL_ROOT="/opt/eth_hedge_sim_2"
API_PORT="5156"
PM2_APP_NAME="eth-hedge-api-2"
;;
3)
INSTALL_ROOT="/opt/eth_hedge_sim_3"
API_PORT="5157"
PM2_APP_NAME="eth-hedge-api-3"
;;
esac
export INSTANCE_SLOT INSTALL_ROOT API_PORT PM2_APP_NAME
}
# 按实际仓库目录对齐槽位(pull/update 时以目录为准)
apply_slot_for_root() {
local root="${1%/}"
[[ -n "${root}" ]] || die "apply_slot_for_root: 空目录"
resolve_slot "$(infer_slot_from_root "${root}")"
INSTALL_ROOT="${root}"
REPO_ROOT="${root}"
# 已有 .env 时保留用户改过的端口
if [[ -f "${root}/.env" ]]; then
local p=""
p="$(grep -E '^API_PORT=' "${root}/.env" 2>/dev/null | head -n1 | cut -d= -f2- | tr -d '\r' || true)"
if [[ -n "${p}" && "${p}" =~ ^[0-9]+$ ]]; then
API_PORT="${p}"
fi
fi
export INSTANCE_SLOT INSTALL_ROOT API_PORT PM2_APP_NAME REPO_ROOT
}
is_allowed_install_root() {
local root="${1%/}"
case "${root}" in
/opt/eth_hedge_sim|/opt/eth_hedge_sim_2|/opt/eth_hedge_sim_3) return 0 ;;
*) return 1 ;;
esac
}
port_is_listening() {
local port="$1"
if command -v ss >/dev/null 2>&1; then
ss -ltn 2>/dev/null | grep -qE ":${port}[[:space:]]" && return 0
fi
if command -v lsof >/dev/null 2>&1; then
lsof -iTCP:"${port}" -sTCP:LISTEN >/dev/null 2>&1 && return 0
fi
return 1
}
# 全新安装前:端口已被占用且不是本槽位 PM2 → 失败
assert_port_free_for_install() {
local port="${1:-${API_PORT}}"
local name="${2:-${PM2_APP_NAME}}"
if command -v pm2 >/dev/null 2>&1; then
if pm2 describe "${name}" >/dev/null 2>&1; then
return 0
fi
fi
if port_is_listening "${port}"; then
die "端口 ${port} 已被占用(槽位 ${INSTANCE_SLOT} / ${name})。请释放端口或换槽位"
fi
}
write_ecosystem_config() {
local root="${1:-${INSTALL_ROOT}}"
local name="${2:-${PM2_APP_NAME}}"
local port="${3:-${API_PORT}}"
local out="${root}/deploy/ecosystem.config.cjs"
mkdir -p "${root}/deploy"
cat >"${out}" <<EOF
module.exports = {
apps: [
{
name: '${name}',
cwd: '${root}/backend',
script: '${root}/.venv/bin/uvicorn',
args: 'app.main:app --host 0.0.0.0 --port ${port}',
interpreter: 'none',
env: {
// MODE 以仓库根 .env 为准(设置页可切 SIM/LIVE),勿在此硬编码覆盖
ENV_NAME: 'test',
TZ: 'Asia/Shanghai',
},
// 密钥与本地配置一律读 ${root}/.env(勿写进本文件)
},
],
};
EOF
log "已写入 PM2: name=${name} port=${port}${out}"
}
random_auth_secret() {
if command -v openssl >/dev/null 2>&1; then
openssl rand -hex 24
return 0
fi
head -c 32 /dev/urandom 2>/dev/null | od -An -tx1 | tr -d ' \n' | head -c 48
echo ""
}
# SKIP_SLOT_PROMPT=1 时用环境变量 INSTANCE_SLOT(默认 1),供「全部更新」等非交互调用
prompt_instance_slot() {
local line=""
local slot=""
if [[ "${SKIP_SLOT_PROMPT:-0}" == "1" ]]; then
resolve_slot "${INSTANCE_SLOT:-1}"
log "槽位 ${INSTANCE_SLOT}: ${INSTALL_ROOT} port=${API_PORT} pm2=${PM2_APP_NAME}"
return 0
fi
echo ""
echo "策略机槽位(同机最多 3 套,互不影响):"
echo " 1 → /opt/eth_hedge_sim :5155 eth-hedge-api"
echo " 2 → /opt/eth_hedge_sim_2 :5156 eth-hedge-api-2"
echo " 3 → /opt/eth_hedge_sim_3 :5157 eth-hedge-api-3"
cm_read line "请选择槽位 [1/2/3,默认1]: "
slot="${line:-1}"
resolve_slot "${slot}"
log "槽位 ${INSTANCE_SLOT}: ${INSTALL_ROOT} port=${API_PORT} pm2=${PM2_APP_NAME}"
}
list_installed_slots() {
local s root
for s in 1 2 3; do
case "${s}" in
1) root="/opt/eth_hedge_sim" ;;
2) root="/opt/eth_hedge_sim_2" ;;
3) root="/opt/eth_hedge_sim_3" ;;
esac
if repo_ready "${root}"; then
echo "${s}"
fi
done
}
require_root() {
if [[ "$(id -u)" -ne 0 ]]; then
die "请使用 root 执行(推荐: sudo -i 后运行)"
@@ -360,11 +522,13 @@ print_post_install_guide() {
echo ""
echo "══════════════════════════════════════"
echo " 比特骆驼自动化对冲系统 部署完成"
echo " 槽位: ${INSTANCE_SLOT:-1}"
echo " 目录: ${INSTALL_ROOT}"
echo " 本机: http://${ip}:${API_PORT}/health"
echo " 进程: pm2 status → ${PM2_APP_NAME}"
echo " 管理: bash ${INSTALL_ROOT}/deploy/manage.sh"
echo " 配置: ${INSTALL_ROOT}/.env"
echo " 同机多套: 菜单选槽位 2/3;实盘请只用槽位 1"
echo "══════════════════════════════════════"
echo ""
}