Files
Omika/infra/deploy/Install-DatabaseSchema.ps1
2026-10-23 18:46:50 +02:00

167 lines
6.8 KiB
PowerShell

# ============================================================
# Omika Diagnostic Center - Application IDEMPOTENTE du schema PostgreSQL
# Base dediee : omika_diagnostics (outil SUPPORT uniquement)
# ============================================================
# A executer SUR LE SERVEUR (Windows Server 2025), PowerShell administrateur.
# NE JAMAIS executer ce script depuis un poste qui n'heberge pas PostgreSQL.
#
# APPLIQUE TOUTES LES MIGRATIONS Diagnostics DANS L'ORDRE (001 -> 016).
# Inclut le schema (001/002/005/006/007/008), les roles/moindre privilege
# (003/009), les tables administrables (010/011/012) et les GRANT de
# retention/suppression (004/013/014/015/016).
#
# CONTEXTE : a executer SUR LE SERVEUR avec un compte PostgreSQL SUPERUSER
# (ex. -Username postgres), car 003/009 font du CREATE ROLE / ALTER ... OWNER
# et les migrations de GRANT (004/013/014/015/016) requierent le superuser.
#
# Garanties :
# - AUCUN mot de passe en dur (jamais dans ce fichier, jamais affiche).
# - AUCUN DROP, AUCUNE suppression de base/table, AUCUNE migration destructive.
# - AUCUNE donnee ecrasee : applique via CREATE/ADD ... IF NOT EXISTS + GRANT idempotent.
# - Idempotent : une seconde execution est un no-op (schema deja present).
# - Applique les fichiers SQL DANS L'ORDRE (001 -> 016), avec ON_ERROR_STOP=1.
# - Exclut 003_diagnostics_roles_preprod.sql (preproduction uniquement).
#
# Usage :
# $env:PGPASSWORD = '...' # facultatif - sinon saisie masquee demandee
# .\Install-DatabaseSchema.ps1
# .\Install-DatabaseSchema.ps1 -DatabaseDir "C:\Omika\Deploy\server-full\Database"
# .\Install-DatabaseSchema.ps1 -PsqlPath "C:\Program Files\PostgreSQL\18\bin\psql.exe"
#
# Note : le script Harden-DatabaseRoles.ps1 reste disponible pour n'appliquer
# QUE 003 (transition/back-compat) ; le present script couvre l'integralite.
# ============================================================
[CmdletBinding()]
param(
[string[]]$SchemaFiles = @(
'001_diagnostics_schema.sql',
'002_actor_key.sql',
'003_diagnostics_roles.sql',
'004_diagnostics_retention.sql',
'005_diagnostics_client_context.sql',
'006_diagnostics_device_license.sql',
'007_diagnostics_usage.sql',
'008_diagnostics_clients.sql',
'009_diagnostics_clients_grants.sql',
'010_diagnostics_web_users.sql',
'011_diagnostics_reference.sql',
'012_diagnostics_tasks.sql',
'013_diagnostics_tasks_delete.sql',
'014_diagnostics_telemetry_retention.sql',
'015_diagnostics_retention_untreated.sql',
'016_diagnostics_event_delete.sql'
),
[string]$DatabaseDir,
[string]$PsqlPath,
[string]$PgHost = '127.0.0.1',
[int]$PgPort = 5432,
[string]$Database = 'omika_diagnostics',
[string]$Username = 'postgres'
)
$ErrorActionPreference = 'Stop'
# --- Resolution de DatabaseDir APRES le bloc param (bug : $PSScriptRoot est
# vide dans une valeur par defaut de param()). Valeur par defaut : dossier
# frere "Database" du dossier du script (layout package : phase5b2\Database
# + phase5b2\Deploy).
if (-not $DatabaseDir) {
if ($PSScriptRoot) {
$DatabaseDir = Join-Path (Split-Path -Parent $PSScriptRoot) 'Database'
}
}
if (-not $DatabaseDir -or -not (Test-Path -LiteralPath $DatabaseDir)) {
Write-Error "Dossier des schemas introuvable : $DatabaseDir. Fournissez -DatabaseDir '<chemin>\Database'."
exit 1
}
# --- Detection de psql.exe (ordre : -PsqlPath explicite, puis PATH, puis
# C:\Program Files\PostgreSQL\*\bin\psql.exe en choisissant la version la
# plus elevee de maniere deterministe).
function Resolve-PsqlPath {
param([string]$ExplicitPath)
if ($ExplicitPath) {
if (-not (Test-Path -LiteralPath $ExplicitPath)) {
Write-Error "PsqlPath fourni introuvable : $ExplicitPath"
exit 1
}
return (Resolve-Path -LiteralPath $ExplicitPath).Path
}
$inPath = Get-Command psql.exe -ErrorAction SilentlyContinue
if ($inPath) {
return $inPath.Source
}
$candidates = @(Get-ChildItem -Path 'C:\Program Files\PostgreSQL\*\bin\psql.exe' -ErrorAction SilentlyContinue)
if ($candidates.Count -gt 0) {
$best = $candidates | ForEach-Object {
$version = 0
$verDir = $_.Directory.Parent.Name
if ($verDir -match '^\d+$') { $version = [int]$verDir }
[PSCustomObject]@{ Path = $_.FullName; Version = $version }
} | Sort-Object -Property Version -Descending | Select-Object -First 1
return $best.Path
}
return $null
}
$psqlExe = Resolve-PsqlPath -ExplicitPath $PsqlPath
if (-not $psqlExe) {
Write-Error "Client 'psql.exe' introuvable. Fournissez -PsqlPath ou placez le client PostgreSQL dans le PATH."
exit 1
}
Write-Host "psql utilise : $psqlExe"
# --- Mot de passe : priorite a PGPASSWORD deja present ; sinon saisie masquee.
# Le mot de passe n'est JAMAIS ecrit dans un fichier ni affiche a l'ecran.
$useExistingPgPassword = -not [string]::IsNullOrEmpty($env:PGPASSWORD)
if (-not $useExistingPgPassword) {
$secure = Read-Host "Mot de passe PostgreSQL (role '$Username', base '$Database')" -AsSecureString
$plain = [Runtime.InteropServices.Marshal]::PtrToStringAuto(
[Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure))
$env:PGPASSWORD = $plain
$plain = $null
}
try {
foreach ($schemaFile in $SchemaFiles) {
$sqlPath = Join-Path $DatabaseDir $schemaFile
if (-not (Test-Path -LiteralPath $sqlPath)) {
throw "Fichier de schema introuvable : $sqlPath"
}
Write-Host "Application du schema IDEMPOTENT : $schemaFile (${PgHost}:${PgPort}/${Database}, role $Username) ..."
& $psqlExe `
-h $PgHost `
-p $PgPort `
-U $Username `
-d $Database `
-v ON_ERROR_STOP=1 `
-f $sqlPath
if ($LASTEXITCODE -ne 0) {
throw "psql a echoue (exit $LASTEXITCODE) sur $schemaFile. Aucune modification destructive possible (CREATE/ADD IF NOT EXISTS uniquement)."
}
}
Write-Host ''
Write-Host "Migrations appliquees (idempotent, fichiers dans l'ordre 001 -> 016)." -ForegroundColor Green
Write-Host "Tables : diagnostic_event, telemetry_metric_hourly, diagnostics_clients,"
Write-Host " diagnostics_users, diagnostics_reference, diagnostics_tasks."
Write-Host "Roles : omika_diagnostics_owner/worker/web/api. Aucune donnee ecrasee."
Write-Host ''
Write-Host "Rappel : definir les mots de passe des roles LOGIN manuellement (jamais ici) :" -ForegroundColor Yellow
Write-Host " \password omika_diagnostics_worker"
Write-Host " \password omika_diagnostics_web"
Write-Host " \password omika_diagnostics_api"
}
finally {
if (-not $useExistingPgPassword) {
Remove-Item Env:PGPASSWORD -ErrorAction SilentlyContinue
}
}