feat(deploy): install silencieuse (dashboard) + MAJ GPO clients Léa + procédure Julien — 2026-07-06
Some checks failed
tests / Lint (ruff + black) (push) Failing after 1m58s
tests / Tests unitaires (sans GPU) (push) Failing after 2m8s
tests / Tests sécurité (critique) (push) Has been skipped

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:
Dom
2026-07-06 10:22:34 +02:00
parent f4bef1de37
commit 48b1fc2800
8 changed files with 547 additions and 0 deletions

View File

@@ -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
# =============================================================================