deploy: 一键部署内置 locale/时区/SimNow 前置探测与 CTP 验证
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+1
-1
@@ -26,7 +26,7 @@ CTP_AUTO_RECONNECT=true
|
|||||||
SIMNOW_USER=
|
SIMNOW_USER=
|
||||||
SIMNOW_PASSWORD=
|
SIMNOW_PASSWORD=
|
||||||
SIMNOW_BROKER_ID=9999
|
SIMNOW_BROKER_ID=9999
|
||||||
# 7×24 环境示例(以 SimNow 官网最新为准)
|
# 7×24 / 日盘前置(deploy.sh 会自动 nc 探测并写入可用线路)
|
||||||
SIMNOW_TD_ADDRESS=tcp://180.168.146.187:10201
|
SIMNOW_TD_ADDRESS=tcp://180.168.146.187:10201
|
||||||
SIMNOW_MD_ADDRESS=tcp://180.168.146.187:10211
|
SIMNOW_MD_ADDRESS=tcp://180.168.146.187:10211
|
||||||
SIMNOW_APP_ID=simnow_client_test
|
SIMNOW_APP_ID=simnow_client_test
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# 国内期货监控系统 - Ubuntu 一键部署
|
# 国内期货监控系统 - Ubuntu 一键部署 / 更新
|
||||||
# root 用户 | 目录 /opt/qihuo | 端口 6600 | PM2
|
# 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
|
set -euo pipefail
|
||||||
|
|
||||||
@@ -8,12 +16,81 @@ APP_DIR="/opt/qihuo"
|
|||||||
REPO_URL="https://git.bz121.com/dekun/qihuo.git"
|
REPO_URL="https://git.bz121.com/dekun/qihuo.git"
|
||||||
SERVICE_NAME="qihuo"
|
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
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
echo "请使用 root 用户运行: sudo bash deploy.sh"
|
echo "请使用 root 用户运行: sudo bash deploy.sh"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
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 "==> 检查系统依赖..."
|
echo "==> 检查系统依赖..."
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
apt-get update -qq
|
apt-get update -qq
|
||||||
|
|
||||||
need_install() {
|
need_install() {
|
||||||
@@ -26,14 +103,28 @@ need_install python3 python3
|
|||||||
need_install python3-venv python3-venv
|
need_install python3-venv python3-venv
|
||||||
need_install git git
|
need_install git git
|
||||||
|
|
||||||
# vnpy_ctp 在 Linux 上需本地编译(Meson + pkg-config 查找 python3-dev)
|
echo "==> 安装 vnpy_ctp 编译依赖..."
|
||||||
echo "==> 安装 vnpy_ctp 编译依赖与 CTP 中文 locale..."
|
apt-get install -y build-essential python3-dev pkg-config locales netcat-openbsd
|
||||||
apt-get install -y build-essential python3-dev pkg-config locales
|
|
||||||
# CTP C++ API 登录回调需要 zh_CN.GB18030(vnpy/vnpy_ctp#24)
|
echo "==> 配置时区 Asia/Shanghai..."
|
||||||
sed -i '/^# zh_CN.GB18030/s/^# //' /etc/locale.gen 2>/dev/null || true
|
if command -v timedatectl &>/dev/null; then
|
||||||
sed -i '/^# zh_CN.UTF-8/s/^# //' /etc/locale.gen 2>/dev/null || true
|
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
|
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
|
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
|
if ! command -v pm2 &>/dev/null; then
|
||||||
echo "==> 安装 PM2..."
|
echo "==> 安装 PM2..."
|
||||||
@@ -64,33 +155,61 @@ echo "==> Python 虚拟环境与依赖..."
|
|||||||
if [ ! -d "$APP_DIR/venv" ]; then
|
if [ ! -d "$APP_DIR/venv" ]; then
|
||||||
python3 -m venv "$APP_DIR/venv"
|
python3 -m venv "$APP_DIR/venv"
|
||||||
fi
|
fi
|
||||||
|
# shellcheck disable=SC1091
|
||||||
source "$APP_DIR/venv/bin/activate"
|
source "$APP_DIR/venv/bin/activate"
|
||||||
pip install --upgrade pip -q
|
pip install --upgrade pip -q
|
||||||
pip install -r "$APP_DIR/requirements.txt"
|
pip install -r "$APP_DIR/requirements.txt"
|
||||||
python -c "from vnpy_ctp import CtpGateway; print('vnpy_ctp OK')"
|
python -c "from vnpy_ctp import CtpGateway; print('vnpy_ctp OK')"
|
||||||
|
|
||||||
|
echo "==> 配置 .env..."
|
||||||
if [ ! -f "$APP_DIR/.env" ]; then
|
if [ ! -f "$APP_DIR/.env" ]; then
|
||||||
echo "==> 生成 .env(请编辑 ADMIN_PASSWORD 后重启)..."
|
|
||||||
cp "$APP_DIR/.env.example" "$APP_DIR/.env"
|
cp "$APP_DIR/.env.example" "$APP_DIR/.env"
|
||||||
RAND_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))")
|
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"
|
sed -i "s/change-this-to-a-random-secret-key/${RAND_KEY}/" "$APP_DIR/.env"
|
||||||
|
echo " 已生成 .env,请编辑 SIMNOW_USER / ADMIN_PASSWORD"
|
||||||
fi
|
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"
|
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 启动/重启服务..."
|
echo "==> PM2 启动/重启服务..."
|
||||||
cd "$APP_DIR"
|
cd "$APP_DIR"
|
||||||
pm2 delete "$SERVICE_NAME" 2>/dev/null || true
|
if pm2 describe "$SERVICE_NAME" &>/dev/null; then
|
||||||
pm2 start ecosystem.config.cjs
|
pm2 restart ecosystem.config.cjs --update-env
|
||||||
|
else
|
||||||
|
pm2 start ecosystem.config.cjs
|
||||||
|
fi
|
||||||
pm2 save
|
pm2 save
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo " 部署完成"
|
echo " 部署完成"
|
||||||
echo " 目录: ${APP_DIR}"
|
echo " 目录: ${APP_DIR}"
|
||||||
echo " 用户: root"
|
echo " 时区: $(date +%Z) $(date '+%Y-%m-%d %H:%M:%S')"
|
||||||
echo " 端口: 6600"
|
echo " 端口: 6600"
|
||||||
echo " 访问: http://<服务器IP>:6600"
|
echo " 访问: http://$(hostname -I 2>/dev/null | awk '{print $1}'):6600"
|
||||||
echo " 日志: pm2 logs ${SERVICE_NAME}"
|
echo " 日志: pm2 logs ${SERVICE_NAME}"
|
||||||
|
echo " SimNow 注册: docs/SIMNOW.md"
|
||||||
echo " 开机自启: pm2 startup && pm2 save"
|
echo " 开机自启: pm2 startup && pm2 save"
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
|
|||||||
+17
-6
@@ -46,14 +46,20 @@ bash deploy.sh
|
|||||||
|
|
||||||
`deploy.sh` 会自动完成:
|
`deploy.sh` 会自动完成:
|
||||||
|
|
||||||
1. 安装 `python3`、`python3-venv`、`git`、`nodejs`、`npm`、`pm2`
|
1. 安装系统依赖:`python3`、`git`、`build-essential`、`python3-dev`、`pkg-config`、`locales`、`netcat-openbsd`、`pm2`
|
||||||
2. `git pull` 或 `git clone` 到 `/opt/qihuo`
|
2. **时区**设为 `Asia/Shanghai`(与 SimNow 交易时段一致)
|
||||||
3. 创建虚拟环境 `venv` 并 `pip install -r requirements.txt`
|
3. **locale**:生成 `zh_CN.GB18030`、`zh_CN.UTF-8`(CTP 登录必需,缺则进程崩溃)
|
||||||
4. 首次生成 `.env`(随机 `SECRET_KEY`)
|
4. `git pull` 或 `git clone` 到 `/opt/qihuo`
|
||||||
5. `pm2 start ecosystem.config.cjs` 并 `pm2 save`
|
5. 创建/保留虚拟环境 `venv`,`pip install -r requirements.txt`,验证 `vnpy_ctp`
|
||||||
|
6. 首次生成 `.env`,并补全 `SIMNOW_ENV=实盘`、`CTP_AUTO_RECONNECT=true` 等缺项
|
||||||
|
7. **自动探测 SimNow 前置**(`nc` 测端口),写入可用的 `SIMNOW_TD/MD_ADDRESS`(优先 `182.254.243.31`,其次 `180.168.146.187`)
|
||||||
|
8. 若已配置 SimNow 账号,运行 `scripts/test_simnow.py` 验证连接
|
||||||
|
9. `pm2 restart --update-env` 或首次 `pm2 start`,并 `pm2 save`
|
||||||
|
|
||||||
部署完成后访问:`http://<服务器IP>:6600`
|
部署完成后访问:`http://<服务器IP>:6600`
|
||||||
|
|
||||||
|
> 再次部署只需 `cd /opt/qihuo && bash deploy.sh`,无需手工装 locale 或改前置地址。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 手动部署
|
## 手动部署
|
||||||
@@ -62,7 +68,12 @@ bash deploy.sh
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
apt update
|
apt update
|
||||||
apt install -y python3 python3-venv python3-pip python3-dev pkg-config git nodejs npm build-essential
|
apt install -y python3 python3-venv python3-pip python3-dev pkg-config git nodejs npm build-essential locales netcat-openbsd
|
||||||
|
timedatectl set-timezone Asia/Shanghai
|
||||||
|
sed -i '/^# zh_CN.GB18030/s/^# //' /etc/locale.gen
|
||||||
|
sed -i '/^# zh_CN.UTF-8/s/^# //' /etc/locale.gen
|
||||||
|
locale-gen zh_CN.GB18030 zh_CN.UTF-8
|
||||||
|
update-locale LANG=zh_CN.UTF-8 LC_ALL=zh_CN.UTF-8
|
||||||
npm install -g pm2
|
npm install -g pm2
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user