pack, sign & publish
Once a package passes mixlar-sdk validate, the last mile is turning the
folder on disk into something you can hand to someone else — or to the app
itself. That’s pack, sign, verify, keygen, and (eventually) publish.
This page assumes you already have a package directory with a plugin.json
in it. If you don’t yet, start with mixlar-sdk create.
The trust model, briefly
Section titled “The trust model, briefly”Every package has a deterministic identity: package_digest(pkg_dir), a
SHA-256 hash over plugin.json (with "sig" blanked, keys sorted, compact
separators) plus the manifest’s entry .py file plus every allow-listed file
(.py/.json/.png/.txt) under widgets/, screen/, and icons/.
Hidden files and __pycache__ are excluded. This is a faithful port of the
app’s own digest routine — same byte layout, same file set, same ordering —
so a package signed with the SDK verifies correctly inside the real app.
plugin.json’s publisher/sig fields carry an ed25519 signature of that
digest. mixlar-sdk verify (and the app’s own check) resolve to one of three
states:
| result | meaning |
|---|---|
UNSIGNED |
sig is empty — falls through to the app’s hash/marketplace trust flow |
VALID |
signed, and the signature verifies against a pinned publisher key |
INVALID |
signed but unverifiable — unknown publisher id, or a tampered/mismatched package |
Only publishers pinned in both the app and the SDK’s mixlar.signing.PUBLISHER_KEYS
are auto-trusted. An unpinned key always verifies INVALID, even if the
signature is cryptographically correct — that’s intentional, not a bug (see
Getting a key pinned below).
The typical flow
Section titled “The typical flow”-
Generate a signing key, once per publisher identity:
Terminal window mixlar-sdk keygen --key-id my-studio -
Pack and sign in one step:
Terminal window mixlar-sdk pack --sign my-studio -
Confirm the signature checks out:
Terminal window mixlar-sdk verify
The sections below cover each command in detail.
keygen
Section titled “keygen”Generates an ed25519 signing keypair.
mixlar-sdk keygen --key-id KEY_ID --out PATH$ mixlar-sdk keygen --key-id my-studio[ok] generated keypair 'my-studio'[..] private key saved to: C:\Users\you\AppData\Roaming\Mixlar\keys\my-studio.ed25519.keyPublic key (share this, keep the private key secret): AjxJeR5Ns4h1GdUYEzP3biW6vWoStOG1/ydMccDFWZU=To be trusted by the app, a maintainer must add: "my-studio": "AjxJeR5Ns4h1GdUYEzP3biW6vWoStOG1/ydMccDFWZU="to plugin-verify.php / the app's _PUBLISHER_KEYS and tomixlar.signing.PUBLISHER_KEYS. Until then, 'mixlar-sdk verify' will reportpackages signed with this key as INVALID (unknown publisher) — that'sexpected for an unpinned key.The private key is written base64-encoded to
%APPDATA%\Mixlar\keys\<key-id>.ed25519.key by default (--out to choose a
different path).
You can also generate keys programmatically:
from mixlar import signing
priv_b64, pub_b64 = signing.generate_keypair()signing.save_private_key_file("my.key", priv_b64)Signs a package folder in place, writing publisher and sig into
plugin.json.
mixlar-sdk sign [pkg] --key-id KEY_ID --key-file PATH--key-id defaults to mixlar-official-1; --key-file defaults to the
conventional %APPDATA%\Mixlar\keys\<key-id>.ed25519.key path
(signing.default_key_file(key_id)).
$ mixlar-sdk sign --key-id my-studio[ok] signed as 'my-studio'[..] digest: a1b2c3d4e5f6a7b8...[..] signature: 9f8e7d6c5b4a3210...Under the hood this is signing.sign_package(pkg_dir, key_id, priv_b64): it
writes publisher and blanks sig first (both fields are covered by the
digest via that blank-sig rule), persists the manifest, computes the
digest, signs the hex digest, and writes the base64 signature back.
Builds a .mixplugin archive, optionally signing first.
mixlar-sdk pack [pkg] --out PATH --sign KEY_ID --key-file PATH| flag | default | meaning |
|---|---|---|
pkg (positional) |
current directory | package folder, resolved by find_package |
--out |
<id>-<version>.mixplugin next to the package |
output archive path |
--sign |
off | key id to sign with before packing (e.g. your publisher id) |
--key-file |
conventional key path for --sign’s key id |
private key file to use |
$ mixlar-sdk pack --sign my-studio[ok] packed C:\...\weather_widget-0.1.0.mixplugin (18,204 bytes)[..] signed with key 'my-studio'If --sign is given but no matching private key file exists, pack fails
fast with a pointer to run keygen first — it never silently produces an
unsigned archive when you asked for a signed one.
A .mixplugin is a plain zip of the package folder, with the folder itself
as the top-level entry — extracting it next to a destination reproduces the
package unchanged. No compression trickery, no embedded code execution.
Packing also:
- validates the manifest first (fails with an error rather than packing something broken)
- zips deterministically — sorted entry order, fixed 1980-01-01 timestamps — so identical inputs produce byte-identical output
Related functions in mixlar.packaging, if you’re scripting around the SDK:
| function | purpose |
|---|---|
pack(pkg_dir, out_path=None, sign_with=None) |
build the archive; sign_with = (key_id, priv_b64) to sign first |
manifest_of(mixplugin_path) |
read plugin.json straight out of the zip, no extraction |
unpack(mixplugin_path, dest_dir) |
extract with path-traversal guards (rejects absolute paths, .. segments, multi-top-level-folder zips) before touching disk |
install(mixplugin_path, plugins_dir=None) |
unpack targeting the app’s default plugins directory |
contents(mixplugin_path) |
list every entry, for inspection |
verify
Section titled “verify”Checks a package’s signature against the pinned publisher keyset.
mixlar-sdk verify [pkg]$ mixlar-sdk verify[error] INVALID — unknown publisher or tampered packageA successful check against a pinned key instead prints:
[ok] VALID — signature verifies against a pinned publisher keyExit code is 1 only on INVALID — UNSIGNED and VALID both exit 0,
since an unsigned package is a normal, expected state, not a failure.
Getting a key pinned
Section titled “Getting a key pinned”Until a self-serve key-issuance flow exists, pinning a key is a manual, two-sided edit:
mixlar-sdk keygen --key-id <your-id>and note the printed public key (base64, 32 raw bytes).- Send
<your-id>and the public key to a Mixlar Labs maintainer. - The maintainer adds one entry in two places, so both the app and any
SDK-based tooling agree on what’s trusted:
mixlar.signing.PUBLISHER_KEYS(this SDK’s copy, used bymixlar-sdk verifyand headless tooling) and the app’s own_PUBLISHER_KEYSdict. - Ship a new SDK/app release with the new entry.
Until pinned, mixlar-sdk verify correctly reports packages signed with your
key as INVALID (unknown publisher) — that’s the expected, safe default,
not something to work around.
link and dev
Section titled “link and dev”Two related commands for local iteration, worth knowing about even though they don’t touch signing:
mixlar-sdk link [pkg] --plugins-dir DIR --symlinkcopies (or, with--symlink, tries to symlink) a package into the app’s plugins directory (%APPDATA%\Mixlar\config\pluginsby default). Copying is the default because Windows symlinks usually need Administrator/Developer Mode.mixlar-sdk dev [pkg] --plugins-dir DIR --interval SECONDSvalidates once, links once, then polls the package for changes and re-syncs — stdlib-only mtime polling, 1 second by default. It keeps the linked copy fresh; getting the running app to notice a change is a separate step covered in Getting started.
publish
Section titled “publish”Packs (if needed) and uploads a .mixplugin to the marketplace.
mixlar-sdk publish [pkg] --url URL --token TOKENReads --url/MIXLAR_MARKETPLACE_URL and --token/MIXLAR_DEV_TOKEN.
$ mixlar-sdk publish[warn] the marketplace upload endpoint is being reworked — no URLconfigured. Pass --url or set $MIXLAR_MARKETPLACE_URL once it's available.This command is currently a stub.Once a URL is configured, publish packs fresh — so what’s uploaded matches
the folder on disk right now — and POSTs it multipart, preferring requests
when installed and falling back to stdlib urllib otherwise.
For where this fits in the bigger picture of getting a plugin distributed, see Distribution and the roadmap.