From 7e6927eacc7f81a06013440cc6efa1d49717f90f Mon Sep 17 00:00:00 2001 From: Alexey Date: Sun, 26 Jul 2026 15:31:02 +0300 Subject: [PATCH] Beta_cartrigesg_bd Beta v0.7 --- INVENTERA_INSTALLER.md | 31 +++ Inventera.iss | 280 +++++++++++++++++++- installer/initialize_inventera_database.ps1 | 33 ++- 3 files changed, 327 insertions(+), 17 deletions(-) diff --git a/INVENTERA_INSTALLER.md b/INVENTERA_INSTALLER.md index 0e2f880..291287d 100644 --- a/INVENTERA_INSTALLER.md +++ b/INVENTERA_INSTALLER.md @@ -25,6 +25,36 @@ Порты PostgreSQL и веб-приложения не могут совпадать. Если один из требуемых портов занят, мастер не позволит продолжить установку. +Перед запуском PostgreSQL установщик сохраняет его автоматически созданный пароль +и выбранный порт в реестре Windows: + +```text +HKEY_LOCAL_MACHINE\Software\Inventera\Installer +``` + +Это позволяет корректно продолжить установку после ошибки или отмены без генерации +нового пароля для уже созданного кластера. + +## Повторный запуск + +Если служба `InventeraPostgreSQL18` уже существует, установщик: + +- не запускает установщик PostgreSQL повторно; +- читает фактический порт из `postgresql.conf`; +- показывает этот порт в мастере и запрещает случайно изменить его; +- использует сохранённый автоматический пароль; +- повторяет создание базы и конфигурации Inventera. + +Занятый порт обнаруженной службы PostgreSQL является нормальным состоянием и не +считается ошибкой. + +Старые сборки Inventera не сохраняли случайный пароль администратора PostgreSQL. +Если найден такой PostgreSQL, автоматический режим остановится с пояснением. Нужно +либо выбрать ручной режим и указать известный пароль администратора, либо удалить +неудачную старую установку PostgreSQL вместе с её каталогом данных и выполнить +чистую автоматическую установку. Каталог данных нельзя удалять, если в нём уже +есть нужные базы. + ## Ручной режим Мастер предлагает: @@ -86,4 +116,5 @@ installer\prerequisites\postgesql.exe - Установщик требует права администратора Windows. - При удалении Inventera база PostgreSQL и файл конфигурации не удаляются автоматически, чтобы не потерять данные. +- Служебные параметры автоматической установки также сохраняются после удаления Inventera, чтобы PostgreSQL оставался доступен при повторной установке приложения. - Если используется другая основная версия PostgreSQL, измените `PostgreSqlMajor` в `Inventera.iss`. diff --git a/Inventera.iss b/Inventera.iss index 545cae0..2025b3a 100644 --- a/Inventera.iss +++ b/Inventera.iss @@ -60,6 +60,11 @@ var DatabasePage: TInputQueryWizardPage; ExistingPsqlPage: TInputFileWizardPage; AutomaticAdminPassword: String; + SavedAutomaticStateAvailable: Boolean; + ExistingInventeraPostgreSql: Boolean; + ExistingPostgreSqlPort: Integer; + ExistingPostgreSqlDataDirectory: String; + ExistingPostgreSqlPsqlPath: String; function IsAutomaticMode: Boolean; begin @@ -68,7 +73,9 @@ end; function ShouldInstallPostgreSql: Boolean; begin - Result := IsAutomaticMode or (PostgreSqlInstallPage.SelectedValueIndex = 0); + Result := + (not ExistingInventeraPostgreSql) and + (IsAutomaticMode or (PostgreSqlInstallPage.SelectedValueIndex = 0)); end; function GetPostgreSqlPort(Param: String): String; @@ -158,7 +165,9 @@ end; function GetPsqlPath: String; begin - if ShouldInstallPostgreSql then + if ExistingInventeraPostgreSql then + Result := ExistingPostgreSqlPsqlPath + else if ShouldInstallPostgreSql then Result := ExpandConstant('{autopf}\PostgreSQL\{#PostgreSqlMajor}\bin\psql.exe') else Result := Trim(ExistingPsqlPage.Values[0]); @@ -234,9 +243,178 @@ begin ) and (ExitCode = 0); end; +function TryReadPostgreSqlPort( + const DataDirectory: String; + var PortNumber: Integer +): Boolean; +var + Lines: TArrayOfString; + I: Integer; + EqualsPosition: Integer; + CommentPosition: Integer; + Line: String; + Value: String; +begin + Result := False; + PortNumber := 0; + + if not LoadStringsFromFile( + AddBackslash(DataDirectory) + 'postgresql.conf', + Lines + ) then + Exit; + + for I := 0 to GetArrayLength(Lines) - 1 do + begin + Line := Trim(Lines[I]); + if (Line <> '') and (Copy(Line, 1, 1) <> '#') and + (Copy(Lowercase(Line), 1, 4) = 'port') then + begin + EqualsPosition := Pos('=', Line); + if EqualsPosition > 0 then + begin + Value := Trim(Copy(Line, EqualsPosition + 1, Length(Line))); + CommentPosition := Pos('#', Value); + if CommentPosition > 0 then + Value := Trim(Copy(Value, 1, CommentPosition - 1)); + Value := RemoveQuotes(Value); + + if IsValidPort(Value, PortNumber) then + begin + Result := True; + Exit; + end; + end; + end; + end; +end; + +function LoadAutomaticInstallerState: Boolean; +var + SavedPassword: String; + SavedPort: String; +begin + Result := False; + SavedPassword := ''; + SavedPort := ''; + + if RegQueryStringValue( + HKLM64, + 'Software\Inventera\Installer', + 'PostgreSqlAdminPassword', + SavedPassword + ) and IsSafePassword(SavedPassword) then + begin + AutomaticAdminPassword := SavedPassword; + Result := True; + end; + + if RegQueryStringValue( + HKLM64, + 'Software\Inventera\Installer', + 'PostgreSqlPort', + SavedPort + ) then + IsValidPort(SavedPort, ExistingPostgreSqlPort); +end; + +function SaveAutomaticInstallerState: Boolean; +begin + Result := RegWriteStringValue( + HKLM64, + 'Software\Inventera\Installer', + 'PostgreSqlAdminUser', + GetAdminUser + ); + + if Result then + Result := RegWriteStringValue( + HKLM64, + 'Software\Inventera\Installer', + 'PostgreSqlAdminPassword', + GetAdminPassword + ); + + if Result then + Result := RegWriteStringValue( + HKLM64, + 'Software\Inventera\Installer', + 'PostgreSqlPort', + GetPostgreSqlPort('') + ); + + if Result then + Result := RegWriteStringValue( + HKLM64, + 'Software\Inventera\Installer', + 'PostgreSqlDataDirectory', + ExistingPostgreSqlDataDirectory + ); + + if Result then + Result := RegWriteStringValue( + HKLM64, + 'Software\Inventera\Installer', + 'PostgreSqlPsqlPath', + ExistingPostgreSqlPsqlPath + ); + + if Result then + SavedAutomaticStateAvailable := True + else + Log('Не удалось сохранить параметры автоматической установки PostgreSQL.'); +end; + +procedure DetectExistingInventeraPostgreSql; +var + SavedPort: String; +begin + ExistingPostgreSqlDataDirectory := + ExpandConstant('{autopf}\PostgreSQL\{#PostgreSqlMajor}\data'); + ExistingPostgreSqlPsqlPath := + ExpandConstant('{autopf}\PostgreSQL\{#PostgreSqlMajor}\bin\psql.exe'); + + ExistingInventeraPostgreSql := + RegKeyExists( + HKLM, + 'SYSTEM\CurrentControlSet\Services\InventeraPostgreSQL{#PostgreSqlMajor}' + ) and FileExists(ExistingPostgreSqlPsqlPath); + + if ExistingInventeraPostgreSql then + begin + if not TryReadPostgreSqlPort( + ExistingPostgreSqlDataDirectory, + ExistingPostgreSqlPort + ) then + begin + SavedPort := ''; + if RegQueryStringValue( + HKLM64, + 'Software\Inventera\Installer', + 'PostgreSqlPort', + SavedPort + ) then + IsValidPort(SavedPort, ExistingPostgreSqlPort); + end; + + if ExistingPostgreSqlPort = 0 then + ExistingPostgreSqlPort := 5432; + + Log( + 'Обнаружена существующая служба PostgreSQL Inventera. Порт: ' + + IntToStr(ExistingPostgreSqlPort) + ); + end; +end; + procedure InitializeWizard; begin - AutomaticAdminPassword := GenerateAutomaticPassword; + ExistingPostgreSqlPort := 0; + AutomaticAdminPassword := ''; + SavedAutomaticStateAvailable := LoadAutomaticInstallerState; + if AutomaticAdminPassword = '' then + AutomaticAdminPassword := GenerateAutomaticPassword; + DetectExistingInventeraPostgreSql; ModePage := CreateInputOptionPage( wpWelcome, @@ -260,7 +438,10 @@ begin ); PostgreSqlInstallPage.Add('Да, установить PostgreSQL из комплекта Inventera'); PostgreSqlInstallPage.Add('Нет, использовать существующий PostgreSQL'); - PostgreSqlInstallPage.SelectedValueIndex := 0; + if ExistingInventeraPostgreSql then + PostgreSqlInstallPage.SelectedValueIndex := 1 + else + PostgreSqlInstallPage.SelectedValueIndex := 0; AdminPage := CreateInputQueryPage( PostgreSqlInstallPage.ID, @@ -280,7 +461,15 @@ begin ); PortsPage.Add('Порт PostgreSQL:', False); PortsPage.Add('Порт веб-приложения Inventera:', False); - PortsPage.Values[0] := '5432'; + if ExistingInventeraPostgreSql then + begin + PortsPage.Values[0] := IntToStr(ExistingPostgreSqlPort); + PortsPage.Edits[0].Enabled := False; + end + else if ExistingPostgreSqlPort > 0 then + PortsPage.Values[0] := IntToStr(ExistingPostgreSqlPort) + else + PortsPage.Values[0] := '5432'; PortsPage.Values[1] := '5050'; DatabasePage := CreateInputQueryPage( @@ -303,13 +492,55 @@ begin 'Файл psql.exe обычно находится в папке C:\Program Files\PostgreSQL\<версия>\bin.' ); ExistingPsqlPage.Add('Путь к psql.exe:', 'Исполняемые файлы|*.exe|Все файлы|*.*', '.exe'); - ExistingPsqlPage.Values[0] := FindExistingPsql; + if ExistingInventeraPostgreSql then + ExistingPsqlPage.Values[0] := ExistingPostgreSqlPsqlPath + else + ExistingPsqlPage.Values[0] := FindExistingPsql; +end; + +procedure CurPageChanged(CurPageID: Integer); +begin + if CurPageID = PortsPage.ID then + begin + if ExistingInventeraPostgreSql then + begin + PortsPage.Values[0] := IntToStr(ExistingPostgreSqlPort); + PortsPage.Edits[0].Enabled := False; + PortsPage.SubCaptionLabel.Caption := + 'Обнаружен PostgreSQL Inventera. Его фактический порт определён автоматически ' + + 'и не требует проверки на занятость.'; + end + else if IsAutomaticMode and SavedAutomaticStateAvailable and + (ExistingPostgreSqlPort > 0) then + begin + PortsPage.Values[0] := IntToStr(ExistingPostgreSqlPort); + PortsPage.Edits[0].Enabled := False; + PortsPage.SubCaptionLabel.Caption := + 'Продолжается предыдущая автоматическая установка. Сохранённый порт PostgreSQL ' + + 'нельзя изменять, чтобы не нарушить подключение к кластеру.'; + end + else + begin + PortsPage.Edits[0].Enabled := True; + PortsPage.SubCaptionLabel.Caption := + 'PostgreSQL и веб-приложение используют разные порты. ' + + 'Установщик проверит необходимые свободные порты.'; + end; + end; end; function ShouldSkipPage(PageID: Integer): Boolean; begin Result := False; + if ExistingInventeraPostgreSql and + ((PageID = PostgreSqlInstallPage.ID) or + (PageID = ExistingPsqlPage.ID)) then + begin + Result := True; + Exit; + end; + if IsAutomaticMode and ((PageID = PostgreSqlInstallPage.ID) or (PageID = AdminPage.ID) or @@ -335,6 +566,22 @@ var begin Result := False; + if (CurPageID = ModePage.ID) and IsAutomaticMode and + ExistingInventeraPostgreSql and + (not SavedAutomaticStateAvailable) then + begin + MsgBox( + 'Обнаружен PostgreSQL Inventera от предыдущей версии установщика, ' + + 'но пароль администратора для него не был сохранён.' + #13#10 + #13#10 + + 'Выберите ручной режим и укажите текущий пароль администратора PostgreSQL ' + + 'либо удалите прежнюю установку PostgreSQL вместе с её каталогом данных ' + + 'перед чистой автоматической установкой.', + mbError, + MB_OK + ); + Exit; + end; + if CurPageID = AdminPage.ID then begin if not IsValidIdentifier(Trim(AdminPage.Values[0])) then @@ -491,6 +738,27 @@ begin Exit; end; + if ExistingInventeraPostgreSql then + begin + WizardForm.PreparingLabel.Caption := + 'Запуск существующей службы PostgreSQL Inventera...'; + Exec( + ExpandConstant('{sys}\sc.exe'), + 'start InventeraPostgreSQL{#PostgreSqlMajor}', + '', + SW_HIDE, + ewWaitUntilTerminated, + IgnoreExitCode + ); + end; + + if IsAutomaticMode and (not SaveAutomaticInstallerState) then + begin + Result := + 'Не удалось сохранить служебные параметры PostgreSQL для повторного запуска установки.'; + Exit; + end; + if ShouldInstallPostgreSql then begin WizardForm.PreparingLabel.Caption := diff --git a/installer/initialize_inventera_database.ps1 b/installer/initialize_inventera_database.ps1 index 29f4df0..b1d8653 100644 --- a/installer/initialize_inventera_database.ps1 +++ b/installer/initialize_inventera_database.ps1 @@ -76,17 +76,23 @@ function Invoke-Psql { '--command', $Sql ) - if ($Capture) { - $output = & $PsqlPath @arguments 2>&1 - if ($LASTEXITCODE -ne 0) { - throw ($output -join [Environment]::NewLine) + $output = & $PsqlPath @arguments 2>&1 + $exitCode = $LASTEXITCODE + $outputText = ($output -join [Environment]::NewLine).Trim() + + if ($exitCode -ne 0) { + if ($outputText -eq '') { + $outputText = "psql завершился с кодом $exitCode." } - return ($output -join [Environment]::NewLine).Trim() + throw $outputText } - & $PsqlPath @arguments - if ($LASTEXITCODE -ne 0) { - throw "psql завершился с кодом $LASTEXITCODE." + if ($Capture) { + return $outputText + } + + if ($outputText -ne '') { + Write-Host $outputText } } @@ -152,9 +158,14 @@ $testArguments = @( '--set', 'ON_ERROR_STOP=1', '--command', 'SELECT 1;' ) -& $PsqlPath @testArguments | Out-Null -if ($LASTEXITCODE -ne 0) { - throw 'Не удалось проверить подключение с учётными данными приложения.' +$testOutput = & $PsqlPath @testArguments 2>&1 +$testExitCode = $LASTEXITCODE +if ($testExitCode -ne 0) { + $testOutputText = ($testOutput -join [Environment]::NewLine).Trim() + if ($testOutputText -eq '') { + $testOutputText = "psql завершился с кодом $testExitCode." + } + throw "Не удалось проверить подключение с учётными данными приложения. $testOutputText" } $configDirectory = Split-Path -Parent $ConfigPath