From e3341f9de3dac05ae5811de4e49b88c50d66251b Mon Sep 17 00:00:00 2001 From: IgorRachkov <89467086+igor04091968@users.noreply.github.com> Date: Wed, 29 Apr 2026 07:11:20 +0300 Subject: [PATCH] Add Inno Setup Windows installer scaffold --- windows/installer/AWatch-rus-Setup.iss | 205 ++++++++++++++++++ windows/installer/Invoke-AWatchRusInstall.ps1 | 47 ++++ windows/installer/README-INSTALLER.md | 24 ++ 3 files changed, 276 insertions(+) create mode 100644 windows/installer/AWatch-rus-Setup.iss create mode 100644 windows/installer/Invoke-AWatchRusInstall.ps1 create mode 100644 windows/installer/README-INSTALLER.md diff --git a/windows/installer/AWatch-rus-Setup.iss b/windows/installer/AWatch-rus-Setup.iss new file mode 100644 index 0000000..aa90e52 --- /dev/null +++ b/windows/installer/AWatch-rus-Setup.iss @@ -0,0 +1,205 @@ +#define MyAppName "AWatch-rus Windows Agent" +#define MyAppVersion "1.0.0" +#define MyAppPublisher "AWatch-rus" +#define MyAppURL "https://example.local/awatch-rus" +#define MyAppExeName "AWatch-rus-Setup.exe" + +[Setup] +AppId={{7F2C4B63-2B8A-4F1C-BA12-66A7E2C0A0A1} +AppName={#MyAppName} +AppVersion={#MyAppVersion} +AppPublisher={#MyAppPublisher} +AppPublisherURL={#MyAppURL} +AppSupportURL={#MyAppURL} +AppUpdatesURL={#MyAppURL} +DefaultDirName={autopf}\AWatch-rus +DefaultGroupName=AWatch-rus +DisableProgramGroupPage=yes +PrivilegesRequired=admin +ArchitecturesInstallIn64BitMode=x64compatible +OutputDir=output +OutputBaseFilename={#MyAppExeName} +Compression=lzma2 +SolidCompression=yes +WizardStyle=modern +SetupLogging=yes +DisableWelcomePage=no +AllowNoIcons=yes +UninstallDisplayIcon={app}\tools\validate-deployment.ps1 + +[Languages] +Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl" +Name: "english"; MessagesFile: "compiler:Default.isl" + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked + +[Files] +Source: "..\ActivityWatch.Windows.Common.psd1"; DestDir: "{app}\tools"; Flags: ignoreversion +Source: "..\ActivityWatch.Windows.Common.psm1"; DestDir: "{app}\tools"; Flags: ignoreversion +Source: "..\browser-domains-native-collector.ps1"; DestDir: "{app}\tools"; Flags: ignoreversion +Source: "..\dlp-endpoint-signals-collector.ps1"; DestDir: "{app}\tools"; Flags: ignoreversion +Source: "..\deploy-single-user.ps1"; DestDir: "{app}\tools"; Flags: ignoreversion +Source: "..\deploy-domain-users.ps1"; DestDir: "{app}\tools"; Flags: ignoreversion +Source: "..\deploy-ensemble.ps1"; DestDir: "{app}\tools"; Flags: ignoreversion +Source: "..\hardening-recovery.ps1"; DestDir: "{app}\tools"; Flags: ignoreversion +Source: "..\validate-deployment.ps1"; DestDir: "{app}\tools"; Flags: ignoreversion +Source: "..\web-category-rules.example.json"; DestDir: "{app}\config"; Flags: ignoreversion +Source: "..\dlp-policy.example.json"; DestDir: "{app}\config"; Flags: ignoreversion +Source: "README-INSTALLER.md"; DestDir: "{app}"; Flags: ignoreversion +Source: "Invoke-AWatchRusInstall.ps1"; DestDir: "{app}\tools"; Flags: ignoreversion + +[Icons] +Name: "{autoprograms}\AWatch-rus\Run validation"; Filename: "powershell.exe"; Parameters: "-NoProfile -ExecutionPolicy Bypass -File ""{app}\tools\validate-deployment.ps1"" -ConfigPath ""C:\ProgramData\ActivityWatch\deployment-config.json"""; WorkingDir: "{app}\tools" +Name: "{autodesktop}\AWatch-rus Validation"; Filename: "powershell.exe"; Parameters: "-NoProfile -ExecutionPolicy Bypass -File ""{app}\tools\validate-deployment.ps1"" -ConfigPath ""C:\ProgramData\ActivityWatch\deployment-config.json"""; Tasks: desktopicon + +[Run] +Filename: "powershell.exe"; Parameters: "-NoProfile -ExecutionPolicy Bypass -File ""{app}\tools\Invoke-AWatchRusInstall.ps1"" -ServerHost ""{code:GetServerHost}"" -ServerPort {code:GetServerPort} -Domain ""{code:GetDomain}"" -Users ""{code:GetUsers}"" -InstallRoot ""{code:GetInstallRoot}"" -StateRoot ""{code:GetStateRoot}"" -CustomRulesPath ""{app}\config\web-category-rules.example.json"" -CustomPolicyPath ""{app}\config\dlp-policy.example.json"" -AfkEnabled:{code:GetAfkEnabled} -WindowEnabled:{code:GetWindowEnabled}"; Flags: runhidden waituntilterminated +Filename: "powershell.exe"; Parameters: "-NoProfile -ExecutionPolicy Bypass -File ""{app}\tools\validate-deployment.ps1"" -ConfigPath ""{code:GetStateRoot}\deployment-config.json"""; Flags: postinstall shellexec skipifsilent + +[UninstallRun] +Filename: "powershell.exe"; Parameters: "-NoProfile -ExecutionPolicy Bypass -Command ""Get-ScheduledTask -TaskName 'ActivityWatch Launch *' -ErrorAction SilentlyContinue | ForEach-Object { Unregister-ScheduledTask -TaskName $_.TaskName -Confirm:$false }; Unregister-ScheduledTask -TaskName 'ActivityWatch Recovery' -Confirm:$false -ErrorAction SilentlyContinue"""; Flags: runhidden waituntilterminated + +[Code] +var + ConfigPage: TWizardPage; + ServerHostEdit: TEdit; + ServerPortEdit: TEdit; + DomainEdit: TEdit; + UsersEdit: TEdit; + InstallRootEdit: TEdit; + StateRootEdit: TEdit; + AfkCheck: TNewCheckBox; + WindowCheck: TNewCheckBox; + +procedure InitializeWizard; +begin + ConfigPage := CreateCustomPage(wpSelectTasks, + 'AWatch-rus configuration', + 'Specify deployment settings passed to deploy-ensemble.ps1'); + + ServerHostEdit := TEdit.Create(ConfigPage); + ServerHostEdit.Parent := ConfigPage.Surface; + ServerHostEdit.Left := ScaleX(0); + ServerHostEdit.Top := ScaleY(8); + ServerHostEdit.Width := ScaleX(420); + ServerHostEdit.Text := 'aw.example.local'; + + ServerPortEdit := TEdit.Create(ConfigPage); + ServerPortEdit.Parent := ConfigPage.Surface; + ServerPortEdit.Left := ScaleX(0); + ServerPortEdit.Top := ScaleY(40); + ServerPortEdit.Width := ScaleX(120); + ServerPortEdit.Text := '5600'; + + DomainEdit := TEdit.Create(ConfigPage); + DomainEdit.Parent := ConfigPage.Surface; + DomainEdit.Left := ScaleX(0); + DomainEdit.Top := ScaleY(72); + DomainEdit.Width := ScaleX(260); + DomainEdit.Text := 'CONTOSO'; + + UsersEdit := TEdit.Create(ConfigPage); + UsersEdit.Parent := ConfigPage.Surface; + UsersEdit.Left := ScaleX(0); + UsersEdit.Top := ScaleY(104); + UsersEdit.Width := ScaleX(420); + UsersEdit.Text := 'user1,user2'; + + InstallRootEdit := TEdit.Create(ConfigPage); + InstallRootEdit.Parent := ConfigPage.Surface; + InstallRootEdit.Left := ScaleX(0); + InstallRootEdit.Top := ScaleY(136); + InstallRootEdit.Width := ScaleX(420); + InstallRootEdit.Text := 'C:\Program Files\ActivityWatch'; + + StateRootEdit := TEdit.Create(ConfigPage); + StateRootEdit.Parent := ConfigPage.Surface; + StateRootEdit.Left := ScaleX(0); + StateRootEdit.Top := ScaleY(168); + StateRootEdit.Width := ScaleX(420); + StateRootEdit.Text := 'C:\ProgramData\ActivityWatch'; + + AfkCheck := TNewCheckBox.Create(ConfigPage); + AfkCheck.Parent := ConfigPage.Surface; + AfkCheck.Top := ScaleY(200); + AfkCheck.Caption := 'Enable AFK watcher'; + AfkCheck.Checked := True; + + WindowCheck := TNewCheckBox.Create(ConfigPage); + WindowCheck.Parent := ConfigPage.Surface; + WindowCheck.Top := ScaleY(224); + WindowCheck.Caption := 'Enable Window watcher'; + WindowCheck.Checked := True; +end; + +function NextButtonClick(CurPageID: Integer): Boolean; +var + PortNum: Integer; +begin + Result := True; + if CurPageID = ConfigPage.ID then + begin + if Trim(ServerHostEdit.Text) = '' then + begin + MsgBox('ServerHost is required.', mbError, MB_OK); + Result := False; + exit; + end; + + PortNum := StrToIntDef(Trim(ServerPortEdit.Text), 0); + if (PortNum < 1) or (PortNum > 65535) then + begin + MsgBox('ServerPort must be in range 1..65535.', mbError, MB_OK); + Result := False; + exit; + end; + + if Trim(UsersEdit.Text) = '' then + begin + MsgBox('Users list is required (comma-separated).', mbError, MB_OK); + Result := False; + exit; + end; + end; +end; + +function GetServerHost(Param: string): string; +begin + Result := Trim(ServerHostEdit.Text); +end; + +function GetServerPort(Param: string): string; +begin + Result := Trim(ServerPortEdit.Text); +end; + +function GetDomain(Param: string): string; +begin + Result := Trim(DomainEdit.Text); +end; + +function GetUsers(Param: string): string; +begin + Result := Trim(UsersEdit.Text); +end; + +function GetInstallRoot(Param: string): string; +begin + Result := Trim(InstallRootEdit.Text); +end; + +function GetStateRoot(Param: string): string; +begin + Result := Trim(StateRootEdit.Text); +end; + +function GetAfkEnabled(Param: string): string; +begin + if AfkCheck.Checked then Result := '$true' else Result := '$false'; +end; + +function GetWindowEnabled(Param: string): string; +begin + if WindowCheck.Checked then Result := '$true' else Result := '$false'; +end; diff --git a/windows/installer/Invoke-AWatchRusInstall.ps1 b/windows/installer/Invoke-AWatchRusInstall.ps1 new file mode 100644 index 0000000..3df51b6 --- /dev/null +++ b/windows/installer/Invoke-AWatchRusInstall.ps1 @@ -0,0 +1,47 @@ +[CmdletBinding()] +param( + [Parameter(Mandatory=$true)][string]$ServerHost, + [int]$ServerPort = 5600, + [Parameter(Mandatory=$true)][string]$Domain, + [Parameter(Mandatory=$true)][string]$Users, + [string]$InstallRoot = 'C:\Program Files\ActivityWatch', + [string]$StateRoot = 'C:\ProgramData\ActivityWatch', + [string]$CustomRulesPath, + [string]$CustomPolicyPath, + [bool]$AfkEnabled = $true, + [bool]$WindowEnabled = $true +) + +$ErrorActionPreference = 'Stop' + +$scriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path +$ensembleScript = Join-Path $scriptRoot 'deploy-ensemble.ps1' + +if (-not (Test-Path -LiteralPath $ensembleScript)) { + throw "deploy-ensemble.ps1 not found at $ensembleScript" +} + +$usersList = $Users.Split(',') | ForEach-Object { $_.Trim() } | Where-Object { $_ } +if (-not $usersList -or $usersList.Count -eq 0) { + throw 'Users must contain at least one username.' +} + +$params = @{ + ServerHost = $ServerHost + ServerPort = $ServerPort + Domain = $Domain + Users = $usersList + InstallRoot = $InstallRoot + StateRoot = $StateRoot + AfkEnabled = $AfkEnabled + WindowEnabled = $WindowEnabled +} + +if ($CustomRulesPath -and (Test-Path -LiteralPath $CustomRulesPath)) { + $params['CustomRulesPath'] = $CustomRulesPath +} +if ($CustomPolicyPath -and (Test-Path -LiteralPath $CustomPolicyPath)) { + $params['CustomPolicyPath'] = $CustomPolicyPath +} + +& $ensembleScript @params diff --git a/windows/installer/README-INSTALLER.md b/windows/installer/README-INSTALLER.md new file mode 100644 index 0000000..52f17a1 --- /dev/null +++ b/windows/installer/README-INSTALLER.md @@ -0,0 +1,24 @@ +# AWatch-rus Inno Setup installer skeleton + +This directory contains a ready-to-build scaffold for a classic Setup.exe installer. + +## Files +- `AWatch-rus-Setup.iss` — Inno Setup script. +- `Invoke-AWatchRusInstall.ps1` — wrapper that invokes `deploy-ensemble.ps1` with installer parameters. + +## Build +1. Install Inno Setup 6 on Windows build host. +2. Open `AWatch-rus-Setup.iss` in Inno Setup Compiler. +3. Build to produce `output/AWatch-rus-Setup.exe`. + +## Behavior +- Requires Administrator privileges. +- Copies toolkit scripts into `{app}\tools`. +- Collects server/domain/users parameters via wizard page. +- Runs `Invoke-AWatchRusInstall.ps1` -> `deploy-ensemble.ps1`. +- Runs validation at the end. +- Uninstall step removes `ActivityWatch` scheduled tasks. + +## Notes +- Default publisher URL is a placeholder (`https://example.local/awatch-rus`), replace it. +- For silent enterprise rollout, extend `[Run]` and parse `/SERVERHOST=...` style custom switches if needed.