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:
2026-07-15 07:12:46 +08:00
parent 8c06ef1e53
commit 54f9ab4c4f
6 changed files with 1050 additions and 3 deletions
+150
View File
@@ -0,0 +1,150 @@
# 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/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`=不健康。
### 用法(容器内)
```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 流水线命令清单
### devMac 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) 同步代码到 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 容器内)
```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`)。