Skip to content

feat(plugin): add a plugin system and move DNS providers and log analytics into plugins - #1991

Draft
Hintay wants to merge 126 commits into
devfrom
feat/plugin-system
Draft

Hintay wants to merge 126 commits into
devfrom
feat/plugin-system

Conversation

@Hintay

@Hintay Hintay commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator

Nginx UI grew by putting every feature into one binary. Two of them dominate its size and its memory: the DNS providers of lego, which every installation carries even though most issue only HTTP-01 certificates, and the Bleve index behind advanced log indexing, which starts with the host and keeps hundreds of megabytes resident. Features for one vendor or one use case, such as another DNS provider, a notification channel or a backup destination, could only be added by changing the core.

This PR adds a plugin system. A plugin is a signed package that Nginx UI installs from a marketplace, from an uploaded file or from an offline directory, then runs as a separate process with the permissions the user approved. The two heavy features above become the first official plugins, so the host only carries them on the nodes that use them.

What changes for users

  • The binary is smaller. Built the same way (-tags=unembed, darwin/arm64), the host is 104 MB with 181 modules, compared with 159 MB and 326 modules on dev. The DNS-01 plugin (59 MB) is installed only where a certificate needs it.
  • Idle memory drops. On a node with a built index of 1.4 million log lines, resident memory after five minutes of idle is 137 MB (98 MB host, 39 MB plugin), compared with 1000 MB before. Startup to a ready API goes from about 470 ms to 180 ms.
  • Nothing to do on upgrade. A node that issues DNS-01 certificates installs the official DNS-01 plugin on its own, from the offline package directory or from the marketplace, at startup and once a day, so renewals keep working. A node that used advanced indexing hands its index, its settings and its city database over to the log analytics plugin the first time the plugin starts, and the host then drops the old table.

Plugins page

System > Plugins has three tabs.

  • Installed. Every plugin as a card: state (running, standby, stopped, needs attention), trust level, release channel, what it provides and conflicts with other plugins. The card turns a plugin on or off, opens its settings and its logs, and sets how it reaches the other nodes of a cluster.
  • Plugin details. A drawer with what the plugin provides, the permissions it holds and the addresses it may connect to, its settings form (generated from the manifest), its update channel, its resource limits and the memory it recommends, and its logs. Where the node has less memory than a plugin recommends, the card, the details and the install dialog warn about it.
  • Marketplace. Catalog entries with icons, descriptions, categories and trust, filtered by category or by available updates. The detail drawer lists the screenshots of the entry, the requested permissions and every installable version, so a user can also install an earlier one. Screenshots follow the theme: an entry may give a dark_url for each image, which is shown in the dark theme, and the light image stands in when there is none.
  • Sources. Catalogs are merged in order, the first that offers a plugin wins. A source is named by the catalog itself and checked when it is added.
  • Nodes. A matrix of every plugin on every child node: in sync, outdated, not installed, offline, unsupported platform or opted out, with one action to sync a plugin to all nodes.

Plugin settings in Preferences > Plugins turn the plugin system and the marketplace on or off, set the default sync policy, allow community plugins, turn automatic updates on, list trusted publishers and set resource limits.

Trust and permissions

  • Signatures decide trust, not catalogs. A package carries plugin.sums signed with minisign. A release key of the project makes it official, a partner key the project vouches for makes it verified, the key of the catalog entry or of a trusted publisher makes it community, anything else is unsigned. Unsigned packages install only in developer mode, community packages only when the operator allows them and after a confirmation.
  • Permissions are approved, then pinned. A plugin declares what it needs (network hosts, log files, nginx configuration, its own storage, notifications and so on). The user approves the set at install time. An update that asks for more stops until the added permissions are approved. With automatic updates on, official and partner plugins update on their own as long as they ask for nothing new.
  • Processes are confined. On Linux each plugin runs in its own cgroup with memory and CPU limits, and the kernel prefers to stop a plugin rather than the host when memory runs out. A manifest can state the memory it recommends, and the interface warns when the node has less.

What plugins can provide

