核心能力 · Five Capabilities
所有能力围绕一条铁律:UI 永远只读 — 写操作回 vault CLI 走 age + git,服务器即使被打穿,最多丢一份 snapshot,不污染主仓库。下面 5 块是从 19 个 commit 里收敛出的最终形态。
架构 · User → UI → vault data
menshen-ui 本身不存任何 secret 副本;它只是把 vault CLI / snapshot 里的 age 加密内容渲染出来。age 私钥永远只在本机,生产读的是预先 export 出来的 snapshot JSON,被打穿也只丢一份 snapshot。
关键里程碑 · Timeline
主体功能 1 个 commit 落地(7a60de5),后续 18 个 commit 都是 UI 打磨 + 部署修。⭐ 是结构性 / 直接影响日用体验的节点。
名场面 · 1 个最值钱的坑(深度)
看起来是小事,但它把"Next.js 路由 favicon vs public favicon"和"middleware 默认拦一切"两个坑同时踩了。修法一行,但教训沉淀成可复用的 trap 模式 — 下个项目接 middleware 第一件事就是显式放行静态资源。
把门 + 钥匙孔的 favicon 上对,花了 3 次 commit + 1 次 revert
- 症状
- 给项目加 favicon(灰白色门 + 钥匙孔,呼应"门神 / vault 保险库"主题)。部署后浏览器 tab 还是空白,F12 Network 看不到 favicon.ico 请求,要么 404 要么被 302 跳登录页。前后 3 个 commit + 1 个 revert 才完全修好。
- 根因 1
- Next.js 路由 favicon 跟 middleware 打架。第一次走 App Router 约定的 app/favicon.tsx(3a3b06f),Next 把它视为路由,middleware 的 matcher 把请求一并拦了走认证流程 → 跳登录页。
- 修法 1
- Revert(ba81094),改放 /public/favicon.svg(21d5b8d)— public 下静态资源不进 Next.js 路由,理论上 middleware 也不该拦。
- 根因 2
- 但本项目 middleware 用了宽松 matcher(/((?!_next).*)),没显式排除 /favicon.svg → 静态资源照样被认证拦截 → 未登录用户拿不到 icon。
- 修法 2
- abf1648 fix(middleware): allow /favicon.svg without auth — 在 middleware 入口判 pathname === '/favicon.svg' 直接 next();此后 tab 终于带图标。
- commit
- 3a3b06f add(失败) → ba81094 Revert → 21d5b8d 改 public → abf1648 middleware 放行。
// middleware.ts — 任何 next-auth middleware 项目第一行要加 export function middleware(req: NextRequest) { const { pathname } = req.nextUrl // 静态资源 / 公开端点必须显式放行,否则 matcher 一律拦 if (pathname === '/favicon.svg' || pathname.startsWith('/api/health')) { return NextResponse.next() } return authMiddleware(req) }
📊 当前现状 · Status
下面五块每条都对应可 grep 到的事实:生产部署 / 数据规模 / 接入服务 / 最近 7 天变动 / 运维状态。
生产部署
- 域名 · menshen.mvp.restry.cn · Caddy 自动 HTTPS
- PM2 进程 · menshen-ui · 单实例 · online
- 部署链路 · mvp-deployer zip → manifest → 异步 build → snapshot 注入
- 端口 · PORT env 动态分配(cf7391a · fc437f5 修过 502)
- 登录 · 复用 wx-gateway(wx.mvp.restry.cn)扫码,openid 白名单 env,无 DB 直发 NextAuth JWT cookie
数据规模
- 412 条 secret · 跨 25 个 project(vault list | wc -l · 本机 2026-06-02 实测)
- 加密文件 · ~/.vault/store/<project>/<KEY>.age · 每条独立 age 文件
- git 同步 · 私有 repo Restry/vault-data · 每次 vault set 自动 commit + push
- 生产 snapshot · .vault-snapshot.json · 部署时 vault export -p menshen-ui 出明文,服务器无 age 私钥
- UI 自己存 0 行数据 · 无 Postgres / Redis / KV,渲染完即扔
接入服务(谁用了谁)
- 本机 vault CLI(dev)· menshen-ui 直拉 vault list/show 子进程,共享同一份 age store
- mvp-deployer(prod 上游)· 部署主机跑 vault export 拉密钥注入 manifest 的 .env;此 snapshot 同时被 menshen-ui 读
- vault hook · 每次 vault set 触发自动重生 snapshot + 重部署(devops/vault-snapshot-deploy-pattern skill)
- 下游消费者 · 25 个 project 通过 vault 拿 DATABASE_URL / AZURE_OPENAI_KEY / SECRET_KEY 等,menshen-ui 是这套的浏览器视图,不是直接被这些项目调用
- wx-gateway · menshen-ui 作为 8 业务方之一接微信 OAuth(menshen app key),零自维护开发者配置
最近 7 天主要变动(git log)
- e5fc364 docs: add project memory.md — 首次落"工具要简单"原则与踩坑史
- d2ec48f docs: add project skill for hermes-agent — agent 接此项目有标准入口
- abf1648 fix(middleware): allow /favicon.svg without auth — favicon 链路最终修复
- 21d5b8d feat(ui): add favicon via /public/favicon.svg — 第三次终于上对
- ba81094 Revert "feat(ui): add favicon" — app router 路由 favicon 被 middleware 吃,撤回
- 465f7c6 ui(mobile): toggle 在手机也显示 — 移动端体验闭环
- 3fb3c85 ui: 默认隐藏 status/non-secret 条目,搜索栏加 toggle — 默认视图收 noise
- fc437f5 fix: pnpm start 不带 -p 让 PORT env 生效 — 防 502 复发
运维状态
- 三栏布局稳定 · 5/9 后无主体功能改动,纯 UI 打磨已收尾
- 桌面 + 手机响应式跑通 · mobile body 滚 + WeChat UA 自动 OAuth + toggle 全平台显示
- vault ↔ UI 数据一致 · dev 直读 CLI / prod 读 snapshot,接口形状一致,无 drift
- snapshot 自动重生 · vault set 触发 hook,无需手动 redeploy(snapshot 缺失或过期 → bootstrap-env 起不来,这是已知运维边界)
- MVP 已满足需求 · memory.md 明确写"不要主动加功能",最近一月只动 UI 细节
🚧 未结清债务 · Open Debt
来源:.hermes/memory.md 已知问题 + 近 19 commits + 实际运维边界。每条 4 列:优先级(P0 立刻 / P1 本月 / P2 排期)· 工作量(S < 0.5d · M 1-2d · L 3d+)· 待办 · 现状(还差什么)。本项目原则上"不要主动加功能",所以多数为 P2 — 真要才动。
| 优先级 | 工作量 | 待办 | 现状(还差什么) |
|---|---|---|---|
| P1 | M | snapshot 过期/缺失告警 | 已知运维边界:snapshot 缺失 → bootstrap-env.cjs 起不来,只能 502。需 daily cron 校验 .vault-snapshot.json mtime,过期 > 7 天或文件缺失飞书告警。 |
| P1 | S | snapshot 在 deploy log 里 mask | snapshot 本质是明文 JSON,deploy phase log 若打印整段就泄。需 mvp-deployer phase log 端做 *_SECRET|*_TOKEN|*_PASSWORD|*KEY 关键字 mask,本项目 deploy hook 验证。 |
| P2 | M | audit log 持久化 | 谁在何时看了哪条 secret,目前只在 stdout 短暂日志;Caddy access log 也只有路径不含 KEY。需小表(SQLite 即可)记 (ts, openid, project, key, action)。 |
| P2 | M | 按 stack/tag 二级过滤 | 3fb3c85 加了 tags chip 但只展示,不能点击过滤。需中栏 chip 可点 → 过滤当前 project 内同 tag 的 secret。 |
| P2 | L | secret 历史版本回看(只读) | git repo 里其实有完整历史(vault-data 每次 set 一个 commit),但 UI 没暴露。要做需在 snapshot 流程同时导出 git log -p 简化版,只读历史 value。 |
| P2 | M | key rotation 提醒 | 部分 secret(如 Azure / 微信)有过期周期,目前完全靠人记。可在 vault metadata 加 rotate_at,UI 列表临期标红,> 90 天未动的 secret 显示提示。 |
| P2 | S | 白名单从 env 移到 vault | 当前 openid 白名单写在 env,改一次要 redeploy。可挪进 vault 的 menshen-ui project,bootstrap 时读,加白名单 → vault set → snapshot 自动重生即可生效。 |