Support up to 3 strategy slots on one host with auto ports.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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 ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user