# 三机代码晋升 Runbook (Phase4) > Mac(dev 源头) → NAS(test 镜像) → VPS(prod 生产) 单向代码晋升。 > 本文档只管 **代码**,不管数据(数据铁律见末节)。 ## 1. 角色与流向 | 机器 | 角色 | 代码路径 | 工具 | |------|------|----------|------| | **Mac** | dev 源头(改代码) | `~/.openclaw/sanguo_projects/sanguo_vnpy_v2` | rsync/scp | | **NAS** | test 镜像(只读副本) | `/volume1/stock/sanguo_vnpy_v2` | rsync(LAN 快) | | **VPS** | prod 生产(实跑) | `C:\sanguo_vnpy_v2` | scp(无 rsync) | ``` 改代码 rsync (Step1) Mac dev ──────────────────► NAS test (镜像) │ └─────── scp (Step2) ─────► VPS prod (生产) ``` > 两路都从 Mac 出发,NAS 是只读镜像(供回归),VPS 是生产实跑。**NAS 不是中转**,VPS 代码不经 NAS。 晋升是 **单向**(Mac → 外),NAS/VPS 永不回推 Mac。NAS 是镜像备份,VPS 是生产实跑。 ## 2. 前置条件 - **Mac SSH key 连 VPS**: `~/.ssh/config` 已配 `Host 49.232.102.198` + key `~/.ssh/id_ed25519`。 - 验证: `ssh 49.232.102.198 'echo VPS_OK'` 应直接通(免密)。 - **严禁 `ssh vps`**: 本机 config 无此别名,会被代理 fake-ip 劫持到 198.18.1.254 报 Connection reset。 - **NAS SSH**: `ssh sanguo-nas`(LAN 免密)。 - **工作目录**: 在 Mac 代码根 `~/.openclaw/sanguo_projects/sanguo_vnpy_v2` 下执行。 ## 3. 使用 ### 3.1 全量晋升(所有模块 + 根文件) 改完一批代码后: ```bash cd ~/.openclaw/sanguo_projects/sanguo_vnpy_v2 bash scripts/nas_sync/promote.sh ``` 推送内容: - 模块目录(13 个): `sanguo_api sanguo_backtest sanguo_common sanguo_data sanguo_factor sanguo_live sanguo_orchestrator sanguo_portfolio sanguo_qmt_bridge sanguo_research sanguo_trader sanguo_web scripts config tests` - 根文件(4 个): `pyproject.toml pytest.ini requirements-lock.txt run_web.py` ### 3.2 单模块快速补推 只想推刚改的一个模块(如 `sanguo_portfolio`): ```bash bash scripts/nas_sync/promote.sh --module sanguo_portfolio ``` `--module` 模式: NAS+VPS 只推该模块,**不推根文件**(根文件改动需走全量)。 可用模块名见上方列表。脚本会在本地缺失时报错退出。 ## 4. Reload 机制(重要) VPS 进程模式是 **一次性任务**(回测 runner_backtest + 采集 schtask bs_eod/akshare),**无常驻 web 服务**。 | 场景 | reload 动作 | |------|-------------| | 采集 schtask(定时) | **无需操作**。下次 schtask 触发时自动加载新代码 | | 回测任务 | **无需操作**。下次启动回测时自动加载新代码 | | 立即让采集生效 | `schtasks /end sanguo-bs-eod && schtasks /run sanguo-bs-eod` | > 一句话: 代码部署后,**下次启动自动生效**,不用 hot reload、不用重启服务。 立即生效命令(在 VPS 上执行,通过 ssh): ```bash ssh 49.232.102.198 'schtasks /end sanguo-bs-eod' ssh 49.232.102.198 'schtasks /run sanguo-bs-eod' ``` ## 5. 落盘验证 脚本 Step3 自动验证 VPS 第一个推送模块的 `.py` 文件 mtime。手动深度核查: ```bash # VPS: 查某模块文件列表+mtime ssh 49.232.102.198 'powershell -NoProfile -Command "Get-ChildItem C:\sanguo_vnpy_v2\sanguo_portfolio -Filter *.py | Select Name,Length,LastWriteTime | Format-Table -Auto"' # VPS: 查某文件是否含新代码标记 ssh 49.232.102.198 'powershell -NoProfile -Command "Select-String -Path C:\sanguo_vnpy_v2\sanguo_common\__init__.py -Pattern SOME_TOKEN"' # NAS: 查某文件 ssh sanguo-nas "ls -la /volume1/stock/sanguo_vnpy_v2/sanguo_portfolio/" ``` > 教训(memory: commit≠部署VPS): 改完代码必须晋升,否则 VPS 跑旧代码。用 `Select-String`/`grep` 验证新代码落盘。 ## 6. Rollback 代码部署出问题需要回退: ```bash # 1. Mac 本地回退到上一个好版本 cd ~/.openclaw/sanguo_projects/sanguo_vnpy_v2 git log --oneline -5 # 找到好版本 git checkout -- sanguo_portfolio/ # 或整个目录 # 2. 重新晋升回退后的代码 bash scripts/nas_sync/promote.sh --module sanguo_portfolio ``` > NAS/VPS 没有 git,rollback = Mac git 回退 + 重新 promote。所以 **Mac git 历史是唯一真相源**。 ## 7. 数据铁律(边界) **只推代码,严禁推 `data/`。** | 允许推 | 严禁推 | |--------|--------| | `sanguo_*/` 代码模块 | `data/`(42GB 数据库 + parquet) | | `scripts/`、`config/`、`tests/` | `logs/`、`vnpy_v4.4.0/`(VPS 已有) | | 根配置文件 | `vnpy_qmt_v0.3.3/`、`docker/`、`.git/` | | | `__pycache__/`、`*.pyc`、`htmlcov/`、`*.log` | 脚本已通过两层防护: 1. **rsync(NAS)**: `--exclude='/data' --exclude='__pycache__' ...` 白名单式排除。 2. **scp(VPS)**: 按模块逐个推,天然隔离 `data/`(不在模块列表里)。 ## 8. 常见坑 | 坑 | 解法 | |----|------| | `ssh vps` 连不上(fake-ip) | 用 `ssh 49.232.102.198`,config 无 vps 别名 | | scp 路径反斜杠报错 | VPS 目标用**正斜杠**: `49.232.102.198:"C:/sanguo_vnpy_v2/"` | | PowerShell 中文乱码 | 用 `-NoProfile`,查文件用 `Get-ChildItem`/`Select-String`,避免中文路径 | | scp 某模块卡住 >60s | Ctrl-C 记录,先验证已通的模块,单独重推失败的 | | 部署后 VPS 行为没变 | 代码是下次启动才加载;采集 schtask 需 `/end && /run` 立即生效 | | macOS bash 3.2 空数组报错 | 脚本已用 `${#arr[@]} -gt 0` 守卫;如改脚本注意 `set -u` + 空数组 |