Deploy your application
Operator how-to for the eight Foundational Steps of the desktop release flow: touch, upgrade link, version, make, pear-build, stage, provision, and multisig.
This is the operator reference for the desktop release flow. The conceptual picture lives in Release pipeline; the type-along path is in Ship your app and Deploy over-the-air updates. Use this page when you need exact commands for the eight Foundational Steps documented in hello-pear-electron's Foundational Steps.
Each step is independent — you can stop at any point if your project is still in proof-of-concept. The first three (touch, upgrade link, version) are setup; the next four (make, pear-build, stage, provision) are the release cycle you repeat for every shipment; the final step (multisig) gates production.
0. Touch and seed
Mint a new pear:// link backed by a fresh Hypercore:
pear touch
# pear://qxenz5wmspmryjc13m9yzsqj1conqotn8fb4ocbufwtz9mtbqq5oAnnounce the link on the swarm:
pear seed pear://qxenz5wmspmryjc13m9yzsqj1conqotn8fb4ocbufwtz9mtbqq5oRun pear seed on at least one always-online machine (a small VPS or a CI runner with pear seed --daemon works) so peers can fetch updates while developer laptops are asleep. On every other machine that should reseed, run the same command — additional seeders raise availability and shorten the first-connection latency.
1. Set the upgrade link
Every shipped build polls a pear:// link from its package.json upgrade field. Set it once per release line:
npm pkg set upgrade=pear://qxenz5wmspmryjc13m9yzsqj1conqotn8fb4ocbufwtz9mtbqq5oOr edit package.json directly:
{
"version": "1.0.0",
"upgrade": "pear://qxenz5wmspmryjc13m9yzsqj1conqotn8fb4ocbufwtz9mtbqq5o"
}For production, set upgrade to the multisig link (step 7c). For internal preview, set it to the provision link (step 6). For developer-team builds, set it to a stage link (step 5). The link decides which release line a binary follows.
2. Version
pear-runtime only swaps the application drive if the new build advertises a higher version:
npm version [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease]npm version rewrites package.json and creates a vX.Y.Z git tag. If this is the first release, leave version at 1.0.0 and skip to step 3.
3. Make distributables
Run the per-OS make scripts:
npm run make:darwin # .app
npm run make:linux # .AppImage
npm run make:win32 # .msixThe full coverage — entitlements, code signing, notarization, MSIX publisher matching, signing certificates — is in Build desktop distributables.
The package.json checklist before a make:
authorpopulatedlicensepopulateddescriptionpopulatednameset per brandproductNameset per brandbuild/icon.icns(macOS),build/icon.ico(Windows),build/icon.png(Linux) are per brand
4. Build the deployment directory
pear-build assembles per-OS makes into the multi-architecture deployment directory layout pear stage expects. Install it with npm i -g pear-build, or invoke it once with npx pear-build.
The expected layout:
pear-chat-1.0.1/
├─ package.json
└─ by-arch/
└─ <platform-arch>/
└─ app/Run pear-build from outside the application folder (see stage size increases). Pass each make's output via --<platform-arch>-app and pick a --target name:
pear-build \
--package=./pear-chat/package.json \
--darwin-arm64-app ./pear-chat/out/PearChat-darwin-arm64/PearChat.app \
--darwin-x64-app ./pear-chat/out/PearChat-darwin-x64/PearChat.app \
--linux-arm64-app ./pear-chat/out/PearChat-linux-arm64/PearChat.AppImage \
--linux-x64-app ./pear-chat/out/PearChat-linux-x64/PearChat.AppImage \
--win32-x64-app ./pear-chat/out/PearChat-win32-x64/PearChat.msix \
--target pear-chat-1.0.1If you omit --target, the deployment directory is named <name>-<version> from package.json. Each make produces a single platform-arch on its own machine — transfer the build outputs to the build machine first, then assemble.
5. Stage
pear stage <upgrade-link> <deployment-directory> syncs the directory into the Hypercore behind the link.
Always dry-run first and read the file-by-file diff:
pear stage --dry-run pear://qxenz5wmspmryjc13m9yzsqj1conqotn8fb4ocbufwtz9mtbqq5o ./pear-chat-1.0.1Look for:
- Files you expect to ship
- No surprise additions (
.DS_Store, editor swap files, secrets, deployment directories nested inside the app) - Sensible byte counts
Run the real stage:
pear stage pear://qxenz5wmspmryjc13m9yzsqj1conqotn8fb4ocbufwtz9mtbqq5o ./pear-chat-1.0.1The output should match the dry-run output diff for diff. If anything differs, abort and investigate before continuing.
Stage checklist
- Always dry-run first.
- Check the dry-run output diff.
- Compare the live stage output to the dry-run output diff.
Confirm stage updates
Open the app on a second machine. The seeding process from step 0 should show peers joining as instances connect. To verify the update flow:
- Change a file.
npm version patch(step 2).npm run make:<os>(step 3).pear-build(step 4).pear stageagain.
If upgrade points to the stage link, every connected instance should fire the updated event from pear.updater (and, in the template, surface an "Update ready!" button).
6. Provision
pear stage is append-only — every file you ever staged stays in the core history. pear provision resyncs from a stage link onto a target link while compacting deletions, producing a leaner drive for prerelease distribution.
The signature is:
pear provision <versioned-source-link> <target-link> <versioned-production-link>A versioned link has the form pear://<fork>.<length>.<key>. Read the <length> from the most recent pear stage output line.
The target link is a fresh pear:// from step 0. While bootstrapping, set the third argument (the versioned production link) to pear://0.0.<target-key>; after a multisig drive exists, use pear://<fork>.<length>.<multisig-key> instead.
Dry-run first:
pear provision --dry-run \
pear://0.1079.qxenz5wmspmryjc13m9yzsqj1conqotn8fb4ocbufwtz9mtbqq5o \
pear://q9sopzoqgas9usoiq7uzkkwngm5pzj4zo3n4esjwwbmw6offis8o \
pear://0.0.q9sopzoqgas9usoiq7uzkkwngm5pzj4zo3n4esjwwbmw6offis8oRead the diff and run for real:
pear provision \
pear://0.1079.qxenz5wmspmryjc13m9yzsqj1conqotn8fb4ocbufwtz9mtbqq5o \
pear://q9sopzoqgas9usoiq7uzkkwngm5pzj4zo3n4esjwwbmw6offis8o \
pear://0.0.q9sopzoqgas9usoiq7uzkkwngm5pzj4zo3n4esjwwbmw6offis8oSwitching builds to provision
Update package.json:
upgrade→ the provision link (step 1)version→ bumped (step 2)
Make, pear-build, and stage again. Then provision a second time so the source link points at the provision target:
pear provision pear://0.1080.qxenz5wmspmryjc13m9yzsqj1conqotn8fb4ocbufwtz9mtbqq5o pear://q9sopzoqgas9usoiq7uzkkwngm5pzj4zo3n4esjwwbmw6offis8o pear://0.0.q9sopzoqgas9usoiq7uzkkwngm5pzj4zo3n4esjwwbmw6offis8oProvision checklist
- Always dry-run first.
- Check the dry-run output diff before provisioning.
7. Multisig
Production releases are gated by a multisig drive — a Hypercore whose write access is controlled by a quorum of signing keys instead of one machine's private key. This is what removes the single point of failure from a typical desktop release flow.
There are three setup steps and four release steps.
7a. Create signing keys
Every signer runs:
npm i -g hypercore-sign
hypercore-sign-generate-keysThe public key lands at ~/.hypercore-sign/default.public. Share it with whoever is assembling the multisig config.
7b. Create the multisig config
Write multisig.json with every signer's public key, a quorum count, an arbitrary namespace, and the provision link's key as srcKey:
{
"type": "drive",
"publicKeys": ["pubkey-signer-1", "pubkey-signer-2", "pubkey-signer-3"],
"namespace": "pear-chat",
"quorum": 2,
"srcKey": "q9sopzoqgas9usoiq7uzkkwngm5pzj4zo3n4esjwwbmw6offis8o"
}This example needs two of three signers to release.
7c. Set upgrade to the multisig link
Install hyper-multisig-cli and compute the multisig link:
npm i -g hyper-multisig-cli
hyper-multisig link
# pear://69qwbihxj4c8te15wt3skj4j1g3ufmbo3mperedjqr1hb55mspooSet upgrade to that link (step 1), bump version (step 2), make, pear-build, and pear stage. Then provision again with the multisig link as the third argument:
pear provision pear://0.1082.qxenz5wmspmryjc13m9yzsqj1conqotn8fb4ocbufwtz9mtbqq5o pear://q9sopzoqgas9usoiq7uzkkwngm5pzj4zo3n4esjwwbmw6offis8o pear://0.0.69qwbihxj4c8te15wt3skj4j1g3ufmbo3mperedjqr1hb55mspooThe provision drive's upgrade field now points at the multisig drive.
7d. Prepare the multisig request
hyper-multisig request <length><length> is the current length of the provision Hypercore (from the most recent pear stage output). hyper-multisig refuses to issue a request if the source drive is not healthily seeded — reseed on more peers before retrying.
7e. Sign
Each signer runs:
hypercore-sign <signing request>and shares the response. Once a quorum of responses exists, the build is ready to verify.
7f. Verify
hyper-multisig verify [--first-commit] <signing request> <response-1> <response-2> ...--first-commit is required only the first time you commit to a fresh multisig drive. Never commit a request that fails verify.
7g. Commit
hyper-multisig commit [--first-commit] <signing request> <response-1> <response-2> ...The commit waits for at least two seeders to fully download the multisig drive before signalling success. Watch the output:
Committing the core...
Committed the core (key <target key>)
Waiting for remote seeders to pick up the changes...
Please add this key to the seeders now. The logs here will notify you when it is picked up by them. Do not shut down until that happens.Once two seeders confirm, it is safe to Ctrl+C.
Do not abort a running commit. If a commit gets interrupted (crash, power loss), the production build is stuck in an intermediate state; run the commit again as soon as possible. From the second commit onwards, a misuse can corrupt the production build — if hyper-multisig ever errors with INCOMPATIBLE_SOURCE_AND_TARGET, do not work around it; reseed the provision on other peers and retry.
The commit does not have to run on the same machine that prepared the request. The request and responses are enough.
Disabling updates
Pass --no-updates per run to skip the OTA check (matches hello-pear-electron's default npm start script):
npm start -- --no-updatesTo disable updates for every run of a build, spread the package config into new PearRuntime({ ... }) and set updates: false:
const pkg = require('../package.json')
const pear = new PearRuntime({
...pkg,
updates: false,
// ... other options
})This is useful for kiosk binaries or air-gapped distributions where OTA must be off.
Release lines and multiple stage drives
A typical project keeps several stage drives in parallel — development, staging, rc, plus any number of custom lines for experiments or hotfixes. Each line has its own pear:// link from step 0 and its own seeded reseeders.
Production has exactly one chain: rc → provision (prerelease) → multisig (production). The rc build's upgrade field points at the multisig link, so rc builds do not receive OTA updates — every rc iteration requires a new installer. The Release pipeline explanation has the conceptual picture.
To bootstrap a new release line:
- Run step 0 to mint and seed a new link.
- Run step 1 in a build dedicated to that line.
- Skip step 2 — the line starts at the current
version. - Run steps 3 and 4 to produce a build.
- The build is now pointed at the new line; share it with whoever should follow that line.
See also
- Release pipeline — conceptual diagrams for stage, provision, multisig, and release lines.
- Build desktop distributables — per-OS makes, signing, notarization, and MSIX publisher details.
- Desktop release npm scripts — the
package.jsonscripts that drive this flow. - Troubleshoot desktop releases — OTA, seeding, deployment-folder, and lost write-access pitfalls.
- Release pipeline glossary — terminology.
pear-runtimereference — the runtime that drives OTA on the client.- Storage and distribution — how releases reach users.