bug#4(dev/2.0.0 studio /play 游戏加载失败回归)逐 bug 深挖结论:非源码 bug,
而是 staging dist 构建漏 `--mode staging` 的部署缺陷(源码 .env.staging/request.ts/
inject.ts CSP 修在 dev/2.0.0 已正确)。CDP 钉死后果链并补进 staging-ops 红线:
缺 --mode staging → VITE_API_BASE 不加载 → axios baseURL= → /app-api/* 打到
前端 :4173(vite preview 不代理 /app-api) → SPA fallback 回 index.html(HTTP 200)
→ getRuntimePackage 拿 HTML 串 → request 拦截器对非 {code} 包络透传不报错
→ respVO.manifestUrl undefined → resolvePackage 静默走 demo clicker(无 warn)
→ demo startRuntime 抛 template-system-under-reconstruction → game_error
→ /play 显示「游戏加载失败」(与任务旁证 resolvePackage/undefined.length 同根因)。
修复=按红线 `npm run build -- --mode staging` 重建 dist + 重起 vite preview :4173;
CDP 实证(9358/9359/9360/9361 抽验):package+manifest 请求改打 :48080、引擎真装载
(game_loaded engine:real via bootGameHost)、逐帧 hash 变化(真玩)、截图见真游戏
(green player + red enemies + Score + GAME OVER),非 demo 非错误屏。bug#2(CSP
unsafe-eval 解 gamedef 静止)同 dist 一并验证通过。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
8.8 KiB
staging 运维配方(机器分工 / 代码同步 / 重部署 / 构建门 / 冒烟)
staging 全栈在 mini-desktop(100.64.0.7):huijing 单体 :48080 + game-studio :4173 + game-admin :4174 + 隔离 MySQL/Redis 容器;共享基建(Gitea/MySQL/Redis/MinIO/new-api)在 mini-infra(100.64.0.8)。 本配方由 2026-06-09~11 多波实战蒸馏(M1 真跑 / 黄金闭环 / UI 走查 / Wave4 集成门);红线类条目同步收录于
../rules/engineering-conventions.md。真 UI 走查另见ui-walkthrough-cdp.md。
1. 机器分工铁律(四机)
- 创始人本地工作站 → lili-mac(M1 Pro / 32G / ARM;不入 Tailscale 调度,会休眠合盖):跑 Claude Code 主会话、本地全栈 dev、本地浏览器/CDP 快走查、
mvn/npm快构建迭代。两条边界:① ARM≠x86,Mac 产出的 JAR/前端 bundle 架构中立可用,但 Docker 镜像 + smoke 门必须在 mini-desktop x86 出(否则镜像架构错、与 prod 不同构=半假门);② Mac 上 CDP 只算"快走查"加速开发,e2e 验收门仍只认 mini-desktop 结果(服务+浏览器同机 localhost,见game-e2e-cdp-harness.md)。 - 权威验收 / 重型构建 / 测试 / staging app / 浏览器 e2e 门 → mini-desktop(15G;java17 + mvn3.8.7 + docker + node22 + chrome146;与 prod 同构)。
- 常驻无人值守 → 6c6g:常开的 agent 编排/Workflow、cron 定时、后台批跑、git 操作(Mac 会休眠故常驻活只能留此)。禁跑重活:重型前端构建 OOM、chrome 连续 exit 144。元规则:同一失败签名第二次出现 → 换机器,不再换启动姿势。
- 共享基建 → mini-infra(绝不在其上跑项目 app / 重型构建;缺 RocketMQ+Nacos,MVP 未部署)。
2. 代码同步(分类器约束)
scp/rsync源码树会被分类器语义拦截(settings 放行也没用)。正解 = git push 到 Gitea → mini-desktop 匿名 HTTP clone/pull:http://101.200.34.71:3000/zizi-al/games-development-ai.git(:3000 HTTP 匿名读,非 :2222 SSH)。- 单文件传输:
cat local | ssh mini-desktop 'cat >/tmp/x'(分类器不拦单文件)。 - 远程脚本省心写法:
ssh mini-desktop 'bash -ls' <<'REMOTE' ... REMOTE(-l拿 PATH,-s读 stdin,免引号地狱)。 - push 竞态与管道掩码(2026-06-12 实翻):①大资产 push 在途时再发 push 会撞 Gitea ref 锁(
remote rejected (failed to update ref)/failed to push some refs)——同仓 push 串行化,等上一笔落地(git ls-remote核)再发;②git push 2>&1 | tail -1的退出码=tail 恒 0,会吞掉推送失败——push 不接管道,要么裸跑判$?,要么tee+PIPESTATUS[0]。每次 push 后以git ls-remote origin <branch>实证远端头,不信本地输出。
3. 后端重部署标准序(授权窗口内执行)
教训:
~/game-staging/repo曾是 stale 克隆,「构建源 ≠ 运行 jar」翻过车——每次部署按此序,以字节码实证收口。
- 备份运行 jar 到
/tmp; - 清本地独有 untracked(
application-staging.yaml以 origin 版为准——origin 是含 executor/passport 段的超集); git reset --hard origin/dev/2.0.0;mvn clean install -pl huijing-server -am -DskipTests(warm .m2 约 33s);- jar 字节码实证含本次修改:
unzip -p <fat.jar> BOOT-INF/lib/<模块>*.jar核对 .class; start-app.sh重启 → 验 health 200 / 四模板 banner / Flyway up-to-date / admin-api 200(M1 无回归)。
高风险/整机重部署安全变体——隔离验证后再切(2026-06-16 整机改新命名空间
com.wanxiang.huijing实证):步 4 构建出新 jar 后,先用新 jar 在隔离端口(如:48090)+ staging profile 起一个实例,live:48080全程不动;在隔离实例上验全(Flyway 全绿无冲突 / health 200 / 本波新端点逐个 200 code=0 / 免短信登录拿 token)。隔离验全过才切:停老 →start-app.sh起新 → 复验(同上 +smoke-test.sh)。任一步 fail → 停新、用/tmp备份 jar 恢复:48080→ 确认 live 恢复。严禁无隔离验证就切 live;备份 jar 留到复验全过再删。 注:start-app.sh/模块名随命名空间改名已更新(huijing-server),重部署前核对 jar 路径与现命名空间一致。
隔离实例 +
-D注入「真验 feature-flagged 后端改动」(不切 live、零改 repo/yaml)——2026-06-16 Lane E G1 实证(GP9 10/10 真验即用此法):要在 staging 真跑一个默认关闭的 feature-flag 后端改动(验它打开后行为对、关着时现行字节不变),不要改 repo 里的application-staging.yaml、也不要重部署 live。配方:用已部署的 jar(或步 4 新 jar)在隔离端口(如:48090)起一个实例,开关与外部依赖全经 JVM-D注入,例:java -jar <fat.jar> --server.port=48090 --spring.profiles.active=staging \ -Daigc.control-plane.enabled=true \ -Daigc.safety.base-url=http://100.64.0.8:3000 -Daigc.safety.api-key=<key> -Daigc.safety.model=<model>Spring 的
-D/--key=val优先级高于 yaml,故开关与 baseUrl/key/model 临时注入即生效,repo 与 live 实例一行不动。在此隔离实例上跑负例集真验(如 10 条恶意 brief 全被 GP9 拦),验完即停,flag 在主干仍默认关 = 现行字节零变。与上一条「整机重部署安全变体」互补:那条是"新 jar 验全过才切 live",这条是"现行字节不动、只临时验一个 flag 行为"。
教训速记(区分两个隔离用法):同样起
:48090隔离实例——「重部署安全变体」目标是最终要切 live(验全过 → 停老起新);「-D注入真验 flag」目标是永不切 live(只验 flag 行为,验完即停,主干字节不变)。混用会误把"只想验 flag"做成了"切 live"。
4. 构建/测试门(红线)
vue-tsc --noEmit是假门禁,前端验证一律npm run build。- mini-desktop 构建 game-studio 必须
npm run build -- --mode staging(vite preview 直接 serve 工作树 dist,默认 build = 事实上改部署)。缺--mode staging的实证后果(2026-06-21 bug#4 钉死):.env.staging的VITE_API_BASE不被加载 → axiosbaseURL烤成空串 → 所有/app-api/*打到前端 origin(:4173),而vite preview不代理/app-api(proxy 仅 dev 生效)、SPA fallback 对未知路径回index.html(HTTP 200!)。后果链:getRuntimePackage拿到 HTML 字符串 →request.ts拦截器对「非{code}包络」直接透传 body 不报错 →respVO是字符串 →respVO.manifestUrlundefined → resolvePackage 静默走 democlicker兜底(无 warn)→ demostartRuntime抛「template system under reconstruction」→game_error→ /play 显示「游戏加载失败」。排障锚:CDP 抓包看 package 请求是否打到:48080(应有,且应顺带一条.../manifest请求);grep distcreate({baseURL:应为http://<后端>:48080而非空串。 - huijing 前端(game-admin)稳定配方:pnpm9(
corepack prepare pnpm@9.15.4 --activate,一举绕开 pnpm10+ 的 lockfile 镜像校验与构建脚本审批两道坎)+ 前端目录.npmrc设 npmmirror →node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build(绕 pnpm-run 预检)。node_modules 腐化(如混入 vite8 / 杂散pnpm-workspace.yaml报 packages 缺失)→ 移开杂散文件 → 清装精确回钉版本。 - huijing-module-system 测试:
SPRING_DATA_REDIS_PORT=26379 mvn test ...(宿主 16379 被 staging redis 占用带密码,嵌入式 RedisServer 失败被吞 → NOAUTH 假红)。 - mini-desktop 长驻 serve/后台进程(2026-06-11 T1-spike 双 lane 实证):ssh 会话内
nohup &/setsid仍可能随会话 teardown 被 SIGHUP 连带杀(症状=稍后访问 ERR_CONNECTION_REFUSED)。稳定配方:首选systemd-run --unit=<name>起 durable 单元;次选独立 launchersetsid bash -c 'exec node serve.cjs'双脱离 + 起服后 5×6s 探活门(curl 200 连续过)确认常驻再继续。另:长任务后台进程严禁与pkill/curl写进同一 heredoc 串行(竞态留孤儿进程占 pid 不占端口)。
5. 冒烟门
- API 就绪门:
deploy/smoke-test.sh(只读 12 项)+--deep(13 项含 generate 入队),含 CORS/鉴权(test1 token)验证。 - gotcha:feed item 的
packageUrl恒 null,真实取包走GET /app-api/runtime/package/{versionId}。 - API 全绿 ≠ UI 通:编排器旁路会掩盖 UI 缺陷,用户可见波次收口前必须做一次真 UI 走查(见
ui-walkthrough-cdp.md)。