#!/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 # Installateur 1-clic non-IT (raccourci Bureau + Demarrage automatique, # per-user, sans admin). Asset statique CRLF/ASCII copie tel quel dans Lea/. INSTALLER_BAT_SRC="$INSTALLER_DIR/Installer-Lea.bat" if [[ ! -f "$INSTALLER_BAT_SRC" ]]; then echo -e "${RED} ERREUR : $INSTALLER_BAT_SRC introuvable${NC}" exit 1 fi cp "$INSTALLER_BAT_SRC" "$ASSEMBLY_DIR/Lea/Installer-Lea.bat" echo " Installer-Lea.bat (installation 1-clic) ajoute" # Notice utilisateur dediee a l'install autonome (remplace la LISEZMOI legacy # du staging, qui decrit l'ancien flux install.bat + Python systeme). LISEZMOI_SRC="$INSTALLER_DIR/LISEZMOI-autonome.txt" if [[ -f "$LISEZMOI_SRC" ]]; then cp "$LISEZMOI_SRC" "$ASSEMBLY_DIR/Lea/LISEZMOI.txt" echo " LISEZMOI.txt (version install autonome) pose" fi 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/Installer-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" "Lea/python-embed/Lib/site-packages/httpx" "Lea/python-embed/Lib/site-packages/httpcore" "Lea/python-embed/Lib/site-packages/h11" "Lea/python-embed/Lib/site-packages/anyio" "Lea/python-embed/Lib/site-packages/typing_extensions.py" 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 ""