Premiere commit - Omika
This commit is contained in:
@@ -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.'
|
||||
Reference in New Issue
Block a user