mirror of
https://github.com/pyMC-dev/pyMC_Repeater.git
synced 2026-10-30 19:51:59 +01:00
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`.
This commit is contained in:
1 parent
bd0c340856
commit
26560fd50a
30 files changed
+5742
-1
No files matched your search
+280
@@ -0,0 +1,280 @@
|
||||
# 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**.
|
||||
- `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.
|
||||
|
||||
## 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. 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:
|
||||
|
||||
```text
|
||||
https://raw.githubusercontent.com/openhop-dev/openhop-plugin-catalogue/main/catalogue.json
|
||||
```
|
||||
|
||||
Override with:
|
||||
|
||||
```yaml
|
||||
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).
|
||||
|
||||
```bash
|
||||
# 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.
|
||||
|
||||
Reference in new issue
Block a user