From c924b2bb77bda351ac2e29c502addc183f5c2ac1 Mon Sep 17 00:00:00 2001 From: john Date: Sat, 27 Jun 2026 21:48:27 +0800 Subject: [PATCH] docs: add release must-read checklist --- PRODUCTION_RELEASE_RULES.md | 2 + docs/release-deploy.md | 1 + docs/发包必看.md | 275 ++++++++++++++++++++++++++++++++++++ 3 files changed, 278 insertions(+) create mode 100644 docs/发包必看.md diff --git a/PRODUCTION_RELEASE_RULES.md b/PRODUCTION_RELEASE_RULES.md index 167030c..4467292 100644 --- a/PRODUCTION_RELEASE_RULES.md +++ b/PRODUCTION_RELEASE_RULES.md @@ -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` 或保留可运行源码树。 diff --git a/docs/release-deploy.md b/docs/release-deploy.md index d2a4ea5..1016c7d 100644 --- a/docs/release-deploy.md +++ b/docs/release-deploy.md @@ -2,6 +2,7 @@ > 2026-06-26 起,103 / Studio 正式禁止 `rsync` 发布。 > Portal 生产必须是**无源码 runtime 模式**,唯一合法入口是 `bash scripts/release-portal-runtime-prod.sh`。 +> 每次发包前先读:[发包必看](发包必看.md)。 ## 当前规则 diff --git a/docs/发包必看.md b/docs/发包必看.md new file mode 100644 index 0000000..19d851a --- /dev/null +++ b/docs/发包必看.md @@ -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 健康结果 +- 是否发现并处理了日志里的错误关键词 + +不要只说“已发布成功”。