# 21 资料库:托管目录、本机目录、远程存储、网络链接与资料集合 > 状态:**实施中(2026-11-06)**:第一期(资料库类型重构、网络链接、资料集合)、第二期(挂载服务、本机目录)、第三期(远程存储)已完成,见 §15。取代 [07 §4.1](07-files-and-knowledge.md) 中"登记服务器路径"的资料库。 ## 1. 背景 现有资料库只能登记**平台服务进程看得到的任意路径**。平台以 agentsvc 运行,systemd 配置了 `ProtectHome=yes`、`ProtectSystem=strict`(06 §5、ADR-0012)。这带来四个问题: - **看不到需要的资料**:home 下的资料无法登记;外接设备上的资料只有权限恰好允许时才能读。 - **范围过宽**:除禁止项外,任何路径都能登记,包括 `/etc` 等系统文件(已验证 agentsvc 能读 `/etc/hostname`)。隐藏文件(`.git`、`.env`)也不会被过滤。 - **来源单一**:网络上的资料(内网文档站、对象存储、NAS、网盘)没有登记方式。 - **选择麻烦**:Profile 只能一个个勾选资料库。 ## 2. 目标与非目标 目标: 1. 资料库分为四种类型:**托管目录、本机目录、远程存储、网络链接**;去掉"任意服务器路径"。 2. 本机目录和远程存储由一个**独立的挂载进程**以部署用户的身份提供: - **只读**; - **过滤隐藏文件**; - 访问范围由挂载进程强制检查。 平台自身的 systemd 限制保持不变。 3. 移动设备、远程存储不可用时,资料库保留,状态显示为失效;恢复后**自动可用**。 4. 资料集合:Profile 可以勾选集合,也可以勾选单个资料库。 非目标: - 本机目录和远程存储不支持写入; - 不做索引检索(`kb_search`); - 网络链接不支持需要登录的地址; - 不做集合嵌套; - 设备断开、恢复不写审计日志; - 不支持需要 OAuth 的网盘和国内网盘(§12)。 ## 3. 资料库类型 | 类型 `kind` | 登记内容 | Agent 看到的路径 | Agent 读写 | 由谁提供 | |---|---|---|---|---| | `managed` 托管目录 | 名称、说明 | `/srv/agent-server/knowledge/files/` | 只读或可写 | 平台自己的目录,后台"文件管理"上传、编辑 | | `local` 本机目录 | 源目录(`/home`、`/mnt`、`/media` 下) | `/srv/agent-server/knowledge/mnt/` | 只读 | 挂载进程 | | `remote` 远程存储 | 存储类型、连接参数、凭据、路径 | `/srv/agent-server/knowledge/mnt/` | 只读 | 挂载进程(rclone) | | `link` 网络链接 | 链接(http/https) | 无,提示词中给出链接 | 只读 | Agent 用 `web_fetch` 读取 | 所有资料库都有:名称、说明(告诉 Agent 这里有什么)、启用状态。 **迁移**:生产环境目前没有登记资料库。开发库中已有的记录迁移为 `local`,源目录取原路径;挂载进程检查不通过的,显示为"位置不允许"。Profile 中的引用保持不变。 ## 4. 挂载进程(agent-mounter) ### 4.1 程序与运行方式 - 一个 Go 程序,同时承担: - 挂载管理(Unix socket 接口); - 文件系统(go-fuse):每个本机目录和远程存储资料库各对应一个挂载,都在同一个进程中提供; - 远程存储时,启动并管理 rclone 子进程(§6)。 - 源码在 `apps/mounter/`,依赖 go-fuse 和 golang.org/x/sys。`deploy.sh` 编译为静态程序,放到 `/srv/agent-server/app/mounter/`(root 所有、只读)。 - 单独的 systemd 服务 `agent-mounter.service`,**以部署用户运行**(`install.sh` 执行时的 `$SUDO_USER`,生产上是 jerry-remote,已确认可以接受它等同于 root)。 - 通过 setuid 的 `fusermount3` 挂载,**不需要 root**。需要在 `/etc/fuse.conf` 中开启 `user_allow_other`,agentsvc 才能访问。 - **systemd 限制**: - 挂载必须发生在主机的挂载命名空间,所以不能用 `ProtectSystem`、`ProtectHome`、`PrivateTmp`、`PrivateMounts`; - `fusermount3` 是 setuid 程序,所以不能开 `NoNewPrivileges`; - systemd 中 `ProtectKernel*` 等选项会创建挂载命名空间,`LockPersonality`、`RestrictRealtime`、`SystemCallFilter` 等在非 root 用户下会隐含 `NoNewPrivileges`,所以都不能用。实际只设了 `MemoryMax`、`TasksMax`(实施时更正)。 - 进程退出时,它提供的所有挂载随之失效。启动时先清理 `mnt/` 下失效的挂载(`fusermount3 -uz`),再按登记重新挂载。只有挂载程序本身有改动时,`deploy.sh` 才重启它,挂载中断几秒后自动恢复。 ### 4.2 文件系统的行为 挂载参数:`ro,nosuid,nodev,noexec,allow_other`。 | 行为 | 说明 | |---|---| | 只读 | 挂载层面只读,任何写入都返回"只读文件系统" | | 隐藏文件 | 以 `.` 开头的文件和目录**不出现在目录列表中,按路径访问也返回"不存在"**。`ls -a`、`find`、`grep -r` 都看不到(已实测) | | 权限 | 文件显示为所有人只读、目录为所有人可进入。实际以部署用户的身份读取,不修改原文件的权限 | | 符号链接 | 原样呈现,由读取方在自己的文件系统视图中解析。指向挂载范围外的链接在平台和沙箱中会失效 | | 防止路径替换 | 每次访问先打开源目录并核对设备号、inode 号,再相对它打开(`openat2` + `RESOLVE_BENEATH \| RESOLVE_NO_SYMLINKS \| RESOLVE_NO_XDEV`)。即使有人在访问过程中把目录替换成符号链接,也不会读到源目录以外的内容;源目录下另外挂载的文件系统不提供 | | 不占用设备 | 不长期持有源目录的句柄(只有 Agent 正在打开的文件会占用),外接设备可以正常卸载、安全弹出,自动挂载的空闲卸载也不受影响。设备拔出后、挂载表变化被处理之前,原路径即使露出挂载点下面的其他文件,也会因设备号不符而返回"输入/输出错误" | | 不可用时 | 资料库状态不是"正常"时,挂载点根目录照常存在,读取目录或文件返回"输入/输出错误" | ### 4.3 访问范围(本机目录,由挂载进程强制检查) 登记、列目录、每次解析源目录时都会检查: 1. 解析符号链接后的真实路径必须位于 `/home`、`/mnt`、`/media` 之下。允许的根目录**写死在代码中**,不接受平台传来的配置。 2. 不能是这三个目录本身,也不能是某个用户的 home 目录本身(`/home/`),只能是更深一层的子目录。 3. 路径中任何一段都不能以 `.` 开头。 4. 必须是目录,并且部署用户能读取。 5. 挂载点由挂载进程自己拼接(`mnt/`),`` 必须匹配资料库 ID 的格式。 后台逐级选择目录时,`list` 只返回子目录名,隐藏以 `.` 开头的目录,并标出不能选择的目录及原因。 ### 4.4 本机目录的设备识别与自动恢复 现场情况: - 外接盘 `/media/jerry-remote/Data` 写在 fstab 中,开机自动挂载; - **它的挂载点下面本身就有文件**:开机日志提示 "Directory … to mount over is not empty"。所以硬盘不在时,这个路径仍然存在,里面是别的文件。 因此,本机目录不能只按路径判断,而是按"设备加设备内路径"识别: - **登记时**挂载进程返回源目录所在的文件系统: - 在系统盘(根分区所在的设备)上,例如 home 下的目录:只记路径; - 在其他设备上:记录**文件系统 UUID**(来自 `/proc/self/mountinfo` 和 `/dev/disk/by-uuid`)和**设备内的相对路径**,同时保留原路径用于显示。 - **每次访问**时,挂载进程先找到"这个 UUID 当前挂在哪里",再拼出实际路径。 - 实际路径同样要经过 §4.3 的检查。设备被挂到允许范围以外时,状态为"位置不允许"。 - 挂载位置的查找结果会缓存。挂载表有变化时(监听 `/proc/self/mountinfo` 的 `POLLPRI` 事件),立即重新计算状态,不需要定时检查。 - 设备重新挂上后,资料**立即恢复可用**,即使挂载路径变了也可以(卷标变化、设备名从 sda 变成 sdb)。换成另一块设备时,即使路径相同,也不会被误认为是原来那块。 状态: | 状态 | 判断 | |---|---| | `ok` 正常 | 设备在、路径存在、检查通过 | | `device_missing` 设备未连接 | 这个 UUID 当前没有挂载在任何地方 | | `path_missing` 路径不存在 | 设备在,但其中的目录被删除或改名 | | `not_allowed` 位置不允许 | 实际路径不在 §4.3 允许的范围内 | | `unreachable` 无法连接 / `auth_failed` 认证失败 | 远程存储(§6) | **设备由谁挂到系统上**:挂载进程只负责识别设备回来了,不负责把设备挂到系统上。 | 方式 | 结论 | |---|---| | fstab 中加 `x-systemd.automount`(以及 `x-systemd.device-timeout=1s`) | **推荐用于用作资料的硬盘**。访问路径时系统自动挂载,设备不在时很快报错。由 root 为每块盘配置一次 | | 桌面自动挂载(udisks2) | 不可靠:需要有人登录桌面,而且只有那个用户能读。目前登录桌面的是 jerry,jerry-remote 读不到 `/media/jerry` | | 挂载进程自己挂载设备 | 不做:需要 root 或桌面会话授权 | 后台资料库页显示这些说明。设备状态为"未连接"时,提示检查设备是否已挂载。 ### 4.5 与平台的通信 - Unix socket:`/srv/agent-server/mounter/mounter.sock`。 - 目录所有者为部署用户、组为 agentsvc,权限 0750;socket 0660; - 平台通过组权限连接。 - **鉴权**:Agent 的进程也是 agentsvc 用户,所以: - 挂载进程启动时生成随机密钥,写入 `/srv/agent-server/mounter/token`(0640); - 平台每次请求都带上这个密钥; - `/srv/agent-server/mounter/` 整个目录加入**内置全局禁止项**,两个引擎的沙箱都看不到。目录里还有远程存储的凭据和缓存(§6)。 - 协议:一行一个 JSON 请求和响应。 | 请求 | 说明 | |---|---| | `list { path }` | 列出可选择的子目录 | | `check { source }` | 按 §4.3 检查本机目录,返回识别结果(是否在系统盘、UUID、设备内路径) | | `test { remote }` | 测试远程存储的连接,列出根目录 | | `sync { mounts }` | 发送完整的登记列表,挂载进程挂上缺少的、卸载多余的、更新有变化的,并返回各挂载的状态 | | `status` | 各挂载的状态及最近一次变化的时间 | - **平台是唯一的状态来源**,登记保存在平台数据库中。 - 挂载进程把最近一次 `sync` 的列表保存到 `/srv/agent-server/mounter/state.json`(0600),只用于开机后不等平台就能立即恢复挂载。平台每次 `sync` 都会覆盖它,而且挂载进程对每一项都会重新检查。 - 远程存储的凭据随 `sync` 下发,也保存在这个文件中。 调用时机: | 时机 | 动作 | |---|---| | 平台启动 | `sync`(挂载进程没启动时,按退避间隔重试) | | 资料库新增、修改、删除、启用、停用 | `sync` | | 挂载进程启动 | 读取 `state.json` 恢复挂载 | | 后台打开资料库页 | `status` | | 每次会话开始运行 | `status`(超时 1 秒),不可用的资料库在提示词中标注"暂不可用" | 不需要定时同步。 ## 5. 托管目录 - 平台在 `/srv/agent-server/knowledge/files/` 下创建目录,所有者 agentsvc。用后台"文件管理"上传、新建、编辑、删除文件。 - **Agent 读写**: - 默认只读; - 可以设为"允许 Agent 写入",按现有读写资料库的方式开放沙箱写权限。 - 页面提示:可写的托管目录由所有使用它的会话共享,写入会影响其他会话。 - 不经过挂载进程,所以隐藏文件不会被过滤。上传 zip 解压时跳过以 `.` 开头的项。 - 删除资料库时,需要二次确认,然后删除目录及其中的文件。 - 以后如果要像技能那样推送、审核、做版本,作为托管目录的一种维护方式(不在本次范围)。 ## 6. 远程存储 ### 6.1 方案 ``` 远程存储 ──rclone mount(只读)──▶ /srv/agent-server/mounter/remote/(内部,Agent 看不到) │ 挂载进程的文件系统(隐藏文件过滤、只读、状态) ▼ /srv/agent-server/knowledge/mnt/(Agent 看到的位置) ``` - **rclone 版本**:随部署放一个**固定版本的官方程序**(下载时校验 sha256),放在 `/srv/agent-server/app/mounter/rclone`。Ubuntu 自带的 1.53 太旧。 - 挂载进程为每个远程存储资料库启动一个 rclone 子进程: ``` rclone mount kb: /srv/agent-server/mounter/remote/ --read-only --vfs-cache-mode full --cache-dir /srv/agent-server/mounter/cache/ --vfs-cache-max-size 1G --vfs-cache-max-age 1h --dir-cache-time 1m ``` - `--vfs-cache-mode full` 让 pandas 等需要随机读取的程序可以正常工作(xlsx、parquet)。 - 缓存有上限,过期自动清除,符合"用时读取,不长期保存"。 - **连接参数和凭据**:通过子进程的环境变量传给 rclone(`RCLONE_CONFIG_KB_*`),**不放在命令行**里,因为命令行在进程列表中所有人可见。 - **状态**: - 根据 rclone 子进程是否存活、连接检查(另起 `rclone lsf` 列根目录,带超时)的结果和错误信息,得出 `ok`、`unreachable`、`auth_failed`、`path_missing`(存储桶不存在)、`mount_failed`(配置不正确)。 - 连接检查的时机:挂载完成后;不正常时每分钟一次,正常时每 5 分钟一次;平台查询状态时,超过 30 秒没检查过的在后台检查。这是唯一的定时检查,本机目录不需要。 - rclone 子进程退出时,挂载进程按退避间隔(5 秒起,最长 5 分钟)重启它。 - 恢复后状态自动变回正常。 - 挂载进程需要联网,只访问管理员登记的地址。 ### 6.2 首期支持的存储 | 存储 | 填写 | 覆盖范围 | |---|---|---| | S3 兼容 | 地址(endpoint)、区域(可选)、存储桶、路径前缀、Access Key、Secret Key | MinIO、AWS S3、阿里云 OSS、腾讯云 COS、华为云 OBS、七牛、Ceph、R2 等 | | WebDAV | 地址、用户名、密码、服务类型(通用 / Nextcloud / ownCloud / SharePoint) | 坚果云、Nextcloud、群晖 WebDAV 等 | SMB、SFTP 视需要在后续增加,表单字段已经确定:SMB 为服务器、共享名、用户名、密码;SFTP 为主机、端口、用户名、密码或私钥。NAS 也可以由系统用 fstab 挂到 `/mnt` 下,再按"本机目录"登记。 ### 6.3 使用提示与限制 - 提示词中把远程存储标注为"远程资料",并提示:先按文件名和目录结构查找,再读具体文件,避免对整个目录 `grep -r`(这相当于下载全部文件,又慢,公有云还会产生流量费)。 - 文件很多的存储桶,应该用路径前缀只挂需要的部分。 - 远程的改动在目录缓存过期(1 分钟)后才能看到。 - 建议使用**只读权限的密钥**。 ### 6.4 凭据 - 凭据保存在资料库记录中。后台只写不读:已设置的显示"已设置",留空表示沿用原值。与 MCP 服务的做法相同。 - 审计日志中不出现凭据的值。 - 以后如有多个资料库共用同一组凭据的需要,再参照 MCP 凭据(20)增加共享凭据。 ## 7. 网络链接 - 登记:名称、链接(http/https)、说明(可选)。后台提供"测试访问"按钮。 - 系统提示词和 `kb_list_roots` 的清单中列出名称、链接和说明,由 Agent 自己决定: - 网页用 `web_fetch` 转为 Markdown; - 文件下载到工作区后再处理。 - **内网放行**:设置 `network.blockPrivateFetch` 开启时(默认开启),网关 `web_fetch` 和 Claude 的 WebFetch 都会拦截私网地址。对当前会话可用的链接资料库: - 放行与登记链接**同源**(协议、主机、端口都相同)的地址; - 重定向的每一跳都要检查; - 只对勾选了这个资料库的 Profile 的会话生效。 - Codex 在沙箱中用 curl 访问网络的规则不变;提示词中建议使用 `web_fetch`。 ## 8. 资料集合 - 集合包括:名称、说明、成员(资料库 ID 列表,可以混合各种类型)。一个资料库可以属于多个集合。不支持集合嵌套。 - Profile 新增 `knowledgeCollectionIds`。**生效的资料库 = 勾选的单个资料库 ∪ 所勾选集合的成员**,去掉停用和已删除的。 - 集合是**引用关系**:修改成员后,所有使用这个集合的 Profile 立即生效。集合页显示"被哪些 Profile 使用",添加成员时提示影响范围。 - Profile 版本快照包含集合 ID。 - 生效资料库统一由一个函数计算,用于: - 运行时的文件访问和提示词(runs.ts 三处); - 工作组能力卡片(teams/cards.ts); - 后台的访问预览。 ## 9. 平台变更 ### 9.1 数据模型 ``` knowledge_roots + kind 'managed' | 'local' | 'remote' | 'link' + source json local:{ path, uuid?, relPath? };remote:{ type, 连接参数(不含凭据) };其他为空 + secrets json remote:凭据(不出现在 DTO 中) + url text link path 派生:managed → knowledge/files/;local、remote → knowledge/mnt/;link 为空 access 只有 managed 可以是 'write' knowledge_collections (id, name, description, root_ids json, created_at, updated_at) profiles + knowledge_collection_ids json 默认 '[]' ``` ### 9.2 运行时 - **文件访问**(access-policy): - `managed` 按 access 开放读或写; - `local`、`remote` 只读开放挂载点; - `link` 不对应路径。 - `/srv/agent-server/mounter/` 加入内置全局禁止项。 - 文件访问策略中按资料库设置的规则(`{ knowledgeRootId }`):写规则只能指向可写的托管目录;读规则不能指向网络链接。 - **提示词和 `kb_list_roots`**: - 列出类型、路径或链接、说明; - 远程存储标注"远程资料"及使用提示(§6.3); - 不可用的资料库标注"暂不可用"及原因。 - **网络放行**:网关 `web_fetch` 和 Claude 的 WebFetch 拦截逻辑(providers/claude/policy.ts)接收当前会话的链接资料库列表,按 §7 放行。 ### 9.3 后台 - **资料库页**: - 上方显示挂载服务的状态(正常 / 不可用)。 - 新建时先选类型: - 托管目录:名称、说明、读写; - 本机目录:逐级选择目录,显示检查结果,以及是否在系统盘、设备 UUID; - 远程存储:存储类型、连接参数、凭据,"测试连接"; - 网络链接:链接、说明,"测试访问"。 - 列表显示类型、状态(失效原因、最近变化时间)。可以在文件管理中打开浏览。 - 外接设备的配置说明(§4.4)。 - **集合**:资料库页内新增"集合"分组,可以新建、编辑成员,并显示被哪些 Profile 使用。 - **Profile**:资料库选择分为"集合"和"单个资料库"两部分,并显示最终生效的列表。 - **文件管理**: - 托管目录可读写; - 本机目录和远程存储只读浏览(经由挂载点,同样看不到隐藏文件); - 网络链接不出现。 ### 9.4 部署 `install.sh`(首次安装,需 root): - 在 `/etc/fuse.conf` 中开启 `user_allow_other`; - 创建目录: - `/srv/agent-server/knowledge/files`(agentsvc,0700); - `/srv/agent-server/knowledge/mnt`(部署用户:agentsvc,0750,平台只能进入,不能创建或删除挂载点); - `/srv/agent-server/mounter`(部署用户:agentsvc,0750); - 安装并启用 `agent-mounter.service`(`User=$SUDO_USER`),平台服务配置 `Wants=agent-mounter.service`。 `deploy.sh`: - 编译挂载进程; - 下载并校验固定版本的 rclone(远程存储一期起); - 只有挂载程序或 rclone 有变化时才重启 agent-mounter。 ## 10. 安全分析 | 风险 | 对策 | |---|---| | 平台被攻破,指挥挂载进程挂载敏感目录 | 允许的根目录写死在挂载进程中;不能挂 home 目录本身和隐藏目录;隐藏文件在文件系统层过滤;只读。最坏情况是部署用户能读的普通资料被只读暴露 | | Agent(同为 agentsvc)调用挂载进程,或读取凭据 | 需要密钥;socket、密钥、state.json、rclone 缓存都在 `/srv/agent-server/mounter/` 下,该目录在全局禁止项中 | | 挂载进程被攻破 | 等同于部署用户被攻破(可 sudo)。对策:代码小、只接受本机 socket 上带密钥的请求、只访问登记的远程地址 | | 访问过程中目录被替换为符号链接 | 逐级用 `openat2` 打开,不跟随符号链接 | | 移动设备拔出后,挂载点下原有的其他文件被当成资料 | 按文件系统 UUID 识别设备 | | 远程存储凭据泄露 | 不出现在命令行、DTO、审计日志中;建议使用只读密钥 | | 链接资料库被用来访问任意内网地址 | 只放行同源地址,只对勾选了它的会话生效,重定向逐跳检查 | | 集合扩大授权范围 | 集合页显示使用者,添加成员时提示 | | 可写托管目录被会话改坏 | 页面提示共享影响;本机原始资料不受影响 | ## 11. 实测记录 **2026-11-05**:bindfs 方案(已放弃,改为自己的文件系统)。 | 测试 | 结果 | |---|---| | jerry-remote 不用 root 挂载 | ✅ | | 挂载传入平台服务的挂载命名空间(`/proc//mountinfo`) | ✅ 平台服务中 `/srv/agent-server/knowledge` 是主机挂载的从属挂载 | | 只读、权限映射 | ✅ | | bubblewrap 沙箱中读取 | ✅ 可读、不可写、指向 `/home` 的符号链接读不到 | **2026-11-06**:go-fuse 原型(约 80 行)。 | 测试 | 结果 | |---|---| | `ls -la`、`find`、`grep -r` | ✅ 看不到 `.ssh`、`.git`、`.env` | | 按路径直接读取隐藏文件 | ✅ "没有那个文件或目录" | | bubblewrap 沙箱中 | ✅ 同上 | | 只读、传入平台服务 | ✅ | **2026-11-06**:挂载服务(第二期)在生产环境实测,用 root 创建的 loop 设备(ext4 镜像)模拟外接盘。 | 测试 | 结果 | |---|---| | 平台连接挂载服务(平台的 `/srv` 是只读的,socket 仍可连接) | ✅ | | agentsvc 在平台服务的命名空间中读取:`ls`、`cat`、`grep -r` | ✅ 原目录权限 0700,agentsvc 不能直接读,经挂载可读;`.env`、`.git` 不可见 | | 写入 | ✅ "只读文件系统" | | 安全弹出(umount) | ✅ 第一版长期持有源目录句柄,umount 报"设备忙",已改为每次访问时打开 | | 弹出后 | ✅ 挂载表变化立即处理,状态"设备未连接",读取返回"输入/输出错误",挂载点根目录照常存在 | | 原路径下面有其他文件 | ✅ 不提供 | | 插回并挂到另一个位置(`/mnt/kbtest` → `/media/kbdisk`) | ✅ 立即恢复,内容正确 | | 挂载服务重启 | ✅ 按 state.json 恢复挂载,随后平台同步的列表为准 | | 现场外接盘 `/media/jerry-remote/Data` | ✅ 识别为 `/dev/sda`,UUID 88bc33be-…,设备内路径 `/` | **2026-11-06**:远程存储(第三期),本机 MinIO(专用只读账户 `agentserver`,存储桶 `agentserver-kb`)。 | 测试 | 结果 | |---|---| | 测试连接:正确凭据 / 错误凭据 / 地址错误 | ✅ 正常并列出根目录 / 认证失败(SignatureDoesNotMatch)/ 无法连接 | | agentsvc 在平台服务的命名空间中读取:`ls`、`cat`、`grep -r`、pandas 读 CSV | ✅;`.env`、`.git` 不可见;写入为"只读文件系统" | | 凭据 | ✅ 不在 rclone 的命令行中;agentsvc 读不到 rclone 进程的环境变量,也进不了内部挂载点 | | 在 MinIO 中禁用账户 | ✅ 30 秒内显示"认证失败",读取返回"输入/输出错误";重新启用后约 40 秒自动恢复 | | 强制结束 rclone | ✅ 显示"rclone 意外退出",5 秒后自动重启并恢复 | **待测**:两个引擎的沙箱中读取本机目录与远程存储(需要在后台登记后开会话)。 ## 12. 以后可以支持的介质(不在本次范围) | 介质 | 方式 | 说明 | |---|---|---| | SMB、SFTP、FTP、HTTP 目录、HDFS、Azure Blob、GCS、Swift | rclone 原生支持 | 增加表单即可 | | NFS | 系统挂载到 `/mnt`,按本机目录登记 | rclone 不支持 | | Google Drive、OneDrive、Dropbox、Box 等 | rclone 原生支持 | 需要在后台完成 OAuth 授权;国内网络访问不稳定 | | 百度网盘、阿里云盘、夸克、115 等 | AList / OpenList 转为 WebDAV | 依赖非公开接口,可能失效,还有违反服务条款的风险,不建议作为正式来源 | | 飞书 / 钉钉文档、Confluence、Notion、语雀 | MCP 服务 | 在线文档不适合挂载成文件 | | Git 仓库 | 托管目录定期 `git pull` | 以后再考虑 | ## 13. 测试 - **挂载进程(Go 单元测试)**: - 路径检查(根目录、home 本身、隐藏路径段、`..`、符号链接逃逸); - 隐藏文件过滤; - `openat2` 防路径替换; - 设备识别:用模拟的 mountinfo 测试 UUID 解析、设备缺失、位置不允许、挂载表变化; - 协议鉴权; - `sync` 的增删改; - rclone 子进程的状态判断与重启(用模拟的程序)。 - **平台测试**: - 四种类型的增删改与校验; - 凭据只写不读、不进入审计日志; - 集合并集与停用过滤; - Profile 快照; - 链接放行(同源放行、不同源拦截、重定向逐跳检查、未勾选的会话不放行); - 全局禁止项包含 mounter 目录; - 挂载进程不可用时的状态; - 文件访问规则的限制。 - **生产实测**:见 §11 待测项,以及在 Claude 和 Codex 会话中分别用 grep、pandas 读取本机目录和 MinIO 中的资料。 ## 14. 分期 | 期 | 内容 | 依赖 | |---|---|---| | 一 | 资料库类型重构(托管目录、网络链接,去掉任意路径)、内网同源放行、资料集合 | 只改平台 | | 二 | 挂载进程:文件系统、隐藏文件过滤、本机目录、设备识别与自动恢复、install.sh / deploy.sh / systemd | 开启 `user_allow_other` | | 三 | 远程存储:rclone、S3 兼容、WebDAV、凭据 | 第二期;MinIO 测试环境 | 每期完成后部署、实测,经你确认后再提交。 ## 15. 实施记录 ### 第一期(2026-11-06) - 数据:迁移 0024 重建 `knowledge_roots`(`kind`、`source`、`secrets`、`url`,去掉 `path`,Agent 看到的目录由类型和 id 派生);新增 `knowledge_collections`;`profiles.knowledge_collection_ids`。已有记录迁移为本机目录(只读),在生产数据库副本上验证过。 - 服务端:`apps/server/src/knowledge/service.ts`(四种类型的增删改、集合、`effective()` 计算生效资料库、测试访问)、`knowledge/describe.ts`(提示词与 `kb_list_roots` 共用的清单、链接放行站点)。本机目录与远程存储暂时不能新建(需要挂载服务)。 - 放行:网关 `web_fetch` 每一跳按目标是否与会话的链接资料库同源决定是否拦截内网;Claude 的 WebFetch 同样处理(`RunContext.fetchAllowOrigins`)。 - 文件访问:只有托管目录可写;网络链接不对应路径;`/srv/agent-server/mounter/` 加入内置全局禁止项。去掉"任意路径"的检查(`checkKnowledgePath`),`/paths/check` 只保留通用检查。 - 后台:资料库页按类型新建,显示状态与所属集合,托管目录可在文件管理中打开,网络链接可测试访问;资料集合的增删改与使用者;Profile 中分别勾选集合与单个资料库,并显示实际可用的列表。 - 测试:`test/knowledge.test.ts`,以及 access-policy、gateway(同源放行、跳转到其他站点仍拦截)、file-manager 的相应修改。 ### 第二期(2026-11-06) - 挂载服务 `apps/mounter`(Go,go-fuse): - `rules.go`:允许范围(写死 `/home`、`/mnt`、`/media`,不能选根目录本身、用户主目录本身、隐藏目录)、逐级选择目录; - `device.go`:解析 `/proc/self/mountinfo`、`/dev/disk/by-uuid`,识别系统盘与外接设备,按 UUID 找到设备当前的挂载位置;设备已连接未挂载时访问原路径以触发自动挂载; - `fs.go`:只读文件系统,隐藏 `.` 开头的文件,统一权限(文件 0444、目录 0555、属主 root),每次访问核对源目录的设备号和 inode 号; - `manager.go`:`sync` 增删改、状态、state.json、启动时清理残留挂载、监听挂载表;不可用时有人访问会触发重新检查(限速 2 秒); - `server.go`:Unix socket 协议(`ping`、`status`、`list`、`check`、`sync`),密钥每次启动重新生成。 - 测试:路径规则、设备识别(模拟挂载表:拔出、换位置、换一块盘、已连接未挂载)、真实 FUSE 挂载(隐藏文件、只读、权限、符号链接替换、不占用源目录、源目录被替换、路径消失与恢复、卸载)、密钥。 - 部署:`deploy/agent-mounter.service`(以执行 deploy.sh 的用户运行,附加 agentsvc 组);`deploy.sh` 编译挂载服务,创建 `knowledge/mnt`、`mounter` 目录,程序或 unit 有变化时才重启挂载服务;`install.sh` 开启 `user_allow_other`;平台服务 `Wants=agent-mounter.service`。 - 平台:`knowledge/mounter.ts`(socket 客户端;`KnowledgeMounts` 负责同步与状态:变更时 sync,挂载服务不可用时按 1 秒起、最长 60 秒的间隔重试;status 发现登记不一致时重新同步)。本机目录新建、修改路径时由挂载服务检查并记录 UUID 与设备内路径;路径不变时不重新检查(设备拔出时也能修改名称、说明)。会话开始时如果用到本机目录,先查询状态(超时 1 秒)。后台接口 `/knowledge/mounter`、`/knowledge/browse`、`/knowledge/check-local`。挂载服务目录在后台文件管理中也不显示。 - 后台:本机目录可以新建,逐级选择目录,显示所在设备与识别方式;挂载服务不可用时页面顶部提示;外接硬盘的 fstab 配置说明。 - 新增状态 `mount_failed`(挂载失败)。SDK 升级到 0.7.0(共享类型有变化)。 ### 第三期(2026-11-06) - 挂载服务(1.1.0):`remote.go` - 连接参数与凭据生成 rclone 的环境变量(`RCLONE_CONFIG_KB_*`),WebDAV 密码经 `rclone obscure -`(从标准输入)处理;使用空的 `mounter/rclone.conf`,不读部署用户自己的 rclone 配置; - 每个远程存储一个 rclone 子进程,挂到 `mounter/remote/`(只有部署用户能访问),缓存在 `mounter/cache/`;本进程的文件系统确认那里确实是 rclone 的挂载后再提供; - 状态判断、定时连接检查、退出后按退避间隔重启;删除或配置变化时停止 rclone、卸载、删除缓存; - `test` 请求:用填写的配置列根目录,返回状态和根目录条目; - 测试:配置生成与校验(必填、地址格式、存储桶名、`..`、换行注入)、错误分类、用 rclone 的 local 后端做真实的两层挂载(读取、隐藏文件、只读、凭据不在命令行、删除后清理)。 - 部署:`deploy.sh` 下载固定版本的 rclone(v1.75.2,校验 sha256,缓存在 `~/.cache/agent-server`),放到 `/srv/agent-server/app/mounter/rclone`。 - 平台:远程存储的字段定义放在共享包(`KNOWLEDGE_REMOTE_TYPES`,后台表单与服务端校验共用);凭据只写不读,留空沿用原值,换了存储类型不沿用;登记列表中的远程存储带凭据发给挂载服务;后台接口 `/knowledge/test-remote`(编辑时没有重新填写的凭据沿用原值);审计日志只记录类型、地址、存储桶、前缀。 - 后台:远程存储表单(S3 兼容:服务商、地址、区域、存储桶、路径前缀、Access Key、Secret Key;WebDAV:地址、服务类型、用户名、密码),"测试连接"显示结果和根目录条目。 - SDK 升级到 0.8.0。