mirror of
https://github.com/mvt-project/mvt.git
synced 2026-09-03 08:30:51 +02:00
The custom modules page now leads with plugin packages. Loading module files with --load-module and MVT_CUSTOM_MODULES moves to a section on developing a module locally. A new "Writing a module" section shows a module which subclasses IOSExtraction. It lists each base class, the command pair it serves and the helpers it provides. "Depending on a built-in module" says to import a built-in class from its family package. "Importing from MVT" says what mvt.plugin exports and what importing from it means. The custom commands page shows a Command subclass which lists its own modules. The sysdiagnose and plugin configuration pages import from mvt.plugin.
158 lines
5.6 KiB
Markdown
158 lines
5.6 KiB
Markdown
# Plugin Configuration
|
|
|
|
Plugin packages that add [custom CLI commands](custom_commands.md) or
|
|
[modules](index.md#installed-module-packages) often need to store their
|
|
own settings, such as an API key, a server URL or the timestamp of the last
|
|
synchronization. MVT provides a namespaced settings base class so each plugin
|
|
keeps its configuration in its own file, and a data folder for anything else a
|
|
plugin needs to keep on disk.
|
|
|
|
!!! warning
|
|
|
|
Do not write plugin settings to MVT's own `config.yaml`. MVT rewrites that
|
|
file with the settings it knows about every time it starts, so any other
|
|
section is deleted.
|
|
|
|
## Where Settings Are Stored
|
|
|
|
Each plugin gets one YAML file in a `plugins` folder next to MVT's own
|
|
configuration:
|
|
|
|
```
|
|
~/.config/mvt/plugins/<plugin name>.yaml
|
|
```
|
|
|
|
The exact parent folder follows the platform convention used for MVT's
|
|
`config.yaml` (for example `~/Library/Application Support/mvt` on macOS). Use
|
|
`plugin_config_path()` from `mvt.plugin` instead of building the path by hand.
|
|
|
|
Plugin names must be lowercase and may only contain letters, digits and dashes,
|
|
matching the `mvt-plugin-<name>` package naming convention. MVT creates the
|
|
`plugins` folder with `0700` permissions and writes the settings files with
|
|
`0600` permissions, because they commonly hold credentials. Files are written
|
|
through a temporary file and moved into place, so an interrupted save never
|
|
leaves a partially written settings file behind.
|
|
|
|
## Plugin Data Folder
|
|
|
|
Everything else a plugin keeps on disk, such as a cache, a downloaded artifact
|
|
or synchronization state, belongs in the folder returned by the `data_folder()`
|
|
class method of the plugin's settings class, or by `plugin_data_folder()` from
|
|
`mvt.plugin`, called with the plugin name if the plugin has no settings class:
|
|
|
|
```
|
|
~/.local/share/mvt/plugin-data/<plugin name>/ # Linux
|
|
~/Library/Application Support/mvt/plugin-data/<plugin name>/ # macOS
|
|
```
|
|
|
|
The folder sits beside MVT's own data, such as the downloaded indicators. It is
|
|
created if it is missing, with `0700` permissions. Asking for it again returns
|
|
the same path and leaves the contents alone, so a plugin can ask for it every
|
|
time it needs the folder. `ExamplePluginSettings` below is the settings class
|
|
defined in the next section:
|
|
|
|
```python
|
|
import os
|
|
|
|
|
|
def cache_path() -> str:
|
|
folder = ExamplePluginSettings.data_folder()
|
|
return os.path.join(folder, "virustotal_lookups_cache.json")
|
|
```
|
|
|
|
A plugin which has no settings class calls
|
|
`plugin_data_folder("example-plugin")` instead.
|
|
|
|
Do not fall back on a path of your own such as `~/.cache/example-plugin`: it
|
|
is a Linux-only convention, and MVT will not create it for you.
|
|
|
|
## Defining Plugin Settings
|
|
|
|
Subclass `MVTPluginSettings`, set `plugin_name` and declare typed fields with
|
|
defaults:
|
|
|
|
```python
|
|
from typing import Optional
|
|
|
|
from mvt.plugin import MVTPluginSettings
|
|
|
|
|
|
class ExamplePluginSettings(MVTPluginSettings):
|
|
plugin_name = "example-plugin"
|
|
|
|
API_KEY: Optional[str] = None
|
|
MAX_RESULTS: int = 25
|
|
LAST_SYNC: Optional[str] = None
|
|
```
|
|
|
|
`load()` returns the current settings and `save()` writes them back:
|
|
|
|
```python
|
|
from datetime import datetime, timezone
|
|
|
|
import click
|
|
|
|
from mvt.plugin import plugin_env_prefix
|
|
|
|
|
|
def sync():
|
|
settings = ExamplePluginSettings.load()
|
|
if not settings.API_KEY:
|
|
prefix = plugin_env_prefix(settings.plugin_name)
|
|
raise click.ClickException(
|
|
f"No API key configured. Set {prefix}API_KEY or "
|
|
"run 'example-plugin configure'."
|
|
)
|
|
|
|
settings.LAST_SYNC = datetime.now(timezone.utc).isoformat()
|
|
settings.save()
|
|
```
|
|
|
|
The message builds the variable name with `plugin_env_prefix()`, see
|
|
[Environment Variables](#environment-variables).
|
|
|
|
A missing settings file is not an error: the plugin then runs on the field
|
|
defaults and on whatever the environment provides. `save()` only persists the
|
|
values that differ from the defaults, and it never touches MVT's `config.yaml`.
|
|
A settings file that cannot be parsed, or that does not hold a mapping of
|
|
setting names to values, raises a `PluginConfigLoadError` naming the file.
|
|
|
|
Every subclass that sets its own `plugin_name` gets its own file and its own
|
|
environment namespace. A subclass that does not redefine `plugin_name` inherits
|
|
it, and therefore shares the file and the environment variables of its parent
|
|
class.
|
|
|
|
## Environment Variables
|
|
|
|
Every field can also be set with an environment variable. The prefix is
|
|
`MVT_PLUGIN_`, followed by the plugin name upper-cased with dashes replaced by
|
|
underscores, followed by the field name. For the example above:
|
|
|
|
```bash
|
|
export MVT_PLUGIN_EXAMPLE_PLUGIN_API_KEY=...
|
|
export MVT_PLUGIN_EXAMPLE_PLUGIN_MAX_RESULTS=50
|
|
```
|
|
|
|
Do not repeat the plugin name in a field name: a plugin named `example-scanner`
|
|
with a `SCANNER_API_KEY` field asks the user for
|
|
`MVT_PLUGIN_EXAMPLE_SCANNER_SCANNER_API_KEY`. Name the field `API_KEY`.
|
|
|
|
Settings resolve in this order, from highest to lowest priority:
|
|
|
|
1. Arguments passed to the settings class directly, such as
|
|
`ExamplePluginSettings(API_KEY="...")`
|
|
2. Environment variables
|
|
3. The plugin's YAML file
|
|
4. The field defaults declared on the settings class
|
|
|
|
!!! tip
|
|
|
|
On shared or multi-user machines, prefer passing API keys through
|
|
environment variables rather than saving them to the plugin file. `save()`
|
|
skips every value that the environment currently supplies, so a credential
|
|
provided that way is not copied into the settings file when a plugin saves
|
|
an unrelated setting.
|
|
|
|
Unknown keys in a plugin's YAML file are ignored, so a settings file written by
|
|
a newer version of a plugin does not break an older one.
|