Plugin binaries and the signed catalog consumed by Easy CSV's in-app plugin
installer (design 023 in the main repo:
easy-csv/docs/design/023_plugin-repository-and-in-app-install.md).
Easy CSV ships no plugin binaries. The app fetches catalog.json from this
repository's latest release, verifies its minisign signature, downloads the asset
for the current platform, checks the sha256 pinned in the catalog, and only then
writes it next to the app.
catalog.json generated by scripts/build-catalog.mjs (release asset)
catalog.json.sig base64 minisign signature of catalog.json (release asset)
schema/ JSON Schema, validated in CI
plugins/<name>/
plugin.json metadata + per-platform upstream recipe (hand maintained)
README.md provenance, license, how the binary is obtained
scripts/
build-catalog.mjs dist/<platform>/* -> catalog.json (size + sha256)
sign-catalog.mjs catalog.json -> catalog.json.sig
.github/workflows/ build/stage -> sign -> publish
One version per plugin, uniform across every platform it publishes — the app
shows a single version per plugin, so a catalog must never mix versions. Each
assets.<platform> entry pins file, size, sha256 and one or more urls
(tried in order; the hash comes from the signed catalog, so mirrors and proxies
cannot break integrity).
Platform keys are exactly the app's PLATFORM_DIR values and must not be
invented:
windows-x86_64, macos-aarch64, macos-x86_64, linux-x86_64-gnu,
linux-aarch64-gnu
- Bump
version(and the upstream reference) inplugins/<name>/plugin.json. - Run the
releaseworkflow (Actions → release → Run workflow). It stages binaries for all five platforms, buildscatalog.json, signs it, and publishes:<name>-v<version>releases holding the binaries,- a
catalog-<date>release holdingcatalog.json+catalog.json.sig, created with--latestso the app's/releases/latest/download/catalog.jsonendpoint resolves to it.
- Publish the plugin releases before the catalog release — the catalog pins their asset URLs.
The repository must stay public: the app downloads release assets with no credentials. A private repository would require shipping a token inside the app.
先把密钥备份掉再谈别的:见
KEY-MANAGEMENT.md—— 它是这套链路里 唯一不可重建的东西,文档里有备份清单、换设备步骤和轮换流程。
The catalog is signed with a dedicated minisign key, separate from the app updater's key, so a compromise of this repository's CI cannot forge an application update.
- Private key:
~/.tauri/easycsv-plugins.keyon the maintainer's machine; in CI it is thePLUGIN_SIGNING_KEYsecret (contents of that file). Losing the local file alone does not stop releases — CI can still sign — but its value can never be read back out of a secret, so a real backup is the only way to keep signing from a new machine. - Public key: committed by the app as
src-tauri/plugin-signing.pub. It is what the app verifies against — treat it like the updater pubkey. It may hold several keys, one base64 line each, so a rotation can be shipped before it is needed. - Where is the key, and is it the right one?
node scripts/key-status.mjs, andnode scripts/key-status.mjs --proveto actually sign a probe and verify it against this repository's public key.
# sign locally (Tauri CLI writes the base64 form the app expects)
node scripts/sign-catalog.mjs --tauri ../../easy-csv/node_modules/.bin/tauri
# audit it with no extra tooling — the same crate the app verifies with
APP=../easy-csv
rustc --edition 2021 -L dependency=$APP/src-tauri/target/debug/deps \
--extern minisign_verify=$(ls $APP/src-tauri/target/debug/deps/libminisign_verify-*.rlib | head -1) \
scripts/verify-signature.rs -o /tmp/verify-signature
/tmp/verify-signature catalog.json catalog.json.sig plugin-signing.pub
# or with the minisign CLI (the .sig is base64-wrapped, so unpack it first)
base64 -d catalog.json.sig > /tmp/catalog.minisig
minisign -V -p plugin-signing.pub -m catalog.json -x /tmp/catalog.minisig# 1. stage binaries the app can actually download
mkdir -p dist/windows-x86_64
cp ../easy-csv/src-tauri/resources/plugins/windows-x86_64/*.exe dist/windows-x86_64/
# 2. build a catalog whose URLs point at a local server
node scripts/build-catalog.mjs --base-url http://127.0.0.1:8099 --out catalog.local.json
node scripts/sign-catalog.mjs catalog.local.json
# 3. serve it (dist/ and the catalog must be reachable)
python -m http.server 8099The app only permits https plus a loopback exception in debug builds, so this
path is for development only. For the same reason the schema requires https://
asset URLs: a locally built catalog is deliberately not schema-valid, and CI
validates only the official one.
Copy the binary into <data dir>/plugins/<platform>/ using exactly the file
name from the catalog (xan.exe on Windows, xan elsewhere, executable bit
set). The app prefers that directory over PATH. See the main repo's
src-tauri/resources/plugins/readme.md.
This repository redistributes third-party binaries. Every plugin directory records its upstream project and license; the redistributed files keep their original license terms (xan and DuckDB are MIT).