feat: add professional deployment ensemble, PowerShell orchestration, and CI standards

This commit is contained in:
igor04091968
2026-04-25 15:29:38 +03:00
parent 0821de0964
commit 63904d2548
17 changed files with 628 additions and 3 deletions
+1
View File
@@ -0,0 +1 @@
* @igor04091968
+13
View File
@@ -0,0 +1,13 @@
## Summary
- what changed
- why it changed
- risk and rollback notes
## Checklist
- [ ] No real secrets or credentials committed
- [ ] Server-side scripts validated (`bash -n`)
- [ ] PowerShell scripts validated (`Invoke-ScriptAnalyzer`)
- [ ] Docs updated (full paths and runbook steps)
- [ ] Rollback steps documented
+47
View File
@@ -0,0 +1,47 @@
name: shell-and-powershell-ci
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
shell-check:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install shellcheck
run: sudo apt-get update && sudo apt-get install -y shellcheck
- name: Run shellcheck
run: |
find . -type f -name "*.sh" -print0 | xargs -0 -r shellcheck
powershell-analyzer:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install PSScriptAnalyzer
shell: pwsh
run: |
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
Install-Module PSScriptAnalyzer -Scope CurrentUser -Force
- name: Analyze PowerShell scripts
shell: pwsh
run: |
$targets = @(
"windows/*.ps1",
"windows/*.psm1",
"windows/*.psd1"
)
$issues = Invoke-ScriptAnalyzer -Path $targets -Recurse -Severity Error,Warning
if ($issues) {
$issues | Format-Table -AutoSize
throw "PSScriptAnalyzer detected issues."
}
+12
View File
@@ -0,0 +1,12 @@
# Local secrets
secrets/deploy.secrets.env
# Runtime / reports
*.log
*.tmp
*.bak
windows/*.report.json
# IDE
.idea/
.vscode/
+27
View File
@@ -0,0 +1,27 @@
# Contributing
## Branching
- Работайте в feature-ветке, не пушьте напрямую в `main`.
- Именование: `feat/...`, `fix/...`, `docs/...`, `chore/...`.
## Commit style
- Предпочтительно Conventional Commits:
- `feat(...)`
- `fix(...)`
- `docs(...)`
- `chore(...)`
## Required checks before PR
- `bash -n` для всех `*.sh`.
- `Invoke-ScriptAnalyzer` для `windows/*.ps1`, `windows/*.psm1`, `windows/*.psd1`.
- Проверка, что нет секретов (`secrets/deploy.secrets.env` не должен быть в индексе git).
- Обновлены инструкции и runbook при изменении поведения.
## PR content
- Изменения и обоснование.
- Риск и rollback.
- Какие команды валидации были выполнены.
+9 -2
View File
@@ -1,4 +1,4 @@
# ActivityWatch-Russian # AWatch-rus
Практический каркас проекта для повторного развёртывания ActivityWatch Server в новом окружении с LXC-контейнером на Proxmox, русифицированным Web UI, systemd-юнитами, шаблонными скриптами деплоя и эксплуатационной документацией. Практический каркас проекта для повторного развёртывания ActivityWatch Server в новом окружении с LXC-контейнером на Proxmox, русифицированным Web UI, systemd-юнитами, шаблонными скриптами деплоя и эксплуатационной документацией.
@@ -8,8 +8,11 @@
- `docs/deployment.md` — пошаговый деплой LXC и ActivityWatch Server. - `docs/deployment.md` — пошаговый деплой LXC и ActivityWatch Server.
- `docs/runbook.md` — быстрый runbook для оператора. - `docs/runbook.md` — быстрый runbook для оператора.
- `docs/operations.md` — регламент сопровождения, бэкапов, обновлений и rollback. - `docs/operations.md` — регламент сопровождения, бэкапов, обновлений и rollback.
- `docs/windows/ensemble.md` — orchestration-пакет для Windows-деплоя и проверки.
- `proxmox/` — шаблонные скрипты подготовки и наполнения CT на стороне Proxmox. - `proxmox/` — шаблонные скрипты подготовки и наполнения CT на стороне Proxmox.
- `aw-server/` — установочные скрипты, env-шаблон, systemd unit и RU patch для Web UI. - `aw-server/` — установочные скрипты, env-шаблон, systemd unit и RU patch для Web UI.
- `ansible/` — Ansible-ensemble для автоматизированного сервера (Debian/CT).
- `windows/` — PowerShell toolkit: single-user, domain-users, ensemble orchestration, hardening/recovery, validation.
## Базовый сценарий ## Базовый сценарий
@@ -20,6 +23,8 @@
5. Внутри контейнера выполнить `aw-server/install_aw_server.sh`. 5. Внутри контейнера выполнить `aw-server/install_aw_server.sh`.
6. Применить русификацию Web UI через `aw-server/apply_webui_ru_patch.sh`. 6. Применить русификацию Web UI через `aw-server/apply_webui_ru_patch.sh`.
7. Проверить API, Web UI и состояние systemd по `docs/runbook.md`. 7. Проверить API, Web UI и состояние systemd по `docs/runbook.md`.
8. Развернуть Windows-клиентов через `windows/deploy-ensemble.ps1`.
9. Проверить итог через `windows/validate-deployment.ps1`.
Скрипты `proxmox/create-ct.sh` и `proxmox/push-aw-artifacts.sh` по умолчанию читают: Скрипты `proxmox/create-ct.sh` и `proxmox/push-aw-artifacts.sh` по умолчанию читают:
@@ -42,15 +47,17 @@
## Ограничения ## Ограничения
- Windows-клиенты и watcher-автоматизация в этой части каркаса не описываются.
- Интеграции с InfluxDB/Grafana/LDAP оставлены как следующий слой, не как обязательная база. - Интеграции с InfluxDB/Grafana/LDAP оставлены как следующий слой, не как обязательная база.
## Быстрые ссылки ## Быстрые ссылки
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/docs/FULL_DEPLOYMENT_MANUAL_RU.md` - `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/docs/FULL_DEPLOYMENT_MANUAL_RU.md`
- `/home/igor/tmp/AWatch-rus/docs/windows/ensemble.md`
- `docs/preparation.md` - `docs/preparation.md`
- `docs/deployment.md` - `docs/deployment.md`
- `docs/runbook.md` - `docs/runbook.md`
- `docs/operations.md` - `docs/operations.md`
- `proxmox/create-ct.sh` - `proxmox/create-ct.sh`
- `aw-server/install_aw_server.sh` - `aw-server/install_aw_server.sh`
- `windows/deploy-ensemble.ps1`
- `windows/validate-deployment.ps1`
+29
View File
@@ -0,0 +1,29 @@
# Ansible ensemble for AWatch-rus
Эта директория содержит минимальный Ansible-ensemble для повторяемого развёртывания ActivityWatch Server в Debian/LXC.
## Файлы
- `/home/igor/tmp/AWatch-rus/ansible/deploy_aw_server.yml` — основной playbook.
- `/home/igor/tmp/AWatch-rus/ansible/inventory.example.ini` — шаблон inventory.
- `/home/igor/tmp/AWatch-rus/ansible/group_vars/all.example.yml` — шаблон переменных.
## Быстрый запуск
1. Скопируйте шаблоны:
- `cp /home/igor/tmp/AWatch-rus/ansible/inventory.example.ini /home/igor/tmp/AWatch-rus/ansible/inventory.ini`
- `cp /home/igor/tmp/AWatch-rus/ansible/group_vars/all.example.yml /home/igor/tmp/AWatch-rus/ansible/group_vars/all.yml`
2. Заполните значения в `inventory.ini` и `group_vars/all.yml`.
3. Запустите:
```bash
cd /home/igor/tmp/AWatch-rus/ansible
ansible-playbook -i inventory.ini deploy_aw_server.yml
```
## Результат
- Установлен ActivityWatch Server.
- Создан systemd-unit `activitywatch-server.service`.
- Установлен RU Web UI patch.
- Выполнена валидация API `http://127.0.0.1:5600/api/0/info`.
+152
View File
@@ -0,0 +1,152 @@
---
- name: Deploy AWatch-rus server
hosts: aw_server
become: true
gather_facts: true
vars:
aw_release_root: "/opt/activitywatch/releases"
aw_release_dir: "{{ aw_release_root }}/{{ aw_server_version }}"
aw_archive_path: "/tmp/activitywatch-{{ aw_server_version }}.zip"
aw_bootstrap_dir: "/tmp/aw-rus-bootstrap"
tasks:
- name: Install base packages
ansible.builtin.apt:
name:
- curl
- unzip
state: present
update_cache: true
- name: Ensure service account exists
ansible.builtin.user:
name: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
home: "{{ aw_server_data_dir }}"
shell: /usr/sbin/nologin
system: true
create_home: false
- name: Ensure required directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
mode: "0755"
loop:
- "{{ aw_release_root }}"
- "{{ aw_release_dir }}"
- "{{ aw_server_webui_dir }}"
- "{{ aw_server_data_dir }}"
- "{{ aw_server_log_dir }}"
- /etc/activitywatch
- "{{ aw_bootstrap_dir }}"
- name: Download ActivityWatch release archive
ansible.builtin.get_url:
url: "{{ aw_server_download_url }}"
dest: "{{ aw_archive_path }}"
mode: "0644"
- name: Unpack ActivityWatch release
ansible.builtin.unarchive:
src: "{{ aw_archive_path }}"
dest: "{{ aw_release_dir }}"
remote_src: true
extra_opts: ["-o"]
- name: Discover extracted AW directory
ansible.builtin.find:
paths: "{{ aw_release_dir }}"
file_type: directory
patterns: "activitywatch*"
register: aw_release_find
- name: Set release extracted path
ansible.builtin.set_fact:
aw_release_extracted: "{{ (aw_release_find.files | sort(attribute='path') | map(attribute='path') | list | first) }}"
- name: Verify extracted directory exists
ansible.builtin.assert:
that:
- aw_release_extracted is defined
- aw_release_extracted | length > 0
fail_msg: "Cannot locate extracted ActivityWatch release directory."
- name: Sync release content to /opt/activitywatch
ansible.builtin.command:
cmd: "rsync -a --delete {{ aw_release_extracted }}/ /opt/activitywatch/"
- name: Copy bootstrap files from repository
ansible.builtin.copy:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
mode: "{{ item.mode }}"
loop:
- { src: "{{ aw_repo_root }}/aw-server/activitywatch-server.service", dest: "/etc/systemd/system/activitywatch-server.service", mode: "0644" }
- { src: "{{ aw_repo_root }}/aw-server/aw-ru-patch.js", dest: "{{ aw_server_webui_dir }}/js/ru-patch-v5.js", mode: "0644" }
- { src: "{{ aw_repo_root }}/aw-server/aw-sw-cleanup.js", dest: "{{ aw_server_webui_dir }}/js/sw-cleanup.js", mode: "0644" }
notify:
- Reload systemd
- Restart activitywatch
- name: Copy WebUI index template from installed distribution
ansible.builtin.copy:
remote_src: true
src: "/opt/activitywatch/aw-webui/index.html"
dest: "{{ aw_server_webui_dir }}/index.html"
mode: "0644"
- name: Insert RU patch scripts into index.html
ansible.builtin.replace:
path: "{{ aw_server_webui_dir }}/index.html"
regexp: '</head>'
replace: '<script src="/js/sw-cleanup.js?v=ansible1"></script></head>'
- name: Insert RU patch loader before body end
ansible.builtin.replace:
path: "{{ aw_server_webui_dir }}/index.html"
regexp: '</body>'
replace: '<script defer="defer" src="/js/ru-patch-v5.js?v=ansible1"></script></body>'
- name: Write /etc/activitywatch/aw-server.env
ansible.builtin.copy:
dest: /etc/activitywatch/aw-server.env
mode: "0640"
content: |
AW_SERVER_HOST={{ aw_server_bind_host }}
AW_SERVER_PORT={{ aw_server_port }}
AW_DATA_DIR={{ aw_server_data_dir }}
AW_LOG_DIR={{ aw_server_log_dir }}
AW_WEBUI_DIR={{ aw_server_webui_dir }}
AW_SERVER_USER={{ aw_server_user }}
AW_SERVER_GROUP={{ aw_server_group }}
- name: Enable and start service
ansible.builtin.systemd:
name: activitywatch-server.service
enabled: true
state: restarted
daemon_reload: true
- name: Wait for API
ansible.builtin.uri:
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/info"
method: GET
status_code: 200
register: aw_api
retries: 10
delay: 3
until: aw_api.status == 200
handlers:
- name: Reload systemd
ansible.builtin.systemd:
daemon_reload: true
- name: Restart activitywatch
ansible.builtin.systemd:
name: activitywatch-server.service
state: restarted
+11
View File
@@ -0,0 +1,11 @@
aw_server_version: "v0.13.2"
aw_server_download_url: "https://github.com/ActivityWatch/activitywatch/releases/download/v0.13.2/activitywatch-v0.13.2-linux-x86_64.zip"
aw_server_bind_host: "0.0.0.0"
aw_server_port: 5600
aw_server_webui_dir: "/opt/activitywatch/webui-ru"
aw_server_data_dir: "/var/lib/activitywatch"
aw_server_log_dir: "/var/log/activitywatch"
aw_server_user: "activitywatch"
aw_server_group: "activitywatch"
aw_repo_root: "/home/igor/tmp/AWatch-rus"
+2
View File
@@ -0,0 +1,2 @@
[aw_server]
aw-ct ansible_host=10.20.30.13 ansible_user=root ansible_port=22
+26
View File
@@ -13,8 +13,11 @@
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/aw-server/apply_webui_ru_patch.sh` - `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/aw-server/apply_webui_ru_patch.sh`
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/windows/deploy-single-user.ps1` - `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/windows/deploy-single-user.ps1`
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/windows/deploy-domain-users.ps1` - `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/windows/deploy-domain-users.ps1`
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/windows/deploy-ensemble.ps1`
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/windows/hardening-recovery.ps1` - `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/windows/hardening-recovery.ps1`
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/windows/validate-deployment.ps1`
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/windows/browser-domains-native-collector.ps1` - `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/windows/browser-domains-native-collector.ps1`
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/ansible/deploy_aw_server.yml`
--- ---
@@ -154,6 +157,21 @@ C:\Deploy\ActivityWatch-Russian\windows\deploy-domain-users.ps1 `
- `-Users 'CONTOSO\user01','CONTOSO\user02'` - `-Users 'CONTOSO\user01','CONTOSO\user02'`
- `-UserListPath <txt|csv>` - `-UserListPath <txt|csv>`
### 3.2.1 Ensemble orchestration (рекомендуется для production)
```powershell
C:\Deploy\ActivityWatch-Russian\windows\deploy-ensemble.ps1 `
-ServerHost aw.example.local `
-ServerPort 5600 `
-Domain CONTOSO `
-Users user1,user2,user3,user4,user5 `
-ValidateAfterDeploy
```
Отчёт сохраняется в:
- `C:\ProgramData\ActivityWatch\ensemble-report-YYYYMMDD-HHMMSS.json`
### 3.3 Single-user развёртывание ### 3.3 Single-user развёртывание
```powershell ```powershell
@@ -171,6 +189,14 @@ C:\Deploy\ActivityWatch-Russian\windows\hardening-recovery.ps1 `
-ConfigPath C:\ProgramData\ActivityWatch\deployment-config.json -ConfigPath C:\ProgramData\ActivityWatch\deployment-config.json
``` ```
### 3.5 Валидация deployment-а (PowerShell report)
```powershell
$report = C:\Deploy\ActivityWatch-Russian\windows\validate-deployment.ps1 `
-ConfigPath C:\ProgramData\ActivityWatch\deployment-config.json
$report | ConvertTo-Json -Depth 12
```
--- ---
## 4) Что должно появиться на Windows после установки ## 4) Что должно появиться на Windows после установки
+17
View File
@@ -4,7 +4,9 @@
- `windows/deploy-single-user.ps1` — развёртывание для одного пользователя. - `windows/deploy-single-user.ps1` — развёртывание для одного пользователя.
- `windows/deploy-domain-users.ps1` — массовое развёртывание по списку пользователей. - `windows/deploy-domain-users.ps1` — массовое развёртывание по списку пользователей.
- `windows/deploy-ensemble.ps1` — orchestration-скрипт полного цикла (deploy + hardening + validation).
- `windows/hardening-recovery.ps1` — повторная регистрация задач, ACL и recovery-loop. - `windows/hardening-recovery.ps1` — повторная регистрация задач, ACL и recovery-loop.
- `windows/validate-deployment.ps1` — машинная проверка состояния и JSON-отчёт.
- `windows/browser-domains-native-collector.ps1` — native collector доменов браузера с категоризацией. - `windows/browser-domains-native-collector.ps1` — native collector доменов браузера с категоризацией.
- `windows/web-category-rules.example.json` — пример кастомных правил категоризации. - `windows/web-category-rules.example.json` — пример кастомных правил категоризации.
@@ -88,6 +90,21 @@ CSV-формат: колонка `User`, `Username`, `SamAccountName` или `Lo
Если список уже содержит `DOMAIN\user`, параметр `-Domain` не нужен. Если список уже содержит `DOMAIN\user`, параметр `-Domain` не нужен.
## Ensemble deploy (production workflow)
```powershell
.\windows\deploy-ensemble.ps1 `
-ServerHost aw.example.local `
-ServerPort 5600 `
-Domain CONTOSO `
-Users user1,user2,user3,user4,user5 `
-ValidateAfterDeploy
```
Итоговый отчёт:
- `C:\ProgramData\ActivityWatch\ensemble-report-YYYYMMDD-HHMMSS.json`
## Категоризация доменов ## Категоризация доменов
- Встроенные категории покрывают базовые рабочие, нейтральные и личные домены. - Встроенные категории покрывают базовые рабочие, нейтральные и личные домены.
+49
View File
@@ -0,0 +1,49 @@
# Windows deployment ensemble (PowerShell)
Документ фиксирует профессиональный workflow развёртывания и контроля ActivityWatch-клиентов в домене Windows.
## Полные пути
- `/home/igor/tmp/AWatch-rus/windows/deploy-ensemble.ps1`
- `/home/igor/tmp/AWatch-rus/windows/deploy-domain-users.ps1`
- `/home/igor/tmp/AWatch-rus/windows/deploy-single-user.ps1`
- `/home/igor/tmp/AWatch-rus/windows/hardening-recovery.ps1`
- `/home/igor/tmp/AWatch-rus/windows/validate-deployment.ps1`
- `/home/igor/tmp/AWatch-rus/windows/ActivityWatch.Windows.Common.psm1`
- `/home/igor/tmp/AWatch-rus/windows/ActivityWatch.Windows.Common.psd1`
## Рекомендованный запуск
```powershell
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process
C:\Deploy\AWatch-rus\windows\deploy-ensemble.ps1 `
-ServerHost 10.10.10.13 `
-ServerPort 5600 `
-Domain AD `
-Users user1,user2,user3,user4,user5 `
-ValidateAfterDeploy
```
## Что делает `deploy-ensemble.ps1`
1. Нормализует список пользователей (`DOMAIN\user`).
2. Вызывает массовый деплой `deploy-domain-users.ps1`.
3. Применяет hardening/recovery (`hardening-recovery.ps1`), если не задан `-SkipHardening`.
4. Опционально запускает контроль (`validate-deployment.ps1`) при `-ValidateAfterDeploy`.
5. Пишет итоговый JSON-отчёт в `C:\ProgramData\ActivityWatch\ensemble-report-*.json`.
## Быстрый health-check
```powershell
$report = C:\Deploy\AWatch-rus\windows\validate-deployment.ps1 `
-ConfigPath C:\ProgramData\ActivityWatch\deployment-config.json
$report | ConvertTo-Json -Depth 12
```
Ожидается:
- `overallOk = true`
- нет missing-файлов
- все `ActivityWatch*` Scheduled Task присутствуют
- активны процессы `aw-watcher-afk` и `aw-watcher-window`
+13 -1
View File
@@ -1,5 +1,17 @@
# Windows validation # Windows validation
## Автоматическая проверка (рекомендуется)
```powershell
$report = .\windows\validate-deployment.ps1 `
-ConfigPath C:\ProgramData\ActivityWatch\deployment-config.json
$report | ConvertTo-Json -Depth 12
```
Критерий:
- `overallOk = true`
## Базовая проверка после развёртывания ## Базовая проверка после развёртывания
### 1. Проверить установленные файлы ### 1. Проверить установленные файлы
@@ -59,7 +71,7 @@ Invoke-WebRequest https://aw.example.local/api/0/info
- `aw-watcher-window_<hostname>` - `aw-watcher-window_<hostname>`
- `aw-watcher-web-edge_<hostname>` или другой browser bucket - `aw-watcher-web-edge_<hostname>` или другой browser bucket
- `aw-watcher-web-category_<hostname>` - `aw-detmir-web-category_<hostname>`
Проверка через API: Проверка через API:
+25
View File
@@ -0,0 +1,25 @@
@{
RootModule = 'ActivityWatch.Windows.Common.psm1'
ModuleVersion = '1.0.0'
GUID = '90b3fcf6-df9f-4f9b-9ee0-8a7de4dc0ee2'
Author = 'igor04091968'
CompanyName = 'Private'
Description = 'Common PowerShell functions for ActivityWatch Windows deployment, hardening and recovery.'
PowerShellVersion = '5.1'
FunctionsToExport = @(
'*-ActivityWatch*',
'Assert-Administrator',
'Normalize-ActivityWatchUsers',
'Get-ActivityWatchPackageUrl',
'Remove-LegacyActivityWatchEntries'
)
CmdletsToExport = @()
VariablesToExport = '*'
AliasesToExport = @()
PrivateData = @{
PSData = @{
Tags = @('ActivityWatch', 'Windows', 'Deployment', 'Recovery')
ProjectUri = 'https://github.com/igor04091968/AWatch-rus'
}
}
}
+109
View File
@@ -0,0 +1,109 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$ServerHost,
[string[]]$Users,
[string]$UserListPath,
[string]$Domain,
[int]$ServerPort = 5600,
[ValidateSet('http', 'https')]
[string]$ServerScheme = 'http',
[string]$Version = 'v0.13.2',
[string]$PackageUrl,
[string]$PackageZipPath,
[string]$InstallRoot = 'C:\Program Files\ActivityWatch',
[string]$StateRoot = 'C:\ProgramData\ActivityWatch',
[int]$PollSeconds = 5,
[int]$PulseSeconds = 30,
[int]$RecoveryIntervalSeconds = 180,
[string]$CustomRulesPath,
[string]$ReportPath,
[switch]$SkipHardening,
[switch]$ValidateAfterDeploy
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$modulePath = Join-Path $PSScriptRoot 'ActivityWatch.Windows.Common.psm1'
Import-Module $modulePath -Force
Assert-Administrator
$resolvedUsers = Normalize-ActivityWatchUsers -Users $Users -UserListPath $UserListPath -Domain $Domain
$timestamp = Get-Date -Format 'yyyyMMdd-HHmmss'
$effectiveReportPath = if ($ReportPath) { $ReportPath } else { Join-Path $StateRoot "ensemble-report-$timestamp.json" }
$deployScript = Join-Path $PSScriptRoot 'deploy-domain-users.ps1'
$hardeningScript = Join-Path $PSScriptRoot 'hardening-recovery.ps1'
$validationScript = Join-Path $PSScriptRoot 'validate-deployment.ps1'
if (-not (Test-Path -LiteralPath $deployScript)) {
throw "Missing script: $deployScript"
}
& $deployScript `
-ServerHost $ServerHost `
-Users $resolvedUsers `
-ServerPort $ServerPort `
-ServerScheme $ServerScheme `
-Version $Version `
-PackageUrl $PackageUrl `
-PackageZipPath $PackageZipPath `
-InstallRoot $InstallRoot `
-StateRoot $StateRoot `
-PollSeconds $PollSeconds `
-PulseSeconds $PulseSeconds `
-RecoveryIntervalSeconds $RecoveryIntervalSeconds `
-CustomRulesPath $CustomRulesPath
if (-not $SkipHardening) {
& $hardeningScript `
-ConfigPath (Join-Path $StateRoot 'deployment-config.json') `
-ServerHost $ServerHost `
-ServerPort $ServerPort `
-ServerScheme $ServerScheme `
-Users $resolvedUsers `
-InstallRoot $InstallRoot `
-StateRoot $StateRoot `
-PollSeconds $PollSeconds `
-PulseSeconds $PulseSeconds `
-RecoveryIntervalSeconds $RecoveryIntervalSeconds `
-CustomRulesPath $CustomRulesPath
}
$report = [ordered]@{
generatedAtUtc = (Get-Date).ToUniversalTime().ToString('o')
server = [ordered]@{
host = $ServerHost
port = $ServerPort
scheme = $ServerScheme
}
packageVersion = $Version
users = @($resolvedUsers)
paths = [ordered]@{
installRoot = $InstallRoot
stateRoot = $StateRoot
configPath = Join-Path $StateRoot 'deployment-config.json'
}
hardeningApplied = (-not $SkipHardening)
}
if ($ValidateAfterDeploy) {
if (-not (Test-Path -LiteralPath $validationScript)) {
throw "Missing script: $validationScript"
}
$validation = & $validationScript -ConfigPath (Join-Path $StateRoot 'deployment-config.json')
$report.validation = $validation
}
$reportDirectory = Split-Path -Path $effectiveReportPath -Parent
if ($reportDirectory) {
New-ActivityWatchDirectory -Path $reportDirectory
}
$report | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath $effectiveReportPath -Encoding UTF8
Write-Host 'ActivityWatch ensemble deploy completed.'
Write-Host "Users: $($resolvedUsers -join ', ')"
Write-Host "Report: $effectiveReportPath"
+86
View File
@@ -0,0 +1,86 @@
[CmdletBinding()]
param(
[string]$ConfigPath = 'C:\ProgramData\ActivityWatch\deployment-config.json'
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$modulePath = Join-Path $PSScriptRoot 'ActivityWatch.Windows.Common.psm1'
Import-Module $modulePath -Force
$config = Read-ActivityWatchDeploymentConfig -Path $ConfigPath
$installRoot = [string]$config.paths.installRoot
$stateRoot = [string]$config.paths.stateRoot
$collectorScript = [string]$config.paths.collectorScript
$rulesPath = [string]$config.paths.rulesPath
$launchScript = [string]$config.paths.launchScript
$recoveryScript = [string]$config.paths.recoveryScript
$requiredFiles = @(
(Join-Path $installRoot 'aw-watcher-afk\aw-watcher-afk.exe'),
(Join-Path $installRoot 'aw-watcher-window\aw-watcher-window.exe'),
$collectorScript,
$rulesPath,
$launchScript,
$recoveryScript,
$ConfigPath
)
$missingFiles = @(
$requiredFiles | Where-Object { -not (Test-Path -LiteralPath $_) }
)
$processNames = @('aw-watcher-afk', 'aw-watcher-window')
$runningProcesses = Get-Process -Name $processNames -ErrorAction SilentlyContinue | Select-Object Name, Id, SessionId
$taskNames = @()
if ($config.userTasks) {
$taskNames += @($config.userTasks | ForEach-Object { [string]$_.launchTaskName })
}
$taskNames += [string]$config.recovery.taskName
$taskNames = $taskNames | Sort-Object -Unique
$tasks = foreach ($taskName in $taskNames) {
$task = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
if ($task) {
[pscustomobject]@{
taskName = $task.TaskName
state = [string]$task.State
present = $true
}
}
else {
[pscustomobject]@{
taskName = $taskName
state = 'Missing'
present = $false
}
}
}
$serverUrl = '{0}://{1}:{2}' -f [string]$config.server.scheme, [string]$config.server.host, [int]$config.server.port
$result = [ordered]@{
generatedAtUtc = (Get-Date).ToUniversalTime().ToString('o')
configPath = $ConfigPath
serverUrl = $serverUrl
installRoot = $installRoot
stateRoot = $stateRoot
files = [ordered]@{
required = $requiredFiles
missing = $missingFiles
ok = ($missingFiles.Count -eq 0)
}
tasks = [ordered]@{
list = $tasks
ok = [bool]($tasks.Count -gt 0 -and -not ($tasks | Where-Object { -not $_.present }))
}
processes = [ordered]@{
list = @($runningProcesses)
ok = [bool](($runningProcesses | Select-Object -ExpandProperty Name -Unique).Count -ge 2)
}
}
$result.overallOk = [bool]($result.files.ok -and $result.tasks.ok -and $result.processes.ok)
$result