插件开发文档

发布

插件进岛屿有两条路:只在自己的电脑上用,或者放进插件市场,让别人在 设置 → 插件市场 里安装。

许可证

插件用什么许可证由你定,开源闭源都可以。岛屿本体是 GPL-3.0,但插件是单独运行的程序,只通过 JSON-RPC 和岛屿通信,不受 GPL 约束。SDK、脚手架、PowerShell 模块和示例插件都是 MIT,拿来写插件不会给你的插件带来任何许可要求。

插件市场目前要求源码公开,以便审核。

只在自己的电脑上用

把插件文件夹放进 %APPDATA%\com.tixxin.fuyu\plugins\<插件 id>\,在 设置 → 插件扩展 打开「允许运行第三方插件」,点 重新加载,再启用插件。不经过市场,也不需要签名。改了文件再点一次 重新加载。

想给朋友用:npx fuyu-plugin pack 打出 zip 发给他,让他照上面解压到插件目录。

放进插件市场

市场列表来自 TixXin/fuyu-plugins 仓库。插件包放在你自己仓库的 GitHub Release 里,市场仓库只记录插件在哪里;它的 CI 每小时检查一次新版本,校验后重建索引并签名。完整步骤见那里的 CONTRIBUTING.md。

第一次上架

  1. npx fuyu-plugin validate 检查,npx fuyu-plugin pack 打包,得到 <id>-<version>.zip。建议同时签名,见下文的「作者签名」。
  2. 在自己的仓库发布 GitHub Release:标签是清单的 version(可以带 v),附件是这个 zip(签了名的再加上 .zip.sig),文件名不能改。脚手架生成的 GitHub Actions 工作流在你推送版本标签时会自动做这一步。
  3. 向 fuyu-plugins 开 pull request,只新增一个文件 plugins/<插件 id>.json(见下文)。PR 会自动检查所有权、下载并校验插件包;维护者阅读源码后合并,一小时内插件出现在插件市场。

npx fuyu-plugin publish(加 --sign 同时签名)会打包,并打印这些步骤。

以后的版本

提高清单的 version,在自己的仓库发新的 Release 就行,一小时内市场会更新。以下版本不会自动上架,要等维护者审核:申请了新权限、第一次加入自定义网页、换了运行时(例如 .ps1 换成 .js)。

已发布的版本不能修改:用户安装时核对的就是那个哈希值,同一个版本号的包变了,市场 CI 会拒绝。有问题就发新版本号。

市场里的描述文件

fuyu-plugins 里的 plugins/<插件 id>.json:

{
  "id": "com.example.clock",
  "repo": "example/fuyu-clock",
  "publisher": "example",
  "categories": ["productivity", "developer"]
}
字段 说明
id 插件 id,和清单相同
repo owner/name,必须公开;插件详情里的「仓库」按钮打开它
publisher repo 的所有者(GitHub 用户或组织),市场上显示为发布者。你必须是它本人,或该组织的公开成员
categories 只能取 productivity、todos、developer、media、messages、information、utilities;音效包写 sounds
path 可选,插件在仓库里的文件夹,例如 plugins/clock,默认仓库根目录
tagPrefix 可选,一个仓库放多个插件时的标签前缀,例如 clock-(标签 clock-v1.2.0)
preRelease 可选,true 时也收录标成 Pre-release 的 Release,见下文
signingKeys 可选,你的签名公钥,见下文的「作者签名」
domain 可选,用来成为已验证发布者的域名,见下文

说明和版本历史

