Auto-updates
How to auto-update a desktop app
By the Omnihorn team · · 8 min read
To auto-update a desktop app, the installed copy asks a server which version belongs on its channel, verifies a signed manifest of the files, downloads only the files that changed, checks their hashes, swaps them in atomically with a rollback on failure, and then restarts or waits for the next launch. A background check at start-up keeps it current.
What does an auto-update loop do?
An auto-update loop is the code that moves an installed app from the version it has to the version you published, without the user downloading anything by hand. Every safe updater, whatever the framework, runs the same five steps in the same order, and each step must succeed before the next one starts.
Check for a different version
Send the installed version, the app id and the channel to the update server. The server answers with the release that belongs on that channel, or "nothing to do".
Verify the signed manifest
Check the manifest’s signature with the public key built into the client. Reject the whole update if it fails, and reject any file path that could escape the install folder.
Download the changed files
Hash the files already on disk, drop the ones that match, download the rest to a temporary folder and verify each file’s SHA-256 against the manifest.
Apply atomically
Rename each current file to a backup, move the new file into place, and restore every backup if any step fails. Delete the backups only after everything succeeded.
Record the version and restart
Persist the new version, remove files the new release no longer has, and start the app again or leave it for the next launch.
How should the version check work?
The client sends what it has; the server decides what it should have. Let the server be the authority on which build belongs on a channel, and have the client treat any different version as an update. Comparing for "newer" on the client blocks the one move you will need in an emergency: going back to an older build.
Keep the check cheap. It runs on every installed copy, so the answer should be small, cacheable on the server, and say "nothing to do" quickly. Send a stable per-installation id if you want honest counts of active installs, and respect the server’s rate limits.
Why does the update need a signed manifest?
The manifest is the list of every file in the release with its path, size and hash. Signing it with a private key that only your server holds, and checking it with a public key built into the client, means a compromised mirror, a hijacked DNS entry or a tampered download cannot slip a different file onto the PC.
Verify the exact bytes that were signed; re-serialising JSON before checking breaks the signature. Then check every path: refuse absolute paths, .. segments, drive letters and Windows reserved names, so a manifest cannot write outside the install folder. More on keys and signatures is in how to sign desktop app updates.
How do you download only the files that changed?
Hash each file already installed and compare it with the manifest. Files that match stay as they are; only the rest are downloaded. For a large app where a release changes a few files, this turns a full download into a small one and makes a failed attempt cheap to retry.
Download into a temporary folder, never straight into the install folder, and verify each file’s hash before it counts as downloaded. Short-lived download URLs are a good idea: they limit what a leaked link is worth. When one expires mid-download, ask for fresh URLs for the remaining files instead of starting over.
How do you apply an update without breaking the app?
Apply all files or none. Rename each current file to a backup, move the verified new file into place, and if any step fails, put every backup back. Only after the last file succeeds do you delete the backups and record the new version. A crash halfway then leaves the old version intact.
Windows adds one wrinkle: a running .exe cannot be overwritten, but it can usually be renamed. Rename it aside, put the new one in its place, and clean up the old file later. Keep a list of the files you installed, so the next update can remove files a release dropped and uninstall can remove exactly what you wrote.
Should the app restart after an update?
It depends on who started the update. If the user pressed "Install update", start the new version when it finishes. If a background check applied it silently, do not pop windows: the user gets the new version the next time they open the app. Never restart an app in the middle of someone’s work.
When should a desktop app check for updates?
The usual answer is twice: when the user opens the updater or app, and once in the background after Windows starts. The background check catches people who leave an app open for weeks or rarely open it. Throttle both with an interval so thousands of installs do not hit your server on every boot.
| When | Good for | Watch out for |
|---|---|---|
| On launch | Users see the newest version soon after release | Slows start-up if the check blocks the window |
| Once per OS start-up, in the background | Updates arrive even if the app is rarely opened | Many PCs boot at the same time; add a random delay |
| On a timer while running | Long-running apps | Needs a process that stays alive |
| Manual only | Managed machines where IT controls versions | Users fall behind unless someone checks |
Make the interval a server-side setting per channel rather than a constant in the client, so you can change it without shipping a new build. Honour Retry-After on 429 and 503 answers, and do not count a failed request as a completed check.
What can go wrong with auto-updates?
| Problem | What a good updater does |
|---|---|
| The network drops mid-download | Keeps what it verified, ends the attempt and tries again later |
| A file’s hash does not match | Discards the download and changes nothing on disk |
| A file is locked by a running process | Renames it aside or rolls the whole update back |
| The manifest signature fails | Refuses the update; never falls back to unsigned files |
| The channel moves to another build mid-download | Stops and picks up the new answer on the next check |
| The server is overloaded | Waits for Retry-After instead of retrying in a loop |
| A release is bad | Lets you point the channel back at an older build; see rolling back a release |
How Omnihorn runs the update loop
Omnihorn runs this loop for you on Windows. Your app does not link a library; the installer Omnihorn builds for you carries the updater. You upload a build, point a channel at it, and installed copies on that channel pick it up on their next check.
| Step | What Omnihorn does |
|---|---|
| Check | The updater asks for the build on its channel. Any version different from the installed one counts as an update, so rollbacks work |
| Verify | Each app has its own Ed25519 key pair; the server signs every build’s manifest and the public key is built into your installer. Unsafe paths refuse the whole update |
| Download | Files that already match are skipped. Download URLs are requested only when a download starts, last six hours, and are renewed for the remaining files if they expire |
| Apply | Current files are renamed to backups and restored if anything fails; files the new build no longer has are removed afterwards |
| Restart | After an update the user starts, the launch target you chose starts again. Background updates do not open windows |
The background check runs once per Windows start-up, after a random wait of up to ten minutes. Each channel has an update-check interval: every 20 hours by default, adjustable up to once a week, every start-up, or manual only. The interval comes from the server, so changing it needs no new installer. Opening the updater window always checks. The details are in Updates on user machines, and the channel model is in Channels and rollouts.
For scripted checks there is a silent mode: omnihorn.exe --no-gui --check-update prints a JSON status line and exits with 2 when an update is available and 0 when the copy is current; add --update-if-allowed to apply it. To try the whole loop, start with the quickstart.
›How often should a desktop app check for updates?
There is no single right number. A background check about once a day plus a check whenever the user opens the updater suits most apps. Make the interval a server setting so you can change it later. Omnihorn’s default is every 20 hours per channel.
›Do I need to download the whole app for every update?
No. Compare file hashes with the manifest and download only files that differ. Omnihorn does this on every update.
›What happens if an update fails halfway?
A safe updater restores the previous files from backups, so the old version keeps working, and tries again on a later check.
›Can I auto-update an app that is already installed without an updater?
Only by getting users to install a version that contains one. With Omnihorn, users install once with the setup.exe it builds; every later version arrives through the update loop.