tk_ota_push 0.2.2
tk_ota_push: ^0.2.2 copied to clipboard
Sovereign OTA code push for Flutter with a 3-step Remote Config Gatekeeper, bank-grade security shield, and multiplatform deployment CLI.
tk_ota_push #
tk_ota_push est une solution 2-en-1 souveraine et on-premise pour Flutter, conçue pour les institutions financières, banques et entreprises (FinTech, Mobile Money, Télécoms). Elle allie :
- Un SDK Mobile (
lib/) : Pilotage intelligent des mises à jour à chaud (Hot Code Push) avec Remote Config Gatekeeper en 3 étapes (0 appel réseau inutile), bouclier de sécurité mobile (Anti-Repackaging APK Xender, détection Root/Jailbreak, Pinning TLS) et gestionnaire d'assets dynamiques. - Une CLI de Déploiement (
bin/) : Outil natif en pur Dart pour compiler, verrouiller les Releases du Store, générer les différentiels binaires (bidiff+zstd) et publier les patchs signés cryptographiquement via HashiCorp Vault.
🏛️ Architecture Globale & Flux de Données #
flowchart TD
subgraph Client["📱 Application Mobile Flutter"]
App["App Métier (main.dart)"]
RC["Remote Config Distant<br/>(Firebase / API Interne)"]
GK{"🛡️ Gatekeeper<br/>3 Conditions"}
Shield["🔒 Bouclier Sécurité<br/>(Certificat APK / Root / TLS)"]
Updater["Moteur Natif Rust<br/>(libflutter / Updater)"]
end
subgraph Server["🏢 Serveur Souverain On-Premise"]
API["Spring Boot API<br/>(Data Plane / Admin)"]
Vault["🔑 HashiCorp Vault<br/>(Transit Engine / Clés RSA)"]
Storage["📦 Coffre-Fort d'Artefacts<br/>(Releases & Différentiels)"]
DB[("PostgreSQL 16<br/>Audit & Métadonnées")]
end
App -->|1. Initialisation| GK
RC -->|2. Charge Payload JSON| GK
GK -->|Condition échouée| Sleep["0 Appel Réseau (0 Mo Data)"]
GK -->|3 Conditions réunies| Shield
Shield -->|Appareil sain| Updater
Updater -->|3. POST /api/v1/patches/check| API
API -->|4. Renvoie URL & Signature| Updater
API -.->|Signe condensat SHA-256| Vault
Updater -->|5. Télécharge Micro-Patch (~100 Ko)| Storage
Updater -->|6. Événements télémétrie| DB
⚡ Pourquoi le "Remote Config Gatekeeper" ? (0 Appel Inutile) #
Dans une architecture classique, le moteur natif appelle le serveur OTA à chaque lancement de l'application. Pour une banque ayant 5 millions d'utilisateurs actifs, cela engendre 10 à 15 millions de requêtes HTTP quotidiennes inutiles, saturant l'infrastructure et consommant le forfait data mobile (3G/4G) des clients.
En configurant auto_update: false dans shorebird.yaml, le SDK tk_ota_push prend le relais et évalue localement les 3 conditions cumulatives en mémoire en 0 milliseconde :
flowchart LR
C1["1. Version Cible<br/>(target_release_version)"] -->|Match| C2["2. Validation Sécurité<br/>(patch_available == true)"]
C2 -->|Match| C3["3. Monotonie Patch<br/>(current < latest)"]
C3 -->|Match| OK["✅ Appel Serveur Autorisé"]
C1 -.->|Différent| NO["🚫 0 Appel Réseau"]
C2 -.->|False| NO
C3 -.->|Déjà à jour| NO
Format du Payload Remote Config #
Que vous utilisiez Firebase Remote Config, une route API interne de la banque ou notre endpoint dédié, voici le JSON consommé par le SDK :
{
"target_release_version": "1.0.0+3",
"patch_available": true,
"latest_patch_number": 2,
"force_update": false,
"release_notes": "Optimisation des virements instantanés et correctif UI."
}
| Champ | Type | Description |
|---|---|---|
target_release_version |
String |
Version installée ciblée (ex: 1.0.0+3). Si l'utilisateur est en 1.0.0+2, l'appel est ignoré. |
patch_available |
bool |
Interrupteur maître. Si false, les serveurs OTA ne sont pas interrogés. |
latest_patch_number |
int |
Dernier numéro de patch disponible. Si le client a déjà ce patch, aucun appel n'est fait. |
force_update |
bool |
false = mode silencieux éco-data ; true = modale bloquante de sécurité. |
release_notes |
String? |
Message affiché à l'utilisateur lors d'une mise à jour critique. |
📦 Installation & Configuration #
1. Dépendance Flutter #
Ajoutez tk_ota_push à votre pubspec.yaml :
dependencies:
flutter:
sdk: flutter
tk_ota_push: ^0.2.0
2. Configuration Moteur (shorebird.yaml) #
À la racine de votre application Flutter :
app_id: votre-app-id
base_url: https://tks-cloud.duckdns.org
patch_public_key: <VOTRE_CLE_PUBLIQUE_RSA_FOURNIE_PAR_LE_SERVEUR>
auto_update: false # INDISPENSABLE : délègue la décision réseau au Gatekeeper
Déclarez le fichier dans pubspec.yaml :
flutter:
assets:
- shorebird.yaml
💻 Intégration dans le Code Dart (main.dart) #
import 'package:flutter/material.dart';
import 'package:tk_ota_push/tk_ota_push.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 1. Initialisation du SDK avec Bouclier de Sécurité Bancaire
final ota = TkOtaPush.init(
appReleaseVersion: '1.0.0+3',
requireWifiForSilentUpdate: false, // true = télécharge uniquement en Wi-Fi
minBatteryPercentageForSilentUpdate: 20,
securityPolicy: const OtaSecurityPolicy(
blockRootedOrJailbrokenDevices: true, // Bloque les appareils compromis
expectedApkSignaturesSha256: [
'6b6929c8ff345cf008496328d7bff081a8ccfb89498bf9916ed668be3c3dedcf', // Empreinte certificat APK officiel
],
pinnedServerCertSha256Fingerprints: [
'42d889f8f4b6bf9d3abb767d3b5f215e0009e4d26cb7b958497e8b291e1b96dc', // Pinning TLS
],
),
// Hook de résilience post-rollback : déclenché si un patch défectueux est révoqué
onRollbackDetected: (event) async {
debugPrint('⚠️ ROLLBACK DÉTECTÉ : #${event.previousPatchNumber} -> #${event.currentPatchNumber}');
// Purger le cache SQLite / SharedPreferences pour éviter tout conflit de schéma
},
// Déclenché si force_update == true dans le Remote Config
onCriticalUpdateRequired: (config, triggerImmediateDownload) async {
debugPrint('🚨 MISE À JOUR CRITIQUE OBLIGATOIRE : ${config.releaseNotes}');
// Afficher une boîte de dialogue bloquante UI
await triggerImmediateDownload();
},
onSecurityViolation: (verdict) async {
debugPrint('⛔ VIOLATION DE SÉCURITÉ : ${verdict.reason}');
},
);
// 2. Synchronisation pilotée par le Remote Config
final remoteConfigPayload = {
'target_release_version': '1.0.0+3',
'patch_available': true,
'latest_patch_number': 2,
'force_update': false,
'release_notes': 'Correction calcul des intérêts de compte épargne.',
};
final outcome = await ota.syncWithRemoteConfig(
OtaRemoteConfig.fromJson(remoteConfigPayload),
);
debugPrint('Résultat Gatekeeper : ${outcome.message}');
debugPrint('Appel réseau effectué ? ${outcome.networkCallPerformed}');
runApp(const MonApplicationBancaire());
}
🎨 Politiques d'Exécution & Exemples Visuels #
1. Mode Silencieux / Éco-Data (force_update: false) #
Idéal pour les évolutions courantes. Le SDK inspecte l'état du réseau et de la batterie :
- Si les conditions sont optimales, le micro-patch différentiel (~50 à 150 Ko) est téléchargé en arrière-plan sans interrompre l'utilisateur.
- Il s'applique automatiquement au prochain démarrage à froid (Cold Start).
- Si la batterie est faible ou que le Wi-Fi est requis, le téléchargement est différé via
workmanager.
2. Mode Critique Forcé (force_update: true) #
Idéal pour les correctifs d'urgence (faille de sécurité, régulation bancaire). Une modale bloquante s'affiche immédiatement :
Future<void> showCriticalUpdateDialog(
BuildContext context,
OtaRemoteConfig config,
Future<bool> Function() onDownload,
) {
return showDialog(
context: context,
barrierDismissible: false,
builder: (ctx) => AlertDialog(
icon: const Icon(Icons.security_update_warning, size: 48, color: Colors.orange),
title: const Text('Mise à Jour de Sécurité Obligatoire'),
content: Text(config.releaseNotes ?? 'Veuillez patienter pendant la mise à jour.'),
actions: [
FilledButton(
onPressed: () async {
final success = await onDownload();
if (success) {
// Notifier l'utilisateur de redémarrer
}
},
child: const Text('Mettre à jour maintenant'),
),
],
),
);
}
🛡️ Le Bouclier de Sécurité Mobile (OtaSecurityPolicy) #
| Protection | Risque Éliminé | Fonctionnement |
|---|---|---|
| Anti-Repackaging APK | Faux APK modifiés ou infectés partagés via Xender / WhatsApp / Telegram | Compare l'empreinte SHA-256 du certificat de signature de l'APK avec expectedApkSignaturesSha256. Si altéré $\rightarrow$ refus strict du patch. |
| Détection Root / Jailbreak | Appareils compromis ou débogueurs malveillants | Détecte les binaires su, Magisk, SuperSU, Cydia. |
| Certificate Pinning TLS | Attaques Man-in-the-Middle (MitM) sur réseaux publics | Valide l'empreinte SHA-256 du certificat HTTPS du serveur OTA souverain. |
🖼️ Gestionnaire d'Assets Dynamiques (OtaAssetManager) #
Permet de mettre à jour des fichiers statiques (traductions JSON fr_CM.json, bannières promotionnelles, icônes SVG) sans aucune recompilation :
final assetManager = OtaAssetManager();
final descriptor = OtaRemoteAssetDescriptor(
assetKey: 'assets/i18n/fr.json',
downloadUrl: 'https://tks-cloud.duckdns.org/api/v1/assets/fr-v2.json',
expectedSha256: '99e887f4f4b6bf9d3abb767d3b5f215e0009e4d26cb7b958497e8b291e1b96dc',
);
// Télécharge, vérifie l'intégrité SHA-256 et stocke dans le cache sécurisé
final file = await assetManager.fetchAndCacheAsset(descriptor);
🛠️ CLI de Déploiement (tk-ota-push) #
La CLI est incluse dans le package et peut être activée globalement sur n'importe quel poste développeur ou serveur CI/CD :
dart pub global activate tk_ota_push
1. Authentification Développeur (Passerelle Web & PKCE) #
tk-ota-push login --server https://tks-cloud.duckdns.org
Ouvre automatiquement votre navigateur pour l'authentification Keycloak / SSO et génère une session chiffrée locale (~/.tk_ota_push/credentials.json).
Vérifier la session active :
tk-ota-push whoami
2. Publication d'une Release du Store (Verrouillage du Binaire de Référence) #
À exécuter le jour où vous soumettez votre binaire sur Google Play ou Apple App Store :
tk-ota-push release 1.0.0+3 \
--server https://tks-cloud.duckdns.org \
--split-debug-info=./build/symbols
Reçu généré dans le terminal :
┌──────────────────────────────────────────────────────────────────────────
│ 🔒 REÇU DE VERROUILLAGE RELEASE (RÉFÉRENCE STORE)
├──────────────────────────────────────────────────────────────────────────
│ ID Release (UUID) : a42e271a-31ab-4476-ae69-24e5d2b8448f
│ Application (Tenant): sara-money-demo (sara-money)
│ Version & Cible : v1.0.0+3 • android (aarch64)
│ Binaire Référence : libapp.so (Scellé)
│ Garde-Fou Natif : pubspec.lock (42d889f8... Scellé)
│ Garde-Fou Assets : flutter_assets/ (99e887f4... Scellé)
│ Symboles (.symbols) : Archivés (app.android-arm64.symbols)
│ Statut Release : LIVE_ON_STORE
└──────────────────────────────────────────────────────────────────────────
3. Publication d'un Patch à Chaud (Différentiel bidiff + zstd) #
Après avoir modifié du code Dart :
tk-ota-push patch 1.0.0+3 \
--server https://tks-cloud.duckdns.org \
--channel stable \
--message "Correction calcul frais de virement" \
--split-debug-info=./build/symbols
Reçu généré dans le terminal :
┌──────────────────────────────────────────────────────────────────────────
│ 🚀 REÇU DE PUBLICATION PATCH OTA #1
├──────────────────────────────────────────────────────────────────────────
│ ID Patch (UUID) : 8b73f831-29cf-4cb6-8801-44ebda29910a
│ Application & Version: sara-money-demo • v1.0.0+3
│ Cible & Canal : android (aarch64) • Canal 'stable'
│ Statut & Rollout : APPROVED_AND_SIGNED (Déploiement : 100%)
│ Taille Différentiel : 142.0 KB (145408 octets)
│ SHA-256 Binaire : 112233445566778899aabbccddeeff001122334455...
│ Signature Vault RSA : d7a81c4e2b0f... (Scellée dans Vault)
│ Symboles (.symbols) : Archivés (app.android-arm64.symbols)
│ Auteur (Git / Dev) : dev@saramoney.cm
└──────────────────────────────────────────────────────────────────────────
4. Workflow à 4 Yeux (--staged) & Promotion de Patch #
Pour soumettre un patch à validation sans le déployer immédiatement :
# 1. Le développeur soumet le patch sur staging
tk-ota-push patch 1.0.0+3 --channel staging --staged --server https://tks-cloud.duckdns.org
# 2. Le Release Manager / RSSI approuve et promeut vers la production (Re-signature Vault automatique)
tk-ota-push promote --patch-id <UUID_DU_PATCH> --to-channel stable --server https://tks-cloud.duckdns.org
📜 Options & Flags de la CLI #
| Commande | Option / Flag | Description |
|---|---|---|
release / patch |
--server <url> |
URL du serveur OTA (défaut : http://localhost:8080 ou session CLI). |
release / patch |
--tenant-id <id> |
Identifiant du Tenant (outrepasse shorebird.yaml). |
release / patch |
--split-debug-info=<dir> |
Répertoire des symboles Dart pour l'archivage automatique .symbols. |
release / patch |
--obfuscate / --no-obfuscate |
Active ou désactive l'obfuscation de code Dart (activé par défaut). |
patch |
--channel <nom> |
Canal de destination : stable, staging, beta (défaut : stable). |
patch |
--rollout <1-100> |
Pourcentage de déploiement progressif Canary (défaut : 100). |
patch |
--staged |
Soumet le patch à l'état STAGED pour approbation à 4 yeux (Maker-Checker). |
patch |
--message <texte> |
Description métier ou traçabilité Git liée au correctif. |
| Toutes | --json |
Affiche la réponse brute de l'API en JSON (pour intégrations CI/CD). |
📄 Licence #
Ce package est distribué sous licence MIT. Consultez le fichier LICENSE pour plus d'informations.