Files
pyMC_Repeater/docs/plugins.md
T

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

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.

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

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

https://repeater-plugins.openhop.dev/catalogue.json

Override with:

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.

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