Repository navigation
v5.8.0 — 记忆网络:几台机器接成一个记忆体
本页会随勘误就地更新。规范全文以
main为准。
这一版只回答一个问题
两台机器,怎样才算「同一个我的记忆」?
不是把"人格"搬过去,是把文件搬过去:设定、会话原文、索引、交接包都是文件。所以这一版做的不是"迁移身份",是给记忆加一根管子——指定一台机器当服务端,其余设备通过虚拟组网连上来,按清单做增量同步。
设计思想(为什么这么设计)
- 记忆是文件,搬家就是搬文件。 不需要任何"人格编解码",也不需要模型参与;同步失败最多是没同步上,不会坏掉记忆本身。
- 真相只有一个。 服务端那份清单就是真相;客户端只按清单拉,不合并、不双写、不反向覆盖。两台机器同时写同一个文件这种问题,不在这版的职责里——那是同目录并发写的问题。
- 落下之前先落盘,落下时原子换。 写盘一律「临时文件 → fsync → os.replace」,任何时刻被杀掉,磁盘上都不会出现写了一半的文件。
- 默认不对外。 不显式指定就只听
127.0.0.1;取不到令牌服务端拒绝启动,没有"默认无鉴权"这一档。 - 只给白名单。
index/、archive/、续接/;..、绝对路径、盘符一律400。
和网盘是两种模式,别混为一谈
网盘模式(persona_cloud_bus) |
节点直连(本版) | |
|---|---|---|
| 解决什么 | 多个程序共用一个目录时怎么写不坏 | 多台机器之间怎么把文件搬过去 |
| 传输谁干 | 外包给网盘 | 自己走 HTTP/1.1(纯标准库) |
| 真相在哪 | 本地那个目录 | 服务端那份清单 |
| 用在哪 | 一台机器 / 一个目录 | 两到几台机器,经 ZeroTier、Tailscale 之类连成同网段 |
| 是否互相替代 | 不替代,可以同时开 | 不替代,可以同时开 |
一句话:网盘管"同一个目录里的并发写",本版管"机器与机器之间的增量"。把两者混成一个"同步",后面就会互相踩。
一条同步从头到尾
客户端 服务端(只有一台)
│ │
├─ GET /health ────────────────────────────────▶ │ 探活,无需令牌,不含路径
│ │
├─ GET /manifest(Bearer 令牌)───────────────▶ │ 白名单目录的 {path,size,sha256,mtime}
│ │
├─ 本地比对 sha256:已一致的跳过 │
│ │
├─ GET /blob?path=…(Bearer)────────────────▶ │ 原始字节,响应头 X-SHA256
│ │
├─ 落 ops/lanbus/ 暂存 → 校验 sha256 → os.replace ─▶ │ 只在白名单目录落地
│ │
└─ (需要回传时)POST /blob?path=…(Bearer)──▶ │ 落盘前再校验一次 X-SHA256,默认 64MB 上限
滚动、重试、跳过都在客户端;服务端只做三件事:报清单、给字节、收字节。服务端不主动推、不删任何文件。
要实现什么 → 对应机制
| 目标 | 机制 |
|---|---|
| 没网盘也能把记忆搬到另一台机器 | 清单 + 按 sha256 增量拉取 |
| 不让人一不小心把库暴露到公网 | --bind 默认 127.0.0.1;启动打印本机 IPv4 并提示「只在虚拟组网网段暴露」 |
| 没有令牌就不存在"裸服务" | 令牌取不到 → 拒绝启动;hmac.compare_digest 比对,不做长度泄露的字符串比较 |
| 不许被路径穿越 | 白名单三目录;..、绝对路径、盘符/冒号 → 400 |
| 杀进程也不会留半个文件 | 临时文件 → fsync → os.replace |
| 日志里能分清是谁 | 所有响应带 X-Node: <主机名> |
细节(按职责读)
- 鉴权:环境变量
PERSONA_LAN_TOKEN优先,其次读本机的令牌文件;服务端启动横幅只打印"令牌来源",不打印令牌内容。 - 权限边界:可写的只有
index/(同步产物)与拉取暂存ops/lanbus/;archive/、续接/只读。 - 拉取的落地顺序:先写暂存 → 校验哈希 → 再原子换进目标目录;校验不过就留在暂存并记一条拒收残片,不动正式文件。
- 可观测:
/health返回节点名、协议版本与可同步文件数(不含完整路径,避免泄露本机目录结构)。
证据(2026-11-10 本机真跑,不是"已实现")
GET /health
→ {"ok":true,"node":"4590-PC","version":1,"root":"lanbus-test","files":2}
GET /manifest(带令牌)
→ 2 个文件:index/a.md(17 字节,sha256 32afc26f…)、续接/b.md(11 字节,sha256 16d0dca6…)
GET /manifest(不带令牌)
→ 401
persona_lanbus.py sync --server http://127.0.0.1:8747 --dry-run
→ 服务端 http://127.0.0.1:8747(节点 4590-PC)
→ 清单 2 个文件(白名单 index/ archive/ 续接/)
→ 比对:需要拉取 0 个,已一致 2 个
→ [dry-run] 只打印计划:不下载、不写 ops\lanbus.json
启动横幅实际打印:令牌来源(内容不打印)、绑定 127.0.0.1:8747(默认安全档,仅本机)、本机 IPv4(127.0.0.1 loopback / 192.168.1.14 网卡)、以及「请只在虚拟组网网段暴露本服务」。
边界(没做的部分)
- 没有托管:没有"中心记忆服务",服务端就是其中一台机器,关机就同步不了。
- 没有第三台中继:两两直连,不做中转。
- 不自动合并冲突:同一个文件两边都改过,本版只按清单判断"谁缺、谁旧",不做语义合并。
- 不做传输加密:加密交给虚拟组网(WireGuard 那些);本服务自身只做鉴权与完整性校验。
- 没有界面:只有
serve/sync/token三个动作;目前也没有接进守护与托盘,不设置就不会自动跑。
修订记录
- 2026-11-10:首次发布——实现入库(此前只存在于作者本机的库根,仓库里只有规范描述)+ 本页说明。
- 编号说明:按人的要求取 5.8(v5.7.0 之后的下一个 MINOR),发布在 v6.0.0 之后,属
RELEASING§三 的例外,已在 CHANGELOG 记明。