reaction-plugin 1.0.0

Plugin interface for reaction, a daemon that scans logs and takes action (alternative to fail2ban)
Documentation

This crate defines the API between reaction's core and plugins.

Plugins must be written in Rust, for now.

This documentation assumes the reader has some knowledge of Rust. However, if you find that something is unclear, don't hesitate to ask for help, even if you're new to Rust.

To implement a plugin, one has to provide an implementation of [PluginInfo], that provides the entrypoint for a plugin. It permits to define 0 to n custom stream and action types.

Note on reaction-plugin API stability

This is the v1 of reaction's plugin interface. It's quite efficient and complete, but it has the big drawback of being Rust-only and [tokio]-only.

In the future, I'd like to define a language-agnostic interface, which will be a major breaking change in the API. However, I'll try my best to reduce the necessary code changes for plugins that use this v1.

Naming & calling conventions

Your plugin should be named reaction-plugin-$NAME, eg. reaction-plugin-postgresql. It will be invoked with one positional argument "serve".

reaction-plugin-$NAME serve

This can be useful if you want to provide CLI functionnality to your users, so you can distinguish between a human user and reaction.

State directory

It will be executed in its own directory, in which it should have write access. The directory is $reaction_state_directory/plugin_data/$NAME. reaction's state_directory defaults to its working directory, which is /var/lib/reaction in most setups.

So your plugin directory should most often be /var/lib/reaction/plugin_data/$NAME, but the plugin shouldn't expect that and use the current working directory instead.

Communication

Communication between the plugin and reaction is based on [remoc], which permits to multiplex channels and remote objects/functions/trait calls over a single transport channel. The channels read and write channels are stdin and stdout, so you shouldn't use them for something else.

[remoc] builds upon [tokio], so you'll need to use tokio too.

Errors

Errors during:

  • config loading in [PluginInfo::load_config]
  • startup in [PluginInfo::start]

should be returned to reaction by the function's return value, permitting reaction to abort startup.

During normal runtime, after the plugin has loaded its config and started, and before reaction is quitting, there is no rusty way to send errors to reaction. Then errors can be printed to stderr. They'll be captured line by line and re-printed by reaction, with the plugin name prepended.

A line can start with DEBUG , INFO , WARN , ERROR . If it starts with none of the above, the line is assumed to be an error.

Example: Those lines:

WARN This is an official warning from the plugin
Freeeee errrooooorrr

Will become:

WARN plugin test: This is an official warning from the plugin
ERROR plugin test: Freeeee errrooooorrr

Plugins should not exit when there is an error: reaction quits only when told to do so, or if all its streams exit, and won't retry starting a failing plugin or stream. Please only exit if you're in a 100% failing state. It's considered better to continue operating in a degraded state than exiting.

Getting started

If you don't have Rust already installed, follow their Getting Started documentation to get rust build tools and learn about editor support.

Then create a new repository with cargo:

cargo new reaction-plugin-$NAME
cd reaction-plugin-$NAME

Add required dependencies:

cargo add reaction-plugin tokio

Replace src/main.rs with those contents:

use reaction_plugin::PluginInfo;

#[tokio::main]
async fn main() {
    let plugin = MyPlugin::default();
    reaction_plugin::main_loop(plugin).await;
}

#[derive(Default)]
struct MyPlugin {}

impl PluginInfo for MyPlugin {
  // ...
}

Your IDE should now propose to implement missing members of the [PluginInfo] trait. Your journey starts!

Examples

Core plugins can be found here: https://framagit.org/ppom/reaction/-/tree/main/plugins.

  • The "virtual" plugin is the simplest and can serve as a good complete example that links custom stream types and custom action types.
  • The "ipset" plugin is a good example of an action-only plugin.