docs: add release must-read checklist

This commit is contained in:
john
2026-06-27 21:48:27 +08:00
parent 0420c42fe2
commit c924b2bb77
3 changed files with 278 additions and 0 deletions
+2
View File
@@ -1,5 +1,7 @@
# 生产发布规则
> 每次生产发包前先读:[docs/发包必看.md](docs/发包必看.md)。那里记录了 2026-06-27 Portal/goosed 事故后的强制检查项。
1. `103` 是正式生产主机,`105` 是云侧入口/历史链路;**本机一律不允许直接 `rsync``103``105`**,也不允许在线改源码后继续运行。
2. **禁止 SSH 登录 `105` 直接修改业务代码**(含服务号菜单脚本 `scripts/wechat-mp-menu.mjs`)。105 上文件是部署产物;变更必须:本地 `test-memind` 修改 → Git commit → 正式发布 → 必要时在目标环境执行 API 同步。详见 [docs/105-server-operations.md](docs/105-server-operations.md)。
2. **Portal 生产必须是无源码 runtime 模式**:构建只发生在本机 Mac,产物是 `.runtime/portal/``103` 只接收 runtime artifact、继承持久目录、启动服务,**禁止**在 `103``npm install``npm run build` 或保留可运行源码树。
+1
View File
@@ -2,6 +2,7 @@
> 2026-06-26 起,103 / Studio 正式禁止 `rsync` 发布。
> Portal 生产必须是**无源码 runtime 模式**,唯一合法入口是 `bash scripts/release-portal-runtime-prod.sh`。
> 每次发包前先读:[发包必看](发包必看.md)。
## 当前规则
+275
View File
@@ -0,0 +1,275 @@
# 发包必看
> 适用范围:`test-memind` Portal runtime 发布、`goosed-prod` 镜像更新、103/105 生产链路排障。
> 这份文档是发布前强制阅读的事故经验清单。后续发包如果绕过这里,容易把 2026-06-27 的问题重新带回生产。
## 0. 先确认边界
- 生产 Portal 主机是 `103 / Studio``john@58.38.22.103`,默认 SSH 命令不要再写旧端口 `2222`
- `105` 是云侧入口/历史链路,不是 Portal 源码真相;不要 SSH 到 105 直接改业务代码。
- Portal 生产目录:`/Users/john/Project/Memind`
- goosed 生产目录:`/Users/john/Project/goosed-prod`
- Portal 生产必须是无源码 runtime 模式,只能用 `scripts/release-portal-runtime-prod.sh` 发包。
- 禁止直接 `rsync` 到 103/105,禁止在 103 上 `npm install``npm run build` 后继续跑。
## 1. 发包前硬性检查
1. 当前代码必须已经包含以下修复,或位于它们之后:
- `fc5b50c fix: make goosed mcp paths container safe`
- `0420c42 fix: make schema bootstrap mysql safe`
2. release worktree 必须干净:
```bash
git status --short
```
3. 不允许把 `.runtime/portal/``node_modules`、本地构建缓存提交进 Git。
4. 如果 release worktree 需要临时复用主 worktree 的依赖,只能临时建 symlink,发布/测试后必须删除:
```bash
ln -s /Users/john/PycharmProjects/test/test-memind/node_modules /Users/john/PycharmProjects/test/test-memind-release-goosed-mcp/node_modules
# 测试或发布完成后:
rm -f /Users/john/PycharmProjects/test/test-memind-release-goosed-mcp/node_modules
```
5. 发包前至少跑:
```bash
node --test db.test.mjs capabilities.test.mjs llm-providers.test.mjs wechat-mp.test.mjs
```
## 2. Portal runtime 发布唯一流程
只使用:
```bash
bash scripts/release-portal-runtime-prod.sh --skip-tests --yes
```
这个脚本必须完成这些动作:
- 本机构建 `.runtime/portal`
- 上传 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。
发布成功后,记录脚本输出里的:
- `release_id`
- `archived_source`
- `full_backup`
- `persist_backup`
- `git_head`
## 3. Portal 发布后必须验收
在本机执行:
```bash
curl -k -i https://m.tkmind.cn/auth/login \
-H 'content-type: application/json' \
--data '{"username":"john","password":"wrong-password"}' | head -40
```
正确结果应该是 `401`,消息类似 `用户名或密码错误`
如果返回 `503` 且消息是 `未配置用户数据库或访问密码`,说明用户系统 bootstrap 失败,不是简单健康检查能发现的问题。
在 103 执行:
```bash
ssh john@58.38.22.103 '
printf "portal="; curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8081/api/status
for p in 18006 18007 18008 18009; do
printf "goosed_${p}="
curl -k -s -o /dev/null -w "%{http_code}\n" https://127.0.0.1:${p}/status
done
pid=$(pgrep -f "node .*server.mjs" | head -1)
echo "portal_pid=${pid}"
ps eww -p "$pid" | tr " " "\n" | awk -F= "/^(DATABASE_URL|H5_PORT|TKMIND_API_TARGETS|GOOSED_MCP_NODE_PATH|GOOSED_MCP_SERVER_PATH)=/ {print \$1\"=SET\"}"
'
```
必须看到:
- `portal=200`
- `goosed_18006=200`
- `goosed_18007=200`
- `goosed_18008=200`
- `goosed_18009=200`
- `DATABASE_URL=SET`
- `TKMIND_API_TARGETS=SET`
- `GOOSED_MCP_NODE_PATH=SET`
- `GOOSED_MCP_SERVER_PATH=SET`
日志检查:
```bash
ssh john@58.38.22.103 '
tail -200 ~/Library/Logs/memind-portal.log |
egrep "User auth bootstrap failed|ER_PARSE_ERROR|Cannot find package|Failed to add extension|No such file|Unknown extension|Provider not set|not configured" || true
'
```
新启动之后不能继续出现上述错误。
## 4. 2026-06-27 事故复盘形成的禁止项
### 4.1 不要只看 `/api/status`
`/api/status` 返回 200 只能说明 Express 进程活着,不能说明登录、数据库、Agent 会话策略都正常。每次 Portal 发包必须额外测 `/auth/login`
### 4.2 schema 必须兼容 MySQL
禁止在 `schema.sql` 里写 PostgreSQL partial index
```sql
KEY idx_xxx (...) WHERE ...
```
MySQL 不支持这种写法。生产曾因此触发:
```text
User auth bootstrap failed
ER_PARSE_ERROR
未配置用户数据库或访问密码
```
`db.mjs` 现在使用 `splitSqlStatements()`,会跳过 SQL 注释里的分号;不要改回 `sql.split(';')`
### 4.3 不要把本地 `node_modules` symlink 打进包
release worktree 如果有 `node_modules -> /Users/john/.../node_modules` symlink,构建脚本必须 realpath 后复制真实依赖。发布后必须确认线上:
```bash
ssh john@58.38.22.103 'cd /Users/john/Project/Memind && test -d node_modules/http-proxy-middleware && echo ok'
```
否则可能出现:
```text
Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'http-proxy-middleware'
```
### 4.4 Portal 下发给 goosed 的 MCP 路径必须是容器内路径
推荐并默认使用:
```bash
GOOSED_MCP_NODE_PATH=/usr/local/bin/node
GOOSED_MCP_SERVER_PATH=/opt/portal/mindspace-sandbox-mcp.mjs
```
不要让新会话继续依赖宿主机路径:
```text
/opt/homebrew/Cellar/node@24/24.16.0/bin/node
/Users/john/Project/Memind/mindspace-sandbox-mcp.mjs
```
否则会触发:
```text
Failed to add extension: IO error: No such file or directory (os error 2)
```
### 4.5 `code_execution` 默认不能下发
当前 goosed 不认识 `code_execution` 扩展。除非确认 goosed 已支持,否则不要设置:
```bash
TKMIND_ENABLE_CODE_EXECUTION_EXTENSION=1
```
### 4.6 Provider 必须同步到全部 goosed target
Portal 现在使用:
```bash
TKMIND_API_TARGETS=https://127.0.0.1:18006,https://127.0.0.1:18007,https://127.0.0.1:18008,https://127.0.0.1:18009
```
发布或重建 goosed 容器后,要确认 18006-18009 都有 provider 配置。否则 Agent 会出现:
```text
Provider 'custom_deepseek' is not configured
```
注意:DeepSeek API key / provider secret 是运行配置,不应该烘焙进镜像。
## 5. goosed-prod 镜像更新必看
当前已验证镜像:
```text
tkmind/goosed:prod-20260627-2125-mcp
```
这个镜像必须保证以下路径在容器内存在:
```text
/usr/local/bin/node
/opt/homebrew/bin/node
/opt/homebrew/opt/node@24/bin/node
/opt/homebrew/Cellar/node@24/24.16.0/bin/node
/opt/portal/mindspace-sandbox-mcp.mjs
```
重建镜像后必须验:
```bash
ssh john@58.38.22.103 '
cd /Users/john/Project/goosed-prod
set -a
source .env
source .paths.env
export PORTAL_RUNTIME_DIR="$portal_runtime_dir" MINDSPACE_ROOT="$mindspace_root"
set +a
/opt/homebrew/bin/docker compose -f docker-compose.prod.yml ps
for c in goosed-prod-1 goosed-prod-2 goosed-prod-3 goosed-prod-4; do
echo "--- $c"
/opt/homebrew/bin/docker exec "$c" sh -lc "
ls -l /usr/local/bin/node \
/opt/homebrew/bin/node \
/opt/homebrew/opt/node@24/bin/node \
/opt/homebrew/Cellar/node@24/24.16.0/bin/node \
/opt/portal/mindspace-sandbox-mcp.mjs
"
done
'
```
重建容器后必须重新确认:
- 四个容器都是 `healthy`
- 18006-18009 `/status` 都是 200。
- provider 已同步到四个 target。
- sandbox-fs smoke 能成功 `add extension` 并完成一次 `/reply`
## 6. 出问题时先走这条判断链
1. `m.tkmind.cn/api/status` 是否 200
2. `/auth/login` 是否进入 400/401,而不是 503
3. 103 Portal 进程是否拿到了 `DATABASE_URL`
4. `~/Library/Logs/memind-portal.log` 是否有 `User auth bootstrap failed`
5. 18006-18009 是否全 200
6. goosed 容器内 `/usr/local/bin/node``/opt/portal/mindspace-sandbox-mcp.mjs` 是否存在?
7. provider 是否同步到四个 target
8. 如果是微信服务号或专属 Agent 报错,必须测真实业务路径,不要只看健康接口。
## 7. 发布完成后给用户的最小回报格式
发布完成后至少说明:
- release id
- git head
- full backup
- persisted backup
- Portal 健康结果
- `/auth/login` 结果
- 18006-18009 健康结果
- 是否发现并处理了日志里的错误关键词
不要只说“已发布成功”。