c210197778
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 根治标记)。
150 lines
6.3 KiB
Markdown
150 lines
6.3 KiB
Markdown
# 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'` 保护**。
|
||
✅ 根治(2026-08-01 已实施:entrypoint git mv 回根单一权威,promote 全量 rsync 自动推可复现,Dockerfile COPY+ENV PYTHONPATH 同步;方案②弃用,bind-mount 必覆盖 /app)
|