Skip to content

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.

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).

  1. Generate a signing key, once per publisher identity:

    Terminal window
    mixlar-sdk keygen --key-id my-studio
  2. Pack and sign in one step:

    Terminal window
    mixlar-sdk pack --sign my-studio
  3. Confirm the signature checks out:

    Terminal window
    mixlar-sdk verify

The sections below cover each command in detail.

Generates an ed25519 signing keypair.

Terminal window
mixlar-sdk keygen --key-id KEY_ID --out PATH
Terminal window
$ 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.key
Public 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 to
mixlar.signing.PUBLISHER_KEYS. Until then, 'mixlar-sdk verify' will report
packages signed with this key as INVALID (unknown publisher) — that's
expected 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.

Terminal window
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)).

Terminal window
$ 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.

Terminal window
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
Terminal window
$ 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

Checks a package’s signature against the pinned publisher keyset.

Terminal window
mixlar-sdk verify [pkg]
Terminal window
$ mixlar-sdk verify
[error] INVALID — unknown publisher or tampered package

A successful check against a pinned key instead prints:

[ok] VALID — signature verifies against a pinned publisher key

Exit code is 1 only on INVALIDUNSIGNED and VALID both exit 0, since an unsigned package is a normal, expected state, not a failure.

Until a self-serve key-issuance flow exists, pinning a key is a manual, two-sided edit:

  1. mixlar-sdk keygen --key-id <your-id> and note the printed public key (base64, 32 raw bytes).
  2. Send <your-id> and the public key to a Mixlar Labs maintainer.
  3. 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 by mixlar-sdk verify and headless tooling) and the app’s own _PUBLISHER_KEYS dict.
  4. 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.

Two related commands for local iteration, worth knowing about even though they don’t touch signing:

  • mixlar-sdk link [pkg] --plugins-dir DIR --symlink copies (or, with --symlink, tries to symlink) a package into the app’s plugins directory (%APPDATA%\Mixlar\config\plugins by default). Copying is the default because Windows symlinks usually need Administrator/Developer Mode.
  • mixlar-sdk dev [pkg] --plugins-dir DIR --interval SECONDS validates 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.

Packs (if needed) and uploads a .mixplugin to the marketplace.

Terminal window
mixlar-sdk publish [pkg] --url URL --token TOKEN

Reads --url/MIXLAR_MARKETPLACE_URL and --token/MIXLAR_DEV_TOKEN.

Terminal window
$ mixlar-sdk publish
[warn] the marketplace upload endpoint is being reworked — no URL
configured. 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.