# 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`=不健康。 ### 用法(容器内) ```bash # 进容器(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 份自动清旧。**模板**:源路径随生产拓扑填。 ### 用法 ```bash # 生产拓扑就绪后(占位示例) ./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 开发目录) ```bash 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 容器内) ```bash # 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=PATTERN`。`nas-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'` 保护**。 根治方案(二选一,部署时定):① dev 根目录恢复 `entrypoint.sh`(从 `docker/` 软链或拷贝); ② 更新容器 entrypoint 指向 `/app/docker/entrypoint.sh`(需 `docker rm` + 重 `run`)。