Thank you for your interest! Rustboard is intentionally small and approachable. This guide covers everything you need to contribute code, plugins, or documentation.
Rustboard follows the Contributor Covenant Code of Conduct.
- Project structure
- Development setup
- Running locally
- Code style
- Testing
- Writing a plugin
- Release process
- Submitting changes
- Good first issues
Rustboard/
├── core/ # HTTP server (Axum) + SSE/WebSocket + SSH runner
│ ├── src/
│ │ ├── main.rs # Route wiring and app entry point
│ │ ├── config.rs # YAML service/preference loading
│ │ ├── health.rs # Background health-check worker
│ │ ├── discover.rs # Docker auto-discovery over SSH
│ │ ├── plugin.rs # Plugin discovery & exec (Extism)
│ │ ├── service.rs # Service and QuickCommand structs
│ │ └── ssh.rs # SSH command runner (blocking + streaming)
│ ├── tests/
│ │ └── config_test.rs
│ └── web/
│ └── index.html # Single-file web UI (vanilla JS + HTMX)
├── cli/ # CLI entry point (clap + reqwest)
├── plugins/ # Shared plugin stub crate
├── plugin-openai-tester/ # Reference WASM plugin (Extism PDK)
├── config/
│ ├── services.yaml # Your local service definitions (gitignored)
│ ├── services.example.yaml
│ └── preferences.yaml
├── docs/
│ ├── ARCHITECTURE.md
│ ├── PLUGINS.md
│ ├── API.md
│ ├── CONFIGURATION.md
│ ├── CONTRIBUTING.md ← this file
│ └── SECURITY.md
├── scripts/
│ ├── build-local.ps1 / .sh
│ ├── release.ps1 / .sh
│ ├── ensure-wsl.ps1
│ └── install-plugin-openai-tester.*
├── install.sh # End-user installer (Linux/macOS)
└── install.ps1 # End-user installer (Windows)
See docs/ARCHITECTURE.md for a deep dive into the codebase.
| Tool | Minimum version | Install |
|---|---|---|
| Rust | 1.75 (stable) | rustup.rs |
| WASM target | — | rustup target add wasm32-wasip1 |
System ssh |
any | Pre-installed on Linux/macOS; use OpenSSH for Windows/WSL |
git clone https://github.com/meliani/Rustboard.git
cd Rustboardcargo build # builds core, cli, web, plugins (native targets)cargo build -p plugin-openai-tester --target wasm32-wasip1 --release
cp target/wasm32-wasip1/release/plugin_openai_tester.wasm \
plugins/bin/plugin-openai-tester.wasmOr use the helper script:
./scripts/install-plugin-openai-tester.sh # debug build
./scripts/install-plugin-openai-tester.sh --releaseThe project targets Linux environments. Use WSL or the helper:
# Run any command inside WSL from PowerShell
.\scripts\ensure-wsl.ps1 cargo build --workspace
.\scripts\ensure-wsl.ps1 cargo run -p core -- config/services.example.yaml# Uses config/services.example.yaml by default
cargo run -p core -- config/services.example.yamlThe server starts on http://localhost:8080. The web UI is served at the root.
PLUGIN_DIR=./plugins/bin cargo run -p core -- config/services.example.yamlIn a second terminal:
cargo run -p cli -- list
cargo run -p cli -- status <service-id>
cargo run -p cli -- logs <service-id>Edit config/services.yaml while the server is running, then:
cargo run -p cli -- config-reload
# or
curl -X POST http://localhost:8080/config/reloadRUST_LOG=debug cargo run -p core -- config/services.example.yaml
RUST_LOG=core=debug,warn cargo run -p core -- config/services.example.yaml- Formatting:
cargo fmt— enforced in CI - Lints:
cargo clippy— all warnings are addressed before merging - Error handling: Use
anyhow::Resultfor fallible functions incore; avoidunwrap()except in tests and short-lived code paths where a panic is appropriate - Async: Never hold a write lock across an
awaitpoint — clone the needed data, release the lock, then do async work - No external HTTP frameworks in the UI: The web UI stays in
core/web/index.htmlas a single file with no build step
# Check before committing
cargo fmt --check
cargo clippy --workspace -- -D warningscargo test --workspaceTests currently live in:
core/tests/config_test.rs— YAML config loadingcore/src/plugin.rs(inline#[cfg(test)]) — plugin discovery and error cases
Before submitting a PR that touches core functionality:
- Server starts cleanly:
cargo run -p core -- config/services.example.yaml - Web UI loads at
http://localhost:8080 -
cargo run -p cli -- listreturns a service list - Config hot-reload works: edit YAML →
POST /config/reload→ UI updates -
GET /healthreturnsok - (If touching discovery)
POST /discoverfinds containers on a Docker host
See the full guide in docs/PLUGINS.md.
Quick steps:
# 1. Create crate with cdylib type
cargo new --lib plugin-my-plugin
# 2. Add extism-pdk dependency, set crate-type = ["cdylib"]
# 3. Implement execute() with #[plugin_fn]
# 4. Build
cargo build -p plugin-my-plugin --target wasm32-wasip1 --release
# 5. Install
cp target/wasm32-wasip1/release/plugin_my_plugin.wasm \
plugins/bin/plugin-my-plugin.wasm
# 6. Test
curl -X POST http://localhost:8080/plugins/exec \
-d '{"name":"plugin-my-plugin","input":{}}'Every push to main automatically:
- Reads the current version from
core/Cargo.toml - Bumps the patch version (e.g.
v0.1.3 → v0.1.4) and pushes a git tag - Cross-compiles for:
x86_64-unknown-linux-musl(Linux, statically linked)x86_64-pc-windows-gnu(Windows)x86_64-apple-darwin(macOS Intel)aarch64-apple-darwin(macOS Apple Silicon)
- Publishes a GitHub Release with all binaries attached
When a non-patch version bump is needed, use the release scripts before merging:
# Bump minor version (0.1.x → 0.2.0)
./scripts/release.sh minor
# Bump major version (0.x.y → 1.0.0)
./scripts/release.sh major
# Set an explicit version
./scripts/release.sh 2.0.0# Windows equivalents
.\scripts\release.ps1 minor
.\scripts\release.ps1 2.0.0These scripts update Cargo.toml version fields and push a tag, which triggers CI.
-
Fork the repository and create a branch from
main:git checkout -b feat/my-feature
-
Make your changes — keep commits focused; one logical change per commit.
-
Ensure the build and tests pass:
cargo fmt --check cargo clippy --workspace -- -D warnings cargo test --workspace -
Open a Pull Request against
mainwith:- A clear title (e.g.
feat: add redis health-check strategyorfix: SSE stream drops on slow consumer) - A short description of what changed and why
- Steps to test the change locally
- A clear title (e.g.
- Keep PRs small and focused — large refactors should be discussed in an issue first
- Documentation updates are always welcome
- New features should include at least one unit test or an integration test note in the PR description
- Plugin contributions can be submitted as standalone repos and linked in the README
Look for issues labelled good first issue on GitHub. Ideas that are typically accessible:
- Adding a new CLI subcommand (maps 1:1 to a REST endpoint)
- Adding a new health-check strategy (e.g.
health_grpc,health_script) - Improving error messages
- Writing a new plugin in any supported language
- Improving the web UI (no build step — edit a single HTML file)
- Adding documentation examples for a specific stack (Kubernetes, Nomad, etc.)
- Project structure
- Development setup
- Running locally
- Writing a plugin
- Release process
- Submitting changes
Rustboard/
├── core/ # HTTP server (Axum) + SSE/WebSocket + SSH runner
│ ├── src/
│ │ ├── main.rs # Route wiring and app entry point
│ │ ├── config.rs # YAML service/preference loading
│ │ ├── health.rs # Background health-check worker
│ │ ├── discover.rs # Docker auto-discovery over SSH
│ │ ├── plugin.rs # Plugin discovery & exec
│ │ ├── service.rs # Service model
│ │ └── ssh.rs # SSH command runner
│ └── web/
│ └── index.html # Single-file web UI (vanilla JS + HTMX)
├── cli/ # Command-line interface (calls core HTTP API)
├── plugins/ # Plugin trait definition (DashboardPlugin)
├── plugin-openai-tester/ # Bundled example plugin (Extism WASM — tests OpenAI API keys)
├── config/
│ ├── services.yaml # Your local service definitions
│ └── services.example.yaml
├── scripts/
│ ├── build-local.ps1 / .sh # Build native binaries
│ ├── release.ps1 / .sh # Tag & trigger a release
│ └── install-plugin-openai-tester.* # Install the bundled plugin
├── install.sh # End-user installer (Linux/macOS)
├── install.ps1 # End-user installer (Windows)
└── .github/workflows/
└── release.yml # CI: auto-tag + cross-compile + publish release
Prerequisites
- Rust stable toolchain (≥ 1.90)
wasm32-wasip1target:rustup target add wasm32-wasip1- SSH access to any remote hosts you want to monitor (optional for local dev)
Clone and build
git clone https://github.com/meliani/Rustboard.git
cd Rustboard
cargo build # builds core, cli, web, plugins
# build + install the bundled WASM plugin:
cargo build -p plugin-openai-tester --target wasm32-wasip1 --release
cp target/wasm32-wasip1/release/plugin_openai_tester.wasm plugins/bin/plugin-openai-tester.wasmOn Windows — the project targets Linux environments. Use WSL or the provided helper:
.\scripts\ensure-wsl.ps1 cargo build --workspaceStart the core server (serves web UI on http://localhost:8080):
cargo run -p core -- config/services.example.yamlUse the CLI (talks to the running core server):
cargo run -p cli -- list
cargo run -p cli -- start <service-id>Run tests:
cargo test --workspaceInstall the bundled plugin for local testing:
./scripts/install-plugin-openai-tester.sh # debug build
./scripts/install-plugin-openai-tester.sh --releaseSet PLUGIN_DIR to point the server at a custom plugin directory:
PLUGIN_DIR=./plugins/bin cargo run -p core -- config/services.example.yamlPlugins are WebAssembly modules (*.wasm) loaded at runtime by the host via Extism. They are fully sandboxed — no filesystem access, no process spawning — and run on any OS without recompilation.
POST /plugins/exec { "name": "my-plugin", "input": { ... } }
│
▼
core/plugin.rs ──extism──▶ my-plugin.wasm
│
execute(json_string) → json_string
The host serialises input to a JSON string, passes it to the plugin's exported execute function, and returns the raw string result to the caller.
Every plugin must export exactly one function:
fn execute(input: String) -> String
- Input — a JSON string (your plugin defines the schema)
- Output — a JSON string; by convention include
"ok": true|false
1. Create the crate
cargo new --lib plugin-my-plugin2. Set Cargo.toml
[package]
name = "plugin-my-plugin"
version = "0.1.0"
edition = "2021"
autobins = false
[lib]
crate-type = ["cdylib"]
[dependencies]
extism-pdk = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"Add the crate to the workspace Cargo.toml:
[workspace]
members = [
...
"plugin-my-plugin",
]3. Write src/lib.rs
use extism_pdk::*;
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct Input {
message: String,
}
#[derive(Serialize)]
struct Output {
ok: bool,
echo: String,
}
#[plugin_fn]
pub fn execute(raw: String) -> FnResult<String> {
let input: Input = serde_json::from_str(&raw)
.map_err(|e| anyhow::anyhow!("invalid input: {}", e))?;
let out = Output { ok: true, echo: input.message };
Ok(serde_json::to_string(&out).unwrap())
}4. Build and install
cargo build -p plugin-my-plugin --target wasm32-wasip1 --release
cp target/wasm32-wasip1/release/plugin_my_plugin.wasm plugins/bin/plugin-my-plugin.wasm5. Invoke
curl -s -X POST http://localhost:8080/plugins/exec \
-H 'Content-Type: application/json' \
-d '{"name":"plugin-my-plugin","input":{"message":"hello"}}'
# → {"ok":true,"output":"{\"ok\":true,\"echo\":\"hello\"}"}Extism has PDKs for Go, Python, JavaScript, C/C++, Zig, and more. The only requirement is that the compiled .wasm exports an execute function following the string-in / string-out contract above.
See the Extism PDK documentation for language-specific guides.
The host grants outbound HTTP access to all hosts by default (with_allowed_host("*")). Plugins use extism_pdk::http::request — not the standard reqwest/fetch APIs, which are not available in WASI. See plugin-openai-tester/src/lib.rs for a complete example.
| Capability | Granted by default |
|---|---|
| Outbound HTTP | ✅ (all hosts) |
| Filesystem read/write | ❌ |
| Spawn processes | ❌ |
| Environment variables | ❌ |
To restrict network access to specific hosts, modify manifest.with_allowed_host(...) in core/src/plugin.rs.
Every push or merged PR to main automatically:
- Bumps the patch version (e.g.
v0.1.3 → v0.1.4) and pushes a git tag - Cross-compiles for Linux x86_64, Windows x86_64, macOS Intel, macOS Apple Silicon
- Publishes a GitHub Release with all binaries attached
No manual steps required for regular patches.
When you need a minor or major release:
# Windows
.\scripts\release.ps1 minor # 0.1.x → 0.2.0
.\scripts\release.ps1 major # x.y.z → 1.0.0
.\scripts\release.ps1 1.5.0 # explicit# Linux / macOS
./scripts/release.sh minor
./scripts/release.sh 1.5.0The script validates you're on main with a clean tree, confirms the new tag, pushes it, and the CI workflow takes over from there.
- Fork the repo and create a branch from
main. - Make your changes and ensure
cargo test --workspacepasses. - Open a Pull Request — keep the description concise and focused.
- Once merged to
main, a release is published automatically.
For large features or breaking changes, open an issue first to discuss the approach.