Добавить безопасную миграцию prod в AWatch-rus
This commit is contained in:
@@ -93,6 +93,7 @@ ansible-playbook -i inventory.ini deploy_aw_windows.yml
|
||||
Playbook:
|
||||
|
||||
- выгружает полный `windows/*` toolkit на целевой хост в InnoSetup-compatible каталог `C:\Program Files\AWatch-rus\windows`, включая DLP и `worktime-session-collector.ps1`;
|
||||
- если найден legacy config `C:\ProgramData\ActivityWatch-Phase2\deployment-config.json`, выполняет безопасную миграцию через `migrate-awatch-rus-paths.ps1`: backup, остановка задач, перенос данных, переписывание путей, пересоздание scheduled tasks и validation;
|
||||
- выполняет `deploy-ensemble.ps1` (deploy + hardening/recovery) с policy/rules из AWatch-rus toolkit;
|
||||
- после deploy принудительно запускает `ActivityWatch Recovery` и все `ActivityWatch Launch *` задачи;
|
||||
- выполняет API smoke-check bucket `aw-watcher-afk_<COMPUTERNAME>` и ожидает свежие `not-afk` события;
|
||||
@@ -110,6 +111,9 @@ Playbook:
|
||||
- `aw_windows_install_root: 'C:\Program Files\AWatch-rus\bin'` — каталог бинарников, совпадает с InnoSetup `AwDefaultInstallRoot`;
|
||||
- `aw_windows_state_root: 'C:\ProgramData\AWatch-rus'` — каталог состояния/отчётов, совпадает с InnoSetup `AwDefaultStateRoot`;
|
||||
- `aw_windows_validation_remote_path: '{{ aw_windows_state_root }}\aw_validate_ansible.json'` — отчёт Ansible-валидации хранится рядом с `ensemble-report-*.json`;
|
||||
- `aw_windows_migration_enabled: true` — включить guard миграции текущего production из `ActivityWatch-Phase2` в единый `AWatch-rus`;
|
||||
- `aw_windows_legacy_install_root` / `aw_windows_legacy_state_root` — старые production paths, откуда выполняется перенос;
|
||||
- `aw_windows_migration_report_remote_path` — JSON-отчёт о миграции на Windows-хосте;
|
||||
- `aw_windows_package_version`, `aw_windows_package_url`, `aw_windows_package_zip_path` — версия и источник Windows-пакета ActivityWatch;
|
||||
- `aw_windows_api_smoke_check_bucket: ""` — автоматически использовать `aw-watcher-afk_<COMPUTERNAME>`;
|
||||
- `aw_windows_fail_on_validation_error: true` — завершать playbook ошибкой, если `validate-deployment.ps1` возвращает `overallOk=false`;
|
||||
|
||||
@@ -42,6 +42,10 @@
|
||||
aw_windows_api_smoke_check_bucket: ""
|
||||
aw_windows_api_smoke_check_limit: 10
|
||||
aw_windows_fail_on_validation_error: true
|
||||
aw_windows_migration_enabled: true
|
||||
aw_windows_legacy_install_root: "C:\\Program Files\\ActivityWatch-Phase2"
|
||||
aw_windows_legacy_state_root: "C:\\ProgramData\\ActivityWatch-Phase2"
|
||||
aw_windows_migration_report_remote_path: "{{ aw_windows_state_root }}\\aw_migration_ansible.json"
|
||||
|
||||
tasks:
|
||||
- name: Проверить обязательные переменные
|
||||
@@ -74,6 +78,7 @@
|
||||
- browser-domains-native-collector.ps1
|
||||
- dlp-endpoint-signals-collector.ps1
|
||||
- worktime-session-collector.ps1
|
||||
- migrate-awatch-rus-paths.ps1
|
||||
- deploy-domain-users.ps1
|
||||
- deploy-ensemble.ps1
|
||||
- hardening-recovery.ps1
|
||||
@@ -89,6 +94,27 @@
|
||||
{{ user }}
|
||||
{% endfor -%}
|
||||
|
||||
- name: Проверить нужен ли migration с legacy ActivityWatch путей
|
||||
when: aw_windows_migration_enabled | bool
|
||||
ansible.windows.win_stat:
|
||||
path: "{{ aw_windows_legacy_state_root }}\\deployment-config.json"
|
||||
register: aw_windows_legacy_config
|
||||
|
||||
- name: Выполнить безопасную migration legacy prod в AWatch-rus
|
||||
when:
|
||||
- aw_windows_migration_enabled | bool
|
||||
- aw_windows_legacy_config.stat.exists | default(false)
|
||||
ansible.windows.win_powershell:
|
||||
script: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$result = & "{{ aw_windows_deploy_root }}\windows\migrate-awatch-rus-paths.ps1" `
|
||||
-OldInstallRoot "{{ aw_windows_legacy_install_root }}" `
|
||||
-OldStateRoot "{{ aw_windows_legacy_state_root }}" `
|
||||
-NewInstallRoot "{{ aw_windows_install_root }}" `
|
||||
-NewStateRoot "{{ aw_windows_state_root }}" `
|
||||
-ToolkitRoot "{{ aw_windows_deploy_root }}\windows"
|
||||
$result | ConvertTo-Json -Depth 8 | Out-File -FilePath "{{ aw_windows_migration_report_remote_path }}" -Encoding utf8
|
||||
|
||||
- name: Запустить Windows/RDP ensemble развёртывание
|
||||
ansible.windows.win_powershell:
|
||||
script: |
|
||||
|
||||
@@ -37,6 +37,12 @@ aw_windows_validation_remote_path: "{{ aw_windows_state_root }}\\aw_validate_ans
|
||||
aw_windows_validation_local_dir: "/tmp/aw-rus-validation"
|
||||
aw_windows_fail_on_validation_error: true
|
||||
|
||||
# Безопасная миграция текущего прода со старых путей в единый профиль AWatch-rus.
|
||||
aw_windows_migration_enabled: true
|
||||
aw_windows_legacy_install_root: "C:\\Program Files\\ActivityWatch-Phase2"
|
||||
aw_windows_legacy_state_root: "C:\\ProgramData\\ActivityWatch-Phase2"
|
||||
aw_windows_migration_report_remote_path: "{{ aw_windows_state_root }}\\aw_migration_ansible.json"
|
||||
|
||||
# По умолчанию AFK bucket вычисляется как aw-watcher-afk_<COMPUTERNAME>.
|
||||
# Задайте явное значение только если watcher пишет в нестандартный bucket.
|
||||
aw_windows_api_smoke_check_enabled: true
|
||||
|
||||
@@ -187,6 +187,19 @@ Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process
|
||||
|
||||
### 3.2 Массовое доменное развёртывание (рекомендуется)
|
||||
|
||||
Если текущий production ещё работает в старых каталогах
|
||||
`C:\Program Files\ActivityWatch-Phase2` и `C:\ProgramData\ActivityWatch-Phase2`,
|
||||
сначала выполните безопасную миграцию:
|
||||
|
||||
```powershell
|
||||
C:\Program Files\AWatch-rus\windows\migrate-awatch-rus-paths.ps1 -WhatIf
|
||||
C:\Program Files\AWatch-rus\windows\migrate-awatch-rus-paths.ps1
|
||||
```
|
||||
|
||||
Скрипт остановит `ActivityWatch Recovery`/`ActivityWatch Launch *`, создаст backup в
|
||||
`C:\ProgramData\AWatch-rus\migration-backups\...`, перенесёт файлы в единые пути,
|
||||
пересоздаст `deployment-config.json`/scheduled tasks и запустит validation.
|
||||
|
||||
Пример со списком пользователей:
|
||||
|
||||
```powershell
|
||||
|
||||
@@ -143,9 +143,37 @@ CSV-формат: колонка `User`, `Username`, `SamAccountName` или `Lo
|
||||
|
||||
Если список уже содержит `DOMAIN\user`, параметр `-Domain` не нужен.
|
||||
|
||||
## Рекомендуемый phased rollout (изолированный профиль)
|
||||
## Безопасная миграция текущего production
|
||||
|
||||
Для безопасного параллельного запуска рядом с legacy-инсталляцией используйте отдельные пути:
|
||||
Если текущий RDP production уже работает в `C:\Program Files\ActivityWatch-Phase2` и
|
||||
`C:\ProgramData\ActivityWatch-Phase2`, не запускайте обычный update без миграции.
|
||||
Сначала выполните перенос в единый профиль AWatch-rus:
|
||||
|
||||
```powershell
|
||||
C:\Program Files\AWatch-rus\windows\migrate-awatch-rus-paths.ps1 -WhatIf
|
||||
C:\Program Files\AWatch-rus\windows\migrate-awatch-rus-paths.ps1
|
||||
```
|
||||
|
||||
Скрипт делает безопасный порядок:
|
||||
|
||||
1. Находит старый `deployment-config.json`.
|
||||
2. Останавливает `ActivityWatch Recovery` и `ActivityWatch Launch *`.
|
||||
3. Создаёт backup старых и новых каталогов в
|
||||
`C:\ProgramData\AWatch-rus\migration-backups\YYYYMMDD-HHMMSS`.
|
||||
4. Копирует бинарники/состояние в единые пути:
|
||||
- `C:\Program Files\AWatch-rus\bin`
|
||||
- `C:\ProgramData\AWatch-rus`
|
||||
5. Переписывает пути в `deployment-config.json`.
|
||||
6. Пересоздаёт launcher/recovery scripts и scheduled tasks.
|
||||
7. Запускает `validate-deployment.ps1`; при ошибке оставляет backup path в сообщении.
|
||||
|
||||
Ansible playbook `ansible/deploy_aw_windows.yml` выполняет этот migration guard
|
||||
автоматически, если на хосте найден
|
||||
`C:\ProgramData\ActivityWatch-Phase2\deployment-config.json`.
|
||||
|
||||
## Рекомендуемый rollout
|
||||
|
||||
Для запуска после миграции используйте единые пути:
|
||||
|
||||
```powershell
|
||||
.\windows\deploy-domain-users.ps1 `
|
||||
@@ -207,7 +235,7 @@ Single-user pilot в таком же стиле:
|
||||
- `C:\ProgramData\AWatch-rus\dlp-policy.json` — активная DLP-политика.
|
||||
- `C:\ProgramData\AWatch-rus\logs\` — логи collector'а.
|
||||
|
||||
Для phased rollout те же файлы формируются в каталоге `StateRoot`, переданном параметром.
|
||||
При переопределении `StateRoot` те же файлы формируются в указанном каталоге.
|
||||
|
||||
## Повторный прогон
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
0754dcba7c651d67a40e09446868d2fcae623a100d4fb01794dd96272d353b49 install-kit-awindows-20260427-211240/README-INSTALL-KIT.txt
|
||||
8d239c67078eb0cbff5ecdb039ec147d6fbe90fcbd6a9adea87928d87b524aae install-kit-awindows-20260427-211240/ansible/README.md
|
||||
089595753398c8b82980919d230dafac548c3ba36817f5c96a68051582f9faa3 install-kit-awindows-20260427-211240/ansible/README.md
|
||||
412bb766bbf0791c3593f38daa771d5d0aa58cc1f2d3c9010fcd4588d0fe87df install-kit-awindows-20260427-211240/ansible/deploy_aw_pfsense_poller.yml
|
||||
90ac38a33918fcd3620f078f51fbf7c6a9d7f8fd1a34d16b38cb3ac45678b0d7 install-kit-awindows-20260427-211240/ansible/deploy_aw_server.yml
|
||||
d44adbc4565566039b5fb9dc870b9229c01408208ca8c08082bb5d2c097aa276 install-kit-awindows-20260427-211240/ansible/deploy_aw_windows.yml
|
||||
a649eeb57472fb259d248983c486f0474bfb7317600feb98f76a80e7af7e4549 install-kit-awindows-20260427-211240/ansible/deploy_aw_windows.yml
|
||||
531bfec24f86d28a06e5c0d73005489a818e2c7b1cce1d98f524a3f76802b8ee install-kit-awindows-20260427-211240/ansible/group_vars/all.example.yml
|
||||
95696c243ab331f06e77a40a9800c4b6668de77675ebbdf2ef54ae49e1b18874 install-kit-awindows-20260427-211240/ansible/group_vars/pfsense-poller.example.yml
|
||||
c5cab36645065815571c99f6d360f910dcccbb54b780c8bfd526a6cdc3684e19 install-kit-awindows-20260427-211240/ansible/group_vars/proxmox-matrix.example.yml
|
||||
35a33c8a1c75ded5e85c6b79e0b3efde07959ff61ee5f66d83b7e0c2abe87fc5 install-kit-awindows-20260427-211240/ansible/group_vars/proxmox.example.yml
|
||||
a4333199f454d5c795d3c240ee072342a90d0ed1fba6073be78c3c5e9cb8b32f install-kit-awindows-20260427-211240/ansible/group_vars/windows.example.yml
|
||||
7c468f252e328fd3bb7ee776a45feea88efc4b438dfef55b832da4eea867aaf2 install-kit-awindows-20260427-211240/ansible/group_vars/windows.example.yml
|
||||
195e7dbdb91f4e77db3263bd0812301ddc912a37ba1519688768bb64a2887567 install-kit-awindows-20260427-211240/ansible/install_full_stack.yml
|
||||
2e4e94d90143923fefd3ec1257d0ec57daa3e96450d85471bc2c418aae37e105 install-kit-awindows-20260427-211240/ansible/inventory.example.ini
|
||||
d9e43352fd6bdb647db9754ab2c557b6bb27f88f51b2bf23d8c19227e535e7b9 install-kit-awindows-20260427-211240/ansible/provision_proxmox_ct_and_deploy_aw.yml
|
||||
@@ -34,6 +34,7 @@ b497400a1ba57cddf28dc8e217115dc85eccb67150cbdbb6a81abd804ed20109 install-kit-aw
|
||||
8857f3e17f3f3ed6f211ce7f0a0c46c586a2548541078401ee3befaa20924b3d install-kit-awindows-20260427-211240/windows/dlp-endpoint-signals-collector.ps1
|
||||
aef0032edd9b1e0c54f7b575664ed511dfc6cb53364e7496cbc95e137678e11a install-kit-awindows-20260427-211240/windows/dlp-policy.example.json
|
||||
f03886caf56c6838e8a163d6b48d1f229e83a5682aeeb447c3a65f13d62dbca4 install-kit-awindows-20260427-211240/windows/hardening-recovery.ps1
|
||||
71911cd53ad0abd8bf83994f8a79bbbfe2c0eaf4f4c5f6c2d636dd7786191c2f install-kit-awindows-20260427-211240/windows/migrate-awatch-rus-paths.ps1
|
||||
5dcf249742bd82fa0c803c878bfa0a1344b7b14df85c05f12e8c205663aea158 install-kit-awindows-20260427-211240/windows/validate-deployment.ps1
|
||||
731098681d89b9af6f3872abd586ac3b1faba2d7f9340211e503f52ad0243b3f install-kit-awindows-20260427-211240/windows/web-category-rules.example.json
|
||||
41171f0d7ed1e8b00dd0faf1a4b75c9cb063fd09aba8d333851a7a31fb297de1 install-kit-awindows-20260427-211240/windows/worktime-session-collector.ps1
|
||||
|
||||
@@ -93,6 +93,7 @@ ansible-playbook -i inventory.ini deploy_aw_windows.yml
|
||||
Playbook:
|
||||
|
||||
- выгружает полный `windows/*` toolkit на целевой хост в InnoSetup-compatible каталог `C:\Program Files\AWatch-rus\windows`, включая DLP и `worktime-session-collector.ps1`;
|
||||
- если найден legacy config `C:\ProgramData\ActivityWatch-Phase2\deployment-config.json`, выполняет безопасную миграцию через `migrate-awatch-rus-paths.ps1`: backup, остановка задач, перенос данных, переписывание путей, пересоздание scheduled tasks и validation;
|
||||
- выполняет `deploy-ensemble.ps1` (deploy + hardening/recovery) с policy/rules из AWatch-rus toolkit;
|
||||
- после deploy принудительно запускает `ActivityWatch Recovery` и все `ActivityWatch Launch *` задачи;
|
||||
- выполняет API smoke-check bucket `aw-watcher-afk_<COMPUTERNAME>` и ожидает свежие `not-afk` события;
|
||||
@@ -110,6 +111,9 @@ Playbook:
|
||||
- `aw_windows_install_root: 'C:\Program Files\AWatch-rus\bin'` — каталог бинарников, совпадает с InnoSetup `AwDefaultInstallRoot`;
|
||||
- `aw_windows_state_root: 'C:\ProgramData\AWatch-rus'` — каталог состояния/отчётов, совпадает с InnoSetup `AwDefaultStateRoot`;
|
||||
- `aw_windows_validation_remote_path: '{{ aw_windows_state_root }}\aw_validate_ansible.json'` — отчёт Ansible-валидации хранится рядом с `ensemble-report-*.json`;
|
||||
- `aw_windows_migration_enabled: true` — включить guard миграции текущего production из `ActivityWatch-Phase2` в единый `AWatch-rus`;
|
||||
- `aw_windows_legacy_install_root` / `aw_windows_legacy_state_root` — старые production paths, откуда выполняется перенос;
|
||||
- `aw_windows_migration_report_remote_path` — JSON-отчёт о миграции на Windows-хосте;
|
||||
- `aw_windows_package_version`, `aw_windows_package_url`, `aw_windows_package_zip_path` — версия и источник Windows-пакета ActivityWatch;
|
||||
- `aw_windows_api_smoke_check_bucket: ""` — автоматически использовать `aw-watcher-afk_<COMPUTERNAME>`;
|
||||
- `aw_windows_fail_on_validation_error: true` — завершать playbook ошибкой, если `validate-deployment.ps1` возвращает `overallOk=false`;
|
||||
|
||||
@@ -42,6 +42,10 @@
|
||||
aw_windows_api_smoke_check_bucket: ""
|
||||
aw_windows_api_smoke_check_limit: 10
|
||||
aw_windows_fail_on_validation_error: true
|
||||
aw_windows_migration_enabled: true
|
||||
aw_windows_legacy_install_root: "C:\\Program Files\\ActivityWatch-Phase2"
|
||||
aw_windows_legacy_state_root: "C:\\ProgramData\\ActivityWatch-Phase2"
|
||||
aw_windows_migration_report_remote_path: "{{ aw_windows_state_root }}\\aw_migration_ansible.json"
|
||||
|
||||
tasks:
|
||||
- name: Проверить обязательные переменные
|
||||
@@ -74,6 +78,7 @@
|
||||
- browser-domains-native-collector.ps1
|
||||
- dlp-endpoint-signals-collector.ps1
|
||||
- worktime-session-collector.ps1
|
||||
- migrate-awatch-rus-paths.ps1
|
||||
- deploy-domain-users.ps1
|
||||
- deploy-ensemble.ps1
|
||||
- hardening-recovery.ps1
|
||||
@@ -89,6 +94,27 @@
|
||||
{{ user }}
|
||||
{% endfor -%}
|
||||
|
||||
- name: Проверить нужен ли migration с legacy ActivityWatch путей
|
||||
when: aw_windows_migration_enabled | bool
|
||||
ansible.windows.win_stat:
|
||||
path: "{{ aw_windows_legacy_state_root }}\\deployment-config.json"
|
||||
register: aw_windows_legacy_config
|
||||
|
||||
- name: Выполнить безопасную migration legacy prod в AWatch-rus
|
||||
when:
|
||||
- aw_windows_migration_enabled | bool
|
||||
- aw_windows_legacy_config.stat.exists | default(false)
|
||||
ansible.windows.win_powershell:
|
||||
script: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$result = & "{{ aw_windows_deploy_root }}\windows\migrate-awatch-rus-paths.ps1" `
|
||||
-OldInstallRoot "{{ aw_windows_legacy_install_root }}" `
|
||||
-OldStateRoot "{{ aw_windows_legacy_state_root }}" `
|
||||
-NewInstallRoot "{{ aw_windows_install_root }}" `
|
||||
-NewStateRoot "{{ aw_windows_state_root }}" `
|
||||
-ToolkitRoot "{{ aw_windows_deploy_root }}\windows"
|
||||
$result | ConvertTo-Json -Depth 8 | Out-File -FilePath "{{ aw_windows_migration_report_remote_path }}" -Encoding utf8
|
||||
|
||||
- name: Запустить Windows/RDP ensemble развёртывание
|
||||
ansible.windows.win_powershell:
|
||||
script: |
|
||||
|
||||
@@ -37,6 +37,12 @@ aw_windows_validation_remote_path: "{{ aw_windows_state_root }}\\aw_validate_ans
|
||||
aw_windows_validation_local_dir: "/tmp/aw-rus-validation"
|
||||
aw_windows_fail_on_validation_error: true
|
||||
|
||||
# Безопасная миграция текущего прода со старых путей в единый профиль AWatch-rus.
|
||||
aw_windows_migration_enabled: true
|
||||
aw_windows_legacy_install_root: "C:\\Program Files\\ActivityWatch-Phase2"
|
||||
aw_windows_legacy_state_root: "C:\\ProgramData\\ActivityWatch-Phase2"
|
||||
aw_windows_migration_report_remote_path: "{{ aw_windows_state_root }}\\aw_migration_ansible.json"
|
||||
|
||||
# По умолчанию AFK bucket вычисляется как aw-watcher-afk_<COMPUTERNAME>.
|
||||
# Задайте явное значение только если watcher пишет в нестандартный bucket.
|
||||
aw_windows_api_smoke_check_enabled: true
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
[CmdletBinding(SupportsShouldProcess = $true)]
|
||||
param(
|
||||
[string]$OldInstallRoot = 'C:\Program Files\ActivityWatch-Phase2',
|
||||
[string]$OldStateRoot = 'C:\ProgramData\ActivityWatch-Phase2',
|
||||
[string]$NewInstallRoot = 'C:\Program Files\AWatch-rus\bin',
|
||||
[string]$NewStateRoot = 'C:\ProgramData\AWatch-rus',
|
||||
[string]$ToolkitRoot = 'C:\Program Files\AWatch-rus\windows',
|
||||
[switch]$SkipValidation
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$modulePath = Join-Path $PSScriptRoot 'ActivityWatch.Windows.Common.psm1'
|
||||
Import-Module $modulePath -Force
|
||||
|
||||
Assert-Administrator
|
||||
|
||||
function Copy-DirectoryContents {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Source,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Destination
|
||||
)
|
||||
|
||||
if (-not (Test-Path -LiteralPath $Source)) {
|
||||
return
|
||||
}
|
||||
|
||||
New-ActivityWatchDirectory -Path $Destination
|
||||
Copy-Item -Path (Join-Path $Source '*') -Destination $Destination -Recurse -Force
|
||||
}
|
||||
|
||||
function Copy-IfExists {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Source,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Destination
|
||||
)
|
||||
|
||||
if (Test-Path -LiteralPath $Source) {
|
||||
Copy-Item -LiteralPath $Source -Destination $Destination -Force
|
||||
}
|
||||
}
|
||||
|
||||
function Convert-PathValue {
|
||||
param(
|
||||
[AllowNull()]
|
||||
[string]$Value
|
||||
)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($Value)) {
|
||||
return $Value
|
||||
}
|
||||
|
||||
return $Value.Replace($OldInstallRoot, $NewInstallRoot).Replace($OldStateRoot, $NewStateRoot)
|
||||
}
|
||||
|
||||
function Stop-AWatchTaskSet {
|
||||
foreach ($task in @(Get-ScheduledTask | Where-Object { $_.TaskName -eq 'ActivityWatch Recovery' -or $_.TaskName -like 'ActivityWatch Launch *' })) {
|
||||
Stop-ScheduledTask -TaskName $task.TaskName -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
function Get-ExistingAWatchConfig {
|
||||
$newConfigPath = Join-Path $NewStateRoot 'deployment-config.json'
|
||||
$oldConfigPath = Join-Path $OldStateRoot 'deployment-config.json'
|
||||
|
||||
if (Test-Path -LiteralPath $oldConfigPath) {
|
||||
return [pscustomobject]@{
|
||||
Path = $oldConfigPath
|
||||
Config = Read-ActivityWatchDeploymentConfig -Path $oldConfigPath
|
||||
}
|
||||
}
|
||||
|
||||
if (Test-Path -LiteralPath $newConfigPath) {
|
||||
return [pscustomobject]@{
|
||||
Path = $newConfigPath
|
||||
Config = Read-ActivityWatchDeploymentConfig -Path $newConfigPath
|
||||
}
|
||||
}
|
||||
|
||||
throw "Не найден deployment-config.json ни в $OldStateRoot, ни в $NewStateRoot."
|
||||
}
|
||||
|
||||
function Update-AWatchConfigPaths {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[pscustomobject]$Config
|
||||
)
|
||||
|
||||
$logsRoot = Join-Path $NewStateRoot 'logs'
|
||||
$Config.paths.installRoot = $NewInstallRoot
|
||||
$Config.paths.stateRoot = $NewStateRoot
|
||||
$Config.paths.logsRoot = $logsRoot
|
||||
$Config.paths.collectorScript = Join-Path $NewStateRoot 'browser-domains-native-collector.ps1'
|
||||
$Config.paths.endpointCollectorScript = Join-Path $NewStateRoot 'dlp-endpoint-signals-collector.ps1'
|
||||
if ($Config.paths.PSObject.Properties.Name -contains 'sessionCollectorScript') {
|
||||
$Config.paths.sessionCollectorScript = Join-Path $NewStateRoot 'worktime-session-collector.ps1'
|
||||
}
|
||||
$Config.paths.rulesPath = Join-Path $NewStateRoot 'web-category-rules.json'
|
||||
if ($Config.paths.PSObject.Properties.Name -contains 'policyPath') {
|
||||
$Config.paths.policyPath = Join-Path $NewStateRoot 'dlp-policy.json'
|
||||
}
|
||||
$Config.paths.launchScript = Join-Path $NewStateRoot 'launch-watchers.ps1'
|
||||
$Config.paths.recoveryScript = Join-Path $NewStateRoot 'recovery-loop.ps1'
|
||||
|
||||
if ($Config.PSObject.Properties.Name -contains 'incidentCapture' -and $Config.incidentCapture.PSObject.Properties.Name -contains 'artifactsRoot') {
|
||||
$Config.incidentCapture.artifactsRoot = Convert-PathValue -Value ([string]$Config.incidentCapture.artifactsRoot)
|
||||
}
|
||||
|
||||
return $Config
|
||||
}
|
||||
|
||||
$existing = Get-ExistingAWatchConfig
|
||||
$backupRoot = Join-Path $NewStateRoot ('migration-backups\' + (Get-Date -Format 'yyyyMMdd-HHmmss'))
|
||||
$newConfigPath = Join-Path $NewStateRoot 'deployment-config.json'
|
||||
$newLogsRoot = Join-Path $NewStateRoot 'logs'
|
||||
|
||||
$summary = [ordered]@{
|
||||
sourceConfig = $existing.Path
|
||||
oldInstallRoot = $OldInstallRoot
|
||||
oldStateRoot = $OldStateRoot
|
||||
newInstallRoot = $NewInstallRoot
|
||||
newStateRoot = $NewStateRoot
|
||||
backupRoot = $backupRoot
|
||||
actions = @(
|
||||
'stop ActivityWatch scheduled tasks',
|
||||
'backup old/new install and state directories',
|
||||
'copy old install/state contents to AWatch-rus paths',
|
||||
'rewrite deployment-config.json paths',
|
||||
'regenerate launcher/recovery scripts',
|
||||
're-register scheduled tasks',
|
||||
'run validate-deployment.ps1'
|
||||
)
|
||||
}
|
||||
|
||||
if ($WhatIfPreference) {
|
||||
return [pscustomobject]$summary
|
||||
}
|
||||
|
||||
if ($PSCmdlet.ShouldProcess($env:COMPUTERNAME, 'Миграция ActivityWatch Windows/RDP путей в AWatch-rus')) {
|
||||
New-ActivityWatchDirectory -Path $NewStateRoot
|
||||
New-ActivityWatchDirectory -Path $backupRoot
|
||||
|
||||
Stop-AWatchTaskSet
|
||||
|
||||
foreach ($item in @(
|
||||
@{ Source = $OldInstallRoot; Name = 'old-install' },
|
||||
@{ Source = $OldStateRoot; Name = 'old-state' },
|
||||
@{ Source = $NewInstallRoot; Name = 'new-install' },
|
||||
@{ Source = $NewStateRoot; Name = 'new-state' }
|
||||
)) {
|
||||
if (Test-Path -LiteralPath $item.Source) {
|
||||
Copy-Item -LiteralPath $item.Source -Destination (Join-Path $backupRoot $item.Name) -Recurse -Force
|
||||
}
|
||||
}
|
||||
|
||||
Copy-DirectoryContents -Source $OldInstallRoot -Destination $NewInstallRoot
|
||||
Copy-DirectoryContents -Source $OldStateRoot -Destination $NewStateRoot
|
||||
New-ActivityWatchDirectory -Path $newLogsRoot
|
||||
|
||||
foreach ($file in @(
|
||||
'browser-domains-native-collector.ps1',
|
||||
'dlp-endpoint-signals-collector.ps1',
|
||||
'worktime-session-collector.ps1',
|
||||
'web-category-rules.example.json',
|
||||
'dlp-policy.example.json'
|
||||
)) {
|
||||
Copy-IfExists -Source (Join-Path $ToolkitRoot $file) -Destination (Join-Path $NewStateRoot $file)
|
||||
}
|
||||
|
||||
Copy-IfExists -Source (Join-Path $OldStateRoot 'web-category-rules.json') -Destination (Join-Path $NewStateRoot 'web-category-rules.json')
|
||||
Copy-IfExists -Source (Join-Path $OldStateRoot 'dlp-policy.json') -Destination (Join-Path $NewStateRoot 'dlp-policy.json')
|
||||
if (-not (Test-Path -LiteralPath (Join-Path $NewStateRoot 'web-category-rules.json'))) {
|
||||
Copy-IfExists -Source (Join-Path $NewStateRoot 'web-category-rules.example.json') -Destination (Join-Path $NewStateRoot 'web-category-rules.json')
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath (Join-Path $NewStateRoot 'dlp-policy.json'))) {
|
||||
Copy-IfExists -Source (Join-Path $NewStateRoot 'dlp-policy.example.json') -Destination (Join-Path $NewStateRoot 'dlp-policy.json')
|
||||
}
|
||||
|
||||
$config = Update-AWatchConfigPaths -Config $existing.Config
|
||||
Write-ActivityWatchDeploymentConfig -Config $config -Path $newConfigPath
|
||||
Write-ActivityWatchLaunchScript -Path $config.paths.launchScript -ConfigPath $newConfigPath
|
||||
Write-ActivityWatchRecoveryScript -Path $config.paths.recoveryScript -ConfigPath $newConfigPath
|
||||
|
||||
$taskDefinitions = @($config.userTasks)
|
||||
Set-ActivityWatchAcl -InstallRoot $NewInstallRoot -StateRoot $NewStateRoot -LogsRoot $newLogsRoot
|
||||
Register-ActivityWatchUserTasks -TaskDefinitions $taskDefinitions -LaunchScriptPath $config.paths.launchScript -ConfigPath $newConfigPath
|
||||
Register-ActivityWatchRecoveryTask -TaskName $config.recovery.taskName -RecoveryScriptPath $config.paths.recoveryScript -ConfigPath $newConfigPath
|
||||
Start-ActivityWatchTasks -TaskDefinitions $taskDefinitions -RecoveryTaskName $config.recovery.taskName
|
||||
Start-Sleep -Seconds 5
|
||||
|
||||
if (-not $SkipValidation) {
|
||||
$validateScript = Join-Path $ToolkitRoot 'validate-deployment.ps1'
|
||||
if (-not (Test-Path -LiteralPath $validateScript)) {
|
||||
$validateScript = Join-Path $PSScriptRoot 'validate-deployment.ps1'
|
||||
}
|
||||
$report = & $validateScript -ConfigPath $newConfigPath
|
||||
if (-not [bool]$report.overallOk) {
|
||||
throw "Миграция выполнена, но validation завершился ошибкой. Backup: $backupRoot"
|
||||
}
|
||||
}
|
||||
|
||||
[pscustomobject]@{
|
||||
migrated = $true
|
||||
backupRoot = $backupRoot
|
||||
configPath = $newConfigPath
|
||||
installRoot = $NewInstallRoot
|
||||
stateRoot = $NewStateRoot
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@ cat > windows/installkit/innosetup/innosetup-rdp-package-filelist.md <<'EOF'
|
||||
- `windows/deploy-ensemble.ps1`
|
||||
- `windows/hardening-recovery.ps1`
|
||||
- `windows/validate-deployment.ps1`
|
||||
- `windows/migrate-awatch-rus-paths.ps1`
|
||||
|
||||
### 1.3 Коллекторы
|
||||
- `windows/worktime-session-collector.ps1` (RDP/session presence)
|
||||
@@ -81,6 +82,7 @@ cat > windows/installkit/innosetup/innosetup-rdp-package-filelist.md <<'EOF'
|
||||
- `windows\deploy-ensemble.ps1`
|
||||
- `windows\hardening-recovery.ps1`
|
||||
- `windows\validate-deployment.ps1`
|
||||
- `windows\migrate-awatch-rus-paths.ps1`
|
||||
- `windows\worktime-session-collector.ps1`
|
||||
- `windows\browser-domains-native-collector.ps1`
|
||||
- `windows\dlp-endpoint-signals-collector.ps1`
|
||||
|
||||
@@ -38,6 +38,7 @@ Source: "..\..\deploy-domain-users.ps1"; DestDir: "{app}\windows"; Flags: ignore
|
||||
Source: "..\..\deploy-ensemble.ps1"; DestDir: "{app}\windows"; Flags: ignoreversion
|
||||
Source: "..\..\hardening-recovery.ps1"; DestDir: "{app}\windows"; Flags: ignoreversion
|
||||
Source: "..\..\validate-deployment.ps1"; DestDir: "{app}\windows"; Flags: ignoreversion
|
||||
Source: "..\..\migrate-awatch-rus-paths.ps1"; DestDir: "{app}\windows"; Flags: ignoreversion
|
||||
Source: "..\..\worktime-session-collector.ps1"; DestDir: "{app}\windows"; Flags: ignoreversion
|
||||
Source: "..\..\browser-domains-native-collector.ps1"; DestDir: "{app}\windows"; Flags: ignoreversion
|
||||
Source: "..\..\dlp-endpoint-signals-collector.ps1"; DestDir: "{app}\windows"; Flags: ignoreversion
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
- `windows/deploy-ensemble.ps1`
|
||||
- `windows/hardening-recovery.ps1`
|
||||
- `windows/validate-deployment.ps1`
|
||||
- `windows/migrate-awatch-rus-paths.ps1`
|
||||
|
||||
### 1.3 Коллекторы
|
||||
- `windows/worktime-session-collector.ps1` (RDP/session presence)
|
||||
@@ -76,6 +77,7 @@
|
||||
- `windows\deploy-ensemble.ps1`
|
||||
- `windows\hardening-recovery.ps1`
|
||||
- `windows\validate-deployment.ps1`
|
||||
- `windows\migrate-awatch-rus-paths.ps1`
|
||||
- `windows\worktime-session-collector.ps1`
|
||||
- `windows\browser-domains-native-collector.ps1`
|
||||
- `windows\dlp-endpoint-signals-collector.ps1`
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
[CmdletBinding(SupportsShouldProcess = $true)]
|
||||
param(
|
||||
[string]$OldInstallRoot = 'C:\Program Files\ActivityWatch-Phase2',
|
||||
[string]$OldStateRoot = 'C:\ProgramData\ActivityWatch-Phase2',
|
||||
[string]$NewInstallRoot = 'C:\Program Files\AWatch-rus\bin',
|
||||
[string]$NewStateRoot = 'C:\ProgramData\AWatch-rus',
|
||||
[string]$ToolkitRoot = 'C:\Program Files\AWatch-rus\windows',
|
||||
[switch]$SkipValidation
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$modulePath = Join-Path $PSScriptRoot 'ActivityWatch.Windows.Common.psm1'
|
||||
Import-Module $modulePath -Force
|
||||
|
||||
Assert-Administrator
|
||||
|
||||
function Copy-DirectoryContents {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Source,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Destination
|
||||
)
|
||||
|
||||
if (-not (Test-Path -LiteralPath $Source)) {
|
||||
return
|
||||
}
|
||||
|
||||
New-ActivityWatchDirectory -Path $Destination
|
||||
Copy-Item -Path (Join-Path $Source '*') -Destination $Destination -Recurse -Force
|
||||
}
|
||||
|
||||
function Copy-IfExists {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Source,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Destination
|
||||
)
|
||||
|
||||
if (Test-Path -LiteralPath $Source) {
|
||||
Copy-Item -LiteralPath $Source -Destination $Destination -Force
|
||||
}
|
||||
}
|
||||
|
||||
function Convert-PathValue {
|
||||
param(
|
||||
[AllowNull()]
|
||||
[string]$Value
|
||||
)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($Value)) {
|
||||
return $Value
|
||||
}
|
||||
|
||||
return $Value.Replace($OldInstallRoot, $NewInstallRoot).Replace($OldStateRoot, $NewStateRoot)
|
||||
}
|
||||
|
||||
function Stop-AWatchTaskSet {
|
||||
foreach ($task in @(Get-ScheduledTask | Where-Object { $_.TaskName -eq 'ActivityWatch Recovery' -or $_.TaskName -like 'ActivityWatch Launch *' })) {
|
||||
Stop-ScheduledTask -TaskName $task.TaskName -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
function Get-ExistingAWatchConfig {
|
||||
$newConfigPath = Join-Path $NewStateRoot 'deployment-config.json'
|
||||
$oldConfigPath = Join-Path $OldStateRoot 'deployment-config.json'
|
||||
|
||||
if (Test-Path -LiteralPath $oldConfigPath) {
|
||||
return [pscustomobject]@{
|
||||
Path = $oldConfigPath
|
||||
Config = Read-ActivityWatchDeploymentConfig -Path $oldConfigPath
|
||||
}
|
||||
}
|
||||
|
||||
if (Test-Path -LiteralPath $newConfigPath) {
|
||||
return [pscustomobject]@{
|
||||
Path = $newConfigPath
|
||||
Config = Read-ActivityWatchDeploymentConfig -Path $newConfigPath
|
||||
}
|
||||
}
|
||||
|
||||
throw "Не найден deployment-config.json ни в $OldStateRoot, ни в $NewStateRoot."
|
||||
}
|
||||
|
||||
function Update-AWatchConfigPaths {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[pscustomobject]$Config
|
||||
)
|
||||
|
||||
$logsRoot = Join-Path $NewStateRoot 'logs'
|
||||
$Config.paths.installRoot = $NewInstallRoot
|
||||
$Config.paths.stateRoot = $NewStateRoot
|
||||
$Config.paths.logsRoot = $logsRoot
|
||||
$Config.paths.collectorScript = Join-Path $NewStateRoot 'browser-domains-native-collector.ps1'
|
||||
$Config.paths.endpointCollectorScript = Join-Path $NewStateRoot 'dlp-endpoint-signals-collector.ps1'
|
||||
if ($Config.paths.PSObject.Properties.Name -contains 'sessionCollectorScript') {
|
||||
$Config.paths.sessionCollectorScript = Join-Path $NewStateRoot 'worktime-session-collector.ps1'
|
||||
}
|
||||
$Config.paths.rulesPath = Join-Path $NewStateRoot 'web-category-rules.json'
|
||||
if ($Config.paths.PSObject.Properties.Name -contains 'policyPath') {
|
||||
$Config.paths.policyPath = Join-Path $NewStateRoot 'dlp-policy.json'
|
||||
}
|
||||
$Config.paths.launchScript = Join-Path $NewStateRoot 'launch-watchers.ps1'
|
||||
$Config.paths.recoveryScript = Join-Path $NewStateRoot 'recovery-loop.ps1'
|
||||
|
||||
if ($Config.PSObject.Properties.Name -contains 'incidentCapture' -and $Config.incidentCapture.PSObject.Properties.Name -contains 'artifactsRoot') {
|
||||
$Config.incidentCapture.artifactsRoot = Convert-PathValue -Value ([string]$Config.incidentCapture.artifactsRoot)
|
||||
}
|
||||
|
||||
return $Config
|
||||
}
|
||||
|
||||
$existing = Get-ExistingAWatchConfig
|
||||
$backupRoot = Join-Path $NewStateRoot ('migration-backups\' + (Get-Date -Format 'yyyyMMdd-HHmmss'))
|
||||
$newConfigPath = Join-Path $NewStateRoot 'deployment-config.json'
|
||||
$newLogsRoot = Join-Path $NewStateRoot 'logs'
|
||||
|
||||
$summary = [ordered]@{
|
||||
sourceConfig = $existing.Path
|
||||
oldInstallRoot = $OldInstallRoot
|
||||
oldStateRoot = $OldStateRoot
|
||||
newInstallRoot = $NewInstallRoot
|
||||
newStateRoot = $NewStateRoot
|
||||
backupRoot = $backupRoot
|
||||
actions = @(
|
||||
'stop ActivityWatch scheduled tasks',
|
||||
'backup old/new install and state directories',
|
||||
'copy old install/state contents to AWatch-rus paths',
|
||||
'rewrite deployment-config.json paths',
|
||||
'regenerate launcher/recovery scripts',
|
||||
're-register scheduled tasks',
|
||||
'run validate-deployment.ps1'
|
||||
)
|
||||
}
|
||||
|
||||
if ($WhatIfPreference) {
|
||||
return [pscustomobject]$summary
|
||||
}
|
||||
|
||||
if ($PSCmdlet.ShouldProcess($env:COMPUTERNAME, 'Миграция ActivityWatch Windows/RDP путей в AWatch-rus')) {
|
||||
New-ActivityWatchDirectory -Path $NewStateRoot
|
||||
New-ActivityWatchDirectory -Path $backupRoot
|
||||
|
||||
Stop-AWatchTaskSet
|
||||
|
||||
foreach ($item in @(
|
||||
@{ Source = $OldInstallRoot; Name = 'old-install' },
|
||||
@{ Source = $OldStateRoot; Name = 'old-state' },
|
||||
@{ Source = $NewInstallRoot; Name = 'new-install' },
|
||||
@{ Source = $NewStateRoot; Name = 'new-state' }
|
||||
)) {
|
||||
if (Test-Path -LiteralPath $item.Source) {
|
||||
Copy-Item -LiteralPath $item.Source -Destination (Join-Path $backupRoot $item.Name) -Recurse -Force
|
||||
}
|
||||
}
|
||||
|
||||
Copy-DirectoryContents -Source $OldInstallRoot -Destination $NewInstallRoot
|
||||
Copy-DirectoryContents -Source $OldStateRoot -Destination $NewStateRoot
|
||||
New-ActivityWatchDirectory -Path $newLogsRoot
|
||||
|
||||
foreach ($file in @(
|
||||
'browser-domains-native-collector.ps1',
|
||||
'dlp-endpoint-signals-collector.ps1',
|
||||
'worktime-session-collector.ps1',
|
||||
'web-category-rules.example.json',
|
||||
'dlp-policy.example.json'
|
||||
)) {
|
||||
Copy-IfExists -Source (Join-Path $ToolkitRoot $file) -Destination (Join-Path $NewStateRoot $file)
|
||||
}
|
||||
|
||||
Copy-IfExists -Source (Join-Path $OldStateRoot 'web-category-rules.json') -Destination (Join-Path $NewStateRoot 'web-category-rules.json')
|
||||
Copy-IfExists -Source (Join-Path $OldStateRoot 'dlp-policy.json') -Destination (Join-Path $NewStateRoot 'dlp-policy.json')
|
||||
if (-not (Test-Path -LiteralPath (Join-Path $NewStateRoot 'web-category-rules.json'))) {
|
||||
Copy-IfExists -Source (Join-Path $NewStateRoot 'web-category-rules.example.json') -Destination (Join-Path $NewStateRoot 'web-category-rules.json')
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath (Join-Path $NewStateRoot 'dlp-policy.json'))) {
|
||||
Copy-IfExists -Source (Join-Path $NewStateRoot 'dlp-policy.example.json') -Destination (Join-Path $NewStateRoot 'dlp-policy.json')
|
||||
}
|
||||
|
||||
$config = Update-AWatchConfigPaths -Config $existing.Config
|
||||
Write-ActivityWatchDeploymentConfig -Config $config -Path $newConfigPath
|
||||
Write-ActivityWatchLaunchScript -Path $config.paths.launchScript -ConfigPath $newConfigPath
|
||||
Write-ActivityWatchRecoveryScript -Path $config.paths.recoveryScript -ConfigPath $newConfigPath
|
||||
|
||||
$taskDefinitions = @($config.userTasks)
|
||||
Set-ActivityWatchAcl -InstallRoot $NewInstallRoot -StateRoot $NewStateRoot -LogsRoot $newLogsRoot
|
||||
Register-ActivityWatchUserTasks -TaskDefinitions $taskDefinitions -LaunchScriptPath $config.paths.launchScript -ConfigPath $newConfigPath
|
||||
Register-ActivityWatchRecoveryTask -TaskName $config.recovery.taskName -RecoveryScriptPath $config.paths.recoveryScript -ConfigPath $newConfigPath
|
||||
Start-ActivityWatchTasks -TaskDefinitions $taskDefinitions -RecoveryTaskName $config.recovery.taskName
|
||||
Start-Sleep -Seconds 5
|
||||
|
||||
if (-not $SkipValidation) {
|
||||
$validateScript = Join-Path $ToolkitRoot 'validate-deployment.ps1'
|
||||
if (-not (Test-Path -LiteralPath $validateScript)) {
|
||||
$validateScript = Join-Path $PSScriptRoot 'validate-deployment.ps1'
|
||||
}
|
||||
$report = & $validateScript -ConfigPath $newConfigPath
|
||||
if (-not [bool]$report.overallOk) {
|
||||
throw "Миграция выполнена, но validation завершился ошибкой. Backup: $backupRoot"
|
||||
}
|
||||
}
|
||||
|
||||
[pscustomobject]@{
|
||||
migrated = $true
|
||||
backupRoot = $backupRoot
|
||||
configPath = $newConfigPath
|
||||
installRoot = $NewInstallRoot
|
||||
stateRoot = $NewStateRoot
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user