افزونه چیست
پنل هرگز کد افزونه را اجرا نمیکند. اپراتور افزونه را روی یک سرور نصب میکند (سرور خود پنل یا سرور دیگر) و 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 (رازی که گذرواژهٔ مدیر افزونه میشود: حداقل ۱۰ نویسه و حداکثر ۷۲ بایت)، number، port، bool، choice، url، path (مسیر پایهای که افزونه پنل مدیریتش را زیر آن سرو میکند: یک بخش، یا خالی برای ریشه؛ حداکثر یکی در هر manifest). گزینهای که 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/. اجرای دوبارهٔ اسکریپت باید در جا آپدیت کند.
ثبت، رویدادها و سلامت
وقتی اپراتور تأیید کرد، پنل اعتبارنامه را همراه کد ادعا به مسیر setup شما POST میکند. فقط با همان کدی که با آن اجرا شدهاید (یا خودتان ساخته و در لاگ نوشتهاید) قبولش کنید و بعد کد را فراموش کنید. توکن و رمز وبهوک را در فایلی نگه دارید که فقط خودتان میتوانید بخوانید.
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 است.
رسمی، تأییدشده، غیررسمی
| سطح | چه کسی فهرست میکند | امضای مانیفست | در پنل |
|---|---|---|---|
| رسمی | انیکس | کلید انیکس | نصب با دستور یا خودکار با SSH؛ صفحهٔ تأیید نام انیکس را نشان میدهد |
| تأییدشده | شما، با بازبینی انیکس | کلید خود شما که فهرست امضاشده آن را تضمین میکند | مثل رسمی، با نام شما |
| غیررسمی | شما، پس از یک بررسی ساده | کلید شما، یا هیچ | هشدار روی کارت و صفحهٔ تأیید؛ فقط نصب با دستور |
مانیفستی که با کلیدی امضا شده که فهرست تضمینش نمیکند، بیامضا حساب میشود؛ افزونهٔ بیامضا فقط دستی ثبت میشود و اپراتور توکنش را خودش میسازد.
ثبت در فهرست
- مخزن را عمومی کنید، با مانیفست در ریشه و دستکم یک انتشار.
- برای فهرست تأییدشده، کلید امضای خودتان را بسازید و مانیفست را امضا کنید. امضا همهٔ فیلدها از جمله نسخه را میپوشاند، پس برای هر انتشار دوباره امضا کنید:
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
- مخزن را در فرم ثبت بفرستید، و برای بازبینی تأییدشده کلید عمومیتان را هم بدهید. فرم همان لحظه مخزن را میخواند و اگر ایرادی باشد میگوید.
- انیکس بازبینی میکند. پس از فهرست شدن، این فهرست هر ده دقیقه آخرین انتشار را دوباره میخواند و پنلها فهرست امضاشده را روزی یک بار، یا هر وقت اپراتور بخواهد، میخوانند.
اگر آخرین انتشار یک افزونه از بررسیها رد نشود، آخرین نسخهٔ سالمش با علامت «قدیمی» نشان داده میشود تا انتشاری دوباره قبول شود.
آپدیت
انتشاری با version بالاتر بدهید: این فهرست در ده دقیقه آن را میبیند و هر پنل در خواندن بعدی روی افزونهٔ شما نشان آپدیت میگذارد. اپراتور با دستور، یا برای افزونهٔ رسمی و تأییدشده از داخل پنل، آپدیت میکند و اسکریپت نصب دوباره در جا اجرا میشود.
آپدیتی که دسترسی یا رویداد تازه بخواهد نصب میشود، ولی پنل دسترسیهای تازه را تا تأیید اپراتور نگه میدارد و افزونه در این مدت با دسترسیهای قبلی کار میکند. هر دسترسی را در همان انتشاری بخواهید که لازمش دارد.