跳转至

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 现有语义一致,接受;如要求收紧需单独评审