Premiere commit - Omika

This commit is contained in:
2026-10-23 18:46:50 +02:00
commit 8ea76c1b24
2155 changed files with 984301 additions and 0 deletions
+318
View File
@@ -0,0 +1,318 @@
# ============================================================
# Omika - Déploiement STATIQUE des fichiers serveur (Phase 5C.2A)
# ============================================================
# CIBLE : Windows Server 2025 (SRV-OMIKA), PowerShell ADMINISTRATEUR.
# NE PAS exécuter depuis le poste de développement.
#
# Ce script fait UNIQUEMENT du déploiement de FICHIERS (statique) :
# 1. Vérifie l'intégrité du package (SHA256SUMS.txt) avant copie.
# 2. Crée l'arborescence C:\Omika (Apps / Data / Logs / Config / Backups / Deploy).
# 3. Sauvegarde les anciennes publications avant remplacement.
# 4. Copie les 5 publications (OmikaApi, OmikaWorker, DiagnosticsWorker,
# DiagnosticsWeb, DiagnosticsApi) en Release win-x64 framework-dependent.
# 5. Préserve la configuration serveur déjà injectée (jamais écrasée).
# 6. Vérifie le nombre de fichiers copiés (source vs cible) et la présence
# des exécutables.
#
# STATIC FILE DEPLOY ONLY - SERVICES NOT ACTIVATED.
# Ce script NE FAIT PAS (interdictions Phase 5C.2A) :
# - AUCUNE création / arrêt / démarrage de service Windows.
# - AUCUNE règle firewall, AUCUNE installation IIS.
# - AUCUNE modification de PostgreSQL, du Collector OpenTelemetry, de HFSQL.
# - AUCUN mot de passe, AUCUN secret, AUCUNE écriture de config secrète.
# - AUCUN choix de compte de service (A_DETERMINER_EN_PHASE_ACTIVATION).
#
# IDEMPOTENT : une seconde exécution re-sauvegarde, re-copie, ne perd ni logs,
# ni checkpoints, ni configuration serveur déjà injectée.
# Prend en charge -WhatIf (aucune modification réelle).
# ============================================================
[CmdletBinding(SupportsShouldProcess = $true)]
param(
[string]$PackageRoot,
[string]$InstallRoot = 'C:\Omika',
[switch]$SkipChecksum
)
$ErrorActionPreference = 'Stop'
# --- Résolution de PackageRoot APRÈS le bloc param (bug : $PSScriptRoot est
# vide dans une valeur par défaut de param()). Valeur par défaut : dossier
# PARENT du dossier du script (le package contient Deploy\ + OmikaApi\ +
# OmikaWorker\ + DiagnosticsWorker\ + DiagnosticsWeb\).
# --- Résolution de PackageRoot APRÈS le bloc param (bug : $PSScriptRoot est
# vide dans une valeur par défaut de param()). Valeur par défaut : dossier
# PARENT du dossier du script (le package contient Deploy\ + OmikaApi\ +
# OmikaWorker\ + DiagnosticsWorker\ + DiagnosticsWeb\ + DiagnosticsApi\).
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 '<chemin du package server-full>' 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
}
}
# --- Calcul SHA256 read-only (indépendant de ShouldProcess) -----------------
# Get-FileHash est SupportsShouldProcess : sous -WhatIf global, il ne retourne
# PAS l'objet attendu (null) et ".Hash.ToLowerInvariant()" lève une exception
# "Impossible d'appeler une méthode dans une expression Null". Cette fonction
# lit directement le fichier via System.IO.File + System.Security.Cryptography.
# SHA256 : opération purement READ-ONLY, insensible à $WhatIfPreference.
# - ouvre en lecture (FileAccess.Read)
# - partage en lecture (FileShare.Read)
# - retourne l'empreinte hexadécimale minuscule sur 64 caractères
# - dispose proprement le FileStream et le SHA256 (finally)
function Get-Sha256Hex {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$Path
)
$stream = $null
$sha256 = $null
try {
$stream = [System.IO.File]::Open(
$Path,
[System.IO.FileMode]::Open,
[System.IO.FileAccess]::Read,
[System.IO.FileShare]::Read)
$sha256 = [System.Security.Cryptography.SHA256]::Create()
$hashBytes = $sha256.ComputeHash($stream)
return ([System.BitConverter]::ToString($hashBytes)).Replace('-', '').ToLowerInvariant()
}
finally {
if ($sha256) { $sha256.Dispose() }
if ($stream) { $stream.Dispose() }
}
}
# --- Vérification SHA256 du package -----------------------------------------
# SHA256SUMS.txt : une ligne par fichier, format '<hash64> <chemin-relatif>'.
# Le chemin est relatif à la racine du package. Échoue si un fichier manque ou
# si un hash diffère (sauf -SkipChecksum).
function Test-PackageChecksums {
param([string]$packageRoot)
$sumsPath = Join-Path $packageRoot 'SHA256SUMS.txt'
if (-not (Test-Path -LiteralPath $sumsPath)) {
Write-Error "SHA256SUMS.txt introuvable : $sumsPath"
exit 1
}
$ok = 0
$fail = 0
foreach ($line in Get-Content -LiteralPath $sumsPath) {
if ([string]::IsNullOrWhiteSpace($line)) { continue }
$parts = $line -split '\s+'
if ($parts.Count -lt 2) { continue }
$expected = $parts[0].Trim().ToLowerInvariant()
$rel = ($parts[1..($parts.Count - 1)] -join ' ').Trim().TrimStart('*')
if ([string]::IsNullOrWhiteSpace($rel)) { continue }
$file = Join-Path $packageRoot $rel
if (-not (Test-Path -LiteralPath $file)) {
Write-Warning "Fichier manquant dans le package : $rel"
$fail++
continue
}
$actual = Get-Sha256Hex -Path $file
if ($actual -ne $expected) {
Write-Error "Checksum SHA256 KO : $rel (attendu $expected, obtenu $actual)"
$fail++
}
else {
$ok++
}
}
if ($fail -gt 0) {
Write-Error "Vérification SHA256 échouée : $fail fichier(s) en erreur."
exit 1
}
Write-Host " SHA256 OK : $ok fichier(s) vérifié(s)."
}
# --- Copie d'une publication avec sauvegarde + préservation config ----------
function Deploy-AppFolder {
param(
[string]$Name,
[string]$Source,
[string]$Target,
[string]$BackupRoot,
[string[]]$Preserve
)
if (-not (Test-Path -LiteralPath $Source)) {
Write-Error "Publication introuvable : $Source"
exit 1
}
$backup = $null
if (Test-Path -LiteralPath $Target) {
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
$backup = Join-Path $BackupRoot ("{0}-{1}" -f $Name, $stamp)
if ($PSCmdlet.ShouldProcess($Target, "Sauvegarder l'ancienne version vers $backup")) {
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"
}
}
$sourceCount = (Get-ChildItem -LiteralPath $Source -Recurse -File).Count
if ($PSCmdlet.ShouldProcess($Target, "Copier $sourceCount fichier(s) depuis $Source")) {
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 (secrets) : restaurer
# depuis la sauvegarde si elle existait déjà sur le serveur.
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"
}
}
}
$targetCount = (Get-ChildItem -LiteralPath $Target -Recurse -File).Count
Write-Host " $Name : $sourceCount fichier(s) source -> $targetCount fichier(s) cible."
if ($targetCount -lt $sourceCount) {
Write-Warning " Nombre de fichiers cible ($targetCount) inférieur à la source ($sourceCount) : copie incomplète ?"
}
}
}
# --- 1. Prérequis ------------------------------------------------------------
Write-Step 'Prérequis'
Assert-Admin
if (-not $SkipChecksum) {
Write-Step 'Vérification SHA256 du package'
Test-PackageChecksums -packageRoot $PackageRoot
}
else {
Write-Warning "Vérification SHA256 ignorée (-SkipChecksum)."
}
# --- 2. Arborescence C:\Omika ------------------------------------------------
Write-Step 'Création de l''arborescence'
$appsRoot = Join-Path $InstallRoot 'Apps'
$dataRoot = Join-Path $InstallRoot 'Data'
$logsRoot = Join-Path $InstallRoot 'Logs'
$configRoot = Join-Path $InstallRoot 'Config'
$backupsRoot = Join-Path $InstallRoot 'Backups'
$deployRoot = Join-Path $InstallRoot 'Deploy'
$folders = @(
$appsRoot,
(Join-Path $appsRoot 'OmikaApi'),
(Join-Path $appsRoot 'OmikaWorker'),
(Join-Path $appsRoot 'DiagnosticsWorker'),
(Join-Path $appsRoot 'DiagnosticsWeb'),
(Join-Path $appsRoot 'DiagnosticsApi'),
(Join-Path $dataRoot 'Api\Diagnostics\Jsonl'),
(Join-Path $dataRoot 'DiagnosticsWorker\Checkpoint'),
(Join-Path $dataRoot 'DiagnosticsApi\Central'),
(Join-Path $logsRoot 'Api'),
(Join-Path $logsRoot 'Worker'),
(Join-Path $logsRoot 'DiagnosticsWorker'),
(Join-Path $logsRoot 'DiagnosticsWeb'),
(Join-Path $logsRoot 'DiagnosticsApi'),
$configRoot,
$backupsRoot,
$deployRoot
)
foreach ($folder in $folders) {
if ($PSCmdlet.ShouldProcess($folder, 'Créer le dossier')) {
New-Item -ItemType Directory -Force -Path $folder | Out-Null
}
}
# --- 3+4. Copie des publications --------------------------------------------
Write-Step 'Copie des publications (avec sauvegarde)'
Deploy-AppFolder -Name 'OmikaApi' `
-Source (Join-Path $PackageRoot 'OmikaApi') `
-Target (Join-Path $appsRoot 'OmikaApi') `
-BackupRoot $backupsRoot `
-Preserve @('appsettings.json', 'appsettings.Production.json')
Deploy-AppFolder -Name 'OmikaWorker' `
-Source (Join-Path $PackageRoot 'OmikaWorker') `
-Target (Join-Path $appsRoot 'OmikaWorker') `
-BackupRoot $backupsRoot `
-Preserve @('appsettings.json', 'appsettings.Production.json')
Deploy-AppFolder -Name 'DiagnosticsWorker' `
-Source (Join-Path $PackageRoot 'DiagnosticsWorker') `
-Target (Join-Path $appsRoot 'DiagnosticsWorker') `
-BackupRoot $backupsRoot `
-Preserve @('appsettings.json', 'appsettings.Production.json')
Deploy-AppFolder -Name 'DiagnosticsWeb' `
-Source (Join-Path $PackageRoot 'DiagnosticsWeb') `
-Target (Join-Path $appsRoot 'DiagnosticsWeb') `
-BackupRoot $backupsRoot `
-Preserve @('appsettings.json', 'appsettings.Production.json')
Deploy-AppFolder -Name 'DiagnosticsApi' `
-Source (Join-Path $PackageRoot 'DiagnosticsApi') `
-Target (Join-Path $appsRoot 'DiagnosticsApi') `
-BackupRoot $backupsRoot `
-Preserve @('appsettings.json', 'appsettings.Production.json')
# --- 5. Vérification des exécutables ----------------------------------------
Write-Step 'Vérification des exécutables'
$expectedExe = @(
(Join-Path $appsRoot 'OmikaApi\Omika.Api.exe'),
(Join-Path $appsRoot 'OmikaWorker\Omika.Worker.exe'),
(Join-Path $appsRoot 'DiagnosticsWorker\Omika.Diagnostics.Worker.exe'),
(Join-Path $appsRoot 'DiagnosticsWeb\Omika.Diagnostics.Web.exe'),
(Join-Path $appsRoot 'DiagnosticsApi\Omika.Diagnostics.Api.exe')
)
foreach ($exe in $expectedExe) {
if (Test-Path -LiteralPath $exe) {
Write-Host " OK : $exe"
}
else {
Write-Warning " EXE absent : $exe (copie incomplète ou nom inattendu)."
}
}
# --- Résumé ------------------------------------------------------------------
Write-Host ''
Write-Host 'DEPLOIEMENT STATIQUE TERMINE (fichiers copies uniquement).' -ForegroundColor Green
Write-Host " Apps : $appsRoot"
Write-Host " Data : $dataRoot"
Write-Host " Logs : $logsRoot"
Write-Host " Config : $configRoot"
Write-Host " Backups : $backupsRoot"
Write-Host ''
Write-Host 'SERVICES NOT ACTIVATED.' -ForegroundColor Yellow
Write-Host 'Aucun service Windows n''a été créé, démarré ni arrêté.' -ForegroundColor Yellow
Write-Host 'Aucun firewall / IIS / PostgreSQL / Collector / HFSQL n''a été modifié.' -ForegroundColor Yellow
Write-Host ''
Write-Host 'RAPPELS (à faire manuellement, jamais automatisé ici) :' -ForegroundColor Yellow
Write-Host ' - Injecter les secrets (connection strings, HmacKey, HFSQL, licence)'
Write-Host ' uniquement SUR LE SERVEUR, via variables d''environnement ou'
Write-Host ' appsettings.Production.json local à ACL restreinte.'
Write-Host ' - Créer les comptes de service Windows dédiés et les services dans la'
Write-Host ' phase d''activation (comptes A_DETERMINER_EN_PHASE_ACTIVATION).'
Write-Host ' - Appliquer les ACL NTFS minimales (voir README-DEPLOYMENT.txt).'
Write-Host ' - Aucune règle firewall 5432/4318 n''a été ni ne doit être ajoutée.'