什么是扩展
面板从不运行扩展的代码。运营者把扩展安装到一台主机上(面板所在的主机或其他主机),由该主机的 systemd 或 Docker 保持运行。然后面板对其进行注册:读取扩展的清单,向运营者展示它请求的内容,批准后为它颁发 API 令牌和签名的事件订阅。
- 可以用任何语言编写:契约就是 HTTP 和 JSON。在 Go 中,addon-kit 已为您实现契约。
- 把数据保存在您自己的数据库中。面板不为扩展保存任何数据,扩展自行备份。
- 只请求完成工作所需的最少权限:运营者在批准前会逐条阅读每项权限及您给出的理由。
从模板开始
addon-template 是一个完整的最小扩展:可以注册、接收事件、响应健康检查并显示一个页面,附带安装脚本、compose 文件和发布工作流。复制并运行:
gh repo create my-addon --template nx-panel/addon-template --public --clone cd my-addon NX_OPT_PORT=8090 go run . # not registered yet — claim code 3F9A1C0B7E21
面板只通过认领码注册由其信任的密钥签名的清单。要在使用 -tags addondev 构建的面板上试用,请用开发密钥签名,只有这样的面板才信任它:
go run github.com/nx-panel/addon-kit/cmd/nx-addon sign -dev nx-addon.json > signed.json NX_MANIFEST_FILE=signed.json go run .
清单
nx-addon.json 位于两处:您公开仓库的根目录,本目录和面板的安装向导在运行任何东西之前从这里读取;以及运行中的扩展的 /.well-known/nx-addon.json,注册时从这里读取。两者必须是同一个文件。所有字段的参考是 SPEC.md;一个可用的示例:
{
"api": 1,
"slug": "hello",
"name": "Hello",
"version": "0.1.0",
"publisher": "Your name",
"license": "Apache-2.0",
"description": { "en": "What it does, in a sentence or two." },
"docs": "https://github.com/you/hello",
"install": {
"docker": { "image": "ghcr.io/you/hello", "compose": "deploy/compose.yml" },
"binary": { "linux-amd64": "hello-linux-amd64.tar.gz", "linux-arm64": "hello-linux-arm64.tar.gz" },
"options": [
{ "key": "port", "type": "port", "default": 8090, "label": { "en": "Port" } }
]
},
"scopes": [ { "scope": "users:read", "purpose": "to show how many accounts the panel has" } ],
"events": ["user.created", "panel.addon_removed"],
"webhook": "/nx/events",
"setup": "/nx/setup",
"health": "/health",
"ui": "/"
}
slug是扩展的身份:在目录、面板和更新检查中都是如此。永远不要更改;新的 slug 就是新的扩展。version是不带v的发布版本;面板据此显示更新标记。scopes是 API 令牌的权限,每项都附有在同意页面上显示的理由;events会投递到webhook。至少请求其中之一。description和每个label支持en、fa、ru和zh;英文为必填,若提供了运营者的语言,面板会显示该语言。- 用面板的方式检查它:
nx-addon check nx-addon.json。
安装选项
install.options 是面板安装向导在安装前提出的问题:端口、数据库、机器人令牌等。类型:string、secret、number、port、bool、choice、url、path(插件管理界面所在的基础路径:一个路径段,留空表示根路径;每个清单最多一个)。带有 when 的选项,只有在前面某个 choice 或 bool 取该值时才会询问。
{ "key": "database", "type": "choice", "choices": ["sqlite", "postgres"], "default": "sqlite",
"label": { "en": "Database" } },
{ "key": "database_dsn", "type": "secret", "required": true, "when": { "database": "postgres" },
"label": { "en": "PostgreSQL DSN" } }
# reaches the addon as
NX_OPT_DATABASE=postgres
NX_OPT_DATABASE_DSN=postgres://…
NX_PANEL_URL=https://panel.example
NX_CLAIM_CODE=K7QF-2M9D-XW4H-8RTA
每个回答都以环境变量 NX_OPT_<KEY> 传给扩展,同时还有面板地址和面板为本次安装签发的认领码。面板从不保存 secret:它只出现在仅显示一次的命令中,或通过 SSH 传递。
发布与 install.sh
两种安装方式——面板给出的命令,以及面板通过 SSH 自动安装——都会从您最新的 GitHub 发布中运行您自己的 install.sh。模板的脚本已经完成这一切;请保留它的参数:
sh install.sh --method script|docker # binary + systemd, or the compose stack
--version v0.1.0 # a release tag (default: the latest)
--binary-file FILE # use this archive instead of downloading it
--opt KEY=VALUE # an answer to an install option; repeat
--panel-url URL # → NX_PANEL_URL
--claim-code CODE # → NX_CLAIM_CODE
sh install.sh # again: update in place, answers and data kept
sh install.sh --uninstall [--purge] # remove; --purge deletes the data too
每个发布必须包含的内容,全部位于同一标签:
nx-addon.json the manifest, its version = the tag install.sh at the root of the repository, at the tag deploy/compose.yml what "install.docker.compose" names hello-linux-amd64.tar.gz every asset "install.binary" names (release assets) hello-linux-arm64.tar.gz SHA256SUMS sha256sum output for the assets — the panel checks it ghcr.io/you/hello:0.1.0 the image, tagged with the version
通过 SSH 以服务方式安装时,面板会自己下载对应主机架构的二进制文件,用 SHA256SUMS 校验,再通过 --binary-file 上传,因此主机无需访问 GitHub。脚本以 root 运行;所有内容都放在 /opt/nx-addons/<slug> 下,回答在 .env,数据在 data/。再次运行脚本必须原地更新。
注册、事件与健康检查
运营者批准后,面板会把凭据连同认领码 POST 到您的 setup 路径。只接受与您启动时所用(或您自行生成并写入日志)的认领码一致的请求,然后丢弃该码。把令牌和 webhook 密钥保存在只有您能读取的文件中。
POST /nx/setup
{
"api": 1,
"claimCode": "K7QF-2M9D-XW4H-8RTA",
"panel": { "url": "https://panel.example", "version": "1.8.0", "id": "…" },
"token": "nx_…",
"scopes": ["users:read"],
"webhook": { "id": 7, "url": "…/nx/events", "events": ["user.created"], "secret": "…" }
}
事件以签名形式到达您的 webhook 路径:
X-Nx-Delivery: 9b1f… # the same on every retry: handle it once X-Nx-Signature: t=1791000000,v1=<hex> # v1 = HMAC-SHA256(secret, "<t>.<body>"); refuse a t more than 5 minutes off
health 路径每分钟被请求一次,正常时必须返回 200;失败会显示在运营者的扩展页面上。用您的令牌调用面板的 /api/v1,并在每次写操作中发送 Idempotency-Key,确保重试绝不会执行两次:
GET https://panel.example/api/v1/users?limit=1 Authorization: Bearer nx_… Idempotency-Key: <one per logical write> # on POST / PUT / PATCH / DELETE
运营者移除扩展时,扩展会在令牌被删除前收到 panel.addon_removed:请丢弃凭据,以便之后可以重新注册。在 Go 中,这一切就是 addon.New、Mount、OnEvent 和 Forget。
官方、已验证、非官方
| 级别 | 由谁上架 | 清单签名 | 在面板中 |
|---|---|---|---|
| 官方 | NX | NX 的密钥 | 可通过命令或经 SSH 自动安装;同意页面显示 NX |
| 已验证 | 您,经 NX 审核 | 您自己的密钥,由签名索引担保 | 与官方相同,并标注您的名字 |
| 非官方 | 您,经过简单检查 | 您的密钥,或不签名 | 卡片和同意页面上有警告;只能通过命令安装 |
用目录不担保的密钥签名的清单视为未签名;未签名的扩展只能手动注册,由运营者为其创建令牌。
如何上架
- 将仓库设为公开,根目录有清单,并至少有一个发布。
- 如需已验证上架,请创建自己的签名密钥并为清单签名。签名覆盖包括版本在内的所有字段,因此每次发布都要重新签名:
go run github.com/nx-panel/addon-kit/cmd/nx-addon keygen -out addon.key # keep addon.key out of the repository go run github.com/nx-panel/addon-kit/cmd/nx-addon sign -key addon.key nx-addon.json > signed.json && mv signed.json nx-addon.json go run github.com/nx-panel/addon-kit/cmd/nx-addon check nx-addon.json go run github.com/nx-panel/addon-kit/cmd/nx-addon verify -pub <your public key> nx-addon.json
- 通过提交表单提交仓库;申请已验证审核时请附上您的公钥。表单会立即读取仓库,若有问题会直接指出。
- NX 进行审核。上架后,本目录每十分钟重新读取一次最新发布;面板每天读取一次签名索引,或在运营者要求时读取。
如果某个扩展的最新发布不再通过检查,目录会继续显示其最后一个正常版本并标记为过时,直到新的发布再次通过。
更新
发布一个 version 更高的版本:本目录会在十分钟内发现它,各个面板在下一次读取时会在您的扩展上显示更新标记。运营者通过命令更新,或者对于官方和已验证的扩展,直接在面板中更新;安装脚本会再次原地运行。
请求新权限或新事件的更新会被安装,但面板会暂缓新的权限,直到运营者批准;在此期间扩展继续以原有权限工作。请在真正需要某项权限的那个发布中再请求它。