Skip to content

Build an addon ​

This page is for developers who want to write an Addon for Nexora: a separate program that works with the panel over the network, with a scoped API token, the panel's signed events and a manifest that says what it needs. It walks through the parts an addon has and the tools that write most of them for you. The manifest's fields are in The addon manifest.

What you build ​

An addon is its own program: its own service or container, its own interface, its own database. The panel never runs its code and stores none of its data. An addon has four parts the panel touches:

PartWhat it does
The manifestnexora-addon.json: who the addon is, what it asks for, how it is installed. Served by the running addon at /.well-known/nexora-addon.json.
A setup pathWhere the panel delivers the token and webhook secret at registration, with the claim code.
A webhook pathWhere the panel posts the events the addon asked for, each one signed.
A health pathAnswers 200 while the addon is healthy. The panel asks every minute.

Everything else (your pages, your API, your database) is yours to design. How the panel treats an addon is described in The addon platform.

The kit and the template ​

Two public repositories write most of this for you.

  • The addon kit, the Go module github.com/nexora-vpn/addon-kit, licensed Apache-2.0, so an addon built on it may be open or closed. The panel validates manifests with the same code.
  • The template, github.com/nexora-vpn/addon-template: a complete minimal addon with a compose file, a binary release and an install script. It registers by claim code, receives events, answers the health check and shows a page with the panel's account count.
Kit packageWhat it does
manifestReads, validates and signs the manifest.
addonThe running half: serves the manifest, takes the credentials at registration, verifies event deliveries, answers the health check, reads the install's answers.
panelA small client for the panel's API with the addon's token, idempotency keys and a wait on 429.
authThe addon's own admin sign-in: hashed passwords, sessions, a TOTP second factor, a limit on failed attempts.
telegramA small Telegram Bot API client, through a proxy or a Bot API mirror; also Bale.
webServes the admin under the install's base path and public pages outside it, with HTTPS from the panel, ACME or a self-signed certificate.
cmd/nexora-addoncheck, keygen, sign and verify a manifest; sign-sums and verify-sums a release's checksums.

The smallest addon:

go
raw, _ := os.ReadFile("nexora-addon.json")
a, err := addon.New(addon.Config{Manifest: raw, DataDir: "data"}.FromEnv())
if err != nil { log.Fatal(err) }
a.OnEvent(func(e addon.Event) { log.Printf("%s %s", e.Event, e.Data) })
mux := http.NewServeMux()
a.Mount(mux) // the manifest, setup, webhook and health paths
log.Fatal(http.ListenAndServe(":8090", mux))

Make the template yours ​

  1. Change slug, name, publisher, the scopes and the events in nexora-addon.json, and SLUG, REPO and BIN at the top of install.sh.
  2. Declare the questions your install needs under install.options (The addon manifest).
  3. Keep your own data in your own database, and read the panel with the token.
  4. Tag a release. The template's release workflow builds the binaries, the checksums, the release and the image. Then sign the checksums (below).

Registration with a claim code ​

An addon starts unregistered. The install passes it a one-time claim code in NEXORA_CLAIM_CODE; started by hand without one, the kit draws a code and logs it.

  1. The panel reads the manifest at the addon's address and checks its signature.
  2. It shows the operator what the addon asks for: each scope with its purpose, the request rate, and each event.
  3. On approval, the panel creates exactly that token and webhook and posts them to the addon's setup path with the claim code. The addon accepts them only with its own code, so a wrong code leaves nothing behind.

The kit keeps the credentials in its data directory and calls the function you set with OnSetup once they arrive. Credentials() returns them, or nil before registration; ClaimCode() returns the code still waiting.

A panel registers by claim code only a manifest signed by a key it trusts: Nexora's, or a developer key the directory vouches for. While you develop, register your addon by hand instead (Addons → Add your own addon): you pick the token's permissions and the webhook's events, and the panel shows the token and the webhook's signing secret once for your program's settings.

Removal and a new install ​

When the operator removes the addon, the panel first sends it panel.addon_removed, whatever events it asked for, then deletes its token and webhook. Call Forget() then, so it can be registered again.

A new claim code over the data of an earlier install (removed with its data kept, then installed again) is a new install. The kit drops the old registration, and NewInstall() tells your addon to apply the answers it otherwise uses only once, such as the first admin's password. Call NewInstallApplied() once they are stored. An update keeps the claim code and is never a new install.

Receiving events ​