Capabilities are features Nginx UI hands to a plugin: dns01, http (pages and API behind Nginx UI's authentication, including WebSockets), notify, probe (site health checks), mcp (tools for the MCP server), storage (backup destinations), cert.deploy, security.blocklist, upstream.discovery and log.sink (nginx access log lines as nginx writes them). A plugin can also add pages, panels, form fields and table columns to the interface through named extension slots, and ship nginx configuration templates and translations without any process at all.

The process talks to the host over JSON-RPC on its standard input and output, and may serve the same calls over gRPC on a unix socket (a named pipe on Windows). Through the host API it can use a key-value store, its settings, notifications, scheduled tasks, stored credentials and the nginx configuration, sites and certificates of the node, each behind a permission.

Certificates

  • DNS-01 through the plugin. The certificate dialog and the HTTPS card of the site editor take the DNS-01 form from the plugin. DNS credentials are rendered from the form each provider declares: sign-in methods, required and optional fields, fixed values and links to the provider documentation.
  • DNS-01 for child nodes on the main node. When a child node is selected and DNS-01 is chosen, the dialog and the HTTPS card offer to validate on the main node, which is the default. The main node issues the certificate with its own DNS credentials, sends it to the paths the child node asks for, keeps renewing it and sends every renewal. The child node needs neither the plugin nor the credentials, and a child node with its own plugin can still validate on its own. Such a certificate is tagged with the node it serves in the certificate list, and deleting it asks whether to also remove its files from the node; they stay by default, and a node keeps them while its nginx configuration still loads them.

Log analytics

Advanced indexing is now the official log analytics plugin. The structured search, the dashboard and the visitor map look and work as before, and the plugin keeps reading the log files of the node directly. Searching the configuration no longer needs a full text index: the host matches configurations with a lightweight in-memory search. A second official plugin, Log Analytics (Preview), built on Tantivy, is offered on the beta channel; the two cannot be enabled together.

For plugin authors

  • Documentation under docs/plugin (English, Simplified and Traditional Chinese): overview, quick start, manifest, protocol, lifecycle, permissions, host API, pages and slots, every capability, packaging, signing, catalogs and versioning.
  • Command line: nginx-ui plugin init scaffolds a Go or Rust plugin; lint and conformance check a plugin before publishing; pack, sign and certify build and sign packages; fetch and marketplace list work with catalogs; install, enable, disable, uninstall and inspect manage a node without the web interface.
  • SDKs in separate repositories of the nginxui organization: plugin-sdk-go, plugin-sdk-rust and @nginxui/plugin-sdk for the browser bundle, built from the machine readable contract in plugin-spec.

Before merging

  • The named pipe transport on Windows has only been cross compiled and vetted, not run on a Windows machine yet.
  • The plugins in the screenshots are local builds signed with a test key that is listed under Trusted Publishers, which is why they show as Community and why the marketplace offers the official version. Official packages are signed in CI with the release key and will show as Official.

Screenshots

Installed plugins

01-installed

Plugin details: conflicts, trust, permissions and network hosts

02-plugin-detail

Marketplace

03-marketplace

Marketplace entry, light and dark: the screenshots follow the theme

Light Dark
04-marketplace-detail-light 05-marketplace-detail-dark

Marketplace sources

06-sources

Plugins across the nodes of a cluster

07-nodes

Plugin preferences, and the trusted publishers whose keys make a package community

Preferences Trusted publishers
08-settings 17-trusted-publishers

Issuing a DNS-01 certificate with the plugin, and a DNS credential rendered from the provider form

Issue certificate DNS credential
09-issue-dns01 10-dns-credential

Log analytics plugin: dashboard and structured search

Dashboard Structured search
11-log-dashboard 12-log-structured

DNS-01 for a child node on the main node: HTTPS card, certificate list, certificate page and deletion

HTTPS card Certificate list
13-https-main-node 14-cert-list
Certificate page Delete
15-cert-delegated 16-cert-remove

Tested

  • Unit tests of the affected packages pass with -race during development, including the plugin manager, supervisor, transports, signatures, marketplace, cluster sync, capabilities, the log analytics handoff and the certificate delegation.
  • nginx-ui plugin conformance passes for the Go and Rust SDKs over both transports.
  • ESLint and bun run typecheck pass for the changed frontend files, and the documentation site builds.
  • Checked in the browser on a main node and a child node: the plugin pages, marketplace sources, screenshots in both themes, the node matrix, DNS-01 through the plugin, DNS-01 for a child node on the main node up to the certificate authority (no ACME account was available locally, so the issued certificate path is covered by unit tests), and log analytics. The HTTPS card was also checked in the dark theme at phone width.
  • Memory and timing figures come from the same 1.4 million line dataset on the same machine for both builds; the low memory runs were repeated on Linux with hard limits down to 256 MB.

Comment thread api/plugin/static.go Fixed
Comment thread api/plugin/static.go Fixed
Comment thread internal/plugin/marketplace.go Fixed
@Hintay
Hintay force-pushed the feat/plugin-system branch 5 times, most recently from 9b2b2b5 to 5cbde51 Compare October 5, 2026 11:54
@0xJacky
0xJacky force-pushed the dev branch 2 times, most recently from bfe541e to 317ee7c Compare October 5, 2026 12:57
@Hintay
Hintay force-pushed the feat/plugin-system branch 2 times, most recently from ab393f7 to e2302f7 Compare October 7, 2026 12:20
@BluLupo

BluLupo commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

It's very nice and interesting!

@Hintay
Hintay force-pushed the feat/plugin-system branch 3 times, most recently from 7b07d0f to 4cd8345 Compare October 15, 2026 08:27
@Hintay
Hintay force-pushed the feat/plugin-system branch 2 times, most recently from 8afa056 to 4585d48 Compare October 17, 2026 13:28
Comment thread internal/plugin/marketplace.go Dismissed
Hintay added 29 commits October 19, 2026 23:17
@Hintay
Hintay force-pushed the feat/plugin-system branch from a930264 to 3eed701 Compare October 19, 2026 14:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants