Skip to main content
An extension is a signed package (.adosplug) that adds behaviour to a node: a driver, a flight behaviour, a Mission Control panel. The SDK and the CLI call these packages plugins. This page covers how the agent installs and runs them. To write one, start with Your first plugin. The plugin host, ados-plugin-host, runs on every node profile whether or not an extension is installed; with none installed it idles. Each extension’s agent half runs as its own service unit: a systemd unit in ados-plugins.slice on Linux, a per-user LaunchAgent on macOS. The agent never loads extension code into its own processes.

The ados plugin CLI

The full command set (info, perms, lint, test, pin, auto-update, sign, keygen and more) with flags and exit codes is in the Plugin CLI reference.

Install an extension

A URL must be HTTPS on GitHub (github.com or its release asset hosts); every redirect is checked against the same list. The agent pins the archive’s SHA-256 between the preview and the install, so it installs the bytes you reviewed. A catalog id installs the version pinned in the agent’s bundled catalog. To install a specific release, use its URL. The CLI prints the plugin id, version, risk and the requested permissions, then asks once: Approve permissions?. Approving installs the extension and grants every requested permission; declining installs nothing. Change single grants afterwards with ados plugin perms <id> --grant <perm> or --revoke <perm>. --yes skips the question for scripts, but refuses a high- or critical-risk extension. Approve those interactively. Mission Control installs the same way from a node’s Extensions page; see Install extensions from Mission Control.

Signatures and trust

Every .adosplug contains a SIGNATURE entry: a signer id and a base64 Ed25519 signature over the archive’s canonical payload hash (a SHA-256 over every entry except SIGNATURE).
  • The agent loads /etc/ados/plugin-keys/<signer-id>.pem and verifies the signature against it.
  • It refuses an archive that is unsigned or does not verify, whose signer is listed in /etc/ados/plugin-revocations.json, or whose archive digest is listed there.
  • Trust follows the key that verifies the signature. Nothing in manifest.yaml names or vouches for a signer.
  • First-party privileges (an inline Mission Control module, resources.class: heavy, an agent half on the unsandboxed macOS backend) go only to a verified signer on the agent’s built-in first-party list, altnautica-2026-A and altnautica-2026-B. Those key bytes are pinned in the agent, so a key file dropped in under a first-party name verifies only as a third party.
  • With ADOS_PLUGIN_REQUIRE_SIGNED=0 (developer mode) an unsigned or unverifiable archive installs as unsigned and gets no first-party privileges.
Enrolling your own keys, rotation and revocation are covered in Signing keys and Revocation and incidents.

Permissions

Every extension declares the capabilities it needs in manifest.yaml:
The agent checks each call against the grants. An extension that sends MAVLink without a mavlink.write grant gets capability_denied back and the call never runs. Each manifest also declares a risk level, one of four: low, medium, high or critical. The full capability list and what each one allows are in Agent capabilities.

Built-in extensions

Three extensions ship inside the agent package. They are not installed by default; list them with ados plugin list --all and install one by id, for example ados plugin install io.altnautica.geofence. A built-in has no archive signature (it is recorded as builtin:<id>) and runs as a sandboxed subprocess unit like any other extension. All three are low risk.
  • io.altnautica.geofence subscribes to vehicle.geofence_breach and republishes a structured alert under plugin.io.altnautica.geofence.alert. A template for turning a vehicle event into a notification.
  • io.altnautica.telemetry-logger subscribes to seven lifecycle topics (armed, disarmed, mode change, battery low, geofence breach, agent ready, agent shutdown) and writes one structured line per event to its log.
  • io.altnautica.mavlink-inspector folds vehicle state (mode, armed state, battery and the last event time) into a snapshot and republishes it under plugin.io.altnautica.mavlink-inspector.snapshot.

Isolation and restarts

On Linux each extension’s unit runs in ados-plugins.slice with cgroup CPU, memory and process limits from the manifest’s agent.resources block, or the agent’s defaults when the manifest sets none. Its stdout and stderr go to a log file under /var/log/ados/plugins/, which ados plugin logs reads. On macOS each extension is a per-user LaunchAgent; launchd enforces neither the sandbox nor the limits, so the agent installs only first-party agent halves there. A unit that exits with an error is restarted by systemd every 2 seconds with no retry limit. Stop a crash-looping extension with ados plugin disable <id>.

Install across a fleet

Install per node. In Mission Control, open each node’s Extensions page, or pick a node from Settings → Extensions. From your own tooling, run ados plugin install over SSH on each node. There is no fleet-wide install.

Next steps

Extensions catalog

The first-party extensions and their permissions.

Install from Mission Control

The per-node Extensions page and the install review.

Plugin CLI reference

Every ados plugin command and exit code.

Your first plugin

Write and sign your own.
Last modified on October 11, 2026