NAVIGATION
ELOWEN DOCUMENTATION

Last updated: 10 October 2026

Browse documentation · Independent plugin processes
Developer reference

Independent plugin processes

Independent plugin processes

A user plugin may declare one manifest.service.entry. Core never imports this entry: the root helper (scripts/elowen-site-gateway.mjs) validates its real path, runs Node --check as the service user (preflight, scripts/elowen-site-gateway.mjs:3705-3715), and owns a hardened elowen-plugin-<name>.socket/.service pair rendered by scripts/pluginServiceUnits.mjs. The ordinary plugin context receives only its own socketPath, the trustProxy projection, status() and restart() (src/plugins/api.ts:2384-2390); each restart is preflighted by the root helper (scripts/elowen-site-gateway.mjs:3920-3921). The authoritative daemon reconciles all installed declarations once its plugin services have started (src/brain/brainService.ts:2447-2449); runners receive null (src/daemon/brainCore.ts:346-349, src/daemon/brainCore.ts:1002). Disable, failed imports, and daemon shutdown never stop an independent process: only the uninstall route calls PluginServiceManager.remove (src/api/routes/plugins/index.ts:469-471), and the shutdown path stops only the registry-contributed services of PluginServiceRunner (src/plugins/serviceRunner.ts:133-146). Uninstall removes it before marketplace files or data are deleted (src/api/routes/plugins/index.ts:469-473). Root ownership records hashes and pending writes so an interrupted update is repairable without adopting unfamiliar host units (scripts/elowen-site-gateway.mjs:3926-3937).

The same service seam owns Chatbot's public transport. The daemon publishes a bounded private policy and exact active credential-hash projection; the worker serves the existing widget, appearance and durable message intake without database access or brain imports. Actual account, Project and spend admission stays on the daemon's existing public route, reached only over loopback. The intake key is the existing visitor/client-turn pair. A lost admission receipt can be delivered again without a second brain turn; an already-running interrupted turn is never re-executed. The worker keeps the browser stream and resumes the daemon's durable event log after transport loss, without a second transcript.

The main vhost generator, not the Sites gateway renderer, exposes explicitly selected plugin hook prefixes on the service socket (src/cli/install/proxy.ts:140-177; the Sites gateway helper has no plugin-service upstream). Core probes readiness before any nginx effect (src/cli/provision/deployment.ts:220-222, src/cli/install/proxy.ts:55-80). ctx.service.trustProxy projects the security.trustProxy setting that the daemon also passes to its shared clientOrigin (src/api/clientIp.ts:43, src/daemon/brainCore.ts:348-349). The Chatbot worker passes its projected value to the same function (server.ts:101 in the registry's plugins/chatbot/src/service/), so the daemon and the worker apply one trust rule. No independent plugin owns host nginx fragments or makes its own trust decision.

Owning code

  • src/plugins/api.ts: PluginServiceHandle and PluginContext.service, the plugin-facing contract. src/plugins/manifest.ts validates the service manifest field.
  • src/privileged/pluginServices.ts: PluginServiceManager, the daemon-side client. It stamps identity, applies restart backoff and reports failures.
  • scripts/elowen-site-gateway.mjs: the root helper's plugin-service domain (serviceSpec, ensurePluginService, pluginServiceReconcile).
  • scripts/pluginServiceUnits.mjs: entry validation and the fixed unit templates.
  • src/daemon/brainCore.ts and src/brain/brainService.ts: wiring and boot order.
  • src/cli/install/proxy.ts and src/cli/provision/deployment.ts: nginx hook prefixes and the readiness probe.
  • src/api/clientIp.ts: the one client-origin rule.

Minimal use

A plugin opts in with one manifest field:

"service": { "entry": "dist/service/main.js" }

The entry is a relative .js or .mjs path. It may not contain whitespace, quotes, %, $, backslashes, control characters or .. segments (scripts/pluginServiceUnits.mjs:6-13). Host code treats a missing handle as "no worker":

const socket = ctx.service?.socketPath ?? null; // null: no declaration, runner, or non-authoritative daemon

The worker side is plugin code. Chatbot's worker listens on inherited socket fd 3 when LISTEN_FDS is 1 and LISTEN_PID matches its own pid (registry plugins/chatbot/src/service/main.ts:21-24). For the full worker contract, see the plugin author services page.

Absence, limits and cost

  • Without a declaration, in a runner process, or in a non-authoritative daemon, ctx.service is null (src/plugins/api.ts:2394-2395, src/daemon/brainCore.ts:346-349).
  • A failed --check preflight leaves the running service untouched (scripts/elowen-site-gateway.mjs:3712-3713).
  • A plugin whose data folder does not exist yet is deferred during bulk reconcile only. Restart and ensure still fail (scripts/elowen-site-gateway.mjs:3793-3802, scripts/elowen-site-gateway.mjs:3897-3901).
  • Failed starts back off at 30 seconds, doubling up to a 5 minute ceiling (src/privileged/pluginServices.ts:80).
  • Unit limits: MemoryMax=512M, TasksMax=64, Restart=always with RestartSec=1, a read-only system with the data folder as the only writable path (scripts/pluginServiceUnits.mjs:57-77).
  • Each service build hashes at most 8192 entries and 64 MiB of JavaScript (scripts/elowen-site-gateway.mjs:3776-3791).
  • Cost: each declaring plugin runs one extra Node process. A daemon boot does not restart an unchanged, active service (scripts/elowen-site-gateway.mjs:3919-3921). Because disable never stops the process, a disabled plugin keeps running until it is uninstalled.

Real consumers

  • Chatbot, a registry plugin: its manifest declares service.entry, and the worker lives in plugins/chatbot/src/service/. The daemon side publishes trustProxy, the loopback daemon address and a hashed credential projection (plugins/chatbot/src/service/daemon.ts:42-44).
  • Sites, a registry plugin: its manifest also declares service (plugins/sites/elowen-plugin.json).