feat(deploy): install silencieuse (dashboard) + MAJ GPO clients Léa + procédure Julien — 2026-07-06
Deux canaux de déploiement des clients Léa, en s'appuyant sur l'installeur EXE
v1.0.2 existant (per-user, upgrade-safe, mode /VERYSILENT /CONFIG=).
- Route dashboard `GET /api/fleet/installer` (web_dashboard/app.py, insertion pure,
125 lignes, 0 suppression) : sert un ZIP {EXE + install_silencieux.bat + enroll.txt
(SERVER_URL/token injectés via _resolve_public_server_url) + LISEZMOI} pour un poste
NEUF. L'EXE génère lui-même le machine_id → pas de pré-enregistrement (distinct de
la voie ZIP download_agent_package). Tri EXE en semver (1.0.10 > 1.0.2). Testée e2e.
- deploy/installer/silent/ : lanceur d'install silencieuse + gabarit enroll + notice.
- deploy/gpo/Deploy-LeaUpdate.ps1 : MAJ upgrade-safe via GPO **logon script user-context**
(install per-user invisible au contexte SYSTEM). Détection version = **DisplayVersion
du registre HKCU** (primaire pour install EXE ; le config.txt EXE ne porte PAS la
version — vérifié sur poste réel), fallback RPA_AGENT_VERSION (installs ZIP).
Comparaison semver, idempotent, journalisation best-effort silencieuse, non bloquant
à l'ouverture de session. Options -DryRun / -TargetVersionOverride / -LogShareOverride.
**Validé sur poste réel .11 (Windows PowerShell 5.1)** : branche « déjà à jour » et
branche « MAJ requise » (DryRun, 0 install), détection = 1.0.2.
- docs/PROCEDURE_DEPLOIEMENT_LEA_JULIEN.md : procédure (poste neuf / MAJ manuelle / GPO / rollback).
- EXE binaire jamais versionné (deploy/installer/releases/.gitignore).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
# =============================================================================
|
||||
|
||||
Reference in New Issue
Block a user