Skip to content

Add plugin system#5

Open
alexandre-daubois wants to merge 6 commits intomainfrom
plugins
Open

Add plugin system#5
alexandre-daubois wants to merge 6 commits intomainfrom
plugins

Conversation

@alexandre-daubois
Copy link
Copy Markdown
Owner

@alexandre-daubois alexandre-daubois commented Mar 31, 2026

This adds a plugin system to Ember. Fix #3

Ping @hslatman, would you like to try this and see if it offers the possibilities you have in mind? Docs are up-to-date in the PR with a complete example to guide you on how you can create a plugin.

If it doesn't match your needs, let's discuss how this implementation could be improved!

@alexandre-daubois alexandre-daubois force-pushed the plugins branch 3 times, most recently from 232f1cd to 8a07116 Compare March 31, 2026 17:34
@hslatman
Copy link
Copy Markdown

hslatman commented Apr 2, 2026

Awesome! I hope to try it today/tomorrow 🙂

@hslatman
Copy link
Copy Markdown

hslatman commented Apr 4, 2026

Hey @alexandre-daubois,

I gave this a quick try, and got a very basic version working retrieving some info from my CrowdSec bouncer. Will need some more time to make it look good now. But at least the foundation looks OK 🙂

Some initial remarks:

  • Building a custom ember currently requires importinggithub.com/alexandre-daubois/ember/internal/app. This doesn't work when implementing it in a different Go module, as it sits in the internal package. My current workaround is to hack on it in a fork of this repo, but considering the compile time plugin method, this is most likely not the way you want plugins to work in the current setup.
  • I have some work in progress to integrate with Caddy's metrics, serving them on the /metrics endpoint. Instead of having my plugin fetch the metrics, and parse just those out, it could be useful to "subscribe" to certain named metrics already fetched by the core, preventing some duplicate requests to /metrics.
  • Related to the above: I copied quite a bit of code from internal packages to be able to reuse logic for parsing the Prometheus metrics. In general I think it could be useful to expose some more packages that could be used in plugins. But that would probably need some more time to take shape.

Below a few nice to haves that came to mind:

  • Support a single plugin to provide multiple tabs. Multiple tabs can now be done by having multiple plugins, but this could be useful for plugins that follow some kind of core logic + add-on functionality. In my CrowdSec module I have a global configuration, but then have a Bouncer and AppSec components, each having their own metrics. With the multiple tabs per plugin setup I could have shared logic fetching all metrics, and then showing it in different tabs.
  • It could be nice to have a way to disable a plugin tab, even though it is compiled in. In my case, with a custom Ember build you could be talking to a custom Caddy build that doesn't have the CrowdSec module enabled, resulting in no useful information being available for the tab. Before you added this plugin functionality I was hacking on my fork enabling the CrowdSec tab conditionally, similar to how you did the FrankenPHP integration. It would be nice to have something like that for plugins too, I think.
  • Very specific to my CrowdSec module, but just noting it here in case it might be useful to others too: in a custom Caddy build my module adds an additional subcommand, which can be used to communicate with custom Caddy admin API endpoints. It could be useful to embed the Ember build as part of that, and showing just the tab with the CrowdSec metrics. But I can see this this may be too far fetched for this project, and could probably be done by building a simple TUI in my subcommand itself too.

Thanks for this so far, and will do some more hacking 😄

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a compile-time plugin system for Ember to allow third-party modules to add custom TUI tabs and/or contribute Prometheus metrics to Ember’s /metrics endpoint, addressing the request in Issue #3 (supporting additional Caddy modules).

Changes:

  • Add public plugin API (pkg/plugin) with registration, lifecycle hooks, TUI rendering, and metrics exporting interfaces.
  • Extract/introduce reusable Prometheus parsing + metric snapshot types into a public pkg/metrics package for plugin authors.
  • Integrate plugins into TUI + daemon flows (tab rendering, key forwarding, help overlay integration, and /metrics export integration).

Reviewed changes

