diff --git a/ansible/deploy_aw_windows_phase2.yml b/ansible/deploy_aw_windows_phase2.yml index 769f554..0f4a4ee 100644 --- a/ansible/deploy_aw_windows_phase2.yml +++ b/ansible/deploy_aw_windows_phase2.yml @@ -15,6 +15,8 @@ - user3 - user4 - user5 + aw_windows_extra_users: [] + aw_windows_users_effective: "{{ (aw_windows_users + aw_windows_extra_users) | unique }}" aw_windows_install_root: "C:\\Program Files\\ActivityWatch-Phase2" aw_windows_state_root: "C:\\ProgramData\\ActivityWatch-Phase2" aw_windows_afk_enabled: true @@ -32,7 +34,7 @@ - aw_windows_server_host is defined - aw_windows_server_port is defined - aw_windows_domain is defined - - aw_windows_users | length > 0 + - aw_windows_users_effective | length > 0 - aw_windows_install_root is defined - aw_windows_state_root is defined fail_msg: "Missing required Windows deployment variables." @@ -68,6 +70,9 @@ {% for user in aw_windows_users -%} {{ user }} {% endfor -%} + {% for user in aw_windows_extra_users -%} + {{ user }} + {% endfor -%} - name: Run phase2 ensemble deployment ansible.windows.win_powershell: diff --git a/ansible/group_vars/windows.example.yml b/ansible/group_vars/windows.example.yml index 2821a72..439ce5d 100644 --- a/ansible/group_vars/windows.example.yml +++ b/ansible/group_vars/windows.example.yml @@ -9,6 +9,10 @@ aw_windows_users: - user3 - user4 - user5 +aw_windows_extra_users: [] +# Например: +# aw_windows_extra_users: +# - Администратор # Рекомендуемый изолированный профиль для фазового раската. aw_windows_install_root: "C:\\Program Files\\ActivityWatch-Phase2" diff --git a/windows/ActivityWatch.Windows.Common.psm1 b/windows/ActivityWatch.Windows.Common.psm1 index 43c0db2..c195dc2 100755 --- a/windows/ActivityWatch.Windows.Common.psm1 +++ b/windows/ActivityWatch.Windows.Common.psm1 @@ -528,18 +528,75 @@ function Get-DeploymentConfig { return Get-Content -LiteralPath `$Path -Raw | ConvertFrom-Json } +function Get-RecoveryConfigPaths { + param([string]`$PrimaryConfigPath) + + `$paths = New-Object System.Collections.Generic.List[string] + if (`$PrimaryConfigPath -and (Test-Path -LiteralPath `$PrimaryConfigPath)) { + `$paths.Add((Resolve-Path -LiteralPath `$PrimaryConfigPath).Path) + } + + `$searchRoot = `$env:ProgramData + if (`$PrimaryConfigPath) { + `$stateRoot = Split-Path -Path `$PrimaryConfigPath -Parent + `$candidateRoot = Split-Path -Path `$stateRoot -Parent + if (`$candidateRoot -and (Test-Path -LiteralPath `$candidateRoot)) { + `$searchRoot = `$candidateRoot + } + } + + if (Test-Path -LiteralPath `$searchRoot) { + Get-ChildItem -LiteralPath `$searchRoot -Directory -ErrorAction SilentlyContinue | + Where-Object { `$_.Name -like 'ActivityWatch*' } | + ForEach-Object { + `$candidate = Join-Path `$_.FullName 'deployment-config.json' + if (Test-Path -LiteralPath `$candidate) { + `$paths.Add(`$candidate) + } + } + } + + return @(`$paths | Sort-Object -Unique) +} + +function Get-RecoveryTaskNames { + param([string[]]`$ConfigPaths) + + `$taskNames = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase) + foreach (`$candidatePath in @(`$ConfigPaths)) { + try { + `$config = Get-DeploymentConfig -Path `$candidatePath + foreach (`$task in @(`$config.userTasks)) { + `$taskName = [string]`$task.launchTaskName + if (-not [string]::IsNullOrWhiteSpace(`$taskName)) { + [void]`$taskNames.Add(`$taskName) + } + } + } + catch { + } + } + + return @(`$taskNames) +} + while (`$true) { + `$sleepSeconds = 180 try { + `$configPaths = Get-RecoveryConfigPaths -PrimaryConfigPath `$ConfigPath + foreach (`$taskName in Get-RecoveryTaskNames -ConfigPaths `$configPaths) { + Start-ScheduledTask -TaskName `$taskName -ErrorAction SilentlyContinue + } + `$config = Get-DeploymentConfig -Path `$ConfigPath - foreach (`$task in @(`$config.userTasks)) { - Start-ScheduledTask -TaskName ([string]`$task.launchTaskName) -ErrorAction SilentlyContinue + if (`$config -and `$config.recovery -and `$config.recovery.intervalSeconds) { + `$sleepSeconds = [Math]::Max([int]`$config.recovery.intervalSeconds, 30) } } catch { } - `$config = Get-DeploymentConfig -Path `$ConfigPath - Start-Sleep -Seconds ([int]`$config.recovery.intervalSeconds) + Start-Sleep -Seconds `$sleepSeconds } "@ @@ -563,6 +620,25 @@ function Remove-LegacyActivityWatchEntries { } } +function Remove-ActivityWatchScheduledTask { + param( + [Parameter(Mandatory = $true)] + [string]$TaskName + ) + + Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false -ErrorAction SilentlyContinue + & cmd.exe /c "schtasks /Delete /TN `"$TaskName`" /F >nul 2>&1" | Out-Null + + for ($attempt = 0; $attempt -lt 10; $attempt++) { + $task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue + if (-not $task) { + return + } + + Start-Sleep -Milliseconds 300 + } +} + function Register-ActivityWatchUserTasks { param( [Parameter(Mandatory = $true)] @@ -576,7 +652,7 @@ function Register-ActivityWatchUserTasks { $powershellExe = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe' foreach ($definition in $TaskDefinitions) { - Unregister-ScheduledTask -TaskName $definition.LaunchTaskName -Confirm:$false -ErrorAction SilentlyContinue + Remove-ActivityWatchScheduledTask -TaskName $definition.LaunchTaskName $action = New-ScheduledTaskAction -Execute $powershellExe -Argument "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$LaunchScriptPath`" -ConfigPath `"$ConfigPath`"" $trigger = New-ScheduledTaskTrigger -AtLogOn -User $definition.UserId @@ -597,7 +673,7 @@ function Register-ActivityWatchRecoveryTask { [string]$ConfigPath ) - Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false -ErrorAction SilentlyContinue + Remove-ActivityWatchScheduledTask -TaskName $TaskName $powershellExe = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe' $action = New-ScheduledTaskAction -Execute $powershellExe -Argument "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$RecoveryScriptPath`" -ConfigPath `"$ConfigPath`""