feat: added auto updater#540
Conversation
|
@dazzling-no-more — substantive PR. Tests pass locally (182 / 0; +13 new). Build clean. Architecture looks right. Holding the merge though, with security framing as the gate. Walking through: What I want to land vs. what concerns me: I want this. In-app updater has been on the v1.9.x roadmap (#366) and the threat model in What concerns me is the rollout mode as currently designed. From your PR description:
For the project's threat model, this is the wrong default. mhrv-rs ships to users in censored regions where supply-chain attacks would be catastrophic — flipping unsigned auto-update on by default, even with a log warning that nobody reads, would be a regression in the security posture of the binary distribution. The user-facing security guarantee today is "what you downloaded from the Telegram channel + GitHub release is what we built". An unsigned auto-updater can quietly break that. Three options to land this safely: Option A (preferred): set up the keypair first, then merge. I'll generate the minisign keypair following ETA: ~2-3 days for me to do the keypair setup + verify the signing CI job in a non-public branch first. Option B: change rollout-mode behavior to download-only, no apply. If we want to merge before keypair setup is done, change rollout mode to:
User experience parity with what we have today (download-and-manually-apply) but with an obvious upgrade path the moment the keypair is set up + a new build is shipped. This avoids the supply-chain regression but lets the code land + start collecting feedback on the desktop / Android apply paths. Option C: opt-in flag for unsigned updates.
Less attractive than B because it puts a security decision on users who shouldn't have to think about it. My recommendation: Option A. Going to do the keypair setup over the next 2-3 days. Will reply on this PR when the signing CI job is verified working in a side branch. Then we merge this PR with signing enforced from day one + no rollout window. If you'd rather B (so the code lands sooner and we can iterate on the apply paths in field-test), happy to take a follow-up commit on this branch that restricts apply-button visibility to Smaller line-by-line stuff (review pass): The architecture is clean — separate Two small items I noticed:
On the I read it. Threat-model recap is accurate, the key-rotation procedure is reasonable. The "deliberately not-yet-automated" note on rotation is honest — automating rotation properly needs key-versioning in the binary which is a separate design call. Leaving as manual is fine for now. Net assessment: this is the right design + good code. Just need to gate the merge on signing being fully set up so we don't ship an unsigned-by-default updater to the userbase. Will close the loop when keypair is ready. Thanks for the substantial work — this is the most important security-adjacent infrastructure feature in the v1.9.x roadmap. [reply via Anthropic Claude | reviewed by @therealaleph] |
|
I prefer option A. Test plan status — local verification done before pushing the branch. Covered locally: ✓ Desktop apply — Windows: .new → spawn → rename → re-exec chain ✓ Rollout mode: MHRV_UPDATE_PUBKEY wiring verified across all three ✓ stage_from_archive end-to-end: synthetic tar.gz containing a binary ✓ Unit suite: 181 passed, 0 failed (cargo test --release). New tests Not covered locally — folding into your Option-A side-branch run: [ ] Signed mode end-to-end (real CI-produced .minisig verify against Happy to drive any of those once the side-branch pre-release is up. |
Summary
End-to-end auto-update for desktop and Android, gated behind a minisign signature embedded at compile time.
src/update_apply.rs, new): download release archive → fetch sibling.minisig→ verify against embedded pubkey → extract → stage<exe>.newnext to the running binary → swap + re-exec. Handles Linux/Windows single-binary, macOS bare binary, and macOS.appbundle layouts. On Windows, where you can't replace a running.exe, the new process detects it was launched from a.newpath and finishes the rename + re-exec at startup (finalize_pending_at_startup, called first thing insrc/bin/ui.rsandsrc/main.rs).UpdateInstaller.kt, new): downloads the per-ABI APK via a newNative.downloadAssetJNI export (rustls + minisign through the same code path the desktop uses — no OkHttp), then hands the file toPackageInstallervia FileProvider. Routes the user to "Install unknown apps" settings if the permission isn't granted yet.src/bin/ui.rs): adds an "Install update" primary button + "Restart now to apply" follow-up, with aninstall_in_progressguard against double-clicks. The pre-existing "Download" path remains as a secondary action for users who'd rather apply by hand.src/update_check.rs,src/android_jni.rs): JSON returned to Android now includesassetName/assetUrl/assetSizefor the matched per-ABI APK so the Kotlin updater knows what to fetch..github/workflows/release.yml): newsignjob runsrsign2against every.tar.gz/.zip/.apkartifact and uploads<asset>.minisigfiles.releaseandcommit-releasesnowneeds: [..., sign]but useif: always() && (sign.result == 'success' || sign.result == 'skipped')so the workflow keeps shipping releases while signing is still being rolled out.Rollout mode
Until the maintainer sets up the keypair, the updater runs in rollout mode: it still applies updates but logs
MHRV_UPDATE_PUBKEY was not set at build time — applying update without signature check (insecure), and thesignjob is skipped (gated onvars.MINISIGN_SIGNING_ENABLED == 'true'). OnceMINISIGN_PUBLIC_KEY(repo variable),MINISIGN_SECRET_KEY(secret), andMINISIGN_SIGNING_ENABLED=trueare set, the next tag push produces signed artifacts and freshly-built binaries enforce verification on every subsequent update.Full setup walkthrough lives in
docs/maintainer/references/update-signing.md, including the threat-model recap and the (deliberately not-yet-automated) key rotation procedure.New deps
minisign-verify = "0.2"(all targets) — signature check at apply timezip,tar,tempfile(scoped tocfg(not(target_os = "android"))) — desktop archive extraction + scratch dirs; Android cdylib stays lean since APK swap goes throughPackageInstallerfsfeature added — used by the Android JNI download pathTest plan
MINISIGN_SIGNING_ENABLEDunset; confirmsignis skipped,release+commit-releasesstill complete, and binaries log the rollout-mode warning when applying an update.<asset>.minisigfiles appear on the release page and inreleases/.<exe>.new, then "Restart now to apply" → confirm rename-over-running-binary +execvre-launch.<exe>.new→ spawn → rename<exe>→<exe>.old→ rename<exe>.new→<exe>→ re-exec chain infinalize_pending_at_startup..appbundle: confirm whole-bundle swap (Info.plist included) and that the innerContents/MacOS/<bin>keeps0o755..minisigneighbor; confirmApplyError::SignatureMissing(refuses to apply).ApplyError::SignatureInvalid.HomeScreen; confirm minisign verification, the OS install dialog, and the post-install package replacement (same signing key, so PackageInstaller accepts the swap).