Plugins
Loading and writing dylib collectors, adapters, and actions.
Plugins extend the static built-in set of collectors, adapters, and actions without rebuilding the agent. Each plugin is a Rust crate compiled to a cdylib and loaded at startup by nanook's plugin host.
Loading plugins
Tell the agent where to look:
[]
= ["/usr/lib/nanook", "./plugins"]
= [
"sha256:d3983dbf3dbc30aa4838bb16c38786d03af5faf2643db2a0cdab7444697ff28b",
]
= true
= true
| Field | What |
|---|---|
dirs | Directories searched for .so / .dylib / .dll files |
allowed | Whitelist of accepted digests, each prefixed with sha256: (64 hex chars) or sha512: (128 hex chars) |
verify | If true, refuse to load any file whose digest isn't in allowed. Default true. |
strict | If true, refuse plugin dirs not owned by the running uid or that are world-writable |
Disabling verify lets the host load any cdylib found in dirs. Only safe when the dirs are entirely under your control.
Signed plugins
The hash allowlist works fine for tiny fleets but doesn't scale: every rebuild changes the digest, and every operator has to copy a fresh sha256 into config. Signed manifests fix that. The publisher signs the cdylib's hash once with a release key, and operators trust the key, not the bytes.
The full operator and publisher story (trust list config, nanook keygen --kind signing, nanook plugins sign, nanook plugins verify, nanook plugins trust *, packaging modes, threat model, platform notes) lives on the Trust page. The same machinery covers the agent binary itself via [self.signature] and nanook self {sign,verify,trust}.
Quick summary: when [plugins.signature].require = true, only cdylibs carrying a manifest signed by one of [plugins.signature].signers will load. Operators add a publisher's key once with nanook plugins trust add <line-or-path> and forget it.
Allowlist + signatures
[plugins].allowed (hash whitelist) and [plugins.signature] (publisher trust) are orthogonal. They are not redundant by accident: each loaded cdylib must clear every gate that is configured.
- Hash in
[plugins.signature].revokedrejects, unconditionally. - If
signature.require = true, the manifest must verify against one ofsigners. - If
[plugins].allowedis non-empty, the hash must appear in it. - Otherwise the load proceeds.
A signed manifest already carries the artifact's sha256, so once you trust the publisher you usually leave allowed empty. Keep both only if you want a locally-curated second gate, so a compromise of the publisher's release key does not by itself admit a fresh build.
Discovering plugins
ABI versioning
Each plugin stamps the nanook-plugin ABI version and the plugin kind into its cdylib via declare_plugin!. The host reads those stamps statically out of the symbol table, with no dlopen involved: dlopen would run the cdylib's global constructors before the host could verify its hash, which means a stray plugin could execute code just by sitting in a search dir. Static reads avoid that.
ABI mismatches surface as a clear error rather than a runtime crash. When you bump the host, plugins built against an older nanook-plugin need a rebuild. nanook plugins doctor tells you which.
Authoring a plugin
Scaffold
This creates a crate with the right Cargo.toml, the trait impl stubs, and a build profile suitable for cdylib.
Flags:
| Flag | What |
|---|---|
-k, --kind | collector, adapter, or action |
--path | Parent directory to scaffold into |
--nanook-path | Use a local checkout of nanook-plugin |
--nanook-git | Use a git URL for nanook-plugin |
--nanook-version | Pin a crates.io version |
Three kinds, three traits
| Kind | Trait | What it does |
|---|---|---|
| collector | CollectorPlugin | Polls and emits metrics |
| adapter | AdapterPlugin | Receives metrics, ships them somewhere |
| action | ActionPlugin | Receives alert payloads, takes action |
Doc surface
Every plugin publishes a Doc struct: name, description, options, metrics, examples markdown. The host parses the examples markdown for nanook doc <name> and for this site's Reference section. Author docs once, get terminal output and HTML for free.
use ;
use Unit;
pub static DOC: Doc = new
.with_opts
.with_metrics
.with_examples_md;
docs/my_collector.md follows the ## Title + fenced code block convention. See any built-in collector's docs/ folder for a model.
Building
The output .so / .dylib / .dll lives at target/release/. Drop it into one of the host's [plugins].dirs, add its digest to [plugins].allowed, and run nanook plugins check before going live.
Hot reload
nanook ctl reload picks up new plugin files and re-validates digests. Plugins that disappear get unloaded.
Reference
Built-in collectors, adapters, and actions ship with their own Doc, which feeds the reference pages. The same surface gets generated automatically for every plugin you write.