deploy: 一键部署内置 locale/时区/SimNow 前置探测与 CTP 验证
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
# 国内期货监控系统 - Ubuntu 一键部署
|
||||
# 国内期货监控系统 - Ubuntu 一键部署 / 更新
|
||||
# root 用户 | 目录 /opt/qihuo | 端口 6600 | PM2
|
||||
#
|
||||
# 已内置修复(避免重复踩坑):
|
||||
# - vnpy_ctp 编译:build-essential python3-dev pkg-config
|
||||
# - CTP 登录崩溃:zh_CN.GB18030 + zh_CN.UTF-8 locale
|
||||
# - 时区:Asia/Shanghai(与 SimNow 交易时段一致)
|
||||
# - SimNow 前置:自动探测可用线路并写入 .env
|
||||
# - PM2 环境变量:LANG/LC_ALL(见 ecosystem.config.cjs)
|
||||
# - .env 缺项补全:SIMNOW_ENV、CTP_AUTO_RECONNECT
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -8,12 +16,81 @@ APP_DIR="/opt/qihuo"
|
||||
REPO_URL="https://git.bz121.com/dekun/qihuo.git"
|
||||
SERVICE_NAME="qihuo"
|
||||
|
||||
# SimNow 前置候选(按优先级;部署时自动 nc 探测)
|
||||
SIMNOW_FRONTS=(
|
||||
"182.254.243.31:30001:30011"
|
||||
"180.168.146.187:10201:10211"
|
||||
"180.168.146.187:10130:10131"
|
||||
"218.202.237.33:10203:10213"
|
||||
)
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "请使用 root 用户运行: sudo bash deploy.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
probe_tcp() {
|
||||
local host="$1" port="$2"
|
||||
if command -v nc &>/dev/null; then
|
||||
nc -z -w 3 "$host" "$port" &>/dev/null
|
||||
return $?
|
||||
fi
|
||||
timeout 3 bash -c "echo >/dev/tcp/${host}/${port}" 2>/dev/null
|
||||
}
|
||||
|
||||
ensure_locale_gen() {
|
||||
local pat="$1"
|
||||
if [ -f /etc/locale.gen ] && grep -q "^# ${pat}" /etc/locale.gen; then
|
||||
sed -i "s/^# ${pat}/${pat}/" /etc/locale.gen
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_env_key() {
|
||||
local file="$1" key="$2" val="$3"
|
||||
if [ ! -f "$file" ]; then
|
||||
return
|
||||
fi
|
||||
if grep -q "^${key}=" "$file"; then
|
||||
return
|
||||
fi
|
||||
echo "${key}=${val}" >>"$file"
|
||||
echo " .env 补全: ${key}=${val}"
|
||||
}
|
||||
|
||||
pick_simnow_front() {
|
||||
local host td_port md_port
|
||||
for entry in "${SIMNOW_FRONTS[@]}"; do
|
||||
IFS=: read -r host td_port md_port <<<"$entry"
|
||||
if probe_tcp "$host" "$td_port" && probe_tcp "$host" "$md_port"; then
|
||||
echo "tcp://${host}:${td_port}|tcp://${host}:${md_port}"
|
||||
return 0
|
||||
fi
|
||||
echo " SimNow 前置不可达: ${host} ${td_port}/${md_port}" >&2
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
update_simnow_front_in_env() {
|
||||
local env_file="$1"
|
||||
local picked td md
|
||||
picked="$(pick_simnow_front)" || {
|
||||
echo "警告: 未能探测到可用 SimNow 前置,请手动编辑 ${env_file}(见 docs/SIMNOW.md)"
|
||||
return 1
|
||||
}
|
||||
td="${picked%%|*}"
|
||||
md="${picked##*|}"
|
||||
echo "==> SimNow 可用前置: TD=${td} MD=${md}"
|
||||
if grep -q "^SIMNOW_TD_ADDRESS=" "$env_file"; then
|
||||
sed -i "s|^SIMNOW_TD_ADDRESS=.*|SIMNOW_TD_ADDRESS=${td}|" "$env_file"
|
||||
sed -i "s|^SIMNOW_MD_ADDRESS=.*|SIMNOW_MD_ADDRESS=${md}|" "$env_file"
|
||||
else
|
||||
echo "SIMNOW_TD_ADDRESS=${td}" >>"$env_file"
|
||||
echo "SIMNOW_MD_ADDRESS=${md}" >>"$env_file"
|
||||
fi
|
||||
}
|
||||
|
||||
echo "==> 检查系统依赖..."
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -qq
|
||||
|
||||
need_install() {
|
||||
@@ -26,14 +103,28 @@ need_install python3 python3
|
||||
need_install python3-venv python3-venv
|
||||
need_install git git
|
||||
|
||||
# vnpy_ctp 在 Linux 上需本地编译(Meson + pkg-config 查找 python3-dev)
|
||||
echo "==> 安装 vnpy_ctp 编译依赖与 CTP 中文 locale..."
|
||||
apt-get install -y build-essential python3-dev pkg-config locales
|
||||
# CTP C++ API 登录回调需要 zh_CN.GB18030(vnpy/vnpy_ctp#24)
|
||||
sed -i '/^# zh_CN.GB18030/s/^# //' /etc/locale.gen 2>/dev/null || true
|
||||
sed -i '/^# zh_CN.UTF-8/s/^# //' /etc/locale.gen 2>/dev/null || true
|
||||
echo "==> 安装 vnpy_ctp 编译依赖..."
|
||||
apt-get install -y build-essential python3-dev pkg-config locales netcat-openbsd
|
||||
|
||||
echo "==> 配置时区 Asia/Shanghai..."
|
||||
if command -v timedatectl &>/dev/null; then
|
||||
timedatectl set-timezone Asia/Shanghai || true
|
||||
fi
|
||||
|
||||
echo "==> 配置 CTP 所需 locale(zh_CN.GB18030 等)..."
|
||||
ensure_locale_gen "zh_CN.GB18030 GB18030"
|
||||
ensure_locale_gen "zh_CN.UTF-8 UTF-8"
|
||||
ensure_locale_gen "en_US.UTF-8 UTF-8"
|
||||
locale-gen zh_CN.GB18030 zh_CN.UTF-8 en_US.UTF-8 2>/dev/null || locale-gen
|
||||
update-locale LANG=zh_CN.UTF-8 LC_ALL=zh_CN.UTF-8 2>/dev/null || true
|
||||
export LANG=zh_CN.UTF-8
|
||||
export LC_ALL=zh_CN.UTF-8
|
||||
|
||||
if ! locale -a 2>/dev/null | grep -qi gb18030; then
|
||||
echo "错误: zh_CN.GB18030 未生成,CTP 连接后会崩溃"
|
||||
exit 1
|
||||
fi
|
||||
echo " locale OK: $(locale -a 2>/dev/null | grep -i gb18030 | head -1)"
|
||||
|
||||
if ! command -v pm2 &>/dev/null; then
|
||||
echo "==> 安装 PM2..."
|
||||
@@ -64,33 +155,61 @@ echo "==> Python 虚拟环境与依赖..."
|
||||
if [ ! -d "$APP_DIR/venv" ]; then
|
||||
python3 -m venv "$APP_DIR/venv"
|
||||
fi
|
||||
# shellcheck disable=SC1091
|
||||
source "$APP_DIR/venv/bin/activate"
|
||||
pip install --upgrade pip -q
|
||||
pip install -r "$APP_DIR/requirements.txt"
|
||||
python -c "from vnpy_ctp import CtpGateway; print('vnpy_ctp OK')"
|
||||
|
||||
echo "==> 配置 .env..."
|
||||
if [ ! -f "$APP_DIR/.env" ]; then
|
||||
echo "==> 生成 .env(请编辑 ADMIN_PASSWORD 后重启)..."
|
||||
cp "$APP_DIR/.env.example" "$APP_DIR/.env"
|
||||
RAND_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))")
|
||||
sed -i "s/change-this-to-a-random-secret-key/${RAND_KEY}/" "$APP_DIR/.env"
|
||||
echo " 已生成 .env,请编辑 SIMNOW_USER / ADMIN_PASSWORD"
|
||||
fi
|
||||
ensure_env_key "$APP_DIR/.env" "SIMNOW_ENV" "实盘"
|
||||
ensure_env_key "$APP_DIR/.env" "CTP_AUTO_RECONNECT" "true"
|
||||
ensure_env_key "$APP_DIR/.env" "SIMNOW_BROKER_ID" "9999"
|
||||
ensure_env_key "$APP_DIR/.env" "SIMNOW_APP_ID" "simnow_client_test"
|
||||
ensure_env_key "$APP_DIR/.env" "SIMNOW_AUTH_CODE" "0000000000000000"
|
||||
update_simnow_front_in_env "$APP_DIR/.env" || true
|
||||
|
||||
mkdir -p "$APP_DIR/logs"
|
||||
|
||||
echo "==> 验证 CTP 环境..."
|
||||
if grep -q "^SIMNOW_USER=.\+" "$APP_DIR/.env" 2>/dev/null && \
|
||||
grep -q "^SIMNOW_PASSWORD=.\+" "$APP_DIR/.env" 2>/dev/null; then
|
||||
set +e
|
||||
python "$APP_DIR/scripts/test_simnow.py"
|
||||
CTP_TEST=$?
|
||||
set -e
|
||||
if [ "$CTP_TEST" -ne 0 ]; then
|
||||
echo "警告: SimNow 连接测试未通过,请检查 .env 账号与网络(见 docs/SIMNOW.md)"
|
||||
else
|
||||
echo " SimNow CTP 连接测试通过"
|
||||
fi
|
||||
else
|
||||
echo " 跳过 CTP 测试(未配置 SIMNOW_USER / SIMNOW_PASSWORD)"
|
||||
fi
|
||||
|
||||
echo "==> PM2 启动/重启服务..."
|
||||
cd "$APP_DIR"
|
||||
pm2 delete "$SERVICE_NAME" 2>/dev/null || true
|
||||
pm2 start ecosystem.config.cjs
|
||||
if pm2 describe "$SERVICE_NAME" &>/dev/null; then
|
||||
pm2 restart ecosystem.config.cjs --update-env
|
||||
else
|
||||
pm2 start ecosystem.config.cjs
|
||||
fi
|
||||
pm2 save
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " 部署完成"
|
||||
echo " 目录: ${APP_DIR}"
|
||||
echo " 用户: root"
|
||||
echo " 时区: $(date +%Z) $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
echo " 端口: 6600"
|
||||
echo " 访问: http://<服务器IP>:6600"
|
||||
echo " 访问: http://$(hostname -I 2>/dev/null | awk '{print $1}'):6600"
|
||||
echo " 日志: pm2 logs ${SERVICE_NAME}"
|
||||
echo " SimNow 注册: docs/SIMNOW.md"
|
||||
echo " 开机自启: pm2 startup && pm2 save"
|
||||
echo "=========================================="
|
||||
|
||||
Reference in New Issue
Block a user