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.

📦
PRODUIT LIÉ
Universal Installer V2.0
← Article précédent Article suivant →