OpenCode 记忆库(opencode-mem Web)受控入口
更新:2026-10-14
📌 结论摘要
opencode-mem Web 已接入 Console 导航。 入口为 https://mem.yohan.fun,位于左侧「CloudCLI 工作台」之后,仅对 admin 可见、以 iframe 内嵌,并且可以跨源编辑(新建、修改、删除记忆全部可用)。依据是 2026-10-14 的端到端实测:公网链路完成「创建 → 检索命中 → 删除 → 复查消失」闭环;同一请求头直连插件原始端口返回 403,证明跨源写入是被发布层修好,而不是插件被放宽。
🗂️ 目录
🧩 已部署链路
手机或电脑浏览器(已登录 A-Auth 的管理员)
-> https://mem.yohan.fun MM 边缘 Nginx(443 + 证书,frame-ancestors 放行 Console)
-> RR 私有回源网关 100.64.0.1:39200(按 Host 路由 + A-Auth admin 校验)
-> 101 Tailnet 100.64.0.2:4748 opencode-mem-web.service(来源头改写代理)
-> 101 loopback 127.0.0.1:4747 opencode-mem 插件 Web(随 opencode serve 进程)
三层职责清晰:MM 只管 TLS 与可嵌入策略;RR 只管「谁可以进来」(复用 Console 的 A-Auth 管理员会话);101 只管「请求怎么被插件接受」。
🔑 跨源编辑为什么需要一层发布代理
插件的来源策略是只放行 loopback 来源(dist/services/cors.js 的 isAllowedBrowserOrigin):当请求头的 Origin 主机名不是 localhost、127.0.0.1 或 [::1],且插件未启用 HTTP 基本认证时,一律返回 403 Cross-origin requests are restricted to loopback origins.。
关键在于浏览器对任何非 GET/HEAD 请求都会带 Origin,即使是同源请求。Console 用 iframe 嵌入 mem.yohan.fun,iframe 内的界面自己再调 /api/*,从浏览器角度这是同源请求(因此不触发 CORS 校验,也不需要 Access-Control-Allow-Origin 匹配);但插件仍然会看到 Origin: https://mem.yohan.fun 并据此判定为跨源,于是新建、编辑、删除全部 403——这就是「只能看、不能改」的根因。
opencode-mem-web.service 运行的 /root/.agents/bin/ocmem-web-proxy.mjs 只做三件事:
| 处理 | 目的 |
|---|---|
Origin / Referer 改写为 loopback |
让插件的来源校验通过 |
sec-fetch-site 改写为 same-origin |
与来源头保持一致,避免二次校验 |
Host 改写为 127.0.0.1:4747 |
保持插件路由判定稳定 |
不修改插件本体:opencode-mem@2.31.2 保持官方安装、pinned 版本,代理只是外部适配层。
实测口径:用真实浏览器打开 http://100.64.0.2:4748/(Origin 主机名为 100.64.0.2,同样不属于 loopback 白名单),在页面内执行同源 fetch:GET /api/stats 返回 200、POST /api/memories 返回 200 并成功建条、DELETE /api/memories/<id> 返回 200。对照实验:同样的请求头直连 http://127.0.0.1:4747/(不经过改写代理)返回 403,证明改写层确实是必要条件,而不是插件被放宽。
已知边界:如果未来出现「从另一个站点用 XHR 直接调用 mem.yohan.fun/api/*」的场景,插件的响应头 Access-Control-Allow-Origin 会停留为 loopback 值,浏览器将按 CORS 拒绝——当前 Console 用 iframe 内嵌、界面自身同源调用,不涉及该路径;一旦要改成跨站点直调,必须在发布代理里补齐 CORS 响应头。
🛡️ 安全边界
| 项目 | 事实 |
|---|---|
| 谁可以访问 | 仅 A-Auth 角色为 admin 的会话;未登录或非管理员经 RR 网关 302 跳转 auth.yohan.fun/login?next=https://mem.yohan.fun/ |
| 插件自带 Token 是否等于门禁 | 不等于。插件会把 window.__OPENCODE_MEM_TOKEN__ 直接注入 index.html,任何能打开首页的人都能读到;它只是同源防护令牌。真正的门禁在 RR 网关的 auth_request |
| 公网暴露面 | 仅 443;4747 只监听 loopback,4748 只监听 101 Tailnet 100.64.0.2,均不对公网监听 |
| Console 侧 | CSP frame-src 增加 https://mem.yohan.fun;导航项标记 admin-only 并加入 adminOnlyViews |
| 记忆内容敏感性 | 记忆库含项目代号、数据库选型、配置与偏好等推断信息,属于私有数据,因此不设 user 可见 |
🧪 运维与验收
| 验收项 | 命令或检查 | 成功标准 |
|---|---|---|
| DNS | dig +short mem.yohan.fun |
返回 edge.yohan.fun → 91.149.239.34 |
| 证书 | MM 执行 openssl x509 -in /www/server/panel/vhost/cert/mem.yohan.fun/fullchain.pem -noout -ext subjectAltName |
DNS:mem.yohan.fun |
| 边缘可嵌入 | curl -I https://mem.yohan.fun/ |
含 content-security-policy: frame-ancestors https://console.yohan.fun ...,且不含 x-frame-options |
| 未登录门禁 | 匿名 curl -I https://mem.yohan.fun/ |
302 到 https://auth.yohan.fun/login?next=https://mem.yohan.fun/ |
| 管理员可读 | 带管理员凭据 GET https://mem.yohan.fun/ |
200 text/html,且首页含 window.__OPENCODE_MEM_TOKEN__ |
| 跨源写 | 带 Origin 的 POST/DELETE https://mem.yohan.fun/api/... |
非 403;创建后可被 GET /api/search?q= 命中 |
| 浏览器实测 | 真实浏览器打开 http://100.64.0.2:4748/ 后执行同源 fetch('POST /api/memories') |
返回 200 且成功建条(Origin 主机名非 loopback) |
| 来源改写生效 | 对照 curl -X DELETE -H 'Origin: https://mem.yohan.fun' http://127.0.0.1:4747/api/memories/x |
直连返回 403(证明改写层确实是必要的) |
| 101 发布服务 | systemctl is-active opencode-mem-web.service |
active,且 enabled |
| 网关契约 | RR 执行 agentmeshos-main-origin-gateway.sh --check |
输出「主节点私有回源网关正常。」 |
| 边缘契约 | MM 执行 agentmeshos-edge-nginx-deploy.sh check |
证书、回源、语法全部通过 |
| 静态契约 | bash tools/console-site/tests/workspace-contract.sh |
入口、视图、CSP 断言全部存在 |
| 嵌入契约 | bash tools/console-site/tests/embed-contract.sh |
opencode-mem 一行 PASS |
🔁 回退与恢复
| 层级 | 回退动作 |
|---|---|
| Console 导航 | 从 tools/console-site/src/index.html、src/app.js、docker/nginx.conf 移除 opencodemem 条目后重新发布镜像;Console 不可用时可 bash scripts/deploy/console-site/agentmeshos-console-site-deploy.sh rollback |
| RR 私有回源网关 | bash /usr/local/lib/agentmeshos/agentmeshos-main-origin-gateway.sh --rollback(脚本自带 last-known-good) |
| MM 边缘配置 | AGENTMESHOS_EDGE_NODE_ID=mm bash /usr/local/lib/agentmeshos/agentmeshos-edge-nginx-deploy.sh rollback(备份在 /var/backups/agentmeshos/edge-nginx/) |
| DNS | 删除 AliDNS mem CNAME;记录 ID 2110054870144705536(2026-10-14 创建) |
| 证书续期 | acme.sh 全局 --cron 已覆盖;单站续期可用同一 --dns dns_ali 参数重签 |
| 101 发布服务 | systemctl disable --now opencode-mem-web.service |
以上各层互相独立:Console 回退不影响 DNS/证书;网关回退不影响 101 的发布服务;插件本体始终未被修改,因此不存在插件侧回滚。
⚠️ 风险与未决项
| 风险或未决项 | 当前事实 | 下一步 |
|---|---|---|
| 来源改写是外部适配层 | 插件默认只放行 loopback;改写依赖插件行为不回退 | 插件升级后必须重跑「跨源写」验收;若插件改为按 Host 判定,需同步调整 ocmem-web-proxy.mjs |
| MM 边缘存在重复 server_name 告警 | agentmeshos-edge.conf 与宝塔站点文件对同名域名重复声明,先加载者生效(当前为边缘配置) |
属于既存事实,本次未改变;如后续清理宝塔站点需重新验证各域名落点 |
| 101 发布服务未纳入仓库部署 | opencode-mem-web.service 由 101 本机维护,不经 GitHub Actions |
若要求统一治理,需另定 101 侧 systemd 资产纳管方案 |
| A-Auth API Key 可访问 | 网关按 Console 同等策略转发 X-AgentMeshOS-API-Key |
与 Console 现有语义一致,接受;如要求收紧需单独评审 |