docs: add release must-read checklist
This commit is contained in:
@@ -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` 或保留可运行源码树。
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
> 2026-06-26 起,103 / Studio 正式禁止 `rsync` 发布。
|
||||
> Portal 生产必须是**无源码 runtime 模式**,唯一合法入口是 `bash scripts/release-portal-runtime-prod.sh`。
|
||||
> 每次发包前先读:[发包必看](发包必看.md)。
|
||||
|
||||
## 当前规则
|
||||
|
||||
|
||||
+275
@@ -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 健康结果
|
||||
- 是否发现并处理了日志里的错误关键词
|
||||
|
||||
不要只说“已发布成功”。
|
||||
Reference in New Issue
Block a user