Copilot reviewed 27 out of 27 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
README.md Documents plugin system and links to plugin guide.
pkg/plugin/registry.go Adds global plugin registry with name validation/collision checks.
pkg/plugin/plugin.go Defines plugin interfaces, config structs, and panic-safe fetch helper.
pkg/plugin/plugin_test.go Adds unit tests for plugin registry, interfaces, and SafeFetch.
pkg/metrics/types.go Introduces exported snapshot/metric types for reuse by plugins.
pkg/metrics/parse.go Adds exported Prometheus text parser used by core and plugins.
internal/ui/tabbar.go Extends tab labeling to support plugin-provided tab names.
internal/ui/tabbar_test.go Updates tests for new tabbar function signature.
internal/ui/plugin_bridge.go Bridges plugin interfaces into the UI runtime with panic safety.
internal/ui/plugin_bridge_test.go Adds tests for plugin bridge behavior and safety wrappers.
internal/ui/help.go Extends help overlay to include plugin keybindings.
internal/ui/help_test.go Updates tests for help overlay signature change.
internal/ui/app.go Integrates plugins into main TUI app loop (tabs, fetch, view, key routing).
internal/ui/app_test.go Adds tests for plugin tab behavior, key forwarding, and fetch handling.
internal/fetcher/prometheus.go Switches core Prometheus parsing to pkg/metrics and keeps test wrappers.
internal/fetcher/fetcher.go Re-exports snapshot/types as aliases to pkg/metrics types.
internal/exporter/exporter.go Adds plugin metric exports to /metrics handler with panic protection.
internal/exporter/exporter_test.go Adds tests ensuring plugin metrics render and panics are isolated.
internal/app/tui.go Wires plugins into TUI startup and stores plugin exports for /metrics.
internal/app/run.go Initializes/cleans up plugins and reads plugin env options.
internal/app/run_test.go Adds tests for plugin init, env options parsing, and closer cleanup.
internal/app/daemon.go Adds daemon-side plugin fetching/subscription + export wiring.
internal/app/daemon_test.go Adds tests for daemon plugin wiring, concurrency fetch, and subscriptions.
ember.go Adds public ember package entrypoint for custom binaries (blank-import plugins).
docs/plugins.md Adds comprehensive plugin development guide and interface reference.
docs/index.md Links plugin documentation from docs index.
cmd/ember/main.go Switches CLI binary to use the new public ember entrypoint.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/ui/app.go
Comment thread internal/ui/app.go Outdated
Comment thread internal/ui/help.go
Comment thread internal/app/run.go Outdated
Comment thread pkg/plugin/registry.go
Comment thread internal/exporter/exporter.go
@alexandre-daubois alexandre-daubois force-pushed the plugins branch 4 times, most recently from a5e769f to 9d70151 Compare April 5, 2026 16:06
@alexandre-daubois
Copy link
Copy Markdown
Owner Author

Super valuable feedback, thank you so much @hslatman! I've pushed changes that should address most of your points:

  • Building a custom Ember no longer requires importing internal/app. There's now a public entry point:
import (                                                                                                                                                                           
    "github.com/alexandre-daubois/ember"
    _ "github.com/myorg/my-plugin"                                                                                                                                                 
)                                                                 

func main() { ember.Run() }
  • The Prometheus parsing logic is now in a public pkg/metrics package, so you can reuse it instead of copying code from internal
  • Plugins can subscribe to core metrics via the MetricsSubscriber interface, avoiding duplicate /metrics requests
  • Multiple tabs per plugin via the MultiRenderer interface: a single plugin can provide N tabs sharing one Fetch call
  • Conditional tab visibility via the Availability interface: your plugin can hide its tab(s) at runtime when the module isn't detected

Everything is documented in the updated docs/plugins.md. I pushed the whole thing into a new commit so it's easier the catch what changed in the docs.

Let me know if this covers your needs!

@hslatman
Copy link
Copy Markdown

hslatman commented Apr 6, 2026

* Building a custom Ember no longer requires importing internal/app. There's now a public entry point:

This is working great.

* The Prometheus parsing logic is now in a public `pkg/metrics` package, so you can reuse it instead of copying code from internal
* Plugins can subscribe to core metrics via the `MetricsSubscriber` interface, avoiding duplicate `/metrics` requests

This is close, but doesn't fully support my use case. In my CrowdSec module I'm working on adding additional metrics that are registered with Caddy's core metrics, so that consumers only need to hit a single /metrics endpoint (in addition to pushing metrics to CrowdSec in its own native way). I don't know how often that is done in custom Caddy modules, and its method is marked EXPERIMENTAL, but it seemed like a convenient way of doing this. It would be great if there was a way for the Ember core to parse out these additional metrics, and pass (just) those back to my plugin.

* Multiple tabs per plugin via the `MultiRenderer` interface: a single plugin can provide N tabs sharing one Fetch call

Looking good!

* Conditional tab visibility via the `Availability` interface: your plugin can hide its tab(s) at runtime when the module isn't detected

I believe the bool currently applies to the plugin as a whole, with no way to make it conditional based on the the tab?

It would be great if the key of the tab could be taken into account. In my use case I have the core module, with some optional functionality that would be shown best in its own tab. If the optional functionality is not enabled, I would like to disable that tab, but not the core tab.

Everything is documented in the updated docs/plugins.md. I pushed the whole thing into a new commit so it's easier the catch what changed in the docs.

👍

@alexandre-daubois
Copy link
Copy Markdown
Owner Author

This is close, but doesn't fully support my use case. In my CrowdSec module I'm working on adding additional metrics that are registered with Caddy's core metrics, so that consumers only need to hit a single /metrics endpoint (in addition to pushing metrics to CrowdSec in its own native way). I don't know how often that is done in custom Caddy modules, and its method is marked EXPERIMENTAL, but it seemed like a convenient way of doing this. It would be great if there was a way for the Ember core to parse out these additional metrics, and pass (just) those back to my plugin.

I addressed this concern in 3ec07a9

I believe the bool currently applies to the plugin as a whole, with no way to make it conditional based on the the tab?

It would be great if the key of the tab could be taken into account. In my use case I have the core module, with some optional functionality that would be shown best in its own tab. If the optional functionality is not enabled, I would like to disable that tab, but not the core tab.

And this one in 8f0646a.

Let me know if this helps!

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.

Adding support for additional Caddy modules

3 participants