The panel posts each event the addon asked for to its webhook path. A delivery carries X-Nexora-Signature: t=<unix>,v1=<hex>, the hex being the HMAC-SHA256 of <t>.<body> under the webhook secret, and an X-Nexora-Delivery id that stays the same on every retry.

The kit checks the signature and refuses a delivery more than five minutes off, hands each verified event to your handler once per delivery id, and answers the panel:

  • OnEvent for a handler that cannot fail.
  • OnEventErr for one that can: an error, or a panic, answers 500, and the panel delivers the event again.

The seen-delivery set lives in memory, so a retry across a restart is handed on again. Keep your handler's effects idempotent. The event names and their payloads are in Event catalogue; how the panel delivers them is in The event bus.

Each event is granted by a scope: the scope the panel's event catalog (GET /api/events) names for it, or that scope's :write, must be among your manifest's scopes, or the panel refuses the manifest.

Calling the panel ​

The token acts with exactly the scopes you were granted, at the request rate your manifest asked for (120 a minute unless it says otherwise). The kit's panel client speaks JSON to the panel's API, sends an Idempotency-Key when you give one, and waits once on 429:

go
c := a.Credentials()
client := &panel.Client{Base: c.Panel.URL, Token: c.Token}
var list struct{ Total int `json:"total"` }
err := client.Get(ctx, "/users?limit=1", &list)

Me() is a cheap check that the credentials still work. The routes and their bodies are the panel's API description (API reference). The panel keeps nothing of yours: store your own data, and data you keep beside users, in your own database.

Health ​

Give the manifest a health path that answers 200 while the addon can do its work. The kit answers it for you. The panel asks every minute; two failures in a row mark the addon Unhealthy and raise panel.addon_unhealthy, and the next 200 raises panel.addon_recovered. An addon without a health path is never asked.

Install options ​

Each question in install.options becomes a field in the panel's install form and a flag of your install script. Answers reach the addon as environment variables NEXORA_OPT_<KEY>, beside NEXORA_PANEL_URL and NEXORA_CLAIM_CODE; read one with addon.Option("key"). The option types and their rules are in The addon manifest.

Two types change how the panel installs you:

  • password becomes your admin's password. The panel holds it to 10 characters and 72 bytes in its form, the rule the kit's auth package holds, so a password your addon would refuse never reaches it.
  • path is the base path your admin is served under. The panel proposes a random one and joins the answer into the address it registers you at. Serve your admin, your API and the routes the panel calls (the manifest, setup, the webhook, health) under it, and your customers' pages outside it (web.Mount).

HTTPS and certificates from the panel ​

The kit's web package serves your public address over HTTPS on the install's one port, from one of:

  • A certificate from the panel: one the operator chose from the panel's store. The addon fetches it from the panel every few minutes (by its claim code before registration, by its token after) and keeps a copy, so it starts with HTTPS even while the panel is down. It needs no port 443 of its own, so several addons share a server with the panel.
  • ACME by TLS-ALPN-01 on port 443, or by HTTP-01 on any port with the authority asking on port 80.
  • A self-signed certificate for an address by IP. The operator approves its fingerprint on the panel's approval screen.

It also checks that the public address reaches this very program. How the operator chooses among these is in The addon directory.

Releases and signing ​

A release publishes the binaries named in the manifest's install.binary, the image in install.docker, a SHA256SUMS of every asset, and SHA256SUMS.sig, signed with the key that signs the manifest:

bash
go install github.com/nexora-vpn/addon-kit/cmd/nexora-addon@latest
nexora-addon check nexora-addon.json                    # would a panel accept it?
nexora-addon keygen -out addon.key                      # once: your signing key
nexora-addon sign -key addon.key nexora-addon.json > signed.json
nexora-addon sign-sums -key addon.key SHA256SUMS > SHA256SUMS.sig
gh release upload v1.2.3 SHA256SUMS.sig

keygen prints your public key and never writes over an existing key file. Keep the private key out of the repository. The signature covers every field of the manifest, so sign again after any change, including the version of each release.

The panel installs, updates or removes an addon over SSH only when the release's install.sh, and for a script install its binary, match a SHA256SUMS signed by the key that signed the release's manifest. A release without SHA256SUMS.sig can still be installed by its command. The details are in The addon manifest.

Getting listed ​

Addons are listed at addons.nexora-panel.org as official, verified or unofficial (The addon directory). To be listed, sign the manifest with your own key (nexora-addon keygen, sign) and open a pull request on the directory. Keep nexora-addon.json at the root of your public repository (for a closed addon, its public distribution repository): the directory and the install wizard read it there.

Text and images under CC BY 4.0.