Nucleus
Packaging & distribution

Auto-update

Vérifiez, téléchargez, validez et installez les mises à jour de votre application depuis Kotlin, sans service tiers.

Le module updater-runtime vérifie, télécharge, valide et installe les mises à jour de votre application depuis Kotlin. Il a une moitié build-time — les métadonnées latest-*.yml générées à côté de vos installateurs — et une moitié runtime, la classe NucleusUpdater. Le format des métadonnées est compatible avec le format de mise à jour d'electron-builder.

Ajouter la dépendance

build.gradle.kts
dependencies {
    implementation("dev.nucleusframework:nucleus.updater-runtime:2.0.7")
    // Optionnel, pour les réseaux d'entreprise avec une CA racine privée :
    implementation("dev.nucleusframework:nucleus.native-http:2.0.7")
}

Vérifier et installer une mise à jour

Créez un NucleusUpdater, interrogez le provider configuré, puis téléchargez et installez :

import dev.nucleusframework.updater.NucleusUpdater
import dev.nucleusframework.updater.UpdateResult
import dev.nucleusframework.updater.provider.GitHubProvider
import java.io.File

val updater = NucleusUpdater {
    provider = GitHubProvider(owner = "myorg", repo = "myapp")
}

when (val result = updater.checkForUpdates()) {
    is UpdateResult.Available -> {
        var installer: File? = null
        updater.downloadUpdate(result.info).collect { progress ->
            println("${progress.percent.toInt()}%")
            progress.file?.let { installer = it }
        }
        // installAndRestart lance l'installateur, quitte, puis relance
        installer?.let(updater::installAndRestart)
    }
    UpdateResult.NotAvailable -> println("À jour")
    is UpdateResult.Error -> println("Erreur : ${result.exception.message}")
}

checkForUpdates() est une fonction suspend ; appelez-la depuis une coroutine.

Formats pris en charge

Le runtime met à jour les formats installés en place et laisse les formats gérés par un store à leur store.

PlateformeMise à jour possibleGéré par le store
macOSDMG, ZIPPKG
WindowsNSIS, NSIS Web, MSIAppX/MSIX
LinuxDEB, RPM, AppImageSnap, Flatpak

macOS a besoin d'un ZIP en plus du DMG

L'updater remplace le .app silencieusement depuis le ZIP ; le DMG ne sert qu'à l'installation initiale. Ajoutez TargetFormat.Zip à côté de TargetFormat.Dmg pour que les deux soient dans le même release — latest-mac.yml référence les deux.

Fonctionnement

Métadonnées

Le plugin écrit un fichier YAML par plateforme, listant chaque installateur avec son SHA-512 et sa taille. Les releases multi-arch ou multi-plateforme ont besoin d'un seul YAML par plateforme qui liste chaque architecture ; la CI les fusionne dans le job de release. Un latest-mac.yml ressemble à ceci :

version: 1.2.3
files:
  - url: MyApp-1.2.3-macos-arm64.dmg
    sha512: VkJl1gDqcBHYbYhMb0HRI...
    size: 102400000
  - url: MyApp-1.2.3-macos-arm64.zip
    sha512: qJ8a5gFDCwv0R2rW6lM3k...
    size: 98000000
releaseDate: '2026-03-01T12:00:00.000Z'

Canaux

Le tag de version pilote le canal : v1.0.0 correspond à latest, v1.0.0-beta.1 à beta, et v1.0.0-alpha.1 à alpha. Chaque canal a son propre *-mac.yml, *.yml et *-linux.yml. Les utilisateurs sur beta voient latest et beta ; ceux sur alpha voient les trois.

Hébergement

L'hébergement se configure dans nativeDistributions.publish { } (voir publication) :

  • GitHub Releases — le workflow de release gère tout de bout en bout.
  • S3 — renseignez les credentials via des variables d'environnement ; le plugin upload à côté du YAML.
  • HTTP générique — upload vous-même sur n'importe quel host statique ; l'updater récupère <baseUrl>/latest-*.yml.

Le bloc publish { } ne fait que générer la config electron-builder — il n'upload pas. C'est la CI qui upload.

Détection après mise à jour

Après installAndRestart ou installAndQuit, l'updater écrit un fichier marqueur dans le répertoire app-data de la plateforme (résolu depuis NucleusApp.appId). Au lancement suivant, wasJustUpdated() retourne true et consumeUpdateEvent() livre un UpdateEvent(previousVersion, newVersion, updateLevel) que vous pouvez utiliser pour une bannière « Quoi de neuf ».

Configurer les réseaux d'entreprise

Si vos utilisateurs sont derrière un proxy d'entreprise avec une CA racine privée, le java.net.http.HttpClient par défaut échoue au handshake TLS. Injectez un NativeHttpClient qui lit le trust store de l'OS :

import dev.nucleusframework.nativehttp.NativeHttpClient

