534 lines
17 KiB
Plaintext
534 lines
17 KiB
Plaintext
#define MyAppName "Inventera"
|
||
#define MyAppVersion "0.1.0-beta"
|
||
#define MyAppPublisher "Inventera"
|
||
#define MyAppExeName "Inventera.Server.exe"
|
||
#define PostgreSqlMajor "18"
|
||
#define PostgreSqlInstaller "installer\prerequisites\postgesql.exe"
|
||
|
||
[Setup]
|
||
AppId={{78BAF0DB-D841-49A7-954C-16DCA688B136}
|
||
AppName={#MyAppName}
|
||
AppVersion={#MyAppVersion}
|
||
AppVerName={#MyAppName} {#MyAppVersion}
|
||
AppPublisher={#MyAppPublisher}
|
||
DefaultDirName={autopf}\{#MyAppName}
|
||
DefaultGroupName={#MyAppName}
|
||
PrivilegesRequired=admin
|
||
OutputDir=dist
|
||
OutputBaseFilename=Inventera
|
||
SetupIconFile=picture\logo\main_logo.ico
|
||
UninstallDisplayIcon={app}\{#MyAppExeName}
|
||
Compression=lzma2/ultra64
|
||
SolidCompression=yes
|
||
WizardStyle=modern
|
||
ArchitecturesAllowed=x64
|
||
ArchitecturesInstallIn64BitMode=x64
|
||
CloseApplications=yes
|
||
RestartApplications=no
|
||
DisableProgramGroupPage=yes
|
||
UsePreviousAppDir=yes
|
||
|
||
[Languages]
|
||
Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl"
|
||
|
||
[Files]
|
||
Source: "{#PostgreSqlInstaller}"; Flags: dontcopy noencryption nocompression
|
||
Source: "installer\initialize_inventera_database.ps1"; Flags: dontcopy noencryption
|
||
Source: "dist\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
||
|
||
[Icons]
|
||
Name: "{group}\Inventera"; Filename: "http://127.0.0.1:{code:GetApplicationPort}"; IconFilename: "{app}\{#MyAppExeName}"
|
||
Name: "{commondesktop}\Inventera"; Filename: "http://127.0.0.1:{code:GetApplicationPort}"; IconFilename: "{app}\{#MyAppExeName}"
|
||
Name: "{commonstartup}\Inventera Server"; Filename: "{app}\{#MyAppExeName}"; WorkingDir: "{app}"; IconFilename: "{app}\{#MyAppExeName}"
|
||
|
||
[Run]
|
||
Filename: "{sys}\netsh.exe"; Parameters: "advfirewall firewall delete rule name=""Inventera Web"""; Flags: runhidden waituntilterminated
|
||
Filename: "{sys}\netsh.exe"; Parameters: "advfirewall firewall add rule name=""Inventera Web"" dir=in action=allow protocol=TCP localport={code:GetApplicationPort}"; Flags: runhidden waituntilterminated
|
||
Filename: "{app}\{#MyAppExeName}"; WorkingDir: "{app}"; Flags: nowait runhidden; StatusMsg: "Запуск сервера Inventera..."
|
||
Filename: "http://127.0.0.1:{code:GetApplicationPort}"; Description: "Открыть Inventera"; Flags: shellexec nowait postinstall skipifsilent
|
||
|
||
[UninstallRun]
|
||
Filename: "{sys}\taskkill.exe"; Parameters: "/F /IM ""{#MyAppExeName}"""; Flags: runhidden waituntilterminated; RunOnceId: "StopInventera"
|
||
Filename: "{sys}\netsh.exe"; Parameters: "advfirewall firewall delete rule name=""Inventera Web"""; Flags: runhidden waituntilterminated; RunOnceId: "RemoveInventeraFirewallRule"
|
||
|
||
[Code]
|
||
var
|
||
ModePage: TInputOptionWizardPage;
|
||
PostgreSqlInstallPage: TInputOptionWizardPage;
|
||
AdminPage: TInputQueryWizardPage;
|
||
PortsPage: TInputQueryWizardPage;
|
||
DatabasePage: TInputQueryWizardPage;
|
||
ExistingPsqlPage: TInputFileWizardPage;
|
||
AutomaticAdminPassword: String;
|
||
|
||
function IsAutomaticMode: Boolean;
|
||
begin
|
||
Result := ModePage.SelectedValueIndex = 0;
|
||
end;
|
||
|
||
function ShouldInstallPostgreSql: Boolean;
|
||
begin
|
||
Result := IsAutomaticMode or (PostgreSqlInstallPage.SelectedValueIndex = 0);
|
||
end;
|
||
|
||
function GetPostgreSqlPort(Param: String): String;
|
||
begin
|
||
Result := Trim(PortsPage.Values[0]);
|
||
end;
|
||
|
||
function GetApplicationPort(Param: String): String;
|
||
begin
|
||
Result := Trim(PortsPage.Values[1]);
|
||
end;
|
||
|
||
function GetDatabaseName: String;
|
||
begin
|
||
if IsAutomaticMode then
|
||
Result := 'Inv_Web'
|
||
else
|
||
Result := Trim(DatabasePage.Values[0]);
|
||
end;
|
||
|
||
function GetDatabaseUser: String;
|
||
begin
|
||
if IsAutomaticMode then
|
||
Result := 'invwebuser'
|
||
else
|
||
Result := Trim(DatabasePage.Values[1]);
|
||
end;
|
||
|
||
function GetDatabasePassword: String;
|
||
begin
|
||
if IsAutomaticMode then
|
||
Result := 'invwebpassword'
|
||
else
|
||
Result := DatabasePage.Values[2];
|
||
end;
|
||
|
||
function GetAdminUser: String;
|
||
begin
|
||
if IsAutomaticMode then
|
||
Result := 'postgres'
|
||
else
|
||
Result := Trim(AdminPage.Values[0]);
|
||
end;
|
||
|
||
function GetAdminPassword: String;
|
||
begin
|
||
if IsAutomaticMode then
|
||
Result := AutomaticAdminPassword
|
||
else
|
||
Result := AdminPage.Values[1];
|
||
end;
|
||
|
||
function QuoteArgument(const Value: String): String;
|
||
begin
|
||
Result := '"' + Value + '"';
|
||
end;
|
||
|
||
function GenerateAutomaticPassword: String;
|
||
var
|
||
I: Integer;
|
||
Characters: String;
|
||
begin
|
||
Characters := 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789!@#_-';
|
||
Result := 'Iv!';
|
||
for I := 1 to 29 do
|
||
Result := Result + Characters[Random(Length(Characters)) + 1];
|
||
end;
|
||
|
||
function FindExistingPsql: String;
|
||
var
|
||
Candidate: String;
|
||
begin
|
||
Result := '';
|
||
Candidate := ExpandConstant('{autopf}\PostgreSQL\18\bin\psql.exe');
|
||
if FileExists(Candidate) then begin Result := Candidate; Exit; end;
|
||
Candidate := ExpandConstant('{autopf}\PostgreSQL\17\bin\psql.exe');
|
||
if FileExists(Candidate) then begin Result := Candidate; Exit; end;
|
||
Candidate := ExpandConstant('{autopf}\PostgreSQL\16\bin\psql.exe');
|
||
if FileExists(Candidate) then begin Result := Candidate; Exit; end;
|
||
Candidate := ExpandConstant('{autopf}\PostgreSQL\15\bin\psql.exe');
|
||
if FileExists(Candidate) then begin Result := Candidate; Exit; end;
|
||
Candidate := ExpandConstant('{autopf}\PostgreSQL\14\bin\psql.exe');
|
||
if FileExists(Candidate) then begin Result := Candidate; Exit; end;
|
||
Candidate := ExpandConstant('{autopf}\PostgreSQL\13\bin\psql.exe');
|
||
if FileExists(Candidate) then begin Result := Candidate; Exit; end;
|
||
end;
|
||
|
||
function GetPsqlPath: String;
|
||
begin
|
||
if ShouldInstallPostgreSql then
|
||
Result := ExpandConstant('{autopf}\PostgreSQL\{#PostgreSqlMajor}\bin\psql.exe')
|
||
else
|
||
Result := Trim(ExistingPsqlPage.Values[0]);
|
||
end;
|
||
|
||
function IsValidPort(const Value: String; var PortNumber: Integer): Boolean;
|
||
begin
|
||
Result := False;
|
||
PortNumber := 0;
|
||
if Trim(Value) = '' then
|
||
Exit;
|
||
|
||
try
|
||
PortNumber := StrToInt(Trim(Value));
|
||
except
|
||
PortNumber := 0;
|
||
Exit;
|
||
end;
|
||
|
||
Result := (PortNumber >= 1) and (PortNumber <= 65535);
|
||
end;
|
||
|
||
function IsValidIdentifier(const Value: String): Boolean;
|
||
var
|
||
I: Integer;
|
||
AllowedFirst: String;
|
||
AllowedRest: String;
|
||
begin
|
||
Result := False;
|
||
if Value = '' then
|
||
Exit;
|
||
|
||
AllowedFirst := 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_';
|
||
AllowedRest := AllowedFirst + '0123456789';
|
||
if Pos(Value[1], AllowedFirst) = 0 then
|
||
Exit;
|
||
|
||
for I := 2 to Length(Value) do
|
||
if Pos(Value[I], AllowedRest) = 0 then
|
||
Exit;
|
||
|
||
Result := True;
|
||
end;
|
||
|
||
function IsSafePassword(const Value: String): Boolean;
|
||
begin
|
||
Result := (Value <> '') and
|
||
(Pos('"', Value) = 0) and
|
||
(Pos(#13, Value) = 0) and
|
||
(Pos(#10, Value) = 0);
|
||
end;
|
||
|
||
function IsTcpPortAvailable(PortNumber: Integer): Boolean;
|
||
var
|
||
PowerShellPath: String;
|
||
Parameters: String;
|
||
ExitCode: Integer;
|
||
begin
|
||
PowerShellPath := ExpandConstant('{sys}\WindowsPowerShell\v1.0\powershell.exe');
|
||
Parameters :=
|
||
'-NoProfile -NonInteractive -ExecutionPolicy Bypass -Command ' +
|
||
'"$listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, ' +
|
||
IntToStr(PortNumber) +
|
||
'); try { $listener.Start(); $listener.Stop(); exit 0 } catch { exit 1 }"';
|
||
|
||
Result := Exec(
|
||
PowerShellPath,
|
||
Parameters,
|
||
'',
|
||
SW_HIDE,
|
||
ewWaitUntilTerminated,
|
||
ExitCode
|
||
) and (ExitCode = 0);
|
||
end;
|
||
|
||
procedure InitializeWizard;
|
||
begin
|
||
AutomaticAdminPassword := GenerateAutomaticPassword;
|
||
|
||
ModePage := CreateInputOptionPage(
|
||
wpWelcome,
|
||
'Режим установки',
|
||
'Выберите способ настройки Inventera',
|
||
'Автоматический режим использует готовые параметры базы. Ручной режим позволяет изменить их.',
|
||
True,
|
||
False
|
||
);
|
||
ModePage.Add('Автоматический');
|
||
ModePage.Add('Ручной');
|
||
ModePage.SelectedValueIndex := 0;
|
||
|
||
PostgreSqlInstallPage := CreateInputOptionPage(
|
||
ModePage.ID,
|
||
'Установка PostgreSQL',
|
||
'Нужно ли установить PostgreSQL?',
|
||
'Выберите «Нет», только если PostgreSQL уже установлен и доступен на этом компьютере.',
|
||
True,
|
||
False
|
||
);
|
||
PostgreSqlInstallPage.Add('Да, установить PostgreSQL из комплекта Inventera');
|
||
PostgreSqlInstallPage.Add('Нет, использовать существующий PostgreSQL');
|
||
PostgreSqlInstallPage.SelectedValueIndex := 0;
|
||
|
||
AdminPage := CreateInputQueryPage(
|
||
PostgreSqlInstallPage.ID,
|
||
'Администратор PostgreSQL',
|
||
'Укажите учётную запись администратора',
|
||
'Эта учётная запись нужна только для создания базы и пользователя Inventera.'
|
||
);
|
||
AdminPage.Add('Логин администратора PostgreSQL:', False);
|
||
AdminPage.Add('Пароль администратора PostgreSQL:', True);
|
||
AdminPage.Values[0] := 'postgres';
|
||
|
||
PortsPage := CreateInputQueryPage(
|
||
AdminPage.ID,
|
||
'Сетевые порты',
|
||
'Укажите порты PostgreSQL и Inventera',
|
||
'PostgreSQL и веб-приложение используют разные порты. Установщик проверит, что нужные порты свободны.'
|
||
);
|
||
PortsPage.Add('Порт PostgreSQL:', False);
|
||
PortsPage.Add('Порт веб-приложения Inventera:', False);
|
||
PortsPage.Values[0] := '5432';
|
||
PortsPage.Values[1] := '5050';
|
||
|
||
DatabasePage := CreateInputQueryPage(
|
||
PortsPage.ID,
|
||
'База данных Inventera',
|
||
'Укажите имя базы и учётную запись приложения',
|
||
'Предлагаемые значения уже заполнены и могут быть изменены.'
|
||
);
|
||
DatabasePage.Add('Имя базы данных:', False);
|
||
DatabasePage.Add('Логин пользователя базы:', False);
|
||
DatabasePage.Add('Пароль пользователя базы:', True);
|
||
DatabasePage.Values[0] := 'Inv_Web';
|
||
DatabasePage.Values[1] := 'invwebuser';
|
||
DatabasePage.Values[2] := 'invwebpassword';
|
||
|
||
ExistingPsqlPage := CreateInputFilePage(
|
||
DatabasePage.ID,
|
||
'Расположение PostgreSQL',
|
||
'Укажите psql.exe существующей установки',
|
||
'Файл psql.exe обычно находится в папке C:\Program Files\PostgreSQL\<версия>\bin.'
|
||
);
|
||
ExistingPsqlPage.Add('Путь к psql.exe:', 'Исполняемые файлы|*.exe|Все файлы|*.*', '.exe');
|
||
ExistingPsqlPage.Values[0] := FindExistingPsql;
|
||
end;
|
||
|
||
function ShouldSkipPage(PageID: Integer): Boolean;
|
||
begin
|
||
Result := False;
|
||
|
||
if IsAutomaticMode and
|
||
((PageID = PostgreSqlInstallPage.ID) or
|
||
(PageID = AdminPage.ID) or
|
||
(PageID = DatabasePage.ID) or
|
||
(PageID = ExistingPsqlPage.ID)) then
|
||
begin
|
||
Result := True;
|
||
Exit;
|
||
end;
|
||
|
||
if (not IsAutomaticMode) and
|
||
(PageID = ExistingPsqlPage.ID) and
|
||
ShouldInstallPostgreSql then
|
||
begin
|
||
Result := True;
|
||
end;
|
||
end;
|
||
|
||
function NextButtonClick(CurPageID: Integer): Boolean;
|
||
var
|
||
PostgreSqlPort: Integer;
|
||
ApplicationPort: Integer;
|
||
begin
|
||
Result := False;
|
||
|
||
if CurPageID = AdminPage.ID then
|
||
begin
|
||
if not IsValidIdentifier(Trim(AdminPage.Values[0])) then
|
||
begin
|
||
MsgBox('Введите корректный логин администратора PostgreSQL.', mbError, MB_OK);
|
||
Exit;
|
||
end;
|
||
if not IsSafePassword(AdminPage.Values[1]) then
|
||
begin
|
||
MsgBox('Введите пароль администратора. Двойные кавычки и переносы строк недопустимы.', mbError, MB_OK);
|
||
Exit;
|
||
end;
|
||
end;
|
||
|
||
if CurPageID = PortsPage.ID then
|
||
begin
|
||
if not IsValidPort(PortsPage.Values[0], PostgreSqlPort) then
|
||
begin
|
||
MsgBox('Порт PostgreSQL должен быть числом от 1 до 65535.', mbError, MB_OK);
|
||
Exit;
|
||
end;
|
||
if not IsValidPort(PortsPage.Values[1], ApplicationPort) then
|
||
begin
|
||
MsgBox('Порт Inventera должен быть числом от 1 до 65535.', mbError, MB_OK);
|
||
Exit;
|
||
end;
|
||
if PostgreSqlPort = ApplicationPort then
|
||
begin
|
||
MsgBox('PostgreSQL и Inventera не могут использовать один и тот же порт.', mbError, MB_OK);
|
||
Exit;
|
||
end;
|
||
if ShouldInstallPostgreSql and (not IsTcpPortAvailable(PostgreSqlPort)) then
|
||
begin
|
||
MsgBox(
|
||
'Порт PostgreSQL ' + IntToStr(PostgreSqlPort) + ' уже занят. Укажите другой порт.',
|
||
mbError,
|
||
MB_OK
|
||
);
|
||
Exit;
|
||
end;
|
||
if not IsTcpPortAvailable(ApplicationPort) then
|
||
begin
|
||
MsgBox(
|
||
'Порт Inventera ' + IntToStr(ApplicationPort) + ' уже занят. Укажите другой порт.',
|
||
mbError,
|
||
MB_OK
|
||
);
|
||
Exit;
|
||
end;
|
||
end;
|
||
|
||
if CurPageID = DatabasePage.ID then
|
||
begin
|
||
if not IsValidIdentifier(Trim(DatabasePage.Values[0])) then
|
||
begin
|
||
MsgBox('Имя базы может содержать только латинские буквы, цифры и знак подчёркивания.', mbError, MB_OK);
|
||
Exit;
|
||
end;
|
||
if not IsValidIdentifier(Trim(DatabasePage.Values[1])) then
|
||
begin
|
||
MsgBox('Логин базы может содержать только латинские буквы, цифры и знак подчёркивания.', mbError, MB_OK);
|
||
Exit;
|
||
end;
|
||
if not IsSafePassword(DatabasePage.Values[2]) then
|
||
begin
|
||
MsgBox('Введите пароль базы. Двойные кавычки и переносы строк недопустимы.', mbError, MB_OK);
|
||
Exit;
|
||
end;
|
||
end;
|
||
|
||
if CurPageID = ExistingPsqlPage.ID then
|
||
begin
|
||
if not FileExists(Trim(ExistingPsqlPage.Values[0])) then
|
||
begin
|
||
MsgBox('Укажите существующий файл psql.exe.', mbError, MB_OK);
|
||
Exit;
|
||
end;
|
||
end;
|
||
|
||
Result := True;
|
||
end;
|
||
|
||
function PostgreSqlInstallParameters: String;
|
||
var
|
||
Prefix: String;
|
||
begin
|
||
Prefix := ExpandConstant('{autopf}\PostgreSQL\{#PostgreSqlMajor}');
|
||
Result :=
|
||
'--mode unattended --unattendedmodeui minimal ' +
|
||
'--enable-components server,commandlinetools ' +
|
||
'--prefix ' + QuoteArgument(Prefix) + ' ' +
|
||
'--datadir ' + QuoteArgument(Prefix + '\data') + ' ' +
|
||
'--serverport ' + GetPostgreSqlPort('') + ' ' +
|
||
'--servicename InventeraPostgreSQL{#PostgreSqlMajor} ' +
|
||
'--superaccount ' + QuoteArgument(GetAdminUser) + ' ' +
|
||
'--superpassword ' + QuoteArgument(GetAdminPassword) + ' ' +
|
||
'--servicepassword ' + QuoteArgument(GetAdminPassword) + ' ' +
|
||
'--create_shortcuts 0';
|
||
end;
|
||
|
||
function DatabaseInitializationParameters: String;
|
||
begin
|
||
Result :=
|
||
'-NoProfile -NonInteractive -ExecutionPolicy Bypass -File ' +
|
||
QuoteArgument(ExpandConstant('{tmp}\initialize_inventera_database.ps1')) + ' ' +
|
||
'-PsqlPath ' + QuoteArgument(GetPsqlPath) + ' ' +
|
||
'-AdminUser ' + QuoteArgument(GetAdminUser) + ' ' +
|
||
'-AdminPassword ' + QuoteArgument(GetAdminPassword) + ' ' +
|
||
'-PostgreSqlPort ' + GetPostgreSqlPort('') + ' ' +
|
||
'-DatabaseName ' + QuoteArgument(GetDatabaseName) + ' ' +
|
||
'-DatabaseUser ' + QuoteArgument(GetDatabaseUser) + ' ' +
|
||
'-DatabasePassword ' + QuoteArgument(GetDatabasePassword) + ' ' +
|
||
'-ApplicationPort ' + GetApplicationPort('') + ' ' +
|
||
'-ConfigPath ' + QuoteArgument(ExpandConstant('{commonappdata}\Inventera\config.json'));
|
||
end;
|
||
|
||
function PrepareToInstall(var NeedsRestart: Boolean): String;
|
||
var
|
||
ExitCode: Integer;
|
||
IgnoreExitCode: Integer;
|
||
Success: Boolean;
|
||
PowerShellPath: String;
|
||
PostgreSqlPort: Integer;
|
||
ApplicationPort: Integer;
|
||
begin
|
||
Result := '';
|
||
NeedsRestart := False;
|
||
|
||
Exec(
|
||
ExpandConstant('{sys}\taskkill.exe'),
|
||
'/F /IM "{#MyAppExeName}"',
|
||
'',
|
||
SW_HIDE,
|
||
ewWaitUntilTerminated,
|
||
IgnoreExitCode
|
||
);
|
||
|
||
if not IsValidPort(GetPostgreSqlPort(''), PostgreSqlPort) or
|
||
not IsValidPort(GetApplicationPort(''), ApplicationPort) then
|
||
begin
|
||
Result := 'Указаны некорректные сетевые порты.';
|
||
Exit;
|
||
end;
|
||
|
||
if ShouldInstallPostgreSql and (not IsTcpPortAvailable(PostgreSqlPort)) then
|
||
begin
|
||
Result := 'Порт PostgreSQL ' + IntToStr(PostgreSqlPort) + ' уже занят.';
|
||
Exit;
|
||
end;
|
||
|
||
if not IsTcpPortAvailable(ApplicationPort) then
|
||
begin
|
||
Result := 'Порт Inventera ' + IntToStr(ApplicationPort) + ' уже занят.';
|
||
Exit;
|
||
end;
|
||
|
||
if ShouldInstallPostgreSql then
|
||
begin
|
||
WizardForm.PreparingLabel.Caption :=
|
||
'Установка PostgreSQL. Это может занять несколько минут, пожалуйста, подождите...';
|
||
ExtractTemporaryFile(ExtractFileName(ExpandConstant('{#PostgreSqlInstaller}')));
|
||
Success := Exec(
|
||
ExpandConstant('{tmp}\postgesql.exe'),
|
||
PostgreSqlInstallParameters,
|
||
'',
|
||
SW_SHOW,
|
||
ewWaitUntilTerminated,
|
||
ExitCode
|
||
);
|
||
if (not Success) or (ExitCode <> 0) then
|
||
begin
|
||
Result := 'Не удалось установить PostgreSQL. Код ошибки: ' + IntToStr(ExitCode);
|
||
Exit;
|
||
end;
|
||
end;
|
||
|
||
WizardForm.PreparingLabel.Caption :=
|
||
'Создание базы данных и сохранение настроек Inventera...';
|
||
ExtractTemporaryFile('initialize_inventera_database.ps1');
|
||
PowerShellPath := ExpandConstant('{sys}\WindowsPowerShell\v1.0\powershell.exe');
|
||
Success := Exec(
|
||
PowerShellPath,
|
||
DatabaseInitializationParameters,
|
||
'',
|
||
SW_HIDE,
|
||
ewWaitUntilTerminated,
|
||
ExitCode
|
||
);
|
||
if (not Success) or (ExitCode <> 0) then
|
||
begin
|
||
Result :=
|
||
'Не удалось создать базу данных или сохранить настройки Inventera. Код ошибки: ' +
|
||
IntToStr(ExitCode);
|
||
Exit;
|
||
end;
|
||
end;
|