项目矩阵 个人工具 Dmenshen-ui · 门神
DDora 主维护 · vault 生态 · 个人工具

menshen-ui · 门神

vault 的手机能开的只读 GUI · 1Password 三栏 × 微信扫码 × age 后端

所有 secret 都在本机 vault CLI 里(age 加密 + git 私有 repo),但终端机器以外查不到 — 手机想翻"那个 API key 是啥来着"得开电脑、跑 vault show、太重。menshen-ui 给 vault 配一个 1Password 风的浏览器投影:左栏 25 个 project、中栏 secret 列表、右栏详情;微信扫码登录 + openid 白名单(只有一个人能进);本地 dev 直接拉 vault 子进程,生产读 .vault-snapshot.json只读、单用户、零 DB — 不引企业级账号系统、不做权限矩阵、不让远程持有 age 私钥,vault 的安全模型一寸不动。

访问 menshen.mvp.restry.cn Restry/menshen-ui · private 依赖 vault CLI · age + git
412
Secrets · 跨项目
25
Projects · vault 分组
3
栏布局 · 1Password 风
1
白名单用户 · openid

核心能力 · Five Capabilities

5 件事,把"手机翻密钥"做成可日用

所有能力围绕一条铁律:UI 永远只读 — 写操作回 vault CLI 走 age + git,服务器即使被打穿,最多丢一份 snapshot,不污染主仓库。下面 5 块是从 19 个 commit 里收敛出的最终形态。

三栏布局
1Password 风
左 nav(项目)/ 中 list(secret)/ 右 detail,密钥管理器的人体工学最优解,不创新。顶 header 含全局搜索 + 头像昵称。
commit 7a60de5 · f00c11d
项目维度索引
25 project 分组
secret 按 vault project 分组(wx-gateway · mvp-deployer · image-studio · echo · cspy ...),顶栏 search 跨项目模糊匹配。
scrollable left nav
移动端可用
手机翻是高频
响应式坍缩 + mobile 走 body 滚(桌面保持视口锁定)。toggle 在手机也显示。WeChat UA 自动触发 OAuth,无需手点登录。
50a8f69 · 465f7c6 · b828708
按 metadata 过滤
默认收 noise
status / non-secret 条目默认隐藏(配置 / 公开值 / 历史 status 三类),搜索栏加 toggle 一键展开;tags chip 替代之前的严重度图标。
3fb3c85 · 173b738 · 6a4ae24
与 vault CLI 双向
同一份 age 文件
本地 dev 直拉 vault list/show 子进程;生产读 .vault-snapshot.json(vault hook 自动重生 + 重部署),接口形状一致,CLI 改了 UI 同步可见。
bootstrap-env.cjs · snapshot pattern

架构 · User → UI → vault data

三层一张图:桌面/手机 → menshen-ui 三栏 → age + git 数据源

menshen-ui 本身不存任何 secret 副本;它只是把 vault CLI / snapshot 里的 age 加密内容渲染出来。age 私钥永远只在本机,生产读的是预先 export 出来的 snapshot JSON,被打穿也只丢一份 snapshot。

Consumer · 单用户(openid 白名单)
桌面浏览器三栏锁视口
微信内置浏览器UA 自动 OAuth
扫码 + JWT cookie
App · menshen-ui (Next.js · 只读 · 无 DB)
menshen-ui · 1Password 风三栏
menshen.mvp.restry.cn
pm2: menshen-ui · PORT env 动态
Left nav25 个 project · scrollable · avatar+nickname
Center listsecret 列表 · tags chip · default-hide toggle
Right detailKEY / value / updated_at / mask 切换
dev: vault subprocess · prod: snapshot JSON
Data Source · age 加密 · 私钥仅本机
CLI · 本地 dev
vault list/show
直接 execFile vault CLI 子进程;延迟 ~50ms;读到的就是 ~/.vault/store/ 当前态。
File · 本机 store
~/.vault/store/*.age
每条 secret 一个 <project>/<KEY>.age,age 加密;CLI 解密时用 ~/.config/age/keys.txt。
Snapshot · 生产
.vault-snapshot.json
部署时 vault export -p menshen-ui 出明文 JSON 上传 mvp-deployer;无 age 私钥;snapshot 过期 → 起来就崩。
UI 层 · Next.js 只读 Data 层 · age + git · 私钥本机

关键里程碑 · Timeline

19 commits · 从三栏底座到 favicon 全套打磨

主体功能 1 个 commit 落地(7a60de5),后续 18 个 commit 都是 UI 打磨 + 部署修。⭐ 是结构性 / 直接影响日用体验的节点。

7a60de5 ★ 底座1Password-style three-column layout — 一个 commit 把整个 UI 底座立起来,后续所有打磨都基于这个三栏。
f00c11d ★ 头部taller header + global top search + scrollable left nav + avatar+nickname — header 加高,全局搜索上顶,左 nav 项目多了能滚。
50a8f69 fix(mobile): 响应式滚动 — mobile 走 body 滚,桌面保持视口锁定。手机查 secret 终于不别扭。
6a4ae24 ui: 删 secret 列表的严重度图标 — 密钥本身没有"严重度"维度,图标是过度设计,直接删。
3fb3c85 ui: 默认隐藏 status / non-secret 条目,搜索栏加 toggle 一键展开,默认视图收 noise。
465f7c6 ★ 移动toggle 在手机也显示 — 之前 toggle 只在桌面出,手机端被默认 hide 卡住翻不到 status 条;补上后移动端体验闭环。
fc437f5 · cf7391a fix(deploy): pnpm start 不写死端口,走 PORT env — mvp-deployer 动态分配端口,写死 3000 → 反代过来 502。
3a3b06f → ba81094 → 21d5b8d favicon 反复 3 次:先走 app/favicon.tsx 路由失败 → revert → 改 /public/favicon.svg 才上对。详见名场面段。
abf1648 ★ 配套fix(middleware): allow /favicon.svg without auth — 静态资源被 middleware 拦认证;放行后浏览器 tab 终于带门 + 钥匙孔。
e5fc364 · d2ec48f docs: 落 .hermes/memory.md + hermes-agent 项目 skill — 项目记忆与"工具要简单"原则首次成文。

名场面 · 1 个最值钱的坑(深度)

favicon 反复 3 次:Next.js 静态资源 × middleware 认证

看起来是小事,但它把"Next.js 路由 favicon vs public favicon"和"middleware 默认拦一切"两个坑同时踩了。修法一行,但教训沉淀成可复用的 trap 模式 — 下个项目接 middleware 第一件事就是显式放行静态资源。

HALL OF FAME · favicon-auth-trap

把门 + 钥匙孔的 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)
}
沉淀:这条坑成了 favicon-auth-trap 模式 — 任何用 next-auth + middleware 的新项目,接 favicon / robots.txt / sitemap.xml / og-image 这类未登录也得拿到的静态资源,middleware 入口都要显式放行,光放 /public 不够。第二条:能用 /public/* 就别用 app/favicon.tsx,Next.js 把它当路由处理会被 middleware 顺手吃掉。

📊 当前现状 · Status

五个维度,逐条带具体数字 / commit / domain

下面五块每条都对应可 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

7 条具体待办 · 每条带优先级 / 工作量 / 现状

来源:.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 自动重生即可生效。

🛠 技术栈 · Stack

RuntimeNext.js 15 · React · pnpm · PM2 StyleTailwind v4 · 无 UI 库 Authnext-auth v5 · WeChat OAuth · openid 白名单 Iconslucide-react Vaultvault CLI · age · git Opsmvp-deployer · Caddy · snapshot pattern Upstreamwx-gateway(扫码登录)