NX Дополнения

Как написать дополнение для NX

Дополнение — это ваша собственная программа, работающая рядом с панелью NX: свой сервис или контейнер, своя база данных, а с панелью она общается только по сети и только с теми правами, которые одобрил оператор. Здесь описано всё, что нужно дополнению, — от первого запуска до размещения в этом каталоге.

Что такое дополнение

Панель никогда не запускает код дополнения. Оператор устанавливает дополнение на сервер — на сервер панели или на другой, — и работу поддерживает systemd или Docker этого сервера. Затем панель его регистрирует: читает манифест, показывает оператору, что дополнение запрашивает, и после одобрения выдаёт ему API-токен и подписанную подписку на события.

  • Пишите на любом языке: контракт — это HTTP и JSON. На Go контракт за вас реализует addon-kit.
  • Храните данные в своей базе. Панель ничего не хранит за дополнение, и дополнение само делает резервные копии.
  • Запрашивайте минимум нужных прав: оператор читает каждое право вместе с вашим объяснением перед одобрением.

Начните с шаблона

addon-template — полное минимальное дополнение: регистрируется, получает события, отвечает на проверку работоспособности и показывает страницу; в комплекте скрипт установки, файл compose и workflow выпуска. Скопируйте и запустите:

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, password (a secret that becomes the addon's admin password: at least 10 characters, at most 72 bytes), 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 — запускают ваш install.sh из последнего выпуска на GitHub. Скрипт шаблона уже всё это делает; сохраните его флаги:

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 вместе с кодом привязки. Принимайте их только с тем кодом, с которым вас запустили (или который вы сгенерировали и записали в лог), после чего забудьте код. Храните токен и секрет вебхука в файле, доступном только вам.

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вашим ключом, за который ручается подписанный индекскак официальное, с вашим именем
Неофициальноевы, после беглой проверкивашим ключом или ничемпредупреждение на карточке и экране согласия; установка только командой

Манифест, подписанный ключом, за который каталог не ручается, считается неподписанным; неподписанное дополнение регистрируется только вручную, а токен для него создаёт оператор.

Как попасть в каталог

  1. Сделайте репозиторий публичным, с манифестом в корне и хотя бы одним выпуском.
  2. Для проверенного размещения создайте свой ключ подписи и подпишите манифест. Подпись покрывает все поля, включая версию, поэтому подписывайте заново для каждого выпуска:
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
  1. Отправьте репозиторий через форму, а для проверенного размещения укажите свой публичный ключ. Форма сразу читает репозиторий и сообщает, если что-то не так.
  2. NX проверяет заявку. После размещения каталог перечитывает последний выпуск каждые десять минут, а панели читают подписанный индекс раз в день или по запросу оператора.

Если последний выпуск перестаёт проходить проверки, размещение показывает последнюю исправную версию с пометкой «устарело», пока новый выпуск снова не пройдёт.

Обновления

Опубликуйте выпуск с большей version: каталог увидит его в течение десяти минут, а каждая панель при следующем чтении поставит на ваше дополнение значок обновления. Оператор обновляет командой или, для официального и проверенного дополнения, из панели; скрипт установки снова запускается на месте.

Обновление, запрашивающее новые права или события, устанавливается, но панель придерживает новые права до одобрения оператора; до тех пор дополнение работает со старыми. Запрашивайте права в том выпуске, которому они нужны.