docs(deploy): VPS生产runbook+三机环境矩阵+NAS ops脚本+修rsync危险命令
vps-production-runbook(VPS运维一站式,已验证状态+拓扑+发布流水线+人类闸口+排障); env-version-matrix(三机Python/deps矩阵+Lock建议); scripts/ops(NAS bridge探针-容器无curl-+VPS→NAS备份脚本); nas-deploy-plan§3(修rsync --exclude语法,原会删万级staging parquet+破坏entrypoint启动).
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
# 三机环境版本矩阵(Mac 开发 / NAS 容器 / Windows VPS)
|
||||
|
||||
> 创建:2026-07-14。目的:记录三机 Python + 关键依赖版本现状,暴露不一致,给出 Lock 建议。
|
||||
> 数据来源:Mac venv311 + NAS 容器均经实地 `pip list` 核实;VPS 基于部署文档 `vps-production-runbook.md`,未本轮直接 SSH 复核(SSH 不通)。
|
||||
|
||||
---
|
||||
|
||||
## 1. 机器角色一览
|
||||
|
||||
| 机器 | 角色 | Python | 虚拟环境 / 路径 | 数据来源 |
|
||||
|------|------|--------|-----------------|----------|
|
||||
| **Mac Mini** | 开发 + 测试 | 3.11.15(venv311) | `./venv311/`(项目内) | 实地 `pip list` |
|
||||
| Mac 系统 Python | 不参与项目 | 3.14.6(homebrew)/ 3.9.6(/usr/bin) | — | `python3 --version` |
|
||||
| **NAS 容器** `sanguo_vnpy_v2` | 测试 + 回测 + 采集(生产近邻) | 3.10.20 | `/app`(容器内)= NAS `/volume1/homes/admin/.sanguo_projects/sanguo_vnpy_v2` | 实地 `docker exec ... pip list` |
|
||||
| **Windows VPS** `49.232.102.198` | 生产(miniQMT + bridge) | 3.10.11 | `C:\Python310\python.exe` | 部署文档(本轮未直连) |
|
||||
|
||||
---
|
||||
|
||||
## 2. 关键依赖版本矩阵
|
||||
|
||||
> 版本号 = `pip show` 的 Version 字段;❌ = 未安装;🚫 = 平台不兼容(Windows only);🔍 = 仅有源码引用(`sys.path.insert`)未 pip install。
|
||||
|
||||
| 依赖 | Mac venv311 (3.11.15) | NAS 容器 (3.10.20) | Windows VPS (3.10.11) | 备注 |
|
||||
|------|----------------------|--------------------|-----------------------|------|
|
||||
| **python** | 3.11.15 | 3.10.20 | 3.10.11 | ⚠️ Mac 比 prod 高一个小版本 |
|
||||
| **vnpy** | 🔍 源码引用(`vnpy_v4.4.0/`) | 🔍 源码引用(`/app/vnpy_v4.4.0`)+ `vnpy 4.4.0` pip metadata | ❌ 不装(VPS 只跑 bridge) | Mac 容器均靠 `sys.path.insert` 引用源码,**不 pip install vnpy** |
|
||||
| vnpy_ctastrategy | ❌ | 1.4.1 | ❌ | |
|
||||
| vnpy_sqlite | ❌ | 1.1.3 | ❌ | |
|
||||
| **fastapi** | 0.139.0(本次新装) | 0.139.0 | ✅ 装了(文档未给版本) | 一致 |
|
||||
| **uvicorn** | 0.51.0(本次新装) | 0.49.0 | ✅ 装了 | 小版本差 |
|
||||
| **akshare** | ❌ | ❌ | ❌ | 三机都没装;日线采集走 NAS 宿主系统 python(akshare 在 NAS 宿主,不在容器里) |
|
||||
| **baostock** | 0.9.3 | 0.9.3 | ❌ | Mac/NAS 一致 |
|
||||
| **polars** | 1.42.1(本次新装) | 1.42.1 | ❌ | Mac/NAS 一致;Mac 此前缺失致 collect-only 失败 |
|
||||
| **xtquant** | 🚫 Windows only | 🚫 Windows only | ✅(miniQMT site-packages) | VPS 专属,靠 miniQMT 客户端 |
|
||||
| **pandas** | 3.0.3 | 2.3.3 | ? | 🔴 **major 版本分裂**(Mac=3.x / NAS=2.x) |
|
||||
| **numpy** | 1.26.4 | 2.2.6 | ? | 🔴 **major 版本分裂**(Mac=1.x / NAS=2.x) |
|
||||
| **pyarrow** | 25.0.0 | 24.0.0 | ❌ | 小版本差 |
|
||||
| **PyJWT** | 2.13.0(本次新装) | 2.13.0 | ❌ | 一致 |
|
||||
| **bcrypt** | 5.0.0(本次新装) | 5.0.0 | ❌ | 一致 |
|
||||
| **TA-Lib** | ❌ | 0.6.8 | ❌ | Mac 缺(需 brew install ta-lib) |
|
||||
| **pyzmq** | ❌ | 27.1.0 | ❌ | Mac 缺 |
|
||||
| **PySide6** | ❌ | 6.8.2.1 | ❌ | Mac 缺(GUI 依赖,dev 可选) |
|
||||
| **SQLAlchemy** | ❌ | 2.0.51 | ❌ | Mac 缺 |
|
||||
| **redis** | ❌ | 8.0.1 | ❌ | Mac 缺 |
|
||||
| **APScheduler** | ❌ | 3.11.3 | ❌ | Mac 缺 |
|
||||
| **deap** | ❌ | 1.4.4 | ❌ | Mac 缺(遗传算法,回测用) |
|
||||
| **plotly** | ❌ | 6.8.0 | ❌ | Mac 缺 |
|
||||
| **scipy** | 1.17.1 | 未列(应已装) | ❌ | Mac 有 |
|
||||
| **pytest** | 9.1.1 | 未列 | ❌ | dev 工具 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 当前发现的不一致(按严重度)
|
||||
|
||||
### 🔴 CRITICAL —— 可能在 Mac 跑通但在 prod 静默踩坑
|
||||
|
||||
1. **pandas major 版本分裂**:Mac venv311 = **pandas 3.0.3**,NAS 容器 = **pandas 2.3.3**。
|
||||
- pandas 3.0 有大量破坏性变更(默认 dtype、`chained assignment`、`SettingWithCopyWarning` 升级为异常等)。
|
||||
- 在 Mac 过的测试,到 NAS 容器可能因为 pandas 3→2 的 API 差异失败或行为不同。
|
||||
- **这是最危险的不一致**:单测绿不代表行为一致。
|
||||
|
||||
2. **numpy major 版本分裂**:Mac venv311 = **numpy 1.26.4**,NAS 容器 = **numpy 2.2.6**。
|
||||
- numpy 2.0 有 breaking change(部分标量类型 Promotion 规则改变、`np.float_` 移除等)。
|
||||
- 方向与 pandas 相反:Mac 反而比 prod 旧。
|
||||
|
||||
3. **Python minor 分裂**:Mac venv311 = **3.11.15**,NAS/VPS = **3.10.x**。
|
||||
- 影响有限但存在(如 `match` 语法、`tomllib`、`ExceptionGroup`、类型 Hint 差异)。
|
||||
- `pyproject.toml` 已声明 `requires-python = ">=3.10"`,理论上 3.11 合法,但偏离 prod。
|
||||
|
||||
### 🟡 WARNING —— Mac venv311 依赖不完整(已知)
|
||||
|
||||
4. **venv311 缺核心运行时依赖**(本轮未补,按任务约束只做 collect-only + trader 子集):
|
||||
- `TA-Lib`(需先 `brew install ta-lib`,C 库依赖)
|
||||
- `pyzmq` / `SQLAlchemy` / `redis` / `APScheduler` / `deap` / `plotly` / `PySide6`
|
||||
- `akshare`(Mac 完全没装,日线采集靠 NAS 宿主 python)
|
||||
- `vnpy`(源码引用,故意不 pip install —— 容器也是源码引用)
|
||||
|
||||
5. **VPS 信息未本轮 SSH 复核**:49.232.102.198:22 Connection closed,本轮失败。文档记录的版本来自 `vps-production-runbook.md`,标注日期前的核实结果。
|
||||
|
||||
### 🟢 INFO —— 可接受的小差异
|
||||
|
||||
6. **polars/pandas/pyarrow 小版本 drift**(pyarrow 25 vs 24、uvicorn 0.51 vs 0.49):patch/minor 差异,影响小。
|
||||
|
||||
---
|
||||
|
||||
## 4. Lock 建议(推荐方案,不执行)
|
||||
|
||||
### 推荐基线:对齐 NAS 容器(最稳定的生产近邻)
|
||||
|
||||
> 理由:NAS 容器是当前依赖最齐、跑得最稳的环境;VPS 只跑 bridge 子集,依赖面窄;Mac 是开发机,应模拟 prod 而不是超前。Python 3.10 是三机唯一交集。
|
||||
|
||||
| 维度 | 推荐基线(= NAS 当前) | 落地动作(Mac 侧) |
|
||||
|------|----------------------|-------------------|
|
||||
| Python | **3.10.x**(容器 3.10.20 / VPS 3.10.11) | 重建 venv:`python3.10 -m venv venv310`(用 homebrew `python@3.10`),废弃 `venv311` |
|
||||
| pandas | **2.3.x**(NAS 2.3.3) | `pip install 'pandas>=2.3,<3'`(**锁 <3**) |
|
||||
| numpy | **2.2.x**(NAS 2.2.6) | `pip install 'numpy>=2.2,<3'` |
|
||||
| polars | **1.42.x** | 已对齐 |
|
||||
| pyarrow | **24.x** | `pip install 'pyarrow>=24,<25'` |
|
||||
| fastapi / uvicorn / PyJWT / bcrypt | 当前版本即可 | 已对齐 |
|
||||
|
||||
### 落地步骤(建议,不在本任务范围)
|
||||
|
||||
1. **生成 lockfile**:在 NAS 容器内跑 `pip freeze > requirements-lock.txt`,作为三机共享 lock 基线。
|
||||
2. **Mac 重建 venv310**:
|
||||
```bash
|
||||
brew install python@3.10
|
||||
/opt/homebrew/bin/python3.10 -m venv venv310
|
||||
./venv310/bin/pip install -r requirements-lock.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
# 不含 vnpy(源码引用)/ PySide6(可选 GUI)/ xtquant(Windows only)/ akshare(可选,按需)
|
||||
```
|
||||
3. **加 requirements 约束**:在 `pyproject.toml` 的 `dependencies` 加上限:
|
||||
```toml
|
||||
"pandas>=2.3,<3", # 避免 pandas 3.x breaking change
|
||||
"numpy>=2.2,<3",
|
||||
```
|
||||
4. **CI 校验**:在 Gitea Actions 加一步 `pip check` + import 探针,防止 drift 再发生。
|
||||
5. **VPS 不动**:VPS 只跑 `sanguo_qmt_bridge`(依赖面 = fastapi + uvicorn + xtquant),与主项目 lock 解耦。
|
||||
|
||||
### 不推荐的方向
|
||||
|
||||
- ❌ **把 prod 升到 pandas 3 / numpy 2 / Python 3.11 来"追平" Mac**:prod 是稳定优先,3.0 新 major 须先在测试库充分回归。
|
||||
- ❌ **在 Mac venv311 补装 vnpy**:vnpy 源码引用是项目既定设计(容器也是源码引用),pip install 会引入版本冲突。
|
||||
|
||||
---
|
||||
|
||||
## 5. 本轮修复记录(2026-07-14)
|
||||
|
||||
为让 `pytest --collect-only` 0 错误通过,在 venv311 内新装:
|
||||
|
||||
| 包 | 版本 | 原因 |
|
||||
|----|------|------|
|
||||
| polars + polars-runtime-32 | 1.42.1 | `tests/factor/test_data_adapter.py` collect 失败;polars 是 `pyproject.toml [alpha]` 真实依赖 |
|
||||
| fastapi | 0.139.0 | `tests/api/*` collect 失败(`No module named 'fastapi'`) |
|
||||
| uvicorn[standard] | 0.51.0 | fastapi.testclient 间接需要 |
|
||||
| PyJWT | 2.13.0 | `sanguo_api/auth.py` `import jwt` |
|
||||
| bcrypt | 5.0.0 | `sanguo_api/auth.py` `import bcrypt` |
|
||||
|
||||
结果:`pytest --collect-only -q` 从 `380 collected + 1 error` → **`384 collected, 0 errors`**。
|
||||
`pytest tests/trader/ tests/data_platform/` → **228 passed, 0 failed**。
|
||||
|
||||
---
|
||||
|
||||
## 6. 待办(跟踪项)
|
||||
|
||||
- [ ] VPS SSH 复核(本轮 22 端口 Connection closed,下一轮巡检补实测版本)
|
||||
- [ ] pandas 3.x 锁上限决策(见 §4 步骤 3)
|
||||
- [ ] venv310 重建计划排期
|
||||
- [ ] requirements-lock.txt 生成(从 NAS 容器 freeze)
|
||||
@@ -47,13 +47,29 @@ DOCKER="/var/packages/Docker/target/usr/bin/docker"
|
||||
SRC=~/.openclaw/sanguo_projects/sanguo_vnpy_v2/
|
||||
DEST=sanguo-nas:/volume1/homes/admin/.sanguo_projects/sanguo_vnpy_v2/
|
||||
|
||||
# 1) 同步代码(已排除数据/缓存/配置——配置含部署态密码,不覆盖)
|
||||
# 1) 同步代码(已排除数据/缓存/venv/entrypoint/配置——配置含部署态密码,不覆盖)
|
||||
# ⚠️ 每个 pattern 必须单独一个 --exclude=PATTERN(见下方警告框)
|
||||
rsync -avz --delete \
|
||||
--exclude='.git' '__pycache__' '*.pyc' '.pytest_cache' \
|
||||
--exclude='data/' 'logs/' 'temp/' '*.log' '.DS_Store' '.venv/' 'node_modules/' 'config/' \
|
||||
--exclude='.git' --exclude='__pycache__' --exclude='*.pyc' --exclude='.pytest_cache' \
|
||||
--exclude='data/' --exclude='data_cache/' --exclude='logs/' --exclude='temp/' \
|
||||
--exclude='*.log' --exclude='.DS_Store' \
|
||||
--exclude='.venv/' --exclude='venv/' --exclude='venv311/' \
|
||||
--exclude='node_modules/' --exclude='config/' \
|
||||
--exclude='entrypoint.sh' \
|
||||
-e ssh \
|
||||
"$SRC" "$DEST"
|
||||
```
|
||||
|
||||
> ⚠️ **rsync --exclude 写法警告(2026-07-14 dry-run 实测固化)**
|
||||
>
|
||||
> 旧写法 `--exclude='.git' '__pycache__' '*.pyc' ...`(一个 `--exclude` 后紧挨多个 bare pattern)**实测排除失效**——rsync 把 bare pattern 当**源路径**处理(报 `lstat: No such file or directory`),`--delete` 因此会误删 NAS 上:
|
||||
> - `data_cache/` 全量 staging parquet(**万级文件,dev/NAS 差约 1 万个**)
|
||||
> - `/app/entrypoint.sh`(容器 Entrypoint,删除后 `docker restart` 启动失败)
|
||||
> - 以及 `venv/` 等 dev-only 目录被误推上去
|
||||
>
|
||||
> **必须**用每 pattern 单独 `--exclude=PATTERN` 写法(如上命令)。每次改排除列表后建议先 `rsync -avzn ...`(dry-run)确认 `deleting` 列表无意外项再实跑。
|
||||
|
||||
```bash
|
||||
# 2) 重启容器
|
||||
ssh sanguo-nas "$DOCKER restart sanguo_vnpy_v2"
|
||||
|
||||
@@ -69,6 +85,24 @@ curl -s http://192.168.2.154:8000/api/v1/auth/login -X POST \
|
||||
| `ssh sanguo-nas "$DOCKER ps"` | `Up` |
|
||||
| `POST /api/v1/auth/login` | 返回 `{"token":...}` |
|
||||
|
||||
### 附:`entrypoint.sh` 根因说明(2026-07-14 dry-run + docker inspect 实证)
|
||||
|
||||
`docker inspect sanguo_vnpy_v2` 显示容器 `Entrypoint=[/app/entrypoint.sh]`、`Cmd=[]`,
|
||||
即容器启动**必须**找到 `/app/entrypoint.sh`。但 dev 仓库根目录已无此文件(已移到
|
||||
`docker/entrypoint.sh`)。若 rsync 带 `--delete` 且不排除,会删掉 NAS 上的
|
||||
`/app/entrypoint.sh` → 下次 `docker restart` 直接启动失败(entrypoint not found)。
|
||||
|
||||
- **短期保护(已落实)**:上方步骤 1 命令加了 `--exclude='entrypoint.sh'`,NAS 现有文件
|
||||
不会被删,容器可继续启动。代价:dev 侧对 entrypoint 的改动不会自动同步(需手动处理)。
|
||||
- **根治方案(待用户确认采用哪种)**:
|
||||
|
||||
| 方案 | 做法 | 适用 |
|
||||
|------|------|------|
|
||||
| ① 纳入 git 根目录版本化 | dev 根目录恢复 `entrypoint.sh`(与 `docker/entrypoint.sh` 统一为同一份),去掉 `--exclude='entrypoint.sh'`,rsync 正常同步 | entrypoint 需随代码迭代频繁改 |
|
||||
| ② 由镜像层提供 | 确认 `entrypoint.sh` 由 Dockerfile `COPY` 进镜像;则 bind-mount 不应覆盖它(调整挂载/文件位置) | entrypoint 极少改、希望与代码解耦 |
|
||||
|
||||
> ⚠️ 两种方案互斥。当前默认走"短期保护",**待用户确认**后再切到 ① 或 ②。
|
||||
|
||||
---
|
||||
|
||||
## 四、依赖变更(改 requirements-docker.txt)— 偶尔
|
||||
|
||||
@@ -0,0 +1,400 @@
|
||||
# VPS 生产运维 Runbook
|
||||
|
||||
> **sanguo QMT bridge 生产环境运维手册。** VPS 已部署并在运行,本文档是"运维 + 已验证状态记录",不是从零搭建指南。
|
||||
>
|
||||
> 相关文档:
|
||||
> - bridge 接口契约:[`sanguo_qmt_bridge/README.md`](../../sanguo_qmt_bridge/README.md)
|
||||
> - 首次部署参考(历史):[`d-phase-windows-deploy.md`](./d-phase-windows-deploy.md) / [`windows-bridge-setup.md`](./windows-bridge-setup.md)
|
||||
> - NAS 容器运维:[`nas-deploy-plan.md`](./nas-deploy-plan.md)
|
||||
>
|
||||
> **安全约定**:本文档进 git,**不硬编码 BRIDGE_TOKEN**。所有命令中 `<BRIDGE_TOKEN>` 为占位符,真实值见 Claude memory `windows-vps-access.md` 或 VPS 系统环境变量 `BRIDGE_TOKEN`。
|
||||
|
||||
---
|
||||
|
||||
## 1. 已验证生产状态(2026-07-14 巡检)
|
||||
|
||||
> 以下事实经实地验证,直接采纳。下次巡检时更新日期并重新核实。
|
||||
|
||||
| 项目 | 值 | 备注 |
|
||||
|------|-----|------|
|
||||
| **VPS** | `49.232.102.198` | 腾讯云轻量,Win Server 2022,4C/16G/180G SSD |
|
||||
| **SSH** | `ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198` | Mac ~/.ssh/id_ed25519 免密 |
|
||||
| **磁盘** | C: 24G 用 / 156G 空闲 | 充裕 |
|
||||
| **Python** | `C:\Python310\python.exe` (3.10.11) | xtquant import OK |
|
||||
| **miniQMT** | `C:\国金QMT交易端\` userdata `userdata_mini` | 模拟账户 `66639661` 已登录 |
|
||||
| **bridge 代码** | `C:\sanguo_qmt_bridge\` | FastAPI + uvicorn :8765 |
|
||||
| **bridge 启动脚本** | `C:\run_bridge.ps1` | 设环境变量 + BRIDGE_SESSION_ID + uvicorn |
|
||||
| **Caddy** | `C:\caddy\` + `C:\run_caddy.ps1` | :443 → :8765 反代,Let's Encrypt 自动续期 |
|
||||
| **schtasks** | `sanguo-bridge` + `sanguo-caddy` 均 Running | SYSTEM 账户 / onstart 自启 |
|
||||
|
||||
### 进程状态
|
||||
|
||||
| 进程 | 映像名 | 角色 |
|
||||
|------|--------|------|
|
||||
| XtMiniQmt | `XtMiniQmt.exe` | miniQMT 客户端,已登录模拟账户 |
|
||||
| python | `python.exe` | uvicorn bridge :8765 |
|
||||
| caddy | `caddy.exe` | HTTPS 反代 :443 → :8765 |
|
||||
|
||||
### bridge 端点验证(VPS 本地 127.0.0.1:8765)
|
||||
|
||||
| 端点 | 响应 |
|
||||
|------|------|
|
||||
| `GET /health` | `{"status":"ok","miniqmt_connected":true}` |
|
||||
| `GET /account` | `{"ok":true,"cash":9997075.51,"frozen":9001769.0,"market_value":2901.0,"total":9999978.51}` |
|
||||
| `GET /positions` | `{"ok":true,"positions":[...sh600000(200股), sz000001(100股)...]}` |
|
||||
|
||||
### 三条访问路径
|
||||
|
||||
| # | 路径 | 地址 | 场景 |
|
||||
|---|------|------|------|
|
||||
| ① | VPS 本地 | `http://127.0.0.1:8765` | 运维 RDP/SSH 内验证 |
|
||||
| ② | 公网 HTTPS | `https://bridge.mysanguo.top` | 外部客户端(Mac 浏览器等)|
|
||||
| ③ | NAS 容器经 Mac 隧道 | `http://192.168.2.101:8765` | NAS sanguo 容器调 bridge |
|
||||
|
||||
---
|
||||
|
||||
## 2. 拓扑图
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────────┐
|
||||
│ Windows VPS · 49.232.102.198 │
|
||||
│ Win Server 2022 · 4C/16G/180G SSD │
|
||||
│ │
|
||||
│ ┌───────────┐ xtquant ┌──────────────┐ │
|
||||
│ │ miniQMT │◄──────────│ bridge │ │
|
||||
│ │ 66639661 │ │ :8765 │ │
|
||||
│ └───────────┘ └──────┬───────┘ │
|
||||
│ │ 反代 │
|
||||
│ ┌──────▼───────┐ │
|
||||
│ │ Caddy │ │
|
||||
│ │ :443 │ │
|
||||
│ │ Let's Encr. │ │
|
||||
│ └──────┬───────┘ │
|
||||
└──────────────────────────────────┼────────────────┘
|
||||
│
|
||||
┌────────────────────────┼───────────────────┐
|
||||
│ │ │
|
||||
① VPS 本地 ② 公网 HTTPS ③ NAS→Mac→VPS
|
||||
http://127.0.0.1:8765 https://bridge. http://192.168.2.101
|
||||
(运维 RDP/SSH) mysanguo.top :8765
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌──────────┐ ┌──────────────┐ ┌──────────────┐
|
||||
│ VPS 终端 │ │ 外部客户端 │ │ Mac Mini │
|
||||
└──────────┘ │(Mac 浏览器等) │ │ 192.168.2.101│
|
||||
└──────────────┘ │ 开发 + 隧道 │
|
||||
│ │
|
||||
│ ssh -N -L │
|
||||
│ 0.0.0.0:8765:│──SSH:22──► VPS
|
||||
│ 127.0.0.1:8765│
|
||||
│ │
|
||||
│ caffeinate │
|
||||
│ -i -s 防睡眠 │
|
||||
└──────┬───────┘
|
||||
│
|
||||
┌──────▼───────┐
|
||||
│ NAS 容器 │
|
||||
│ 192.168.2.154│
|
||||
│ sanguo_vnpy │
|
||||
│ _v2 (测试+备) │
|
||||
└──────────────┘
|
||||
|
||||
⚠ 华为光猫拦截 NAS→VPS 的 80/443 → NAS 不能走路径②
|
||||
→ NAS 容器走路径③:HTTP 到 Mac :8765 → Mac SSH 隧道 → VPS :8765
|
||||
→ Mac 需常驻 SSH tunnel + caffeinate -i -s 防睡眠
|
||||
```
|
||||
|
||||
### 三机分工
|
||||
|
||||
| 机器 | IP | 角色 | 能做什么 | 不能做什么 |
|
||||
|------|-----|------|----------|-----------|
|
||||
| **VPS** | 49.232.102.198 | 生产 | miniQMT + bridge + Caddy 全链路 | 无开发环境 |
|
||||
| **Mac Mini** | 192.168.2.101 | 开发 + 隧道中继 | 写代码、git、scp 部署、SSH 隧道 | 跑不了 xtquant(Windows only) |
|
||||
| **NAS** | 192.168.2.154 | 测试 + 备份 | Docker 容器跑集成测试、回测、每日备份 | 无法直连 VPS 80/443(光猫拦截) |
|
||||
|
||||
---
|
||||
|
||||
## 3. 日常运维操作
|
||||
|
||||
> 以下命令均从 **Mac Mini** 执行,通过 SSH 远程操控 VPS。
|
||||
|
||||
### 3.1 健康巡检(一条命令验三端点)
|
||||
|
||||
**快速健康(无需 token):**
|
||||
|
||||
```bash
|
||||
ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198 \
|
||||
"curl -s http://127.0.0.1:8765/health"
|
||||
```
|
||||
|
||||
期望:`{"status":"ok","miniqmt_connected":true}`
|
||||
|
||||
**完整三端点验证(含 account + positions,需 token):**
|
||||
|
||||
复杂引号场景用 stdin 喂 PowerShell(见 [§8 Windows 坑](#8-windows-跑命令的坑)):
|
||||
|
||||
```bash
|
||||
ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198 "powershell -Command -" <<'PS1'
|
||||
Write-Host "=== health ==="
|
||||
curl.exe -s http://127.0.0.1:8765/health
|
||||
Write-Host "`n=== account ==="
|
||||
curl.exe -s -H "X-Bridge-Token: <BRIDGE_TOKEN>" http://127.0.0.1:8765/account
|
||||
Write-Host "`n=== positions ==="
|
||||
curl.exe -s -H "X-Bridge-Token: <BRIDGE_TOKEN>" http://127.0.0.1:8765/positions
|
||||
PS1
|
||||
```
|
||||
|
||||
**检查 schtasks 状态 + 进程 + 磁盘:**
|
||||
|
||||
```bash
|
||||
ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198 "powershell -Command -" <<'PS1'
|
||||
Write-Host "=== schtasks ==="
|
||||
schtasks /query /tn "sanguo-bridge" /fo list | Select-String "Status"
|
||||
schtasks /query /tn "sanguo-caddy" /fo list | Select-String "Status"
|
||||
Write-Host "=== processes ==="
|
||||
Get-Process python,caddy,XtMiniQmt -ErrorAction SilentlyContinue | Format-Table Name,Id,CPU -Auto
|
||||
Write-Host "=== disk ==="
|
||||
Get-PSDrive C | Format-Table Used,Free -Auto
|
||||
PS1
|
||||
```
|
||||
|
||||
### 3.2 bridge 重启(必须换 BRIDGE_SESSION_ID)
|
||||
|
||||
> **关键**:xtquant 的 `connect()` 复用旧 session 会返回 -1。每次重启 bridge 前必须换 `BRIDGE_SESSION_ID`,否则 bridge 起来但 miniQMT 连不上。
|
||||
|
||||
**步骤:**
|
||||
|
||||
1. **改 session_id** — RDP 或 SSH 编辑 `C:\run_bridge.ps1`,把 `$env:BRIDGE_SESSION_ID` 改为新值(如日期递增 `20260714` → `20260715` 或加后缀 `20260714b`):
|
||||
|
||||
```bash
|
||||
# SSH 在线编辑(PowerShell 替换)
|
||||
ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198 "powershell -Command -" <<'PS1'
|
||||
$path = "C:\run_bridge.ps1"
|
||||
$content = Get-Content $path -Raw
|
||||
# 把旧 session_id 替换为今天的(按实际值调整正则)
|
||||
$newId = Get-Date -Format "yyyyMMddHHmm"
|
||||
$content = $content -replace 'BRIDGE_SESSION_ID\s*=\s*"\d+"', "BRIDGE_SESSION_ID = `"$newId`""
|
||||
Set-Content $path -Value $content -Encoding UTF8
|
||||
Write-Host "session_id updated to $newId"
|
||||
# 确认
|
||||
Select-String -Path $path -Pattern "BRIDGE_SESSION_ID"
|
||||
PS1
|
||||
```
|
||||
|
||||
2. **重启 schtask:**
|
||||
|
||||
```bash
|
||||
ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198 \
|
||||
"schtasks /end /tn \"sanguo-bridge\" & schtasks /run /tn \"sanguo-bridge\""
|
||||
```
|
||||
|
||||
3. **等 5 秒后验证健康**(同 §3.1 快速健康命令)。
|
||||
|
||||
### 3.3 Caddy 重启
|
||||
|
||||
Caddy 无 session 状态问题,直接重启:
|
||||
|
||||
```bash
|
||||
ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198 \
|
||||
"schtasks /end /tn \"sanguo-caddy\" & schtasks /run /tn \"sanguo-caddy\""
|
||||
```
|
||||
|
||||
验证公网:
|
||||
|
||||
```bash
|
||||
curl -s https://bridge.mysanguo.top/health
|
||||
```
|
||||
|
||||
### 3.4 miniQMT 崩溃恢复(human-gated)
|
||||
|
||||
> miniQMT 是券商客户端 GUI 程序,崩溃后需要人工 RDP 登录。**无法自动化。**
|
||||
|
||||
**症状**:`/health` 返回 `{"miniqmt_connected": false}`,或 bridge 日志 xtquant connect 报错。
|
||||
|
||||
**恢复步骤(人工操作):**
|
||||
|
||||
1. RDP 登录 VPS(远程桌面 `49.232.102.198`)
|
||||
2. 手动启动 miniQMT 客户端 → 登录模拟账户 `66639661`
|
||||
3. 确认客户端进入极简模式/独立交易界面
|
||||
4. **换 BRIDGE_SESSION_ID 后重启 bridge**(同 §3.2)
|
||||
5. 验证 `/health` → `miniqmt_connected: true`
|
||||
|
||||
---
|
||||
|
||||
## 4. dev → test → prod 发布流水线
|
||||
|
||||
```
|
||||
Mac Mini (开发) NAS (测试) VPS (生产)
|
||||
───────────── ────────── ──────────
|
||||
写代码 git pull scp bridge 代码
|
||||
sanguo_qmt_bridge/ → 容器跑集成测试 → schtasks 重启
|
||||
本地 unit test 验证通过 → 健康巡检
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
git push ──────────► gitea ──────► NAS pull ────► VPS deploy
|
||||
(git.mysanguo.top) (Docker 容器) (scp + restart)
|
||||
│
|
||||
每日 VPS 状态
|
||||
备份 → NAS
|
||||
```
|
||||
|
||||
| 阶段 | 机器 | 动作 | 验证 |
|
||||
|------|------|------|------|
|
||||
| dev | Mac Mini | 改 `sanguo_qmt_bridge/` 代码 | 本地 unit test(`pytest tests/`)|
|
||||
| push | Mac Mini | `git push origin master` | gitea 仓库更新 |
|
||||
| test | NAS | 容器 `git pull` + 跑集成测试 | bridge_client 测试通过、数据管道 OK |
|
||||
| prod | VPS | scp 变更文件 + 重启 bridge | `/health` + `/account` + `/positions` 三通 |
|
||||
| backup | NAS | 每日 VPS 状态备份到 NAS | bridge 代码 + config 快照 |
|
||||
|
||||
> **注意**:NAS 测试只能验 bridge_client 端(发 HTTP 请求到 bridge),无法验 xtquant/miniQMT 端(Windows only)。bridge 服务端 + xtquant + miniQMT 的端到端验证只能在 VPS 上做。
|
||||
|
||||
---
|
||||
|
||||
## 5. 代码部署到 VPS(Mac → VPS scp + 重启)
|
||||
|
||||
### 5.1 scp 变更文件
|
||||
|
||||
bridge 代码在 Mac 的 `sanguo_qmt_bridge/` 目录,改完后 scp 到 VPS:
|
||||
|
||||
```bash
|
||||
# 只传变更的 .py 文件(快速迭代)
|
||||
scp -i ~/.ssh/id_ed25519 \
|
||||
sanguo_qmt_bridge/bridge.py \
|
||||
sanguo_qmt_bridge/xt_gateway.py \
|
||||
sanguo_qmt_bridge/auth.py \
|
||||
Administrator@49.232.102.198:C:/sanguo_qmt_bridge/
|
||||
|
||||
# 或整目录同步(含 requirements.txt 等)
|
||||
scp -i ~/.ssh/id_ed25519 sanguo_qmt_bridge/*.py \
|
||||
Administrator@49.232.102.198:C:/sanguo_qmt_bridge/
|
||||
```
|
||||
|
||||
> **Windows scp 路径用正斜杠**:`C:/sanguo_qmt_bridge/`(不是反斜杠)。
|
||||
|
||||
### 5.2 换 session_id + 重启
|
||||
|
||||
```bash
|
||||
# 1. 换 BRIDGE_SESSION_ID(同 §3.2 步骤 1)
|
||||
ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198 "powershell -Command -" <<'PS1'
|
||||
$path = "C:\run_bridge.ps1"
|
||||
$content = Get-Content $path -Raw
|
||||
$newId = Get-Date -Format "yyyyMMddHHmm"
|
||||
$content = $content -replace 'BRIDGE_SESSION_ID\s*=\s*"\d+"', "BRIDGE_SESSION_ID = `"$newId`""
|
||||
Set-Content $path -Value $content -Encoding UTF8
|
||||
Write-Host "session_id → $newId"
|
||||
PS1
|
||||
|
||||
# 2. 重启 schtask
|
||||
ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198 \
|
||||
"schtasks /end /tn \"sanguo-bridge\" & schtasks /run /tn \"sanguo-bridge\""
|
||||
|
||||
# 3. 等 5 秒,验证
|
||||
sleep 5
|
||||
ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198 \
|
||||
"curl -s http://127.0.0.1:8765/health"
|
||||
```
|
||||
|
||||
### 5.3 如改了 requirements.txt(依赖变更)
|
||||
|
||||
```bash
|
||||
ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198 \
|
||||
"cd C:\sanguo_qmt_bridge && C:\Python310\python.exe -m pip install -r requirements.txt"
|
||||
# 然后同 5.2 重启
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 人类闸口清单
|
||||
|
||||
> 以下操作无法自动化,必须人工执行。自动化脚本碰到这些步骤应明确报"需人工干预"。
|
||||
|
||||
| 操作 | 为什么只能人做 | 频率 |
|
||||
|------|---------------|------|
|
||||
| **VPS 开通 / 重置密码** | 云厂商控制台操作 | 极低(首次/安全事件) |
|
||||
| **miniQMT 券商客户端登录** | GUI 程序 + 可能需验证码/密码 | 崩溃后 / VPS 重启后 |
|
||||
| **BRIDGE_TOKEN 轮换** | 需同时在 VPS + NAS + Mac 三处同步更新 | 定期(安全策略) |
|
||||
| **Let's Encrypt 证书异常处理** | Caddy 自动续期,但 Rate Limit / DNS 异常需人工介入 | 极低(自动续期正常时无需干预) |
|
||||
| **华为光猫 / 网络配置变更** | 运营商设备,SSH 碰不到 | 极低 |
|
||||
| **VPS 计划任务创建/修改** | 首次配置 `schtasks /create` 需 RDP | 低(配置变更时) |
|
||||
|
||||
### BRIDGE_TOKEN 轮换流程(人工)
|
||||
|
||||
1. 生成新 token:`python -c "import secrets; print(secrets.token_urlsafe(32))"`
|
||||
2. VPS:更新 `C:\run_bridge.ps1` 里的 BRIDGE_TOKEN(或系统环境变量)
|
||||
3. NAS:更新 sanguo 容器环境变量 + `docker restart sanguo_vnpy_v2`
|
||||
4. Mac:更新 `sanguo_trader/bridge_client.py` 引用的配置或环境变量
|
||||
5. VPS:换 session_id + 重启 bridge(§3.2)
|
||||
6. 全链路验证三端点
|
||||
|
||||
---
|
||||
|
||||
## 7. 排障表
|
||||
|
||||
| 现象 | 根因 | 排查 / 修复 |
|
||||
|------|------|-------------|
|
||||
| `/health` → `miniqmt_connected: false` | miniQMT 未登录 / 崩溃 / userdata 路径错 | RDP 检查 miniQMT 客户端状态(§3.4),确认 `MINIQMT_USERDATA` 指向正确 userdata_mini |
|
||||
| bridge 启动 xtquant connect 返回 -1 | **BRIDGE_SESSION_ID 与旧 session 冲突** | 换 session_id 后重启(§3.2)——这是最常见坑 |
|
||||
| 401 token 无效 | VPS 与 NAS/Mac 的 `BRIDGE_TOKEN` 不一致 | 核对三处 token 值一致 |
|
||||
| 公网 `https://bridge.mysanguo.top` 502 | Caddy 没启 或 bridge 没启 | 先验 VPS 本地 `curl 127.0.0.1:8765/health`;本地通=bridge OK→查 Caddy(§3.3);本地不通→查 bridge |
|
||||
| 公网 DNS 解析到错误 IP(如 198.18.1.244) | **Mac 本地代理(Clash/Surge)DNS 劫持到 fake-IP**。公网 DNS(8.8.8.8)解析正确到 49.232.102.198,但代理层拦截 | 修复①:`/etc/hosts` 加 `49.232.102.198 bridge.mysanguo.top`;修复②:代理规则里该域名直连(bypass proxy) |
|
||||
| NAS 容器访问 `bridge.mysanguo.top` 超时 | **华为光猫拦截 NAS→VPS 的 80/443** | 走路径③:Mac SSH 隧道 `http://192.168.2.101:8765`(Mac 需常驻 tunnel + caffeinate) |
|
||||
| Mac SSH 隧道断了 → NAS 容器连不上 bridge | Mac 睡眠 / SSH 进程退出 | Mac 跑 `caffeinate -i -s &` 防睡眠;用 autossh 或 launchd 守护 SSH tunnel |
|
||||
| 下单报 `[120141][证券交易未初始化]` | **非交易日**(miniQMT 交易日才初始化交易通道)| 等交易日。这是 miniQMT 设计,不是 bug |
|
||||
| 影子下单未触发 | `live.enabled` 未开 / 当日无成交 / BRIDGE_TOKEN 未设 / bridge_url 不对 | 逐项检查 NAS `config/data_platform.yaml` + 环境变量 |
|
||||
| 重复下单 | — | 不会。`paper_shadow_orders` 表 `UNIQUE(account_id, trade_id)` 幂等去重 |
|
||||
| cmd 中文/emoji 乱码 | cmd 默认 GBK 编码 | 用 PowerShell + `chcp 65001`,或 python 加 `-X utf8`(§8) |
|
||||
| scp 中文路径失败 | Windows 中文目录 + SSH 编码 | 用正斜杠路径 + ASCII 变量名,中文路径用搜索代替字面量 |
|
||||
|
||||
---
|
||||
|
||||
## 8. Windows 跑命令的坑
|
||||
|
||||
> Windows SSH 远程跑命令有三个经典坑:编码、引号、中文路径。
|
||||
|
||||
### 8.1 编码(GBK → UTF8)
|
||||
|
||||
cmd 默认 GBK,中文输出和 emoji 会乱码。
|
||||
|
||||
```bash
|
||||
# PowerShell 设 UTF8(代码页 65001)
|
||||
ssh ... "powershell -Command \"[Console]::OutputEncoding = [Text.Encoding]::UTF8; chcp 65001; <你的命令>\""
|
||||
|
||||
# Python 加 -X utf8
|
||||
ssh ... "C:\Python310\python.exe -X utf8 script.py"
|
||||
```
|
||||
|
||||
### 8.2 引号嵌套 → 用 stdin
|
||||
|
||||
SSH → cmd → PowerShell 三层引号极易出错。复杂脚本用 stdin 喂:
|
||||
|
||||
```bash
|
||||
# powershell -Command - 从 stdin 读脚本
|
||||
ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198 "powershell -Command -" <<'PS1'
|
||||
# 这里写 PowerShell,引号无需转义
|
||||
curl.exe -s -H "X-Bridge-Token: <BRIDGE_TOKEN>" http://127.0.0.1:8765/account
|
||||
PS1
|
||||
```
|
||||
|
||||
`<<'PS1'` 的单引号防止 Mac shell 展开变量,PowerShell 在 VPS 侧原样执行。
|
||||
|
||||
### 8.3 中文路径
|
||||
|
||||
`C:\国金QMT交易端\` 等中文路径在 SSH 传输中可能因编码错乱。
|
||||
|
||||
- scp 目标用正斜杠:`C:/sanguo_qmt_bridge/`(ASCII)
|
||||
- 需引用中文路径时,在 PowerShell 内用变量拼接或 `Get-ChildItem` 搜索,不写字面量:
|
||||
|
||||
```powershell
|
||||
# 不写 "C:\国金QMT交易端\...",用搜索
|
||||
$qmt = Get-ChildItem C:\ -Directory | Where-Object Name -like "*QMT*"
|
||||
```
|
||||
|
||||
### 8.4 curl vs Invoke-WebRequest
|
||||
|
||||
PowerShell 里 `curl` 默认是 `Invoke-WebRequest` 的别名(参数语法不同)。要用的标准 curl:
|
||||
|
||||
```powershell
|
||||
curl.exe -s http://... # 显式 .exe 绕过别名
|
||||
```
|
||||
|
||||
cmd 里 `curl` 直接就是 `curl.exe`,无此问题。
|
||||
Reference in New Issue
Block a user