Skip to main content
A plugin is a signed zip with the .adosplug extension. This page covers the build pipeline, the publishing options, and how operators install what you ship.

Build the GCS bundle

If your plugin has a GCS half, build it first. From your plugin folder (under extensions/<your-plugin>/ in the ADOSExtensions monorepo):
pnpm build emits gcs/plugin.bundle.js. Verify it exists before packing. An agent-only plugin has no GCS bundle and skips this step.

Generate a signing key

Mint an Ed25519 keypair with the agent CLI. The signer id you choose here is written into every archive you sign and must match a public-key filename on the agent (covered under Trust the publisher):
This writes two files into ./keys:
  • my-publisher-2026-A.pem (public key, mode 0644)
  • my-publisher-2026-A.priv.pem (private key, mode 0600)
It also prints a short fingerprint so an operator can cross-check the key that ends up on their agent. Keep the private half offline. Never commit a *.priv.pem file to git.
keygen is a developer aid. A production publisher key deserves a hardware token or air-gapped workflow for the private half, not a one-liner on your laptop.

Pack and sign

The first-class path packs a plugin directory and signs it in one step with ados plugin sign:
The command stages the plugin contents, computes the canonical payload hash (a SHA-256 over the sorted list of each archive entry’s path and SHA-256, so every file is bound, not just the manifest), signs that 32-byte digest with your Ed25519 key, and writes a SIGNATURE file into the archive with two lines: the signer key id, then the base64 signature. It also writes a .sha256 checksum next to the archive and prints the signer id, signature, and SHA-256. The --signer-id value must equal the stem of the public-key filename installed on the agent. If they do not match, the install is rejected with the signature-invalid exit code (3).
Run ados plugin lint dist/com.example.my-plugin-1.0.0.adosplug before you publish. The linter runs static checks on the packed archive and reports manifest or packaging problems early.

Pack and sign in the monorepo (CI)

The ADOSExtensions monorepo ships shell scripts that the release workflow uses. They split the pack and sign into two steps and read the signing key from the environment, which suits GitHub Actions secrets. Pack first. pack.sh lives at the monorepo root; pass either the folder name or a path:
pack.sh builds the GCS bundle if a gcs/ folder is present, reads the plugin id and version from manifest.yaml, and zips into dist/<plugin-id>-<version>.adosplug. Packing produces an unsigned archive. Then sign the packed archive with your Ed25519 private key:
The signed archive lands at dist/com.example.my-plugin-1.0.0.signed.adosplug. sign.sh computes the same canonical payload hash as ados plugin sign and writes the same two-line SIGNATURE file, so an archive from either path verifies identically on the agent. ADOS_SIGNING_KEY_INLINE=1 accepts a base64 key in the env var itself (used by GitHub Actions secrets):

Publish on GitHub

Tag your release with <plugin-folder>-v<version>:
The reference workflow at altnautica/ADOSExtensions/.github/workflows/release.yml runs tests, packs, signs, and uploads the .signed.adosplug to a GitHub release. Copy it into your own repo and adjust the tag prefix. Operators install by downloading the .adosplug from your release page.

Install on the GCS

In Mission Control -> Settings -> Plugins -> Install plugin:
  1. Drag the .adosplug into the dialog or click Choose file.
  2. The host parses the archive and shows a manifest preview: name, version, signer, risk band, requested permissions.
  3. Approve required permissions (pinned on) and any optional permissions you want to grant.
  4. Click Install.
The plugin appears in the plugin list; its panels mount in their declared slots.

Install on the agent

ados plugin install parses the archive, verifies the signature, then prints the requested permissions and prompts for approval before the install completes. Pass --yes to skip the prompt for low and medium-risk plugins (it refuses high and critical-risk plugins, which must be approved interactively). After install, enable starts the plugin and remove uninstalls it. Logs are written to /var/log/ados/plugins/<id>.log; ados plugin logs <id> tails that file.

Trust the publisher

A plugin signed by a key the agent does not know is rejected with the signature-invalid exit code (3) and the message signer <id> not in /etc/ados/plugin-keys/. Copy the publisher’s public key (the .pem half from keygen, not the .priv.pem) into the agent’s trust directory, naming the file after the signer id:
The signer id in the archive’s SIGNATURE file must match a public-key filename in /etc/ados/plugin-keys/. To retire a key, add its signer id to /etc/ados/plugin-revocations.json; plugins signed with a revoked id refuse to load. To install an unsigned archive during local iteration, pass --allow-unsigned:
Unsigned installs are a developer convenience; a production agent should only install signed archives from trusted publishers.

Registry

A hosted registry is on the roadmap. Once it lands, publishing becomes a one-click flow from the GCS, and operators install by search instead of by file. Until then GitHub Releases is the recommended channel; when the registry opens, your existing release process keeps working unchanged. The registry just adds a metadata overlay.