跳到正文

插件 ​

Obsidian Arc 的核心是一个通用的 AI 对话站点。只有部分实例才需要的功能——比如某个社群站点要加在账户上的字段,或者某个运营方自建的风控服务——以插件的形式存在:放在 plugins/ 目录下,按需编译进二进制。

没编译进去的插件不贡献任何代码、路由、数据表或界面文字;二进制仍然是部署说明里承诺的那一个文件。

这一页讲编译进二进制的插件。更常用的是插件包:拖进后台就装好,删除即真删。

编译进来的插件在后台的「插件」页里管理,用法和浏览器的扩展页一样:安装、启用、禁用、卸载,每个插件都有自己的清单。

本仓库不带插件 ​

这个仓库只有插件机制,没有任何具体的插件:plugins/ 是空的,默认构建就是核心本身。具体站点的插件放在那个站点自己的仓库里,构建时叠到这棵源码树上——Go 只允许 internal/ 目录里的代码引用 internal/ 下的包,所以插件必须真的落在这棵树里,而不是从外面引用它。

一个插件在树里占三处:

位置内容
plugins/<名字>/服务端:plugin.Register 的实现、迁移和清理脚本、测试
web/src/plugins/<名字>/<名字>.plugin.ts浏览器端:声明式的字段、设置卡片、列表与文案
cmd/server/plugin_<名字>.go一行带构建标签的空白引用,把服务端编进二进制

设置键、数据表和迁移版本号一旦发布就不再改名:一个已经在用某个插件的实例,只要继续把它编译进去,什么都不用迁移。

选择编译哪些插件 ​

bash
make build                                 # 默认:只有核心
make build PLUGINS="alpha beta"            # 带上 plugins/alpha 和 plugins/beta
go build ./cmd/server                      # 不带任何构建标签 = 只要核心

每个插件对应一个构建标签 plugin_<名字>。Docker 镜像同样接受 --build-arg PLUGINS="…",默认值和 Makefile 一致。

make version 会顺带打印这次构建带了哪些插件,make release 也会把它写进 dist/PLUGIN_LIST。make deploy 在服务器上发现上一次部署带着的插件这一次没带时会拒绝继续,因为这样会让一个正在用它的实例悄悄失去功能;确实要去掉时,先在后台卸载它,再加 DROP_PLUGINS=1。服务器上的记录叫 PLUGIN_LIST,放在它 compose 目录下。新列表只在 docker compose up 成功后写入;若服务器上还没有这个文件,部署开始时先把旧记录复制过去,内容不变。

在后台管理插件 ​

「插件」页列出本程序带有的每个插件和它在本实例的状态:

状态含义
未安装编译进来了,但没有为它执行迁移、它的设置不被识别、它的钩子都不运行
已启用正常工作
已禁用数据表和设置都在,功能全部停止:路由返回 404,设置不列出也不能写入,账户字段不再读写,登录守卫不再检查,浏览器也不再下载它的前端代码
本构建未包含由带着它的构建安装过,当前这个二进制没有它;在重新带上之前不起作用
  • 安装:可以选择是否安装后立即启用,并在同一步里填好它的初始设置(插件前端声明了哪些设置项,安装面板里就出现哪些,其中只画出当前管理员可写的,见下文「权限」)。迁移、初始设置和安装记录在同一个事务里写入,任何一步失败都会整体回滚。
  • 禁用:保留一切,停止一切,需要两步验证码。
  • 卸载:需要两步验证码。默认保留数据,以后重新安装会接着用;勾选「同时删除数据」会删除它的设置,并执行插件自带的清理脚本删除它的数据表和字段,同时清除它的迁移记录,重新安装时从空表开始。
  • 清单:版本、作者、许可证、说明,以及从各个扩展点读出来的实际接入内容——设置项、账户字段、注册与登录检查、接口、控制台命令、数据迁移、后台页面。
  • 每次安装、启用、禁用、卸载都会记进安全日志(事件 plugin)。
  • 控制台里是 plugin list、plugin show、plugin install、plugin enable、plugin disable --code、plugin uninstall --code [--purge] --yes。

权限 ​

「插件」页拆成三项后台权限,超级管理员在管理员页按需授予:

权限能做什么
plugins查看插件列表与清单
plugins_manage安装、启用、禁用(也能查看)
plugins_remove卸载,保留数据(也能查看)

plugins_remove 只能卸载,并保留数据,以后重装接着用。同时删除数据(后台的勾选项,控制台的 --purge)只限超级管理员:清理脚本以数据库属主的身份删表删列,这一步不在委派的卸载权限之内。持有 plugins_remove 的管理员勾选它时,请求返回 403(admin_permission_denied),什么都不改。

安装时随请求提交的每个设置项,还要各自通过该键自己的权限检查,与共享设置接口对这个键的要求相同:插件为它声明的所属后台权限,没有声明的按核心的归属。plugins_manage 不等于能设置插件的所有键。提交的键里只要有一个不够权限,整次安装就被拒绝(403,admin_permission_denied),并且在检查两步验证码和执行迁移之前就返回,什么都不写。后台的安装面板只提交服务端按本次查看者标明可写的键(插件列表与详情返回的 writable_settings),所以只持有 plugins_manage、又没有被授予某个受限键的管理员,不会因为一个自己没动过的键而安装失败;未填写的密钥照旧不提交。

