feat(desktop): move NSIS installs to Velopack on the next update - #2167
Merged
Conversation
|
React Doctor found no new issues. 🎉 Reviewed by React Doctor for commit |
Windows in-place NSIS updates fail on roughly 8.5% of installs per 60 days, so an NSIS install has to move to the Velopack install rather than keep retrying the path that breaks. This is the half that runs before anything is executed. detectWindowsInstallLayout probes `..\Update.exe` and `Uninstall MemryNote.exe` to tell a Velopack install from an NSIS one. createInstallerHandoff downloads `MemryNote-win-Setup.exe` from the same release tag into `userData/installer-handoff/`, runs Get-AuthenticodeSignature over it, and arms only when judgeAuthenticode sees status Valid and a signer subject containing `CN=Open Source Developer Kaan Karaca`. Any other verdict deletes the file, so an unverified binary is never spawned. launch() runs at most once per armed plan: the shutdown backstop can race the shutdown sequence into a second install call, and two hand-off scripts would uninstall and install twice. renderInstallerHandoffScript stays free of imports and of non-erasable syntax so Node can strip its types for the smoke workflow. Its command lines are pinned by a test because they only ever run on Windows.
The install marker could not tell a Velopack hand-off apart from a plain NSIS install, so the migration would have been invisible in telemetry and a silent install-on-quit that never came back left no trace at all. markUpdateInstallStarted now takes the installer and stores it; markers written by older builds simply lack the field. resolveUpdateInstallOutcome reads it back against the current version and the install layout: same version is the existing UPDATE_INSTALL_DID_NOT_APPLY, a new version from the Velopack layout is app_update_installed with action `migrated`, and a new version still on the NSIS layout is INSTALLER_HANDOFF_DID_NOT_APPLY, which means the update arrived but the migration did not. The errorCode of the existing failure is unchanged so Error Tracking keeps one issue.
An NSIS install stays on electron-updater for checking and downloading, and migrates at the install step instead. Both install paths now go through the hand-off, so the migration reaches a user who never presses Restart. The backend defers onDownloaded until the hand-off settles, so "Restart to install" only appears once the Velopack installer is on disk and verified, or the hand-off has declined. Restart-to-install and the silent install-on-quit both launch the script and then turn autoInstallOnAppQuit off, which is what stops electron-updater from also running the NSIS installer from its own quit hook. will-quit writes the marker before the hand-off, because the installer runs after this process exits and that write is the last evidence left. A session-end quit still skips the install entirely, marker included. Every refusal lands on the plain NSIS install: a release without the asset, a download failure, a signature failure, and a spawn failure.
`Memrynote.exe --cli migrate-installer <path-to-MemryNote-win-Setup.exe>` drives the same verify-then-hand-off path against a local file. It is how the Windows smoke workflow exercises the hand-off from a real NSIS install without waiting for a release to carry the asset, and it gives a stuck user a manual route. Windows only. It copies the file into the hand-off directory, refuses unless Get-AuthenticodeSignature agrees, and exits 0 only once the script is spawned.
The hand-off is a batch script, an NSIS `_?=` uninstall and a PowerShell JSON shape, none of which run anywhere but Windows. A workflow is the only place they can be proven. The smoke workflow moves here from the prototype branch with a second `migrate` job: install the published NSIS build, hand off, then assert the NSIS directory, its uninstall key and its shortcut are gone, the Velopack install is signed and present, the Start menu entry is back, and Memrynote.exe is running from the new install. scripts/installer-handoff-script.mjs renders the script from the same function the app calls, so the job cannot drift from the shipped one. The CLI route is gated off until a published NSIS build carries the command.
The install guide told a Windows user on the old installer that nothing would change. It now says the move happens once, on the next update, that the vault, settings and index stay put, that the app reopens itself, and that a taskbar pin may need pinning again. Observability gains the hand-off's verification rule, its fallbacks and the three outcomes the next launch can report.
| } | ||
| const scriptPath = path.win32.join(tmpDir, `memry-installer-handoff-${pid}.cmd`) | ||
| try { | ||
| fsDeps.writeFileSync(scriptPath, renderInstallerHandoffScript(plan)) |
h4yfans
force-pushed
the
feat/nsis-to-velopack-handoff
branch
from
September 11, 2026 19:23
7e65197 to
11e48fc
Compare
h4yfans
marked this pull request as ready for review
September 11, 2026 19:23
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A Windows user on an NSIS install currently stays on the update path that fails on roughly 8.5% of installs per 60 days. This moves them to the Velopack install automatically, on the next update they take, with no prompt and no manual reinstall.
An NSIS install keeps electron-updater for checking and downloading. The install step is what changes. Once electron-updater has the NSIS
setup.exeon disk,installer-handoff.tslooks forMemryNote-win-Setup.exeon the same release tag, streams it intouserData/installer-handoff/, and verifies it before anything is executed.onDownloadedis deferred until that settles, so "Restart to install" never appears before everything the hand-off needs exists.Verification rule.
Get-AuthenticodeSignature -LiteralPath <file> | ConvertTo-Json -Compress, parsed byjudgeAuthenticode. Status must beValid(serialized as0or the enum name) andSignerCertificate.Subjectmust containCN=Open Source Developer Kaan Karaca. Anything else deletes the file and declines. An unverified binary is never spawned, on either the updater path or the CLI path.The hand-off script. Pinned by
installer-handoff-script.test.ts, rendered with CRLF, spawned detached throughcmd.exe /c:Order matters in four places. It waits for our own PID and image name, so a recycled PID cannot match.
pingis the sleep becausetimeout /taborts with "Input redirection is not supported" understdio: 'ignore'. The uninstaller runs from a copy with_?=last and unquoted, so it blocks andRMDir /r $INSTDIRcan remove the original, and without--updated, so the tolerant full-removal path runs and the Start-menu shortcut at the path Velopack is about to reuse is gone first. Setup.exe runs only after that, and launches the app itself.Fallbacks. Every refusal lands on the plain NSIS install, which is exactly today's behavior.
MemryNote-win-Setup.exe(HEAD404)HEADor the download failsINSTALLER_HANDOFF_DOWNLOAD_FAILEDthrough the existing updater error path, then the NSIS update installs as usual.INSTALLER_HANDOFF_UNVERIFIED, the downloaded file is deleted, the NSIS update installs as usual.launch()returns false, soautoInstallOnAppQuitis left on and electron-updater installs the NSIS update from its own quit hook.session-endguard. It applies on the next user-initiated quit.Next launch. The marker now carries
installer(electron-updater,velopack,velopack-handoff; older markers lack the field).resolveUpdateInstallOutcomereads it against the running version and the install layout. Same version is the existingUPDATE_INSTALL_DID_NOT_APPLY, now withsource: velopack-handoff. A new version from the Velopack layout isapp_update_installedwithaction: migrated. A new version still on the NSIS layout isINSTALLER_HANDOFF_DID_NOT_APPLY, meaning the update arrived but the migration did not. The silent install-on-quit path writes the marker too, which it never did before, so a Windows update that applies on a normal quit is no longer invisible.Memrynote.exe --cli migrate-installer <path-to-MemryNote-win-Setup.exe>drives the same verify-then-hand-off path against a local file. Windows only, exit 0 only once the script is spawned.Stacked on #2164.
Release note
none
Test plan
Run from the worktree on Node 24 (
.nvmrc), unless noted.pnpm --filter @memry/desktop typecheck:node— pass.pnpm --filter @memry/desktop typecheck:test— pass. No test file was added to thetsconfig.test.*.jsonexclude backlog.pnpm --filter @memry/desktop exec vitest run --config config/vitest.config.ts --project main src/main/installer-handoff.test.ts src/main/installer-handoff-script.test.ts src/main/updater-nsis-handoff.test.ts src/main/cli/migrate-installer.test.ts src/main/telemetry/update-install-marker.test.ts src/main/cli/headless.test.ts src/main/updater.test.ts src/main/updater-velopack.test.ts— 134 pass.pnpm --filter @memry/desktop test:main— 8263 pass, 3 fail. The three areapps/desktop/scripts/check-cert-hashes-config.test.ts, which passes on Node 24 and only fails when the suite runs on the Node 26 the native modules happen to be built for here. Unrelated to this diff.pnpm lint— 0 errors. The one warning is pre-existing invault-switcher.tsx.pnpm check:architecture,pnpm check:contracts— pass.pnpm ipc:check— pass on Node 24. No contract changed. On Node 26 it dies on--experimental-transform-types, which that version removed.pnpm docs:impact --base origin/feat/velopack-windows-updater --strict— pass, docs changed on this branch.pnpm docs:build,git diff --check— pass.What none of that covers: the batch script, the
_?=uninstaller behavior and theGet-AuthenticodeSignature | ConvertTo-Jsonshape only ever run on Windows, so every test here asserts the rendered text and the parse rules rather than the effect. Themigratejob in.github/workflows/velopack-smoke.ymlis what proves them, and it needs a published release carryingMemryNote-win-Setup.exebefore it can run. It installs the published NSIS build, hands off, then asserts the NSIS directory, its uninstall key and its shortcut are gone, the Velopack install is signed and present, the Start menu entry is back andMemrynote.exeis running from the new install. Itsrun_cli_migrationinput stays off until a published NSIS build containsmigrate-installer. The hand-off is also unverified end to end against a real update until that release exists.