Files
theta-agent/installer/windows/installer.iss
T
wmantly 12d55a4454 feat(icons): clean tray badge set + Start menu/installer icon
New cmd/icon-gen renders the four state badges (Red/Yellow/Green/Blue) as a
rounded-square badge with a subtle vertical gradient and a crisp white theta,
256px with 4x4 supersampling. The tray embeds these (icons.go, generated) and
builds a proper multi-size Windows ICO (16..256) via exact box filtering --
replacing the old flat 48px circle and nearest-neighbour scaling.

The installer now bundles theta-agent.ico (multi-size, Blue badge) and uses it
for the Start menu 'Theta Agent Tray' shortcut, the setup.exe icon
(SetupIconFile), and the uninstaller display icon. Dead duplicate icon arrays
in the root package (tray_icons.go) removed.
2026-08-10 18:49:30 -07:00

306 lines
12 KiB
Plaintext

; Theta Agent ??? Windows installer (Inno Setup 6.4+)
;
; Fully offline: bundles the agent, tray, session helper, the official WireGuard
; for Windows client, the OpenCredential credential provider, and the VC++ v14
; runtime. Nothing on the target machine requires internet access.
;
; Usage:
; iscc "/DMyAppVersion=2.2.0" installer\windows\installer.iss
; theta-agent-2.2.0-windows-amd64-setup.exe /SILENT ^
; /SERVER_URL=https://sso.example.com /JOIN_KEY=tjk_...
;
; The version is passed in by scripts/setup-build-env.ps1 (derived from the
; git tag). The default below exists only so a bare `iscc installer.iss` call
; still compiles; it should never be the version in a real build.
;
; Interactively, a wizard page asks for the Theta Directory URL and a join key
; (with a button that opens the Theta Directory's Directory -> Install Agent page
; to mint one). In silent mode, /SERVER_URL, /JOIN_KEY, /AUTH_TOKEN, /PUBLIC_KEY
; and /B64_CONFIG (base64 of a full agent.yml) drive the same result. The values
; are written into agent.yml so the installed service enrolls on first start.
#ifndef MyAppVersion
#define MyAppVersion "0.0.0-dev"
#endif
#define MyAppName "Theta Agent"
#define MyAppPublisher "Theta42"
#define MyAppExeName "theta-agent-windows-amd64.exe"
#define AgentDir "..\..\dist"
#define VendorDir "vendor"
[Setup]
AppId={{E2F64E2C-7A2B-4F4D-9E8C-9C0D0E9F3A21}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppPublisher={#MyAppPublisher}
VersionInfoVersion={#MyAppVersion}
DefaultDirName={autopf}\Theta42
DefaultGroupName=Theta42
DisableProgramGroupPage=yes
PrivilegesRequired=admin
ArchitecturesAllowed=x64compatible
ArchitecturesInstallIn64BitMode=x64compatible
OutputDir={#AgentDir}
OutputBaseFilename=theta-agent-{#MyAppVersion}-windows-amd64-setup
SetupIconFile=theta-agent.ico
Compression=lzma2
SolidCompression=yes
WizardStyle=modern
UninstallDisplayName={#MyAppName}
UninstallDisplayIcon={app}\theta-agent.ico
CloseApplications=no
MinVersion=10.0.17763
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Dirs]
; The daemon (SYSTEM) and the tray (logged-in user) share %ProgramData%\Theta42
; for agent.yml, the tray IPC socket and the WireGuard config. Users need write
; access for the socket; the agent.yml ACL is tightened by the code.
Name: "{commonappdata}\Theta42"; Permissions: users-modify
Name: "{app}\vendor"
[Files]
Source: "{#AgentDir}\theta-agent-windows-amd64.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "{#AgentDir}\theta-agent-tray-windows-amd64.exe"; DestDir: "{app}\tray"; Flags: ignoreversion
Source: "{#AgentDir}\theta-agent-helper-windows-amd64.exe"; DestDir: "{app}"; Flags: ignoreversion
; WireGuard for Windows ??? official, vendor-signed MSI. Installs offline (the
; driver is signed; no signature phone-home).
Source: "{#VendorDir}\wireguard-amd64-0.5.3.msi"; DestDir: "{app}\vendor"; Flags: ignoreversion
; Product icon (Start menu shortcut, uninstaller display icon). Generated by
; cmd/icon-gen from the same badge the tray uses.
Source: "theta-agent.ico"; DestDir: "{app}"; Flags: ignoreversion
; OpenCredential credential provider installer (BSD-3 pGina fork) + the VC++
; runtime it needs. Both install silently at [Run].
Source: "{#VendorDir}\OpenCredentialInstaller-1.0.0.0.exe"; DestDir: "{app}\vendor"; Flags: ignoreversion
Source: "{#VendorDir}\vc_redist.x64.exe"; DestDir: "{app}\vendor"; Flags: ignoreversion
[Icons]
Name: "{group}\Theta Agent Tray"; Filename: "{app}\tray\theta-agent-tray-windows-amd64.exe"; IconFilename: "{app}\theta-agent.ico"; Comment: "Theta Agent status tray"
Name: "{group}\Open Agent Config"; Filename: "notepad.exe"; Parameters: "{commonappdata}\Theta42\agent.yml"; Comment: "Open the agent configuration file"
Name: "{group}\Uninstall Theta Agent"; Filename: "{uninstallexe}"; IconFilename: "{app}\theta-agent.ico"
[Registry]
; Start the tray for every interactive logon.
Root: HKLM; Subkey: "Software\Microsoft\Windows\CurrentVersion\Run"; ValueType: string; ValueName: "ThetaAgentTray"; ValueData: "{app}\tray\theta-agent-tray-windows-amd64.exe"; Flags: uninsdeletevalue
[Run]
; VC++ v14 runtime (OpenCredential native deps).
Filename: "{app}\vendor\vc_redist.x64.exe"; Parameters: "/install /quiet /norestart"; StatusMsg: "Installing VC++ runtime..."; Flags: runhidden waituntilterminated
; OpenCredential credential provider ??? must be registered before logon.
Filename: "{app}\vendor\OpenCredentialInstaller-1.0.0.0.exe"; Parameters: "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"; StatusMsg: "Installing OpenCredential credential provider..."; Flags: runhidden waituntilterminated
; WireGuard for Windows client.
Filename: "msiexec.exe"; Parameters: "/i ""{app}\vendor\wireguard-amd64-0.5.3.msi"" /qn /norestart"; StatusMsg: "Installing WireGuard client..."; Flags: runhidden waituntilterminated
; The WireGuard client launches its UI at the end of the MSI; close it ??? the
; tunnel is managed by the agent (wireguard.exe /installtunnelservice).
Filename: "taskkill.exe"; Parameters: "/f /im wireguard.exe"; Flags: runhidden
; Register the agent as a SYSTEM auto-start service.
Filename: "{app}\{#MyAppExeName}"; Parameters: "install-service"; StatusMsg: "Registering theta-agent service..."; Flags: runhidden waituntilterminated
; Show the tray right away, in silent and interactive installs alike (a silent
; install is the common path from the Directory's install command, and the tray
; should appear immediately there too, not only at the next logon).
Filename: "{app}\tray\theta-agent-tray-windows-amd64.exe"; Description: "Start Theta Agent tray"; StatusMsg: "Starting Theta Agent tray..."; Flags: nowait postinstall
[Code]
var
ServerURL: String;
JoinKey: String;
AuthToken: String;
PublicKey: String;
B64Config: String;
AgentConfigPage: TWizardPage;
ServerURLEdit: TNewEdit;
JoinKeyEdit: TNewEdit;
OpenSSOButton: TNewButton;
// Reads a custom setup command-line parameter (e.g. /SERVER_URL=https://...).
// {param:...} raises when the parameter is absent, so the exception becomes "".
function GetCmdParam(const Name: String): String;
begin
try
Result := ExpandConstant('{param:' + Name + '}');
except
Result := '';
end;
end;
function InitializeSetup(): Boolean;
begin
ServerURL := GetCmdParam('SERVER_URL');
JoinKey := GetCmdParam('JOIN_KEY');
AuthToken := GetCmdParam('AUTH_TOKEN');
PublicKey := GetCmdParam('PUBLIC_KEY');
B64Config := GetCmdParam('B64_CONFIG');
Result := True;
end;
// Opens the Theta Directory page so the operator can mint a join key right
// from the wizard. Uses the Server URL they just typed.
procedure OnOpenSSOClick(Sender: TObject);
var
Url: String;
ErrorCode: Integer;
begin
Url := Trim(ServerURLEdit.Text);
if Url = '' then begin
MsgBox('Enter the Theta Directory URL first (e.g. https://directory.example.com).',
mbInformation, MB_OK);
Exit;
end;
if not ShellExec('open', Url, '', '', SW_SHOWNORMAL, ewNoWait, ErrorCode) then
MsgBox('Could not open the browser: ' + SysErrorMessage(ErrorCode), mbError, MB_OK);
end;
procedure CreateAgentConfigPage();
var
InfoLabel: TNewStaticText;
UrlLabel: TNewStaticText;
KeyLabel: TNewStaticText;
Y: Integer;
begin
AgentConfigPage := CreateCustomPage(wpWelcome,
'Theta Directory connection',
'Tell the agent which Theta Directory to enroll with.');
InfoLabel := TNewStaticText.Create(AgentConfigPage);
InfoLabel.Parent := AgentConfigPage.Surface;
InfoLabel.WordWrap := True;
InfoLabel.Caption := 'Paste the Theta Directory URL for this deployment. Then either paste a join key '
+ '(mint one with the button below, under Directory -> Install Agent) or leave it blank to '
+ 'enroll from the tray / CLI later.';
// WordWrap + AutoSize are mutually exclusive in VCL; give the wrapped label a
// fixed height so the fields below it land on screen.
InfoLabel.Width := AgentConfigPage.SurfaceWidth;
InfoLabel.Height := ScaleY(48);
Y := InfoLabel.Top + InfoLabel.Height + ScaleY(12);
UrlLabel := TNewStaticText.Create(AgentConfigPage);
UrlLabel.Parent := AgentConfigPage.Surface;
UrlLabel.Caption := 'Theta Directory URL:';
UrlLabel.Top := Y;
ServerURLEdit := TNewEdit.Create(AgentConfigPage);
ServerURLEdit.Parent := AgentConfigPage.Surface;
ServerURLEdit.Top := UrlLabel.Top + UrlLabel.Height + ScaleY(4);
ServerURLEdit.Width := AgentConfigPage.SurfaceWidth;
ServerURLEdit.Text := ServerURL;
OpenSSOButton := TNewButton.Create(AgentConfigPage);
OpenSSOButton.Parent := AgentConfigPage.Surface;
OpenSSOButton.Top := ServerURLEdit.Top + ServerURLEdit.Height + ScaleY(10);
OpenSSOButton.Left := ServerURLEdit.Left;
OpenSSOButton.Caption := 'Open Theta Directory install-agent page...';
OpenSSOButton.Width := WizardForm.CalculateButtonWidth([OpenSSOButton.Caption]);
OpenSSOButton.Height := ScaleY(23);
OpenSSOButton.OnClick := @OnOpenSSOClick;
KeyLabel := TNewStaticText.Create(AgentConfigPage);
KeyLabel.Parent := AgentConfigPage.Surface;
KeyLabel.Caption := 'Join key (optional):';
KeyLabel.Top := OpenSSOButton.Top + OpenSSOButton.Height + ScaleY(12);
JoinKeyEdit := TNewEdit.Create(AgentConfigPage);
JoinKeyEdit.Parent := AgentConfigPage.Surface;
JoinKeyEdit.Top := KeyLabel.Top + KeyLabel.Height + ScaleY(4);
JoinKeyEdit.Width := AgentConfigPage.SurfaceWidth;
JoinKeyEdit.Text := JoinKey;
end;
procedure InitializeWizard();
begin
CreateAgentConfigPage();
end;
// Pull the values the operator typed into the wizard so WriteAgentConfig can
// use them. In silent mode the wizard is walked programmatically and
// CurPageChanged fires too -- reading the (empty) edit boxes there would wipe
// the /SERVER_URL /JOIN_KEY command-line params and leave agent.yml with an
// empty server_url. Only take the edit values when the wizard is actually
// being shown interactively.
procedure CurPageChanged(CurPageID: Integer);
begin
if (CurPageID = AgentConfigPage.ID) and (not WizardSilent()) then begin
ServerURL := Trim(ServerURLEdit.Text);
JoinKey := Trim(JoinKeyEdit.Text);
end;
end;
// Minimal base64 decoder returning a plain String (agent.yml is ASCII).
function B64Decode(const S: String): String;
var
i, v, p: Integer;
buf: array[0..3] of Integer;
outStr: String;
begin
outStr := '';
v := 0;
for i := 1 to Length(S) do begin
if S[i] = '=' then begin
buf[v] := 0;
Inc(v);
end else begin
p := Pos(S[i], 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/');
buf[v] := p - 1;
Inc(v);
end;
if v = 4 then begin
outStr := outStr + Chr((buf[0] shl 2) or (buf[1] shr 4));
outStr := outStr + Chr(((buf[1] and $F) shl 4) or (buf[2] shr 2));
outStr := outStr + Chr(((buf[2] and 3) shl 6) or buf[3]);
v := 0;
end;
end;
Result := outStr;
end;
// Write agent.yml only after all files are in place. The file lives in
// ProgramData so the SYSTEM service and user processes share it.
procedure WriteAgentConfig(ConfigPath: String);
var
Lines: TArrayOfString;
Decoded: String;
begin
// /B64_CONFIG=<base64 agent.yml> overrides everything (the SSO's Custom Config
// wizard emits it).
if B64Config <> '' then begin
Decoded := B64Decode(B64Config);
SetArrayLength(Lines, 1);
Lines[0] := Decoded;
SaveStringsToUTF8FileWithoutBOM(ConfigPath, Lines, False);
Exit;
end;
SetArrayLength(Lines, 17);
Lines[0] := '# theta-agent configuration (written by installer)';
Lines[1] := 'server_url: "' + ServerURL + '"';
Lines[2] := 'auth_token: "' + AuthToken + '"';
Lines[3] := 'join_key: "' + JoinKey + '"';
Lines[4] := 'public_key: "' + PublicKey + '"';
Lines[5] := 'auto_vpn: false';
Lines[6] := 'service_name: "theta-agent"';
Lines[7] := 'desktop_helper: "' + ExpandConstant('{app}') + '\theta-agent-helper-windows-amd64.exe"';
Lines[8] := 'public_ip_detect: true';
Lines[9] := 'capabilities:';
Lines[10] := ' telemetry: true';
Lines[11] := ' ldap_tunnel: true';
Lines[12] := ' wireguard: true';
Lines[13] := ' secrets: false';
Lines[14] := ' iam: false';
Lines[15] := ' reboot: false';
Lines[16] := ' arbitrary_bash: false';
SaveStringsToUTF8FileWithoutBOM(ConfigPath, Lines, False);
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssPostInstall then
WriteAgentConfig(ExpandConstant('{commonappdata}\Theta42\agent.yml'));
end;