diff --git a/deploy/gpo/Deploy-LeaUpdate.ps1 b/deploy/gpo/Deploy-LeaUpdate.ps1 new file mode 100644 index 000000000..2c35663c6 --- /dev/null +++ b/deploy/gpo/Deploy-LeaUpdate.ps1 @@ -0,0 +1,181 @@ +<# +.SYNOPSIS + Met a jour l'agent Lea (installe per-user) via un script d'OUVERTURE DE SESSION GPO. + +.DESCRIPTION + ============================================================================ + A DEPLOYER EN : User Configuration > Policies > Windows Settings > + Scripts (Logon/Logoff) > Logon. + ET PAS EN "Computer > Startup". + ============================================================================ + RAISON (non negociable) : l'installeur Lea est PER-USER + (%LOCALAPPDATA%\Programs\Lea, cle de desinstallation sous HKCU). Un script de + DEMARRAGE MACHINE tourne en contexte SYSTEM : il ne verrait NI l'installation + NI HKCU du collaborateur -> il ne mettrait jamais rien a jour. Le contexte + UTILISATEUR est donc obligatoire. + + Le script, a chaque ouverture de session : + 1. Detecte la version installee (DisplayVersion de la cle de desinstallation + Inno dans HKCU — primaire pour un install EXE ; fallback RPA_AGENT_VERSION + du config.txt, present uniquement sur les installs via ZIP). + 2. La compare a la version cible (comparaison semver correcte : + 1.0.2 < 1.0.10, contrairement a une comparaison de chaines). + 3. Si plus ancienne : lance l'installeur en mode SILENCIEUX + UPGRADE-SAFE + (SANS /CONFIG -> machine_id, serveur et token du poste PRESERVES). + 4. Journalise le resultat. IDEMPOTENT : ne refait rien si deja a jour. + + Ne leve JAMAIS d'exception bloquant l'ouverture de session (tout en try/catch, + sortie 0 sur erreur non geree). + +.NOTES + Execution : configurer la GPO pour lancer + powershell.exe -NoProfile -ExecutionPolicy Bypass -File Deploy-LeaUpdate.ps1 + + Test sur un poste (lecture seule, ne telecharge/installe RIEN) : + powershell.exe -NoProfile -ExecutionPolicy Bypass -File Deploy-LeaUpdate.ps1 -DryRun +#> + +param( + # -DryRun : teste la DECISION (version installee vs cible) et journalise ce + # qui SERAIT fait, sans rien telecharger ni installer. Pour valider un poste. + [switch]$DryRun, + # Overrides optionnels (sinon = valeurs du bloc PARAMETRES ci-dessous). + # Utiles pour tester un poste (cible bumpee, journal local) sans editer le script. + [string]$TargetVersionOverride, + [string]$LogShareOverride +) + +# ============================================================================ +# PARAMETRES A ADAPTER PAR LE DSI / Julien (les 3 seules lignes a toucher) +# ============================================================================ + +# Source de l'installeur : chemin UNC du domaine (recommande) OU URL http(s). +# UNC : \\SRV-FICHIERS\Deploiement$\Lea\Lea-Setup-v1.0.2.exe +# HTTPS : https://lea.labs.laurinebazin.design/api/fleet/installer/exe +$ExeSource = '\\SRV-FICHIERS\Deploiement$\Lea\Lea-Setup-v1.0.2.exe' + +# Version cible : DOIT correspondre a l'EXE ci-dessus. +$TargetVersion = '1.0.2' + +# Dossier de journalisation partage (audit Julien). Vide => journal local %TEMP%. +$LogShare = '\\SRV-FICHIERS\Deploiement$\Lea\logs' + +# ============================================================================ +# Ne rien modifier en dessous de cette ligne. +# ============================================================================ + +# Overrides de ligne de commande (prioritaires sur le bloc PARAMETRES). +if ($TargetVersionOverride) { $TargetVersion = $TargetVersionOverride } +if ($PSBoundParameters.ContainsKey('LogShareOverride')) { $LogShare = $LogShareOverride } + +$InstallDir = Join-Path $env:LOCALAPPDATA 'Programs\Lea' +$ConfigTxt = Join-Path $InstallDir 'config.txt' +$AppUninst = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\{B3F9A1E2-5C4D-4E7F-9A1B-2C3D4E5F6789}_is1' + +function Write-Log([string]$msg) { + $line = '{0} {1}\{2} {3}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $env:COMPUTERNAME, $env:USERNAME, $msg + Write-Output $line + # Journalisation best-effort : ne DOIT jamais perturber l'ouverture de session. + # -ErrorAction SilentlyContinue car un partage injoignable produit une erreur + # NON-terminante (non capturee par try/catch) — verifie sur poste reel .11. + try { + $dir = if ($LogShare) { $LogShare } else { Join-Path $env:TEMP 'Lea-update-logs' } + if (-not (Test-Path $dir -ErrorAction SilentlyContinue)) { + New-Item -ItemType Directory -Path $dir -Force -ErrorAction SilentlyContinue | Out-Null + } + $file = Join-Path $dir ('{0}_{1}.log' -f $env:COMPUTERNAME, $env:USERNAME) + Add-Content -Path $file -Value $line -Encoding UTF8 -ErrorAction SilentlyContinue + } catch { } +} + +function Get-InstalledVersion { + # 1. Source de verite pour un install EXE (Inno) : DisplayVersion dans HKCU + # (install per-user). Toujours ecrite par l'installeur -> methode primaire. + # Verifie sur poste reel .11 : le config.txt genere par l'EXE ne contient + # PAS de ligne de version, seul le registre fait foi. + if (Test-Path $AppUninst) { + $dv = (Get-ItemProperty -Path $AppUninst -Name DisplayVersion -ErrorAction SilentlyContinue).DisplayVersion + if ($dv) { return $dv } + } + # 2. Fallback : RPA_AGENT_VERSION dans config.txt (present sur les installs + # via ZIP/Python ; absent des installs via EXE). + if (Test-Path $ConfigTxt) { + $m = Select-String -Path $ConfigTxt -Pattern '^\s*RPA_AGENT_VERSION\s*=\s*(.+?)\s*$' -ErrorAction SilentlyContinue + if ($m) { return $m.Matches[0].Groups[1].Value.Trim() } + } + return $null +} + +function ConvertTo-VersionParts([string]$v) { + $clean = ($v -replace '^[vV]', '').Trim() + $parts = New-Object System.Collections.Generic.List[int] + foreach ($p in ($clean -split '\.')) { + $n = 0 + if ([int]::TryParse($p, [ref]$n)) { $parts.Add($n) } else { $parts.Add(0) } + } + while ($parts.Count -lt 3) { $parts.Add(0) } + return $parts +} + +function Compare-Semver([string]$a, [string]$b) { + # -1 si ab. Tolerant (piege 1.0.2 < 1.0.10). + $pa = ConvertTo-VersionParts $a + $pb = ConvertTo-VersionParts $b + $max = [Math]::Max($pa.Count, $pb.Count) + for ($i = 0; $i -lt $max; $i++) { + $va = if ($i -lt $pa.Count) { $pa[$i] } else { 0 } + $vb = if ($i -lt $pb.Count) { $pb[$i] } else { 0 } + if ($va -lt $vb) { return -1 } + if ($va -gt $vb) { return 1 } + } + return 0 +} + +try { + $installed = Get-InstalledVersion + if (-not $installed) { + Write-Log 'Lea non installe sur ce profil -> ignore (les nouveaux postes passent par le dashboard).' + exit 0 + } + if ((Compare-Semver $installed $TargetVersion) -ge 0) { + Write-Log ('Deja a jour (installe={0}, cible={1}) -> rien a faire.' -f $installed, $TargetVersion) + exit 0 + } + + if ($DryRun) { + Write-Log ('[DryRun] MAJ qui SERAIT appliquee : {0} -> {1}. Aucune action (ni telechargement ni installation).' -f $installed, $TargetVersion) + exit 0 + } + + Write-Log ('MAJ requise : installe={0} < cible={1}. Recuperation de l''installeur...' -f $installed, $TargetVersion) + + # Recuperer l'EXE (UNC ou URL) vers un fichier temporaire local. + $localExe = Join-Path $env:TEMP ('Lea-Setup-{0}.exe' -f $TargetVersion) + if ($ExeSource -match '^(?i)https?://') { + Invoke-WebRequest -Uri $ExeSource -OutFile $localExe -UseBasicParsing + } else { + Copy-Item -Path $ExeSource -Destination $localExe -Force + } + if (-not (Test-Path $localExe)) { + Write-Log ('ECHEC : installeur introuvable apres recuperation ({0}).' -f $ExeSource) + exit 1 + } + + # Installation SILENCIEUSE + UPGRADE-SAFE (sans /CONFIG : identite du poste preservee). + # L'installeur arrete Lea (via lock), sauvegarde _backup, puis met a jour. + $logInstall = Join-Path $env:TEMP ('Lea-upgrade-{0}.log' -f (Get-Date -Format 'yyyyMMdd_HHmmss')) + $installArgs = @('/VERYSILENT', '/SUPPRESSMSGBOXES', '/NORESTART', ('/LOG={0}' -f $logInstall)) + $proc = Start-Process -FilePath $localExe -ArgumentList $installArgs -Wait -PassThru + $rc = $proc.ExitCode + + if ($rc -eq 0) { + $now = Get-InstalledVersion + Write-Log ('MAJ OK : {0} -> {1} (code 0).' -f $installed, $now) + } else { + Write-Log ('ECHEC MAJ : code {0}. Journal installeur : {1}.' -f $rc, $logInstall) + } + exit $rc +} catch { + Write-Log ('ERREUR non geree : ' + $_.Exception.Message) + exit 0 # ne JAMAIS bloquer l'ouverture de session +} diff --git a/deploy/installer/releases/.gitignore b/deploy/installer/releases/.gitignore new file mode 100644 index 000000000..5d78a1dc0 --- /dev/null +++ b/deploy/installer/releases/.gitignore @@ -0,0 +1,5 @@ +# L'installeur EXE (~33 Mo binaire) est déposé ici mais JAMAIS versionné. +# On garde le dossier + son LISEZMOI, on ignore tout le reste. +* +!.gitignore +!LISEZMOI.txt diff --git a/deploy/installer/releases/LISEZMOI.txt b/deploy/installer/releases/LISEZMOI.txt new file mode 100644 index 000000000..a7734d86e --- /dev/null +++ b/deploy/installer/releases/LISEZMOI.txt @@ -0,0 +1,17 @@ +============================================================ + deploy/installer/releases/ — Depot des installeurs EXE +============================================================ + +DEPOSER ICI l'installeur "Lea-Setup-vX.Y.Z.exe" (buildé sur .11). + +Le dashboard sert le plus RECENT via la route : + GET /api/fleet/installer + -> paquet ZIP "Lea-Install-Silencieux-vX.Y.Z.zip" + (EXE + install_silencieux.bat + enroll.txt pré-rempli + LISEZMOI) + +L'EXE (~33 Mo binaire) n'est VOLONTAIREMENT pas versionné dans git. +Pour pointer ailleurs : variable d'environnement RPA_LEA_RELEASES_DIR. + +Ce fichier LISEZMOI.txt sert uniquement à garder le dossier présent +dans le dépôt (l'EXE, lui, est déposé à la main / par script de release). +============================================================ diff --git a/deploy/installer/silent/LISEZMOI_SILENCIEUX.txt b/deploy/installer/silent/LISEZMOI_SILENCIEUX.txt new file mode 100644 index 000000000..fd2cf8de5 --- /dev/null +++ b/deploy/installer/silent/LISEZMOI_SILENCIEUX.txt @@ -0,0 +1,40 @@ +============================================================ + Lea - Installation silencieuse (nouveau poste) +============================================================ + +CE DOSSIER CONTIENT : + - Lea-Setup-vX.Y.Z.exe : l'installeur Lea + - install_silencieux.bat : le lanceur d'installation + - enroll.txt : les parametres d'enrolement a remplir + - LISEZMOI_SILENCIEUX.txt : ce fichier + +------------------------------------------------------------ +QUAND UTILISER CETTE VOIE ? +------------------------------------------------------------ +Pour installer Lea sur un poste qui NE l'a PAS encore. +(Pour METTRE A JOUR un poste deja installe : voir la voie GPO, + ou relancer simplement l'installeur qui detecte l'existant.) + +------------------------------------------------------------ +PROCEDURE (3 etapes, ~2 min, sans droits administrateur) +------------------------------------------------------------ + 1. Ouvrir "enroll.txt" avec le Bloc-notes. + Renseigner : USER_NAME, USER_EMAIL, SERVER_URL, API_TOKEN. + (Remplacer les valeurs "CONFIGURE_ME".) Enregistrer. + + 2. Double-cliquer sur "install_silencieux.bat". + L'installation se deroule sans fenetre d'installation. + Ne pas fermer la fenetre noire avant le message "OK". + + 3. Verifier que Lea demarre (icone dans la barre des taches, + pres de l'horloge). C'est termine. + +------------------------------------------------------------ +EN CAS DE PROBLEME +------------------------------------------------------------ + - Message "valeurs CONFIGURE_ME" : enroll.txt n'est pas rempli. + - Message "aucun Lea-Setup trouve" : l'EXE n'est pas dans ce dossier. + - Autre erreur : le journal detaille est indique a l'ecran + (dossier %TEMP%, fichier Lea-install-silencieux.log). + Le transmettre a l'administrateur AIVANOV. +============================================================ diff --git a/deploy/installer/silent/enroll.txt b/deploy/installer/silent/enroll.txt new file mode 100644 index 000000000..588ef5e68 --- /dev/null +++ b/deploy/installer/silent/enroll.txt @@ -0,0 +1,31 @@ +# ============================================================ +# enroll.txt — Enrolement pour installation SILENCIEUSE +# ------------------------------------------------------------ +# Utilise par install_silencieux.bat : +# Lea-Setup-vX.Y.Z.exe /VERYSILENT /CONFIG=enroll.txt +# +# L'installeur lit ce fichier au demarrage et remplit les +# informations d'enrolement SANS interface graphique. +# +# A FAIRE : renseigner les valeurs ci-dessous (remplacer les +# CONFIGURE_ME), enregistrer, puis lancer install_silencieux.bat. +# +# Format : CLE=valeur, une ligne par parametre, # = commentaire. +# NOTE : ce fichier sert l'INSTALL NEUVE. Sur une mise a jour, +# l'installeur preserve tout seul l'identite du poste et +# IGNORE ce fichier (voir la voie GPO). +# ============================================================ + +# --- Identite du collaborateur --- +USER_NAME=Prenom Nom +USER_EMAIL=prenom.nom@clinique-wallerstein.fr +USER_ID= + +# --- Connexion au serveur Lea --- +# Exemples de SERVER_URL : +# LAN clinique (DGX) : http://192.168.1.178:5005/api/v1 +# Internet : https://lea.labs.laurinebazin.design/api/v1 +SERVER_URL=CONFIGURE_ME + +# Token d'authentification (fourni par l'administrateur AIVANOV) +API_TOKEN=CONFIGURE_ME diff --git a/deploy/installer/silent/install_silencieux.bat b/deploy/installer/silent/install_silencieux.bat new file mode 100644 index 000000000..1ed11c9d7 --- /dev/null +++ b/deploy/installer/silent/install_silencieux.bat @@ -0,0 +1,73 @@ +@echo off +chcp 65001 >nul 2>&1 +setlocal enabledelayedexpansion +title Lea - Installation silencieuse (nouveau poste) + +:: ============================================================ +:: install_silencieux.bat +:: Installe Lea sur un NOUVEAU poste, sans interface graphique. +:: +:: Pre-requis : ce script, l'installeur "Lea-Setup-v*.exe" et +:: "enroll.txt" (rempli) doivent etre dans le MEME dossier. +:: +:: Installation PER-USER : aucun droit administrateur requis. +:: ============================================================ + +set "HERE=%~dp0" +set "ENROLL=%HERE%enroll.txt" +set "LOG=%TEMP%\Lea-install-silencieux.log" + +echo. +echo ============================================================ +echo Lea - Installation silencieuse (nouveau poste) +echo ============================================================ +echo. + +:: --- 1. Localiser l'installeur (Lea-Setup-v*.exe, version la plus recente) --- +set "EXE=" +for /f "delims=" %%f in ('dir /b /o-n "%HERE%Lea-Setup-v*.exe" 2^>nul') do ( + if not defined EXE set "EXE=%HERE%%%f" +) +if not defined EXE ( + echo ERREUR : aucun "Lea-Setup-v*.exe" trouve dans ce dossier. + echo Placez ce script a cote de l'installeur Lea. + echo. + pause + exit /b 1 +) +echo Installeur detecte : !EXE! + +:: --- 2. Verifier enroll.txt --- +if not exist "%ENROLL%" ( + echo ERREUR : "enroll.txt" introuvable a cote de ce script. + echo. + pause + exit /b 1 +) +findstr /C:"CONFIGURE_ME" "%ENROLL%" >nul 2>&1 +if not errorlevel 1 ( + echo. + echo ATTENTION : "enroll.txt" contient encore des valeurs CONFIGURE_ME. + echo Ouvrez enroll.txt, renseignez SERVER_URL / API_TOKEN / USER_*, + echo enregistrez, puis relancez ce script. + echo. + pause + exit /b 1 +) + +:: --- 3. Installation silencieuse (per-user, pas d'admin) --- +echo Installation en cours (silencieuse, ~1-2 min)... ne fermez pas cette fenetre. +"!EXE!" /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /CONFIG="%ENROLL%" /LOG="%LOG%" +set "RC=!errorlevel!" + +echo. +if "!RC!"=="0" ( + echo Installation terminee - OK. + echo Lea est enrolle et pret. Journal : %LOG% +) else ( + echo ERREUR : l'installation a echoue ^(code !RC!^). + echo Consultez le journal : %LOG% +) +echo. +pause +endlocal diff --git a/docs/PROCEDURE_DEPLOIEMENT_LEA_JULIEN.md b/docs/PROCEDURE_DEPLOIEMENT_LEA_JULIEN.md new file mode 100644 index 000000000..6ce7aee52 --- /dev/null +++ b/docs/PROCEDURE_DEPLOIEMENT_LEA_JULIEN.md @@ -0,0 +1,75 @@ +# Procédure de déploiement Léa — pour Julien (informaticien clinique) + +> Version agent cible : **1.0.2** · Installeur : `Lea-Setup-v1.0.2.exe` (per-user, aucun droit admin requis). +> Rédigé le 2026-07-06. Poste pilote validé : **Émilie**. + +Trois cas. Le n°1 pour un poste **neuf**, le n°2/3 pour **mettre à jour** un poste déjà installé. + +--- + +## 1. Installer Léa sur un NOUVEAU poste (install silencieuse) + +1. Depuis un navigateur (dashboard Léa), télécharger le paquet : + **`GET /api/fleet/installer`** → `Lea-Install-Silencieux-vX.Y.Z.zip`. + *(Le ZIP contient l'EXE + `install_silencieux.bat` + `enroll.txt` déjà pré-rempli avec l'URL serveur et le token.)* +2. Sur le poste cible : dézipper le paquet dans un dossier. +3. Ouvrir **`enroll.txt`** (Bloc-notes), compléter **`USER_NAME`** et **`USER_EMAIL`** (l'URL et le token sont déjà remplis). Enregistrer. +4. Double-cliquer **`install_silencieux.bat`**. Attendre le message **« Installation terminée - OK »**. +5. Vérifier : icône Léa près de l'horloge (barre des tâches). + +> L'installeur génère un **`machine_id` unique** pour ce poste : rien à pré-enregistrer. + +--- + +## 2. Mettre à jour un poste déjà installé — voie MANUELLE (immédiate) + +C'est la voie utilisée pour Émilie, à rejouer poste par poste **en attendant l'autorisation AD/GPO**. + +1. Récupérer **`Lea-Setup-v1.0.2.exe`** (le même EXE, aucune régénération). +2. Sur le poste, le lancer par **double-clic** (mode interactif). +3. Le wizard doit s'afficher **pré-rempli** avec la config du poste (serveur/token/utilisateur). + ⚠️ **S'il est vide** → l'installeur n'a pas détecté l'install existante : **ne pas continuer**, prévenir AIVANOV (risque de créer un nouveau poste au lieu de mettre à jour). +4. Valider. L'installeur **arrête Léa**, sauvegarde l'ancienne version (`_backup`), met à jour en **préservant l'identité** du poste, purge les captures accumulées. +5. Vérifier après MAJ (mêmes points qu'Émilie) : Léa relancée en **v1.0.2**, icône présente, capture d'écran OK, comportement RDP OK. + +> **Rollback** si besoin : restaurer le dossier `_backup` (le dossier d'install est `%LOCALAPPDATA%\Programs\Lea`). + +--- + +## 3. Mettre à jour la flotte via AD/GPO (quand l'autorisation est en place) + +Script fourni : **`deploy/gpo/Deploy-LeaUpdate.ps1`**. + +### ⚠️ Point crucial : script d'OUVERTURE DE SESSION (contexte utilisateur) +Léa est installée **par utilisateur** (`%LOCALAPPDATA%`). Un script de **démarrage machine** (contexte SYSTEM) **ne verrait pas** l'installation et ne mettrait rien à jour. Le script doit donc être en : +**`User Configuration → Policies → Windows Settings → Scripts (Logon/Logoff) → Logon`.** + +### Étapes +1. Déposer `Lea-Setup-v1.0.2.exe` sur un partage du domaine accessible aux postes (ex. `\\SRV-FICHIERS\Deploiement$\Lea\`). +2. Éditer les **3 lignes de paramètres** en tête de `Deploy-LeaUpdate.ps1` : + - `$ExeSource` = chemin UNC (ou URL) de l'EXE ; + - `$TargetVersion` = `1.0.2` (doit correspondre à l'EXE) ; + - `$LogShare` = dossier partagé pour les journaux d'audit (ou vide → journal local). +3. Ajouter le script en **Logon script** (commande : `powershell.exe -NoProfile -ExecutionPolicy Bypass -File Deploy-LeaUpdate.ps1`). +4. À l'ouverture de session, le script : détecte la version installée, compare à la cible, met à jour **seulement si plus ancienne** (idempotent, silencieux, upgrade-safe), puis journalise. + +### Comportement (garanti par le script) +- Poste **déjà à jour** → ne fait rien. +- Poste **sans Léa** → ignoré (les postes neufs passent par le cas 1). +- Erreur (partage indisponible, etc.) → journalisée, **n'empêche jamais l'ouverture de session**. + +### Audit +Un journal par poste/utilisateur dans `$LogShare` : `_.log` (ligne datée : version installée → version après MAJ, ou motif d'échec). + +--- + +## Aide-mémoire + +| Besoin | Voie | Artefact | +|---|---|---| +| Poste neuf | Install silencieuse | Dashboard `GET /api/fleet/installer` | +| MAJ immédiate d'un poste | Manuelle (double-clic) | `Lea-Setup-v1.0.2.exe` | +| MAJ de toute la flotte | AD/GPO (logon script) | `deploy/gpo/Deploy-LeaUpdate.ps1` | +| Revenir en arrière | Rollback | dossier `_backup` | + +En cas de doute ou de wizard **non** pré-rempli lors d'une MAJ : **s'arrêter et contacter AIVANOV**. diff --git a/web_dashboard/app.py b/web_dashboard/app.py index af08283ff..09087b8c5 100644 --- a/web_dashboard/app.py +++ b/web_dashboard/app.py @@ -2481,6 +2481,131 @@ def download_agent_package(machine_id): ) +# ============================================================================= +# Fleet — Téléchargement de l'installeur SILENCIEUX (EXE, nouveaux postes) +# ============================================================================= +# +# Voie EXE (python-embedded, upgrade-safe) DISTINCTE de la voie ZIP/Python +# ci-dessus (download_agent_package). Sert un paquet d'installation silencieuse +# pour un NOUVEAU poste : l'EXE génère lui-même un machine_id unique → pas de +# pré-enregistrement requis (contrairement à la voie ZIP, qui bake un +# machine_id assigné). L'EXE lourd (~33 Mo binaire) n'est PAS versionné : il est +# déposé sur l'hôte dans RPA_LEA_RELEASES_DIR (défaut deploy/installer/releases/) +# après build sur .11. + +_LEA_RELEASES_DIR = Path( + os.getenv("RPA_LEA_RELEASES_DIR", str(BASE_PATH / "deploy" / "installer" / "releases")) +) +_LEA_SILENT_DIR = BASE_PATH / "deploy" / "installer" / "silent" + + +def _silent_exe_version(name: str) -> str: + """Extrait la version d'un nom "Lea-Setup-v1.0.2.exe" → "1.0.2" (sans regex).""" + stem = name[:-4] if name.lower().endswith(".exe") else name + prefix = "Lea-Setup-v" + return stem[len(prefix):] if stem.startswith(prefix) else "0.0.0" + + +def _resolve_silent_installer_exe(releases_dir: "Path | None" = None) -> "Path | None": + """Retourne l'EXE Lea-Setup-v*.exe le plus récent du dossier releases, ou None. + + Tri par version sémantique décroissante (pas lexicographique : 1.0.10 > 1.0.2). + Paramètre injectable pour les tests (évite de démarrer Flask — DETTE-013). + """ + d = releases_dir or _LEA_RELEASES_DIR + try: + exes = list(d.glob("Lea-Setup-v*.exe")) + except Exception: + return None + if not exes: + return None + + def _ver_key(p: Path): + parts = [] + for tok in _silent_exe_version(p.name).split("."): + parts.append(int(tok) if tok.isdigit() else 0) + return parts + + return sorted(exes, key=_ver_key, reverse=True)[0] + + +def _build_silent_enroll(token: str) -> str: + """Génère enroll.txt (clés /CONFIG de l'installeur EXE) avec SERVER_URL et + token injectés. USER_* laissés en placeholder : remplis par poste avant le + lancement de install_silencieux.bat. + """ + server_url = _resolve_public_server_url() + return f"""\ +# ============================================================ +# enroll.txt — Enrolement pour installation SILENCIEUSE +# Genere par le dashboard Fleet. SERVER_URL et API_TOKEN sont +# deja renseignes. A COMPLETER : USER_NAME / USER_EMAIL. +# Puis lancer install_silencieux.bat. +# ============================================================ + +# --- Identite du collaborateur (a remplir) --- +USER_NAME=Prenom Nom +USER_EMAIL=prenom.nom@clinique-wallerstein.fr +USER_ID= + +# --- Connexion serveur (pre-renseignee, ne pas modifier) --- +SERVER_URL={server_url} +API_TOKEN={token} +""" + + +@app.route('/api/fleet/installer') +def download_silent_installer(): + """Sert un paquet ZIP d'installation SILENCIEUSE pour un nouveau poste. + + Contenu : Lea-Setup-v*.exe + install_silencieux.bat + enroll.txt + (SERVER_URL/token pré-injectés) + LISEZMOI. L'auth Basic est gérée par + before_request. + """ + exe = _resolve_silent_installer_exe() + if exe is None: + api_logger.error( + "download_silent_installer — aucun Lea-Setup-v*.exe dans %s.", + _LEA_RELEASES_DIR, + ) + return jsonify({ + 'error': 'Installeur EXE introuvable', + 'detail': ( + f'Aucun Lea-Setup-v*.exe dans {_LEA_RELEASES_DIR}. ' + 'Déposer l\'EXE (buildé sur .11) dans ce dossier, ou définir ' + 'la variable d\'environnement RPA_LEA_RELEASES_DIR.' + ), + }), 404 + + version = _silent_exe_version(exe.name) + token = os.environ.get('RPA_API_TOKEN', 'CONFIGURE_ME') + enroll_txt = _build_silent_enroll(token) + + # Artefacts d'accompagnement — source de vérité = deploy/installer/silent/. + bat_path = _LEA_SILENT_DIR / "install_silencieux.bat" + readme_path = _LEA_SILENT_DIR / "LISEZMOI_SILENCIEUX.txt" + + output_buffer = io.BytesIO() + with zipfile.ZipFile(output_buffer, 'w', zipfile.ZIP_DEFLATED) as z: + z.write(exe, arcname=exe.name) + if bat_path.exists(): + z.write(bat_path, arcname="install_silencieux.bat") + if readme_path.exists(): + z.write(readme_path, arcname="LISEZMOI_SILENCIEUX.txt") + z.writestr("enroll.txt", enroll_txt.encode('utf-8')) + output_buffer.seek(0) + + api_logger.info( + "download_silent_installer — paquet servi (EXE %s, v%s).", exe.name, version + ) + return send_file( + output_buffer, + as_attachment=True, + download_name=f"Lea-Install-Silencieux-v{version}.zip", + mimetype='application/zip', + ) + + # ============================================================================= # Cartographie des processus — Process Mining # =============================================================================