sync_nas_to_vps.sh: 同步质量验证日线(raw+qfq parquet)NAS→VPS via Mac (光猫拦直连)。符号可配置(默认模拟账户持仓600000/000001),幂等(rsync --update), 前缀规则对齐 bridge_client.to_bridge_code。已验证:24文件落地VPS结构正确。 验证通过→Mac cron每日收盘后跑=生产数据管道。
scripts/ops — NAS 运维脚本
NAS(测试 + 备份角色)的运维脚本。仅用标准库 / set -euo pipefail bash,
不引入第三方依赖,不硬编码密钥。
| 脚本 | 用途 | 运行位置 |
|---|---|---|
nas_bridge_probe.py |
QMT bridge 连通性 / 健康探针 | NAS 容器内(容器无 curl,只能 python) |
nas_backup_vps.sh |
生产 VPS → NAS 每日快照备份(模板) | NAS(ssh 免密拉取生产) |
1. nas_bridge_probe.py — bridge 健康探针
为何存在:容器镜像没装 curl/wget,bridge 连通性只能用 python urllib 探。
走 Mac tunnel http://192.168.2.101:8765(华为光猫拦 NAS→VPS 公网 80/443)。
接口:
GET /health(免鉴权)→{"status":"ok","miniqmt_connected":true}GET /account(需X-Bridge-Token)→{"ok":true,"cash":..,"total":..}
退出码:0=健康(可达 + miniQMT 连接),1=不健康。
用法(容器内)
# 进容器(Docker 不在 NAS PATH,须全路径)
/var/packages/Docker/target/usr/bin/docker exec -it sanguo_vnpy_v2 \
python /app/scripts/ops/nas_bridge_probe.py
# 额外探 /account(从环境读 token,绝不硬编码)
/var/packages/Docker/target/usr/bin/docker exec -it -e BRIDGE_TOKEN=xxx \
sanguo_vnpy_v2 python /app/scripts/ops/nas_bridge_probe.py
# 覆盖 bridge URL(换 tunnel 端点时)
BRIDGE_URL=http://192.168.2.101:8765 BRIDGE_TOKEN=xxx \
python /app/scripts/ops/nas_bridge_probe.py
环境变量:
| 变量 | 默认 | 说明 |
|---|---|---|
BRIDGE_URL |
http://192.168.2.101:8765 |
bridge 基址(Mac tunnel 端点) |
BRIDGE_TOKEN |
— | 设了才探 /account;从环境读,绝不硬编码 |
BRIDGE_TIMEOUT |
8 |
单请求超时秒数 |
2. nas_backup_vps.sh — 生产→NAS 每日快照备份
方向(铁律):生产 VPS → NAS 单向,绝不反向写(NAS 是测试/备份角色, 回灌会污染生产库)。
策略:每日 YYYYMMDD/ 快照 + --link-dest 硬链接去重(7 份近乎 1 份体积)
- 保留最近 N 份自动清旧。模板:源路径随生产拓扑填。
用法
# 生产拓扑就绪后(占位示例)
./nas_backup_vps.sh \
--host sanguo-vps \
--paths /var/lib/sanguo/data,/opt/sanguo/config \
--dest /volume1/stock/backup/sanguo_vps \
--keep 7
# 先 dry-run 看会做什么(不传输/不删除)
./nas_backup_vps.sh --host sanguo-vps --paths /a,/b --dest /volume1/.../x --dry-run
参数:
| 参数 | 必填 | 默认 | 说明 |
|---|---|---|---|
--host |
是 | — | 生产源 ssh 别名(~/.ssh/config) |
--paths |
是 | — | 生产侧目录列表(逗号分隔,绝对路径) |
--dest |
是 | — | NAS 备份根目录(快照建在其下 YYYYMMDD/) |
--keep |
否 | 7 |
保留最近 N 份快照 |
--dry-run |
否 | — | 只打印,不传输/不删除 |
前置:生产 host 别名加进 NAS 的 ~/.ssh/config + key 免密。
别名不存在时 preflight 会清晰报错并给 ~/.ssh/config 占位片段。
3. dev→test→prod 流水线命令清单
dev(Mac Mini 开发目录)
SRC=~/.openclaw/sanguo_projects/sanguo_vnpy_v2/
DEST=sanguo-nas:/volume1/homes/admin/.sanguo_projects/sanguo_vnpy_v2/
DOCKER="/var/packages/Docker/target/usr/bin/docker"
# (1) 同步代码到 NAS(test/staging)—— 注意每个 pattern 单独 --exclude
# ⚠️ 务必排除 data_cache/(见下方"已知坑"),否则 --delete 会删 NAS 上
# 万级 staging parquet
rsync -avz --delete \
--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='.superpowers/' \
--exclude='entrypoint.sh' \
-e ssh "$SRC" "$DEST"
# (2) 重启容器(bind-mount,改代码即生效,重启重载进程)
ssh sanguo-nas "$DOCKER restart sanguo_vnpy_v2"
# (3) 冒烟:看日志 + 探健康
sleep 8
ssh sanguo-nas "$DOCKER logs --tail 30 sanguo_vnpy_v2"
test(NAS 容器内)
# bridge 连通性(容器无 curl,用 python 探针)
/var/packages/Docker/target/usr/bin/docker exec -it sanguo_vnpy_v2 \
python /app/scripts/ops/nas_bridge_probe.py
prod(生产 VPS,部署时按 docs/deployment/nas-deploy-plan.md 第五节,
不要动 frpc/socat/Caddy 外网链路)
生产方向数据备份:
nas_backup_vps.sh从 VPS 拉 → NAS,单向。
已知坑
-
rsync
--exclude语法:--exclude='a' 'b' 'c'(一个--exclude后跟多个 bare pattern)是错的——rsync 会把b/c当源路径(报lstat错,排除不生效)。 正确写法:每个 pattern 单独一个--exclude=PATTERN。nas-deploy-plan.md第三节的 命令需修。 -
data_cache/必须排除:dev↔NAS 同步是代码迭代,data_cache/是本机下载缓存 (dev 59k 文件 / NAS 69k 文件,差异万级 staging parquet)。带--delete不排除会删 NAS 上大量 staging 文件。deploy-plan 排除列表漏了它,已在上文命令补--exclude='data_cache/'。 -
venv/(无点)必须排除:dev 机有venv/(Python 3.14)和venv311/(Python 3.11), deploy-plan 只排了.venv/(带点)。不排会把 509 个 venv 文件(含 182MB 的_polars_runtime.abi3.so)推到 NAS 容器(容器是 Python 3.10,这堆二进制毫无用处)。 已在上文命令补--exclude='venv/' --exclude='venv311/'。 -
entrypoint.sh位置不一致(会炸容器,已确认):容器Entrypoint=[/app/entrypoint.sh](docker inspect实证),但 dev 侧根目录已无entrypoint.sh(移到docker/entrypoint.sh)。 直接跑带--delete的 rsync 会删 NAS 的/app/entrypoint.sh→ 下次docker restart启动失败(entrypoint not found)。已在上文命令加--exclude='entrypoint.sh'保护。 根治方案(二选一,部署时定):① dev 根目录恢复entrypoint.sh(从docker/软链或拷贝); ② 更新容器 entrypoint 指向/app/docker/entrypoint.sh(需docker rm+ 重run)。