feat(deploy): Phase4 代码晋升脚本 promote.sh + runbook
Mac→NAS(rsync代码镜像)+Mac→VPS(scp生产部署)单向晋升管线。 - 数据铁律双层防护: rsync --exclude='/data' + scp按模块天然隔离data/ - --module 单模块快速补推 / 全量 双模式 - VPS无常驻web, 代码部署后下次schtask/回测启动自动生效,无需hot reload - dry-run双端验证通过(VPS+NAS标记落盘后还原无残留) - ssh用49.232.102.198(严禁ssh vps,fake-ip劫持198.18.1.254) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
# 三机代码晋升 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 <good_commit> -- 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` + 空数组 |
|
||||
Executable
+144
@@ -0,0 +1,144 @@
|
||||
#!/bin/bash
|
||||
# Phase4: Mac→NAS(代码镜像) + Mac→VPS(生产部署) 单向代码晋升
|
||||
#
|
||||
# 用法:
|
||||
# 全量晋升(所有模块+根文件): bash scripts/nas_sync/promote.sh
|
||||
# 单模块晋升(快速/验证): bash scripts/nas_sync/promote.sh --module sanguo_portfolio
|
||||
#
|
||||
# 设计:
|
||||
# - NAS 走 rsync(增量快),VPS 无 rsync 走 scp -r(按模块逐个推).
|
||||
# - --module X: NAS+VPS 都只推该模块,不推根文件(用于快速补推/小范围验证).
|
||||
# - 数据铁律: 严禁推 data/ (rsync --exclude '/data'; scp 按模块天然隔离).
|
||||
# - reload: VPS 无常驻 web 服务,代码部署后下次 schtask/回测启动自动生效,
|
||||
# 无需 hot reload. 如需立即生效见 runbook.
|
||||
# - ssh 用 `49.232.102.198`(~/.ssh/config 已配 key),严禁 `ssh vps`(fake-ip 劫持).
|
||||
set -eu
|
||||
|
||||
VPS=49.232.102.198
|
||||
NAS=sanguo-nas
|
||||
NAS_CODE=/volume1/stock/sanguo_vnpy_v2
|
||||
VPS_CODE='C:/sanguo_vnpy_v2' # scp 目标用正斜杠
|
||||
|
||||
ALL_MODS=(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)
|
||||
ROOT_FILES=(pyproject.toml pytest.ini requirements-lock.txt run_web.py)
|
||||
|
||||
# 解析 --module 参数
|
||||
PUSH_MOD=""
|
||||
if [[ "${1:-}" == "--module" ]]; then
|
||||
if [ -z "${2:-}" ]; then
|
||||
echo "ERROR: --module 需要模块名参数" >&2
|
||||
echo "可用模块: ${ALL_MODS[*]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
PUSH_MOD="$2"
|
||||
if [ ! -d "$PUSH_MOD" ]; then
|
||||
echo "ERROR: 本地目录不存在: $PUSH_MOD" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# rsync exclude: 前导 / = 仅根级匹配,不影响模块内同名子目录
|
||||
RSYNC_EXCLUDE=(--exclude='/data' --exclude='/data_backup' --exclude='/logs'
|
||||
--exclude='__pycache__' --exclude='.git' --exclude='.pytest_cache'
|
||||
--exclude='.mypy_cache' --exclude='.ruff_cache'
|
||||
--exclude='/vnpy_v4.4.0' --exclude='/vnpy_qmt_v0.3.3'
|
||||
--exclude='/htmlcov' --exclude='/docker' --exclude='/docs'
|
||||
--exclude='/examples' --exclude='/venv310' --exclude='/venv'
|
||||
--exclude='/requirements' --exclude='*.log'
|
||||
--exclude='build*.log' --exclude='rebuild*.log'
|
||||
--exclude='test.db' --exclude='*.pyc' --exclude='*.pyo'
|
||||
--exclude='.coverage' --exclude='*.egg-info')
|
||||
|
||||
echo "=============================================="
|
||||
echo " Phase4 代码晋升 $(date '+%F %T')"
|
||||
if [ -n "$PUSH_MOD" ]; then
|
||||
echo " 模式: 单模块 [$PUSH_MOD] (NAS+VPS 只推该模块,不推根文件)"
|
||||
else
|
||||
echo " 模式: 全量 (所有模块+根文件)"
|
||||
fi
|
||||
echo "=============================================="
|
||||
|
||||
# ---- Step 1/3: Mac → NAS (rsync 代码镜像) ----
|
||||
echo ""
|
||||
echo "[1/3] rsync Mac → NAS ($NAS:$NAS_CODE) ..."
|
||||
if [ -n "$PUSH_MOD" ]; then
|
||||
rsync -az "${RSYNC_EXCLUDE[@]}" "$PUSH_MOD/" "$NAS:$NAS_CODE/$PUSH_MOD/"
|
||||
echo " ✅ NAS 单模块镜像完成: $PUSH_MOD"
|
||||
else
|
||||
# 不用 --delete! NAS 有 data_backup 等独有目录,删了就丢数据
|
||||
rsync -avz "${RSYNC_EXCLUDE[@]}" ./ "$NAS:$NAS_CODE/"
|
||||
echo " ✅ NAS 全量镜像完成"
|
||||
fi
|
||||
|
||||
# ---- Step 2/3: Mac → VPS (scp 按模块/根文件推) ----
|
||||
echo ""
|
||||
echo "[2/3] scp Mac → VPS ($VPS:$VPS_CODE) ..."
|
||||
if [ -n "$PUSH_MOD" ]; then
|
||||
MODS=("$PUSH_MOD")
|
||||
ROOT_PUSH=() # 单模块不推根文件
|
||||
else
|
||||
MODS=("${ALL_MODS[@]}")
|
||||
ROOT_PUSH=("${ROOT_FILES[@]}")
|
||||
fi
|
||||
|
||||
PUSHED=0
|
||||
FAILED=0
|
||||
for m in "${MODS[@]}"; do
|
||||
if [ ! -d "$m" ]; then
|
||||
echo " skip(本地缺失): $m"
|
||||
continue
|
||||
fi
|
||||
if scp -r -C -o ConnectTimeout=20 "$m" "$VPS:$VPS_CODE/" 2>/dev/null; then
|
||||
echo " pushed $m"
|
||||
PUSHED=$((PUSHED+1))
|
||||
else
|
||||
echo " FAIL $m (scp 退出非零)"
|
||||
FAILED=$((FAILED+1))
|
||||
fi
|
||||
done
|
||||
# ROOT_PUSH 在 --module 模式下为空数组; macOS bash 3.2 + set -u 下 "${arr[@]}" 会报错,
|
||||
# 用 ${#arr[@]} 计数守卫绕过(空数组长度=0, 跳过循环)
|
||||
if [ "${#ROOT_PUSH[@]}" -gt 0 ]; then
|
||||
for f in "${ROOT_PUSH[@]}"; do
|
||||
if [ ! -f "$f" ]; then
|
||||
echo " skip(本地缺失): $f"
|
||||
continue
|
||||
fi
|
||||
if scp -C -o ConnectTimeout=20 "$f" "$VPS:$VPS_CODE/" 2>/dev/null; then
|
||||
echo " pushed $f"
|
||||
PUSHED=$((PUSHED+1))
|
||||
else
|
||||
echo " FAIL $f"
|
||||
FAILED=$((FAILED+1))
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ "$FAILED" -gt 0 ]; then
|
||||
echo ""
|
||||
echo "❌ $FAILED 个文件推送失败,请检查上方日志。已成功 $PUSHED 个。"
|
||||
exit 1
|
||||
fi
|
||||
echo " 共推送 $PUSHED 个模块/文件"
|
||||
|
||||
# ---- Step 3/3: VPS 落盘验证 ----
|
||||
echo ""
|
||||
echo "[3/3] VPS 落盘验证 ..."
|
||||
# 验证目标: 取第一个推送的模块,列其 .py 文件的 mtime
|
||||
VERIFY_MOD="${MODS[0]}"
|
||||
VERIFY_CMD="powershell -NoProfile -Command \"Get-ChildItem -Path 'C:\\sanguo_vnpy_v2\\${VERIFY_MOD}' -Filter '*.py' -File -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending | Select-Object -First 3 Name,Length,LastWriteTime | Format-Table -Auto | Out-String\""
|
||||
if ssh -o ConnectTimeout=20 "$VPS" "$VERIFY_CMD" 2>/dev/null | grep -q '\.py'; then
|
||||
echo " ✅ VPS 落盘确认: $VERIFY_MOD/*.py 已就位"
|
||||
else
|
||||
echo " ⚠️ VPS 验证未取到 $VERIFY_MOD 的 .py 文件(可能目录为空或 ssh 超时,请手动核查)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=============================================="
|
||||
echo " ✅ 晋升完成 $(date '+%F %T')"
|
||||
echo " - NAS 代码镜像: $NAS:$NAS_CODE"
|
||||
echo " - VPS 生产代码: $VPS:$VPS_CODE"
|
||||
echo " - reload: VPS 无常驻 web,下次 schtask/回测启动自动生效"
|
||||
echo "=============================================="
|
||||
Reference in New Issue
Block a user