.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 (underextensions/<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):./keys:
my-publisher-2026-A.pem(public key, mode 0644)my-publisher-2026-A.priv.pem(private key, mode 0600)
*.priv.pem file to git.
Pack and sign
The first-class path packs a plugin directory and signs it in one step withados plugin sign:
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:
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>:
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:- Drag the
.adospluginto the dialog or click Choose file. - The host parses the archive and shows a manifest preview: name, version, signer, risk band, requested permissions.
- Approve required permissions (pinned on) and any optional permissions you want to grant.
- Click Install.
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:
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: