Lumi/companion/installer/Lumi.Companion.iss
2026-07-24 22:12:39 +02:00

242 lines
7.6 KiB
Plaintext

#ifndef AppVersion
#define AppVersion "0.2.0"
#endif
#ifndef SourceRoot
#error SourceRoot must point at the self-contained Companion publish directory.
#endif
#ifndef OutputRoot
#error OutputRoot must point at the installer output directory.
#endif
[Setup]
AppId={{EDEB45E4-41CB-4D7A-A479-4073A1A08B93}
AppName=Lumi Companion
AppVersion={#AppVersion}
AppVerName=Lumi Companion {#AppVersion}
AppPublisher=OokamiKunTV
AppCopyright=Copyright (c) 2026 OokamiKunTV
DefaultDirName={localappdata}\Programs\Lumi Companion
DefaultGroupName=Lumi Companion
DisableProgramGroupPage=yes
PrivilegesRequired=lowest
ArchitecturesAllowed=x64compatible
ArchitecturesInstallIn64BitMode=x64compatible
OutputDir={#OutputRoot}
OutputBaseFilename=Lumi.Companion-Setup
Compression=lzma2/ultra64
SolidCompression=yes
WizardStyle=modern
SetupLogging=yes
CloseApplications=yes
CloseApplicationsFilter=Lumi.Companion.App.exe
RestartApplications=no
UninstallDisplayName=Lumi Companion
UninstallDisplayIcon={app}\Lumi.Companion.App.exe
VersionInfoCompany=OokamiKunTV
VersionInfoCopyright=Copyright (c) 2026 OokamiKunTV
VersionInfoDescription=Lumi Companion installer
VersionInfoProductName=Lumi Companion
VersionInfoProductTextVersion={#AppVersion}
LicenseFile={#SourceRoot}\legal\LUMI-COMPANION-LICENCE.txt
InfoBeforeFile={#SourceRoot}\legal\PRIVACY-NOTICE.txt
InfoAfterFile={#SourceRoot}\legal\THIRD-PARTY-NOTICES.txt
[Files]
Source: "{#SourceRoot}\Lumi.Companion.App.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "{#SourceRoot}\components\*"; DestDir: "{app}\components"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "{#SourceRoot}\legal\*"; DestDir: "{app}\legal"; Flags: ignoreversion recursesubdirs createallsubdirs
[Icons]
Name: "{autoprograms}\Lumi Companion"; Filename: "{app}\Lumi.Companion.App.exe"
Name: "{autoprograms}\Lumi Companion - Legal and Privacy"; Filename: "{app}\legal\LEGAL-README.txt"
[Run]
Filename: "{app}\Lumi.Companion.App.exe"; Description: "Open Lumi Companion"; Flags: nowait postinstall skipifsilent
[Code]
var
PairingPackagePath: String;
PairingHost: String;
PairingOperatorName: String;
PairingOperatorContact: String;
PairingPrivacyUrl: String;
HostAcknowledgementPage: TInputOptionWizardPage;
function LocatePairingPackage: String;
var
FindRec: TFindRec;
begin
Result := '';
if FindFirst(ExpandConstant('{src}\*.lumi-pairing.json'), FindRec) then
begin
try
Result := AddBackslash(ExpandConstant('{src}')) + FindRec.Name;
finally
FindClose(FindRec);
end;
end;
end;
function JsonStringValue(const Json: String; const Key: String): String;
var
Marker: String;
Tail: String;
MarkerPos: Integer;
ColonPos: Integer;
EndQuotePos: Integer;
begin
Result := '';
Marker := '"' + Key + '"';
MarkerPos := Pos(Marker, Json);
if MarkerPos = 0 then
Exit;
Tail := Copy(Json, MarkerPos + Length(Marker), Length(Json));
ColonPos := Pos(':', Tail);
if ColonPos = 0 then
Exit;
Tail := Trim(Copy(Tail, ColonPos + 1, Length(Tail)));
if (Length(Tail) < 2) or (Tail[1] <> '"') then
Exit;
Tail := Copy(Tail, 2, Length(Tail));
EndQuotePos := Pos('"', Tail);
if EndQuotePos = 0 then
Exit;
Result := Copy(Tail, 1, EndQuotePos - 1);
end;
function LoadPairingJson(const PackagePath: String; var Json: String): Boolean;
var
Lines: TArrayOfString;
Index: Integer;
begin
Result := False;
Json := '';
if (PackagePath = '') or not LoadStringsFromFile(PackagePath, Lines) then
Exit;
for Index := 0 to GetArrayLength(Lines) - 1 do
begin
if Index > 0 then
Json := Json + #10;
Json := Json + Lines[Index];
end;
Result := True;
end;
function ReadPairingHost(const Json: String): String;
var
Candidate: String;
LowerCandidate: String;
begin
Result := '';
Candidate := JsonStringValue(Json, 'host');
LowerCandidate := Lowercase(Candidate);
if ((Pos('https://', LowerCandidate) = 1) or (Pos('http://', LowerCandidate) = 1)) and
(Length(Candidate) <= 500) then
Result := Candidate;
end;
procedure InitializeWizard;
var
HostDisplay: String;
Explanation: String;
OperatorDetails: String;
Json: String;
begin
PairingPackagePath := LocatePairingPackage;
if LoadPairingJson(PairingPackagePath, Json) then
begin
PairingHost := ReadPairingHost(Json);
PairingOperatorName := Copy(JsonStringValue(Json, 'operator_name'), 1, 200);
PairingOperatorContact := Copy(JsonStringValue(Json, 'operator_contact'), 1, 300);
PairingPrivacyUrl := Copy(JsonStringValue(Json, 'privacy_url'), 1, 500);
end;
if PairingHost <> '' then
HostDisplay := PairingHost
else
HostDisplay := 'No paired Lumi host was identified beside this installer.';
OperatorDetails := '';
if PairingOperatorName <> '' then
OperatorDetails := OperatorDetails + #13#10 + 'Operator name: ' + PairingOperatorName;
if PairingOperatorContact <> '' then
OperatorDetails := OperatorDetails + #13#10 + 'Operator contact: ' + PairingOperatorContact;
if PairingPrivacyUrl <> '' then
OperatorDetails := OperatorDetails + #13#10 + 'Privacy information: ' + PairingPrivacyUrl;
Explanation :=
'This copy of Lumi Companion is intended to connect to:' + #13#10#13#10 +
HostDisplay + OperatorDetails + #13#10#13#10 +
'The person or organisation controlling that Lumi installation is the Host Operator. ' +
'The Host Operator is responsible for the Hosted Service it controls, including server configuration, ' +
'access, server-side data processing, retention, integrations, and required user notices. ' +
'OokamiKunTV is the software developer and is not automatically the operator of an independently hosted Lumi installation. ' +
'Responsibility always follows the actual facts and applicable law.';
HostAcknowledgementPage := CreateInputOptionPage(
wpInfoBefore,
'Lumi host and data responsibility',
'Review the host that will receive Companion data.',
Explanation,
False,
False);
HostAcknowledgementPage.Add(
'I understand which Lumi host this Companion will use and that the Host Operator controls its server-side service and processing.');
end;
function NextButtonClick(CurPageID: Integer): Boolean;
begin
Result := True;
{ A silent deployment is an administrator/operator action and has no wizard
UI in which an interactive acknowledgement can be collected. }
if WizardSilent then
Exit;
if (HostAcknowledgementPage <> nil) and (CurPageID = HostAcknowledgementPage.ID) and
not HostAcknowledgementPage.Values[0] then
begin
MsgBox(
'Confirm that you understand the host and operator responsibility before continuing.',
mbInformation,
MB_OK);
Result := False;
end;
end;
procedure ImportPairingPackage;
var
SourcePath: String;
TargetPath: String;
begin
{ Portable and installed builds share the same DPAPI credential directory. Do
not create another authorized device merely because an existing user moves
the executable into its durable installed location. }
if FileExists(ExpandConstant('{localappdata}\Lumi\Companion\device.credential')) then
Exit;
SourcePath := PairingPackagePath;
if SourcePath = '' then
SourcePath := LocatePairingPackage;
if SourcePath = '' then
Exit;
TargetPath := AddBackslash(ExpandConstant('{app}')) + ExtractFileName(SourcePath);
if not CopyFile(SourcePath, TargetPath, False) then
MsgBox(
'Lumi Companion was installed, but its one-time pairing package could not be imported. ' +
'Open Companion and choose the pairing file manually.',
mbError,
MB_OK);
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssPostInstall then
ImportPairingPackage;
end;