Protection Anti-Bootloop, Rollback automatique et Acquittement
Un risque critique de tout système de mise à jour à chaud est le déploiement d'un patch contenant une anomalie qui provoque un crash dès l'initialisation de l'application (bootloop). TkushPatch met en œuvre une double barrière de protection :
- Au niveau du moteur natif Rust (
updater/library/src/updater.rs) : détection automatique d'échec de démarrage, révocation locale immédiate du patch défectueux et envoi d'un événement télémétriquePatchInstallFailure. - Au niveau du SDK Flutter
tk_ota_push(OtaRollbackTrackerdanslib/src/security/ota_security_shield.dart) : détection de la régression du numéro de patch (current_patch < last_executed_patch) et déclenchement du hookonRollbackDetectedpour purger les caches applicatifs locaux.
1. Mécanisme natif Anti-Bootloop (Moteur Rust)
- Démarrage sous surveillance : Au lancement de l'application, un compteur de tentatives est incrémenté.
- Confirmation de stabilité : Dès que l'interface s'affiche avec succès, le patch actif est marqué comme stable.
- Repli automatique si échec : Si l'application plante avant confirmation, le moteur mobile désactive le patch et redémarre instantanément sur la release de base saine.
Détection de crash au démarrage (handle_prior_boot_failure_if_necessary)
Lorsqu'un patch est chargé au démarrage, le moteur Rust enregistre son numéro dans state.json comme patch en cours d'amorçage (currently_booting_patch) avec l'horodatage boot_started_at.
Si l'application crashe avant que le moteur Flutter n'ait confirmé le démarrage complet, l'ouverture suivante de l'application exécute handle_prior_boot_failure_if_necessary() (updater/library/src/updater.rs) :
- Le moteur constate que
currently_booting_patchest toujours renseigné. - Il appelle
state.record_boot_failure_for_patch(patch.number): le patch est marqué définitivement comme défectueux (PatchKnownBad) sur cet appareil afin de ne plus jamais tenter de le charger (UpdateStatus::UpdateIsBadPatch). - Il prépare un message de diagnostic précis :
crash_recovery: patch <N> failed to boot (detected_at=<ts>,boot_started_at=<ts>,file_ok=<bool>,file_size=<bytes>) - Il met en file d'attente un événement
EventType::PatchInstallFailure(__patch_install_failure__) qui est transmis au serveur surPOST /api/v1/patches/events.
Rollback piloté par le serveur (rolled_back_patch_numbers)
En plus de la détection locale de crash, le serveur peut ordonner la révocation de patchs via le tableau rolled_back_patch_numbers dans la réponse de POST /api/v1/patches/check. Dès réception de cette liste, la fonction roll_back_patches_if_needed(rolled_back_patches) du moteur Rust désinstalle localement les patchs révoqués.
2. Résilience côté Dart et nettoyage du cache (OtaRollbackTracker)
Lorsqu'une application subit un rollback du Patch #2 vers le Patch #1 (ou vers le binaire Store #0), le Patch #2 a pu écrire des données dans SharedPreferences, Hive ou SQLite avec un nouveau schéma que le code du Patch #1 ne comprend pas.
Le SDK tk_ota_push résout ce problème grâce à OtaRollbackTracker :
final ota = TkOtaPush.init(
appReleaseVersion: '1.0.0+3',
// Optionnel : utiliser FileOtaPatchStateStore pour persister last_executed_patch sur disque
rollbackTracker: OtaRollbackTracker(
store: FileOtaPatchStateStore(File('/chemin/sandbox/ota_patch_state.json')),
),
onRollbackDetected: (OtaRollbackEvent event) async {
debugPrint(
'Rollback détecté sur la release ${event.releaseVersion} '
'à ${event.detectedAt.toIso8601String()} : '
'Patch #${event.previousPatchNumber} -> Patch #${event.restoredPatchNumber}',
);
// Purger ici les caches locaux potentiellement incompatibles avant runApp()
},
);
Fonctionnement de verifyStartupAndHandleRollback()
- À chaque appel de
syncWithRemoteConfig(Étape 0a) ou d'un appel explicite àverifyStartupAndHandleRollback(), le SDK lit le patch actif (currentPatchNumber() ?? 0). OtaRollbackTracker.evaluateAndRecordcompare ce numéro aveclast_executed_patchenregistré pour cettereleaseVersiondansOtaPatchStateStore(InMemoryOtaPatchStateStorepar défaut ouFileOtaPatchStateStoresur fichier JSON).- Il met à jour
last_executed_patchavec la valeur courante. - Si
effectiveCurrent < previous, il retourne un objetOtaRollbackEventcontenant :releaseVersion(String)previousPatchNumber(int)restoredPatchNumber(int,0si retour à la Release Store de base)detectedAt(DateTimeen UTC)
- Le callback
onRollbackDetectedest immédiatement attendu (await) avant la suite de l'initialisation.
3. Télémétrie envoyée au serveur (POST /api/v1/patches/events)
Le moteur Rust envoie les événements suivants (définis dans updater/library/src/events.rs) au contrôleur ShorebirdUpdaterController.reportPatchEvent :
Enum Rust (EventType) | Identifiant JSON (type) | Déclencheur |
|---|---|---|
PatchDownload | __patch_download__ | Le téléchargement binaire du patch est terminé. |
PatchInstallSuccess | __patch_install__ | Le patch a démarré avec succès au lancement suivant. |
PatchInstallFailure | __patch_install_failure__ | Le patch a échoué au démarrage (crash_recovery) ou lors de l'installation. |
PatchUpdateFailure | __patch_update_failure__ | Échec lors de la vérification ou de la préparation de la mise à jour. |
Chaque événement PatchEvent contient également une classification normalisée failure_category (FailureCategory dans updater.rs) :
"network"(réessayable)"server"(réessayable)"out_of_space"(non réessayable)"permission_denied"(non réessayable)"filesystem"(non réessayable)"corrupt_patch"(non réessayable)"invalid_signature"(non réessayable)"unknown"(réessayable par prudence)
4. Acquittement d'alerte Rollback dans la Console Support (/support)
Lorsqu'un appareil remonte un événement __patch_install_failure__, son statut passe en alerte Rollback Actif dans l'écran Device Fleet & Support (/support) de la Console Web.
- Un opérateur ou ingénieur support peut inspecter l'appareil (via son
client_idou son code court#6959347F) et cliquer sur Acquitter l'alerte Rollback. - Le serveur enregistre l'horodatage
rollback_acknowledged_atsur l'enregistrement de l'appareil sans jamais supprimer ni modifier les événements du journal d'audit télémétrique. - L'indicateur de santé de l'appareil repasse à l'état normal tant qu'aucun nouvel échec postérieur (
latestFailureAt > rollbackAcknowledgedAt) ne survient.