Files
pyMC_Repeater/docs/plugins.md
T

303 lines
8.7 KiB
Markdown

# openHop Plugins
The plugin system installs and supervises **external** applications around the
Repeater. Plugins are not imported into the Repeater process. They talk to
existing interfaces (REST, WebSocket/SSE, Companion frame server, static web).
## Layout
Default root (under Repeater storage):
```text
/var/lib/openhop_repeater/plugins/
└── openhop.nomad/
├── releases/0.1.0/
│ ├── openhop-plugin.json
│ ├── ui/ # optional application UI assets
│ └── venv/ # isolated Python environment
├── current -> releases/0.1.0
├── data/ # persistent plugin-owned data
├── logs/plugin.log
└── state.json # manager metadata only (id, version, enabled)
```
- Code and virtualenv are **version-specific**.
- The installed wheel is retained with each release so its virtualenv can be
rebuilt automatically if a future container image changes Python versions.
- `data/` is **not** version-specific and is never interpreted by the manager.
- IPC socket default: `/var/lib/openhop_repeater/plugin-manager.sock`.
Override with config:
```yaml
plugins:
enabled: true
# root: "/var/lib/openhop_repeater/plugins"
# socket: "/var/lib/openhop_repeater/plugin-manager.sock"
```
## Manifest (`openhop-plugin.json`)
Schema version **1**:
```json
{
"schema": 1,
"id": "openhop.nomad",
"name": "NOMAD Bridge",
"version": "0.1.0",
"runtime": {
"type": "python",
"entrypoint": "meshcore-nomad-bridge"
}
}
```
UI-only application plugin:
```json
{
"schema": 1,
"id": "openhop.console",
"name": "openHop Console",
"version": "1.0.0",
"ui": {
"type": "application",
"entry": "ui/index.html"
}
}
```
A plugin may declare both `runtime` and `ui`.
## Plugin manager service
Native installs:
```bash
sudo cp openhop-plugin-manager.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now openhop-plugin-manager
```
The manager starts enabled service plugins on boot and supervises crashes
(restarts with a simple crash-loop limit: 5 unexpected exits in 60s → `FAILED`).
Repeater does **not** require the manager. If the socket is missing, plugin API
calls return HTTP 503 and normal Repeater operation continues.
The Docker image runs Repeater and the manager under a small process supervisor,
with `tini` as PID 1 to reap orphaned children. It forwards shutdown signals to
both services and their plugin processes, and restarts the manager if it exits
unexpectedly while Repeater remains running. Set `OPENHOP_PLUGIN_MANAGER=0` to
run Repeater without the manager.
## Install a local wheel
Via API (authenticated):
```bash
curl -X POST -H "Authorization: Bearer $TOKEN" \
-F "wheel=@openhop_nomad_plugin-0.1.0-py3-none-any.whl" \
http://127.0.0.1:8000/api/plugins/install
```
Or JSON with a host path:
```bash
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"wheel_path":"/tmp/plugin.whl"}' \
http://127.0.0.1:8000/api/plugins/install
```
Installation creates an isolated venv under the plugin release directory and
**never** mutates the Repeater virtualenv.
## Enable / disable / lifecycle
```bash
# Enable (also starts service plugins)
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"id":"openhop.nomad"}' \
http://127.0.0.1:8000/api/plugins/enable
curl -X POST .../api/plugins/disable -d '{"id":"openhop.nomad"}'
curl -X POST .../api/plugins/start -d '{"id":"openhop.nomad"}'
curl -X POST .../api/plugins/stop -d '{"id":"openhop.nomad"}'
curl -X POST .../api/plugins/restart -d '{"id":"openhop.nomad"}'
```
List / status / logs:
```bash
curl -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8000/api/plugins/
curl -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8000/api/plugins/openhop.nomad
curl -H "Authorization: Bearer $TOKEN" \
"http://127.0.0.1:8000/api/plugins/logs?id=openhop.nomad&tail=100"
```
## Plugin configuration
Each plugin owns `$OPENHOP_PLUGIN_DATA/config.json`. The manager treats it as an
opaque JSON object — it does not validate plugin-specific keys.
Plugins can declare editor defaults in the manifest:
```json
{
"config": {
"defaults": {
"meshcore_host": "127.0.0.1",
"nomad_url": "http://127.0.0.1:8080"
}
}
}
```
Optionally ship `config.default.json` next to `openhop-plugin.json` in the wheel
(`share/openhop/plugins/<id>/`). On install, if `data/config.json` is missing,
the manager seeds it from those defaults. The settings UI shows defaults when no
saved config exists, and offers **Reset to defaults**.
API:
```bash
# Read
curl -H "Authorization: Bearer $TOKEN" \
"http://127.0.0.1:8000/api/plugins/settings?id=openhop.nomad"
# Write (optionally restart service plugins)
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"id": "openhop.nomad",
"restart": true,
"config": {
"nomad_url": "http://192.168.0.170:8080",
"nomad_model": "qwen2.5:3b-instruct",
"meshcore_host": "127.0.0.1",
"meshcore_port": 5001
}
}' \
http://127.0.0.1:8000/api/plugins/settings
```
The Plugins page in the web UI provides a Settings dialog for the same file.
## Service plugin environment
Started processes receive:
| Variable | Example |
|----------|---------|
| `OPENHOP_PLUGIN_ID` | `openhop.nomad` |
| `OPENHOP_PLUGIN_DATA` | `/var/lib/openhop_repeater/plugins/openhop.nomad/data` |
Plugin-specific configuration (for example NOMAD `config.json`) lives under
`$OPENHOP_PLUGIN_DATA` and is owned by the plugin.
## Application UI plugins
Enabled UI plugins are served by Repeater at:
```text
/plugins/{id}/
/plugins/{id}/assets/...
```
Path traversal is rejected. Missing paths fall back to the manifest `ui.entry`
document (SPA-friendly). Disabled plugins are not exposed.
## Uninstall
Default **keeps** `data/`:
```bash
curl -X DELETE -H "Authorization: Bearer $TOKEN" \
http://127.0.0.1:8000/api/plugins/openhop.nomad
```
Delete data as well:
```bash
curl -X DELETE -H "Authorization: Bearer $TOKEN" \
"http://127.0.0.1:8000/api/plugins/openhop.nomad?delete_data=true"
```
## NOMAD acceptance
1. Build the `openhop-nomad-plugin` wheel.
2. Install via `/api/plugins/install`.
3. Enable a Repeater Companion frame server reachable from the plugin. In the
standard Docker image, `127.0.0.1:5001` reaches the same container.
4. Write `$OPENHOP_PLUGIN_DATA/config.json` with `nomad_url` / `nomad_model`.
5. Enable the plugin — `meshcore-nomad-bridge` should report `RUNNING`.
6. Confirm its logs show a successful Companion connection, not only a running
retry loop. A `RUNNING` process can still be reconnecting to a disabled frame
server.
## Future work (not in this version)
- Web Component dashboard widgets and settings panels
- Permission scopes, signing, non-Python runtimes
- Plugin SDK
## Plugin catalogue
Repeater browses a curated static catalogue and installs the exact plugin wheel
approved there. The catalogue and wheels are hosted on openHop's R2-backed
origin, so normal installs and update checks do not call the GitHub API.
Default catalogue URL:
```text
https://repeater-plugins.openhop.dev/catalogue.json
```
Override with:
```yaml
plugins:
catalogue_url: "https://repeater-plugins.openhop.dev/catalogue.json"
```
Catalogue schema 2 records each plugin's currently approved `version`, exact
`wheel_url`, and lowercase `sha256`. The manager downloads only from
`https://repeater-plugins.openhop.dev/plugins/`, verifies the checksum before
installation, and confirms the wheel manifest ID and version match the
catalogue. Publishing a newer GitHub Release does not make it available until
the catalogue maintainers approve and publish that exact wheel.
Schema 1 repository-only catalogues remain readable for compatibility with
custom deployments, but the default openHop catalogue uses schema 2 and needs
no GitHub token.
```bash
# List catalogue (annotated with installed / latest when available)
curl -H "Authorization: Bearer $TOKEN" \
http://127.0.0.1:8000/api/plugins/catalogue
# Install the currently approved catalogue version (enabled by default)
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"id":"openhop.nomad"}' \
http://127.0.0.1:8000/api/plugins/catalogue_install
# Check for updates
curl -H "Authorization: Bearer $TOKEN" \
"http://127.0.0.1:8000/api/plugins/updates?id=openhop.nomad"
# Apply update
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"id":"openhop.nomad"}' \
http://127.0.0.1:8000/api/plugins/update
```
R2 catalogue or wheel outages do not affect already-installed plugins or
Repeater startup. Local `.whl` install continues to work unchanged.