Files
pyMC_Repeater/docs/plugins.md
T
Rightup 26560fd50a Add comprehensive tests for plugin management and IPC functionality
- Introduced tests for the plugin catalogue installation and update flows in `test_plugin_catalogue_install.py`.
- Added unit tests for the GitHub Releases plugin client in `test_plugin_github_releases.py`.
- Implemented IPC tests for the plugin manager Unix socket protocol in `test_plugin_ipc.py`.
- Created lifecycle and installation tests for the plugin manager in `test_plugin_manager_lifecycle.py`.
- Added tests for plugin manifest parsing in `test_plugin_manifest.py`.
- Developed tests for plugin filesystem layout and state management in `test_plugin_storage.py`.
2026-09-03 22:16:38 +01:00

7.7 KiB

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):

/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.
  • 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:

plugins:
  enabled: true
  # root: "/var/lib/openhop_repeater/plugins"
  # socket: "/var/lib/openhop_repeater/plugin-manager.sock"

Manifest (openhop-plugin.json)

Schema version 1:

{
  "schema": 1,
  "id": "openhop.nomad",
  "name": "NOMAD Bridge",
  "version": "0.1.0",
  "runtime": {
    "type": "python",
    "entrypoint": "meshcore-nomad-bridge"
  }
}

UI-only application plugin:

{
  "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:

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.

Install a local wheel

Via API (authenticated):

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:

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

# 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:

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:

{
  "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:

# 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:

/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/:

curl -X DELETE -H "Authorization: Bearer $TOKEN" \
  http://127.0.0.1:8000/api/plugins/openhop.nomad

Delete data as well:

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. Write $OPENHOP_PLUGIN_DATA/config.json with nomad_url / nomad_model.
  4. Enable the plugin — meshcore-nomad-bridge should report RUNNING.
  5. The bridge talks to the Companion frame server (default 127.0.0.1:5001).

Future work (not in this version)

  • GitHub release installation and catalogue
  • Automatic updates / rollback between versions
  • Web Component dashboard widgets and settings panels
  • Plugin management UI page
  • Permission scopes, signing, non-Python runtimes
  • Plugin SDK

Plugin catalogue

Repeater can browse a curated static catalogue and install plugins from GitHub Releases.

Default catalogue URL:

https://raw.githubusercontent.com/openhop-dev/openhop-plugin-catalogue/main/catalogue.json

Override with:

plugins:
  catalogue_url: "https://raw.githubusercontent.com/openhop-dev/openhop-plugin-catalogue/main/catalogue.json"

The catalogue lists plugin identity and repository only. Versions and wheel assets come from GitHub Releases on each plugin repo (draft and prerelease tags are ignored by default).

# List catalogue (annotated with installed / latest when available)
curl -H "Authorization: Bearer $TOKEN" \
  http://127.0.0.1:8000/api/plugins/catalogue

# Install latest stable release of a catalogue plugin (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

Catalogue or GitHub outages do not affect already-installed plugins or Repeater startup. Local .whl install continues to work unchanged.