Skip to content

Instance plugins โ€‹

Instance plugins install and manage software on a running instance โ€” connecting to a private network, mounting data transfer tooling, running a dev server. A plugin is a declarative plugin.yaml spec describing lifecycle steps (install, configure, start, health-check, stop) that spawn runs on the instance.

Not the same as a workflow-engine plugin

An instance plugin (this page) adds software to a machine. A workflow-engine plugin โ€” like the Nextflow nf-spawn executor โ€” is a workflow adapter that plugs into an engine, not into an instance. Different systems, despite both being called "plugins."

Available plugins โ€‹

The official registry lives at spore-host/spore-plugins:

PluginWhat it does
tailscaleConnect the instance to your Tailscale private network
rstudio-serverBrowser-based R development environment
globus-personal-endpointHigh-speed data transfer via Globus Connect Personal
spore-syncLive bidirectional directory sync

Trust & permissions โ€‹

Installing a plugin runs its author's code

A plugin can run commands on your local machine (the controller) and, on the EC2 instance, as root. Installing one is equivalent to running code from its author in both places. Review a third-party plugin.yaml before installing it, and pin production use to a version or commit rather than a moving reference.

What the install sources resolve to:

  • name / name@version โ€” the official registry; a version is a published registry release.
  • github:org/repo/path[@ref] โ€” a plugin from any GitHub repo. The @ref is a git ref (tag, branch, or commit); pin to a tag or commit so the definition can't change under you. A bare github: ref (no @) tracks the default branch and can move.
  • ./path.yaml โ€” a local file, for development.

