From 211a6a6eace9f2515f24af0d9a79f84321fde926 Mon Sep 17 00:00:00 2001 From: igor04091968 Date: Fri, 8 May 2026 00:34:52 +0300 Subject: [PATCH] fix(windows-deploy): enforce awHostname override and safe single-instance collector restart --- ansible/deploy_aw_windows.yml | 41 ++++++++++++++++++++ ansible/group_vars/aw_windows.yml | 1 + ansible/group_vars/windows.example.yml | 1 + windows/ActivityWatch.Windows.Common.psm1 | 4 +- windows/browser-domains-native-collector.ps1 | 3 +- windows/deploy-domain-users.ps1 | 2 + windows/deploy-ensemble.ps1 | 3 ++ windows/deploy-single-user.ps1 | 2 + windows/dlp-endpoint-signals-collector.ps1 | 3 +- windows/file-operations-collector.ps1 | 1 + windows/hardening-recovery.ps1 | 3 ++ windows/worktime-session-collector.ps1 | 2 +- 12 files changed, 62 insertions(+), 4 deletions(-) diff --git a/ansible/deploy_aw_windows.yml b/ansible/deploy_aw_windows.yml index d7384ff..c80063f 100644 --- a/ansible/deploy_aw_windows.yml +++ b/ansible/deploy_aw_windows.yml @@ -160,6 +160,9 @@ {% if (aw_windows_package_zip_path | default('') | string | length) > 0 %} $params.PackageZipPath = "{{ aw_windows_package_zip_path }}" {% endif %} + {% if (aw_windows_hostname_override | default('') | string | length) > 0 %} + $params.AwHostname = "{{ aw_windows_hostname_override }}" + {% endif %} {% if aw_windows_skip_hardening | bool %} $params.SkipHardening = $true {% endif %} @@ -183,6 +186,44 @@ ansible.windows.win_powershell: script: | $ErrorActionPreference = 'Stop' + + function Get-CollectorKey { + param([string]$CommandLine) + if (-not $CommandLine) { return $null } + $cl = $CommandLine.ToLowerInvariant() + if ($cl -like '*browser-domains-native-collector.ps1*') { return 'browser' } + if ($cl -like '*file-operations-collector.ps1*') { return 'fileops' } + if ($cl -like '*dlp-endpoint-signals-collector.ps1*') { return 'endpoint' } + if ($cl -like '*email-outbound-collector.ps1*') { return 'email' } + if ($cl -like '*worktime-session-collector.ps1*') { return 'worktime' } + return $null + } + + $collectorProcs = Get-CimInstance Win32_Process | + Where-Object { $_.Name -eq 'powershell.exe' -and $_.CommandLine } | + ForEach-Object { + $key = Get-CollectorKey -CommandLine $_.CommandLine + if ($key) { + [pscustomobject]@{ + ProcessId = [int]$_.ProcessId + SessionId = [int]$_.SessionId + CreationDate = $_.CreationDate + CollectorKey = $key + } + } + } | + Where-Object { $_ -ne $null } + + # Keep only one process per (collector, session): newest survives, older duplicates are stopped. + foreach ($group in ($collectorProcs | Group-Object CollectorKey, SessionId)) { + $ordered = @($group.Group | Sort-Object CreationDate -Descending) + if ($ordered.Count -le 1) { continue } + foreach ($dup in $ordered | Select-Object -Skip 1) { + Stop-Process -Id $dup.ProcessId -Force -ErrorAction SilentlyContinue + } + } + + Start-Sleep -Seconds 2 Start-ScheduledTask -TaskName "{{ aw_windows_recovery_task_name }}" Get-ScheduledTask | Where-Object TaskName -like "{{ aw_windows_launch_task_pattern }}" | diff --git a/ansible/group_vars/aw_windows.yml b/ansible/group_vars/aw_windows.yml index 08685b1..ef87ba4 100644 --- a/ansible/group_vars/aw_windows.yml +++ b/ansible/group_vars/aw_windows.yml @@ -24,6 +24,7 @@ aw_windows_extra_users: [] aw_windows_install_root: "C:\\Program Files\\AWatch-rus\\bin" aw_windows_state_root: "C:\\ProgramData\\AWatch-rus" +aw_windows_hostname_override: "" aw_windows_afk_enabled: true aw_windows_window_enabled: true diff --git a/ansible/group_vars/windows.example.yml b/ansible/group_vars/windows.example.yml index 707b658..3a0d770 100644 --- a/ansible/group_vars/windows.example.yml +++ b/ansible/group_vars/windows.example.yml @@ -21,6 +21,7 @@ aw_windows_extra_users: [] # Единые Windows/RDP пути: те же, что использует InnoSetup. aw_windows_install_root: "C:\\Program Files\\AWatch-rus\\bin" aw_windows_state_root: "C:\\ProgramData\\AWatch-rus" +aw_windows_hostname_override: "" # Например: SHARKON2025 aw_windows_afk_enabled: true aw_windows_window_enabled: true aw_windows_file_ops_enabled: true diff --git a/windows/ActivityWatch.Windows.Common.psm1 b/windows/ActivityWatch.Windows.Common.psm1 index 5f1e7fb..af4e0c6 100755 --- a/windows/ActivityWatch.Windows.Common.psm1 +++ b/windows/ActivityWatch.Windows.Common.psm1 @@ -376,6 +376,7 @@ function New-ActivityWatchDeploymentConfig { [string]$LaunchScriptPath, [Parameter(Mandatory = $true)] [string]$RecoveryScriptPath, + [string]$AwHostname, [Parameter(Mandatory = $true)] [pscustomobject[]]$UserTasks, [string]$PackageVersion = 'v0.13.2' @@ -386,6 +387,7 @@ function New-ActivityWatchDeploymentConfig { return [pscustomobject]@{ version = 1 generatedAtUtc = (Get-Date).ToUniversalTime().ToString('o') + awHostname = if ([string]::IsNullOrWhiteSpace($AwHostname)) { [string]$env:COMPUTERNAME } else { [string]$AwHostname } server = [pscustomobject]@{ host = $ServerHost port = $ServerPort @@ -742,7 +744,7 @@ function Start-CollectorScriptIfNeeded { `$installRoot = [string]`$config.paths.installRoot `$stateRoot = [string]`$config.paths.stateRoot `$script:ApiBase = '{0}://{1}:{2}/api/0' -f [string]`$config.server.scheme, [string]`$config.server.host, [string]`$config.server.port -`$script:Hostname = `$env:COMPUTERNAME +`$script:Hostname = if (`$config.PSObject.Properties.Name -contains 'awHostname' -and -not [string]::IsNullOrWhiteSpace([string]`$config.awHostname)) { [string]`$config.awHostname } else { `$env:COMPUTERNAME } `$script:KnownBuckets = @{} `$collectorScript = [string]`$config.paths.collectorScript `$endpointCollectorScript = if (`$config.paths.PSObject.Properties.Name -contains 'endpointCollectorScript') { [string]`$config.paths.endpointCollectorScript } else { Join-Path `$stateRoot 'dlp-endpoint-signals-collector.ps1' } diff --git a/windows/browser-domains-native-collector.ps1 b/windows/browser-domains-native-collector.ps1 index f4eff79..b40b48c 100755 --- a/windows/browser-domains-native-collector.ps1 +++ b/windows/browser-domains-native-collector.ps1 @@ -62,13 +62,14 @@ $resolvedIncidentLogPath = if ($IncidentLogPath) { $IncidentLogPath } else { Joi $resolvedLocalAgentLogsEnabled = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'logging' -and $deploymentConfig.logging.PSObject.Properties.Name -contains 'localAgentLogsEnabled') { [bool]$deploymentConfig.logging.localAgentLogsEnabled } else { $true } $resolvedIncidentArtifactsRoot = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $deploymentConfig.incidentCapture.PSObject.Properties.Name -contains 'artifactsRoot') { [string]$deploymentConfig.incidentCapture.artifactsRoot } else { Join-Path $env:LOCALAPPDATA 'AWatch-rus\\incident-artifacts' } $resolvedIncidentScreenshotEnabled = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $deploymentConfig.incidentCapture.PSObject.Properties.Name -contains 'screenshotEnabled') { [bool]$deploymentConfig.incidentCapture.screenshotEnabled } else { $true } +$resolvedHostname = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'awHostname' -and -not [string]::IsNullOrWhiteSpace([string]$deploymentConfig.awHostname)) { [string]$deploymentConfig.awHostname } else { [string]$env:COMPUTERNAME } if ($resolvedLocalAgentLogsEnabled -and -not (Test-Path -LiteralPath $resolvedLogsRoot)) { New-Item -Path $resolvedLogsRoot -ItemType Directory -Force | Out-Null } $script:ApiBase = '{0}://{1}:{2}/api/0' -f $resolvedServerScheme, $resolvedServerHost, $resolvedServerPort -$script:Hostname = $env:COMPUTERNAME +$script:Hostname = $resolvedHostname $script:SessionId = (Get-Process -Id $PID).SessionId $script:KnownBuckets = @{} $script:LocalAgentLogsEnabled = $resolvedLocalAgentLogsEnabled diff --git a/windows/deploy-domain-users.ps1 b/windows/deploy-domain-users.ps1 index 92e5494..e2dd12b 100755 --- a/windows/deploy-domain-users.ps1 +++ b/windows/deploy-domain-users.ps1 @@ -24,6 +24,7 @@ param( [bool]$IncidentScreenshotEnabled = $true, [string]$IncidentArtifactsRoot, [bool]$LogonMarkerEnabled = $true, + [string]$AwHostname, [string]$CustomRulesPath, [string]$CustomPolicyPath ) @@ -100,6 +101,7 @@ $config = New-ActivityWatchDeploymentConfig ` -IncidentScreenshotEnabled $IncidentScreenshotEnabled ` -IncidentArtifactsRoot $IncidentArtifactsRoot ` -LogonMarkerEnabled $LogonMarkerEnabled ` + -AwHostname $AwHostname ` -LaunchScriptPath $launchScriptPath ` -RecoveryScriptPath $recoveryScriptPath ` -UserTasks $taskDefinitions ` diff --git a/windows/deploy-ensemble.ps1 b/windows/deploy-ensemble.ps1 index fa6fd75..b915666 100644 --- a/windows/deploy-ensemble.ps1 +++ b/windows/deploy-ensemble.ps1 @@ -24,6 +24,7 @@ param( [bool]$IncidentScreenshotEnabled = $true, [string]$IncidentArtifactsRoot, [bool]$LogonMarkerEnabled = $true, + [string]$AwHostname, [string]$CustomRulesPath, [string]$CustomPolicyPath, [string]$ReportPath, @@ -71,6 +72,7 @@ if (-not (Test-Path -LiteralPath $deployScript)) { -IncidentScreenshotEnabled $IncidentScreenshotEnabled ` -IncidentArtifactsRoot $IncidentArtifactsRoot ` -LogonMarkerEnabled $LogonMarkerEnabled ` + -AwHostname $AwHostname ` -CustomRulesPath $CustomRulesPath ` -CustomPolicyPath $CustomPolicyPath @@ -94,6 +96,7 @@ if (-not $SkipHardening) { -IncidentScreenshotEnabled $IncidentScreenshotEnabled ` -IncidentArtifactsRoot $IncidentArtifactsRoot ` -LogonMarkerEnabled $LogonMarkerEnabled ` + -AwHostname $AwHostname ` -CustomRulesPath $CustomRulesPath ` -CustomPolicyPath $CustomPolicyPath } diff --git a/windows/deploy-single-user.ps1 b/windows/deploy-single-user.ps1 index 160265d..04c70e7 100755 --- a/windows/deploy-single-user.ps1 +++ b/windows/deploy-single-user.ps1 @@ -22,6 +22,7 @@ param( [bool]$IncidentScreenshotEnabled = $true, [string]$IncidentArtifactsRoot, [bool]$LogonMarkerEnabled = $true, + [string]$AwHostname, [string]$CustomRulesPath, [string]$CustomPolicyPath ) @@ -92,6 +93,7 @@ $config = New-ActivityWatchDeploymentConfig ` -IncidentScreenshotEnabled $IncidentScreenshotEnabled ` -IncidentArtifactsRoot $IncidentArtifactsRoot ` -LogonMarkerEnabled $LogonMarkerEnabled ` + -AwHostname $AwHostname ` -LaunchScriptPath $launchScriptPath ` -RecoveryScriptPath $recoveryScriptPath ` -UserTasks $taskDefinitions ` diff --git a/windows/dlp-endpoint-signals-collector.ps1 b/windows/dlp-endpoint-signals-collector.ps1 index 0b2f810..ad4f077 100644 --- a/windows/dlp-endpoint-signals-collector.ps1 +++ b/windows/dlp-endpoint-signals-collector.ps1 @@ -778,13 +778,14 @@ $resolvedLogPath = if ($LogPath) { $LogPath } else { Join-Path $resolvedLogsRoot $resolvedLocalAgentLogsEnabled = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'logging' -and $deploymentConfig.logging.PSObject.Properties.Name -contains 'localAgentLogsEnabled') { [bool]$deploymentConfig.logging.localAgentLogsEnabled } else { $true } $resolvedIncidentArtifactsRoot = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $deploymentConfig.incidentCapture.PSObject.Properties.Name -contains 'artifactsRoot') { [string]$deploymentConfig.incidentCapture.artifactsRoot } else { Join-Path $env:LOCALAPPDATA 'AWatch-rus\\incident-artifacts' } $resolvedIncidentScreenshotEnabled = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $deploymentConfig.incidentCapture.PSObject.Properties.Name -contains 'screenshotEnabled') { [bool]$deploymentConfig.incidentCapture.screenshotEnabled } else { $true } +$resolvedHostname = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'awHostname' -and -not [string]::IsNullOrWhiteSpace([string]$deploymentConfig.awHostname)) { [string]$deploymentConfig.awHostname } else { [string]$env:COMPUTERNAME } if ($resolvedLocalAgentLogsEnabled -and -not (Test-Path -LiteralPath $resolvedLogsRoot)) { New-Item -Path $resolvedLogsRoot -ItemType Directory -Force | Out-Null } $script:ApiBase = '{0}://{1}:{2}/api/0' -f $resolvedServerScheme, $resolvedServerHost, $resolvedServerPort -$script:Hostname = $env:COMPUTERNAME +$script:Hostname = $resolvedHostname $script:SessionId = (Get-Process -Id $PID).SessionId $script:KnownBuckets = @{} $script:Cooldown = @{} diff --git a/windows/file-operations-collector.ps1 b/windows/file-operations-collector.ps1 index bc63d55..c40b810 100644 --- a/windows/file-operations-collector.ps1 +++ b/windows/file-operations-collector.ps1 @@ -145,6 +145,7 @@ function Send-FileOperationEvent { $config = Get-DeploymentConfig -Path $ConfigPath if (-not $config) { throw "Configuration file not found: $ConfigPath" } +$script:Hostname = if ($config.PSObject.Properties.Name -contains 'awHostname' -and -not [string]::IsNullOrWhiteSpace([string]$config.awHostname)) { [string]$config.awHostname } else { [string]$env:COMPUTERNAME } $scheme = if ($ServerScheme) { $ServerScheme } elseif ($config.server.scheme) { $config.server.scheme } else { 'http' } $hostName = if ($ServerHost) { $ServerHost } elseif ($config.server.host) { $config.server.host } else { 'localhost' } diff --git a/windows/hardening-recovery.ps1 b/windows/hardening-recovery.ps1 index d382f41..5a7c9d5 100755 --- a/windows/hardening-recovery.ps1 +++ b/windows/hardening-recovery.ps1 @@ -21,6 +21,7 @@ param( [bool]$IncidentScreenshotEnabled, [string]$IncidentArtifactsRoot, [bool]$LogonMarkerEnabled, + [string]$AwHostname, [string]$CustomRulesPath, [string]$CustomPolicyPath, [switch]$RepairPackage, @@ -73,6 +74,7 @@ $effectiveIncidentCaptureEnabled = if ($PSBoundParameters.ContainsKey('IncidentC $effectiveIncidentScreenshotEnabled = if ($PSBoundParameters.ContainsKey('IncidentScreenshotEnabled')) { [bool]$IncidentScreenshotEnabled } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $existingConfig.incidentCapture.PSObject.Properties.Name -contains 'screenshotEnabled') { [bool]$existingConfig.incidentCapture.screenshotEnabled } else { $true } $effectiveIncidentArtifactsRoot = if ($PSBoundParameters.ContainsKey('IncidentArtifactsRoot') -and $IncidentArtifactsRoot) { $IncidentArtifactsRoot } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $existingConfig.incidentCapture.PSObject.Properties.Name -contains 'artifactsRoot') { [string]$existingConfig.incidentCapture.artifactsRoot } else { Join-Path $effectiveStateRoot 'incident-artifacts' } $effectiveLogonMarkerEnabled = if ($PSBoundParameters.ContainsKey('LogonMarkerEnabled')) { [bool]$LogonMarkerEnabled } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'sessionEvents' -and $existingConfig.sessionEvents.PSObject.Properties.Name -contains 'logonEnabled') { [bool]$existingConfig.sessionEvents.logonEnabled } else { $true } +$effectiveAwHostname = if ($PSBoundParameters.ContainsKey('AwHostname') -and -not [string]::IsNullOrWhiteSpace($AwHostname)) { [string]$AwHostname } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'awHostname' -and -not [string]::IsNullOrWhiteSpace([string]$existingConfig.awHostname)) { [string]$existingConfig.awHostname } else { [string]$env:COMPUTERNAME } $effectiveVersion = if ($Version) { $Version } elseif ($existingConfig) { [string]$existingConfig.package.version } else { 'v0.13.2' } $effectiveUsers = if ($Users -or $UserListPath) { @@ -139,6 +141,7 @@ $config = New-ActivityWatchDeploymentConfig ` -IncidentScreenshotEnabled $effectiveIncidentScreenshotEnabled ` -IncidentArtifactsRoot $effectiveIncidentArtifactsRoot ` -LogonMarkerEnabled $effectiveLogonMarkerEnabled ` + -AwHostname $effectiveAwHostname ` -LaunchScriptPath $effectiveLaunchScript ` -RecoveryScriptPath $effectiveRecoveryScript ` -UserTasks $taskDefinitions ` diff --git a/windows/worktime-session-collector.ps1 b/windows/worktime-session-collector.ps1 index 7ebf555..65e0b36 100644 --- a/windows/worktime-session-collector.ps1 +++ b/windows/worktime-session-collector.ps1 @@ -111,7 +111,7 @@ function Test-SessionIsActive { } $cfg = Get-Config -Path $ConfigPath -$hostValue = if ($Hostname) { $Hostname } else { [string]$env:COMPUTERNAME } +$hostValue = if ($Hostname) { $Hostname } elseif ($cfg.PSObject.Properties.Name -contains 'awHostname' -and -not [string]::IsNullOrWhiteSpace([string]$cfg.awHostname)) { [string]$cfg.awHostname } else { [string]$env:COMPUTERNAME } $apiBase = '{0}://{1}:{2}/api/0' -f [string]$cfg.server.scheme, [string]$cfg.server.host, [string]$cfg.server.port $bucketId = 'aw-worktime-sessions_' + $hostValue $pulse = 120