# ============================================================ # 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 '\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 } }