77 lines
3.3 KiB
Markdown
77 lines
3.3 KiB
Markdown
# Sub2API Business Plugin Control Plane V1
|
|
|
|
This directory contains the independent administrator-only control plane for
|
|
Business Plugins. It is deliberately separate from Sub2API Core and from the
|
|
existing `.s2plugin` OpenAI OAuth transport runtime.
|
|
|
|
The control plane owns the plugin catalog, signed package verification,
|
|
revision directories, lifecycle state, encrypted configuration metadata,
|
|
menu preview/apply, and its own audit log. Core remains authoritative for
|
|
users, administrator roles, balances, subscriptions, billing, and audit
|
|
records. The service never connects to Core PostgreSQL/Redis and never sends a
|
|
Core JWT or Admin Key to the browser.
|
|
|
|
## Run locally
|
|
|
|
```sh
|
|
CORE_BASE_URL=http://127.0.0.1:8080 \
|
|
PLUGIN_HOST=127.0.0.1 PLUGIN_PORT=8090 \
|
|
PLUGIN_REGISTRY_DIR=./data \
|
|
PLUGIN_ENV=development \
|
|
PLUGIN_ALLOW_UNSIGNED=true \
|
|
PLUGIN_CONFIG_KEY=local-development-secret-at-least-32-chars \
|
|
go run .
|
|
```
|
|
|
|
`PLUGIN_ALLOW_UNSIGNED=true` is a development-only switch. Production
|
|
packages must contain `signature.json`, use Ed25519, and match a trusted key
|
|
from `PLUGIN_TRUSTED_PUBLISHERS` (a JSON object of key ID to base64 public
|
|
key). `PLUGIN_CONFIG_KEY` is required in every environment; use a randomly
|
|
generated secret in production and keep it stable across restarts so encrypted
|
|
plugin configuration remains decryptable.
|
|
|
|
Open `/admin/` directly or expose the service through the reverse proxy in
|
|
`deploy/`. The first login is the existing Core administrator login; no plugin
|
|
user table is created. The control plane stores only a short-lived server-side
|
|
session and encrypted plugin configuration.
|
|
|
|
## Control-plane endpoints
|
|
|
|
```text
|
|
GET /healthz
|
|
GET /readyz
|
|
POST /login POST /login/2fa POST /logout
|
|
GET /api/me GET /api/plugins GET /api/plugins/{id}
|
|
POST /api/plugins/{id}/install (multipart field: package)
|
|
POST /api/plugins/{id}/upgrade (multipart field: package)
|
|
POST /api/plugins/{id}/enable|disable|rollback|uninstall
|
|
GET|PUT /api/plugins/{id}/config
|
|
POST /api/plugins/{id}/menu-preview|menu-apply
|
|
POST /api/menu-items/preview|apply (JSON: {"plugin_id":"..."})
|
|
GET /api/audit
|
|
```
|
|
|
|
Every mutation requires the plugin CSRF token and an `Idempotency-Key`. A
|
|
mutation returns an operation ID even when it completes synchronously. Failed
|
|
installation and upgrade never replace the active revision. Uninstall is
|
|
allowed only after disable and removes plugin files, not Core data.
|
|
|
|
## Plugin package
|
|
|
|
Packages are ZIP files with `manifest.json`, optional detached
|
|
`signature.json`, and declared `ui/` files. A package may also include
|
|
`service/` files when the control plane owns the plugin process; external
|
|
service packages omit `backend.command` and require a configured loopback
|
|
`service_url` before enabling. SHA-256 hashes in the manifest cover every
|
|
declared file. Absolute paths, traversal, duplicate entries, symlinks,
|
|
undeclared hashes, oversized files, unknown manifest fields, and untrusted
|
|
publishers are rejected before staging. Installation uses a per-plugin
|
|
revision directory and an atomic registry JSON update.
|
|
|
|
## Deliberate V1 limits
|
|
|
|
The control plane does not register Core routes, change Core migrations, run
|
|
arbitrary proxy URLs, provide transparent iframe SSO, or move billing and
|
|
subscription hot-path logic out of Core. A subscription manager remains a
|
|
separate business plugin that consumes its own typed Core API adapter.
|