# 发布门禁(逐项执行,全绿才能 release)

> 配套 [架构守则.md](架构守则.md)。本清单是**可执行命令**,不是原则复述;每次发版逐条跑,贴结果。
> 铁律:**绝不发布未经测试的正式 release**(远程桌面回归的教训)。

## 0. 仓库状态

```bash
# 同步盘(飞牛/iCloud)可能弄出冲突副本、回滚文件内容——先验明正身
find . -path ./node_modules -prune -o -name '*冲突副本*' -print   # 必须为空
git status --short                                                # 无意外改动
git log --oneline -3                                              # HEAD 是你以为的那个
```

## 1. 构建(本地)

```bash
# --provenance=false 必须带:Docker 29+containerd 下缺它会让实例重建仍用旧镜像(踩过)
docker build --provenance=false --sbom=false -t ghcr.io/gloridust/wechat-on-cloud:latest ./docker
docker build --provenance=false --sbom=false --build-arg WOC_VERSION=dev-$(git rev-parse --short HEAD) \
  -t ghcr.io/gloridust/woc-panel:latest ./panel
cd panel/server && npx tsc --noEmit && cd ../web && npx tsc --noEmit && npm run build
```

## 2. 实例镜像探针(每条都跑)

```bash
docker rm -f woc-gate 2>/dev/null
docker run -d --name woc-gate --security-opt seccomp=unconfined --shm-size=1g \
  -e PUID=1000 -e PGID=1000 -e WOC_APP_TYPE=chromium ghcr.io/gloridust/wechat-on-cloud:latest
sleep 25

# ① XSETTINGS DPI 单位(96 DPI = 98304;写 96 会让一切 CEF 崩溃——史上最大 P0)
docker exec woc-gate bash -c 'DISPLAY=:1 dump_xsettings | grep "Xft/DPI 98304"' || echo "❌ DPI"

# ② 微信 CEF 等价探针:裸 chromium、不带任何 scale flag、存活 8s
docker exec woc-gate bash -c '
  rm -rf /tmp/probe; DISPLAY=:1 chromium --no-sandbox --disable-gpu --user-data-dir=/tmp/probe about:blank >/dev/null 2>&1 &
  p=$!; sleep 8; kill -0 $p 2>/dev/null && echo "✓ CEF探针存活" || echo "❌ CEF探针崩溃"; kill -9 $p 2>/dev/null'

# ③ 崩溃循环检测:干净窗口 30s 内退出次数必须为 0
sleep 30; docker logs --since 30s woc-gate 2>&1 | grep -c '已退出'   # 期望 0

# ④ 微信模式走到"等待安装"
docker rm -f woc-gate-wx 2>/dev/null
docker run -d --name woc-gate-wx --security-opt seccomp=unconfined --shm-size=1g \
  -e PUID=1000 -e PGID=1000 -e WOC_APP_TYPE=wechat ghcr.io/gloridust/wechat-on-cloud:latest
sleep 25; docker logs woc-gate-wx 2>&1 | grep -E "尚未安装|启动 微信"   # 必须命中

docker rm -f woc-gate woc-gate-wx
```

## 3. 升级路径测试(用户做的是升级,不是全新安装)

拿一个**上一 release 镜像**跑起来的实例(带已有数据卷),用面板「升级实例」原地升到新镜像,然后:

- 应用正常启动、无重启循环;
- 若涉及微信:进入桌面手动点开一篇公众号文章/一个附件,窗口能打开(DPI 事故正是死在这一步);
- 面板对该实例的「可升级」角标消失。

## 4. 面板部署验证

```bash
docker compose up -d && sleep 4
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:36080/     # 200
```

- 登录 → 进入一个实例 → 桌面出画面;
- 「管理」页加载正常(升级横幅/角标状态正确);
- 新建的容器 `docker inspect <c> --format '{{.HostConfig.LogConfig}}'` 含 max-size(R3)。

## 5. 发布

```bash
git push origin main
gh release create vX.Y.Z --target main --title "..." --notes "..."
```

release notes 必须写清:

1. **谁受影响、什么症状**(用户的语言,不是代码的语言);
2. **要不要升级实例**——更新面板 ≠ 更新实例,每次都要显式说;
3. **回滚指引**(R1):出问题如何回到上一版。

发布后:确认 CI(release.yml)双架构构建成功,再在相关 issue 回复(回复前经用户确认)。

## 6. P0 热修的特别约束

- diff 最小化:只含修复,不夹带任何特性;
- 理论必须解释**全部**已观测症状,且有 A/B 实证(R6),否则只能作为「缓解」发布并如实标注;
- 修完在 [P0复盘.md](P0复盘.md) 记一笔:症状 → 根因 → 为什么会发生 → 哪条守则能防它(没有就加一条)。
