Deploy over-the-air updates
Fourth of four onboarding steps: run the installed app, ship a second version, watch the OTA cycle, and preview pear provision and multisig.
This is part 4 of 4 in the getting started path. You start from the v1.0.1 you shipped in part 3: a pear:// link, an active pear seed, and a packaged build that points its upgrade field at the same link. From here you put the live OTA cycle to work — run the installed app, change something visible in the renderer, stage a second version, and watch the OTA update event lifecycle fire. The last two steps preview pear provision and multisig, the production-grade replacements for pear release.
Full production-ready reference: hello-pear-electron. The complete version of this chat lives at holepunchto/hello-pear-electron — Holepunch's official Electron template, the same shape Keet and PearPass ship. Clone it any time to see the finished structure or to crib code.
You do not need cosigners, a Windows machine, or Apple signing credentials to follow along. The deeper guides cover the production-only material:
- Deploy a Pear desktop app — every command, every release line.
- Build desktop distributables — code-signing, notarization, MSIX publisher details.
- Release pipeline — the conceptual picture.
Before you start
You need:
- The shipped v1.0.1 from part 3 — ship. That part covered
pear touch, theupgradelink,npm version patch,electron-forge make,pear-build,pear stage, andpear release. - The
pear seedfrom part 3 still running. Without an active seeder, peers cannot fetch the new version. - The same
pearandpear-buildCLIs from part 3 (npm i -g pear pear-buildornpx ...).
Open the installed build
open ./pear-chat/out/PearChat-darwin-arm64/PearChat.app
# Linux: ./pear-chat/out/PearChat-linux-x64/PearChat.AppImage
# Windows: install PearChat-1.0.1.msix and launch from Start menuThe header shows v1.0.1. Send a few chat messages so you can confirm the transcript survives the restart. Leave the app open.
This must be the installed .app, not npm start. The dev script forwards --no-updates, which intentionally disables OTA. Only the packaged build polls for updates.
On macOS, the first launch may prompt Gatekeeper because this build is unsigned. Right-click the .app → Open → Open to bypass it once. Production signing and notarization is covered in Build desktop distributables.
Make a visible change
Edit renderer/index.html and change the header so the new version is obviously different:
- <h1 class="text-sm font-semibold">Pear chat</h1>
+ <h1 class="text-sm font-semibold">Pear chat — v2</h1>Any visible tweak works (text, color, emoji). The point is to confirm the renderer asset on disk swaps after the update.
Bump, make, build, stage, release
Same release cycle as part 3, with a patch bump. Run from your terminal in the project root — your packaged app keeps running in the GUI:
Bump the version
npm version patch # 1.0.1 → 1.0.2Make the distributables
npm run make:darwinBuild the deployment directory
cd ..
pear-build \
--package=./pear-chat/package.json \
--darwin-arm64-app ./pear-chat/out/PearChat-darwin-arm64/PearChat.app \
--target pear-chat-1.0.2Stage and release the deployment directory
pear stage pear://<your-pear-link> ./pear-chat-1.0.2
pear release pear://<your-pear-link>On every cycle, the --target value and the deployment dir you pass to pear stage must match the version you just bumped to. The example above assumes you went from 1.0.1 to 1.0.2; if you've already iterated past that, use pear-chat-<current-version> everywhere. Otherwise pear-build writes vX into a directory named after vY, which makes the next bump confusing to debug. You can also omit --target and pear-build will auto-name the dir as <name>-<version> from package.json.
Watch the OTA cycle fire
Within seconds the running app reacts. The events come from part 2's wiring (pear.updater.on('updating'|'updated') → preload bridge → renderer):
- The version label in the header flips from
v1.0.1toupdating…. - The yellow Update ready! button appears.
- Click it.
applyUpdate()swaps the application drive,appAfterUpdate()restarts the process. - The header now reads "Pear chat — v2" and the version label shows
v1.0.2. The Corestore-backed chat transcript replays from disk.
If nothing happens for more than a minute, check that pear seed is still running and that upgrade in package.json matches the link you staged to — see App did not update.
Part 2 wires the runtime with delay: 0, so updates fire as soon as the new content reaches the local drive. The default pear-runtime-updater delay is a random value up to one hour after the 60s boot grace period — great for production (seeders avoid a thundering herd) but invisible in a tutorial. If you keep that default and the Update ready! button never appears, restart the app to reset the grace period — see Tune PearRuntime delay for live OTA visibility.
The walkthrough stops here. The remaining steps — provision and multisig — change real pear:// links and require coordinating with at least one other signer, so a tutorial walkthrough is the wrong format. The next two sections summarize what each command does and link to the production guides.
Provision (production preview)
A staged link contains the full append-only history — every file you ever staged, even ones you later deleted. For production releases you usually want a fresh, minimal core. pear provision resyncs a stage link onto a new link, dropping history along the way:
pear provision <versioned-stage-link> <target-link> <versioned-production-link>A versioned link has the form pear://<fork>.<length>.<key>. You get <length> from the pear stage output for the run you want to ship.
Provisioning is what stakeholders, QA, and dogfooders install. The full walkthrough — including the recovery flow if a stage or provision link is lost — lives in Deploy a Pear desktop app.
Multisig (production releases)
A multisig drive is a Hypercore where write access is gated by a quorum of signing keys instead of a single owner machine. This is what production Pear apps use so no single laptop can push a malicious update.
The setup, in 30 seconds:
- Every signer generates a keypair:
npm i -g hypercore-sign && hypercore-sign-generate-keys. Each signer's public key goes in a shared list. - One person writes a
multisig.jsonwith the signers' public keys, a quorum (e.g. 2 of 3), a namespace, and the provision link's key assrcKey. npm i -g hyper-multisig-cli && hyper-multisig linkoutputs the newpear://link backed by the multisig config. Set this as yourupgradefield.
The release flow becomes:
hyper-multisig request <length> # prepare a signing request
hypercore-sign <signing request> # each signer runs this; shares response
hyper-multisig verify <request> <responses...>
hyper-multisig commit <request> # commits when quorum is reachedRelease pipeline and Deploy a Pear desktop app cover the full quorum lifecycle, key rotation, recovering from lost write-access, and the release lines pattern (development → staging → rc → prerelease → production) that real teams use.
What you've learned
You now have an end-to-end mental model for iterating a Pear Electron app:
| Stage | What it is | Reversible? |
|---|---|---|
| Iterate loop | npm version patch → make → pear-build → pear stage → pear release | Yes — re-release a different length |
| OTA cycle | pear.updater emits updating/updated; renderer button calls applyUpdate + appAfterUpdate | Yes — restart loads the previous bundle until the next swap |
pear provision | Resync onto a clean prerelease core | History is permanent on the new core |
| Multisig commit | Quorum signs and publishes | Cryptographically committed |
Every release iteration after part 3 is the same six commands: npm version patch, npm run make:<os>, pear-build, pear stage --dry-run, pear stage, pear release. Once provision and multisig are wired, pear provision and the four-step multisig flow replace pear release.
Where to go next
- Deploy a Pear desktop app — the canonical how-to with every command, every flag, and every recovery procedure.
- Build desktop distributables — code-signing, notarization, MSIX publisher details.
- Troubleshoot desktop releases — "the app did not update", lost write-access, stage size blowups, OTA polling cadence.
- Release pipeline — the conceptual picture, deployment layers, and release lines.
- Release pipeline glossary — terminology.
hello-pear-electron— the upstream template every snippet in this getting started path is based on.