fix: route portal canary through isolated edge tunnel
Memind CI / Test, build, and release guards (push) Has been cancelled

This commit is contained in:
john
2026-07-26 20:30:56 +08:00
parent 286069449b
commit 473ad80882
10 changed files with 306 additions and 122 deletions
+10 -4
View File
@@ -1,6 +1,6 @@
# Local and 103 runtime topology
> Last confirmed: 2026-07-07 09:19 CST.
> Last confirmed: 2026-07-26 20:18 CST.
>
> This is the current topology source of truth for local Memind and 103 production. Prefer this document over older migration notes. Older architecture documents may contain historical paths from before the MindSpace split.
@@ -39,14 +39,20 @@ Portal must be released as a runtime artifact. Do not edit source directly on 10
| Item | Value |
|------|-------|
| Identity router | `127.0.0.1:18080` |
| Stable edge upstream | `105 nginx -> 58.38.22.103:8081` |
| Identity router | `127.0.0.1:18082` |
| Canary edge tunnel | `105 127.0.0.1:19082 -> 103 127.0.0.1:18082` |
| Candidate Portal | `127.0.0.1:18081` |
| Candidate goosed | `127.0.0.1:18015` |
| Release entry | `scripts/release-portal-canary-prod.sh` |
| Rollback entry | `scripts/rollback-portal-canary-prod.sh` |
The stable Portal stays on 8081. The 105 reverse tunnel points to 18080 only after the
candidate, router, backups, immutable identity checks, and fallback checks pass.
The stable Portal stays on 8081 and the normal 105 edge path is a direct upstream, not
the retired 19081 tunnel. A canary release starts an isolated 19082 tunnel and changes
the two committed 105 nginx upstreams only after the candidate, router, 103/105 backups,
immutable identity checks, and tunnel checks pass. It then proves stable fallback before
declaring success. Port 18080 is occupied by a separate Memory V2 mock service and must
not be reused.
## MindSpace Service
+16
View File
@@ -114,6 +114,22 @@ ssh root@120.26.184.105 '
- 禁止在 `105` 直接改 `server.mjs` / `wechat-mp.mjs` / `scripts/wechat-mp-menu.mjs`
- 禁止跳过健康检查就宣称“已发布”
## 103 Portal 用户灰度的 105 入口切换
103 Portal 灰度不发布业务源码到 105。正式入口由已提交的
`scripts/release-portal-canary-prod.sh` 受控变更:
1. 先备份并校验 105 活动的 `m.tkmind.cn.conf``wechat.m.tkmind.cn.conf`
2. 在 103 启动独立路由器 `18082`,并通过反向隧道只暴露为 105 本机 `19082`
3. 候选、身份路由和隧道全部通过后,脚本才把两份 nginx 上游从
`58.38.22.103:8081` 切到 `127.0.0.1:19082`
4. 必须先 `nginx -t`,再 reload,并在切换后主动证明候选故障会回落稳定 8081;
任一步失败恢复备份并回到稳定入口。
5. 回滚只能使用 `scripts/rollback-portal-canary-prod.sh`,禁止在 105 手工 `sed -i`
该动作属于 commit、CI、完整 Gate report 和明确生产批准约束下的正式发布运维,
不构成允许在线编辑 105 配置源码的一般例外。
## 服务号底部菜单(`wechat-mp-menu.mjs`
菜单名称与链接定义在本地:
+3 -1
View File
@@ -545,7 +545,9 @@ failed = skipped = blocked = unknown = cleanup_failed = 0
### 10.0 用户级灰度前置条件
用户级灰度入口由 `scripts/release-portal-canary-prod.sh` 安装:稳定 Portal 保持 8081
候选 Portal 使用 18081,候选 goosed 使用 18015,身份路由器使用 18080。灰度身份必须
候选 Portal 使用 18081,候选 goosed 使用 18015,身份路由器使用 18082105 使用独立
19082 反向隧道访问路由器。活动 nginx 上游在脚本备份、`nginx -t` 和健康检查后,才允许
从稳定 `58.38.22.103:8081` 切到 `127.0.0.1:19082`。灰度身份必须
使用不可变用户 ID、唯一用户名或微信绑定账号标识;禁止按显示昵称(包括“唐”)匹配。未命中、
身份解析失败或候选不可用必须自动回落稳定版本。
+13 -10
View File
@@ -6,8 +6,9 @@ Portal 的首个生产动作必须是用户级灰度,不能直接整包替换
| 组件 | 地址 | 说明 |
|---|---|---|
| 稳定 Portal | `127.0.0.1:8081` | 保持当前 live 目录和进程,不在灰度发布替换 |
| 灰度路由器 | `127.0.0.1:18080` | 105 的 `19081` 反向隧道在全部检查通过后切到这里 |
| 稳定 Portal | `103:8081` | 105 当前直接回源 `58.38.22.103:8081`灰度发布替换该进程 |
| 灰度路由器 | `103 127.0.0.1:18082` | 避开已占用的 18080;只通过专用灰度隧道暴露给 105 |
| 灰度边缘隧道 | `105 127.0.0.1:19082` | 反向 SSH 到 103 的 18082;不复用已退役的 19081 链路 |
| 候选 Portal | `127.0.0.1:18081` | 独立、无源码 runtime;使用被动运行时模式 |
| 候选 goosed | `127.0.0.1:18015` | 独立容器并挂载候选 runtime,不重建稳定 9 个容器 |
@@ -40,14 +41,16 @@ bash scripts/release-portal-canary-prod.sh --yes
1. 验证源码、分支、远端主线、Gate report 和 artifact hash。
2. 执行 103 只读预检。
3. 上传经过验证的候选包。
4. 创建稳定 live 全量备份持久目录备份,并校验 SHA256、gzip 和关键目录
4. 创建稳定 live 全量备份持久目录备份和 105 两份活动 nginx 配置备份,并校验完整性
5. 启动独立候选 goosed、候选 Portal 和灰度路由器。
6. 验证不可变身份配置、稳定/候选健康和路由器诊断。
7. 短暂阻止新 Agent Run,排空活动任务
8. 只把反向隧道从 8081 切到 18080
9. 主动停止候选,证明目标身份会自动回落稳定版,再恢复候选
7. 启动独立的 `105:19082 -> 103:18082` 灰度隧道并从 105 验证
8. 短暂阻止新 Agent Run,排空活动任务
9. 逐文件原子替换、验证并 reload 105 nginx,把活动上游从 `58.38.22.103:8081` 切到 `127.0.0.1:19082`
10. 主动停止候选,证明目标身份会自动回落稳定版,再恢复候选。
任一步失败都会把隧道恢复到稳定 8081,停止候选服务并保留候选目录和备份证据。
任一步失败都会把 105 nginx 恢复到稳定 `58.38.22.103:8081`,停止灰度隧道和候选服务,
并保留候选目录与备份证据。活动 nginx 配置变更只能由已提交的发布脚本执行,禁止在线手改。
## 回滚
@@ -57,10 +60,10 @@ bash scripts/rollback-portal-canary-prod.sh --yes
回滚只执行以下动作:
- 把 105 反向隧道恢复到稳定 Portal 8081
- 停止灰度路由器、候选 Portal 和候选 goosed。
- 把 105 两份活动 nginx 上游恢复到稳定 Portal `58.38.22.103:8081` 并验证 reload
- 停止灰度隧道、灰度路由器、候选 Portal 和候选 goosed。
- 清除发布排水标记。
- 验证稳定 Portal 和 105 隧道健康。
- 验证稳定 Portal 和 105 HTTPS 入口健康。
不会删除候选目录、备份或稳定运行目录。
+7 -4
View File
@@ -61,17 +61,20 @@ bash scripts/release-portal-canary-prod.sh --yes
- 上传 artifact 到 103。
- 103 备份当前 `/Users/john/Project/Memind` 全目录。
- 103 单独备份持久目录:`.env``MindSpace/``data/``users/``.tailscale/``public/plaza-covers/``logs/`
- 原子切换 live 目录
- 更新 LaunchAgent 指向 `/Users/john/Project/Memind/scripts/run-memind-portal-prod.sh`
- 健康检查 `http://127.0.0.1:8081/api/status` 返回 200。
- 备份并校验 105 的 `m.tkmind.cn.conf``wechat.m.tkmind.cn.conf`
- 保持稳定 Portal `103:8081` 不变,启动候选 Portal `18081`、候选 goosed `18015` 和身份路由器 `18082`
- 启动独立 `105:19082 -> 103:18082` 灰度隧道;禁止复用历史 19081 或占用 18080。
- `nginx -t` 通过后才把 105 活动上游从 `58.38.22.103:8081` 切到 `127.0.0.1:19082`
- 健康检查稳定、候选、回落和 105 HTTPS 入口均通过。
- 校验 `MindSpace/*/public/*.html` 下载/附件相对链接均已落盘(`check-mindspace-public-links.mjs --downloads-only`)。
发布成功后,记录脚本输出里的:
- `release_id`
- `archived_source`
- `candidate_dir`
- `full_backup`
- `persist_backup`
- 105 nginx 配置备份
- `git_head`
## 3. Portal 发布后必须验收