# ============================================================ # Omika Diagnostic Center — Déploiement Phase 5B2 (SERVER uniquement) # ============================================================ # CIBLE : Windows Server 2025, PowerShell ADMINISTRATEUR. # NE PAS exécuter depuis le poste de développement. # # Ce script : # 1. Vérifie les prérequis (admin + runtime .NET 10 présent). # 2. Crée C:\Omika\Apps\DiagnosticsWorker et C:\Omika\Apps\DiagnosticsWeb. # 3. Copie les publications (binaires framework-dependent win-x64). # 4. Sauvegarde l'ancienne version avant remplacement. # 5. Copie le Worker (Windows Service REEL). StartupType MANUAL + STOPPED : # JAMAIS de demarrage avant Phase 5C. Si le service n'existe pas, sa # creation est DIFFEREE a la Phase 5C (compte A_DETERMINER_EN_PHASE_5C, # aucun compte LocalSystem/NetworkService fige automatiquement). # 6. Préserve la configuration serveur déjà injectée (jamais écrasée). # # Ce script NE FAIT PAS (interdictions Phase 5B2A) : # - AUCUN mot de passe PostgreSQL en dur ; AUCUNE donnée secrète écrite. # - AUCUNE ouverture de port 5432 ni 4318 ; AUCUNE règle firewall. # - AUCUNE modification du Collector OpenTelemetry. # - AUCUNE modification de HFSQL ni de Omika.Api. # - AUCUN démarrage de la Phase 5C. # - AUCUN démarrage du Worker (service MANUAL + STOPPED, jamais Automatic). # - AUCUN choix d'hébergement Web (IIS vs Kestrel) : le Web est copié seulement. # # IDEMPOTENT : une seconde exécution ne crée pas de service en double, ne # ré-applique pas le schéma, n'écrase pas la config secrète, ne multiplie pas # les dossiers, et ne perd ni logs ni checkpoints. # ============================================================ [CmdletBinding()] param( [string]$PackageRoot, [string]$ServiceName = 'OmikaDiagnosticsWorker', [string]$DisplayName = 'Omika Diagnostics Worker', [string]$WorkerTarget = 'C:\Omika\Apps\DiagnosticsWorker', [string]$WebTarget = 'C:\Omika\Apps\DiagnosticsWeb', [string]$BackupRoot = 'C:\Omika\Backups' ) $ErrorActionPreference = 'Stop' # --- Resolution de PackageRoot APRES le bloc param (bug : $PSScriptRoot est # vide dans une valeur par defaut de param()). Valeur par defaut : dossier # PARENT du dossier du script (le package phase5b2 contient Deploy\ + # DiagnosticsWorker\ + DiagnosticsWeb\ + Database\). if (-not $PackageRoot) { if ($PSScriptRoot) { $PackageRoot = Split-Path -Parent $PSScriptRoot } } if (-not $PackageRoot -or -not (Test-Path -LiteralPath $PackageRoot)) { Write-Error "PackageRoot introuvable : '$PackageRoot'. Fournissez -PackageRoot '' explicitement." exit 1 } function Write-Step([string]$msg) { Write-Host "== $msg ==" -ForegroundColor Cyan } function Assert-Admin { $id = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = New-Object Security.Principal.WindowsPrincipal($id) if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { Write-Error "Ce script doit être exécuté en PowerShell ADMINISTRATEUR." exit 1 } } function Deploy-Folder([string]$Name, [string]$Source, [string]$Target, [string[]]$Preserve) { $backup = $null if (Test-Path -LiteralPath $Target) { $stamp = Get-Date -Format 'yyyyMMdd-HHmmss' $backup = Join-Path $BackupRoot ("{0}-{1}" -f $Name, $stamp) New-Item -ItemType Directory -Force -Path $backup | Out-Null Copy-Item -Path (Join-Path $Target '*') -Destination $backup -Recurse -Force -ErrorAction SilentlyContinue Write-Host " Ancienne version sauvegardée : $backup" } New-Item -ItemType Directory -Force -Path $Target | Out-Null Copy-Item -Path (Join-Path $Source '*') -Destination $Target -Recurse -Force # Préserver la configuration serveur déjà injectée (secret en 5B2B) : # on restaure les fichiers de config depuis la sauvegarde s'ils existaient. if ($backup) { foreach ($file in $Preserve) { $oldFile = Join-Path $backup $file if (Test-Path -LiteralPath $oldFile) { Copy-Item -Path $oldFile -Destination (Join-Path $Target $file) -Force Write-Host " Configuration serveur préservée : $file" } } } } # --- 1. Prérequis --------------------------------------------------------- Write-Step 'Prérequis' Assert-Admin $dotnet = Get-Command dotnet -ErrorAction SilentlyContinue if (-not $dotnet) { Write-Error "Runtime .NET introuvable. Installez 'ASP.NET Core Runtime 10.x' (hébergement) sur le serveur." exit 1 } $hasAspNetCore10 = (& $dotnet.Source --list-runtimes 2>$null) -match 'Microsoft\.AspNetCore\.App\s+10\.' if (-not $hasAspNetCore10) { Write-Warning "Runtime 'Microsoft.AspNetCore.App 10.x' non détecté. Le Worker et le Web (framework-dependent) en ont besoin. Installez le ASP.NET Core Runtime 10.x avant de démarrer les applications." } $workerSource = Join-Path $PackageRoot 'DiagnosticsWorker' $webSource = Join-Path $PackageRoot 'DiagnosticsWeb' if (-not (Test-Path -LiteralPath $workerSource)) { Write-Error "Publication Worker introuvable : $workerSource"; exit 1 } if (-not (Test-Path -LiteralPath $webSource)) { Write-Error "Publication Web introuvable : $webSource"; exit 1 } # --- 2+3+4. Dossiers + copie + sauvegarde --------------------------------- Write-Step 'Copie des publications (avec sauvegarde)' New-Item -ItemType Directory -Force -Path $BackupRoot | Out-Null Deploy-Folder 'DiagnosticsWorker' $workerSource $WorkerTarget @('appsettings.json', 'appsettings.Production.json') Deploy-Folder 'DiagnosticsWeb' $webSource $WebTarget @('appsettings.json', 'appsettings.Production.json') # --- 5+6. Service Windows (Worker uniquement) ----------------------------- Write-Step 'Service Windows Worker' $workerExe = Join-Path $WorkerTarget 'Omika.Diagnostics.Worker.exe' if (-not (Test-Path -LiteralPath $workerExe)) { Write-Error "Exécutable Worker introuvable : $workerExe" exit 1 } # Binaire = VRAI Windows Service ? $serviceCapable = Test-Path -LiteralPath (Join-Path $WorkerTarget 'Microsoft.Extensions.Hosting.WindowsServices.dll') if (-not $serviceCapable) { Write-Error "Le binaire Worker fourni n'est PAS un Windows Service (Microsoft.Extensions.Hosting.WindowsServices.dll absent). Re-publier avec EnableWindowsService=true. Aucun service ne sera cree." exit 1 } $existing = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue $serviceInstalled = $false if ($existing) { # Deja installe : NE JAMAIS demarrer. Forcer Manual + Stopped tant que la # Phase 5C n'a pas valide la source JSONL distante. Set-Service -Name $ServiceName -StartupType Manual Stop-Service -Name $ServiceName -Force -ErrorAction SilentlyContinue Write-Host " Service '$ServiceName' existant : force en Manual + Stopped (jamais demarre avant Phase 5C)." $serviceInstalled = $true } else { # Compte de service definitif = A_DETERMINER_EN_PHASE_5C. Pour ne JAMAIS # figer LocalSystem/NetworkService automatiquement, la creation du service # est DIFFEREE a la Phase 5C. Seuls les fichiers sont installes ici. Write-Warning "Creation du service '$ServiceName' DIFFEREE a la Phase 5C (compte definitif = A_DETERMINER_EN_PHASE_5C). Fichiers installes uniquement, aucun service cree, aucun compte fige." } # --- Résumé ---------------------------------------------------------------- Write-Host '' Write-Host 'DEPLOIEMENT 5B2 TERMINE (fichiers copies ; service STOPPE / Manual).' -ForegroundColor Green Write-Host " Worker : $WorkerTarget" Write-Host " Web : $WebTarget (hébergement NON décidé ici — voir README-DEPLOYMENT.txt)" Write-Host '' if ($serviceInstalled) { Write-Host 'OmikaDiagnosticsWorker installed but intentionally stopped.' -ForegroundColor Yellow Write-Host 'Phase 5C is required before enabling or starting the service.' -ForegroundColor Yellow } else { Write-Host 'OmikaDiagnosticsWorker service creation deferred to Phase 5C.' -ForegroundColor Yellow Write-Host 'No service installed: no Windows restart can start the Worker before Phase 5C.' -ForegroundColor Yellow Write-Host 'Phase 5C is required before enabling or starting the service.' -ForegroundColor Yellow } Write-Host '' Write-Host 'RAPPELS (a faire manuellement, jamais automatise ici) :' -ForegroundColor Yellow Write-Host " - Injecter la connection string PostgreSQL via variable d'environnement" Write-Host " ConnectionStrings__DiagnosticsDatabase (ou appsettings.Production.json local, ACL restreinte)." Write-Host " - Définir le dossier JSONL : Observability__Remote__Directory." Write-Host " - Appliquer le schéma : Deploy\Install-DatabaseSchema.ps1 (mot de passe jamais en dur)." Write-Host " - Durcir PostgreSQL (003) : Deploy\Harden-DatabaseRoles.ps1 (contexte administrateur)." Write-Host " - Décider l'hébergement Web (IIS ou Kestrel) après vérification des rôles serveur." Write-Host " - Aucune règle firewall 5432/4318 n'a été ni ne doit être ajoutée." Write-Host " - Phase 5C : determiner le compte de service (A_DETERMINER_EN_PHASE_5C)," Write-Host " creer/activer le service, puis seulement alors demarrer le Worker."