Two things limit what a plugin can reach:

  • Local steps run with a minimal environment. A plugin's controller-side steps see only PATH + HOME unless the plugin explicitly allowlists variables via env_passthrough (see the field reference). So a plugin can't silently scoop up your AWS or other credentials โ€” it must name each variable it needs (e.g. Tailscale's TS_API_CLIENT_SECRET), and you can see that list in the spec before installing.
  • spawn plugin validate statically checks a spec offline (schema, semver, step/condition types, undeclared template refs) without contacting AWS or an instance โ€” run it on any third-party spec before installing.

Known limitation: no pre-install inspection yet

There is not yet a spawn plugin inspect / install --dry-run that renders a plugin's resolved source, local vs. remote commands, requested env, opened ports, and root usage before you run it โ€” today the pre-install check is reading the plugin.yaml plus spawn plugin validate. Tracked in spawn#387 (inspection) and spawn#388 (an explicit permissions: block).

Installing a plugin โ€‹

Install onto a running instance with spawn plugin install <ref>:

sh
# From the official registry, by name.
# tailscale mints a short-lived key from your OAuth client, so you pass the ACL
# tag as config and the OAuth client via env โ€” not a raw auth key.
export TS_API_CLIENT_ID=...  TS_API_CLIENT_SECRET=...
spawn plugin install tailscale --instance my-job --config tag=tag:spore

# Pin to a specific version
spawn plugin install rstudio-server@v1.0.0 --instance my-job

# From any GitHub repo
spawn plugin install github:myorg/my-plugins/my-tool --instance my-job

# From a local file (development)
spawn plugin install ./my-plugin.yaml --instance my-job

Per-plugin configuration is passed with repeatable --config key=value pairs.

Manage installed plugins:

sh
spawn plugin list --instance my-job        # what's installed
spawn plugin status tailscale --instance my-job
spawn plugin remove tailscale --instance my-job

Installing at launch โ€‹

Declare plugins to install during startup with --plugin (repeatable; takes a ref[@version]):

sh
spawn launch analysis --instance-type r6i.4xlarge --plugin rstudio-server --ttl 8h

For per-plugin config, use a launch config file's plugins: block:

yaml
# launch.yaml
instance_type: r6i.4xlarge
ttl: 8h
plugins:
  - ref: tailscale
    config:
      tag: tag:spore   # OAuth client via TS_API_CLIENT_ID/SECRET env (see above)
sh
spawn launch analysis --config launch.yaml

Writing a plugin โ€‹

A plugin is a plugin.yaml file declaring lifecycle steps. Minimal example:

yaml
name: my-tool                # kebab-case, must match the directory name
version: v1.0.0              # semver
description: "Install and run my-tool"
author: you

config:
  api_key:
    type: string             # string | int | bool
    required: true
    description: "API key for my-tool"

conditions:
  remote:
    - type: platform         # command | platform
      os: linux

remote:                      # steps run on the instance
  install:                   # phases: install, configure, start, stop, health
    - type: run              # remote step types: run | fetch | extract
      run: curl -fsSL https://example.com/install.sh | sh
  start:
    - type: run
      run: my-tool serve --key={{ config.api_key }}
  health:
    interval: 30s
    steps:
      - type: run
        run: my-tool status

outputs:
  endpoint:
    description: "Service endpoint"

Template references in the config, instance, outputs, and pushed namespaces (for example config.api_key or instance.name, written in double braces) are substituted at run time. See AUTHORING.md for the full spec, including controller-side local steps and the push API for moving captured values to the instance.

plugin.yaml field reference โ€‹

Top level:

FieldTypeDescription
namestringPlugin id, kebab-case; must match the directory name.
versionstringSemVer (e.g. v1.0.0).
descriptionstringOne-line summary.
configmapUser-supplied parameters, keyed by name (see below).
conditionsblocklocal / remote lists of preconditions checked before running.
localblockSteps that run on your machine (the controller).
remoteblockSteps that run on the instance.
outputsmapValues surfaced after provisioning, keyed by name.

config.<name> (a parameter):

FieldTypeDescription
typestringstring, int, or bool.
requiredboolFail if the user didn't supply it.
defaultanyValue used when unset.
descriptionstringShown in help/validation.

conditions.local[] / conditions.remote[]:

FieldTypeDescription
typestringcommand (a probe command must succeed) or platform.
runstringCommand to run for type: command.
osstringRequired OS for type: platform (e.g. linux).
messagestringShown when the condition fails.

remote phases: install, configure, start, stop, each a list of steps; plus health (interval + steps) for the recurring health-check loop.

local block: provision, deprovision, and reconcile (re-run when the instance's IP changes after a stop/start) step lists, plus env_passthrough โ€” the allowlist of controller env vars a local step may read. Local steps otherwise run with a minimal environment (PATH+HOME only) so plugin scripts can't scoop up your AWS/other credentials; a plugin that needs a controller-side secret (e.g. Tailscale's TS_API_CLIENT_SECRET) lists it here and spawn injects only those.

Step fields โ€‹

FieldTypeDescription
typestringrun, fetch, extract, or push.
runstringShell command (type: run).
url / deststringDownload source / destination (type: fetch).
src / deststringArchive path / target dir (type: extract).
key / valuestringValue to push to the instance (type: push).
backgroundboolRun without waiting (e.g. a long-lived server).
capturemapvarname โ†’ JMESPath into the step's stdout JSON, for later template use.
envmapExtra environment for this step.
as_userboolRun a remote run step as the instance's login user, not root (for tools that refuse root, e.g. Globus Connect Personal).

outputs.<name>.source is local_capture (captured by a local step) or pushed (delivered via the push API).

Surfacing outputs โ€‹

A plugin declares the values worth reporting in its outputs: block โ€” for a service, typically its URL and login user:

yaml
outputs:
  url:
    description: "Service endpoint"
    source: local_capture
  username:
    description: "Login user"
    source: pushed

After the plugin provisions, those values are reported for the instance:

sh
spawn plugin status rstudio-server --instance analysis
Plugin rstudio-server โ€” healthy
  url:      https://analysis.abc123.spore.host
  username: ec2-user

Validate before you ship โ€‹

Lint a spec offline (no instance, no AWS) with spawn plugin validate:

sh
spawn plugin validate ./my-tool/plugin.yaml
spawn plugin validate plugins/*/plugin.yaml      # whole registry

It checks schema, semver, that the directory matches the plugin name, that step and condition types are valid for their context, and that every config template reference points at a declared parameter. The official registry runs this in CI on every change.

Contributing to the registry โ€‹

Open a PR against spore-host/spore-plugins adding plugins/<name>/plugin.yaml. CI validates it automatically; gated integration tests then install it on a real instance.

Data movement patterns โ€‹

A common companion to plugins is moving data on and off the instance around your job. The --pre-stop hook syncs results out before any shutdown โ€” TTL expiry, idle stop, or Spot interruption:

sh
spawn launch process --instance-type r7i.4xlarge --ttl 8h \
  --pre-stop "aws s3 sync /data/output s3://my-bucket/output/" \
  --command "python process.py --input /data/input --output /data/output"

For persistent shared storage across instances, mount EFS:

sh
spawn launch analysis --efs-id fs-0abc123 --efs-mount /shared \
  --command "python analyze.py --data /shared/datasets --output /shared/results"

Data written to /shared persists after the instance terminates.