val updater = NucleusUpdater {
    provider = GitHubProvider(owner = "myorg", repo = "myapp")
    httpClient = NativeHttpClient.create()
}

Voir native-ssl pour les détails du trust manager.

Mettre en place les mises à jour sans mot de passe sur Linux

Par défaut, appliquer une mise à jour DEB ou RPM passe par pkexec, qui affiche un dialogue de mot de passe PolicyKit. Vous pouvez laisser l'app installer les mises à jour vérifiées sans mot de passe, en conditionnant l'install à une vérification de signature.

Ça s'appuie sur la signature Linux. Activez-le sur le bloc signing :

build.gradle.kts
linux {
    signing {
        enabled.set(true)
        keyId.set("AB12CD34EF56")
        silentUpdate.set(true)   // nécessite enabled = true
    }
}

Le script post-install root du paquet met alors en place les mises à jour sans mot de passe ainsi :

  1. Un helper de mise à jour root-owned dans /opt/<App>/nucleus-update-helper, et une policy polkit scopée à ce binaire précis (allow_active=yes).
  2. Votre clé publique de signature, bundlée dans /opt/<App>/resources/nucleus-update.pub.asc.
  3. Au moment de la mise à jour, le runtime télécharge le paquet et sa signature détachée <file>.asc, puis lance pkexec <helper> <file>.
  4. Le helper vérifie la signature contre la clé bundlée, contrôle que le nom du paquet correspond à l'app installée, et seulement alors lance dpkg -i ou rpm -U.

La règle polkit allow_active=yes est scopée au seul binaire helper, et le helper refuse tout paquet dont la signature ne valide pas contre la clé bundlée : une install sans mot de passe ne peut donc pas installer un paquet arbitraire — il faudrait votre clé privée de signature. Si le helper ou le <file>.asc est absent, le runtime retombe sur l'install pkexec avec prompt de mot de passe. L'install sans mot de passe nécessite une session locale active.

Référence de l'API

Méthodes runtime

NucleusUpdater expose :

  • suspend fun checkForUpdates(): UpdateResult — retourne Available(info, level), NotAvailable ou Error(exception).
  • fun downloadUpdate(info: UpdateInfo): Flow<DownloadProgress> — émet la progression ; l'émission finale a file != null.
  • fun installAndRestart(file: File) — lance l'installateur, quitte le processus, relance après installation.
  • fun installAndQuit(file: File) — installation silencieuse, pas de relance. La mise à jour s'applique au prochain lancement manuel.
  • fun isUpdateSupported(): Boolean — indique si le type d'exécutable courant peut s'auto-mettre à jour.
  • fun wasJustUpdated(): Boolean et fun consumeUpdateEvent(): UpdateEvent? — détection après mise à jour.

Types de résultat et de niveau

  • UpdateResult.Available(info: UpdateInfo, level: UpdateLevel)
  • UpdateResult.NotAvailable
  • UpdateResult.Error(exception: UpdateException)

UpdateLevel indique l'ampleur du saut : MAJOR, MINOR, PATCH ou PRE_RELEASE. Branchez votre UI dessus — dialogue forcé pour les bumps majeurs, installation silencieuse pour les patches.

Champs du builder

NucleusUpdater { } configure un UpdaterConfig : currentVersion, channel, provider, httpClient, allowDowngrade, allowPrerelease et executableType.

Providers

  • GitHubProvider(owner, repo, token? = null)
  • GenericProvider(baseUrl)

Les en-têtes d'authentification personnalisés proviennent de authHeaders() du provider, pas de la config.

Commandes d'installation

OSFormatCommande
LinuxDEBpkexec dpkg -i <file>
LinuxRPMpkexec rpm -U <file>
LinuxAppImageremplacement sur place
macOSDMG/ZIPopen <file> / extraction
WindowsNSIS/EXE<file> /S
WindowsMSImsiexec /i <file> /passive

Exceptions

UpdateException est le type de base. Sous-types : NetworkException, ChecksumException, NoMatchingFileException, ParseException.

Notes

  • Les SHA-512 sont vérifiés après téléchargement ; un échec supprime le fichier et remonte une erreur.
  • Les mises à jour Linux sans mot de passe (signing.silentUpdate) ajoutent une deuxième vérification, plus forte : une signature GPG validée contre la clé bundlée dans l'app.
  • Les tokens GitHub passent par l'en-tête Authorization, jamais dans les paramètres d'URL.
  • Pour le Mac App Store, le Microsoft Store, Snapcraft et Flathub, le store gère les mises à jour ; ignorez updater-runtime entièrement.

Et ensuite

  • Publication — configurer les providers dans nativeDistributions.publish { }.
  • CI/CD — le workflow de release qui génère et uploade les métadonnées.
  • Signature de code — signature GPG des DEB/RPM et mises à jour sans mot de passe.