modularize docs

This commit is contained in:
KingKDot
2026-06-22 13:04:33 -05:00
parent 7aaf115e2f
commit 5f75f5ca6f
9 changed files with 981 additions and 1535 deletions
+36 -1535
View File
File diff suppressed because it is too large Load Diff
+13
View File
@@ -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. |
+89
View File
@@ -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.
+71
View File
@@ -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.
+282
View File
@@ -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.
+17
View File
@@ -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.
+305
View File
@@ -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`. |
+92
View File
@@ -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.
+76
View File
@@ -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 |