Architecture d'un auto-update robuste
Un système d'auto-update Python fiable repose sur 5 composants distincts : le checker de version (interroge le serveur), le downloader delta (télécharge seulement les différences), l'applicateur (installe la mise à jour), le launcher (redémarre proprement) et le rollback (restaure si échec). Universal Installer V2 fournit tous ces composants.
Un auto-update qui plante laisse l'application dans un état incohérent. La règle absolue : ne jamais modifier les fichiers actifs en cours d'exécution. Toujours écrire dans un dossier temporaire, puis basculer atomiquement.
Vérification de version
from universal_installer.updater import UpdateChecker
from packaging.version import Version
checker = UpdateChecker(
manifest_url="https://cdn.exemple.fr/releases/manifest.json",
public_key_path="keys/public.pem",
current_version="1.1.0"
)
update_info = checker.check()
if update_info.available:
print(f"Mise à jour disponible : {update_info.version}")
print(f"Taille patch : {update_info.patch_size_mb:.1f} MB")
print(f"Taille complète : {update_info.full_size_mb:.1f} MB")
print(f"Changelog : {update_info.changelog_url}")
else:
print("Application à jour.")
Téléchargement delta
from universal_installer.updater import DeltaDownloader
import threading
downloader = DeltaDownloader(update_info)
# Téléchargement en arrière-plan avec callback de progression
def on_progress(downloaded_mb, total_mb, speed_mbps):
pct = int(downloaded_mb / total_mb * 100)
update_ui_progress(pct, f"{speed_mbps:.1f} MB/s")
thread = threading.Thread(
target=downloader.download,
kwargs={{
'output_path': 'tmp/update.uxcd',
'progress_callback': on_progress,
'verify_signature': True, # ECDSA P-384
'resume': True # reprise si interrompu
}}
)
thread.start()
# L'application continue à fonctionner pendant le téléchargement
Application de la mise à jour
from universal_installer.updater import UpdateApplicator
applicator = UpdateApplicator(
current_dir=r"C:\MonApp",
backup_dir=r"C:\MonApp_backup",
patch_path="tmp/update.uxcd"
)
# L'applicateur :
# 1. Sauvegarde current_dir → backup_dir
# 2. Extrait le patch dans un dossier tmp
# 3. Fusionne les fichiers modifiés avec la version actuelle
# 4. Vérifie le SHA-256 de chaque fichier mis à jour
# 5. Valide l'intégrité globale de la nouvelle version
result = applicator.apply()
if not result.success:
print(f"Échec : {result.error}")
applicator.rollback() # restaure backup automatiquement
Redémarrage propre
Impossible de remplacer un EXE en cours d'exécution sur Windows. La technique : lancer un process updater séparé qui attend la fermeture de l'application principale, remplace les fichiers, puis relance :
import subprocess, sys, os
from pathlib import Path
def schedule_restart_update(patch_path: str):
updater_exe = Path(sys.executable).parent / "ui-updater.exe"
# Lancer l'updater qui attend PID courant + applique + relance
subprocess.Popen([
str(updater_exe),
"--wait-pid", str(os.getpid()),
"--patch", patch_path,
"--app", sys.executable,
"--backup", r"C:\MonApp_backup"
], creationflags=subprocess.DETACHED_PROCESS)
# Fermer l'application principale proprement
save_application_state()
sys.exit(0)
Rollback automatique
# Dans ui-updater.exe — après application du patch
def verify_update_and_launch(app_path: str, backup_path: str) -> bool:
# Lancer la nouvelle version avec timeout de démarrage
proc = subprocess.Popen([app_path, "--post-update-check"])
try:
proc.wait(timeout=30) # 30s pour confirmer le démarrage
if proc.returncode == 0:
shutil.rmtree(backup_path) # Succès → supprimer le backup
return True
except subprocess.TimeoutExpired:
proc.kill()
# Échec → rollback
shutil.rmtree(Path(app_path).parent)
shutil.copytree(backup_path, Path(app_path).parent)
subprocess.Popen([backup_path]) # relancer l'ancienne version
return False
UX de mise à jour
- Téléchargement silencieux — en arrière-plan dès le démarrage, sans bloquer l'interface.
- Notification non intrusive — toast Windows / bandeau en bas de fenêtre : "Mise à jour prête — redémarrer pour installer".
- Redémarrage différé — l'utilisateur choisit le moment du redémarrage, pas forcé.
- Changelog intégré — dialog "Nouveautés de v1.2" avant la confirmation.
- Rollback transparent — si la mise à jour échoue, l'ancienne version se relance automatiquement avec un message explicatif.
Conclusion
Un auto-update Python robuste requiert trois éléments non négociables : le téléchargement en arrière-plan sans bloquer l'UI, l'écriture dans un dossier temporaire avant de basculer (jamais en place), et le rollback automatique si la nouvelle version ne démarre pas. Universal Installer V2 fournit tous ces composants prêts à l'emploi avec vérification ECDSA et delta UXC.