Update the application - fakeivchenko/lwjwae GitHub Wiki
An application can find its new versions, download them, and install them, and it installs only what your key signed. In this guide, a notes application at version 1.0.0 finds version 1.1.0, shows what changed, and downloads it.

How a release works
A release is a directory on any web server: the package of every platform, a manifest.json that
names them with their size and SHA-256, and manifest.json.sig, the Ed25519 signature of the
manifest. The application downloads the manifest and the signature, checks the signature with the
public key that it was built with, and only then believes the manifest. It then checks the size and
the SHA-256 of the package that it downloads. HTTPS alone isn't trusted, so whoever controls the
server, or a cache in front of it, can't make the application install a file that your private key
didn't sign for.
Set up the build
-
Generate the keys once, for the life of the application:
./gradlew generateUpdateKeysThe task prints a public key and a private key. Keep the private key in a secret of your CI and never in the repository. A new pair locks the installed versions out of later updates, because they trust only the public key that they were built with.
-
Tell the plugin where the manifest lives, and give it the public key:
lwjwae { updates { manifestUrl = "https://example.com/notes/manifest.json" publicKey = "MCowBQYDK2VwAyEA..." notes = "Notes sync between your computers." } }The plugin writes both into the application, where
application.updater()finds them. The version that runs is the version of the project. -
Build a release. On each platform,
packageUpdateputs its package intobuild/lwjwae/update, named after the platform, such aslinux-x64.AppImage. With the files of every platform in one directory,updateManifestwrites the manifest and signs it with the private key from theLWJWAE_UPDATE_PRIVATE_KEYenvironment variable:./gradlew packageUpdate LWJWAE_UPDATE_PRIVATE_KEY=... ./gradlew updateManifest -
Upload the directory to the manifest URL. The release is out.
minimumVersion = "1.0.0" in updates {} marks every older version, which then sees the update as
mandatory. What the application does with that is up to you.
Find and install an update from Java
Updater updater = application.updater();
application.bind("currentVersion", Void.class, _ -> updater.currentVersion());
application.bind(
"checkForUpdate",
Void.class,
_ ->
updater
.check()
.join()
.map(update -> Map.of("version", update.version(), "notes", update.notes()))
.orElse(null));
application.bind(
"download",
Void.class,
_ -> {
Update update = updater.check().join().orElseThrow();
downloaded.set(
updater
.download(update, fraction -> application.emit("downloaded", fraction))
.join());
return update.installable();
});
application.bind(
"restart",
Void.class,
_ -> {
updater.installAndRestart(downloaded.get());
return null;
});
| Method | What it does |
|---|---|
check() |
Downloads and verifies the manifest, and returns the Update for this platform if its version is newer than the running one. |
download(update, progress) |
Downloads the package, reports from 0 to 1, and checks its size and SHA-256. |
installAndRestart(downloaded) |
Starts the installer, which waits for the application to quit, puts the new version in place, and starts it. Then quits the application. |
An Update has the version, the notes, whether it is mandatory, and whether this copy is
installable. A version that isn't newer than the running one is no update, so a manifest can't
take the application back.
The guide application serves a signed release from the same machine, so that it runs without a web server. A real application needs nothing of the kind: it gets the URL and the key from the plugin.
Find and install an update from the page
lwjwae.updates does the same in two calls:
const update = await lwjwae.updates.check();
if (update?.installable) {
await lwjwae.updates.install({ onProgress: (fraction) => bar.value = fraction });
}
check resolves to { version, notes, mandatory, installable, size }, or to null when there is
no update. install downloads the update and installs it, which ends the application. Both reject
with no-update, not-installable, update-rejected, or download-failed.
Platforms
| Platform | What gets installed | Installable when |
|---|---|---|
| Windows | The .msi of the new version, with msiexec /passive. |
The application runs as a native executable. |
| macOS | The .app bundle of the new version, signed by the same team. |
The bundle is in a directory that the user can write. |
| Linux | The AppImage of the new version, in place of the running one. | The application runs as an AppImage. A .deb or an Arch package updates through its package manager, so there check finds the update, but it isn't installable. |
Good to know
checkfails withUpdateRejectedExceptionfor a signature that isn't the one of your key or a manifest that isn't one, and withUpdateDownloadFailedExceptionwhen either can't be downloaded.- Under
./gradlew run, the application isn't a package, socheckanddownloadwork, but no update is installable. - The taskbar can show the download too:
updater.download(update, application::progress). See Show progress in the taskbar.