docs: update README for .ndp plugin packaging and installation instructions

Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
Deluan 2025-12-28 21:43:18 -05:00
parent e52b757cd4
commit 4e392f7b07
10 changed files with 178 additions and 157 deletions

View File

@ -29,38 +29,41 @@ Navidrome supports WebAssembly (Wasm) plugins for extending functionality. Plugi
### 1. Create a minimal plugin
Create `main.go`:
```go
package main
import (
"encoding/json"
"github.com/extism/go-pdk"
)
//go:wasmexport nd_manifest
func ndManifest() int32 {
manifest := map[string]string{
"name": "My Plugin",
"author": "Your Name",
"version": "1.0.0",
}
out, _ := json.Marshal(manifest)
pdk.Output(out)
return 0
}
import "github.com/extism/go-pdk"
func main() {}
// Implement your capability functions here
```
### 2. Build with TinyGo
Create `manifest.json`:
```json
{
"name": "My Plugin",
"author": "Your Name",
"version": "1.0.0"
}
```
### 2. Build with TinyGo and package as .ndp
```bash
tinygo build -o my-plugin.wasm -target wasip1 -buildmode=c-shared .
# Compile to WebAssembly
tinygo build -o plugin.wasm -target wasip1 -buildmode=c-shared .
# Package as .ndp (zip archive)
zip -j my-plugin.ndp manifest.json plugin.wasm
```
### 3. Install
Copy `my-plugin.wasm` to your Navidrome plugins folder and enable plugins in your config:
Copy `my-plugin.ndp` to your Navidrome plugins folder and enable plugins in your config:
```toml
[Plugins]
@ -74,23 +77,31 @@ Folder = "/path/to/plugins"
### What is a Plugin?
A Navidrome plugin is a WebAssembly (`.wasm`) file that:
A Navidrome plugin is an `.ndp` package file (zip archive) containing:
1. **Exports `nd_manifest`** Returns JSON describing the plugin
2. **Exports capability functions** Implements one or more capabilities
1. **`manifest.json`** Plugin metadata (name, author, version, permissions)
2. **`plugin.wasm`** Compiled WebAssembly module with capability functions
### Plugin Package Structure
```
my-plugin.ndp (zip archive)
├── manifest.json # Required: Plugin metadata
└── plugin.wasm # Required: Compiled WebAssembly module
```
### Plugin Naming
Plugins are identified by their **filename** (without `.wasm` extension), not the manifest `name` field:
Plugins are identified by their **filename** (without `.ndp` extension), not the manifest `name` field:
- `my-plugin.wasm` → plugin ID is `my-plugin`
- `my-plugin.ndp` → plugin ID is `my-plugin`
- The manifest `name` is the display name shown in the UI
This allows users to have multiple instances of the same plugin with different configs by renaming the files.
### The Manifest
Every plugin must export `nd_manifest` returning JSON:
Every plugin must include a `manifest.json` file. Example:
```json
{
@ -110,8 +121,6 @@ Every plugin must export `nd_manifest` returning JSON:
**Required fields:** `name`, `author`, `version`
**Capabilities are auto-detected** from which functions your plugin exports. You don't declare them in the manifest.
---
## Capabilities
@ -572,12 +581,12 @@ Enabled = true
Folder = "/path/to/plugins" # Default: DataFolder/plugins
AutoReload = true # Auto-reload on file changes (dev mode)
LogLevel = "debug" # Plugin-specific log level
CacheSize = "100MB" # Compilation cache size limit
CacheSize = "200MB" # Compilation cache size limit
```
### Plugin Configuration
Plugin configuration is managed through the Navidrome web UI. Navigate to the Plugins page, select a plugin, and edit its configuration as a JSON object with string key-value pairs.
Plugin configuration is managed through the Navidrome web UI. Navigate to the Plugins page, select a plugin, and edit its configuration as key-value pairs.
Access configuration values in your plugin:
@ -607,8 +616,31 @@ Plugins can be written in any language that compiles to WebAssembly. We recommen
```bash
# Install TinyGo: https://tinygo.org/getting-started/install/
# Build
tinygo build -o my-plugin.wasm -target wasip1 -buildmode=c-shared .
# Build WebAssembly module
tinygo build -o plugin.wasm -target wasip1 -buildmode=c-shared .
# Package as .ndp
zip -j my-plugin.ndp manifest.json plugin.wasm
```
### Rust
```bash
# Build WebAssembly module
cargo build --release --target wasm32-unknown-unknown
# Package as .ndp
zip -j my-plugin.ndp manifest.json target/wasm32-unknown-unknown/release/plugin.wasm
```
### Python (with extism-py)
```bash
# Build WebAssembly module (requires extism-py installed)
extism-py plugin.wasm -o plugin.wasm *.py
# Package as .ndp
zip -j my-plugin.ndp manifest.json plugin.wasm
```
### Using XTP CLI (Scaffolding)
@ -625,8 +657,9 @@ xtp plugin init \
--path ./my-agent \
--name my-agent
# Build
# Build and package
cd my-agent && xtp plugin build
zip -j my-agent.ndp manifest.json dist/plugin.wasm
```
See [schemas/README.md](schemas/README.md) for available schemas.
@ -671,16 +704,18 @@ Plugins run in a secure WebAssembly sandbox:
## Runtime Management
### Auto-Reload (Development)
### Auto-Reload
With `AutoReload = true`, Navidrome watches the plugins folder and automatically reloads plugins when files change.
With `AutoReload = true`, Navidrome watches the plugins folder and automatically detects when `.ndp` files are added, modified, or removed. When a plugin file changes, the plugin is disabled and its metadata is re-read from the archive.
### Programmatic Control
If the `AutoReload` setting is disabled, Navidrome needs to be restarted to pick up plugin changes.
Plugins can be enabled/disabled via the Navidrome UI or API. The plugin state is persisted in the database.
### Enabling/Disabling Plugins
Plugins can be enabled/disabled via the Navidrome UI. The plugin state is persisted in the database.
### Important Notes
- **In-flight requests** When reloading, existing requests complete before the new version takes over
- **Config changes** Plugin configuration is read at load time; changes require a reload
- **Config changes** Changes to the plugin configuration in the UI are applied immediately
- **Cache persistence** The in-memory cache is cleared when a plugin is unloaded

View File

@ -23,18 +23,20 @@ This folder contains example plugins demonstrating various capabilities and lang
- **Python plugins:** [extism-py](https://github.com/extism/python-pdk)
- **Rust plugins:** [Rust](https://rustup.rs/) with `wasm32-unknown-unknown` target
### Build All (Go plugins)
### Build All Plugins
```bash
make all
```
This creates `.ndp` package files for each plugin.
### Build Individual Plugin
```bash
make minimal.wasm
make wikimedia.wasm
make discord-rich-presence.wasm
make minimal.ndp
make wikimedia.ndp
make discord-rich-presence.ndp
```
### Clean
@ -47,15 +49,15 @@ make clean
### With Extism CLI
Test any plugin without running Navidrome:
Test any plugin without running Navidrome. First extract the `.wasm` file from the `.ndp` package:
```bash
# Install: https://extism.org/docs/install
# Test manifest
extism call minimal.wasm nd_manifest --wasi
# Extract the wasm file from the package
unzip -p minimal.ndp plugin.wasm > minimal.wasm
# Test with input
# Test a capability function
extism call minimal.wasm nd_get_artist_biography --wasi \
--input '{"id":"1","name":"The Beatles"}'
```
@ -63,6 +65,7 @@ extism call minimal.wasm nd_get_artist_biography --wasi \
For plugins that make HTTP requests, allow the hosts:
```bash
unzip -p wikimedia.ndp plugin.wasm > wikimedia.wasm
extism call wikimedia.wasm nd_get_artist_biography --wasi \
--input '{"id":"1","name":"Yussef Dayes"}' \
--allow-host "query.wikidata.org" \
@ -71,7 +74,7 @@ extism call wikimedia.wasm nd_get_artist_biography --wasi \
### With Navidrome
1. Copy the `.wasm` file to your plugins folder
1. Copy the `.ndp` file to your plugins folder
2. Enable plugins in `navidrome.toml`:
```toml
[Plugins]
@ -92,8 +95,9 @@ Copy the [minimal](minimal/) example and modify:
```bash
cp -r minimal my-plugin
cd my-plugin
# Edit main.go
tinygo build -o my-plugin.wasm -target wasip1 -buildmode=c-shared .
# Edit main.go and manifest.json
tinygo build -o plugin.wasm -target wasip1 -buildmode=c-shared .
zip -j my-plugin.ndp manifest.json plugin.wasm
```
### Option 2: Bootstrap with XTP CLI
@ -108,6 +112,11 @@ xtp plugin init \
--template go \
--path ./my-plugin \
--name my-plugin
# Then create manifest.json and package
cd my-plugin
xtp plugin build
zip -j my-plugin.ndp manifest.json dist/plugin.wasm
```
Available schemas in [../schemas/](../schemas/):

View File

@ -23,18 +23,19 @@ A Python example plugin that fetches album cover images from the [Cover Art Arch
From the `plugins/examples` directory:
```bash
make coverartarchive-py.wasm
make coverartarchive-py.ndp
```
Or directly:
```bash
extism-py plugin/__init__.py -o coverartarchive-py.wasm
extism-py plugin/__init__.py -o plugin.wasm
zip -j coverartarchive-py.ndp manifest.json plugin.wasm
```
## Installation
1. Copy `coverartarchive-py.wasm` to your Navidrome plugins folder
1. Copy `coverartarchive-py.ndp` to your Navidrome plugins folder
2. Enable plugins in `navidrome.toml`:
```toml
@ -50,15 +51,10 @@ extism-py plugin/__init__.py -o coverartarchive-py.wasm
## Testing
Test the manifest:
```bash
extism call coverartarchive-py.wasm nd_manifest --wasi
```
Test album image retrieval (using Portishead's "Dummy" MBID):
Extract the wasm file and test:
```bash
unzip -p coverartarchive-py.ndp plugin.wasm > coverartarchive-py.wasm
extism call coverartarchive-py.wasm nd_get_album_images --wasi \
--input '{"name":"Dummy","artist":"Portishead","mbid":"76df3287-6cda-33eb-8e9a-044b5e15ffdd"}' \
--allow-host "coverartarchive.org" --allow-host "archive.org"

View File

@ -14,15 +14,11 @@ This is a WebSocket-based WASM plugin for Navidrome that displays real-time cryp
Configure in the Navidrome UI (Settings → Plugins → crypto-ticker):
```json
{
"tickers": "BTC,ETH,SOL,MATIC"
}
```
| Key | Description | Default |
|-----------|----------------------------------------------------------------------|-----------|
| `tickers` | Comma-separated list of cryptocurrency symbols (e.g., `BTC,ETH,SOL`) | `BTC,ETH` |
- `tickers` is a comma-separated list of cryptocurrency symbols
- The plugin will append `-USD` to any symbol without a trading pair specified
- Default: `BTC,ETH` if not configured
The plugin will append `-USD` to any symbol without a trading pair specified.
## How it Works
@ -40,19 +36,23 @@ This plugin was scaffolded using XTP CLI:
xtp plugin init --schema-file ../schemas/websocket_callback.yaml --template go --path ./crypto-ticker --name crypto-ticker
```
To build the plugin to WASM:
To build the plugin and package as `.ndp`:
```bash
# Using TinyGo (recommended - smaller binary)
tinygo build -o crypto-ticker.wasm -target wasip1 -buildmode=c-shared .
tinygo build -o plugin.wasm -target wasip1 -buildmode=c-shared .
zip -j crypto-ticker.ndp manifest.json plugin.wasm
```
# Or using standard Go
GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o crypto-ticker.wasm .
Or from the `plugins/examples/` directory:
```bash
make crypto-ticker.ndp
```
## Installation
Copy the resulting `crypto-ticker.wasm` to your Navidrome plugins folder.
Copy the resulting `crypto-ticker.ndp` to your Navidrome plugins folder.
## Example Output

View File

@ -63,34 +63,30 @@ To work within this model the plugin stores no in-memory state. Connections are
Configure in the Navidrome UI (Settings → Plugins → discord-rich-presence):
```json
{
"clientid": "123456789012345678",
"users": "alice:token123,bob:token456"
}
```
- `ClientID` is your Discord application ID
- `Users` is a comma-separated list of `username:token` pairs used for authorization
| Key | Description | Example |
|------------|----------------------------------------------------------|--------------------------------|
| `clientid` | Your Discord application ID | `123456789012345678` |
| `users` | Comma-separated list of `username:token` pairs | `alice:token123,bob:token456` |
## Building
From the `plugins/examples/` directory:
```sh
make discord-rich-presence.wasm
make discord-rich-presence.ndp
```
Or manually:
```sh
cd discord-rich-presence
tinygo build -target wasip1 -buildmode=c-shared -o ../discord-rich-presence.wasm .
tinygo build -target wasip1 -buildmode=c-shared -o plugin.wasm .
zip -j discord-rich-presence.ndp manifest.json plugin.wasm
```
## Installation
Place the resulting `discord-rich-presence.wasm` in your Navidrome plugins folder and enable plugins in your configuration:
Place the resulting `discord-rich-presence.ndp` in your Navidrome plugins folder and enable plugins in your configuration:
```toml
[Plugins]
@ -100,21 +96,21 @@ Folder = "/path/to/plugins"
## Files
| File | Description |
|------|-------------|
| `main.go` | Plugin entry point, manifest, scrobbler implementation |
| `rpc.go` | Discord gateway communication and RPC logic |
| `pdk.gen.go` | Generated types from XTP schemas (combined) |
| File | Description |
|----------------|---------------------------------------------------------|
| `main.go` | Plugin entry point, manifest, scrobbler implementation |
| `rpc.go` | Discord gateway communication and RPC logic |
| `pdk.gen.go` | Generated types from XTP schemas (combined) |
| `nd_host_*.go` | Host function wrappers (copied from `plugins/host/go/`) |
## Host Services Used
| Service | Purpose |
|---------|---------|
| Cache | Store Discord sequence numbers and processed image URLs |
| Service | Purpose |
|-----------|------------------------------------------------------------------|
| Cache | Store Discord sequence numbers and processed image URLs |
| Scheduler | Schedule heartbeats (recurring) and activity clearing (one-time) |
| WebSocket | Maintain persistent connection to Discord gateway |
| Artwork | Get track artwork URLs for rich presence display |
| WebSocket | Maintain persistent connection to Discord gateway |
| Artwork | Get track artwork URLs for rich presence display |
## Implementation Details

View File

@ -23,19 +23,20 @@ rustup target add wasm32-wasip1
# Build the plugin
cargo build --target wasm32-wasip1 --release
# The output will be at target/wasm32-wasip1/release/library_inspector.wasm
# Package as .ndp
zip -j library-inspector.ndp manifest.json target/wasm32-wasip1/release/library_inspector.wasm
```
Or use the provided Makefile from the examples directory:
```bash
cd plugins/examples
make library-inspector.wasm
make library-inspector.ndp
```
## Installation
1. Copy the `.wasm` file to your Navidrome plugins folder
1. Copy the `.ndp` file to your Navidrome plugins folder
2. Enable plugins in your Navidrome configuration:
```toml
@ -50,11 +51,9 @@ Folder = "/path/to/plugins"
Configure the inspection interval in the Navidrome UI (Settings → Plugins → library-inspector):
```json
{
"cron": "@every 5m"
}
```
| Key | Description | Default |
|--------|------------------------------------------|--------------|
| `cron` | Cron expression for inspection interval | `@every 1m` |
### Cron Expression Examples

View File

@ -8,12 +8,19 @@ This is a minimal example demonstrating how to create a Navidrome plugin using G
2. Build the plugin:
```bash
go mod tidy
tinygo build -o minimal.wasm -target wasip1 -buildmode=c-shared ./main.go
tinygo build -o plugin.wasm -target wasip1 -buildmode=c-shared ./main.go
zip -j minimal.ndp manifest.json plugin.wasm
```
Or using the examples Makefile:
```bash
cd plugins/examples
make minimal.ndp
```
## Installing
Copy `minimal.wasm` to your Navidrome plugins folder (default: `<data-folder>/plugins/`).
Copy `minimal.ndp` to your Navidrome plugins folder (default: `<data-folder>/plugins/`).
## Configuration
@ -29,7 +36,7 @@ Agents = "lastfm,spotify,minimal"
## What This Example Demonstrates
- Exporting the required `nd_manifest` function
- Plugin package structure (`.ndp` = zip with `manifest.json` + `plugin.wasm`)
- Implementing `nd_get_artist_biography` as a MetadataAgent capability
- Basic JSON input/output handling with the Extism PDK

View File

@ -23,18 +23,19 @@ A Python example plugin that demonstrates the **Scheduler** and **SubsonicAPI**
From the `plugins/examples` directory:
```bash
make nowplaying-py.wasm
make nowplaying-py.ndp
```
Or directly:
```bash
extism-py plugin/__init__.py -o nowplaying-py.wasm
extism-py plugin/__init__.py -o plugin.wasm
zip -j nowplaying-py.ndp manifest.json plugin.wasm
```
## Installation
1. Copy `nowplaying-py.wasm` to your Navidrome plugins folder
1. Copy `nowplaying-py.ndp` to your Navidrome plugins folder
2. Enable plugins in `navidrome.toml`:
```toml
@ -43,20 +44,14 @@ extism-py plugin/__init__.py -o nowplaying-py.wasm
Folder = "/path/to/plugins"
```
3. Configure the plugin in the UI (Settings → Plugins → nowplaying-py):
```json
{
"cron": "*/1 * * * *",
"user": "admin"
}
```
3. Configure the plugin in the UI (Settings → Plugins → nowplaying-py)
### Configuration Options
## Configuration
| Key | Description | Default |
|--------|-------------------------------------|------------------------------|
| `cron` | Cron expression for check frequency | `*/1 * * * *` (every minute) |
| `user` | Navidrome user for SubsonicAPI | `admin` |
| Key | Description | Default |
|--------|-------------------------------------|---------------|
| `cron` | Cron expression for check frequency | `*/1 * * * *` |
| `user` | Navidrome user for SubsonicAPI | `admin` |
## Testing

View File

@ -20,7 +20,7 @@ A Navidrome plugin written in Rust that sends HTTP webhook notifications when tr
From the `plugins/examples` directory:
```bash
make webhook-rs.wasm
make webhook-rs.ndp
```
Or build directly with cargo:
@ -28,28 +28,20 @@ Or build directly with cargo:
```bash
cd webhook-rs
cargo build --release
cp target/wasm32-unknown-unknown/release/webhook_rs.wasm ../webhook-rs.wasm
zip -j webhook-rs.ndp manifest.json target/wasm32-unknown-unknown/release/webhook_rs.wasm
```
## Installation
Copy `webhook-rs.wasm` to your Navidrome plugins folder (configured via `Plugins.Folder` in your config).
Copy `webhook-rs.ndp` to your Navidrome plugins folder (configured via `Plugins.Folder` in your config).
## Configuration
Configure in the Navidrome UI (Settings → Plugins → webhook-rs):
```json
{
"urls": "https://example.com/webhook,https://another.example.com/notify"
}
```
### Configuration Options
| Key | Description | Example |
|--------|--------------------------------------|---------------------------------------------------------|
| `urls` | Comma-separated list of webhook URLs | `"https://example.com/hook1,https://example.com/hook2"` |
| Key | Description | Example |
|--------|--------------------------------------|-----------------------------------------------------------|
| `urls` | Comma-separated list of webhook URLs | `https://example.com/hook1,https://example.com/hook2` |
## Webhook Request Format

View File

@ -22,16 +22,11 @@ xtp plugin init \
## Building
### Using XTP CLI (recommended)
```bash
xtp plugin build
```
### Using TinyGo
```bash
tinygo build -target wasip1 -buildmode=c-shared -o dist/plugin.wasm .
tinygo build -target wasip1 -buildmode=c-shared -o plugin.wasm .
zip -j wikimedia.ndp manifest.json plugin.wasm
```
### Using the Makefile
@ -39,15 +34,22 @@ tinygo build -target wasip1 -buildmode=c-shared -o dist/plugin.wasm .
From the `plugins/examples` directory:
```bash
make wikimedia.wasm
make wikimedia.ndp
```
### Using XTP CLI
```bash
xtp plugin build
zip -j wikimedia.ndp manifest.json dist/plugin.wasm
```
## Installation
Copy the `.wasm` file to your Navidrome plugins folder:
Copy the `.ndp` file to your Navidrome plugins folder:
```bash
cp dist/plugin.wasm /path/to/navidrome/plugins/wikimedia.wasm
cp wikimedia.ndp /path/to/navidrome/plugins/
```
Then enable plugins in your `navidrome.toml`:
@ -73,23 +75,13 @@ brew install extism/tap/extism # macOS
# or see https://extism.org/docs/install for other platforms
```
Run these commands from the `plugins/examples` directory.
### Test the manifest
Extract the wasm file from the package and test:
```bash
extism call wikimedia.wasm nd_manifest --wasi
```
# Extract wasm from package
unzip -p wikimedia.ndp plugin.wasm > wikimedia.wasm
Expected output:
```json
{"name":"Wikimedia","author":"Navidrome","version":"1.0.0","description":"Fetches artist metadata from Wikidata, DBpedia and Wikipedia","website":"https://navidrome.org","permissions":{"http":{"reason":"Fetch metadata from Wikimedia APIs","allowedHosts":["query.wikidata.org","dbpedia.org","en.wikipedia.org"]}}}
```
### Test artist URL lookup
```bash
# With MBID (The Beatles)
# Test artist URL lookup with MBID (The Beatles)
extism call wikimedia.wasm nd_get_artist_url --wasi \
--input '{"id":"1","name":"The Beatles","mbid":"b10bbbfc-cf9e-42e0-be17-e2c3e1d2600d"}' \
--allow-host "query.wikidata.org"