237 lines
6.8 KiB
PowerShell
237 lines
6.8 KiB
PowerShell
[CmdletBinding()]
|
||
param(
|
||
[Parameter(Mandatory = $true)]
|
||
[string]$PsqlPath,
|
||
|
||
[Parameter(Mandatory = $true)]
|
||
[string]$AdminUser,
|
||
|
||
[Parameter(Mandatory = $true)]
|
||
[string]$AdminPassword,
|
||
|
||
[Parameter(Mandatory = $true)]
|
||
[ValidateRange(1, 65535)]
|
||
[int]$PostgreSqlPort,
|
||
|
||
[Parameter(Mandatory = $true)]
|
||
[string]$DatabaseName,
|
||
|
||
[Parameter(Mandatory = $true)]
|
||
[string]$DatabaseUser,
|
||
|
||
[Parameter(Mandatory = $true)]
|
||
[string]$DatabasePassword,
|
||
|
||
[Parameter(Mandatory = $true)]
|
||
[ValidateRange(1, 65535)]
|
||
[int]$ApplicationPort,
|
||
|
||
[Parameter(Mandatory = $false)]
|
||
[ValidateSet('full', 'cartridge')]
|
||
[string]$Edition = 'full',
|
||
|
||
[Parameter(Mandatory = $true)]
|
||
[string]$ConfigPath
|
||
)
|
||
|
||
$ErrorActionPreference = 'Stop'
|
||
|
||
trap {
|
||
$logDirectory = Split-Path -Parent $ConfigPath
|
||
New-Item -ItemType Directory -Path $logDirectory -Force -ErrorAction SilentlyContinue | Out-Null
|
||
$_ | Out-String | Set-Content -LiteralPath (Join-Path $logDirectory 'install-error.log') -Encoding UTF8
|
||
$env:PGPASSWORD = $null
|
||
exit 1
|
||
}
|
||
|
||
function Assert-SafeIdentifier {
|
||
param([string]$Value, [string]$Label)
|
||
if ($Value -notmatch '^[A-Za-z_][A-Za-z0-9_]*$') {
|
||
throw "$Label содержит недопустимые символы."
|
||
}
|
||
}
|
||
|
||
function Quote-Identifier {
|
||
param([string]$Value)
|
||
return '"' + $Value.Replace('"', '""') + '"'
|
||
}
|
||
|
||
function Quote-Literal {
|
||
param([string]$Value)
|
||
return "'" + $Value.Replace("'", "''") + "'"
|
||
}
|
||
|
||
function Convert-PsqlOutput {
|
||
param([string]$Text)
|
||
|
||
if ([string]::IsNullOrEmpty($Text)) {
|
||
return $Text
|
||
}
|
||
|
||
# Russian PostgreSQL messages can be emitted in the Windows ANSI code page
|
||
# while Windows PowerShell 5.1 decodes them as the OEM code page.
|
||
if ($Text -match '[\u2500-\u259F]') {
|
||
try {
|
||
$culture = [System.Globalization.CultureInfo]::CurrentCulture
|
||
$oemEncoding = [System.Text.Encoding]::GetEncoding($culture.TextInfo.OEMCodePage)
|
||
$ansiEncoding = [System.Text.Encoding]::GetEncoding($culture.TextInfo.ANSICodePage)
|
||
return $ansiEncoding.GetString($oemEncoding.GetBytes($Text))
|
||
}
|
||
catch {
|
||
return $Text
|
||
}
|
||
}
|
||
|
||
return $Text
|
||
}
|
||
|
||
function Invoke-Psql {
|
||
param(
|
||
[string]$Database,
|
||
[string]$Sql,
|
||
[string]$Username = $AdminUser,
|
||
[switch]$Capture
|
||
)
|
||
|
||
$sqlFilePath = Join-Path `
|
||
([System.IO.Path]::GetTempPath()) `
|
||
("inventera-{0}.sql" -f ([Guid]::NewGuid().ToString('N')))
|
||
|
||
[System.IO.File]::WriteAllText(
|
||
$sqlFilePath,
|
||
$Sql + [Environment]::NewLine,
|
||
[System.Text.UTF8Encoding]::new($false)
|
||
)
|
||
|
||
try {
|
||
$arguments = @(
|
||
'--host', '127.0.0.1',
|
||
'--port', [string]$PostgreSqlPort,
|
||
'--username', $Username,
|
||
'--dbname', $Database,
|
||
'--no-password',
|
||
'--set', 'ON_ERROR_STOP=1',
|
||
'--tuples-only',
|
||
'--no-align',
|
||
'--file', $sqlFilePath
|
||
)
|
||
|
||
$previousErrorActionPreference = $ErrorActionPreference
|
||
try {
|
||
$ErrorActionPreference = 'Continue'
|
||
$output = & $PsqlPath @arguments 2>&1
|
||
$exitCode = $LASTEXITCODE
|
||
}
|
||
finally {
|
||
$ErrorActionPreference = $previousErrorActionPreference
|
||
}
|
||
|
||
$outputText = Convert-PsqlOutput -Text (
|
||
($output -join [Environment]::NewLine).Trim()
|
||
)
|
||
|
||
if ($exitCode -ne 0) {
|
||
if ($outputText -eq '') {
|
||
$outputText = "psql завершился с кодом $exitCode."
|
||
}
|
||
throw $outputText
|
||
}
|
||
|
||
if ($Capture) {
|
||
return $outputText
|
||
}
|
||
|
||
if ($outputText -ne '') {
|
||
Write-Host $outputText
|
||
}
|
||
}
|
||
finally {
|
||
Remove-Item -LiteralPath $sqlFilePath -Force -ErrorAction SilentlyContinue
|
||
}
|
||
}
|
||
|
||
if (-not (Test-Path -LiteralPath $PsqlPath -PathType Leaf)) {
|
||
throw "Не найден psql.exe: $PsqlPath"
|
||
}
|
||
|
||
Assert-SafeIdentifier -Value $AdminUser -Label 'Логин администратора PostgreSQL'
|
||
Assert-SafeIdentifier -Value $DatabaseName -Label 'Имя базы данных'
|
||
Assert-SafeIdentifier -Value $DatabaseUser -Label 'Логин пользователя базы данных'
|
||
|
||
$env:PGPASSWORD = $AdminPassword
|
||
$connected = $false
|
||
$lastConnectionError = $null
|
||
|
||
for ($attempt = 1; $attempt -le 45; $attempt++) {
|
||
try {
|
||
Invoke-Psql -Database 'postgres' -Sql 'SELECT 1;' | Out-Null
|
||
$connected = $true
|
||
break
|
||
}
|
||
catch {
|
||
$lastConnectionError = $_
|
||
Start-Sleep -Seconds 2
|
||
}
|
||
}
|
||
|
||
if (-not $connected) {
|
||
throw "Не удалось подключиться к PostgreSQL на порту $PostgreSqlPort. $lastConnectionError"
|
||
}
|
||
|
||
$databaseIdentifier = Quote-Identifier $DatabaseName
|
||
$databaseLiteral = Quote-Literal $DatabaseName
|
||
$userIdentifier = Quote-Identifier $DatabaseUser
|
||
$userLiteral = Quote-Literal $DatabaseUser
|
||
$passwordLiteral = Quote-Literal $DatabasePassword
|
||
|
||
$roleExists = Invoke-Psql -Database 'postgres' -Sql "SELECT 1 FROM pg_roles WHERE rolname = $userLiteral;" -Capture
|
||
if ($roleExists -eq '1') {
|
||
Invoke-Psql -Database 'postgres' -Sql "ALTER ROLE $userIdentifier WITH LOGIN PASSWORD $passwordLiteral;"
|
||
}
|
||
else {
|
||
Invoke-Psql -Database 'postgres' -Sql "CREATE ROLE $userIdentifier WITH LOGIN PASSWORD $passwordLiteral;"
|
||
}
|
||
|
||
$databaseExists = Invoke-Psql -Database 'postgres' -Sql "SELECT 1 FROM pg_database WHERE datname = $databaseLiteral;" -Capture
|
||
if ($databaseExists -ne '1') {
|
||
Invoke-Psql -Database 'postgres' -Sql "CREATE DATABASE $databaseIdentifier OWNER $userIdentifier ENCODING 'UTF8';"
|
||
}
|
||
else {
|
||
Invoke-Psql -Database 'postgres' -Sql "ALTER DATABASE $databaseIdentifier OWNER TO $userIdentifier;"
|
||
}
|
||
|
||
Invoke-Psql -Database 'postgres' -Sql "GRANT ALL PRIVILEGES ON DATABASE $databaseIdentifier TO $userIdentifier;"
|
||
|
||
$env:PGPASSWORD = $DatabasePassword
|
||
try {
|
||
Invoke-Psql `
|
||
-Database $DatabaseName `
|
||
-Username $DatabaseUser `
|
||
-Sql 'SELECT 1;' | Out-Null
|
||
}
|
||
catch {
|
||
throw "Не удалось проверить подключение с учётными данными приложения. $_"
|
||
}
|
||
|
||
$configDirectory = Split-Path -Parent $ConfigPath
|
||
New-Item -ItemType Directory -Path $configDirectory -Force | Out-Null
|
||
|
||
$config = [ordered]@{
|
||
edition = $Edition
|
||
database = [ordered]@{
|
||
host = '127.0.0.1'
|
||
port = $PostgreSqlPort
|
||
name = $DatabaseName
|
||
user = $DatabaseUser
|
||
password = $DatabasePassword
|
||
}
|
||
application = [ordered]@{
|
||
host = '0.0.0.0'
|
||
port = $ApplicationPort
|
||
}
|
||
}
|
||
|
||
$config | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $ConfigPath -Encoding UTF8
|
||
$env:PGPASSWORD = $null
|
||
Write-Host "База данных $DatabaseName и конфигурация Inventera подготовлены."
|