Files
LoreMind/installers/desktop/build-windows.ps1
IETM_FIXE\ietm6 eb78a75621
Some checks failed
E2E Tests / e2e (push) Has been cancelled
Build & Push Images / build (brain) (push) Successful in 1m10s
Build & Push Images / build (core) (push) Successful in 3m8s
Build & Push Images / build (web) (push) Successful in 1m41s
Build & Push Images / build-switcher (push) Successful in 39s
Ajout du binaire tesseract pour la reconnaissance OCR des PDF pour l'installation local
Correction d'un problème d'écrasement de BDD à la réinstallation
2026-06-18 10:28:31 +02:00

244 lines
12 KiB
PowerShell

#Requires -Version 5.1
<#
.SYNOPSIS
Construit l'installeur de BUREAU Windows de LoreMind (.msi), sans Docker.
.DESCRIPTION
Pipeline complet "local-first" :
1. Build du front Angular (web/ -> web/dist/web)
2. Prep du Brain (Python embeddable) (brain/ -> dist-embed : python.exe signe PSF + deps + sources)
3. Build du Core en fat jar + front (core/ -> target/*.jar, profil Maven "desktop")
4. Assemblage de la charge utile (jar + brain) dans un dossier d'entree jpackage
5. jpackage -> installeur .msi avec JRE embarque
L'app resultante se lance d'un double-clic : le Core demarre en profil Spring
"local" (H2 fichier + stockage filesystem) et lance lui-meme le Brain en sidecar.
Aucune dependance externe a installer cote utilisateur (ni Docker, ni Java, ni Python).
L'IA fonctionne via le cloud (1min.ai / Gemini / Mistral...) ou via Ollama si present.
.PARAMETER Version
Version de l'installeur (X.Y.Z, numerique). Defaut : derivee de core/pom.xml
(le suffixe -beta est retire car les MSI n'acceptent qu'une version numerique).
.PARAMETER SkipFront / SkipBrain / SkipJar
Sauter une etape (build incrementaux pendant la mise au point).
.PREREQUIS (sur la machine de build uniquement, PAS chez l'utilisateur final)
- JDK 21+ avec jpackage dans le PATH (Temurin OK).
- WiX Toolset v3 (https://github.com/wixtoolset/wix3/releases) — requis par
jpackage pour produire un .msi sur Windows.
- Node.js + npm (build Angular).
- Python + pip (telecharge les wheels cp312 du Brain ; toute version 3.x convient,
on cible explicitement 3.12 via --python-version) + acces Internet (python.org).
.NOTES
Projet : LoreMind — assistant pour Maitres de Jeu de JDR
Licence : AGPL-3.0
#>
[CmdletBinding()]
param(
[string]$Version,
[switch]$SkipFront,
[switch]$SkipBrain,
[switch]$SkipJar
)
$ErrorActionPreference = 'Stop'
function Write-Step($m) { Write-Host "==> $m" -ForegroundColor Cyan }
function Write-Ok($m) { Write-Host " OK $m" -ForegroundColor Green }
function Write-Err($m) { Write-Host " XX $m" -ForegroundColor Red }
# --- Chemins ---------------------------------------------------------------
$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
$WebDir = Join-Path $RepoRoot 'web'
$BrainDir = Join-Path $RepoRoot 'brain'
$CoreDir = Join-Path $RepoRoot 'core'
$StageDir = Join-Path $CoreDir 'target\dist-input' # charge utile jpackage
$OutDir = Join-Path $CoreDir 'target\dist-out' # .msi produit
# --- Version (numerique pour MSI) ------------------------------------------
if (-not $Version) {
$pom = Get-Content (Join-Path $CoreDir 'pom.xml') -Raw
# IMPORTANT : viser la version DU PROJET, pas le premier <version> du fichier
# (qui est celui du parent spring-boot-starter-parent). On l'ancre sur
# l'artifactId du projet, puis on garde la partie numerique (MSI = X.Y.Z).
if ($pom -match '<artifactId>loremind-core</artifactId>\s*<version>([0-9]+\.[0-9]+\.[0-9]+)') {
$Version = $Matches[1]
} else {
$Version = '0.0.0'
}
}
Write-Host ""
Write-Host "============================================================" -ForegroundColor Magenta
Write-Host " LoreMind - Build installeur bureau Windows (v$Version)" -ForegroundColor Magenta
Write-Host "============================================================" -ForegroundColor Magenta
# --- Verif outils ----------------------------------------------------------
if (-not (Get-Command jpackage -ErrorAction SilentlyContinue)) {
Write-Err "jpackage introuvable dans le PATH. Installez un JDK 21+ (Temurin)."
exit 1
}
# --- 1. Front Angular ------------------------------------------------------
if (-not $SkipFront) {
Write-Step "Build du front Angular"
Push-Location $WebDir
try {
if (-not (Test-Path 'node_modules')) { npm ci }
npm run build
if ($LASTEXITCODE -ne 0) { throw "Echec du build Angular" }
} finally { Pop-Location }
Write-Ok "Front construit (web/dist/web)"
} else { Write-Step "Front : saute (--SkipFront)" }
# --- 2. Brain (Python embeddable officiel, PAS d'exe gele) -----------------
# On embarque le Python *embeddable* de python.org (python.exe SIGNE par la PSF)
# + les dependances + les sources .py. Aucun executable "gele" type PyInstaller
# -> plus de faux positif antivirus (le bootloader packe etait pris pour un
# trojan). Le Core lancera : brain\python\python.exe brain\run_local.py
$PyVersion = '3.12.8' # doit matcher python:3.12 du Docker
$PyTag = 'python312' # prefixe du fichier ._pth
$BrainEmbed = Join-Path $BrainDir 'dist-embed' # staging du brain empaquete
if (-not $SkipBrain) {
Write-Step "Preparation du Brain (Python embeddable $PyVersion)"
if (Test-Path $BrainEmbed) { Remove-Item $BrainEmbed -Recurse -Force }
$PyDir = Join-Path $BrainEmbed 'python'
New-Item -ItemType Directory -Force -Path $PyDir | Out-Null
# a) Telecharger + extraire le Python embeddable (zip officiel).
$zip = Join-Path $BrainEmbed 'python-embed.zip'
$url = "https://www.python.org/ftp/python/$PyVersion/python-$PyVersion-embed-amd64.zip"
Write-Host " Telechargement $url"
Invoke-WebRequest -Uri $url -OutFile $zip -UseBasicParsing
Expand-Archive -Path $zip -DestinationPath $PyDir -Force
Remove-Item $zip -Force
# b) Activer site-packages dans le ._pth (sinon les deps ne sont pas importees).
$pth = Join-Path $PyDir "$PyTag._pth"
Set-Content -Path $pth -Encoding ascii -Value @(
"$PyTag.zip", '.', 'Lib\site-packages', 'import site'
)
# c) Installer les deps en wheels cp312 dans python\Lib\site-packages.
# --python-version 3.12 + --only-binary : on telecharge les roues 3.12
# meme si le pip courant tourne sous une autre version de Python.
$site = Join-Path $PyDir 'Lib\site-packages'
New-Item -ItemType Directory -Force -Path $site | Out-Null
Push-Location $BrainDir
try {
python -m pip install --upgrade pip *> $null
python -m pip install --target $site --only-binary=:all: --python-version 3.12 -r requirements.txt
if ($LASTEXITCODE -ne 0) { throw "Echec pip install (deps Brain)" }
} finally { Pop-Location }
# d) Copier les sources du Brain + le point d'entree.
Copy-Item (Join-Path $BrainDir 'app') (Join-Path $BrainEmbed 'app') -Recurse
Copy-Item (Join-Path $BrainDir 'run_local.py') (Join-Path $BrainEmbed 'run_local.py')
# e) Tesseract (OCR des PDF scannes). Pas de zip portable officiel : on COPIE
# l'install UB-Mannheim (tesseract.exe + DLL), prerequis a installer une
# fois via `choco install tesseract`. Langues fra+eng tirees de tessdata_fast
# (leger, coherent). Si Tesseract n'est pas installe -> skip gracieux :
# l'app reste fonctionnelle, seul l'OCR des scans manquera (cf. run_local.py
# + pdf_extractor.py qui detectent l'absence et degradent proprement).
$tessSrc = Join-Path $env:ProgramFiles 'Tesseract-OCR'
if (Test-Path (Join-Path $tessSrc 'tesseract.exe')) {
$tessDst = Join-Path $BrainEmbed 'tesseract'
Copy-Item $tessSrc $tessDst -Recurse
New-Item -ItemType Directory -Force -Path (Join-Path $tessDst 'tessdata') | Out-Null
foreach ($lang in @('fra','eng')) {
Invoke-WebRequest -UseBasicParsing `
-Uri "https://github.com/tesseract-ocr/tessdata_fast/raw/main/$lang.traineddata" `
-OutFile (Join-Path $tessDst "tessdata\$lang.traineddata")
}
Write-Ok "Tesseract bundle (OCR des scans actif)"
} else {
Write-Host " !! Tesseract introuvable ($tessSrc) -> OCR des scans NON bundle (degradation gracieuse). Pour l'activer : choco install tesseract" -ForegroundColor Yellow
}
Write-Ok "Brain prepare (brain/dist-embed : python embeddable + deps + sources)"
} else { Write-Step "Brain : saute (--SkipBrain)" }
# --- 3. Core (fat jar avec front embarque) ---------------------------------
if (-not $SkipJar) {
Write-Step "Build du Core (fat jar, profil desktop)"
Push-Location $CoreDir
try {
# -Pdesktop : copie web/dist/web dans le jar (classpath:/static).
cmd /c "mvn -q -Pdesktop -DskipTests clean package"
if ($LASTEXITCODE -ne 0) { throw "Echec du build Maven" }
} finally { Pop-Location }
Write-Ok "Core construit (core/target)"
} else { Write-Step "Core : saute (--SkipJar)" }
# --- 4. Assemblage de la charge utile --------------------------------------
Write-Step "Assemblage de la charge utile jpackage"
if (Test-Path $StageDir) { Remove-Item $StageDir -Recurse -Force }
New-Item -ItemType Directory -Force -Path $StageDir | Out-Null
# Le jar repackage Spring Boot (executable) — on ignore le *.jar.original.
$jar = Get-ChildItem (Join-Path $CoreDir 'target') -Filter 'loremind-core-*.jar' |
Where-Object { $_.Name -notlike '*.original' } | Select-Object -First 1
if (-not $jar) { Write-Err "Jar introuvable dans core/target. Lancez sans --SkipJar."; exit 1 }
Copy-Item $jar.FullName (Join-Path $StageDir 'loremind-core.jar')
# Le Brain (python embeddable + deps + sources) -> stage/brain/
# stage/brain/python/python.exe , stage/brain/app , stage/brain/run_local.py
if (-not (Test-Path $BrainEmbed)) { Write-Err "Brain introuvable (brain/dist-embed). Lancez sans --SkipBrain."; exit 1 }
$stageBrain = Join-Path $StageDir 'brain'
New-Item -ItemType Directory -Force -Path $stageBrain | Out-Null
Copy-Item (Join-Path $BrainEmbed '*') $stageBrain -Recurse
Write-Ok "Charge utile prete ($StageDir)"
# --- 5. jpackage -> .msi ---------------------------------------------------
Write-Step "Generation de l'installeur (.msi) via jpackage"
if (Test-Path $OutDir) { Remove-Item $OutDir -Recurse -Force }
New-Item -ItemType Directory -Force -Path $OutDir | Out-Null
# $APPDIR est substitue par jpackage au LANCEMENT par le dossier 'app' de
# l'install (qui contient le jar ET le dossier brain copie depuis --input).
# Le Brain se lance via le python embeddable + run_local.py. brain.sidecar.command
# est une liste : les deux chemins separes par une virgule (aucun chemin Windows
# ne contient de virgule) sont bindes en List<String> par Spring.
$brainCmd = '$APPDIR\brain\python\python.exe,$APPDIR\brain\run_local.py'
# --win-upgrade-uuid : UUID STABLE du produit. Permet a Windows Installer de
# reconnaitre qu'une nouvelle version est une MISE A JOUR du meme produit -> upgrade
# en place propre (pas besoin de desinstaller avant, pas de doublon). Les donnees
# (base H2, images) vivent de toute facon dans ~/.loremind, HORS du dossier
# d'installation, donc jamais touchees par l'installeur.
# /!\ NE JAMAIS CHANGER cet UUID : le modifier casserait la chaine d'upgrade.
#
# Note : pas de --win-console (app de bureau). Les logs Spring/Brain peuvent
# etre rediriges vers un fichier via une option ulterieure si besoin de debug.
jpackage `
--type msi `
--name LoreMind `
--app-version $Version `
--vendor 'IGML Creation' `
--input $StageDir `
--main-jar loremind-core.jar `
--main-class org.springframework.boot.loader.launch.JarLauncher `
--dest $OutDir `
--java-options '-Dspring.profiles.active=local' `
--java-options "-Dbrain.sidecar.command=$brainCmd" `
--win-per-user-install `
--win-upgrade-uuid 'a7c4e1d2-9b3f-4e6a-8d05-1f2c3b4a5e6d' `
--win-menu --win-menu-group 'LoreMind' `
--win-shortcut --win-dir-chooser
if ($LASTEXITCODE -ne 0) {
Write-Err "Echec jpackage. Cause probable : WiX Toolset v3 absent."
Write-Err "Installez-le : https://github.com/wixtoolset/wix3/releases"
exit 1
}
$msi = Get-ChildItem $OutDir -Filter '*.msi' | Select-Object -First 1
Write-Host ""
Write-Host "============================================================" -ForegroundColor Green
Write-Host " Installeur genere !" -ForegroundColor Green
Write-Host " $($msi.FullName)" -ForegroundColor Green
Write-Host "============================================================" -ForegroundColor Green