禁用和卸载无论持有哪项权限,都还要求操作者本人已开启两步验证并输入验证码:插件可能正站在注册入口前面,被盗的会话悄悄关掉它就等于把门打开了。

升级时 ​

第一次用这个版本启动时,还没有任何插件状态记录。服务器会按「痕迹」决定:插件的迁移已经执行过,或者它的设置已经存过值,就视为这个实例一直在用它,直接记为已启用;否则记为未安装,等管理员去装。所以之前就在用某个功能、后来才把它做成插件的实例,升级后什么都不用做。

卸载后留下的记录不会被当成「没有记录」:保留数据卸载的插件,下次启动不会因为数据还在就被自动装回去。

多实例 ​

插件状态在做出改动的那个进程里立即生效;指向同一个数据库的其他进程要等重启才会读到新状态。在那之前,如果卸载时删除了数据,它们查询被删掉的字段会报错。多实例部署时,改完插件请重启其余实例。

去掉一个插件会怎样 ​

说的是从构建里去掉(PLUGINS 里不再列出它),不是在后台卸载:

  • 它的路由不再存在(返回 404),它的设置键会被后台当作未知键拒绝写入;
  • 数据库里它留下的数据原样保留:列还在、表还在,核心读写时绕开它们;
  • 它留下的设置行不会出现在后台设置接口里——那可能是它的密钥,而负责给密钥打码的正是这个已经不在的插件;
  • 插件页把它列为「本构建未包含」;以后重新编译进来,它接着用原来的数据和原来的状态。

插件能改动什么 ​

插件只能通过核心模块显式开放的扩展点接入,方向永远是插件依赖核心、核心不知道任何插件:

扩展点用途
database.Migrate 的插件迁移目录插件自己的表和列。从核心搬出来的迁移保留原版本号;插件新写的迁移命名为 <插件>_NNNN_*.sql
settings.Define / settings.AddCaptchaMode插件的设置项(默认值、是否为密钥、所属后台权限、校验)和注册验证码模式
user.DefineField + auth.Service.SetFieldRule账户上的附加字段:自动参与查询、扫描、写入、唯一性检查与后台搜索;注册时是否必填由插件的设置决定
auth.Service.AddGuard注册 / 登录前的检查,令牌放在请求体的 guards 里。登录守卫的拒绝同样适用于 SSH 控制台的密码入口,以及第三方登录的回调和补全表单;这几处没有请求体,传给守卫的令牌为空,要求令牌的守卫应在这里拒绝
auth.Handlers.Extend/api/site 里 plugins.<名字> 下给浏览器的配置
oauth.Service.BindSubject某个登录提供方的主体号即某个账户字段的值
admin.Handlers.Mount / console.Register后台接口(和其他后台接口走同一层权限包装)与控制台命令
invite.Handlers.DecorateInvitees给邀请人自己的邀请列表补充信息
plugin.Host.Handle后台以外的公开接口(比如机器人 Webhook)
plugin.Host.AllowOrigin页面需要加载第三方服务脚本时放宽 CSP 的来源
plugin.Purger卸载并删除数据时执行的清理脚本

每个扩展点登记的东西都带着所属插件的名字,核心在运行它之前先问一次这个插件现在是否启用——插件自己的代码不需要检查任何状态,关掉它也不需要重启。

浏览器端 ​

每个插件在 web/src/plugins/<名字>/<名字>.plugin.ts 有一个对应模块,构建成一个独立的小 chunk(几 KB),只有当 /api/site 列出了这个插件时才会下载。

插件模块只做声明,不写界面:账户字段长什么样、守卫怎么拿令牌、设置卡片里有哪些控件、一张分页列表、账户面板里的几个按钮、错误码和通知的文案、它自己的中英文字典。核心用自己的组件把这些画出来,所以插件的设置会和同页其他设置一起标记未保存、一起提交、一起报错,也不会带来一套风格不一致的控件。

插件还可以声明自己的后台子页面(adminPages):一个路径、标题、图标和打开它需要的权限。设置卡片和列表的 page 写成 plugin:<路径> 就会放到这个页面上,由核心的通用页面加载、保存。插件的设置和列表可以放在它自己的页面里;插件被禁用或卸载后,这个页面随之从后台导航里消失。

插件还可以往 API 密钥页放东西(keyIssuing):顶部的一条提示,和一个每创建一个密钥都要回答的确认——点「创建」时弹出警告,要求输入指定的字才能继续;取消或输错,密钥就不会创建。要输入的字由样式表从属性里画出来,不写在页面的文字里,所以选不中、复制不了,输入框也不接受粘贴和拖入:它要的是让人读一遍再亲手打出来。这是一句提醒,不是防护,也不是服务器端的检查——不走这个页面、直接调接口创建的密钥不会被问到。没有后端的插件包(只有 manifest.json 和 web/ui.js)就够做这件事,用 arcpack pack 打包。

Obsidian Arc · 自托管 AI 工作空间