Releasing a builtin plugin
July 21, 2026 · View on GitHub
English | 简体中文
Maintainer guide for cutting an official release of a Motrix builtin plugin. For building and testing, see Development in the README.
Background
This repo was extracted out of motrix-turbo (per the "Builtin Plugin
Independent Update" design, 2026-07-18) so each builtin plugin can be
versioned and released on its own tag instead of waiting for a full app
release. motrix-turbo downloads the signed .moext files published here
through its lockfile-pinned scripts/fetch-builtins.mjs step
(scripts/builtins.lock.json pins each plugin's tag and sha256).
This repo is the sole source of truth for builtin plugin code. After
releasing a new plugin version, bump the corresponding lockfile entry in
motrix-turbo so the app picks it up.
Release process
- Bump the
versionfield inplugins/<id>/motrix-plugin.json. - Tag the commit as
<id>@<version>, e.g.:git tag motrix.url-resolver@1.1.0 git push origin motrix.url-resolver@1.1.0 - The tag push triggers
.github/workflows/release.yml, which runs two jobs:- build (no secret): asserts the manifest version matches the tag,
builds, typechecks, and tests the whole workspace, packs just that
plugin into
.moext+.metadata.json, and hands them over as a workflow artifact; - sign (bound to the
plugin-signingEnvironment — a reviewer must approve the run): asserts the tag commit is an ancestor ofmain, signs the pre-built.moext(Ed25519 detached signature) using only scripts checked out frommain, cross-checks the signature againstkeys/signing-key.pub.pem, and publishes a GitHub Release with the.moext,.moext.sig, and.metadata.jsonattached.
- build (no secret): asserts the manifest version matches the tag,
builds, typechecks, and tests the whole workspace, packs just that
plugin into
Tags must match motrix.<name>@<semver> — anything else is rejected by
scripts/parse-tag.mjs before packing starts.
Signing key policy
- The Ed25519 private signing key lives ONLY in the
MOTRIX_PLUGIN_SIGNING_KEYGitHub Actions secret, scoped to theplugin-signingEnvironment;release.ymlbinds every release run to that Environment. The key is never committed, never present on a developer machine long-term, and never logged. - Two protections govern who can mint a release: the protected-tag ruleset
for
motrix.*@*(only repo admins can create, move, or delete release tags) and the Environment's required-reviewer rule (a reviewer must approve each release run before any step executes). Both live in GitHub configuration, not in workflow code — when in doubt, re-verify withgh ruleset listandgh api repos/<owner>/<repo>/environments; the dated status note lives in the comments ofrelease.yml. - Key rotation: run
scripts/keygen.mjsto generate a fresh keypair, store the new private key material only in the GitHub secret, replacekeys/signing-key.pub.pemwith the new public key, and discard the local private-key file. - The corresponding public key is committed at
keys/signing-key.pub.pem— the canonical copy consumers verify against.motrix-turbopins its own copy atscripts/builtins-signing.pub.pem; itsfetch-builtins.mjsverifies every fetched.moext's Ed25519 signature (in addition to the lockfile sha256) at every source before installing.
Verifying an artifact
With a release's .moext and its .moext.sig in the same directory:
node scripts/verify.mjs <id>-<version>.moext --pub keys/signing-key.pub.pem