Files
sanguo_vnpy_v2/scripts/ops
claude_dev c210197778
CI/CD / test (push) Successful in 8s
CI/CD / nas-deploy (push) Failing after 34s
CI/CD / nas-verify (push) Has been skipped
fix(entrypoint): +x 执行位 + SKIP_REDIS_CHECK 默认1(治 run535 NAS crash loop)
run535 部署后容器 crash loop(exit126),两个独立根因:
1. Permission denied: entrypoint.sh git mv 后 644 无执行位,rsync 推 NAS 也是 644,
   bind mount 覆盖镜像层 chmod +x 无效,tini exec 失败。→ entrypoint.sh git 100755。
2. Redis 连接超时:新版 entrypoint 检查 SKIP_REDIS_CHECK!=1 才查 Redis(旧版 if false
   硬跳过),NAS 无 Redis 卡检查 crash。→ 默认 SKIP_REDIS_CHECK=1(复现旧版跳过)。

附:文档(nas-deploy-plan 方案①已实施 + ops/README 根治标记)。
2026-08-01 21:43:29 +08:00
..

scripts/ops — NAS 运维脚本

NAS(测试 + 备份角色)的运维脚本。仅用标准库 / set -euo pipefail bash, 不引入第三方依赖,不硬编码密钥。

脚本 用途 运行位置
nas_bridge_probe.py QMT bridge 连通性 / 健康探针 NAS 容器内(容器无 curl,只能 python
nas_backup_vps.sh 生产 VPS → NAS 每日快照备份(模板) NASssh 免密拉取生产)

1. nas_bridge_probe.py — bridge 健康探针

为何存在:容器镜像没装 curl/wgetbridge 连通性只能用 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 流水线命令清单

devMac 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) 同步代码到 NAStest/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"

testNAS 容器内)

# 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,单向。


已知坑

  1. rsync --exclude 语法--exclude='a' 'b' 'c'(一个 --exclude 后跟多个 bare pattern)是错的——rsync 会把 b/c 当源路径(报 lstat 错,排除不生效)。 正确写法:每个 pattern 单独一个 --exclude=PATTERNnas-deploy-plan.md 第三节的 命令需修。

  2. data_cache/ 必须排除dev↔NAS 同步是代码迭代,data_cache/ 是本机下载缓存 dev 59k 文件 / NAS 69k 文件,差异万级 staging parquet)。带 --delete 不排除会删 NAS 上大量 staging 文件。deploy-plan 排除列表漏了它,已在上文命令补 --exclude='data_cache/'

  3. 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/'

  4. 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' 保护 根治(2026-08-01 已实施:entrypoint git mv 回根单一权威,promote 全量 rsync 自动推可复现,Dockerfile COPY+ENV PYTHONPATH 同步;方案②弃用,bind-mount 必覆盖 /app