插件的 README.md 和 CHANGELOG.md 会写进市场索引(各最多 16 KB),显示在插件市场的详情里,也显示在官网的插件页上。先在插件包里找;包里没有(写进了 .fuyuignore)时,读仓库里 Release 标签处 path 文件夹中的同名文件。它们跟着版本走,改了要发新版本才会更新。

  • 支持标题、段落、列表、引用、代码块、粗体、斜体和 http(s) 链接;HTML 和图片不显示(图片只显示替代文字)。
  • CHANGELOG.md 建议每个版本一节、最新的在最前(## 1.2.0)。

预发布版本

描述文件里写 "preRelease": true 时,标成 Pre-release 的 Release 也会收录,版本号用 semver 预发布后缀(例如 1.3.0-beta.1)。

  • 插件市场只列正式版。
  • 用户在插件设置页打开「接收这个插件的预发布版本」后,更新会安装比正式版新的预发布版本。
  • 正式版发布后(例如 1.3.0 高于 1.3.0-beta.2),装着预发布版本的用户会更新到正式版。关掉开关不会退回旧版本。

作者签名

签名是可选的,但签了名,用户的岛屿才会自动更新你的插件。岛屿还会记下第一次看到的密钥,之后换了密钥的版本要用户确认才装。这样即使你的 GitHub 账号或仓库被盗,攻击者发的新版本也不会悄悄装到用户电脑上。

  1. 只需一次:npx fuyu-plugin keygen 生成签名密钥。私钥存在 ~/.fuyu-plugin/signing.key(Windows 上用 DPAPI 加密,只有当前 Windows 账户能解开),也可以用环境变量 FUYU_PLUGIN_SIGNING_KEY(PEM 文本)提供。它打印的 { keyId, publicKey } 写进描述文件的 "signingKeys": [ … ]。
  2. 每次发版:npx fuyu-plugin pack --sign,把 <id>-<version>.zip 和 <id>-<version>.zip.sig 一起附到 Release。已经打好的包可以用 npx fuyu-plugin sign <zip> 补签。
  3. 登记了 signingKeys 之后,没有签名或签名不对的版本不会上架,维护者也不能放行。

备份私钥。 丢了只能换新密钥,而换密钥后的第一个版本要每个用户手动确认。最安全的做法是在自己电脑上签名再上传,不要把私钥放进仓库的 CI。

换密钥

旧密钥还在时,在插件文件夹里运行 npx fuyu-plugin keys rotate(一把密钥签多个插件时加 --ids a,b):

  1. 它把旧密钥改名为 <密钥文件>.<旧 keyId>.old 留着,在原位置生成新密钥,并打印每个插件要加进 signingKeys 的新条目。新条目里的 endorsedBy 是旧密钥对「插件 id + 新密钥」的签名。
  2. 把新条目加在旧密钥后面(旧密钥先别删),开 PR。
  3. 合并后用新密钥签名发版。岛屿看到新密钥有已记下的密钥的证明,就当作同一个作者,照常自动更新。

丢了旧密钥就做不出证明,换密钥后的第一个版本要用户手动确认。

用户那边会看到什么

  • 安装:第一次安装某个发布者的插件时,按钮显示 信任并安装。装好后插件默认是关闭的,用户到 设置 → 插件扩展 里启用并授权。
  • 标签:签了名的插件带「作者已签名」;带自定义网页的带「自定义界面」;按 runtime 标出「无需另外安装」或「需要 Node.js」;可选权限标成「可选,用到时询问」。
  • 更新:岛屿在启动 30 秒后和之后每 6 小时检查一次市场,有新版本时卡片上提示。「自动更新插件」打开时,只有作者签了名(密钥和已安装的版本相同)、来自可信发布者、而且不申请新权限的更新会自动安装,其他的只提示。
  • 点赞:插件上架后,fuyu-plugins 会在它的 Discussions 里自动开一条点赞帖,每小时统计帖子的 👍,市场卡片显示这个数字。想让用户来点赞,把帖子地址放进 README。

安装链接

fuyu://market/install/<插件 id> 打开岛屿的 设置 → 插件市场,并把这个插件置顶。官网插件页的「在浮屿中安装」按钮用的就是它,你也可以放进自己的 README。

链接本身不会安装任何东西:用户在置顶的卡片上自己点「安装」,照常经过信任发布者和权限的确认。插件不在市场里时会提示链接无效。需要浮屿 0.3.6 或更新的版本,旧版会忽略这种链接。

已验证发布者

市场在发布者名字旁显示「已验证 · GitHub 组织」或「已验证 · <域名>」,说明发布者确实是它的名字所指的组织或个人,不代表插件安全。

  • 在 GitHub 上验证过域名的组织自动得到。
  • 其他发布者在描述文件里写 "domain": "example.com",并让 https://example.com/.well-known/focusd-publisher.json 返回 { "github": "<publisher>" }。市场每小时重新检查一次。

下架

发现某个版本有严重问题(比如会删数据),到 fuyu-plugins 开 issue 或 PR 申请撤销。被撤销的版本:

  • 已安装的会被停用(或按申请卸载,插件数据保留),插件卡片显示原因;
  • 不能再启用,也不能从市场安装;
  • 手动放进插件目录的同 id 同版本也会被拦截。

撤销不能取消,误撤销的插件请发一个新版本。撤销名单的格式和签名见 插件市场运维。

安装时岛屿检查什么

  1. 市场索引的签名有效。
  2. 下载的包大小和 SHA-256 与索引一致。
  3. zip 里的路径没有跳出插件目录,解压后总大小不超过 100 MB。
  4. 清单的 id 和 version 与索引一致,申请的权限没有超出索引里列出的。
  5. 有作者签名时签名有效;签名密钥和已安装的版本不同时,要用户确认。

全部通过才替换旧版本。更新时保留启用状态,但新版本要更多权限时插件会暂停,等用户重新授权。卸载插件会删除插件目录和授权记录,但保留 plugins-data/<id>/ 里的数据和凭据管理器里的秘密。

在 GitHub 上编辑这一页