From 7f0b21677594fd73b07cc54baba48d0da5c38a5c Mon Sep 17 00:00:00 2001 From: john Date: Wed, 1 Jul 2026 15:21:55 +0800 Subject: [PATCH] docs: add portal-release skill for package/deploy/verify/rollback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codifies the 103 production release workflow used for the 0701bug003 deploy: doc-mandated pre-release tests, build + dry-run, release-portal-runtime-prod.sh, full post-deploy verification (health, /auth/login, all 4 goosed ports, log keyword scan, real API-driven page-create smoke test), and both a preferred rollback (redeploy previous good commit) and an emergency manual rollback path. Passwords are taken from env vars (JOHN_PASSWORD/H5_ACCESS_PASSWORD), matching the existing scripts/fruit-theme-john-e2e.mjs convention — never hardcoded in the skill file. Co-Authored-By: Claude --- .claude/skills/portal-release/SKILL.md | 170 +++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 .claude/skills/portal-release/SKILL.md diff --git a/.claude/skills/portal-release/SKILL.md b/.claude/skills/portal-release/SKILL.md new file mode 100644 index 0000000..66edbfb --- /dev/null +++ b/.claude/skills/portal-release/SKILL.md @@ -0,0 +1,170 @@ +--- +name: portal-release +description: Package, deploy, verify, and roll back the Memind Portal runtime on 103 production. Use for any "打包/发布/上线/回滚/发布验证" request targeting 103. +--- + +# Portal 生产发布 / 验证 / 回滚 + +103(`john@58.38.22.103`)是唯一生产 Portal 主机。这个 skill 是打包、发布、验证、回滚的**唯一入口**——不要现场发明新流程,不要直接 SSH 到 103 手改代码或手写文件到 `MindSpace/`。 + +## 0. 发布前置检查(必须) + +```bash +git status --short +``` + +**worktree 必须干净。** 如果这个仓库有并发运行的其他 agent/session(本仓库经常有),工作区里可能混着别人未提交的改动: + +- 先辨认清楚哪些文件是"这次要发布的改动",哪些是别人正在写的无关改动 +- **不要**未经确认就自己 stash 别人的未提交内容——这是需要用户明确同意的动作,先问,拿到同意后再 `git stash push -u -m "<说明>" -- <具体文件...>` +- 发布完成后必须 `git stash pop` 把对方的改动还回去,不要留着不还 + +确认当前分支和要发布的 commit 就是打算上线的那个(`git log --oneline -3`)。 + +## 1. 发布前测试(必须,对应 docs/发包必看.md #1.5) + +```bash +node --test db.test.mjs capabilities.test.mjs llm-providers.test.mjs wechat-mp.test.mjs +``` + +全绿之后才能进入打包发布,不要跳过。 + +## 2. 打包 + 发布(唯一入口) + +先 dry-run 确认构建产物正常(不会碰生产): + +```bash +bash scripts/release-portal-runtime-prod.sh --skip-tests --dry-run +``` + +确认无误后正式发布(`--skip-tests` 是因为第 1 步已经手动跑过;这里不再重复跑脚本内置的窄范围测试): + +```bash +bash scripts/release-portal-runtime-prod.sh --skip-tests --yes +``` + +这个脚本会自动完成: + +1. 本机构建 `.runtime/portal` +2. 上传到 103 +3. **103 备份当前 `/Users/john/Project/Memind` 全目录**(`full_backup`) +4. **103 单独备份持久目录**:`.env`、`MindSpace/`、`data/`、`users/`、`.tailscale/`、`public/plaza-covers/`、`logs/`(`persist_backup`) +5. 原子切换 live 目录(旧源码树移入 `archived_source`,不是删除) +6. 更新 LaunchAgent,健康检查 `http://127.0.0.1:8081/api/status` 200 +7. 脚本内置 `trap rollback ERR`:如果切换过程中途失败,会自动尝试把旧目录移回来并重启旧服务——但**这只覆盖脚本执行期间的失败**,脚本成功退出之后发现问题需要走第 4 节手动回滚 + +**记录脚本输出里的这几行,回滚和报告都要用**: + +``` +release_id=... +archived_source=/Users/john/Project/archives/Memind-source-before- +full_backup=/Users/john/Project/backups/memind/memind-full--before.tar.gz +persist_backup=/Users/john/Project/backups/memind/memind-persisted--before.tar.gz +``` + +## 3. 发布后验证(必须,不能只看 /api/status) + +### 3.1 健康检查 + auth/login(本机执行) + +```bash +curl -k -i https://m.tkmind.cn/auth/login \ + -H 'content-type: application/json' \ + --data '{"username":"john","password":"wrong-password"}' | head -20 +``` + +必须是 **401** + `用户名或密码错误`。如果是 `503` + `未配置用户数据库或访问密码`,说明用户系统 bootstrap 失败——不是简单健康检查能发现的问题,必须当场处理,不能算发布成功。 + +### 3.2 Portal + 四个 goosed 实例(SSH 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\"}" +' +``` + +必须全部是 `200`,五个 env var 全部 `SET`。 + +### 3.3 日志关键词检查(SSH 103) + +```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" || echo "no error keywords found" +' +``` + +新启动之后不能再出现这些关键词。 + +### 3.4 真实业务路径验证(必须,测试账号 `john`) + +只看健康接口不算数,必须走一次真实的"生成页面 → 发布 → 能访问"链路。**禁止直接 SSH 到 103 手写文件到 `MindSpace/` 目录充当验证**——那是绕过应用本身的路径,不代表真实用户会走的流程,而且是对生产文件系统的越权写入。只能通过应用自己的 API。 + +密码通过环境变量传入,**不要把明文密码写进这个文件或提交到仓库**(沿用 `scripts/fruit-theme-john-e2e.mjs` 里的既有约定:`JOHN_PASSWORD` / `H5_ACCESS_PASSWORD`,向用户当场要,或从 `.env` 之外的安全渠道取): + +```bash +CJ=/tmp/portal-release-verify-cookies.txt + +# 1. 登录(JOHN_PASSWORD 从环境变量取,不要硬编码) +curl -k -s -c "$CJ" -X POST https://m.tkmind.cn/auth/login \ + -H 'Content-Type: application/json' \ + -d "{\"username\":\"john\",\"password\":\"${JOHN_PASSWORD}\"}" +# 必须 authenticated:true + +# 2. 通过真实 API 创建一个页面(验证 mindspace pages 管线本身没坏) +BODY=$(node -e "console.log(JSON.stringify({title:'发布验证页',summary:'release verify',content:'发布验证页ok',content_format:'html',page_type:'html',category_code:'draft'}))") +curl -k -s -X POST https://m.tkmind.cn/api/mindspace/v1/pages \ + -H "Content-Type: application/json" -b "$CJ" -c "$CJ" -d "$BODY" +# 必须 201,hasThumbnail:true + +# 3. 只读验证公网静态页服务没坏:用一个已知存在的已发布页面,不要新写文件 +curl -k -s -o /dev/null -w '%{http_code}\n' \ + "https://m.tkmind.cn/MindSpace/<已知已发布用户ID>/public/<已知文件名>.html" +# 必须 200 +``` + +第 2 步创建的测试页面记录会留在生产库里(`draft`/`private`,风险很低)。**不要自己调用 DELETE 清理**——删除生产数据记录需要用户明确同意,验证完之后把 page id 报给用户,让用户决定是否清理。 + +## 4. 手动回滚(脚本成功退出之后才发现问题时用) + +**优先方式:重新发布上一个已知good的 commit**,而不是手动倒腾备份目录——这样能完整复用第 2 节的持久化保留逻辑,不会把回滚期间用户新写的 `MindSpace`/`data`/`users` 数据搞丢: + +```bash +git log --oneline -5 # 找到上一个已验证通过的 commit +git checkout <上一个好的 commit 或分支> +bash scripts/release-portal-runtime-prod.sh --skip-tests --yes +# 然后重新走第 3 节完整验证 +``` + +**紧急方式**(本地仓库回不去、必须直接用远端备份时): + +```bash +ssh john@58.38.22.103 ' + launchctl bootout gui/$(id -u)/cn.tkmind.memind-portal 2>/dev/null || true + lsof -tiTCP:8081 -sTCP:LISTEN | xargs kill 2>/dev/null || true + mv /Users/john/Project/Memind /Users/john/Project/Memind-bad-release-$(date +%s) + mv /Users/john/Project/Memind + launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/cn.tkmind.memind-portal.plist + launchctl kickstart -k gui/$(id -u)/cn.tkmind.memind-portal +' +``` + +⚠️ `archived_source` 里的 `MindSpace`/`data`/`users` 是**发布那一刻**的快照。如果坏版本上线后已经有用户写入了新数据,直接整体换回去会丢失这段时间的新数据——先确认这一点,必要时把坏版本目录里当前的 `MindSpace`/`data`/`users` 复制出来,回滚后再合并回去,而不是整体覆盖。回滚后必须重新走第 3 节验证。 + +## 5. 发布完成后的最小回报格式(必须,不能只说"已发布成功") + +- release id / git head / git branch +- full backup 路径 +- persisted backup 路径 +- Portal 健康结果(200/非200) +- `/auth/login` 结果(401 + 正确消息,还是别的) +- 18006-18009 健康结果 +- 日志关键词检查结果 +- 第 3.4 节真实业务路径验证结果(含遗留的测试 page id,交给用户决定是否清理) +- 是否发现并处理了问题;如果发布后发现回滚,说明触发原因