Source of the official OpsKat extensions.
Each extension is a WASM guest built on the extension SDK that ships with OpsKat
(github.com/opskat/opskat/pkg/extsdk). For how to write one — manifest, asset
types, tools, policies, pages, testing — read
extensions/README.md
in the OpsKat repository; it is not repeated here.
extensions/<name>/
go.mod # its own Go module: github.com/opskat/extensions/<name>
main.go ... # the guest; registrations live in init()
*_test.go # unit tests, driven through opskat.TestHost
manifest.json # name, version, hostABI, capability grants
SKILL.md # what the model reads about the extension (optional)
locales/ # en.json, zh-CN.json
frontend/ # page sources (optional)
Makefile # `build` produces dist/, `test` runs the tests
dist/ # build output, git-ignored
Current extensions: elasticsearch.
make build EXT=elasticsearch # -> extensions/elasticsearch/dist/
make test EXT=elasticsearchBuilding needs Go with GOOS=wasip1 support, and Node.js with pnpm for an extension
that has a frontend/. dist/ is a complete extension directory: main.wasm,
manifest.json, SKILL.md when present, locales/, and the built page under frontend/
when the extension has one.
Either use "Install from directory" in the app's extension settings and pick
extensions/<name>/dist, or from a terminal:
opsctl ext dev "$PWD/extensions/elasticsearch/dist"Re-running make build and opsctl ext dev again hot-reloads the extension.
Extensions reach the OpsKat extension store through CI; nobody uploads anything
by hand. index.json at the repository root lists every published version, and
index.json.sig is its ed25519 signature, which the app checks before showing
the store.
- Bump the version in a pull request. Change
versioninextensions/<name>/manifest.json(MAJOR.MINOR.PATCH). Theindex-checkjob fails the pull request if the version is below the highest versionindex.jsonalready publishes for that extension; runmake index-checkto see the same result locally. - Merge. On the push to
main, thepackagejob (publish prepare, no secrets) buildsdist/withmake buildfor every extension whose manifest version is not inindex.jsonyet, zips it withmanifest.jsonat the zip root, and loads the zip through OpsKat's own extension loader to read the display name, description, icon, capabilities,hostABIandminAppVersionthe app will show. Thepublishjob (publish release), which runs no extension build code, then pushes each zip as a single-layer OCI artifact toghcr.io/opskat/extensions/<name>:<version>, records its sha256 and size, signsindex.jsonand commitsindex.jsonandindex.json.sigtomain. That commit carries[skip ci], so it does not publish again.
A version already in index.json is never rebuilt or overwritten: to ship a
change, bump the version. If any step fails, the job fails and nothing is
committed, so every version in index.json is in the registry.
The tool is tools/publish, its own Go module (go -C tools/publish run . -h):
check (the pull-request gate), prepare and release (the two CI halves),
publish (both, on one machine — make publish) and keygen. make ci runs its
tests.
Done once; the first publish after merging shows whether it worked.
- Generate the signing key pair:
go -C tools/publish run . keygen. It prints aprivate:and apublic:line, both standard base64. Never commit the private key. - Add the private key as a repository secret named
EXTENSION_INDEX_SIGNING_KEY(Settings → Secrets and variables → Actions). Without it, publishing fails with an error naming the secret. - Put the public key into the app: add it to OpsKat's built-in list of trusted index keys and ship that release. To rotate, add the new key to the app first and keep the old one listed — the app accepts a signature from any trusted key — then replace the secret.
- Make the packages public: after an extension's first publish, set
ghcr.io/opskat/extensions/<name>to public (organization → Packages → package settings → Change visibility), so the app can pull it without logging in. In the same settings, "Manage Actions access" must give this repository write access. - Let CI push to
main: Settings → Actions → General → Workflow permissions → "Read and write permissions". Ifmainis protected, allow GitHub Actions to bypass the rule, or the index commit is rejected.