fix(dashboard): servir le ZIP Lea complet autoportant à l'enrôlement Fleet

L'endpoint /api/fleet/download/<machine_id> servait deploy/Lea_v1.0.0.zip
(sources seules, suppose Python système) → installation impossible chez un
utilisateur non-IT sans Python. Désormais il sert en priorité le ZIP complet
deploy/build/Lea_full_v1.0.1.zip (python-embed inclus), avec fallback sur
l'ancien ZIP léger s'il est seul. Résolution du template à la volée (le ZIP
complet peut être buildé après le démarrage du dashboard) + message d'erreur
explicite. L'injection de Lea/config.txt est inchangée.

Le title du bouton de téléchargement ne ment plus : 'installation autonome,
sans Python — dézipper puis double-cliquer Lea.bat'.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dom
2026-06-22 15:58:51 +02:00
parent 33ddb51c3c
commit e212f4141c
2 changed files with 41 additions and 10 deletions

View File

@@ -2219,8 +2219,33 @@ def proxy_fleet(endpoint):
# Fleet — Téléchargement du ZIP installeur pré-configuré
# =============================================================================
# Chemin du ZIP template Léa
_LEA_ZIP_TEMPLATE = BASE_PATH / "deploy" / "Lea_v1.0.0.zip"
# Chemin du ZIP template Léa.
# ZIP COMPLET autoportant (runtime Python embedded inclus + source à jour) :
# l'utilisateur dézippe puis double-clique Lea.bat, sans Python système ni UAC.
# Construit par deploy/build_package_full.sh. Le placeholder Lea/config.txt
# (CONFIGURE_ME) est remplacé à la volée par download_agent_package().
# Fallback historique vers l'ancien ZIP léger (sources seules, suppose Python
# système) uniquement s'il existe et que le complet est absent — pour ne pas
# casser un environnement où le complet n'a pas encore été buildé.
_LEA_ZIP_TEMPLATE_FULL = BASE_PATH / "deploy" / "build" / "Lea_full_v1.0.1.zip"
_LEA_ZIP_TEMPLATE_LEGACY = BASE_PATH / "deploy" / "Lea_v1.0.0.zip"
def _resolve_lea_zip_template():
"""Résout le ZIP à servir, à la volée (le complet peut être buildé
après le démarrage du dashboard). Préfère le ZIP complet autoportant ;
retombe sur l'ancien ZIP léger uniquement s'il existe.
Retourne None si aucun template n'est présent.
"""
if _LEA_ZIP_TEMPLATE_FULL.exists():
return _LEA_ZIP_TEMPLATE_FULL
if _LEA_ZIP_TEMPLATE_LEGACY.exists():
return _LEA_ZIP_TEMPLATE_LEGACY
return None
# Compat : référence statique conservée pour le code/log historique.
_LEA_ZIP_TEMPLATE = _resolve_lea_zip_template() or _LEA_ZIP_TEMPLATE_FULL
# URL publique du serveur (env ou fallback)
_RPA_PUBLIC_URL = os.getenv(
@@ -2356,17 +2381,23 @@ def download_agent_package(machine_id):
"""Génère et sert un ZIP Léa pré-configuré pour ce machine_id.
- Vérifie que le machine_id est enregistré et actif dans la fleet.
- Lit le ZIP template (deploy/Lea_v1.0.0.zip).
- Remplace config.txt par une version personnalisée.
- Lit le ZIP template complet autoportant (deploy/build/Lea_full_v*.zip),
avec fallback sur l'ancien ZIP léger (deploy/Lea_v1.0.0.zip) s'il est seul.
- Remplace Lea/config.txt par une version personnalisée.
- Renvoie le ZIP modifié en téléchargement (tout en mémoire).
"""
# Sécurité : l'auth Basic est déjà gérée par before_request
# 1. Vérifier que le ZIP template existe
if not _LEA_ZIP_TEMPLATE.exists():
# 1. Résoudre + vérifier que le ZIP template existe (à la volée)
zip_template = _resolve_lea_zip_template()
if zip_template is None:
return jsonify({
'error': 'ZIP template introuvable',
'detail': f'{_LEA_ZIP_TEMPLATE} absent — exécuter deploy/build_package.sh',
'detail': (
f'Ni {_LEA_ZIP_TEMPLATE_FULL} ni {_LEA_ZIP_TEMPLATE_LEGACY} '
'présents — exécuter deploy/build_package_full.sh (ZIP complet '
'autoportant) ou deploy/build_package.sh (ZIP léger).'
),
}), 500
# 2. Vérifier que le machine_id est enregistré
@@ -2387,7 +2418,7 @@ def download_agent_package(machine_id):
# 5. Créer le ZIP personnalisé en mémoire
output_buffer = io.BytesIO()
try:
with zipfile.ZipFile(_LEA_ZIP_TEMPLATE, 'r') as src_zip:
with zipfile.ZipFile(zip_template, 'r') as src_zip:
with zipfile.ZipFile(output_buffer, 'w', zipfile.ZIP_DEFLATED) as dst_zip:
for item in src_zip.infolist():
if item.filename == 'Lea/config.txt':
@@ -2399,7 +2430,7 @@ def download_agent_package(machine_id):
except zipfile.BadZipFile:
return jsonify({
'error': 'ZIP template corrompu',
'detail': f'{_LEA_ZIP_TEMPLATE} n\'est pas un ZIP valide.',
'detail': f'{zip_template} n\'est pas un ZIP valide.',
}), 500
output_buffer.seek(0)

View File

@@ -2290,7 +2290,7 @@
: '<span style="display:inline-block;padding:3px 10px;border-radius:12px;font-size:11px;font-weight:600;background:rgba(100,116,139,0.15);color:#64748b;">révoqué</span>';
const downloadBtn = isActive
? `<a href="/api/fleet/download/${encodeURIComponent(agent.machine_id)}" class="btn btn-primary btn-small" style="text-decoration:none;font-size:11px;" title="Télécharger l'installeur pré-configuré">📥</a>`
? `<a href="/api/fleet/download/${encodeURIComponent(agent.machine_id)}" class="btn btn-primary btn-small" style="text-decoration:none;font-size:11px;" title="Télécharger Léa (installation autonome, sans Python — dézipper puis double-cliquer Lea.bat)">📥</a>`
: `<span style="font-size:16px;color:#475569;cursor:not-allowed;opacity:0.4;" title="Agent révoqué — installeur indisponible">📥</span>`;
return `<tr style="border-bottom:1px solid #334155;">