feat(deploy): installeur Inno Setup pour déploiement professionnel
- Lea.iss : script Inno Setup 6 (enrollment 2 pages, licence, machine_id) - build_installer.sh : staging + ISCC (compatible Wine sur Linux) - uninstall_lea.ps1 : kill PID + cleanup + notif serveur - configure_embed.ps1 : Python 3.12 embedded optionnel - config_template.txt : modèle pour installation silencieuse - LICENSE.txt : CGU AI Act Art. 50 - README.md : doc build, signing, déploiement silencieux Paramètres d'installation silencieuse : Lea-Setup-v1.0.0.exe /VERYSILENT /CONFIG=enroll.txt /LOG=install.log Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
61
deploy/installer/LICENSE.txt
Normal file
61
deploy/installer/LICENSE.txt
Normal file
@@ -0,0 +1,61 @@
|
||||
============================================================
|
||||
Lea - Conditions Generales d'Utilisation
|
||||
============================================================
|
||||
|
||||
Version 1.0 — Avril 2026
|
||||
Editeur : AIVANOV
|
||||
|
||||
1. OBJET
|
||||
--------
|
||||
Lea est un logiciel d'assistance intelligente destine a automatiser
|
||||
des taches repetitives sur poste de travail Windows, pour le compte
|
||||
de son employeur (AIVANOV et ses clients autorises).
|
||||
|
||||
2. NATURE DES DONNEES COLLECTEES
|
||||
--------------------------------
|
||||
Lors de son utilisation, Lea capture :
|
||||
- Des captures d'ecran du poste de travail
|
||||
- Les evenements clavier et souris
|
||||
- Les metadonnees systeme (nom de la machine, processus actifs)
|
||||
|
||||
Les donnees sensibles (mots de passe, numeros de securite sociale,
|
||||
informations de cartes bancaires) sont automatiquement floutees
|
||||
avant transmission au serveur, sauf desactivation explicite par
|
||||
l'administrateur (parametre RPA_BLUR_SENSITIVE=false).
|
||||
|
||||
3. TRANSMISSION ET STOCKAGE
|
||||
---------------------------
|
||||
Les donnees sont transmises via HTTPS chiffre a un serveur central
|
||||
gere par AIVANOV. Elles sont conservees 180 jours minimum pour des
|
||||
raisons de conformite, puis purgees automatiquement.
|
||||
|
||||
4. SYSTEME D'IA (AI ACT - ARTICLE 50)
|
||||
-------------------------------------
|
||||
Lea utilise des modeles d'intelligence artificielle pour comprendre
|
||||
et automatiser les taches. Conformement a l'Article 50 du Reglement
|
||||
europeen sur l'Intelligence Artificielle, l'utilisateur est informe
|
||||
qu'il interagit avec un systeme d'IA.
|
||||
|
||||
5. CONTROLE PAR L'UTILISATEUR
|
||||
-----------------------------
|
||||
L'utilisateur peut a tout moment :
|
||||
- Arreter l'enregistrement (clic droit sur icone > C'est termine)
|
||||
- Declencher un arret d'urgence (clic droit > ARRET D'URGENCE)
|
||||
- Quitter Lea completement (clic droit > Quitter Lea)
|
||||
- Desinstaller Lea via le panneau de configuration Windows
|
||||
|
||||
6. RESPONSABILITE
|
||||
-----------------
|
||||
L'utilisateur s'engage a ne pas utiliser Lea sur des donnees qu'il
|
||||
n'est pas autorise a traiter dans le cadre de ses fonctions.
|
||||
AIVANOV ne pourra etre tenu responsable d'un usage non conforme.
|
||||
|
||||
7. CONTACT
|
||||
----------
|
||||
Pour toute question ou demande d'acces/rectification/suppression
|
||||
de donnees : dpo@aivanov.com
|
||||
|
||||
============================================================
|
||||
En cliquant sur "J'accepte", vous confirmez avoir pris connaissance
|
||||
de ces conditions et les accepter.
|
||||
============================================================
|
||||
554
deploy/installer/Lea.iss
Normal file
554
deploy/installer/Lea.iss
Normal file
@@ -0,0 +1,554 @@
|
||||
; ============================================================
|
||||
; Lea.iss — Script Inno Setup pour l'installeur Lea
|
||||
; ------------------------------------------------------------
|
||||
; Compile avec Inno Setup 6.2+ (ISCC.exe Lea.iss)
|
||||
;
|
||||
; Ce script produit Lea-Setup-v{VERSION}.exe dans ..\releases\
|
||||
;
|
||||
; Fonctions principales :
|
||||
; - Page de bienvenue + licence (CGU)
|
||||
; - Page custom d'enrollment (nom, email, ID AIVANOV, URL, token)
|
||||
; - Generation d'un machine_id unique par poste
|
||||
; - Generation automatique de config.txt
|
||||
; - Installation silencieuse de Python 3.12 embedded (optionnelle)
|
||||
; - Raccourci demarrage automatique (checkbox)
|
||||
; - Installation silencieuse : /VERYSILENT /CONFIG=path\to\config.txt
|
||||
; - Desinstallation propre (kill process, cleanup, export logs)
|
||||
;
|
||||
; Pre-requis staging :
|
||||
; Le dossier ..\build\installer_staging\ doit contenir :
|
||||
; - Le package Lea complet (agent_v1/, lea_ui/, run_agent_v1.py, Lea.bat, ...)
|
||||
; - Optionnel : python-3.12-embed\ (runtime Python embedded pre-configure)
|
||||
; build_installer.sh s'occupe de preparer ce staging.
|
||||
; ============================================================
|
||||
|
||||
#define MyAppName "Lea"
|
||||
#define MyAppVersion "1.0.0"
|
||||
#define MyAppPublisher "AIVANOV"
|
||||
#define MyAppURL "https://lea.labs.laurinebazin.design"
|
||||
#define MyAppExeName "Lea.bat"
|
||||
#define MyAppDescription "Lea - Assistante IA pour l'automatisation"
|
||||
|
||||
; Chemin du staging (peut etre surcharge via ISCC /DSourceDir=...)
|
||||
#ifndef SourceDir
|
||||
#define SourceDir "..\build\installer_staging"
|
||||
#endif
|
||||
|
||||
; Chemin de sortie des installeurs
|
||||
#ifndef OutputDir
|
||||
#define OutputDir "..\releases"
|
||||
#endif
|
||||
|
||||
; Activer le bundle Python embedded si present dans le staging
|
||||
#define PythonEmbedDir "python-3.12-embed"
|
||||
|
||||
[Setup]
|
||||
AppId={{B3F9A1E2-5C4D-4E7F-9A1B-2C3D4E5F6789}
|
||||
AppName={#MyAppName}
|
||||
AppVersion={#MyAppVersion}
|
||||
AppVerName={#MyAppName} {#MyAppVersion}
|
||||
AppPublisher={#MyAppPublisher}
|
||||
AppPublisherURL={#MyAppURL}
|
||||
AppSupportURL={#MyAppURL}
|
||||
AppUpdatesURL={#MyAppURL}
|
||||
DefaultDirName={autopf}\{#MyAppName}
|
||||
DefaultGroupName={#MyAppName}
|
||||
DisableProgramGroupPage=yes
|
||||
OutputDir={#OutputDir}
|
||||
OutputBaseFilename=Lea-Setup-v{#MyAppVersion}
|
||||
; Compression correcte (pas trop aggressive pour que l'install reste rapide)
|
||||
Compression=lzma2
|
||||
SolidCompression=yes
|
||||
; Support HiDPI
|
||||
WizardStyle=modern
|
||||
; Langue FR par defaut
|
||||
ShowLanguageDialog=no
|
||||
; Autorise l'install en mode user si pas admin (bascule sur LOCALAPPDATA)
|
||||
PrivilegesRequired=lowest
|
||||
PrivilegesRequiredOverridesAllowed=dialog
|
||||
; Icone de l'installeur (decommenter si disponible)
|
||||
; SetupIconFile=lea.ico
|
||||
; Uninstall
|
||||
UninstallDisplayName={#MyAppName} {#MyAppVersion}
|
||||
; UninstallDisplayIcon={app}\lea.ico ; decommenter quand l'icone sera fournie
|
||||
; Architecture : 64-bit uniquement (Windows 10+ / 11)
|
||||
ArchitecturesAllowed=x64compatible
|
||||
ArchitecturesInstallIn64BitMode=x64compatible
|
||||
; Version minimale Windows : 10
|
||||
MinVersion=10.0
|
||||
; Informations legales
|
||||
VersionInfoVersion={#MyAppVersion}
|
||||
VersionInfoCompany={#MyAppPublisher}
|
||||
VersionInfoDescription={#MyAppDescription}
|
||||
VersionInfoCopyright=Copyright (C) 2026 {#MyAppPublisher}
|
||||
; Licence CGU affichee avant le choix du repertoire
|
||||
LicenseFile=LICENSE.txt
|
||||
|
||||
[Languages]
|
||||
Name: "french"; MessagesFile: "compiler:Languages\French.isl"
|
||||
|
||||
[Files]
|
||||
; Package complet (code Python + .bat + requirements)
|
||||
; Note : install.bat EST copie (execute par [Run] pour creer le venv Python)
|
||||
; Note : config.txt n'est PAS copie depuis le staging (il est genere par [Code])
|
||||
Source: "{#SourceDir}\*"; \
|
||||
DestDir: "{app}"; \
|
||||
Flags: ignoreversion recursesubdirs createallsubdirs; \
|
||||
Excludes: "{#PythonEmbedDir}\*,config.txt,*.log,sessions\*,__pycache__\*"
|
||||
|
||||
; Python 3.12 embedded (optionnel, copie conditionnelle via check)
|
||||
Source: "{#SourceDir}\{#PythonEmbedDir}\*"; \
|
||||
DestDir: "{app}\python-embed"; \
|
||||
Flags: ignoreversion recursesubdirs createallsubdirs skipifsourcedoesntexist; \
|
||||
Components: pythonembed
|
||||
|
||||
; Script de desinstallation custom (kill + export logs)
|
||||
Source: "uninstall_lea.ps1"; DestDir: "{app}"; Flags: ignoreversion
|
||||
|
||||
; Script de configuration du runtime Python embedded (optionnel)
|
||||
Source: "configure_embed.ps1"; DestDir: "{app}"; Flags: ignoreversion; Components: pythonembed
|
||||
|
||||
; Licence CGU (affichee dans la page licence ET conservee dans {app})
|
||||
Source: "LICENSE.txt"; DestDir: "{app}"; Flags: ignoreversion isreadme
|
||||
|
||||
; Template de config pour installation silencieuse (reference)
|
||||
Source: "config_template.txt"; DestDir: "{app}"; Flags: ignoreversion
|
||||
|
||||
[Components]
|
||||
Name: "core"; Description: "Lea (obligatoire)"; Types: full compact custom; Flags: fixed
|
||||
Name: "pythonembed"; Description: "Python 3.12 embedded (recommande si Python non installe sur le poste)"; Types: full
|
||||
Name: "autostart"; Description: "Demarrer Lea automatiquement au demarrage de Windows"; Types: full
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "Creer un raccourci sur le bureau"; GroupDescription: "Raccourcis :"; Flags: unchecked
|
||||
Name: "startmenuicon"; Description: "Creer un raccourci dans le menu Demarrer"; GroupDescription: "Raccourcis :"
|
||||
|
||||
[Icons]
|
||||
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; WorkingDir: "{app}"; Tasks: startmenuicon
|
||||
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; WorkingDir: "{app}"; Tasks: desktopicon
|
||||
; Raccourci autostart (shell:startup) — cree si composant autostart selectionne
|
||||
Name: "{userstartup}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; \
|
||||
WorkingDir: "{app}"; Components: autostart
|
||||
|
||||
[Run]
|
||||
; Apres copie : executer install.bat pour creer le venv et installer les dependances Python
|
||||
; Skip si bundle embedded (dans ce cas, on utilise python-embed directement)
|
||||
Filename: "{app}\install.bat"; \
|
||||
WorkingDir: "{app}"; \
|
||||
StatusMsg: "Installation des composants Python (1-2 minutes)..."; \
|
||||
Flags: runhidden waituntilterminated; \
|
||||
Components: not pythonembed
|
||||
|
||||
; Configuration Python embedded : creer un Lea.bat qui pointe sur python-embed
|
||||
Filename: "{cmd}"; \
|
||||
Parameters: "/c copy /y ""{app}\Lea.bat"" ""{app}\Lea.bat.bak"" && powershell -NoProfile -ExecutionPolicy Bypass -File ""{app}\configure_embed.ps1"""; \
|
||||
WorkingDir: "{app}"; \
|
||||
StatusMsg: "Configuration du runtime Python embedded..."; \
|
||||
Flags: runhidden waituntilterminated skipifsilent; \
|
||||
Components: pythonembed
|
||||
|
||||
; Lancer Lea a la fin de l'installation (optionnel)
|
||||
Filename: "{app}\{#MyAppExeName}"; \
|
||||
Description: "Lancer {#MyAppName} maintenant"; \
|
||||
Flags: postinstall skipifsilent nowait shellexec
|
||||
|
||||
[UninstallRun]
|
||||
; Tuer le process via PID du lock avant suppression des fichiers
|
||||
Filename: "powershell.exe"; \
|
||||
Parameters: "-NoProfile -ExecutionPolicy Bypass -File ""{app}\uninstall_lea.ps1"" -AppDir ""{app}"""; \
|
||||
RunOnceId: "KillLeaProcess"; \
|
||||
Flags: runhidden waituntilterminated
|
||||
|
||||
[UninstallDelete]
|
||||
Type: filesandordirs; Name: "{app}\.venv"
|
||||
Type: filesandordirs; Name: "{app}\__pycache__"
|
||||
Type: filesandordirs; Name: "{app}\agent_v1\__pycache__"
|
||||
Type: filesandordirs; Name: "{app}\agent_v1\sessions"
|
||||
Type: filesandordirs; Name: "{app}\agent_v1\logs"
|
||||
Type: files; Name: "{app}\lea_agent.lock"
|
||||
Type: files; Name: "{app}\config.txt"
|
||||
Type: files; Name: "{app}\machine_id.txt"
|
||||
|
||||
; ============================================================
|
||||
; Code Pascal : pages custom + generation config.txt + helpers
|
||||
; ============================================================
|
||||
[Code]
|
||||
const
|
||||
SERVER_URL_DEFAULT = 'https://lea.labs.laurinebazin.design/api/v1';
|
||||
SERVER_HOST_DEFAULT = 'lea.labs.laurinebazin.design';
|
||||
DEFAULT_TOKEN = '86031addb338e449fccdb1a983f61807aec15d42d482b9c7748ad607dc23caab';
|
||||
|
||||
var
|
||||
EnrollmentPage: TInputQueryWizardPage;
|
||||
TokenPage: TInputQueryWizardPage;
|
||||
MachineIdValue: string;
|
||||
ConfigFilePath: string;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// Helper : ajoute des guillemets autour d'une chaine
|
||||
// --------------------------------------------------------------------
|
||||
function AddQuotes(const S: string): string;
|
||||
begin
|
||||
Result := '"' + S + '"';
|
||||
end;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// Wrapper CreateGUIDString (via PowerShell, fallback par defaut)
|
||||
// --------------------------------------------------------------------
|
||||
function CreateGUIDString(var Guid: string): Boolean;
|
||||
var
|
||||
ResultCode: Integer;
|
||||
TmpFile: string;
|
||||
Lines: TArrayOfString;
|
||||
begin
|
||||
Result := False;
|
||||
TmpFile := ExpandConstant('{tmp}\guid.txt');
|
||||
// powershell : genere un GUID
|
||||
if Exec('powershell.exe',
|
||||
'-NoProfile -Command "[guid]::NewGuid().ToString() | Out-File -Encoding ASCII ' + AddQuotes(TmpFile) + '"',
|
||||
'', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
|
||||
begin
|
||||
if LoadStringsFromFile(TmpFile, Lines) and (GetArrayLength(Lines) > 0) then
|
||||
begin
|
||||
Guid := Trim(Lines[0]);
|
||||
Result := Length(Guid) > 0;
|
||||
end;
|
||||
DeleteFile(TmpFile);
|
||||
end;
|
||||
end;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// Recupere le hostname de la machine
|
||||
// --------------------------------------------------------------------
|
||||
function GetComputerNameString(): string;
|
||||
var
|
||||
Buffer: string;
|
||||
begin
|
||||
Buffer := ExpandConstant('{computername}');
|
||||
if Length(Buffer) = 0 then
|
||||
Buffer := 'unknown-host';
|
||||
Result := Buffer;
|
||||
end;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// Genere un identifiant machine unique : UUID4 + hostname hashe
|
||||
// --------------------------------------------------------------------
|
||||
function GenerateMachineId(): string;
|
||||
var
|
||||
Guid: string;
|
||||
Hostname: string;
|
||||
I: Integer;
|
||||
Hash: Cardinal;
|
||||
begin
|
||||
// Essaye d'utiliser le GUID genere par Windows (via PowerShell)
|
||||
Guid := '';
|
||||
if CreateGUIDString(Guid) then
|
||||
Result := LowerCase(StringChange(StringChange(StringChange(Guid, '{', ''), '}', ''), '-', ''))
|
||||
else
|
||||
Result := IntToStr(GetTickCount);
|
||||
|
||||
// Ajoute un hash du hostname pour stabilite
|
||||
Hostname := GetComputerNameString();
|
||||
Hash := 0;
|
||||
for I := 1 to Length(Hostname) do
|
||||
Hash := (Hash * 31 + Ord(Hostname[I])) and $FFFFFFFF;
|
||||
|
||||
Result := Copy(Result, 1, 16) + '-' + Format('%08x', [Hash]);
|
||||
end;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// Charge une configuration depuis /CONFIG=path (installation silencieuse)
|
||||
// Format du fichier : NOM=valeur, une ligne par parametre
|
||||
// Cles attendues : USER_NAME, USER_EMAIL, USER_ID, SERVER_URL, API_TOKEN
|
||||
// --------------------------------------------------------------------
|
||||
procedure LoadConfigFromCommandLine(); forward;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// Initialisation : cree les pages custom d'enrollment
|
||||
// --------------------------------------------------------------------
|
||||
procedure InitializeWizard();
|
||||
begin
|
||||
// Page 1 : informations collaborateur
|
||||
EnrollmentPage := CreateInputQueryPage(wpSelectTasks,
|
||||
'Identification du collaborateur',
|
||||
'Veuillez renseigner vos informations pour l''enrollment',
|
||||
'Ces informations sont envoyees au serveur Lea pour identifier votre poste. ' +
|
||||
'Elles sont stockees de maniere securisee et ne sont jamais partagees avec des tiers.');
|
||||
|
||||
EnrollmentPage.Add('Nom et prenom :', False);
|
||||
EnrollmentPage.Add('Email professionnel :', False);
|
||||
EnrollmentPage.Add('ID interne AIVANOV (optionnel) :', False);
|
||||
|
||||
EnrollmentPage.Values[0] := '';
|
||||
EnrollmentPage.Values[1] := '';
|
||||
EnrollmentPage.Values[2] := '';
|
||||
|
||||
// Page 2 : configuration serveur (URL + token)
|
||||
TokenPage := CreateInputQueryPage(EnrollmentPage.ID,
|
||||
'Connexion au serveur Lea',
|
||||
'Configuration de la connexion au serveur central',
|
||||
'L''URL du serveur est pre-remplie par defaut. Le token d''authentification ' +
|
||||
'vous est fourni par votre administrateur AIVANOV. Laissez la valeur par defaut ' +
|
||||
'si vous ne savez pas quoi mettre.');
|
||||
|
||||
TokenPage.Add('URL du serveur (avec /api/v1) :', False);
|
||||
TokenPage.Add('Token d''authentification :', False);
|
||||
|
||||
TokenPage.Values[0] := SERVER_URL_DEFAULT;
|
||||
TokenPage.Values[1] := DEFAULT_TOKEN;
|
||||
|
||||
// Si un fichier /CONFIG= est passe en ligne de commande, pre-remplir
|
||||
LoadConfigFromCommandLine();
|
||||
end;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// Implementation de LoadConfigFromCommandLine (declare en forward ci-dessus)
|
||||
// --------------------------------------------------------------------
|
||||
procedure LoadConfigFromCommandLine();
|
||||
var
|
||||
ConfigParam: string;
|
||||
Lines: TArrayOfString;
|
||||
I: Integer;
|
||||
Line, Key, Value: string;
|
||||
EqPos: Integer;
|
||||
begin
|
||||
ConfigParam := ExpandConstant('{param:CONFIG}');
|
||||
if Length(ConfigParam) = 0 then Exit;
|
||||
if not FileExists(ConfigParam) then Exit;
|
||||
|
||||
if not LoadStringsFromFile(ConfigParam, Lines) then Exit;
|
||||
|
||||
for I := 0 to GetArrayLength(Lines) - 1 do
|
||||
begin
|
||||
Line := Trim(Lines[I]);
|
||||
if (Length(Line) = 0) or (Line[1] = '#') then Continue;
|
||||
|
||||
EqPos := Pos('=', Line);
|
||||
if EqPos = 0 then Continue;
|
||||
|
||||
Key := Trim(Copy(Line, 1, EqPos - 1));
|
||||
Value := Trim(Copy(Line, EqPos + 1, Length(Line)));
|
||||
|
||||
if Key = 'USER_NAME' then EnrollmentPage.Values[0] := Value
|
||||
else if Key = 'USER_EMAIL' then EnrollmentPage.Values[1] := Value
|
||||
else if Key = 'USER_ID' then EnrollmentPage.Values[2] := Value
|
||||
else if Key = 'SERVER_URL' then TokenPage.Values[0] := Value
|
||||
else if Key = 'API_TOKEN' then TokenPage.Values[1] := Value;
|
||||
end;
|
||||
end;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// Validation des pages custom (Nom/Email obligatoires, token non vide)
|
||||
// --------------------------------------------------------------------
|
||||
function NextButtonClick(CurPageID: Integer): Boolean;
|
||||
var
|
||||
Email: string;
|
||||
begin
|
||||
Result := True;
|
||||
|
||||
if CurPageID = EnrollmentPage.ID then
|
||||
begin
|
||||
if Length(Trim(EnrollmentPage.Values[0])) = 0 then
|
||||
begin
|
||||
MsgBox('Le nom est obligatoire.', mbError, MB_OK);
|
||||
Result := False;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
Email := Trim(EnrollmentPage.Values[1]);
|
||||
if (Length(Email) = 0) or (Pos('@', Email) = 0) then
|
||||
begin
|
||||
MsgBox('Un email valide est obligatoire.', mbError, MB_OK);
|
||||
Result := False;
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
|
||||
if CurPageID = TokenPage.ID then
|
||||
begin
|
||||
if Length(Trim(TokenPage.Values[0])) = 0 then
|
||||
begin
|
||||
MsgBox('L''URL du serveur est obligatoire.', mbError, MB_OK);
|
||||
Result := False;
|
||||
Exit;
|
||||
end;
|
||||
if Length(Trim(TokenPage.Values[1])) < 16 then
|
||||
begin
|
||||
if MsgBox('Le token parait court (< 16 caracteres). Continuer quand meme ?',
|
||||
mbConfirmation, MB_YESNO) = IDNO then
|
||||
begin
|
||||
Result := False;
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// Ecrit config.txt genere dans le dossier d'installation
|
||||
// --------------------------------------------------------------------
|
||||
procedure WriteGeneratedConfig();
|
||||
var
|
||||
Config: string;
|
||||
ServerUrl, ServerHost, Token: string;
|
||||
UserName, UserEmail, UserId: string;
|
||||
SlashPos: Integer;
|
||||
begin
|
||||
ConfigFilePath := ExpandConstant('{app}\config.txt');
|
||||
|
||||
ServerUrl := Trim(TokenPage.Values[0]);
|
||||
Token := Trim(TokenPage.Values[1]);
|
||||
UserName := Trim(EnrollmentPage.Values[0]);
|
||||
UserEmail := Trim(EnrollmentPage.Values[1]);
|
||||
UserId := Trim(EnrollmentPage.Values[2]);
|
||||
|
||||
// Derive ServerHost depuis ServerUrl : https://host/api/v1 -> host
|
||||
ServerHost := ServerUrl;
|
||||
ServerHost := StringChange(ServerHost, 'https://', '');
|
||||
ServerHost := StringChange(ServerHost, 'http://', '');
|
||||
SlashPos := Pos('/', ServerHost);
|
||||
if SlashPos > 0 then
|
||||
ServerHost := Copy(ServerHost, 1, SlashPos - 1);
|
||||
|
||||
Config :=
|
||||
'# ============================================================' + #13#10 +
|
||||
'# Configuration Lea (genere par l''installeur)' + #13#10 +
|
||||
'# ============================================================' + #13#10 +
|
||||
'# Genere le ' + GetDateTimeString('yyyy-mm-dd hh:nn:ss', '-', ':') + #13#10 +
|
||||
'# Installe par : ' + UserName + ' <' + UserEmail + '>' + #13#10 +
|
||||
'# ID interne : ' + UserId + #13#10 +
|
||||
'# Machine ID : ' + MachineIdValue + #13#10 +
|
||||
'# ============================================================' + #13#10 +
|
||||
'' + #13#10 +
|
||||
'# Adresse du serveur Lea (URL complete avec /api/v1)' + #13#10 +
|
||||
'RPA_SERVER_URL=' + ServerUrl + #13#10 +
|
||||
'' + #13#10 +
|
||||
'# Cle d''authentification (fournie par l''administrateur)' + #13#10 +
|
||||
'RPA_API_TOKEN=' + Token + #13#10 +
|
||||
'' + #13#10 +
|
||||
'# Nom du serveur (sans https://, sans /api/v1)' + #13#10 +
|
||||
'RPA_SERVER_HOST=' + ServerHost + #13#10 +
|
||||
'' + #13#10 +
|
||||
'# Identifiant unique de cette machine (genere a l''install)' + #13#10 +
|
||||
'RPA_MACHINE_ID=' + MachineIdValue + #13#10 +
|
||||
'' + #13#10 +
|
||||
'# Informations collaborateur (utilisees pour l''audit cote serveur)' + #13#10 +
|
||||
'RPA_USER_NAME=' + UserName + #13#10 +
|
||||
'RPA_USER_EMAIL=' + UserEmail + #13#10;
|
||||
|
||||
if Length(UserId) > 0 then
|
||||
Config := Config + 'RPA_USER_ID=' + UserId + #13#10;
|
||||
|
||||
Config := Config + '' + #13#10 +
|
||||
'# ============================================================' + #13#10 +
|
||||
'# Parametres avances (ne pas modifier sauf indication)' + #13#10 +
|
||||
'# ============================================================' + #13#10 +
|
||||
'' + #13#10 +
|
||||
'# Flouter les zones de texte dans les captures (securite donnees)' + #13#10 +
|
||||
'RPA_BLUR_SENSITIVE=true' + #13#10 +
|
||||
'' + #13#10 +
|
||||
'# Duree de conservation des logs en jours (minimum 180 pour conformite)' + #13#10 +
|
||||
'RPA_LOG_RETENTION_DAYS=180' + #13#10;
|
||||
|
||||
if not SaveStringToFile(ConfigFilePath, Config, False) then
|
||||
MsgBox('Echec de l''ecriture de config.txt dans ' + ConfigFilePath, mbError, MB_OK);
|
||||
end;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// Ecrit le machine_id.txt (identifiant du poste)
|
||||
// --------------------------------------------------------------------
|
||||
procedure WriteMachineId();
|
||||
var
|
||||
MachineIdFile: string;
|
||||
begin
|
||||
MachineIdFile := ExpandConstant('{app}\machine_id.txt');
|
||||
if not SaveStringToFile(MachineIdFile, MachineIdValue, False) then
|
||||
MsgBox('Echec de l''ecriture de machine_id.txt', mbError, MB_OK);
|
||||
end;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// Notifie le serveur de l'enrollment (best-effort, non bloquant)
|
||||
// POST vers {SERVER_URL}/agents/enroll avec les infos collaborateur
|
||||
// --------------------------------------------------------------------
|
||||
procedure NotifyServerEnrollment();
|
||||
var
|
||||
ResultCode: Integer;
|
||||
PsScript: string;
|
||||
PsFile: string;
|
||||
ServerUrl, Token: string;
|
||||
begin
|
||||
ServerUrl := Trim(TokenPage.Values[0]);
|
||||
Token := Trim(TokenPage.Values[1]);
|
||||
|
||||
PsFile := ExpandConstant('{tmp}\enroll.ps1');
|
||||
PsScript :=
|
||||
'$ErrorActionPreference = ''SilentlyContinue''' + #13#10 +
|
||||
'$body = @{' + #13#10 +
|
||||
' machine_id = ''' + MachineIdValue + '''' + #13#10 +
|
||||
' hostname = $env:COMPUTERNAME' + #13#10 +
|
||||
' user_name = ''' + EnrollmentPage.Values[0] + '''' + #13#10 +
|
||||
' user_email = ''' + EnrollmentPage.Values[1] + '''' + #13#10 +
|
||||
' user_id = ''' + EnrollmentPage.Values[2] + '''' + #13#10 +
|
||||
' agent_version = ''' + '{#MyAppVersion}' + '''' + #13#10 +
|
||||
'} | ConvertTo-Json' + #13#10 +
|
||||
'try {' + #13#10 +
|
||||
' Invoke-RestMethod -Uri ''' + ServerUrl + '/agents/enroll'' ' +
|
||||
'-Method POST -Body $body -ContentType ''application/json'' ' +
|
||||
'-Headers @{ Authorization = ''Bearer ' + Token + ''' } -TimeoutSec 10 | Out-Null' + #13#10 +
|
||||
'} catch { exit 0 }' + #13#10;
|
||||
|
||||
SaveStringToFile(PsFile, PsScript, False);
|
||||
Exec('powershell.exe',
|
||||
'-NoProfile -ExecutionPolicy Bypass -File ' + AddQuotes(PsFile),
|
||||
'', SW_HIDE, ewWaitUntilTerminated, ResultCode);
|
||||
DeleteFile(PsFile);
|
||||
end;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// Hook : actions apres copie des fichiers (ssPostInstall)
|
||||
// --------------------------------------------------------------------
|
||||
procedure CurStepChanged(CurStep: TSetupStep);
|
||||
begin
|
||||
if CurStep = ssInstall then
|
||||
begin
|
||||
// Genere le machine_id AVANT la copie des fichiers
|
||||
MachineIdValue := GenerateMachineId();
|
||||
end;
|
||||
|
||||
if CurStep = ssPostInstall then
|
||||
begin
|
||||
// Ecrit config.txt et machine_id.txt
|
||||
WriteGeneratedConfig();
|
||||
WriteMachineId();
|
||||
// Notifie le serveur (best-effort)
|
||||
NotifyServerEnrollment();
|
||||
end;
|
||||
end;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// Desinstallation : proposer d'exporter les logs avant suppression
|
||||
// --------------------------------------------------------------------
|
||||
function InitializeUninstall(): Boolean;
|
||||
var
|
||||
LogDir, ExportDir: string;
|
||||
ResultCode: Integer;
|
||||
begin
|
||||
Result := True;
|
||||
LogDir := ExpandConstant('{app}\agent_v1\logs');
|
||||
|
||||
if DirExists(LogDir) then
|
||||
begin
|
||||
if MsgBox('Voulez-vous exporter les logs de Lea avant la desinstallation ?' + #13#10 +
|
||||
'(les logs seront copies dans votre dossier Documents)',
|
||||
mbConfirmation, MB_YESNO) = IDYES then
|
||||
begin
|
||||
ExportDir := ExpandConstant('{userdocs}\Lea_logs_export');
|
||||
ForceDirectories(ExportDir);
|
||||
Exec('powershell.exe',
|
||||
'-NoProfile -Command "Copy-Item -Path ' + AddQuotes(LogDir + '\*') +
|
||||
' -Destination ' + AddQuotes(ExportDir) + ' -Recurse -Force"',
|
||||
'', SW_HIDE, ewWaitUntilTerminated, ResultCode);
|
||||
MsgBox('Logs exportes dans : ' + ExportDir, mbInformation, MB_OK);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
227
deploy/installer/README.md
Normal file
227
deploy/installer/README.md
Normal file
@@ -0,0 +1,227 @@
|
||||
# Installeur Lea (Inno Setup)
|
||||
|
||||
Installeur Windows professionnel pour Lea, remplacant le ZIP + `install.bat` artisanal.
|
||||
|
||||
## Resume
|
||||
|
||||
Produit `Lea-Setup-v1.0.0.exe` dans `deploy/releases/`.
|
||||
|
||||
Caracteristiques :
|
||||
- Interface francaise, moderne (style wizard Windows 10/11)
|
||||
- Page custom d'enrollment (nom, email, ID interne, URL serveur, token)
|
||||
- Generation automatique de `machine_id` unique (GUID + hash hostname)
|
||||
- `config.txt` genere a partir des donnees saisies
|
||||
- Option bundle Python 3.12 embedded (postes sans droits admin)
|
||||
- Raccourci demarrage automatique (`shell:startup`) optionnel
|
||||
- Notification serveur a l'install / desinstall (best-effort)
|
||||
- Installation silencieuse : `/VERYSILENT /CONFIG=enroll.txt`
|
||||
- Desinstallation propre : kill process, cleanup, export logs
|
||||
|
||||
## Pre-requis pour compiler
|
||||
|
||||
### Inno Setup 6.2+
|
||||
|
||||
Telecharger depuis [jrsoftware.org](https://jrsoftware.org/isinfo.php) et installer
|
||||
`innosetup-6.x.exe`. Le compilateur `ISCC.exe` doit etre accessible.
|
||||
|
||||
### Alternative Linux : Wine
|
||||
|
||||
```bash
|
||||
# Installation
|
||||
winetricks innosetup
|
||||
# Ou : telecharger innosetup-6.x.exe et lancer : wine innosetup-6.x.exe
|
||||
|
||||
# Verifier
|
||||
ls "$HOME/.wine/drive_c/Program Files (x86)/Inno Setup 6/ISCC.exe"
|
||||
```
|
||||
|
||||
Le script `build_installer.sh` detecte automatiquement Wine si present.
|
||||
|
||||
## Build local
|
||||
|
||||
### Build complet (staging + compilation)
|
||||
|
||||
```bash
|
||||
cd rpa_vision_v3
|
||||
./deploy/installer/build_installer.sh
|
||||
```
|
||||
|
||||
Produit `deploy/releases/Lea-Setup-v1.0.0.exe`.
|
||||
|
||||
### Build staging uniquement (sans ISCC)
|
||||
|
||||
```bash
|
||||
./deploy/installer/build_installer.sh --stage-only
|
||||
```
|
||||
|
||||
Prepare `deploy/build/installer_staging/` puis affiche la commande ISCC a executer
|
||||
sur Windows.
|
||||
|
||||
### Nettoyer avant
|
||||
|
||||
```bash
|
||||
./deploy/installer/build_installer.sh --clean
|
||||
```
|
||||
|
||||
## Build sur Windows (recommande pour production)
|
||||
|
||||
1. Copier le dossier `deploy/` sur le PC Windows
|
||||
2. Ouvrir `deploy/installer/Lea.iss` dans Inno Setup Compiler
|
||||
3. `Build > Compile` (ou F9)
|
||||
4. Recuperer `deploy/releases/Lea-Setup-v1.0.0.exe`
|
||||
|
||||
## Python 3.12 embedded (optionnel)
|
||||
|
||||
Pour bundler Python directement dans l'installeur (evite d'exiger que les postes
|
||||
aient Python installe) :
|
||||
|
||||
```bash
|
||||
# Sur Linux
|
||||
cd deploy/installer
|
||||
wget https://www.python.org/ftp/python/3.12.8/python-3.12.8-embed-amd64.zip
|
||||
mkdir python-3.12-embed
|
||||
unzip python-3.12.8-embed-amd64.zip -d python-3.12-embed/
|
||||
```
|
||||
|
||||
Le staging copie automatiquement ce dossier si present. Le composant
|
||||
"pythonembed" devient alors selectionnable dans l'installeur.
|
||||
|
||||
Le script `configure_embed.ps1` :
|
||||
1. Patche `python312._pth` pour activer `import site`
|
||||
2. Installe `pip` via `get-pip.py`
|
||||
3. Installe `requirements_agent.txt`
|
||||
4. Reecrit `Lea.bat` pour pointer sur `python-embed\pythonw.exe`
|
||||
|
||||
## Installation silencieuse (deploiement de masse)
|
||||
|
||||
Pour deployer sans interaction utilisateur (GPO, SCCM, script PowerShell) :
|
||||
|
||||
1. Preparer un fichier `enroll.txt` par poste (ou un commun) :
|
||||
|
||||
```
|
||||
USER_NAME=Jean Dupont
|
||||
USER_EMAIL=jean.dupont@aivanov.com
|
||||
USER_ID=EMP-00123
|
||||
SERVER_URL=https://lea.labs.laurinebazin.design/api/v1
|
||||
API_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
```
|
||||
|
||||
2. Lancer l'installeur :
|
||||
|
||||
```cmd
|
||||
Lea-Setup-v1.0.0.exe /VERYSILENT /CONFIG=C:\temp\enroll.txt /DIR="C:\Lea"
|
||||
```
|
||||
|
||||
Parametres Inno Setup utiles :
|
||||
- `/VERYSILENT` : aucune UI
|
||||
- `/SILENT` : barre de progression seulement
|
||||
- `/DIR="..."` : dossier d'installation
|
||||
- `/LOG="install.log"` : log d'installation
|
||||
- `/TASKS="startmenuicon,autostart"` : composants a installer (voir `[Tasks]` et `[Components]`)
|
||||
- `/CONFIG=path` : fichier d'enrollment (custom, specifique a Lea)
|
||||
|
||||
## Signature du .exe (SmartScreen)
|
||||
|
||||
Sans signature, Windows SmartScreen affiche un avertissement rouge ("Cet editeur
|
||||
est inconnu"). Pour eviter cela, signer l'installeur avec un certificat
|
||||
code-signing.
|
||||
|
||||
### Options de certificat
|
||||
|
||||
1. **Certificat OV (Organization Validation)** : ~200-400 EUR/an
|
||||
- Sectigo, DigiCert, GlobalSign
|
||||
- SmartScreen apprend la reputation progressivement (~30 installations)
|
||||
- Livre sur token USB FIPS depuis 2023
|
||||
|
||||
2. **Certificat EV (Extended Validation)** : ~400-700 EUR/an
|
||||
- Reputation SmartScreen immediate (pas d'avertissement des la 1ere install)
|
||||
- Strict : obligatoirement sur token USB
|
||||
|
||||
### Signature manuelle (avec signtool.exe du Windows SDK)
|
||||
|
||||
```cmd
|
||||
signtool sign ^
|
||||
/tr http://timestamp.sectigo.com ^
|
||||
/td sha256 ^
|
||||
/fd sha256 ^
|
||||
/a ^
|
||||
"deploy\releases\Lea-Setup-v1.0.0.exe"
|
||||
|
||||
signtool verify /pa /v "deploy\releases\Lea-Setup-v1.0.0.exe"
|
||||
```
|
||||
|
||||
### Signature automatique dans Inno Setup
|
||||
|
||||
Ajouter dans `Lea.iss` apres `[Setup]` :
|
||||
|
||||
```
|
||||
SignTool=signtool $f
|
||||
```
|
||||
|
||||
Et declarer le signtool via `ISCC.exe /Ssigntool=...` au build.
|
||||
|
||||
### Solution interne (certif AIVANOV)
|
||||
|
||||
Si AIVANOV a deja un certificat code-signing, le token USB + mot de passe
|
||||
suffisent. Sinon, Sectigo OV est un bon choix d'entree de gamme.
|
||||
|
||||
## Structure du dossier installer/
|
||||
|
||||
```
|
||||
deploy/installer/
|
||||
├── Lea.iss # Script Inno Setup principal
|
||||
├── build_installer.sh # Helper bash (staging + ISCC)
|
||||
├── uninstall_lea.ps1 # Script de desinstallation propre
|
||||
├── configure_embed.ps1 # Configuration Python embedded
|
||||
├── config_template.txt # Modele config pour /VERYSILENT /CONFIG=
|
||||
├── LICENSE.txt # CGU affichees dans la page licence
|
||||
└── README.md # Ce fichier
|
||||
```
|
||||
|
||||
## Test de l'installeur
|
||||
|
||||
1. **Machine de test Windows 11** (VM ou PC physique, idealement sans Python)
|
||||
2. Copier `Lea-Setup-v1.0.0.exe` sur la machine
|
||||
3. Double-cliquer : verifier que l'enrollment s'affiche en francais
|
||||
4. Tester l'installation (avec et sans Python deja installe)
|
||||
5. Verifier le fichier `C:\Program Files\Lea\config.txt` genere
|
||||
6. Verifier le raccourci `shell:startup` (si option cochee)
|
||||
7. Lancer Lea, verifier la connexion au serveur
|
||||
8. Tester la desinstallation depuis "Ajout/suppression de programmes"
|
||||
|
||||
### Test automatise (PowerShell, sur la VM)
|
||||
|
||||
```powershell
|
||||
# Installation silencieuse
|
||||
$cfg = "C:\temp\enroll.txt"
|
||||
@"
|
||||
USER_NAME=Test Automatique
|
||||
USER_EMAIL=test@aivanov.com
|
||||
"@ | Out-File -Encoding ASCII $cfg
|
||||
|
||||
.\Lea-Setup-v1.0.0.exe /VERYSILENT /CONFIG=$cfg /LOG="C:\temp\install.log"
|
||||
|
||||
# Verifications
|
||||
Test-Path "C:\Program Files\Lea\config.txt"
|
||||
Get-Content "C:\Program Files\Lea\machine_id.txt"
|
||||
|
||||
# Desinstallation silencieuse
|
||||
$uninst = Get-WmiObject Win32_Product | Where-Object { $_.Name -like "Lea*" }
|
||||
$uninst.Uninstall()
|
||||
```
|
||||
|
||||
## Notes et limites connues
|
||||
|
||||
- **Endpoint serveur `/agents/enroll` et `/agents/uninstall` :** pas encore
|
||||
implemente cote serveur (avril 2026). L'installeur envoie la requete en
|
||||
best-effort, une erreur est silencieusement ignoree. A implementer dans
|
||||
`agent_v0/server_v1/api_stream.py` quand necessaire.
|
||||
- **Python embedded :** le patch `python312._pth` + pip bootstrap fonctionne mais
|
||||
augmente la taille de l'installeur (~25 MB). A reserver aux postes sans
|
||||
Python.
|
||||
- **Code signing :** indispensable pour deploiement hopital/client. Prevoir le
|
||||
budget certificat (400-700 EUR/an) dans la roadmap commerciale.
|
||||
|
||||
## Historique
|
||||
|
||||
- v1.0.0 (2026-04-13) : Premiere version de l'installeur Inno Setup.
|
||||
220
deploy/installer/build_installer.sh
Executable file
220
deploy/installer/build_installer.sh
Executable file
@@ -0,0 +1,220 @@
|
||||
#!/bin/bash
|
||||
# ============================================================
|
||||
# build_installer.sh — Prepare le staging et invoque ISCC
|
||||
# ------------------------------------------------------------
|
||||
#
|
||||
# Ce script :
|
||||
# 1. Invoque build_package.sh pour generer le package classique
|
||||
# 2. Copie le package dans deploy/build/installer_staging/
|
||||
# 3. Copie les helpers de l'installeur (uninstall, licence)
|
||||
# 4. Appelle Inno Setup (ISCC.exe) si disponible
|
||||
# (sinon, affiche les instructions pour compiler sous Windows)
|
||||
#
|
||||
# Usage :
|
||||
# ./deploy/installer/build_installer.sh # Build complet
|
||||
# ./deploy/installer/build_installer.sh --stage-only # Prepare le staging uniquement
|
||||
# ./deploy/installer/build_installer.sh --clean # Nettoyer avant
|
||||
#
|
||||
# Pre-requis :
|
||||
# - bash, rsync, zip (pour le package de base)
|
||||
# - Inno Setup 6.2+ installe (Windows ou Wine) pour compiler
|
||||
#
|
||||
# Sur Linux, ISCC.exe peut etre execute via Wine :
|
||||
# wine "/home/dom/.wine/drive_c/Program Files (x86)/Inno Setup 6/ISCC.exe" Lea.iss
|
||||
# ============================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Couleurs
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
DEPLOY_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
PROJECT_ROOT="$(dirname "$DEPLOY_DIR")"
|
||||
|
||||
STAGING_DIR="$DEPLOY_DIR/build/installer_staging"
|
||||
RELEASES_DIR="$DEPLOY_DIR/releases"
|
||||
BASE_BUILD_DIR="$DEPLOY_DIR/build/Lea"
|
||||
|
||||
# Recupere la version depuis config.py
|
||||
VERSION=$(grep -oP 'AGENT_VERSION\s*=\s*"([^"]+)"' "$PROJECT_ROOT/agent_v0/agent_v1/config.py" | grep -oP '"[^"]+"' | tr -d '"' || echo "1.0.0")
|
||||
|
||||
echo -e "${GREEN}============================================================${NC}"
|
||||
echo -e "${GREEN} Build installeur Inno Setup Lea v${VERSION}${NC}"
|
||||
echo -e "${GREEN}============================================================${NC}"
|
||||
echo ""
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# Parsing des arguments
|
||||
# ---------------------------------------------------------------
|
||||
STAGE_ONLY=0
|
||||
CLEAN=0
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--stage-only) STAGE_ONLY=1 ;;
|
||||
--clean) CLEAN=1 ;;
|
||||
*) echo "Argument inconnu : $arg" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# 1. Clean optionnel
|
||||
# ---------------------------------------------------------------
|
||||
if [[ $CLEAN -eq 1 ]]; then
|
||||
echo -e "${YELLOW}[0/5] Nettoyage des anciens builds...${NC}"
|
||||
rm -rf "$STAGING_DIR"
|
||||
rm -rf "$BASE_BUILD_DIR"
|
||||
rm -f "$RELEASES_DIR"/Lea-Setup-*.exe
|
||||
echo " OK"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
mkdir -p "$RELEASES_DIR"
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# 2. Build du package de base (reutilise build_package.sh)
|
||||
# ---------------------------------------------------------------
|
||||
echo "[1/5] Build du package de base..."
|
||||
if [[ ! -d "$BASE_BUILD_DIR" ]] || [[ $CLEAN -eq 1 ]]; then
|
||||
bash "$DEPLOY_DIR/build_package.sh" >/dev/null
|
||||
fi
|
||||
if [[ ! -d "$BASE_BUILD_DIR" ]]; then
|
||||
echo -e "${RED} Erreur : $BASE_BUILD_DIR n'a pas ete cree par build_package.sh${NC}"
|
||||
exit 1
|
||||
fi
|
||||
echo " Package de base pret : $BASE_BUILD_DIR"
|
||||
echo ""
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# 3. Copie vers staging
|
||||
# ---------------------------------------------------------------
|
||||
echo "[2/5] Preparation du staging installeur..."
|
||||
rm -rf "$STAGING_DIR"
|
||||
mkdir -p "$STAGING_DIR"
|
||||
|
||||
# Copie tout sauf config.txt (genere par l'installeur) et install.bat
|
||||
# install.bat est conserve mais sera appele en mode silencieux par ISS
|
||||
rsync -a \
|
||||
--exclude='__pycache__' \
|
||||
--exclude='*.pyc' \
|
||||
--exclude='.venv' \
|
||||
--exclude='sessions/' \
|
||||
--exclude='logs/' \
|
||||
"$BASE_BUILD_DIR/" \
|
||||
"$STAGING_DIR/"
|
||||
|
||||
# On supprime le config.txt du staging : c'est l'installeur qui le generera
|
||||
rm -f "$STAGING_DIR/config.txt"
|
||||
|
||||
echo " Staging : $STAGING_DIR"
|
||||
echo " Fichiers : $(find "$STAGING_DIR" -type f | wc -l)"
|
||||
echo ""
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# 4. Copie des helpers installeur (uninstall, licence, etc.)
|
||||
# ---------------------------------------------------------------
|
||||
echo "[3/5] Copie des helpers installeur..."
|
||||
cp "$SCRIPT_DIR/uninstall_lea.ps1" "$STAGING_DIR/" 2>/dev/null || true
|
||||
cp "$SCRIPT_DIR/configure_embed.ps1" "$STAGING_DIR/" 2>/dev/null || true
|
||||
cp "$SCRIPT_DIR/LICENSE.txt" "$STAGING_DIR/" 2>/dev/null || true
|
||||
cp "$SCRIPT_DIR/config_template.txt" "$STAGING_DIR/config_template.txt" 2>/dev/null || true
|
||||
echo " Helpers copies"
|
||||
echo ""
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# 5. Python embedded (optionnel)
|
||||
# ---------------------------------------------------------------
|
||||
PYTHON_EMBED_SRC="${PYTHON_EMBED_DIR:-$SCRIPT_DIR/python-3.12-embed}"
|
||||
if [[ -d "$PYTHON_EMBED_SRC" ]]; then
|
||||
echo "[4/5] Copie de Python 3.12 embedded..."
|
||||
rsync -a "$PYTHON_EMBED_SRC/" "$STAGING_DIR/python-3.12-embed/"
|
||||
echo " Python embedded inclus"
|
||||
else
|
||||
echo -e "${YELLOW}[4/5] Python 3.12 embedded non trouve dans $PYTHON_EMBED_SRC${NC}"
|
||||
echo " L'installeur sera produit SANS bundle Python."
|
||||
echo " Pour bundler Python : voir README.md section 'Python embedded'"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# 6. Stage-only : on s'arrete ici
|
||||
# ---------------------------------------------------------------
|
||||
if [[ $STAGE_ONLY -eq 1 ]]; then
|
||||
echo -e "${GREEN} Staging pret. Utiliser ISCC pour compiler :${NC}"
|
||||
echo " ISCC.exe \"$SCRIPT_DIR/Lea.iss\""
|
||||
echo ""
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# 7. Compilation avec ISCC (si disponible)
|
||||
# ---------------------------------------------------------------
|
||||
echo "[5/5] Compilation Inno Setup..."
|
||||
|
||||
# Chercher ISCC : natif Linux (rare), Wine, ou WSL
|
||||
ISCC_BIN=""
|
||||
if command -v iscc >/dev/null 2>&1; then
|
||||
ISCC_BIN="iscc"
|
||||
elif command -v ISCC.exe >/dev/null 2>&1; then
|
||||
ISCC_BIN="ISCC.exe"
|
||||
elif command -v wine >/dev/null 2>&1; then
|
||||
# Chemins Wine courants
|
||||
for path in \
|
||||
"$HOME/.wine/drive_c/Program Files (x86)/Inno Setup 6/ISCC.exe" \
|
||||
"$HOME/.wine/drive_c/Program Files/Inno Setup 6/ISCC.exe"; do
|
||||
if [[ -f "$path" ]]; then
|
||||
ISCC_BIN="wine \"$path\""
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ -z "$ISCC_BIN" ]]; then
|
||||
echo ""
|
||||
echo -e "${YELLOW} ISCC (Inno Setup Compiler) introuvable.${NC}"
|
||||
echo ""
|
||||
echo " Le staging est pret dans : $STAGING_DIR"
|
||||
echo ""
|
||||
echo " Pour compiler l'installeur, deux options :"
|
||||
echo ""
|
||||
echo " 1) Sur un PC Windows avec Inno Setup 6 installe :"
|
||||
echo " - Copier le dossier deploy/ sur le PC"
|
||||
echo " - Ouvrir deploy/installer/Lea.iss dans Inno Setup"
|
||||
echo " - Cliquer 'Compile' (F9)"
|
||||
echo " - Recuperer deploy/releases/Lea-Setup-v${VERSION}.exe"
|
||||
echo ""
|
||||
echo " 2) Sur Linux avec Wine :"
|
||||
echo " - winetricks innosetup (ou installer le .exe manuellement)"
|
||||
echo " - wine \"\$HOME/.wine/drive_c/Program Files (x86)/Inno Setup 6/ISCC.exe\" \\"
|
||||
echo " \"$SCRIPT_DIR/Lea.iss\""
|
||||
echo ""
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo " ISCC trouve : $ISCC_BIN"
|
||||
eval "$ISCC_BIN \"$SCRIPT_DIR/Lea.iss\""
|
||||
|
||||
# Verification du resultat
|
||||
OUTPUT_EXE="$RELEASES_DIR/Lea-Setup-v${VERSION}.exe"
|
||||
if [[ -f "$OUTPUT_EXE" ]]; then
|
||||
EXE_SIZE=$(du -h "$OUTPUT_EXE" | cut -f1)
|
||||
echo ""
|
||||
echo -e "${GREEN}============================================================${NC}"
|
||||
echo -e "${GREEN} Installeur produit !${NC}"
|
||||
echo -e "${GREEN}============================================================${NC}"
|
||||
echo ""
|
||||
echo " Fichier : $OUTPUT_EXE"
|
||||
echo " Taille : $EXE_SIZE"
|
||||
echo ""
|
||||
echo " Deploiement :"
|
||||
echo " - Signer le .exe avec un certificat code-signing (voir README.md)"
|
||||
echo " - Publier sur : https://lea.labs.laurinebazin.design/downloads/"
|
||||
echo " - Installation silencieuse : Lea-Setup-v${VERSION}.exe /VERYSILENT /CONFIG=enroll.txt"
|
||||
echo ""
|
||||
else
|
||||
echo -e "${RED} Erreur : $OUTPUT_EXE n'a pas ete produit${NC}"
|
||||
exit 1
|
||||
fi
|
||||
27
deploy/installer/config_template.txt
Normal file
27
deploy/installer/config_template.txt
Normal file
@@ -0,0 +1,27 @@
|
||||
# ============================================================
|
||||
# config_template.txt — Modele pour installation silencieuse
|
||||
# ------------------------------------------------------------
|
||||
#
|
||||
# Ce fichier est utilise en mode /VERYSILENT pour pre-remplir
|
||||
# les valeurs d'enrollment sans interface graphique.
|
||||
#
|
||||
# Usage :
|
||||
# Lea-Setup-v1.0.0.exe /VERYSILENT /CONFIG=enroll.txt
|
||||
#
|
||||
# L'installeur lit ce fichier au demarrage et remplit les pages
|
||||
# custom (nom, email, ID, URL, token) automatiquement.
|
||||
#
|
||||
# Toutes les cles ci-dessous sont optionnelles. Si une cle est
|
||||
# absente, la valeur par defaut de l'installeur est utilisee.
|
||||
#
|
||||
# Format : CLE=valeur, une ligne par parametre, # = commentaire.
|
||||
# ============================================================
|
||||
|
||||
# Identite du collaborateur (obligatoires sauf USER_ID)
|
||||
USER_NAME=Prenom Nom
|
||||
USER_EMAIL=prenom.nom@aivanov.com
|
||||
USER_ID=
|
||||
|
||||
# Connexion serveur (valeurs par defaut deja pre-remplies)
|
||||
SERVER_URL=https://lea.labs.laurinebazin.design/api/v1
|
||||
API_TOKEN=86031addb338e449fccdb1a983f61807aec15d42d482b9c7748ad607dc23caab
|
||||
112
deploy/installer/configure_embed.ps1
Normal file
112
deploy/installer/configure_embed.ps1
Normal file
@@ -0,0 +1,112 @@
|
||||
# ============================================================
|
||||
# configure_embed.ps1 — Configure le runtime Python embedded
|
||||
# ------------------------------------------------------------
|
||||
#
|
||||
# Quand le composant 'pythonembed' est installe, on a :
|
||||
# <AppDir>\python-embed\ <-- runtime Python 3.12 embedded
|
||||
#
|
||||
# Ce script :
|
||||
# 1. Active l'import des packages (patch de python312._pth)
|
||||
# 2. Installe pip dans le runtime embedded
|
||||
# 3. Installe les dependances requirements_agent.txt
|
||||
# 4. Reecrit Lea.bat pour pointer sur python-embed\pythonw.exe
|
||||
#
|
||||
# Doit etre execute avec le CWD = <AppDir>
|
||||
# ============================================================
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$AppDir = Get-Location
|
||||
$EmbedDir = Join-Path $AppDir "python-embed"
|
||||
$PythonExe = Join-Path $EmbedDir "python.exe"
|
||||
|
||||
if (-not (Test-Path $PythonExe)) {
|
||||
Write-Host "Python embedded introuvable, abandon."
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "Configuration de Python embedded..."
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# 1. Decommenter la ligne 'import site' dans python312._pth
|
||||
# (necessaire pour que pip puisse fonctionner)
|
||||
# ---------------------------------------------------------------
|
||||
$PthFile = Get-ChildItem -Path $EmbedDir -Filter "python*._pth" | Select-Object -First 1
|
||||
if ($PthFile) {
|
||||
$Content = Get-Content $PthFile.FullName
|
||||
$NewContent = $Content -replace '^#import site', 'import site'
|
||||
Set-Content -Path $PthFile.FullName -Value $NewContent
|
||||
Write-Host " python._pth patche (import site active)"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# 2. Installer pip (bootstrap via get-pip.py)
|
||||
# ---------------------------------------------------------------
|
||||
$GetPip = Join-Path $env:TEMP "get-pip.py"
|
||||
Write-Host " Telechargement de get-pip.py..."
|
||||
Invoke-WebRequest -Uri "https://bootstrap.pypa.io/get-pip.py" -OutFile $GetPip -UseBasicParsing
|
||||
|
||||
Write-Host " Installation de pip..."
|
||||
& $PythonExe $GetPip --no-warn-script-location
|
||||
Remove-Item $GetPip -Force
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# 3. Installer les dependances
|
||||
# ---------------------------------------------------------------
|
||||
$Requirements = Join-Path $AppDir "requirements_agent.txt"
|
||||
if (Test-Path $Requirements) {
|
||||
Write-Host " Installation des dependances Python..."
|
||||
& $PythonExe -m pip install --no-warn-script-location -r $Requirements
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# 4. Reecrire Lea.bat pour utiliser python-embed
|
||||
# ---------------------------------------------------------------
|
||||
$LeaBat = Join-Path $AppDir "Lea.bat"
|
||||
$NewLeaBat = @"
|
||||
@echo off
|
||||
chcp 65001 >nul 2>&1
|
||||
title Lea - Assistante IA
|
||||
cd /d "%~dp0"
|
||||
|
||||
if exist "lea_agent.lock" (
|
||||
for /f "usebackq tokens=* delims=" %%i in ("lea_agent.lock") do (
|
||||
taskkill /F /PID %%i >nul 2>&1
|
||||
)
|
||||
del /f /q "lea_agent.lock" >nul 2>&1
|
||||
timeout /t 2 >nul
|
||||
)
|
||||
|
||||
if exist "config.txt" (
|
||||
for /f "usebackq eol=# tokens=1,* delims==" %%a in ("config.txt") do (
|
||||
if not "%%a"=="" if not "%%b"=="" set "%%a=%%b"
|
||||
)
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Demarrage de Lea (runtime embedded)...
|
||||
echo.
|
||||
|
||||
start "" /b "%~dp0python-embed\pythonw.exe" run_agent_v1.py
|
||||
|
||||
timeout /t 3 >nul
|
||||
set "LEA_ALIVE=0"
|
||||
if exist "lea_agent.lock" (
|
||||
for /f "usebackq tokens=* delims=" %%i in ("lea_agent.lock") do (
|
||||
tasklist /FI "PID eq %%i" /NH 2>nul | findstr /I "pythonw" >nul && set "LEA_ALIVE=1"
|
||||
)
|
||||
)
|
||||
if "%LEA_ALIVE%"=="0" (
|
||||
echo.
|
||||
echo Lea n'a pas demarre correctement. Affichage des erreurs :
|
||||
echo.
|
||||
"%~dp0python-embed\python.exe" run_agent_v1.py
|
||||
pause
|
||||
)
|
||||
"@
|
||||
|
||||
Set-Content -Path $LeaBat -Value $NewLeaBat -Encoding ASCII
|
||||
Write-Host " Lea.bat reecrit pour runtime embedded"
|
||||
|
||||
Write-Host "Configuration terminee."
|
||||
exit 0
|
||||
99
deploy/installer/uninstall_lea.ps1
Normal file
99
deploy/installer/uninstall_lea.ps1
Normal file
@@ -0,0 +1,99 @@
|
||||
# ============================================================
|
||||
# uninstall_lea.ps1 — Script de desinstallation propre de Lea
|
||||
# ------------------------------------------------------------
|
||||
#
|
||||
# Appele par Inno Setup via [UninstallRun] AVANT la suppression
|
||||
# des fichiers. Roles :
|
||||
#
|
||||
# 1. Tuer proprement le process Lea (via PID du lock)
|
||||
# 2. Nettoyer shell:startup (supprimer le raccourci auto-start)
|
||||
# 3. Notifier le serveur de la desinstallation (best-effort)
|
||||
# 4. Supprimer le lock file
|
||||
#
|
||||
# Usage (par Inno Setup) :
|
||||
# powershell.exe -NoProfile -ExecutionPolicy Bypass \
|
||||
# -File uninstall_lea.ps1 -AppDir "C:\Program Files\Lea"
|
||||
# ============================================================
|
||||
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$AppDir
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'SilentlyContinue'
|
||||
Write-Host "Desinstallation de Lea en cours..."
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# 1. Tuer le process via PID du lock file
|
||||
# ---------------------------------------------------------------
|
||||
$LockFile = Join-Path $AppDir "lea_agent.lock"
|
||||
if (Test-Path $LockFile) {
|
||||
try {
|
||||
$Pid = (Get-Content $LockFile -ErrorAction Stop | Select-Object -First 1).Trim()
|
||||
if ($Pid -match '^\d+$') {
|
||||
Write-Host " Arret du process Lea (PID $Pid)..."
|
||||
Stop-Process -Id ([int]$Pid) -Force -ErrorAction SilentlyContinue
|
||||
Start-Sleep -Seconds 1
|
||||
}
|
||||
} catch {
|
||||
Write-Host " Lock file illisible (ignore)."
|
||||
}
|
||||
Remove-Item $LockFile -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# 2. Nettoyer shell:startup (peut ne pas exister si composant
|
||||
# non installe, on le supprime silencieusement dans tous les cas)
|
||||
# ---------------------------------------------------------------
|
||||
$StartupDir = [Environment]::GetFolderPath('Startup')
|
||||
$StartupShortcut = Join-Path $StartupDir "Lea.lnk"
|
||||
if (Test-Path $StartupShortcut) {
|
||||
Write-Host " Suppression du raccourci auto-start..."
|
||||
Remove-Item $StartupShortcut -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# 3. Notifier le serveur de la desinstallation (best-effort)
|
||||
# ---------------------------------------------------------------
|
||||
$ConfigFile = Join-Path $AppDir "config.txt"
|
||||
$MachineIdFile = Join-Path $AppDir "machine_id.txt"
|
||||
if ((Test-Path $ConfigFile) -and (Test-Path $MachineIdFile)) {
|
||||
try {
|
||||
$ConfigLines = Get-Content $ConfigFile
|
||||
$ServerUrl = ($ConfigLines | Where-Object { $_ -match '^RPA_SERVER_URL=' } | Select-Object -First 1) -replace '^RPA_SERVER_URL=', ''
|
||||
$Token = ($ConfigLines | Where-Object { $_ -match '^RPA_API_TOKEN=' } | Select-Object -First 1) -replace '^RPA_API_TOKEN=', ''
|
||||
$MachineId = (Get-Content $MachineIdFile -ErrorAction Stop | Select-Object -First 1).Trim()
|
||||
|
||||
if ($ServerUrl -and $Token -and $MachineId) {
|
||||
Write-Host " Notification du serveur..."
|
||||
$Body = @{
|
||||
machine_id = $MachineId
|
||||
hostname = $env:COMPUTERNAME
|
||||
event = 'uninstall'
|
||||
timestamp = (Get-Date -Format "o")
|
||||
} | ConvertTo-Json
|
||||
|
||||
Invoke-RestMethod `
|
||||
-Uri "$ServerUrl/agents/uninstall" `
|
||||
-Method POST `
|
||||
-Body $Body `
|
||||
-ContentType 'application/json' `
|
||||
-Headers @{ Authorization = "Bearer $Token" } `
|
||||
-TimeoutSec 5 `
|
||||
-ErrorAction SilentlyContinue | Out-Null
|
||||
}
|
||||
} catch {
|
||||
# Best-effort : on ignore toute erreur reseau/auth
|
||||
Write-Host " Notification serveur echouee (ignore)."
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# 4. Supprimer les fichiers restants verrouilles eventuellement
|
||||
# ---------------------------------------------------------------
|
||||
Start-Sleep -Seconds 1
|
||||
Get-ChildItem -Path $AppDir -Filter "*.pyc" -Recurse -ErrorAction SilentlyContinue |
|
||||
Remove-Item -Force -ErrorAction SilentlyContinue
|
||||
|
||||
Write-Host "Desinstallation : pre-traitement termine."
|
||||
exit 0
|
||||
Reference in New Issue
Block a user