diff --git a/deploy/build_package_full.sh b/deploy/build_package_full.sh new file mode 100755 index 000000000..d7a534746 --- /dev/null +++ b/deploy/build_package_full.sh @@ -0,0 +1,230 @@ +#!/bin/bash +# ============================================================ +# build_package_full.sh — Construit le ZIP Lea COMPLET autoportant +# ------------------------------------------------------------ +# +# Produit : deploy/build/Lea_full_v.zip +# +# Ce ZIP est destine a etre servi par le dashboard Fleet +# (web_dashboard/app.py -> /api/fleet/download/). +# Contrairement a deploy/Lea_v1.0.0.zip (sources seules, suppose +# Python systeme), ce ZIP est 100% autonome : +# +# - Code source Lea A JOUR (working tree courant du repo, +# via build_package.sh : agent_v0/agent_v1, lea_ui, run_agent_v1) +# - Runtime Python 3.12 embedded complet (python-embed/) +# avec toutes les dependances pre-installees (mss, pynput, +# pystray, plyer, requests, PIL, pywin32, socketio...) +# - Lea.bat pointant directement sur python-embed\pythonw.exe +# (version embedded de configure_embed.ps1 : ni venv, ni pip, +# ni reseau, ni Python systeme) +# - python312._pth patche (import site active) +# - Lea/config.txt placeholder (CONFIGURE_ME) que le dashboard +# remplace a la volee par la config de l'agent +# - PAS de install.bat (plus aucune etape d'installation Python) +# +# Experience utilisateur cible (non-IT) : +# dezipper -> double-clic Lea.bat -> Lea demarre dans le systray. +# Aucune installation de Python, aucun UAC. +# +# Usage : +# ./deploy/build_package_full.sh # Build complet +# ./deploy/build_package_full.sh --clean # Nettoyer avant +# +# Pre-requis : +# - bash, rsync, zip +# - deploy/installer/python-3.12-embed/ (runtime embedded, ~80 Mo, +# non versionne — restaure depuis lea_python_embed_working.tgz si absent) +# ============================================================ + +set -euo pipefail + +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # deploy/ +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" # racine repo +INSTALLER_DIR="$SCRIPT_DIR/installer" +STAGING_DIR="$SCRIPT_DIR/build/installer_staging" +BUILD_DIR="$SCRIPT_DIR/build" +ASSEMBLY_DIR="$BUILD_DIR/Lea_full_assembly" # arborescence Lea/ temporaire + +# Version lue depuis la source courante. +# NB : la ligne peut etre soit AGENT_VERSION = "1.0.1" soit +# AGENT_VERSION = os.environ.get("RPA_AGENT_VERSION", "1.0.1"). +# La regex de build_package.sh/build_installer.sh ne gere QUE la 1ere forme +# (et retombe sur 1.0.0 pour la 2e). Ici on prend le DERNIER litteral entre +# guillemets de la ligne AGENT_VERSION (= la valeur par defaut effective), +# pour nommer le ZIP de maniere stable quelle que soit la forme. +VERSION=$(grep -m1 'AGENT_VERSION' "$PROJECT_ROOT/agent_v0/agent_v1/config.py" \ + | grep -oP '"[^"]+"' | tr -d '"' | tail -1) +VERSION="${VERSION:-1.0.0}" +OUTPUT_ZIP="$BUILD_DIR/Lea_full_v${VERSION}.zip" + +echo -e "${GREEN}============================================================${NC}" +echo -e "${GREEN} Build ZIP Lea COMPLET autoportant v${VERSION}${NC}" +echo -e "${GREEN}============================================================${NC}" +echo "" + +CLEAN=0 +for arg in "$@"; do + case "$arg" in + --clean) CLEAN=1 ;; + *) echo "Argument inconnu : $arg" ;; + esac +done + +# --------------------------------------------------------------- +# 1. Regenerer le staging depuis la SOURCE COURANTE du repo +# build_installer.sh --stage-only appelle build_package.sh +# (qui copie agent_v0/agent_v1, lea_ui, run_agent_v1.py courants) +# puis ajoute python-3.12-embed/ + helpers, et exclut install.bat. +# +# --clean est TOUJOURS force : sans lui, build_installer.sh reutilise +# un deploy/build/Lea/ deja present (cache du build precedent) et ne +# re-execute PAS build_package.sh -> la source embarquee serait perimee. +# On veut au contraire garantir le working tree COURANT du repo. +# --------------------------------------------------------------- +echo "[1/6] Regeneration du staging depuis la source courante (--clean force)..." +bash "$INSTALLER_DIR/build_installer.sh" --stage-only --clean +if [[ ! -d "$STAGING_DIR" ]]; then + echo -e "${RED} ERREUR : staging $STAGING_DIR absent apres build_installer.sh${NC}" + exit 1 +fi +echo " Staging pret : $STAGING_DIR" +echo "" + +# --------------------------------------------------------------- +# 2. Assembler l'arborescence Lea/ (prefixe attendu par le dashboard +# qui remplace exactement 'Lea/config.txt'). +# --------------------------------------------------------------- +echo "[2/6] Assemblage de l'arborescence Lea/..." +rm -rf "$ASSEMBLY_DIR" +mkdir -p "$ASSEMBLY_DIR/Lea" + +# Copier le staging, en renommant python-3.12-embed -> python-embed +# (chemin attendu par le Lea.bat embedded : %~dp0python-embed\pythonw.exe) +rsync -a \ + --exclude='python-3.12-embed' \ + --exclude='install.bat' \ + --exclude='config.txt' \ + "$STAGING_DIR/" \ + "$ASSEMBLY_DIR/Lea/" + +rsync -a "$STAGING_DIR/python-3.12-embed/" "$ASSEMBLY_DIR/Lea/python-embed/" +echo " Source + python-embed/ assembles" +echo "" + +# --------------------------------------------------------------- +# 3. Lea.bat embedded : extraire le bloc canonique de configure_embed.ps1 +# (le here-string $NewLeaBat). C'est la SEULE source de verite du +# Lea.bat embedded ; on ne le duplique pas dans ce script. +# --------------------------------------------------------------- +echo "[3/6] Generation de Lea.bat (runtime embedded)..." +LEA_BAT_OUT="$ASSEMBLY_DIR/Lea/Lea.bat" +python3 - "$INSTALLER_DIR/configure_embed.ps1" "$LEA_BAT_OUT" <<'PYEOF' +import sys, re +ps1_path, out_path = sys.argv[1], sys.argv[2] +text = open(ps1_path, encoding="utf-8").read() +# Extrait le here-string PowerShell : $NewLeaBat = @" ... "@ +m = re.search(r'\$NewLeaBat\s*=\s*@"\r?\n(.*?)\r?\n"@', text, re.DOTALL) +if not m: + sys.exit("ERREUR : bloc $NewLeaBat introuvable dans configure_embed.ps1") +content = m.group(1) +# CRLF pour un .bat Windows +content = content.replace("\r\n", "\n").replace("\n", "\r\n") +if not content.endswith("\r\n"): + content += "\r\n" +open(out_path, "wb").write(content.encode("ascii")) +print(f" Lea.bat genere depuis configure_embed.ps1 ({len(content)} octets)") +PYEOF +echo "" + +# --------------------------------------------------------------- +# 4. Patcher python312._pth (import site active) — idempotent. +# Necessaire pour que l'embed charge site-packages. +# --------------------------------------------------------------- +echo "[4/6] Patch python312._pth (import site)..." +PTH_FILE=$(find "$ASSEMBLY_DIR/Lea/python-embed" -name "python*._pth" | head -1) +if [[ -z "$PTH_FILE" ]]; then + echo -e "${RED} ERREUR : python*._pth introuvable dans python-embed/${NC}" + exit 1 +fi +# Decommente '#import site' s'il est commente ; sinon laisse tel quel. +sed -i 's/^#import site/import site/' "$PTH_FILE" +if ! grep -q '^import site' "$PTH_FILE"; then + printf 'import site\r\n' >> "$PTH_FILE" +fi +echo " $(basename "$PTH_FILE") : import site actif" +echo "" + +# --------------------------------------------------------------- +# 5. config.txt placeholder (CONFIGURE_ME) — cible de l'injection +# dashboard (app.py remplace 'Lea/config.txt'). +# --------------------------------------------------------------- +echo "[5/6] Pose du config.txt placeholder..." +cp "$INSTALLER_DIR/../lea_package/config.txt" "$ASSEMBLY_DIR/Lea/config.txt" +if ! grep -q 'CONFIGURE_ME' "$ASSEMBLY_DIR/Lea/config.txt"; then + echo -e "${YELLOW} AVERTISSEMENT : config.txt ne contient pas CONFIGURE_ME (placeholder inattendu)${NC}" +fi +echo " Lea/config.txt (placeholder) pose" +echo "" + +# --------------------------------------------------------------- +# 6. Validation de completude AVANT zip (un ZIP incomplet = install +# cassee chez le client non-IT). +# --------------------------------------------------------------- +echo "[6/6] Validation + creation du ZIP..." +REQUIRED=( + "Lea/run_agent_v1.py" + "Lea/agent_v1/config.py" + "Lea/agent_v1/main.py" + "Lea/lea_ui/server_client.py" + "Lea/Lea.bat" + "Lea/config.txt" + "Lea/python-embed/python.exe" + "Lea/python-embed/pythonw.exe" + "Lea/python-embed/Lib/site-packages/mss" + "Lea/python-embed/Lib/site-packages/win32" + "Lea/python-embed/Lib/site-packages/socketio" +) +MISSING=() +for f in "${REQUIRED[@]}"; do + [[ -e "$ASSEMBLY_DIR/$f" ]] || MISSING+=("$f") +done +# install.bat NE DOIT PAS etre present +if [[ -e "$ASSEMBLY_DIR/Lea/install.bat" ]]; then + echo -e "${RED} ERREUR : install.bat present dans l'assemblage (doit etre absent).${NC}" + exit 1 +fi +if [[ ${#MISSING[@]} -gt 0 ]]; then + echo -e "${RED} ERREUR : assemblage incomplet. Manquants :${NC}" + printf ' - %s\n' "${MISSING[@]}" + exit 1 +fi +echo " Completude verifiee (${#REQUIRED[@]} elements, install.bat absent)" + +# Verif source A JOUR : le config.py embarque doit etre identique au repo +if ! diff -q "$PROJECT_ROOT/agent_v0/agent_v1/config.py" "$ASSEMBLY_DIR/Lea/agent_v1/config.py" >/dev/null; then + echo -e "${RED} ERREUR : agent_v1/config.py embarque DIFFERE de la source repo !${NC}" + echo " Le ZIP n'embarque pas la source a jour — build interrompu." + exit 1 +fi +echo " Source a jour confirmee (agent_v1/config.py == repo)" + +rm -f "$OUTPUT_ZIP" +( cd "$ASSEMBLY_DIR" && zip -q -r -X "$OUTPUT_ZIP" Lea ) +ZIP_SIZE=$(du -h "$OUTPUT_ZIP" | cut -f1) +echo "" +echo -e "${GREEN}============================================================${NC}" +echo -e "${GREEN} ZIP complet produit !${NC}" +echo -e "${GREEN}============================================================${NC}" +echo "" +echo " Fichier : $OUTPUT_ZIP" +echo " Taille : $ZIP_SIZE" +echo "" +echo " Servi par le dashboard via web_dashboard/app.py (_LEA_ZIP_TEMPLATE)." +echo " L'utilisateur : dezippe -> double-clic Lea.bat (aucun Python systeme requis)." +echo ""