Support up to 3 strategy slots on one host with auto ports.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
// 默认槽位 1 模板;安装/更新时由 deploy/lib/common.sh#write_ecosystem_config 按目录重写。
|
||||||
module.exports = {
|
module.exports = {
|
||||||
apps: [
|
apps: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ TZ_NAME="${EHS_TZ:-Asia/Shanghai}"
|
|||||||
NODE_MAJOR="${NODE_MAJOR:-20}"
|
NODE_MAJOR="${NODE_MAJOR:-20}"
|
||||||
PM2_APP_NAME="${PM2_APP_NAME:-eth-hedge-api}"
|
PM2_APP_NAME="${PM2_APP_NAME:-eth-hedge-api}"
|
||||||
API_PORT="${API_PORT:-5155}"
|
API_PORT="${API_PORT:-5155}"
|
||||||
|
INSTANCE_SLOT="${INSTANCE_SLOT:-1}"
|
||||||
|
|
||||||
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
DEPLOY_DIR="$(cd "${LIB_DIR}/.." && pwd)"
|
DEPLOY_DIR="$(cd "${LIB_DIR}/.." && pwd)"
|
||||||
@@ -28,6 +29,167 @@ die() {
|
|||||||
exit 1
|
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() {
|
require_root() {
|
||||||
if [[ "$(id -u)" -ne 0 ]]; then
|
if [[ "$(id -u)" -ne 0 ]]; then
|
||||||
die "请使用 root 执行(推荐: sudo -i 后运行)"
|
die "请使用 root 执行(推荐: sudo -i 后运行)"
|
||||||
@@ -360,11 +522,13 @@ print_post_install_guide() {
|
|||||||
echo ""
|
echo ""
|
||||||
echo "══════════════════════════════════════"
|
echo "══════════════════════════════════════"
|
||||||
echo " 比特骆驼自动化对冲系统 部署完成"
|
echo " 比特骆驼自动化对冲系统 部署完成"
|
||||||
|
echo " 槽位: ${INSTANCE_SLOT:-1}"
|
||||||
echo " 目录: ${INSTALL_ROOT}"
|
echo " 目录: ${INSTALL_ROOT}"
|
||||||
echo " 本机: http://${ip}:${API_PORT}/health"
|
echo " 本机: http://${ip}:${API_PORT}/health"
|
||||||
echo " 进程: pm2 status → ${PM2_APP_NAME}"
|
echo " 进程: pm2 status → ${PM2_APP_NAME}"
|
||||||
echo " 管理: bash ${INSTALL_ROOT}/deploy/manage.sh"
|
echo " 管理: bash ${INSTALL_ROOT}/deploy/manage.sh"
|
||||||
echo " 配置: ${INSTALL_ROOT}/.env"
|
echo " 配置: ${INSTALL_ROOT}/.env"
|
||||||
|
echo " 同机多套: 菜单选槽位 2/3;实盘请只用槽位 1"
|
||||||
echo "══════════════════════════════════════"
|
echo "══════════════════════════════════════"
|
||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-6
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# deploy/lib/install.sh — 一键部署策略机
|
# deploy/lib/install.sh — 一键部署策略机(支持槽位 1/2/3)
|
||||||
set -e
|
set -e
|
||||||
set -u
|
set -u
|
||||||
if [ -n "${BASH_VERSION:-}" ]; then
|
if [ -n "${BASH_VERSION:-}" ]; then
|
||||||
@@ -13,10 +13,12 @@ source "${LIB_DIR}/common.sh"
|
|||||||
run_pipeline() {
|
run_pipeline() {
|
||||||
local root="$1"
|
local root="$1"
|
||||||
REPO_ROOT="${root}"
|
REPO_ROOT="${root}"
|
||||||
step "构建并启动策略机 (pull_and_restart.sh)"
|
INSTALL_ROOT="${root}"
|
||||||
|
apply_slot_for_root "${root}"
|
||||||
|
step "构建并启动策略机 (pull_and_restart.sh) slot=${INSTANCE_SLOT}"
|
||||||
bash "${REPO_ROOT}/deploy/pull_and_restart.sh"
|
bash "${REPO_ROOT}/deploy/pull_and_restart.sh"
|
||||||
pm2_save_startup
|
pm2_save_startup
|
||||||
verify_health || true
|
verify_health "${API_PORT}" || true
|
||||||
print_post_install_guide
|
print_post_install_guide
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,7 +26,8 @@ install_fresh() {
|
|||||||
require_root
|
require_root
|
||||||
step "一键部署策略机 — 环境检测与依赖"
|
step "一键部署策略机 — 环境检测与依赖"
|
||||||
ensure_system_deps
|
ensure_system_deps
|
||||||
step "克隆仓库 → ${INSTALL_ROOT}"
|
assert_port_free_for_install "${API_PORT}" "${PM2_APP_NAME}"
|
||||||
|
step "克隆仓库 → ${INSTALL_ROOT}(槽位 ${INSTANCE_SLOT} / :${API_PORT})"
|
||||||
if [[ -d "${INSTALL_ROOT}" ]]; then
|
if [[ -d "${INSTALL_ROOT}" ]]; then
|
||||||
die "目录已存在: ${INSTALL_ROOT},请先卸载或选修复"
|
die "目录已存在: ${INSTALL_ROOT},请先卸载或选修复"
|
||||||
fi
|
fi
|
||||||
@@ -40,13 +43,13 @@ install_repair() {
|
|||||||
if ! REPO_ROOT="$(resolve_repo_root)"; then
|
if ! REPO_ROOT="$(resolve_repo_root)"; then
|
||||||
die "未找到安装目录 ${INSTALL_ROOT}"
|
die "未找到安装目录 ${INSTALL_ROOT}"
|
||||||
fi
|
fi
|
||||||
step "修复环境(保留 .env 与数据库)"
|
step "修复环境(保留 .env 与数据库) slot=${INSTANCE_SLOT}"
|
||||||
run_pipeline "${REPO_ROOT}"
|
run_pipeline "${REPO_ROOT}"
|
||||||
}
|
}
|
||||||
|
|
||||||
handle_existing() {
|
handle_existing() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "检测到已部署: ${INSTALL_ROOT}"
|
echo "检测到已部署: ${INSTALL_ROOT}(槽位 ${INSTANCE_SLOT})"
|
||||||
echo " a) 取消"
|
echo " a) 取消"
|
||||||
echo " b) 修复/重装环境(保留 .env 与 SQLite)"
|
echo " b) 修复/重装环境(保留 .env 与 SQLite)"
|
||||||
local choice=""
|
local choice=""
|
||||||
@@ -59,6 +62,15 @@ handle_existing() {
|
|||||||
|
|
||||||
main_install() {
|
main_install() {
|
||||||
require_root
|
require_root
|
||||||
|
# manage.sh 已 prompt;直接调用时按 INSTANCE_SLOT / INSTALL_ROOT 对齐
|
||||||
|
if [[ "${INSTALL_ROOT}" == "/opt/eth_hedge_sim_2" ]]; then
|
||||||
|
resolve_slot 2
|
||||||
|
elif [[ "${INSTALL_ROOT}" == "/opt/eth_hedge_sim_3" ]]; then
|
||||||
|
resolve_slot 3
|
||||||
|
else
|
||||||
|
resolve_slot "${INSTANCE_SLOT:-1}"
|
||||||
|
fi
|
||||||
|
|
||||||
if repo_ready "${INSTALL_ROOT}"; then
|
if repo_ready "${INSTALL_ROOT}"; then
|
||||||
handle_existing
|
handle_existing
|
||||||
else
|
else
|
||||||
|
|||||||
+27
-24
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# deploy/lib/uninstall.sh — 一键卸载(仅本项目 PM2,删除 /opt 安装目录)
|
# deploy/lib/uninstall.sh — 一键卸载指定槽位(仅该槽位 PM2 + 目录)
|
||||||
set -e
|
set -e
|
||||||
set -u
|
set -u
|
||||||
if [ -n "${BASH_VERSION:-}" ]; then
|
if [ -n "${BASH_VERSION:-}" ]; then
|
||||||
@@ -13,6 +13,8 @@ source "${LIB_DIR}/common.sh"
|
|||||||
main_uninstall() {
|
main_uninstall() {
|
||||||
require_root
|
require_root
|
||||||
|
|
||||||
|
resolve_slot "${INSTANCE_SLOT:-1}"
|
||||||
|
|
||||||
local root=""
|
local root=""
|
||||||
if ! root="$(resolve_repo_root)"; then
|
if ! root="$(resolve_repo_root)"; then
|
||||||
if [[ -d "${INSTALL_ROOT}" ]]; then
|
if [[ -d "${INSTALL_ROOT}" ]]; then
|
||||||
@@ -22,13 +24,15 @@ main_uninstall() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
REPO_ROOT="${root}"
|
REPO_ROOT="${root}"
|
||||||
|
INSTALL_ROOT="${root}"
|
||||||
|
apply_slot_for_root "${root}"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "将卸载 比特骆驼自动化对冲系统 (eth_hedge_sim):"
|
echo "将卸载 比特骆驼自动化对冲系统 槽位 ${INSTANCE_SLOT}:"
|
||||||
echo " - 停止并删除 PM2 进程: ${PM2_APP_NAME}"
|
echo " - 停止并删除 PM2 进程: ${PM2_APP_NAME}"
|
||||||
echo " - 删除安装目录: ${INSTALL_ROOT}"
|
echo " - 删除安装目录: ${INSTALL_ROOT}"
|
||||||
echo " - 备份 .env / 数据库到 ${BACKUP_ROOT}"
|
echo " - 备份 .env / 数据库到 ${BACKUP_ROOT}"
|
||||||
echo " - 不影响其它 PM2 应用 / 不卸载系统 Python、Node、PM2"
|
echo " - 不影响其它槽位 / 其它 PM2 / 系统 Python、Node、PM2"
|
||||||
echo ""
|
echo ""
|
||||||
if ! confirm_yes "确认卸载并删除 ${INSTALL_ROOT}?"; then
|
if ! confirm_yes "确认卸载并删除 ${INSTALL_ROOT}?"; then
|
||||||
log "已取消卸载"
|
log "已取消卸载"
|
||||||
@@ -37,7 +41,7 @@ main_uninstall() {
|
|||||||
|
|
||||||
local stamp backup_dir
|
local stamp backup_dir
|
||||||
stamp="$(TZ="${TZ_NAME}" date +%Y%m%d-%H%M%S)"
|
stamp="$(TZ="${TZ_NAME}" date +%Y%m%d-%H%M%S)"
|
||||||
backup_dir="${BACKUP_ROOT}/pre-uninstall-${stamp}"
|
backup_dir="${BACKUP_ROOT}/pre-uninstall-slot${INSTANCE_SLOT}-${stamp}"
|
||||||
mkdir -p "${backup_dir}"
|
mkdir -p "${backup_dir}"
|
||||||
|
|
||||||
step "备份配置与数据"
|
step "备份配置与数据"
|
||||||
@@ -49,7 +53,9 @@ main_uninstall() {
|
|||||||
fi
|
fi
|
||||||
{
|
{
|
||||||
echo "created_at=${stamp}"
|
echo "created_at=${stamp}"
|
||||||
|
echo "instance_slot=${INSTANCE_SLOT}"
|
||||||
echo "install_root=${INSTALL_ROOT}"
|
echo "install_root=${INSTALL_ROOT}"
|
||||||
|
echo "api_port=${API_PORT}"
|
||||||
echo "pm2_app=${PM2_APP_NAME}"
|
echo "pm2_app=${PM2_APP_NAME}"
|
||||||
echo "action=rm_rf_install_root"
|
echo "action=rm_rf_install_root"
|
||||||
} >"${backup_dir}/uninstall.manifest"
|
} >"${backup_dir}/uninstall.manifest"
|
||||||
@@ -60,28 +66,23 @@ main_uninstall() {
|
|||||||
|
|
||||||
step "删除安装目录 ${INSTALL_ROOT}"
|
step "删除安装目录 ${INSTALL_ROOT}"
|
||||||
if [[ -d "${INSTALL_ROOT}" ]]; then
|
if [[ -d "${INSTALL_ROOT}" ]]; then
|
||||||
# 安全:只允许删除约定安装路径(默认 /opt/eth_hedge_sim)
|
if is_allowed_install_root "${INSTALL_ROOT}"; then
|
||||||
case "${INSTALL_ROOT}" in
|
rm -rf "${INSTALL_ROOT}"
|
||||||
/opt/eth_hedge_sim|/opt/eth_hedge_sim/)
|
log "已删除: ${INSTALL_ROOT}"
|
||||||
rm -rf "${INSTALL_ROOT}"
|
elif [[ "${ALLOW_UNSAFE_UNINSTALL:-}" == "1" ]]; then
|
||||||
log "已删除: ${INSTALL_ROOT}"
|
rm -rf "${INSTALL_ROOT}"
|
||||||
;;
|
log "已删除(ALLOW_UNSAFE_UNINSTALL=1): ${INSTALL_ROOT}"
|
||||||
*)
|
else
|
||||||
if [[ "${ALLOW_UNSAFE_UNINSTALL:-}" == "1" ]]; then
|
die "拒绝删除非约定路径 ${INSTALL_ROOT};允许: /opt/eth_hedge_sim[_2|_3];或 ALLOW_UNSAFE_UNINSTALL=1"
|
||||||
rm -rf "${INSTALL_ROOT}"
|
fi
|
||||||
log "已删除(ALLOW_UNSAFE_UNINSTALL=1): ${INSTALL_ROOT}"
|
|
||||||
else
|
|
||||||
die "拒绝删除非默认路径 ${INSTALL_ROOT};若确认,设置 ALLOW_UNSAFE_UNINSTALL=1"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
else
|
else
|
||||||
log "安装目录不存在,跳过删除"
|
log "安装目录不存在,跳过删除"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 清理历史「移走未删」残留
|
# 清理历史「移走未删」残留(仅当前槽位前缀)
|
||||||
local leftover
|
local leftover base
|
||||||
for leftover in /opt/eth_hedge_sim.removed.* /opt/eth_hedge_sim.old.*; do
|
base="${INSTALL_ROOT}"
|
||||||
|
for leftover in "${base}.removed."* "${base}.old."*; do
|
||||||
if [[ -e "${leftover}" ]]; then
|
if [[ -e "${leftover}" ]]; then
|
||||||
rm -rf "${leftover}"
|
rm -rf "${leftover}"
|
||||||
log "已清理残留: ${leftover}"
|
log "已清理残留: ${leftover}"
|
||||||
@@ -90,11 +91,13 @@ main_uninstall() {
|
|||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "卸载完成."
|
echo "卸载完成."
|
||||||
|
echo " 槽位: ${INSTANCE_SLOT}"
|
||||||
echo " 配置/数据备份: ${backup_dir}"
|
echo " 配置/数据备份: ${backup_dir}"
|
||||||
echo " 安装目录已删除: ${INSTALL_ROOT}"
|
echo " 安装目录已删除: ${INSTALL_ROOT}"
|
||||||
echo ""
|
echo ""
|
||||||
echo "重新部署:"
|
echo "重新部署该槽位:"
|
||||||
echo " curl -fsSL https://git.bz121.com/dekun/eth_hedge_sim/raw/branch/main/deploy/manage.sh | bash"
|
echo " bash /opt/eth_hedge_sim/deploy/manage.sh # 选 1 → 槽位 ${INSTANCE_SLOT}"
|
||||||
|
echo " 或: INSTANCE_SLOT=${INSTANCE_SLOT} SKIP_SLOT_PROMPT=1 bash …/deploy/lib/install.sh"
|
||||||
}
|
}
|
||||||
|
|
||||||
main_uninstall "$@"
|
main_uninstall "$@"
|
||||||
|
|||||||
+42
-6
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# deploy/lib/update.sh — 更新(仅 git pull,禁止 scp)
|
# deploy/lib/update.sh — 更新(仅 git pull,禁止 scp);支持单槽位或全部串行
|
||||||
set -e
|
set -e
|
||||||
set -u
|
set -u
|
||||||
if [ -n "${BASH_VERSION:-}" ]; then
|
if [ -n "${BASH_VERSION:-}" ]; then
|
||||||
@@ -10,22 +10,58 @@ LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|||||||
# shellcheck source=common.sh
|
# shellcheck source=common.sh
|
||||||
source "${LIB_DIR}/common.sh"
|
source "${LIB_DIR}/common.sh"
|
||||||
|
|
||||||
|
update_one_slot() {
|
||||||
|
local slot="$1"
|
||||||
|
resolve_slot "${slot}"
|
||||||
|
if ! repo_ready "${INSTALL_ROOT}"; then
|
||||||
|
log "槽位 ${slot} 未安装(${INSTALL_ROOT}),跳过"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
step "更新槽位 ${slot}: ${INSTALL_ROOT} :${API_PORT} ${PM2_APP_NAME}"
|
||||||
|
ensure_system_deps
|
||||||
|
bash "${INSTALL_ROOT}/deploy/pull_and_restart.sh"
|
||||||
|
verify_health "${API_PORT}" || true
|
||||||
|
log "槽位 ${slot} 更新完成"
|
||||||
|
}
|
||||||
|
|
||||||
|
update_all_slots() {
|
||||||
|
local s
|
||||||
|
local any=0
|
||||||
|
step "串行更新全部已安装策略机槽位(避免同机并行 npm build)"
|
||||||
|
for s in $(list_installed_slots); do
|
||||||
|
any=1
|
||||||
|
update_one_slot "${s}"
|
||||||
|
done
|
||||||
|
if [[ "${any}" -eq 0 ]]; then
|
||||||
|
die "未找到已安装的策略机(槽位 1/2/3)"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
log "全部槽位更新完成"
|
||||||
|
}
|
||||||
|
|
||||||
main_update() {
|
main_update() {
|
||||||
require_root
|
require_root
|
||||||
|
local mode="${EHS_UPDATE_MODE:-one}"
|
||||||
|
if [[ "${mode}" == "all" ]]; then
|
||||||
|
update_all_slots
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
resolve_slot "${INSTANCE_SLOT:-1}"
|
||||||
if ! REPO_ROOT="$(resolve_repo_root)"; then
|
if ! REPO_ROOT="$(resolve_repo_root)"; then
|
||||||
die "未找到安装目录 ${INSTALL_ROOT},请先执行「1) 一键部署策略机」"
|
die "未找到安装目录 ${INSTALL_ROOT},请先执行「1) 一键部署策略机」并选择该槽位"
|
||||||
fi
|
fi
|
||||||
if ! repo_ready "${REPO_ROOT}"; then
|
if ! repo_ready "${REPO_ROOT}"; then
|
||||||
die "安装不完整,请先执行「1) 一键部署策略机」"
|
die "安装不完整: ${REPO_ROOT}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
step "更新 — 环境检测 (缺则装,有则跳过)"
|
step "更新 — 环境检测 (缺则装,有则跳过)"
|
||||||
ensure_system_deps
|
ensure_system_deps
|
||||||
step "更新 比特骆驼自动化对冲系统 (git pull + 构建 + pm2 reload)"
|
step "更新槽位 ${INSTANCE_SLOT} (git pull + 构建 + pm2 reload)"
|
||||||
bash "${REPO_ROOT}/deploy/pull_and_restart.sh"
|
bash "${REPO_ROOT}/deploy/pull_and_restart.sh"
|
||||||
verify_health || true
|
verify_health "${API_PORT}" || true
|
||||||
echo ""
|
echo ""
|
||||||
log "更新完成"
|
log "更新完成 slot=${INSTANCE_SLOT} http://127.0.0.1:${API_PORT}/health"
|
||||||
}
|
}
|
||||||
|
|
||||||
main_update "$@"
|
main_update "$@"
|
||||||
|
|||||||
+51
-6
@@ -182,13 +182,15 @@ show_banner() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
show_menu() {
|
show_menu() {
|
||||||
echo " 1) 一键部署策略机"
|
echo " 1) 一键部署策略机(可选槽位 1/2/3)"
|
||||||
echo " 2) 一键部署中控机"
|
echo " 2) 一键部署中控机"
|
||||||
echo " 3) 一键卸载"
|
echo " 3) 一键卸载策略机(选槽位)"
|
||||||
echo " 4) 更新策略机"
|
echo " 4) 更新策略机(选槽位 / 全部)"
|
||||||
echo " 5) 更新中控机"
|
echo " 5) 更新中控机"
|
||||||
echo " 0) 退出"
|
echo " 0) 退出"
|
||||||
echo ""
|
echo ""
|
||||||
|
echo " 槽位: 1→:5155 2→:5156 3→:5157(同机最多三套,实盘请只用 1)"
|
||||||
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
# 交互读入(兼容 curl|bash)
|
# 交互读入(兼容 curl|bash)
|
||||||
@@ -224,6 +226,42 @@ deploy_control_node() {
|
|||||||
log "中控部署完成 → http://<本机IP>:5160 默认账号 admin / admin123"
|
log "中控部署完成 → http://<本机IP>:5160 默认账号 admin / admin123"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
run_with_slot_env() {
|
||||||
|
# 子脚本继承当前槽位变量
|
||||||
|
env \
|
||||||
|
INSTANCE_SLOT="${INSTANCE_SLOT}" \
|
||||||
|
INSTALL_ROOT="${INSTALL_ROOT}" \
|
||||||
|
API_PORT="${API_PORT}" \
|
||||||
|
PM2_APP_NAME="${PM2_APP_NAME}" \
|
||||||
|
SKIP_SLOT_PROMPT=1 \
|
||||||
|
EHS_FROM_MANAGE=1 \
|
||||||
|
"$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt_update_target() {
|
||||||
|
local line=""
|
||||||
|
echo ""
|
||||||
|
echo "更新目标:"
|
||||||
|
echo " 1/2/3) 指定槽位"
|
||||||
|
echo " a) 全部已安装槽位(串行,适合 2G 测机)"
|
||||||
|
cm_read line "请选择 [1/2/3/a,默认1]: "
|
||||||
|
line="${line:-1}"
|
||||||
|
case "${line}" in
|
||||||
|
a|A|all|ALL)
|
||||||
|
EHS_UPDATE_MODE=all
|
||||||
|
;;
|
||||||
|
1|2|3)
|
||||||
|
EHS_UPDATE_MODE=one
|
||||||
|
resolve_slot "${line}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "无效选择,使用槽位 1"
|
||||||
|
EHS_UPDATE_MODE=one
|
||||||
|
resolve_slot 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
main_menu() {
|
main_menu() {
|
||||||
bootstrap_repo
|
bootstrap_repo
|
||||||
# shellcheck source=lib/common.sh
|
# shellcheck source=lib/common.sh
|
||||||
@@ -237,16 +275,23 @@ main_menu() {
|
|||||||
cm_read choice "请选择 [0-5]: "
|
cm_read choice "请选择 [0-5]: "
|
||||||
case "${choice}" in
|
case "${choice}" in
|
||||||
1)
|
1)
|
||||||
bash "${LIB_DIR}/install.sh"
|
prompt_instance_slot
|
||||||
|
run_with_slot_env bash "${LIB_DIR}/install.sh"
|
||||||
;;
|
;;
|
||||||
2)
|
2)
|
||||||
deploy_control_node
|
deploy_control_node
|
||||||
;;
|
;;
|
||||||
3)
|
3)
|
||||||
bash "${LIB_DIR}/uninstall.sh"
|
prompt_instance_slot
|
||||||
|
run_with_slot_env bash "${LIB_DIR}/uninstall.sh"
|
||||||
;;
|
;;
|
||||||
4)
|
4)
|
||||||
bash "${LIB_DIR}/update.sh"
|
prompt_update_target
|
||||||
|
if [[ "${EHS_UPDATE_MODE}" == "all" ]]; then
|
||||||
|
env EHS_UPDATE_MODE=all SKIP_SLOT_PROMPT=1 bash "${LIB_DIR}/update.sh"
|
||||||
|
else
|
||||||
|
run_with_slot_env bash "${LIB_DIR}/update.sh"
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
5)
|
5)
|
||||||
deploy_control_node
|
deploy_control_node
|
||||||
|
|||||||
+32
-11
@@ -2,41 +2,57 @@
|
|||||||
# 一键更新/部署:仅 git pull,禁止 scp 传代码。
|
# 一键更新/部署:仅 git pull,禁止 scp 传代码。
|
||||||
# 用法:
|
# 用法:
|
||||||
# bash /opt/eth_hedge_sim/deploy/pull_and_restart.sh
|
# bash /opt/eth_hedge_sim/deploy/pull_and_restart.sh
|
||||||
# 或通过 manage.sh 选 1/3
|
# bash /opt/eth_hedge_sim_2/deploy/pull_and_restart.sh
|
||||||
|
# 或通过 manage.sh 选槽位后部署/更新
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
|
|
||||||
# 有 root 时:检测 Python / Node / PM2,缺则装、有则跳过(pull 前用当前脚本)
|
_COMMON="$ROOT/deploy/lib/common.sh"
|
||||||
if [[ -f "$ROOT/deploy/lib/common.sh" ]] && [[ "$(id -u)" -eq 0 ]]; then
|
if [[ -f "$_COMMON" ]]; then
|
||||||
# shellcheck source=lib/common.sh
|
# shellcheck source=lib/common.sh
|
||||||
source "$ROOT/deploy/lib/common.sh"
|
source "$_COMMON"
|
||||||
|
apply_slot_for_root "$ROOT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 有 root 时:检测 Python / Node / PM2,缺则装、有则跳过(pull 前用当前脚本)
|
||||||
|
if [[ -f "$_COMMON" ]] && [[ "$(id -u)" -eq 0 ]]; then
|
||||||
ensure_system_deps
|
ensure_system_deps
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "[比特骆驼] pull @ $ROOT"
|
echo "[比特骆驼] pull @ $ROOT (slot=${INSTANCE_SLOT:-?} port=${API_PORT:-?})"
|
||||||
if [[ -d "$ROOT/.git" ]]; then
|
if [[ -d "$ROOT/.git" ]]; then
|
||||||
git fetch --all --prune
|
git fetch --all --prune
|
||||||
git pull --ff-only
|
git pull --ff-only
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# pull 后再检测一次,确保用上仓库里最新的 python3-venv / ensurepip 逻辑
|
# pull 后再检测一次,确保用上仓库里最新的 python3-venv / ensurepip 逻辑
|
||||||
if [[ -f "$ROOT/deploy/lib/common.sh" ]] && [[ "$(id -u)" -eq 0 ]]; then
|
if [[ -f "$_COMMON" ]]; then
|
||||||
# shellcheck source=lib/common.sh
|
# shellcheck source=lib/common.sh
|
||||||
source "$ROOT/deploy/lib/common.sh"
|
source "$_COMMON"
|
||||||
|
apply_slot_for_root "$ROOT"
|
||||||
|
fi
|
||||||
|
if [[ -f "$_COMMON" ]] && [[ "$(id -u)" -eq 0 ]]; then
|
||||||
ensure_system_deps
|
ensure_system_deps
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -f "$ROOT/.env" ]]; then
|
if [[ ! -f "$ROOT/.env" ]]; then
|
||||||
echo "[比特骆驼] missing .env — copy from example"
|
echo "[比特骆驼] missing .env — copy from example"
|
||||||
cp "$ROOT/.env.example" "$ROOT/.env"
|
cp "$ROOT/.env.example" "$ROOT/.env"
|
||||||
# 云上测试机默认直连 OKX
|
# 云上测试机默认直连 OKX;端口按槽位
|
||||||
|
local_port="${API_PORT:-5155}"
|
||||||
sed -i 's/^OKX_HTTP_PROXY=.*/OKX_HTTP_PROXY=/' "$ROOT/.env" || true
|
sed -i 's/^OKX_HTTP_PROXY=.*/OKX_HTTP_PROXY=/' "$ROOT/.env" || true
|
||||||
sed -i 's/^API_PORT=.*/API_PORT=5155/' "$ROOT/.env" || true
|
sed -i "s/^API_PORT=.*/API_PORT=${local_port}/" "$ROOT/.env" || true
|
||||||
sed -i 's/^ENV_NAME=.*/ENV_NAME=test/' "$ROOT/.env" || true
|
sed -i 's/^ENV_NAME=.*/ENV_NAME=test/' "$ROOT/.env" || true
|
||||||
sed -i 's/^EXCHANGE=.*/EXCHANGE=okx/' "$ROOT/.env" || true
|
sed -i 's/^EXCHANGE=.*/EXCHANGE=okx/' "$ROOT/.env" || true
|
||||||
echo "[比特骆驼] wrote .env — please review AUTH_* secrets"
|
if [[ -f "$_COMMON" ]]; then
|
||||||
|
secret="$(random_auth_secret)"
|
||||||
|
if [[ -n "${secret}" ]]; then
|
||||||
|
sed -i "s/^AUTH_SECRET=.*/AUTH_SECRET=${secret}/" "$ROOT/.env" || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo "[比特骆驼] wrote .env — API_PORT=${local_port};请按需修改 AUTH_*"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! command -v python3 >/dev/null 2>&1; then
|
if ! command -v python3 >/dev/null 2>&1; then
|
||||||
@@ -93,8 +109,13 @@ else
|
|||||||
echo "[比特骆驼] pm2 already present, skip install"
|
echo "[比特骆驼] pm2 already present, skip install"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# 按本目录槽位重写 PM2 配置(避免 git pull 盖回槽位 1 硬编码)
|
||||||
|
if [[ -f "$_COMMON" ]]; then
|
||||||
|
write_ecosystem_config "$ROOT" "${PM2_APP_NAME}" "${API_PORT}"
|
||||||
|
fi
|
||||||
|
|
||||||
# 仅 reload 本项目进程,禁止 pm2 restart all
|
# 仅 reload 本项目进程,禁止 pm2 restart all
|
||||||
pm2 startOrReload "$ROOT/deploy/ecosystem.config.cjs" --update-env
|
pm2 startOrReload "$ROOT/deploy/ecosystem.config.cjs" --update-env
|
||||||
pm2 save
|
pm2 save
|
||||||
|
|
||||||
echo "[比特骆驼] done. health: http://127.0.0.1:5155/health"
|
echo "[比特骆驼] done. slot=${INSTANCE_SLOT:-1} health: http://127.0.0.1:${API_PORT:-5155}/health"
|
||||||
|
|||||||
+168
-147
@@ -1,147 +1,168 @@
|
|||||||
# 中控(Fleet Control)使用说明
|
# 中控(Fleet Control)使用说明
|
||||||
|
|
||||||
中控与策略机在**同一仓库**(`eth_hedge_sim`),**独立进程**部署。中控通常跑在**本地局域网服务器**,**不访问交易所**;只通过专用 API Token 对多台策略机做:状态监控、启停、远程更新代码、免密进入策略页。
|
中控与策略机在**同一仓库**(`eth_hedge_sim`),**独立进程**部署。中控通常跑在**本地局域网服务器**,**不访问交易所**;只通过专用 API Token 对多台策略机做:状态监控、启停、远程更新代码、免密进入策略页。
|
||||||
|
|
||||||
建议策略机与中控使用**同一 git 版本**(同 `main` / 同 tag),避免 `/api/fleet/*` 接口漂移。
|
建议策略机与中控使用**同一 git 版本**(同 `main` / 同 tag),避免 `/api/fleet/*` 接口漂移。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. 架构
|
## 1. 架构
|
||||||
|
|
||||||
```text
|
```text
|
||||||
本地中控 (5160) 云端策略机 × N (5155)
|
本地中控 (5160) 云端策略机 × N (5155)
|
||||||
┌─────────────────┐ ┌──────────────────┐
|
┌─────────────────┐ ┌──────────────────┐
|
||||||
│ control 前端/后端 │──X-Fleet-Token──│ eth-hedge-api │
|
│ control 前端/后端 │──X-Fleet-Token──│ eth-hedge-api │
|
||||||
│ 不连交易所 │──SSH 无需主路径──│ OKX/币安交易密钥 │
|
│ 不连交易所 │──SSH 无需主路径──│ OKX/币安交易密钥 │
|
||||||
└─────────────────┘ └──────────────────┘
|
└─────────────────┘ └──────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
| 组件 | 端口 | PM2 名 | 代码目录 | 配置 |
|
| 组件 | 端口 | PM2 名 | 代码目录 | 配置 |
|
||||||
|------|------|--------|----------|------|
|
|------|------|--------|----------|------|
|
||||||
| 策略机 | 5155 | `eth-hedge-api` | `backend/` + `frontend/` | 仓库根 `.env` |
|
| 策略机(槽位 1) | 5155 | `eth-hedge-api` | `/opt/eth_hedge_sim` | 该目录 `.env` |
|
||||||
| 中控 | 5160 | `eth-hedge-control` | `control/backend` + `control/frontend` | 仓库根 `.env.control` |
|
| 策略机(槽位 2,可选) | 5156 | `eth-hedge-api-2` | `/opt/eth_hedge_sim_2` | 独立 `.env` / SQLite |
|
||||||
|
| 策略机(槽位 3,可选) | 5157 | `eth-hedge-api-3` | `/opt/eth_hedge_sim_3` | 独立 `.env` / SQLite |
|
||||||
中控浏览器**不直连**策略机 API(避免跨域与密钥进前端);一律由中控后端代理。
|
| 中控 | 5160 | `eth-hedge-control` | `control/backend` + `control/frontend` | 仓库根 `.env.control` |
|
||||||
|
|
||||||
---
|
中控浏览器**不直连**策略机 API(避免跨域与密钥进前端);一律由中控后端代理。
|
||||||
|
|
||||||
## 2. 一键部署(manage.sh)
|
### 同机多套策略机(测试用)
|
||||||
|
|
||||||
目标机 Ubuntu 22.04:
|
- **实盘**:一机一套,只用**槽位 1**。
|
||||||
|
- **测试**:同一台机可再装槽位 2/3(完整独立克隆 + 独立进程),菜单选槽位即可;端口/PM2 名自动分配。
|
||||||
```bash
|
- 中控登记多套时,每套一条「策略机」:Base URL 带不同端口,例如 `http://x.x.x.x:5155`、`:5156`、`:5157`(或各自反代域名)。
|
||||||
curl -fsSL https://git.bz121.com/dekun/eth_hedge_sim/raw/branch/main/deploy/manage.sh | bash
|
- 更新「全部」为**串行**(避免 2G 内存并行 `npm build`)。
|
||||||
```
|
|
||||||
|
| 槽位 | 目录 | 端口 | PM2 |
|
||||||
交互菜单:
|
|------|------|------|-----|
|
||||||
|
| 1 | `/opt/eth_hedge_sim` | 5155 | `eth-hedge-api` |
|
||||||
| 选项 | 作用 |
|
| 2 | `/opt/eth_hedge_sim_2` | 5156 | `eth-hedge-api-2` |
|
||||||
|------|------|
|
| 3 | `/opt/eth_hedge_sim_3` | 5157 | `eth-hedge-api-3` |
|
||||||
| 1) 一键部署策略机 | 克隆/构建,启动 `eth-hedge-api` :5155 |
|
|
||||||
| 2) 一键部署中控机 | 构建中控,启动 `eth-hedge-control` :5160;自动补全 `.env.control`(**已有非空值不覆盖**) |
|
非交互示例:
|
||||||
| 3) 一键卸载 | 卸载策略相关(按现有 uninstall 逻辑) |
|
|
||||||
| 4) 更新策略机 | `git pull` + 构建 + reload 策略机 |
|
```bash
|
||||||
| 5) 更新中控机 | `control/deploy/update.sh` |
|
INSTANCE_SLOT=2 SKIP_SLOT_PROMPT=1 bash /opt/eth_hedge_sim/deploy/lib/install.sh
|
||||||
| 0) 退出 | — |
|
```
|
||||||
|
|
||||||
也可本机已安装后:
|
---
|
||||||
|
|
||||||
```bash
|
## 2. 一键部署(manage.sh)
|
||||||
bash /opt/eth_hedge_sim/deploy/manage.sh
|
|
||||||
# 或仅更新中控
|
目标机 Ubuntu 22.04:
|
||||||
bash /opt/eth_hedge_sim/control/deploy/update.sh
|
|
||||||
# 开发机 SSH 触发中控(可选)
|
```bash
|
||||||
# CONTROL_HOST=... CONTROL_PASS=... python scripts/deploy_control.py
|
curl -fsSL https://git.bz121.com/dekun/eth_hedge_sim/raw/branch/main/deploy/manage.sh | bash
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
交互菜单:
|
||||||
|
|
||||||
## 3. 中控登录与系统设置
|
| 选项 | 作用 |
|
||||||
|
|------|------|
|
||||||
### 3.1 默认账号
|
| 1) 一键部署策略机 | 选槽位 1/2/3 → 克隆/构建,启动对应端口 |
|
||||||
|
| 2) 一键部署中控机 | 构建中控,启动 `eth-hedge-control` :5160;自动补全 `.env.control`(**已有非空值不覆盖**) |
|
||||||
- 首次:`admin` / `admin123`(一键部署写入 `.env.control`,已有值不覆盖)。
|
| 3) 一键卸载 | 选槽位,只卸该套(不影响其它槽位) |
|
||||||
- **改密后**:登录页与设置页**不再显示**默认账号提示。
|
| 4) 更新策略机 | 选槽位,或 `a` 全部串行更新 |
|
||||||
- 登录框**不预填用户名**,并关闭浏览器自动填充。
|
| 5) 更新中控机 | `control/deploy/update.sh` |
|
||||||
|
| 0) 退出 | — |
|
||||||
### 3.2 系统设置(三个 Tab)
|
|
||||||
|
也可本机已安装后:
|
||||||
1. **中控登录账户**
|
|
||||||
- 修改用户名/密码(改后旧会话作废)。
|
```bash
|
||||||
- **本地局域网免登录**:开关;开启后,从私网 IP(如 `192.168.x` / `10.x` / `172.16–31.x` / 本机)访问可自动进入监控区,无需输密码。
|
bash /opt/eth_hedge_sim/deploy/manage.sh
|
||||||
2. **添加策略机** — 名称 + 公网 Base URL(如 `https://dc.hyf2.cc`)。
|
# 或仅更新中控
|
||||||
3. **策略机列表** — 生成/轮换 Token、删除。
|
bash /opt/eth_hedge_sim/control/deploy/update.sh
|
||||||
|
# 开发机 SSH 触发中控(可选)
|
||||||
### 3.3 `.env.control` 常用项
|
# CONTROL_HOST=... CONTROL_PASS=... python scripts/deploy_control.py
|
||||||
|
```
|
||||||
见仓库 [`.env.control.example`](../.env.control.example):
|
|
||||||
|
---
|
||||||
| 变量 | 说明 |
|
|
||||||
|------|------|
|
## 3. 中控登录与系统设置
|
||||||
| `CONTROL_AUTH_USERNAME` / `PASSWORD` | 中控登录 |
|
|
||||||
| `CONTROL_AUTH_SECRET` | Token 加密与签名密钥(部署自动填默认值,有值不覆盖) |
|
### 3.1 默认账号
|
||||||
| `CONTROL_LAN_AUTH_BYPASS` | `1`=局域网免登录,`0`=关闭 |
|
|
||||||
| `CONTROL_SSE_INTERVAL_SEC` | 监控 SSE 推送间隔(默认 1 秒) |
|
- 首次:`admin` / `admin123`(一键部署写入 `.env.control`,已有值不覆盖)。
|
||||||
| `CONTROL_SSE_PROBE_TIMEOUT_SEC` | 单次探测策略机超时(默认 2.5 秒) |
|
- **改密后**:登录页与设置页**不再显示**默认账号提示。
|
||||||
| `CONTROL_POLL_INTERVAL_SEC` | 兼容保留(旧前端轮询;现以 SSE 为主) |
|
- 登录框**不预填用户名**,并关闭浏览器自动填充。
|
||||||
|
|
||||||
**勿提交** `.env.control` 到 git。
|
### 3.2 系统设置(三个 Tab)
|
||||||
|
|
||||||
---
|
1. **中控登录账户**
|
||||||
|
- 修改用户名/密码(改后旧会话作废)。
|
||||||
## 4. 策略机配对(API Token)
|
- **本地局域网免登录**:开关;开启后,从私网 IP(如 `192.168.x` / `10.x` / `172.16–31.x` / 本机)访问可自动进入监控区,无需输密码。
|
||||||
|
2. **添加策略机** — 名称 + 公网 Base URL(如 `https://dc.hyf2.cc`)。
|
||||||
1. 中控 **策略机列表** → **生成 Token**(明文只显示一次,中控侧加密保存)。
|
3. **策略机列表** — 生成/轮换 Token、删除。
|
||||||
2. 登录该策略机 → **系统设置 → 登录账户 → 中控 API Token** → 粘贴保存(策略机存哈希)。
|
|
||||||
3. 中控监控区显示「已配对」;若 Token 不一致则「配对失败」并提示重新生成保存。
|
### 3.3 `.env.control` 常用项
|
||||||
|
|
||||||
### 策略机 Fleet 接口(头:`X-Fleet-Token`,也可 `Authorization: Fleet <token>`)
|
见仓库 [`.env.control.example`](../.env.control.example):
|
||||||
|
|
||||||
| 方法 | 路径 | 作用 |
|
| 变量 | 说明 |
|
||||||
|------|------|------|
|
|------|------|
|
||||||
| GET | `/api/fleet/status` | 状态摘要 + 持仓/策略详情 |
|
| `CONTROL_AUTH_USERNAME` / `PASSWORD` | 中控登录 |
|
||||||
| POST | `/api/fleet/start` | 启动策略(LIVE 仍过策略机门禁) |
|
| `CONTROL_AUTH_SECRET` | Token 加密与签名密钥(部署自动填默认值,有值不覆盖) |
|
||||||
| POST | `/api/fleet/pause` | 停止/暂停 |
|
| `CONTROL_LAN_AUTH_BYPASS` | `1`=局域网免登录,`0`=关闭 |
|
||||||
| POST | `/api/fleet/update` | 本机 `deploy/lib/update.sh`(reload,不自动 start) |
|
| `CONTROL_SSE_INTERVAL_SEC` | 监控 SSE 推送间隔(默认 1 秒) |
|
||||||
| POST | `/api/fleet/issue-login` | 签发一次性免密登录票 |
|
| `CONTROL_SSE_PROBE_TIMEOUT_SEC` | 单次探测策略机超时(默认 2.5 秒) |
|
||||||
|
| `CONTROL_POLL_INTERVAL_SEC` | 兼容保留(旧前端轮询;现以 SSE 为主) |
|
||||||
免密进策略页:中控拿到 ticket 后打开
|
|
||||||
`https://<策略机>/fleet-login?ticket=...`
|
**勿提交** `.env.control` 到 git。
|
||||||
→ `POST /api/auth/fleet-exchange` 兑换普通会话 → `/plan`。
|
|
||||||
票约 **60 秒、一次性**;长期 Token **不进**浏览器地址栏。
|
---
|
||||||
|
|
||||||
---
|
## 4. 策略机配对(API Token)
|
||||||
|
|
||||||
## 5. 监控区
|
1. 中控 **策略机列表** → **生成 Token**(明文只显示一次,中控侧加密保存)。
|
||||||
|
2. 登录该策略机 → **系统设置 → 登录账户 → 中控 API Token** → 粘贴保存(策略机存哈希)。
|
||||||
- **实时推送**:浏览器通过 SSE(`GET /api/nodes/status/stream`)接收状态;中控约每秒**并行**拉取各策略机 `/api/fleet/status`,有变化才推送;断线自动重连。工具栏显示「实时 · 1s」。
|
3. 中控监控区显示「已配对」;若 Token 不一致则「配对失败」并提示重新生成保存。
|
||||||
- **刷新**:仍可手动走 `/api/nodes/status/all` 拉一次。
|
|
||||||
- **卡片**:在线/离线、SIM/LIVE、阶段、持仓、轮次、行情、Token 状态。
|
### 策略机 Fleet 接口(头:`X-Fleet-Token`,也可 `Authorization: Fleet <token>`)
|
||||||
- **运行中**:卡片绿色;底部按钮显示「运行中」且不可点启动。
|
|
||||||
- **点击卡片**:放大弹层 — 策略详情 + 持仓腿表;**净浮盈 / 浮盈** 正绿负红加粗。
|
| 方法 | 路径 | 作用 |
|
||||||
- **登录策略机**:免密新标签打开策略页。
|
|------|------|------|
|
||||||
- **更新代码 / 勾选更新**:**二次确认**后执行;会 reload 进程,**不会**自动 start。
|
| GET | `/api/fleet/status` | 状态摘要 + 持仓/策略详情 |
|
||||||
|
| POST | `/api/fleet/start` | 启动策略(LIVE 仍过策略机门禁) |
|
||||||
中控**不做**:改策略参数、代开平仓、资金划转、直连交易所。
|
| POST | `/api/fleet/pause` | 停止/暂停 |
|
||||||
|
| POST | `/api/fleet/update` | 本机 `deploy/lib/update.sh`(reload,不自动 start) |
|
||||||
---
|
| POST | `/api/fleet/issue-login` | 签发一次性免密登录票 |
|
||||||
|
|
||||||
## 6. 安全边界
|
免密进策略页:中控拿到 ticket 后打开
|
||||||
|
`https://<策略机>/fleet-login?ticket=...`
|
||||||
- 中控无交易所 API Key;交易密钥只在各策略机 `.env`。
|
→ `POST /api/auth/fleet-exchange` 兑换普通会话 → `/plan`。
|
||||||
- Fleet Token 权限含启停/更新/签发登录票 → 泄露后两边立即轮换。
|
票约 **60 秒、一次性**;长期 Token **不进**浏览器地址栏。
|
||||||
- 局域网免登录仅私网 IP;勿对公网暴露中控端口。
|
|
||||||
- 策略机 LIVE 启停仍走本机 `live_ready()` 等门禁,中控不能绕过。
|
---
|
||||||
|
|
||||||
---
|
## 5. 监控区
|
||||||
|
|
||||||
## 7. 相关文档与入口
|
- **实时推送**:浏览器通过 SSE(`GET /api/nodes/status/stream`)接收状态;中控约每秒**并行**拉取各策略机 `/api/fleet/status`,有变化才推送;断线自动重连。工具栏显示「实时 · 1s」。
|
||||||
|
- **刷新**:仍可手动走 `/api/nodes/status/all` 拉一次。
|
||||||
| 文档 | 内容 |
|
- **卡片**:在线/离线、SIM/LIVE、阶段、持仓、轮次、行情、Token 状态。
|
||||||
|------|------|
|
- **运行中**:卡片绿色;底部按钮显示「运行中」且不可点启动。
|
||||||
| 本文 | 中控安装、配对、监控、安全 |
|
- **点击卡片**:放大弹层 — 策略详情 + 持仓腿表;**净浮盈 / 浮盈** 正绿负红加粗。
|
||||||
| [更新说明](./更新说明.md) | 发版变更记录 |
|
- **登录策略机**:免密新标签打开策略页。
|
||||||
| [代码结构](./代码结构.md) | 仓库目录(含 `control/`) |
|
- **更新代码 / 勾选更新**:**二次确认**后执行;会 reload 进程,**不会**自动 start。
|
||||||
| [README](../README.md) | 总览与部署入口 |
|
|
||||||
|
中控**不做**:改策略参数、代开平仓、资金划转、直连交易所。
|
||||||
产品文档索引(README「文档」一节)含本页链接。
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 安全边界
|
||||||
|
|
||||||
|
- 中控无交易所 API Key;交易密钥只在各策略机 `.env`。
|
||||||
|
- Fleet Token 权限含启停/更新/签发登录票 → 泄露后两边立即轮换。
|
||||||
|
- 局域网免登录仅私网 IP;勿对公网暴露中控端口。
|
||||||
|
- 策略机 LIVE 启停仍走本机 `live_ready()` 等门禁,中控不能绕过。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 相关文档与入口
|
||||||
|
|
||||||
|
| 文档 | 内容 |
|
||||||
|
|------|------|
|
||||||
|
| 本文 | 中控安装、配对、监控、安全 |
|
||||||
|
| [更新说明](./更新说明.md) | 发版变更记录 |
|
||||||
|
| [代码结构](./代码结构.md) | 仓库目录(含 `control/`) |
|
||||||
|
| [README](../README.md) | 总览与部署入口 |
|
||||||
|
|
||||||
|
产品文档索引(README「文档」一节)含本页链接。
|
||||||
|
|||||||
Reference in New Issue
Block a user