modularize docs
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
# Plugin Docs
|
||||
|
||||
These docs are split from `plugins/PLUGINS.md` so each plugin area has a smaller reference.
|
||||
|
||||
| File | Covers |
|
||||
|------|--------|
|
||||
| [bundle-format.md](bundle-format.md) | Bundle layout, manifest fields, nav entries, TypeScript entrypoints. |
|
||||
| [server-side-plugins.md](server-side-plugins.md) | Server-only plugin runtime, RPC/SSE, persistent data, build hooks, build UI settings/actions. |
|
||||
| [wasm-plugins.md](wasm-plugins.md) | Plugin 2.0 WASM runtime and agent-side host bridges. |
|
||||
| [install-and-manage.md](install-and-manage.md) | Uploading, opening, enabling, auto-load, Plugin Manager behavior. |
|
||||
| [samples.md](samples.md) | Included sample plugins and what each demonstrates. |
|
||||
| [legacy-native-plugins.md](legacy-native-plugins.md) | Legacy native plugins, direct OS/API use, native ABI, build scripts, static Linux shim. |
|
||||
| [signing.md](signing.md) | Ed25519 plugin signing and trusted key management. |
|
||||
@@ -0,0 +1,89 @@
|
||||
# Bundle Format
|
||||
|
||||
Plugin bundles are zip files named after the plugin ID. `config.json` must be at the zip root.
|
||||
|
||||
## Plugin 2.0 Layout
|
||||
|
||||
```text
|
||||
<pluginId>.zip
|
||||
config.json
|
||||
<pluginId>.html
|
||||
<pluginId>.css
|
||||
<pluginId>.js optional when src/ui.ts exists
|
||||
<pluginId>.wasm optional agent-side WASM module
|
||||
server.js optional server runtime
|
||||
src/
|
||||
ui.ts compiled to assets/<pluginId>.js
|
||||
server.ts compiled to server.js
|
||||
shared.ts optional local module
|
||||
```
|
||||
|
||||
After extraction the server stores assets under `Overlord-Server/plugins/<pluginId>/assets/`, generated metadata in `manifest.json`, optional source in `src/`, and persistent plugin files in `data/`.
|
||||
|
||||
## Runtime Selection
|
||||
|
||||
```json
|
||||
{
|
||||
"apiVersion": 2,
|
||||
"runtime": "wasm",
|
||||
"wasm": "sample-wasm.wasm"
|
||||
}
|
||||
```
|
||||
|
||||
Use:
|
||||
|
||||
| Runtime | Use case |
|
||||
|---------|----------|
|
||||
| `server` | Server-only extension, build plugin, dashboard plugin, RPC-backed plugin. |
|
||||
| `wasm` | Sandboxed agent-side Plugin 2.0 module. |
|
||||
| `native` or omitted v1 fields | Legacy shared-library agent plugin. |
|
||||
|
||||
Server-only plugins are not sent to clients and do not use auto-load.
|
||||
|
||||
## Manifest Fields
|
||||
|
||||
`config.json` is merged into the generated `manifest.json`.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "sample",
|
||||
"name": "Sample Plugin",
|
||||
"apiVersion": 2,
|
||||
"runtime": "server",
|
||||
"version": "1.0.0",
|
||||
"description": "An example plugin",
|
||||
"entry": "sample.html",
|
||||
"assets": {
|
||||
"html": "sample.html",
|
||||
"css": "sample.css",
|
||||
"js": "sample.js"
|
||||
},
|
||||
"navbar": {
|
||||
"label": "Sample",
|
||||
"icon": "fa-cube"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`navbar.icon` accepts a Font Awesome 6 solid icon class such as `fa-cube`, `fa-key`, or `fa-network-wired`. If the plugin is enabled, navbar plugins appear in the Plugin Apps group and open at `/plugins/<id>`.
|
||||
|
||||
## TypeScript UI And Server Logic
|
||||
|
||||
Plugin bundles can ship source-oriented browser and server logic. The server compiles these during extraction:
|
||||
|
||||
```json
|
||||
{
|
||||
"apiVersion": 2,
|
||||
"uiEntry": "src/browser/main.ts",
|
||||
"serverEntry": "src/backend/index.ts"
|
||||
}
|
||||
```
|
||||
|
||||
Defaults:
|
||||
|
||||
| Source file | Output |
|
||||
|-------------|--------|
|
||||
| `src/ui.ts` | `assets/<pluginId>.js` |
|
||||
| `src/server.ts` | `server.js` |
|
||||
|
||||
Only files under `src/` are copied for compilation. Extraction does not run `npm install`, download packages, or execute build scripts. Use relative imports and APIs already available in the browser or server plugin runtime.
|
||||
@@ -0,0 +1,71 @@
|
||||
# Install And Manage Plugins
|
||||
|
||||
## Install Or Upload
|
||||
|
||||
Install a plugin by uploading its zip from the Plugins page or placing the zip under `Overlord-Server/plugins`. The server extracts the bundle, generates `manifest.json`, and copies browser assets into the plugin assets directory.
|
||||
|
||||
`config.json` must be in the root of the zip. It is merged into the generated manifest on extraction.
|
||||
|
||||
## Open Plugin UI
|
||||
|
||||
Plugin pages are served at:
|
||||
|
||||
```text
|
||||
/plugins/<pluginId>
|
||||
```
|
||||
|
||||
Per-client plugins can also receive a client context through:
|
||||
|
||||
```text
|
||||
/plugins/<pluginId>?clientId=<clientId>
|
||||
```
|
||||
|
||||
Navbar plugins appear in the Plugin Apps group when enabled and configured with:
|
||||
|
||||
```json
|
||||
{
|
||||
"navbar": {
|
||||
"label": "My Plugin",
|
||||
"icon": "fa-cube"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Plugin Manager Behavior
|
||||
|
||||
| Plugin kind | Manager behavior |
|
||||
|-------------|------------------|
|
||||
| Server-only (`runtime: "server"`) | Shown as a server extension. Auto-load controls are hidden. It is not sent to clients. |
|
||||
| WASM (`runtime: "wasm"`) | Shows declared needs and approval state. Can be loaded on compatible clients after needs approval. |
|
||||
| Legacy native | Shows native binary/runtime compatibility. Requires matching OS/architecture binary. |
|
||||
|
||||
## Auto-Load
|
||||
|
||||
Auto-load applies to client-side plugins only. It sends enabled client plugins to newly connected clients and can optionally dispatch startup events.
|
||||
|
||||
Server-only plugins do not use auto-load because their code runs on the server rather than inside an agent.
|
||||
|
||||
Auto-load notes:
|
||||
|
||||
| Rule | Detail |
|
||||
|------|--------|
|
||||
| Enabled flag | Disabled plugins are never auto-loaded. |
|
||||
| WASM needs | WASM plugins with unapproved needs are blocked. |
|
||||
| Persistence | Auto-load state is saved in `.plugin-state.json`. |
|
||||
| Platform matching | WASM is universal; legacy native requires a matching binary. |
|
||||
|
||||
## Useful APIs
|
||||
|
||||
| Endpoint | Purpose |
|
||||
|----------|---------|
|
||||
| `GET /api/plugins` | List installed plugins, runtime, needs, enabled state, server state, and auto-load state. |
|
||||
| `POST /api/plugins/<id>/autoload` | Configure auto-load for client-side plugins. Server-only plugins reject this. |
|
||||
| `POST /api/plugins/<id>/rpc` | Call a server-side plugin RPC method. |
|
||||
| `GET /api/plugins/<id>/stream` | Subscribe to server-side plugin broadcasts. |
|
||||
| `GET /api/build/plugins` | List enabled build plugins and their Build page settings/actions. |
|
||||
|
||||
## Per-User Plugin Access
|
||||
|
||||
Admins can limit which plugins a user can use from the Users page through Plugin Access. The policy supports no access, selected plugins, or all plugins.
|
||||
|
||||
That same policy applies to normal plugin pages, server-side plugin RPC/SSE, client-side plugin loading, and build-hook plugins. A user must also have the relevant base permission, such as `clients:build`, before plugin access matters.
|
||||
@@ -0,0 +1,282 @@
|
||||
# Legacy Native Plugins
|
||||
|
||||
Legacy native v1 plugins run as shared libraries inside the agent. They are still supported and still useful when you need capabilities that the WASM runtime intentionally does not expose.
|
||||
|
||||
Use legacy native plugins when you need direct OS APIs, native libraries, custom process/thread behavior, platform-specific integrations, or code that cannot reasonably be compiled to the Plugin 2.0 WASM ABI.
|
||||
|
||||
Native plugins run with the same privileges as the agent. They are not sandboxed.
|
||||
|
||||
## What Legacy Plugins Can Do
|
||||
|
||||
| Capability | Notes |
|
||||
|------------|-------|
|
||||
| Call native OS APIs | Win32, POSIX, Objective-C/C APIs, platform SDKs, and local native libraries. |
|
||||
| Use language runtimes | C, C++, Go, Rust, and other languages that can export a C ABI. |
|
||||
| Run platform-specific code | Package separate binaries per OS/architecture. |
|
||||
| Emit plugin events | Send events back to the UI/server through the host callback. |
|
||||
| Receive UI/server commands | Handle `PluginOnEvent` calls from plugin UI actions or server-side routing. |
|
||||
|
||||
WASM Plugin 2.0 is preferred for sandboxed, portable agent modules. Legacy native plugins are the escape hatch for privileged native behavior.
|
||||
|
||||
## Bundle Layout
|
||||
|
||||
A legacy bundle is a zip with web assets plus one or more platform binaries:
|
||||
|
||||
```text
|
||||
<pluginId>.zip
|
||||
<pluginId>.html
|
||||
<pluginId>.css
|
||||
<pluginId>.js
|
||||
<pluginId>-windows-amd64.dll
|
||||
<pluginId>-linux-amd64.so
|
||||
<pluginId>-darwin-arm64.dylib
|
||||
```
|
||||
|
||||
The browser assets provide the plugin UI. The native binary is sent to a matching client when the plugin is loaded for that client.
|
||||
|
||||
## Binary Naming
|
||||
|
||||
Binary filenames must follow:
|
||||
|
||||
```text
|
||||
<pluginId>-<os>-<arch>.<ext>
|
||||
```
|
||||
|
||||
| OS | Arch | Example |
|
||||
|----|------|---------|
|
||||
| `windows` | `amd64` | `sample-windows-amd64.dll` |
|
||||
| `windows` | `arm64` | `sample-windows-arm64.dll` |
|
||||
| `linux` | `amd64` | `sample-linux-amd64.so` |
|
||||
| `linux` | `arm64` | `sample-linux-arm64.so` |
|
||||
| `darwin` | `amd64` | `sample-darwin-amd64.dylib` |
|
||||
| `darwin` | `arm64` | `sample-darwin-arm64.dylib` |
|
||||
|
||||
The server never sends an x64 native binary to an ARM client, or the reverse. If no binary matches the target client, loading fails for that client.
|
||||
|
||||
## Required ABI
|
||||
|
||||
Every native plugin exports C-callable functions. The ABI differs slightly between Windows and Unix-like systems.
|
||||
|
||||
| Export | Windows signature | Linux/macOS signature | Required |
|
||||
|--------|-------------------|-----------------------|----------|
|
||||
| `PluginOnLoad` | `int PluginOnLoad(char* hostInfo, int hostInfoLen, uint64 callback)` | `int PluginOnLoad(char* hostInfo, int hostInfoLen, uintptr callback, uintptr ctx)` | Yes |
|
||||
| `PluginOnEvent` | `int PluginOnEvent(char* event, int eventLen, char* payload, int payloadLen)` | Same | Yes |
|
||||
| `PluginOnUnload` | `void PluginOnUnload()` | Same | Yes |
|
||||
| `PluginSetCallback` | `void PluginSetCallback(uint64 callback)` | Not used | Windows only |
|
||||
| `PluginGetRuntime` | `const char* PluginGetRuntime()` | Same | Recommended |
|
||||
|
||||
Return `0` from `PluginOnLoad` and `PluginOnEvent` for success. Return a non-zero value when the plugin cannot initialize or handle the event.
|
||||
|
||||
## HostInfo JSON
|
||||
|
||||
`PluginOnLoad` receives host metadata as a JSON byte buffer:
|
||||
|
||||
```json
|
||||
{
|
||||
"clientId": "abc123",
|
||||
"os": "windows",
|
||||
"arch": "amd64",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
```
|
||||
|
||||
Use this to branch by client OS, architecture, client ID, or agent version.
|
||||
|
||||
## Host Callback
|
||||
|
||||
The host callback lets a native plugin emit events back to the agent/server.
|
||||
|
||||
Windows uses `__stdcall`:
|
||||
|
||||
```c
|
||||
void __stdcall callback(const char *event, uintptr_t eventLen,
|
||||
const char *payload, uintptr_t payloadLen);
|
||||
```
|
||||
|
||||
Linux/macOS use a normal C function plus the opaque context pointer received by `PluginOnLoad`:
|
||||
|
||||
```c
|
||||
void callback(uintptr_t ctx,
|
||||
const char *event, int eventLen,
|
||||
const char *payload, int payloadLen);
|
||||
```
|
||||
|
||||
The event name and payload are byte buffers. JSON payloads are conventional, but the ABI only requires bytes plus lengths.
|
||||
|
||||
## Minimal C Skeleton
|
||||
|
||||
```c
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define EXPORT __declspec(dllexport)
|
||||
typedef void (__stdcall *host_callback_t)(
|
||||
const char *event, uintptr_t eventLen,
|
||||
const char *payload, uintptr_t payloadLen);
|
||||
static host_callback_t g_callback = 0;
|
||||
#else
|
||||
#define EXPORT __attribute__((visibility("default")))
|
||||
typedef void (*host_callback_t)(
|
||||
uintptr_t ctx,
|
||||
const char *event, int eventLen,
|
||||
const char *payload, int payloadLen);
|
||||
static host_callback_t g_callback = 0;
|
||||
static uintptr_t g_ctx = 0;
|
||||
#endif
|
||||
|
||||
EXPORT const char *PluginGetRuntime(void) {
|
||||
return "c";
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
EXPORT void PluginSetCallback(uint64_t callback) {
|
||||
g_callback = (host_callback_t)(uintptr_t)callback;
|
||||
}
|
||||
|
||||
EXPORT int PluginOnLoad(const char *hostInfo, int hostInfoLen, uint64_t callback) {
|
||||
g_callback = (host_callback_t)(uintptr_t)callback;
|
||||
if (g_callback) g_callback("ready", 5, "{\"ok\":true}", 11);
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
EXPORT int PluginOnLoad(const char *hostInfo, int hostInfoLen, uintptr_t callback, uintptr_t ctx) {
|
||||
g_callback = (host_callback_t)callback;
|
||||
g_ctx = ctx;
|
||||
if (g_callback) g_callback(g_ctx, "ready", 5, "{\"ok\":true}", 11);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
EXPORT int PluginOnEvent(const char *event, int eventLen, const char *payload, int payloadLen) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT void PluginOnUnload(void) {
|
||||
}
|
||||
```
|
||||
|
||||
## Runtime Detection
|
||||
|
||||
`PluginGetRuntime` should return a pointer to a static, null-terminated string.
|
||||
|
||||
| Return value | Meaning |
|
||||
|--------------|---------|
|
||||
| `c` | C plugin; fully unloadable. |
|
||||
| `cpp` | C++ plugin; fully unloadable. |
|
||||
| `rust` | Rust plugin; fully unloadable when written without unload-hostile globals. |
|
||||
| `go` or absent | Go runtime; intentionally not unloaded. |
|
||||
|
||||
Go shared libraries cannot be fully unloaded safely because the Go runtime owns threads and process-global state. The host treats Go plugins as non-freeable to avoid crashes.
|
||||
|
||||
## Loading Model
|
||||
|
||||
| Platform | Loading behavior |
|
||||
|----------|------------------|
|
||||
| Windows | DLLs are loaded from memory through the custom PE loader. |
|
||||
| Linux | Shared libraries are loaded through the static-agent subprocess shim when needed. |
|
||||
| macOS | Dynamic libraries are loaded through the platform loader. |
|
||||
|
||||
Because Windows uses an in-memory PE loader, some runtime primitives that assume normal `LoadLibrary` initialization can be fragile. The Rust sample avoids `std::sync::Mutex` for this reason and uses C-style globals; the host serializes plugin calls.
|
||||
|
||||
## Static Linux Agent Shim
|
||||
|
||||
Linux agents are compiled as static musl binaries for portability. Static binaries cannot directly call `dlopen`, so Linux native plugin support uses an embedded plugin-host shim.
|
||||
|
||||
When loading a Linux native plugin, the agent:
|
||||
|
||||
1. Writes the plugin `.so` to an anonymous in-memory file.
|
||||
2. Writes the embedded shim to another in-memory file.
|
||||
3. Creates a bidirectional socket.
|
||||
4. Starts the shim and passes file descriptors to it.
|
||||
5. The shim loads the `.so` and forwards events over the socket.
|
||||
|
||||
Plugin authors normally do not need shim-specific code. Export the normal v1 ABI and compile the `.so` for the target libc/architecture.
|
||||
|
||||
## IPC Protocol Notes
|
||||
|
||||
The agent and Linux shim use a length-prefixed binary protocol:
|
||||
|
||||
```text
|
||||
[4-byte little-endian total payload length][1-byte message type][payload bytes]
|
||||
```
|
||||
|
||||
Important message types:
|
||||
|
||||
| Direction | Type | Meaning |
|
||||
|-----------|------|---------|
|
||||
| agent to shim | `0x01` | Load plugin with HostInfo. |
|
||||
| agent to shim | `0x02` | Deliver plugin event. |
|
||||
| agent to shim | `0x03` | Unload plugin. |
|
||||
| shim to agent | `0x10` | Plugin callback event. |
|
||||
| shim to agent | `0x11` | Ready/runtime string. |
|
||||
| shim to agent | `0x12` | Error string. |
|
||||
| shim to agent | `0x13` | Load result. |
|
||||
|
||||
Most plugin authors can ignore this, but it is useful when debugging Linux native plugin load failures.
|
||||
|
||||
## Build Scripts
|
||||
|
||||
Legacy samples include `build.bat` and `build.sh`. They compile native binaries and zip them with web assets.
|
||||
|
||||
Default usage:
|
||||
|
||||
```bash
|
||||
cd plugins/sample-c
|
||||
./build.sh
|
||||
```
|
||||
|
||||
Multiple targets:
|
||||
|
||||
```bash
|
||||
BUILD_TARGETS="linux-amd64 linux-arm64 windows-amd64 darwin-arm64" ./build.sh
|
||||
```
|
||||
|
||||
Windows:
|
||||
|
||||
```bat
|
||||
cd plugins\sample-c
|
||||
build.bat
|
||||
```
|
||||
|
||||
The scripts support `BUILD_TARGETS` and usually accept an optional plugin directory argument.
|
||||
|
||||
## Cross-Compilation
|
||||
|
||||
Install the compiler matching the target platform, or set `CC` for C/C++ samples.
|
||||
|
||||
Common compiler names used by the sample scripts:
|
||||
|
||||
| Target | Compiler |
|
||||
|--------|----------|
|
||||
| `linux-amd64` | `x86_64-linux-gnu-gcc` |
|
||||
| `linux-arm64` | `aarch64-linux-gnu-gcc` |
|
||||
| `linux-arm` | `arm-linux-gnueabihf-gcc` |
|
||||
| `windows-amd64` | `x86_64-w64-mingw32-gcc` |
|
||||
| `windows-arm64` | `aarch64-w64-mingw32-gcc` |
|
||||
| `darwin-amd64` | `x86_64-apple-darwin-gcc` |
|
||||
| `darwin-arm64` | `aarch64-apple-darwin-gcc` |
|
||||
|
||||
Rust samples use Cargo targets. Go samples use `go build -buildmode=c-shared` with CGO enabled.
|
||||
|
||||
## Language Notes
|
||||
|
||||
| Sample | Notes |
|
||||
|--------|-------|
|
||||
| `sample-c` | Smallest ABI surface, no runtime, fully unloadable. |
|
||||
| `sample-cpp` | C++ convenience with exported C ABI. Return `cpp` from `PluginGetRuntime`. |
|
||||
| `sample-rust` | Exports a C ABI. Avoid assumptions that require normal OS loader initialization. |
|
||||
| `sample-go` | Uses `-buildmode=c-shared`; treated as non-freeable because the Go runtime cannot be safely unloaded. |
|
||||
|
||||
## UI Event Flow
|
||||
|
||||
The browser UI sends events through the normal plugin event endpoint. The server forwards them to the client. The agent calls:
|
||||
|
||||
```text
|
||||
PluginOnEvent(event, eventLen, payload, payloadLen)
|
||||
```
|
||||
|
||||
The native plugin can reply by invoking the host callback. Those callback events are delivered back to the server/UI as plugin events.
|
||||
|
||||
## Security Notes
|
||||
|
||||
Legacy native plugins are trusted executable code. They can call OS APIs, read/write files available to the agent process, spawn subprocesses, open sockets, and interact with native libraries. Use plugin signing, per-user plugin access, and code review before deploying third-party native plugins.
|
||||
@@ -0,0 +1,17 @@
|
||||
# Sample Plugins
|
||||
|
||||
The `plugins/` directory includes small examples that exercise each plugin style.
|
||||
|
||||
| Directory | Demonstrates |
|
||||
|-----------|--------------|
|
||||
| `sample-build-hooks` | Server-only build plugin, custom Build page action, build settings, and replacing the produced artifact with a `.txt` file. |
|
||||
| `sample-ts-fullstack` | TypeScript UI and TypeScript server runtime with shared local modules. |
|
||||
| `sample-wasm` | Plugin 2.0 WASM module built from C/WASI. |
|
||||
| `sample-wasm-hostinfo` | TinyGo WASM plugin that queries HostInfo. |
|
||||
| `sample-wasm-platform-note` | Rust WASM plugin that branches by HostInfo/platform. |
|
||||
| `sample-c` | Legacy native C plugin for the smallest unloadable ABI surface. |
|
||||
| `sample-cpp` | Legacy native C++ plugin using exported C ABI functions. |
|
||||
| `sample-go` | Legacy native Go plugin. Go plugins are intentionally not unloaded because the Go runtime cannot be safely unloaded from shared libraries. |
|
||||
| `sample-rust` | Legacy native Rust plugin with C ABI exports and unload-friendly runtime handling. |
|
||||
|
||||
Use `sample-build-hooks` as the starting point for uploaders, artifact publishers, or custom release buttons.
|
||||
@@ -0,0 +1,305 @@
|
||||
# Server-Side Plugins
|
||||
|
||||
Server-side plugins run inside the Overlord server process environment. They are for global plugin pages, persistent server data, RPC-backed tools, build pipeline customization, uploads, artifact replacement, signing, or internal release workflows.
|
||||
|
||||
They are trusted code. A server-side plugin has normal server filesystem and network access, so treat it like part of the program itself. Use plugin signing and only install code you trust.
|
||||
|
||||
## Server-Only Plugins
|
||||
|
||||
Set `runtime: "server"` in `config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Internal Builder",
|
||||
"version": "1.0.0",
|
||||
"apiVersion": 2,
|
||||
"runtime": "server",
|
||||
"description": "Adds custom build buttons and upload hooks."
|
||||
}
|
||||
```
|
||||
|
||||
Server-only plugins:
|
||||
|
||||
| Behavior | Notes |
|
||||
|----------|-------|
|
||||
| Plugin Manager look | Shown as server extensions, with worker/build-plugin state where available. |
|
||||
| Client loading | Never sent to agents. |
|
||||
| Auto-load | Hidden and unavailable because there is no per-client agent module. |
|
||||
| Build access | Can hook the builder when `server.js` or `src/server.ts` exports build handlers. |
|
||||
|
||||
## Lifecycle
|
||||
|
||||
If a bundle includes `server.js` or compiled `src/server.ts`, the server starts one worker for the plugin when the plugin is enabled.
|
||||
|
||||
```js
|
||||
export default {
|
||||
async setup(ctx) {
|
||||
ctx.log.info("server plugin started");
|
||||
},
|
||||
|
||||
async onEvent(ctx, event) {
|
||||
ctx.log.info(`plugin event from ${event.clientId}`);
|
||||
},
|
||||
|
||||
rpc: {
|
||||
async list(ctx, params) {
|
||||
return ctx.db.query("select * from items").all();
|
||||
},
|
||||
},
|
||||
|
||||
async teardown(ctx) {
|
||||
ctx.log.info("server plugin stopping");
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## The `ctx` Object
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `ctx.pluginId` | Sanitized plugin ID. |
|
||||
| `ctx.db` | `bun:sqlite` database backed by `plugins/<id>/data/plugin.db`; WAL mode is enabled. |
|
||||
| `ctx.dataDir` | Absolute path to the plugin's persistent `data/` directory. |
|
||||
| `ctx.log` | `{ debug, info, warn, error }` forwarded to the server logger as `[plugin:<id>] ...`. |
|
||||
| `ctx.broadcast(channel, data)` | Sends JSON-serializable data to every UI subscribed to this plugin's SSE stream. |
|
||||
|
||||
## Persistent Data
|
||||
|
||||
Each plugin owns:
|
||||
|
||||
```text
|
||||
Overlord-Server/plugins/<id>/data/
|
||||
```
|
||||
|
||||
The directory is created automatically and is not removed when a plugin is reinstalled or deleted. Use it for SQLite state, cached files, generated artifacts, helper binaries, or plugin-local configuration.
|
||||
|
||||
The server also exposes plugin data APIs for listing, reading, writing, deleting, and running files in that directory. Execution endpoints are restricted to admin/operator roles.
|
||||
|
||||
## UI RPC And Broadcasts
|
||||
|
||||
Browser UI code can call plugin RPC methods:
|
||||
|
||||
```js
|
||||
const res = await fetch(`/api/plugins/my-plugin/rpc`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ method: "list", params: {} }),
|
||||
});
|
||||
const { ok, result, error } = await res.json();
|
||||
```
|
||||
|
||||
Subscribe to server broadcasts:
|
||||
|
||||
```js
|
||||
const stream = new EventSource(`/api/plugins/my-plugin/stream`);
|
||||
stream.addEventListener("changed", (event) => {
|
||||
const payload = JSON.parse(event.data);
|
||||
console.log(payload);
|
||||
});
|
||||
```
|
||||
|
||||
RPC calls require `clients:control`, time out after 30 seconds, and must target a method exported in the plugin's `rpc` map. SSE streams also require `clients:control`.
|
||||
|
||||
## Build Hooks
|
||||
|
||||
Server-side plugins can hook the agent builder without shipping agent code. Browser UI is never called from the build process; only `server.js` or compiled `src/server.ts` participates.
|
||||
|
||||
```js
|
||||
export default {
|
||||
onBuildPrepare(ctx, payload) {
|
||||
return { config: { outputName: "custom-agent" } };
|
||||
},
|
||||
|
||||
onBuildTarget(ctx, payload) {
|
||||
if (payload.platform === "linux-amd64") {
|
||||
return {
|
||||
env: { CGO_ENABLED: "0" },
|
||||
addTags: ["my_feature"],
|
||||
ldflagsAppend: "-X overlord-client/cmd/agent/config.PluginChannel=stable",
|
||||
message: "linux-amd64 template patched",
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
buildHooks: {
|
||||
before_donut(ctx, payload) {
|
||||
ctx.log.info(`Donut input: ${payload.file.filename}`);
|
||||
},
|
||||
after_donut(ctx, payload) {
|
||||
ctx.log.info(`Donut output: ${payload.file.filename}`);
|
||||
},
|
||||
before_sgn(ctx, payload) {
|
||||
ctx.log.info(`SGN iterations: ${payload.iterations}`);
|
||||
},
|
||||
after_sgn(ctx, payload) {
|
||||
ctx.log.info(`SGN output: ${payload.file.filename}`);
|
||||
},
|
||||
},
|
||||
|
||||
async onBuildArtifact(ctx, payload) {
|
||||
ctx.log.info(`artifact ready: ${payload.file.path}`);
|
||||
return { message: `saw ${payload.file.filename}` };
|
||||
},
|
||||
|
||||
async onBuildComplete(ctx, payload) {
|
||||
ctx.log.info(`build ${payload.buildId} produced ${payload.files.length} files`);
|
||||
},
|
||||
|
||||
onBuildFailed(ctx, payload) {
|
||||
ctx.log.warn(`build failed: ${payload.error}`);
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
`onBuildTarget` can return:
|
||||
|
||||
| Field | Effect |
|
||||
|-------|--------|
|
||||
| `env` | Merges string values into the Go build environment. |
|
||||
| `ldflags` | Replaces the full `-ldflags` string. |
|
||||
| `ldflagsAppend` | Appends to the existing `-ldflags` string. |
|
||||
| `tags` | Replaces the full build tag list. |
|
||||
| `addTags` / `removeTags` | Adds or removes build tags. |
|
||||
| `outputName` | Renames the target output file after server sanitization. |
|
||||
| `skip: true` | Skips that target. |
|
||||
| `message` / `messages` | Writes informational lines into the build stream. |
|
||||
|
||||
## Transform Hook Stages
|
||||
|
||||
Implement transform hooks either in `buildHooks` with the snake-case name, or as a named method such as `onBuildBeforeDonut`.
|
||||
|
||||
| Hook | Named method | When it runs |
|
||||
|------|--------------|--------------|
|
||||
| `post_build` | `onBuildPostBuild` | After the raw Go build output exists. |
|
||||
| `before_upx` / `after_upx` | `onBuildBeforeUpx` / `onBuildAfterUpx` | Around UPX compression. |
|
||||
| `before_script_wrapper` / `after_script_wrapper` | `onBuildBeforeScriptWrapper` / `onBuildAfterScriptWrapper` | Around `.bat`, `.cmd`, and `.ps1` wrapping. |
|
||||
| `before_ipa` / `after_ipa` | `onBuildBeforeIpa` / `onBuildAfterIpa` | Around iOS IPA packaging. |
|
||||
| `before_donut` / `after_donut` | `onBuildBeforeDonut` / `onBuildAfterDonut` | Around Windows PE to shellcode conversion with Donut. |
|
||||
| `before_linux_shellcode` / `after_linux_shellcode` | `onBuildBeforeLinuxShellcode` / `onBuildAfterLinuxShellcode` | Around Linux ELF shellcode wrapping. |
|
||||
| `before_sgn` / `after_sgn` | `onBuildBeforeSgn` / `onBuildAfterSgn` | Around SGN shellcode encoding. |
|
||||
| `before_sgn_txt` / `after_sgn_txt` | `onBuildBeforeSgnTxt` / `onBuildAfterSgnTxt` | Around SGN TXT artifact conversion. |
|
||||
|
||||
Transform hook payloads include:
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `buildId` | Current build ID. |
|
||||
| `platform`, `os`, `arch`, `targetKey` | Target identity. |
|
||||
| `outDir`, `clientDir` | Build output and client source directories. |
|
||||
| `config` | Full build config, including selected build plugins. |
|
||||
| `file` | `{ filename, path, platform, size }` for the current artifact. |
|
||||
|
||||
Some stages include extra fields such as `outputFilename`, `outputPath`, `donutArch`, `shellcodeArch`, or `iterations`.
|
||||
|
||||
## Artifact Replacement And Uploads
|
||||
|
||||
`onBuildArtifact` receives `payload.file` with:
|
||||
|
||||
```js
|
||||
{
|
||||
name,
|
||||
filename,
|
||||
path,
|
||||
platform,
|
||||
version,
|
||||
size,
|
||||
}
|
||||
```
|
||||
|
||||
Return `file: { filename }` to replace the downloadable build artifact with another file already inside `dist-clients`, or return `files: [...]` to add extra downloadable artifacts from `dist-clients`.
|
||||
|
||||
Paths outside the build output directory are ignored for downloadable metadata, but the hook can still upload, copy, sign, or publish files itself because server-side plugins run with normal server privileges.
|
||||
|
||||
## Build Plugin Settings, Buttons, And Requirements
|
||||
|
||||
Declare builder UI in `config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"runtime": "server",
|
||||
"build": {
|
||||
"label": "Uploader",
|
||||
"description": "Uploads completed builds to an internal bucket.",
|
||||
"enabledByDefault": true,
|
||||
"settings": [
|
||||
{
|
||||
"key": "bucket",
|
||||
"label": "Bucket",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"placeholder": "release-builds"
|
||||
},
|
||||
{
|
||||
"key": "publish",
|
||||
"label": "Publish after build",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"id": "shellcode-release",
|
||||
"label": "Shellcode Release",
|
||||
"icon": "fa-solid fa-fire",
|
||||
"setBuild": { "useDonut": true, "useSgn": true },
|
||||
"setSettings": { "publish": true }
|
||||
}
|
||||
],
|
||||
"requires": [
|
||||
{
|
||||
"field": "useSgn",
|
||||
"truthy": true,
|
||||
"message": "Uploader release mode requires SGN to be enabled."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Supported setting types are `string`, `textarea`, `number`, `boolean`, and `select`.
|
||||
|
||||
Build profiles save these values and send them to hooks as:
|
||||
|
||||
```js
|
||||
payload.config.buildPlugins[ctx.pluginId] // { enabled, settings }
|
||||
```
|
||||
|
||||
Buttons in `build.actions` can set core builder fields through `setBuild` and plugin fields through `setSettings`. Requirements can check:
|
||||
|
||||
| Requirement field | Meaning |
|
||||
|-------------------|---------|
|
||||
| `field` | Core build field such as `useSgn` or `useDonut`. |
|
||||
| `pluginSetting` | Setting from this plugin's build settings. |
|
||||
| `platforms` | Selected build platforms. |
|
||||
|
||||
See `plugins/sample-build-hooks` for a minimal server-only build plugin with its own build button and artifact replacement.
|
||||
|
||||
## RBAC And Build Plugin Access
|
||||
|
||||
Build-hook plugins use the normal per-user plugin access policy. Admins can open a user in the Users page, choose Plugin Access, and set one of these modes:
|
||||
|
||||
| Mode | Effect |
|
||||
|------|--------|
|
||||
| No Access | The user cannot open plugin pages or use build plugins. |
|
||||
| Selected Plugins | The user can only open and use checked plugins. |
|
||||
| All Plugins | The user can use every enabled plugin. |
|
||||
|
||||
For build plugins this means:
|
||||
|
||||
| Path | Enforcement |
|
||||
|------|-------------|
|
||||
| Build page plugin list | `/api/build/plugins` only returns build plugins the user can access. |
|
||||
| Build start | Inaccessible build plugin settings are ignored server-side, so a user cannot invoke a hidden build hook by editing the request. |
|
||||
| Plugin RPC/UI/assets | Plugin pages, assets, RPC, and SSE streams require access to that plugin. |
|
||||
|
||||
Users still need `clients:build` to build at all. Plugin access is the second gate that decides which build-hook plugins they can see and use.
|
||||
|
||||
## Caveats
|
||||
|
||||
| Caveat | Detail |
|
||||
|--------|--------|
|
||||
| Worker trust | Workers are not a security sandbox. They can access server files/network with normal server permissions. |
|
||||
| One worker per plugin | RPC calls into the same plugin serialize; long calls block other RPCs for that plugin. |
|
||||
| SQLite | Each plugin gets a separate DB. Avoid opening the same DB from another process. |
|
||||
| Worker crash | Crashes are logged and `pluginState.lastError` is set; disable and re-enable the plugin to restart it. |
|
||||
| Bundled builds | The production build emits both the main server bundle and `src/server/plugin-runtime/worker-host.ts`. |
|
||||
@@ -0,0 +1,92 @@
|
||||
# Plugin Signing
|
||||
|
||||
Plugins can be signed with Ed25519 keys. The server verifies signatures on upload and displays trust state in the Plugin Manager. Unsigned or untrusted plugins require explicit confirmation; invalid signatures are blocked.
|
||||
|
||||
## Trust Levels
|
||||
|
||||
| Status | Behavior |
|
||||
|--------|----------|
|
||||
| Signed + trusted | Loads immediately. |
|
||||
| Signed + untrusted | Requires confirmation. |
|
||||
| Unsigned | Requires confirmation. |
|
||||
| Invalid signature | Blocked. |
|
||||
|
||||
## Generate A Signing Key
|
||||
|
||||
```bash
|
||||
cd Overlord-Server
|
||||
bun run scripts/plugin-keygen.ts --out my-signing-key
|
||||
```
|
||||
|
||||
This creates:
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `my-signing-key.key` | Private key. Keep it secret. |
|
||||
| `my-signing-key.pub` | Public key plus fingerprint. |
|
||||
|
||||
## Sign A Plugin
|
||||
|
||||
```bash
|
||||
cd Overlord-Server
|
||||
bun run scripts/plugin-sign.ts --key my-signing-key.key ../plugins/sample-go/sample.zip
|
||||
```
|
||||
|
||||
The signer injects `signature.json` into the zip.
|
||||
|
||||
## Trust A Key
|
||||
|
||||
Via `config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"plugins": {
|
||||
"trustedKeys": [
|
||||
"a1b2c3d4e5f6...64-char-hex-fingerprint..."
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Via environment variable:
|
||||
|
||||
```text
|
||||
TRUSTED_PLUGIN_KEYS=fingerprint1,fingerprint2
|
||||
```
|
||||
|
||||
Via API:
|
||||
|
||||
```bash
|
||||
curl -X POST /api/plugins/trusted-keys \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"fingerprint": "a1b2c3..."}'
|
||||
|
||||
curl /api/plugins/trusted-keys
|
||||
|
||||
curl -X DELETE /api/plugins/trusted-keys/a1b2c3...
|
||||
```
|
||||
|
||||
You can also add trusted keys from the Plugins page.
|
||||
|
||||
## Build Script Integration
|
||||
|
||||
Set `PLUGIN_SIGN_KEY` to sign during plugin build:
|
||||
|
||||
```bash
|
||||
PLUGIN_SIGN_KEY=path/to/my-signing-key.key ./sample-go/build.sh
|
||||
```
|
||||
|
||||
On Windows:
|
||||
|
||||
```bat
|
||||
set PLUGIN_SIGN_KEY=path\to\my-signing-key.key
|
||||
sample-go\build.bat
|
||||
```
|
||||
|
||||
## How Signing Works
|
||||
|
||||
1. The canonical content digest hashes every zip file except `signature.json`.
|
||||
2. Filenames are sorted alphabetically and combined as `filename:sha256hex`.
|
||||
3. The digest is signed with Ed25519.
|
||||
4. `signature.json` stores the signature, public key, and algorithm.
|
||||
5. Upload verification recomputes the digest and compares the signer fingerprint against trusted keys.
|
||||
@@ -0,0 +1,76 @@
|
||||
# WASM Plugins
|
||||
|
||||
Plugin 2.0 WASM plugins run inside the agent through a sandboxed runtime. They cannot access files or OS APIs directly; they declare needs in `config.json`, and the agent exposes approved host bridge operations.
|
||||
|
||||
## Config
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "My WASM Plugin",
|
||||
"version": "1.0.0",
|
||||
"apiVersion": 2,
|
||||
"runtime": "wasm",
|
||||
"wasm": "my-plugin.wasm",
|
||||
"needs": {
|
||||
"files": [
|
||||
{
|
||||
"bucket": "pluginData",
|
||||
"access": ["read", "write", "list", "mkdir"],
|
||||
"reason": "Store plugin state"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Supported file buckets are `home`, `desktop`, `documents`, `downloads`, `temp`, `appData`, `pluginData`, and `fullDisk`.
|
||||
|
||||
Supported operations are `read`, `write`, `list`, `delete`, and `mkdir`. Approval is tracked by a stable hash of normalized needs; changing needs requires approval again.
|
||||
|
||||
## Required Exports
|
||||
|
||||
| Export | Signature |
|
||||
|--------|-----------|
|
||||
| `overlord_alloc` | `ptr(uint32 size)` |
|
||||
| `overlord_free` | `void(ptr, uint32 size)` |
|
||||
| `overlord_on_load` | `int32(ptr hostInfo, uint32 hostInfoLen)` |
|
||||
| `overlord_on_event` | `int32(ptr event, uint32 eventLen, ptr payload, uint32 payloadLen)` |
|
||||
| `overlord_on_unload` | `void()` |
|
||||
|
||||
## Host Imports
|
||||
|
||||
Imports are available from both `env` and `overlord`.
|
||||
|
||||
| Import | Behavior |
|
||||
|--------|----------|
|
||||
| `overlord_emit(event, eventLen, payload, payloadLen)` | Sends an event through the plugin event channel. |
|
||||
| `overlord_host_info(out, outLen)` | Writes HostInfo JSON into `out`. |
|
||||
| `overlord_fs_stat(bucket, bucketLen, path, pathLen, out, outLen)` | Writes JSON file metadata into `out`. |
|
||||
| `overlord_fs_list(bucket, bucketLen, path, pathLen, out, outLen)` | Writes a JSON directory listing into `out`. |
|
||||
| `overlord_fs_read(bucket, bucketLen, path, pathLen, out, outLen)` | Writes file bytes into `out`, max 32 MB. |
|
||||
| `overlord_fs_write(bucket, bucketLen, path, pathLen, data, dataLen)` | Writes bytes to a bucket-relative path. |
|
||||
| `overlord_fs_delete(bucket, bucketLen, path, pathLen)` | Deletes a bucket-relative file or directory. |
|
||||
| `overlord_fs_mkdir(bucket, bucketLen, path, pathLen)` | Creates a bucket-relative directory. |
|
||||
|
||||
Bridge functions return non-negative byte counts or `0` on success. Negative return codes mean denied, invalid path/memory, not found, output buffer too small, file too large, or I/O failure.
|
||||
|
||||
## HostInfo
|
||||
|
||||
WASM plugins are universal across OS/architecture. To branch by platform, read HostInfo from `overlord_on_load(hostInfo, hostInfoLen)` or `overlord_host_info(out, outLen)`.
|
||||
|
||||
```json
|
||||
{
|
||||
"clientId": "abc123",
|
||||
"os": "windows",
|
||||
"arch": "amd64",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
```
|
||||
|
||||
## Samples
|
||||
|
||||
| Directory | Toolchain |
|
||||
|-----------|-----------|
|
||||
| `plugins/sample-wasm` | C/WASI |
|
||||
| `plugins/sample-wasm-hostinfo` | TinyGo |
|
||||
| `plugins/sample-wasm-platform-note` | Rust |
|
||||
Reference in New Issue
Block a user