Compare commits

..
203 changed files with 1889 additions and 62763 deletions
+34
View File
@@ -0,0 +1,34 @@
name: Build Windows installer
on:
workflow_dispatch:
push:
paths:
- 'windows/**'
- '.github/workflows/build-installer.yml'
jobs:
build:
runs-on: windows-latest
env:
SIGNTOOL_CMD: ${{ secrets.SIGNTOOL_CMD }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Inno
shell: pwsh
run: |
choco install innosetup --no-progress -y
- name: Build installer
shell: pwsh
run: |
./windows/installer/build-installer.ps1
- name: Upload Setup.exe artifact
uses: actions/upload-artifact@v4
with:
name: awatch-rus-setup
path: windows/installer/output/*.exe
if-no-files-found: error
+2 -4
View File
@@ -18,7 +18,7 @@ jobs:
- name: Run shellcheck
run: |
find . -type f -name "*.sh" -print0 | xargs -0 -r shellcheck -e SC1007,SC1090,SC2016
find . -type f -name "*.sh" -print0 | xargs -0 -r shellcheck
powershell-analyzer:
runs-on: ubuntu-latest
@@ -40,9 +40,7 @@ jobs:
"windows/*.psm1",
"windows/*.psd1"
)
$issues = $targets | ForEach-Object {
Invoke-ScriptAnalyzer -Path $_ -Recurse -Severity Error
}
$issues = Invoke-ScriptAnalyzer -Path $targets -Recurse -Severity Error,Warning
if ($issues) {
$issues | Format-Table -AutoSize
throw "PSScriptAnalyzer detected issues."
-2
View File
@@ -1,13 +1,11 @@
# Local secrets
secrets/deploy.secrets.env
secrets/runtime.env
# Runtime / reports
*.log
*.tmp
*.bak
windows/*.report.json
.rollout-logs/
# IDE
.idea/
-11236
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -1,20 +0,0 @@
---
created: 2026-05-07T21:46:00Z
title: Deploy standalone agent on SHARKON2025 and validate live flow
area: tooling
files:
- windows/installkit/innosetup/AWatch-rus-InnoSetup.iss
- windows/install-standalone-service.ps1
- windows/aw-standalone-service.ps1
- windows/installkit/innosetup/BUILD.md
- docs/windows/deployment.md
- docs/windows/troubleshooting.md
---
## Problem
Standalone InnoSetup mode and service wrapper are implemented and pushed, and AW API write path was verified by manual heartbeat posts. But production value still depends on real endpoint rollout: installer must be deployed on SHARKON2025 (192.168.100.21), service must be running persistently, and UI/API must show continuously fresh events without manual seeding.
## Solution
Deploy the newly built installer `AWatch-rus-InstallKit.exe` to SHARKON2025, run installation with target `10.10.10.13:5600`, verify `AWatchRusStandaloneAgent` state, inspect `standalone-agent-service.log`, and confirm fresh `metadata.end` progression for `aw-dlp-endpoint-signals_SHARKON2025`, `aw-file-operations_SHARKON2025`, and `aw-worktime-sessions_SHARKON2025` over time.
+3 -16
View File
@@ -5,29 +5,21 @@
## Что входит
- `docs/preparation.md` — подготовка инфраструктуры и входных параметров.
- `docs/codebase-onboarding.md` — обзор структуры репозитория и маршрут изучения для новичка.
- `docs/deployment.md` — пошаговый деплой LXC и ActivityWatch Server.
- `docs/runbook.md` — быстрый runbook для оператора.
- `docs/operations.md` — регламент сопровождения, бэкапов, обновлений и rollback.
- `docs/windows/ensemble.md` — orchestration-пакет для Windows-деплоя и проверки.
- `docs/linux-client.md` — user-space rollout Linux-клиента ActivityWatch на удалённый `AW server`.
- `docs/linux-remote-worker.md` — полный Linux remote-worker stack: GUI, SSH/console и browser admin UI вроде Proxmox `:8006`.
- `docs/console-ssh-logger.md` — логирование только консольных команд и SSH-сессий в AW.
- `docs/dlp-gap-analysis.md` — разрыв до enterprise DLP и roadmap.
- `docs/dlp-aggregator.md` — прототип централизованной агрегации DLP/file-operation событий.
- `docs/dlp-reliability-roadmap.md` — roadmap повышения надёжности DLP-коллекторов.
- `docs/powershell-analysis.md` — статический анализ работоспособности DLP PowerShell-скриптов.
- `proxmox/` — шаблонные скрипты подготовки и наполнения CT на стороне Proxmox.
- `aw-server/` — установочные скрипты, env-шаблон, systemd unit и RU patch для Web UI.
- `ansible/` — Ansible-ensemble для автоматизированного сервера (Debian/CT).
- `pfsense/` — внешний poller для pfSense API и systemd unit под Debian/Ubuntu utility VM.
- `windows/` — PowerShell toolkit: single-user, domain-users, ensemble orchestration, hardening/recovery, validation, Windows/RDP DLP telemetry (`aw-dlp-incidents_*`, `aw-dlp-endpoint-signals_*`) и session-level presence для удалённых Windows/RDP пользователей (`aw-worktime-sessions_*`).
- `windows/` — PowerShell toolkit: single-user, domain-users, ensemble orchestration, hardening/recovery, validation, phase-2 DLP telemetry (`aw-dlp-incidents_*`, `aw-dlp-endpoint-signals_*`).
- `scripts/quality-gate.sh` — локальный preflight-пайплайн проверок.
- `scripts/aggregate_dlp_events.py` — сбор `aw-file-operations_*` и `aw-dlp-incidents_*` в SQLite/PostgreSQL.
- `scripts/install_aw_linux_client.sh` — установка Linux bundle + autostart для remote AW server.
- `scripts/install_aw_console_ssh_logger.sh` — user-space установка console/ssh logger.
- `scripts/install_aw_linux_web_category_logger.sh` — user-space классификация browser admin UI по title/class.
- `scripts/install_aw_linux_remote_worker.sh` — полный Linux remote-worker installer.
## Базовый сценарий
@@ -46,9 +38,9 @@
- `ansible/provision_proxmox_ct_and_deploy_aw.yml`
- `ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml` (массово по матрице CT)
Для централизованного деплоя Windows/RDP-клиентов через WinRM:
Для централизованного phase-2 деплоя Windows-клиентов через WinRM:
- `ansible/deploy_aw_windows.yml`
- `ansible/deploy_aw_windows_phase2.yml`
Для внешнего pfSense poller'а:
@@ -59,11 +51,6 @@
- `docs/linux-client.md`
- `scripts/install_aw_linux_client.sh`
Для полного Linux remote-worker сценария:
- `docs/linux-remote-worker.md`
- `scripts/install_aw_linux_remote_worker.sh`
Для режима “только консоль/ssh” без GUI watcher'ов:
- `docs/console-ssh-logger.md`
-17
View File
@@ -1,17 +0,0 @@
#!/bin/sh
set -eu
REPO_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
cd "$REPO_DIR"
echo "==> Fetch origin"
git fetch origin
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
echo "==> Pull origin/$CURRENT_BRANCH"
git pull --ff-only origin "$CURRENT_BRANCH"
echo "==> Done"
git status --short --branch
-34
View File
@@ -1,34 +0,0 @@
#!/bin/sh
set -eu
REPO_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
cd "$REPO_DIR"
BRANCH=$(git rev-parse --abbrev-ref HEAD)
if [ "${1:-}" = "" ]; then
echo "Usage: $0 \"commit message\"" >&2
exit 1
fi
COMMIT_MESSAGE=$1
echo "==> Git status"
git status --short --branch
echo "==> Stage changes"
git add -A
if git diff --cached --quiet; then
echo "No staged changes to commit."
exit 0
fi
echo "==> Commit"
git commit -m "$COMMIT_MESSAGE"
echo "==> Push origin/$BRANCH"
git push origin "$BRANCH"
echo "==> Done"
+39 -67
View File
@@ -1,51 +1,46 @@
# Ansible ensemble for AWatch-rus
Эта директория содержит Ansible-ensemble для полного развёртывания AWatch-rus:
Эта директория содержит Ansible-ensemble для двух сценариев:
- деплой на уже существующий Debian host/CT;
- полный цикл с нуля в Proxmox: создание CT + bootstrap + установка ActivityWatch + RU patch;
- централизованное развёртывание Windows/RDP collector'ов по WinRM;
- развёртывание внешнего pfSense poller'а на Debian/Ubuntu utility VM.
- полный цикл с нуля в Proxmox: создание CT + bootstrap + установка ActivityWatch + RU patch.
- централизованный деплой Windows phase-2 collectors по WinRM.
- deployment внешнего pfSense poller'а на Debian/Ubuntu utility VM.
## Файлы
- `ansible/deploy_aw_server.yml` — основной playbook для уже существующего Debian/CT host.
- `ansible/provision_proxmox_ct_and_deploy_aw.yml`полный playbook для Proxmox.
- `ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml` — массовый полный playbook (несколько CT).
- `ansible/deploy_aw_windows.yml` — WinRM playbook для развёртывания Windows/RDP collector'ов.
- `ansible/deploy_aw_pfsense_poller.yml`развёртывание pfSense poller'а.
- `ansible/install_full_stack.yml` — полный установочный playbook (оркестратор всех этапов).
- `ansible/inventory.example.ini` — шаблон inventory.
- `ansible/group_vars/*.example.yml` — шаблоны переменных.
- `/home/igor/tmp/AWatch-rus/ansible/deploy_aw_server.yml` — основной playbook.
- `/home/igor/tmp/AWatch-rus/ansible/provision_proxmox_ct_and_deploy_aw.yml`full-stack playbook для Proxmox.
- `/home/igor/tmp/AWatch-rus/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml` — массовый full-stack playbook (несколько CT).
- `/home/igor/tmp/AWatch-rus/ansible/deploy_aw_windows_phase2.yml` — WinRM playbook для развёртывания phase-2 Windows collector'ов.
- `/home/igor/tmp/AWatch-rus/ansible/deploy_aw_pfsense_poller.yml`deployment pfSense poller'а.
- `/home/igor/tmp/AWatch-rus/ansible/install_full_stack.yml` — полный установочный playbook (оркестратор всех этапов).
- `/home/igor/tmp/AWatch-rus/ansible/inventory.example.ini` — шаблон inventory.
- `/home/igor/tmp/AWatch-rus/ansible/group_vars/all.example.yml` — шаблон переменных.
- `/home/igor/tmp/AWatch-rus/ansible/group_vars/proxmox.example.yml` — шаблон переменных CT в Proxmox.
- `/home/igor/tmp/AWatch-rus/ansible/group_vars/proxmox-matrix.example.yml` — шаблон матрицы CT.
- `/home/igor/tmp/AWatch-rus/ansible/group_vars/windows.example.yml` — шаблон переменных Windows phase-2.
- `/home/igor/tmp/AWatch-rus/ansible/group_vars/pfsense-poller.example.yml` — шаблон переменных pfSense poller'а.
## Быстрый запуск
1. Скопируйте шаблоны:
- `cp ansible/inventory.example.ini ansible/inventory.ini`
- `cp ansible/group_vars/all.example.yml ansible/group_vars/all.yml`
- `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 ansible
cd /home/igor/tmp/AWatch-rus/ansible
ansible-playbook -i inventory.ini deploy_aw_server.yml
```
## Секреты (пароли) безопасно
Рекомендуемый способ не хранить пароли в репозитории — перед запуском экспортировать их в переменные окружения:
- Linux `aw_server` (SSH пароль root): `AW_SSH_PASSWORD`
- Windows `aw_windows` (WinRM пароль): `AW_WINRM_PASSWORD`
В `group_vars/aw_server.yml` и `group_vars/windows.yml` они читаются через `lookup('env', ...)`.
## Полный установочный playbook (всё за один запуск)
Если нужно прогнать полный цикл одной командой:
```bash
cd ansible
cd /home/igor/tmp/AWatch-rus/ansible
ansible-playbook -i inventory.ini install_full_stack.yml
```
@@ -53,7 +48,7 @@ ansible-playbook -i inventory.ini install_full_stack.yml
- `provision_proxmox_ct_and_deploy_aw.yml` (если есть хосты в группе `[proxmox]`);
- `deploy_aw_server.yml` (группа `[aw_server]`);
- `deploy_aw_windows.yml` (группа `[aw_windows]`);
- `deploy_aw_windows_phase2.yml` (группа `[aw_windows]`);
- `deploy_aw_pfsense_poller.yml` (группа `[aw_pfsense_pollers]`).
Пустые группы в `inventory.ini` безопасны: соответствующий play будет пропущен.
@@ -61,51 +56,50 @@ ansible-playbook -i inventory.ini install_full_stack.yml
## Полный запуск с нуля в Proxmox
1. Подготовьте inventory и vars:
- `cp ansible/inventory.example.ini ansible/inventory.ini`
- `cp ansible/group_vars/all.example.yml ansible/group_vars/all.yml`
- `cp ansible/group_vars/proxmox.example.yml ansible/group_vars/proxmox.yml`
- `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`
- `cp /home/igor/tmp/AWatch-rus/ansible/group_vars/proxmox.example.yml /home/igor/tmp/AWatch-rus/ansible/group_vars/proxmox.yml`
2. Заполните `group_vars/proxmox.yml` и `group_vars/all.yml`.
3. Запустите playbook:
```bash
cd ansible
cd /home/igor/tmp/AWatch-rus/ansible
ansible-playbook -i inventory.ini provision_proxmox_ct_and_deploy_aw.yml
```
## Массовый запуск (матрица CT)
1. Подготовьте матрицу:
- `cp ansible/group_vars/proxmox-matrix.example.yml ansible/group_vars/proxmox-matrix.yml`
- `cp /home/igor/tmp/AWatch-rus/ansible/group_vars/proxmox-matrix.example.yml /home/igor/tmp/AWatch-rus/ansible/group_vars/proxmox-matrix.yml`
2. Заполните `proxmox-matrix.yml`.
3. Запустите:
```bash
cd ansible
cd /home/igor/tmp/AWatch-rus/ansible
ansible-playbook -i inventory.ini provision_proxmox_ct_matrix_and_deploy_aw.yml
```
## Windows/RDP rollout (WinRM)
## Windows phase-2 rollout (WinRM)
1. Подготовьте inventory и vars:
- `cp ansible/inventory.example.ini ansible/inventory.ini`
- `cp ansible/group_vars/windows.example.yml ansible/group_vars/windows.yml`
- `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/windows.example.yml /home/igor/tmp/AWatch-rus/ansible/group_vars/windows.yml`
2. Заполните `inventory.ini` (секция `[aw_windows]`) и `group_vars/windows.yml`.
- Для русской локализации Windows часто нужен `ansible_user=Администратор` (а не `Administrator`).
- Если WinRM закрыт, playbook не сможет стартовать и нужно сначала открыть `5985/5986` и `wsman`.
3. Запустите:
```bash
cd ansible
ansible-playbook -i inventory.ini deploy_aw_windows.yml
cd /home/igor/tmp/AWatch-rus/ansible
ansible-playbook -i inventory.ini deploy_aw_windows_phase2.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;
- выгружает `windows/*` toolkit на целевой хост в `C:\Deploy\AWatch-rus\windows`;
- выполняет `deploy-ensemble.ps1` (deploy + hardening/recovery) с phase-2 policy/rules;
- после deploy принудительно запускает `ActivityWatch Recovery` и все `ActivityWatch Launch *` задачи;
- выполняет API smoke-check bucket `aw-watcher-afk_<COMPUTERNAME>` и ожидает свежие `not-afk` события;
- выполняет API smoke-check bucket `aw-watcher-afk_SHARKON2025` и ожидает свежие `not-afk` события;
- запускает `validate-deployment.ps1`;
- забирает JSON-отчёт в локальную директорию (`/tmp/aw-rus-validation` по умолчанию).
@@ -116,27 +110,17 @@ Playbook:
- `aw_windows_incident_capture_enabled: false` — отключить блок incidentCapture;
- `aw_windows_incident_screenshot_enabled: false` — не делать скриншот при DLP-инциденте;
- `aw_windows_incident_artifacts_root: 'C:\...\incident-artifacts'` — переопределить путь артефактов;
- `aw_windows_deploy_root: 'C:\Program Files\AWatch-rus'` — каталог toolkit, совпадает с InnoSetup `{app}`;
- `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`;
- `aw_windows_skip_hardening: true` — пропустить `hardening-recovery.ps1` внутри ensemble-скрипта.
## Развёртывание pfSense poller
## pfSense poller rollout
1. Подготовьте vars:
- `cp ansible/group_vars/pfsense-poller.example.yml ansible/group_vars/pfsense-poller.yml`
- `cp /home/igor/tmp/AWatch-rus/ansible/group_vars/pfsense-poller.example.yml /home/igor/tmp/AWatch-rus/ansible/group_vars/pfsense-poller.yml`
2. Добавьте inventory group `[aw_pfsense_pollers]`.
3. Запустите:
```bash
cd ansible
cd /home/igor/tmp/AWatch-rus/ansible
ansible-playbook -i inventory.ini deploy_aw_pfsense_poller.yml
```
@@ -155,16 +139,4 @@ Playbook:
- Для Web UI используется checksum-based cache-bust для `ru-patch-v5.js` и `sw-cleanup.js`, чтобы браузер не держал старую DLP/русскую статику после деплоя.
- На `#/home` Web UI делит хосты на `Windows RDP` и `Virtual servers + Proxmox`.
- Выполнена валидация API `http://127.0.0.1:5600/api/0/info`.
- Для полного сценария CT создаётся автоматически через `pct create`.
- На Windows/RDP host развёрнуты AFK/window watchers, browser domain collector, DLP endpoint collector и worktime session collector.
- Проверочный JSON-отчёт Windows playbook должен иметь `overallOk=true`.
## Prod rollout одной командой
Для ручного запуска с dry-run и логированием используйте:
```bash
bash scripts/prod_rollout.sh
```
Скрипт попросит `AW_SSH_PASSWORD` и `AW_WINRM_PASSWORD` интерактивно (ввод скрыт) и сложит логи в `.rollout-logs/`.
- Для full-stack сценария CT создаётся автоматически через `pct create`.
+11 -11
View File
@@ -1,5 +1,5 @@
---
- name: Развернуть pfSense ActivityWatch poller
- name: Deploy pfSense ActivityWatch poller
hosts: aw_pfsense_pollers
become: true
gather_facts: true
@@ -10,14 +10,14 @@
aw_pfsense_service_name: "aw-pfsense-poller.service"
tasks:
- name: Установить обязательные пакеты
- name: Install required packages
ansible.builtin.apt:
name:
- python3
state: present
update_cache: true
- name: Создать каталоги
- name: Ensure directories exist
ansible.builtin.file:
path: "{{ item }}"
state: directory
@@ -26,29 +26,29 @@
- "{{ aw_pfsense_install_root }}"
- "{{ aw_pfsense_config_dir }}"
- name: Установить скрипт pfSense poller
- name: Install pfSense poller script
ansible.builtin.copy:
src: "{{ aw_repo_root }}/pfsense/pfsense-aw-poller.py"
dest: "{{ aw_pfsense_install_root }}/pfsense-aw-poller.py"
mode: "0755"
- name: Установить systemd service
- name: Install systemd service
ansible.builtin.copy:
src: "{{ aw_repo_root }}/pfsense/pfsense-aw-poller.service"
dest: "/etc/systemd/system/{{ aw_pfsense_service_name }}"
mode: "0644"
notify:
- Перезагрузить systemd
- Reload systemd
- name: Записать конфигурацию pfSense poller
- name: Write pfSense poller config
ansible.builtin.copy:
dest: "{{ aw_pfsense_config_dir }}/poller.json"
mode: "0600"
content: "{{ aw_pfsense_poller_config | to_nice_json }}"
notify:
- Перезапустить pfSense poller
- Restart pfSense poller
- name: Включить и запустить pfSense poller
- name: Enable and start pfSense poller
ansible.builtin.systemd:
name: "{{ aw_pfsense_service_name }}"
enabled: true
@@ -56,11 +56,11 @@
daemon_reload: true
handlers:
- name: Перезагрузить systemd
- name: Reload systemd
ansible.builtin.systemd:
daemon_reload: true
- name: Перезапустить pfSense poller
- name: Restart pfSense poller
ansible.builtin.systemd:
name: "{{ aw_pfsense_service_name }}"
state: restarted
+118 -625
View File
@@ -1,5 +1,5 @@
---
- name: Развернуть сервер AWatch-rus
- name: Deploy AWatch-rus server
hosts: aw_server
become: true
gather_facts: true
@@ -9,29 +9,22 @@
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"
aw_release_install_dir: "{{ aw_release_root }}/aw-server-rust-{{ aw_server_version }}"
aw_ru_patch_cache_bust: "{{ lookup('file', aw_repo_root + '/aw-server/aw-ru-patch.js') | hash('sha1') | truncate(12, true, '') }}"
aw_sw_cleanup_cache_bust: "{{ lookup('file', aw_repo_root + '/aw-server/aw-sw-cleanup.js') | hash('sha1') | truncate(12, true, '') }}"
aw_host_groups_cache_bust: "{{ lookup('file', aw_repo_root + '/aw-server/aw-host-groups.json') | hash('sha1') | truncate(12, true, '') }}"
aw_worktime_classes: "{{ lookup('file', aw_repo_root + '/aw-server/settings/classes-worktime.json') | from_json }}"
aw_default_views: "{{ lookup('file', aw_repo_root + '/aw-server/settings/views-default.json') | from_json }}"
tasks:
- name: Установить базовые пакеты
- name: Install base packages
ansible.builtin.apt:
name:
- curl
- rsync
- unzip
state: present
update_cache: true
- name: Создать системную группу сервиса
ansible.builtin.group:
name: "{{ aw_server_group }}"
system: true
state: present
- name: Создать системную учётную запись сервиса
- name: Ensure service account exists
ansible.builtin.user:
name: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
@@ -40,630 +33,156 @@
system: true
create_home: false
- name: Создать обязательные каталоги
- 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_release_install_dir }}"
- /opt/activitywatch
- /opt/activitywatch/bin
- "{{ aw_server_webui_dir }}"
- "{{ aw_server_webui_dir }}/js"
- "{{ aw_server_data_dir }}"
- "{{ aw_server_db_path | dirname }}"
- "{{ aw_server_data_dir }}/.config"
- "{{ aw_server_data_dir }}/.config/activitywatch"
- "{{ aw_server_data_dir }}/.config/activitywatch/aw-server-rust"
- "{{ aw_server_data_dir }}/backups"
- "{{ aw_server_log_dir }}"
- /etc/activitywatch
- "{{ aw_bootstrap_dir }}"
- name: Настроить каталоги ActivityWatch с владельцем сервиса
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
mode: "0755"
loop:
- /opt/activitywatch
- /opt/activitywatch/bin
- "{{ aw_release_root }}"
- "{{ aw_release_dir }}"
- "{{ aw_release_install_dir }}"
- "{{ aw_server_webui_dir }}"
- "{{ aw_server_webui_dir }}/js"
- "{{ aw_server_data_dir }}"
- "{{ aw_server_db_path | dirname }}"
- "{{ aw_server_data_dir }}/.config"
- "{{ aw_server_data_dir }}/.config/activitywatch"
- "{{ aw_server_data_dir }}/.config/activitywatch/aw-server-rust"
- "{{ aw_server_data_dir }}/backups"
- "{{ aw_server_log_dir }}"
- name: (Check mode) Пропустить установку релиза ActivityWatch
ansible.builtin.debug:
msg: "ansible_check_mode=true: download/unarchive/install of ActivityWatch release is skipped."
when: ansible_check_mode
- name: Установить релиз ActivityWatch (download/unarchive/install)
when: not ansible_check_mode
block:
- name: Скачать архив релиза ActivityWatch
ansible.builtin.get_url:
url: "{{ aw_server_download_url }}"
dest: "{{ aw_archive_path }}"
mode: "0644"
- name: Распаковать релиз ActivityWatch
ansible.builtin.unarchive:
src: "{{ aw_archive_path }}"
dest: "{{ aw_release_dir }}"
remote_src: true
extra_opts: ["-o"]
- name: Найти распакованный каталог ActivityWatch
ansible.builtin.find:
paths: "{{ aw_release_dir }}"
recurse: true
file_type: directory
patterns: "activitywatch*"
register: aw_release_find
- name: Найти бинарный файл AW server
ansible.builtin.find:
paths: "{{ aw_release_dir }}"
recurse: true
file_type: file
patterns:
- aw-server-rust
- aw-server
register: aw_server_binary_find
- name: Найти index.html WebUI
ansible.builtin.find:
paths: "{{ aw_release_dir }}"
recurse: true
file_type: file
patterns:
- index.html
register: aw_webui_index_find
- name: Сохранить пути распакованного релиза (binary + webui index)
ansible.builtin.set_fact:
aw_release_extracted: "{{ (aw_release_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list | first) | default('') }}"
aw_server_binary_path: >-
{{
(
(
(aw_server_binary_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list)
| select('match', '.*/aw-server-rust$') | list | first
)
| default(
(
(aw_server_binary_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list | first)
),
true
)
) | default('')
}}
aw_webui_index_path: >-
{{
(
(
(aw_webui_index_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list)
| select('search', '/static/index\\.html$') | list | first
)
| default(
(
(aw_webui_index_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list | first)
),
true
)
) | default('')
}}
- name: Сохранить каталог WebUI (dirname index.html)
ansible.builtin.set_fact:
aw_webui_source_path: "{{ aw_webui_index_path | dirname }}"
- name: Проверить, что компоненты релиза найдены
ansible.builtin.assert:
that:
- aw_release_extracted is defined
- aw_release_extracted | length > 0
- aw_server_binary_path is defined
- aw_server_binary_path | length > 0
- aw_webui_source_path is defined
- aw_webui_source_path | length > 0
fail_msg: "Не удалось найти бинарный файл или WebUI в распакованном релизе ActivityWatch."
- name: Создать каталог установленного релиза
ansible.builtin.file:
path: "{{ aw_release_install_dir }}"
state: directory
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
mode: "0755"
- name: Установить бинарный файл AW server
ansible.builtin.copy:
remote_src: true
src: "{{ aw_server_binary_path }}"
dest: "{{ aw_release_install_dir }}/aw-server-rust"
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
mode: "0755"
- name: Создать ссылку на активный бинарный файл AW server
ansible.builtin.file:
src: "{{ aw_release_install_dir }}/aw-server-rust"
dest: /opt/activitywatch/bin/aw-server-rust
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
state: link
force: true
- name: Синхронизировать WebUI в RU каталог
ansible.builtin.command:
cmd: "rsync -a {{ aw_webui_source_path }}/ {{ aw_server_webui_dir }}/"
- name: Настроить владельца файлов /opt/activitywatch
ansible.builtin.file:
path: /opt/activitywatch
state: directory
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
recurse: true
- name: Установить systemd service из шаблона репозитория
ansible.builtin.copy:
dest: /etc/systemd/system/activitywatch-server.service
- name: Download ActivityWatch release archive
ansible.builtin.get_url:
url: "{{ aw_server_download_url }}"
dest: "{{ aw_archive_path }}"
mode: "0644"
content: >-
{{
lookup('file', aw_repo_root + '/aw-server/activitywatch-server.service')
| replace('__AW_SERVER_USER__', aw_server_user)
| replace('__AW_SERVER_GROUP__', aw_server_group)
| replace('__AW_SERVER_DATA_DIR__', aw_server_data_dir)
}}
- 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" }
- { src: "{{ aw_repo_root }}/aw-server/aw-host-groups.json", dest: "{{ aw_server_webui_dir }}/js/aw-host-groups.json", mode: "0644" }
notify:
- Перезагрузить systemd
- Перезапустить activitywatch
- Reload systemd
- Restart activitywatch
- name: (Check mode) Пропустить WebUI patch и запуск сервиса
ansible.builtin.debug:
msg: "ansible_check_mode=true: WebUI patch + service start + API checks are skipped."
when: ansible_check_mode
- name: Применить WebUI RU patch и запустить сервис
when: not ansible_check_mode
block:
- name: Скопировать RU patch файлы WebUI из репозитория
ansible.builtin.copy:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
mode: "{{ item.mode }}"
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
loop:
- { 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" }
- { src: "{{ aw_repo_root }}/aw-server/aw-worktime-panel.js", dest: "{{ aw_server_webui_dir }}/js/aw-worktime-panel.js", mode: "0644" }
- { src: "{{ aw_repo_root }}/aw-server/aw-host-groups.json", dest: "{{ aw_server_webui_dir }}/js/aw-host-groups.json", mode: "0644" }
- name: Создать каталог /root/bootstrap для apply_webui_ru_patch.sh
ansible.builtin.file:
path: /root/bootstrap
state: directory
mode: "0755"
- name: Скопировать RU patch файлы для apply_webui_ru_patch.sh (хотфиксы compiled JS чанков)
ansible.builtin.copy:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
mode: "{{ item.mode }}"
loop:
- { src: "{{ aw_repo_root }}/aw-server/aw-ru-patch.js", dest: "/root/bootstrap/aw-ru-patch.js", mode: "0644" }
- { src: "{{ aw_repo_root }}/aw-server/aw-sw-cleanup.js", dest: "/root/bootstrap/aw-sw-cleanup.js", mode: "0644" }
- { src: "{{ aw_repo_root }}/aw-server/aw-worktime-panel.js", dest: "/root/bootstrap/aw-worktime-panel.js", mode: "0644" }
- { src: "{{ aw_repo_root }}/aw-server/aw-host-groups.json", dest: "/root/bootstrap/aw-host-groups.json", mode: "0644" }
- name: Скопировать apply_webui_ru_patch.sh скрипт
ansible.builtin.copy:
src: "{{ aw_repo_root }}/aw-server/apply_webui_ru_patch.sh"
dest: /opt/activitywatch/aw-server/apply_webui_ru_patch.sh
mode: "0755"
- name: Записать /etc/activitywatch/aw-server.env перед хотфиксами
ansible.builtin.copy:
dest: /etc/activitywatch/aw-server.env
mode: "0640"
owner: root
group: root
content: |
AW_SERVER_BIND_HOST={{ aw_server_bind_host }}
AW_SERVER_PORT={{ aw_server_port }}
AW_SERVER_DATA_DIR={{ aw_server_data_dir }}
AW_SERVER_DB_PATH={{ aw_server_db_path }}
AW_SERVER_LOG_DIR={{ aw_server_log_dir }}
AW_SERVER_WEBUI_DIR={{ aw_server_webui_dir }}
AW_SERVER_USER={{ aw_server_user }}
AW_SERVER_GROUP={{ aw_server_group }}
AW_WORKTIME_REPORT_BASE={{ aw_worktime_report_base }}
AW_WORKTIME_TZ={{ aw_worktime_timezone }}
XDG_DATA_HOME={{ aw_server_data_dir }}/.local/share
XDG_CONFIG_HOME={{ aw_server_data_dir }}/.config
- name: Установить скрипт AW worktime API
ansible.builtin.copy:
src: "{{ aw_repo_root }}/aw-server/aw-worktime-api.py"
dest: /usr/local/bin/aw-worktime-api.py
owner: root
group: root
mode: "0755"
- name: Установить systemd unit AW worktime API
ansible.builtin.copy:
src: "{{ aw_repo_root }}/aw-server/aw-worktime-api.service"
dest: /etc/systemd/system/aw-worktime-api.service
owner: root
group: root
mode: "0644"
- name: Установить скрипт AW worktime UI bridge
ansible.builtin.copy:
src: "{{ aw_repo_root }}/aw-server/aw-worktime-ui-bridge.py"
dest: /usr/local/bin/aw-worktime-ui-bridge.py
owner: root
group: root
mode: "0755"
- name: Установить systemd unit AW worktime UI bridge
ansible.builtin.copy:
src: "{{ aw_repo_root }}/aw-server/aw-worktime-ui-bridge.service"
dest: /etc/systemd/system/aw-worktime-ui-bridge.service
owner: root
group: root
mode: "0644"
- name: Установить systemd timer AW worktime UI bridge
ansible.builtin.copy:
src: "{{ aw_repo_root }}/aw-server/aw-worktime-ui-bridge.timer"
dest: /etc/systemd/system/aw-worktime-ui-bridge.timer
owner: root
group: root
mode: "0644"
- name: Перезагрузить systemd после установки AW worktime API
ansible.builtin.systemd:
daemon_reload: true
- name: Включить и перезапустить AW worktime API
ansible.builtin.systemd:
name: aw-worktime-api.service
enabled: true
state: restarted
- name: Отключить legacy timer aw-worktime-afk-bridge (если есть)
ansible.builtin.systemd:
name: aw-worktime-afk-bridge.timer
enabled: false
state: stopped
failed_when: false
- name: Включить и перезапустить AW worktime UI bridge timer
ansible.builtin.systemd:
name: aw-worktime-ui-bridge.timer
enabled: true
state: restarted
- name: Выполнить разовый прогон AW worktime UI bridge
ansible.builtin.systemd:
name: aw-worktime-ui-bridge.service
state: started
failed_when: false
- name: Применить хотфиксы compiled JS чанков (Trends, Timespiral, Category helper)
ansible.builtin.command:
cmd: "/opt/activitywatch/aw-server/apply_webui_ru_patch.sh"
register: apply_ru_patch_result
failed_when: false
- name: Вывести результат применения хотфиксов
ansible.builtin.debug:
msg: "apply_webui_ru_patch.sh: {{ apply_ru_patch_result.stdout }}"
- name: Проверить наличие index.html после копирования
ansible.builtin.stat:
path: "{{ aw_server_webui_dir }}/index.html"
register: aw_webui_ru_index
- name: Проверить, что index.html доступен для RU patch
ansible.builtin.assert:
that:
- aw_webui_ru_index.stat.exists
fail_msg: "Не найден index.html WebUI для применения RU patch."
- name: Удалить старые теги RU patch из index.html
ansible.builtin.replace:
path: "{{ aw_server_webui_dir }}/index.html"
regexp: '<script[^>]+(?:ru-patch-v5\.js|sw-cleanup\.js|aw-ru-patch\.js|aw-sw-cleanup\.js)[^>]*></script>'
replace: ''
- name: Добавить cleanup script RU patch в index.html
ansible.builtin.replace:
path: "{{ aw_server_webui_dir }}/index.html"
regexp: '</head>'
replace: '<script src="/js/sw-cleanup.js?v={{ aw_sw_cleanup_cache_bust }}"></script></head>'
- name: Добавить загрузчик RU patch перед закрытием body
ansible.builtin.replace:
path: "{{ aw_server_webui_dir }}/index.html"
regexp: '</body>'
replace: '<script defer="defer" src="/js/ru-patch-v5.js?v={{ aw_ru_patch_cache_bust }}"></script></body>'
- name: Скопировать merge script AW DB на сервер
ansible.builtin.copy:
src: "{{ aw_repo_root }}/scripts/merge_aw_server_dbs.py"
dest: /usr/local/bin/merge_aw_server_dbs.py
owner: root
group: root
mode: "0755"
- name: Проверить наличие legacy root DB
ansible.builtin.stat:
path: /root/.local/share/activitywatch/aw-server-rust/sqlite.db
register: aw_legacy_root_db
- name: Проверить наличие target DB
ansible.builtin.stat:
path: "{{ aw_server_db_path }}"
register: aw_target_db
- name: Остановить сервис перед merge server DB
ansible.builtin.systemd:
name: activitywatch-server.service
state: stopped
when: aw_legacy_root_db.stat.exists | default(false)
- name: Создать backup каталоги server DB
ansible.builtin.file:
path: "{{ aw_server_data_dir }}/backups/db"
state: directory
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
mode: "0755"
when: aw_legacy_root_db.stat.exists | default(false)
- name: Backup target DB перед merge
ansible.builtin.copy:
remote_src: true
src: "{{ aw_server_db_path }}"
dest: "{{ aw_server_data_dir }}/backups/db/target-before-merge-{{ ansible_date_time.iso8601_basic_short }}.sqlite.db"
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
mode: "0644"
when:
- aw_legacy_root_db.stat.exists | default(false)
- aw_target_db.stat.exists | default(false)
- name: Backup legacy root DB перед merge
ansible.builtin.copy:
remote_src: true
src: /root/.local/share/activitywatch/aw-server-rust/sqlite.db
dest: "{{ aw_server_data_dir }}/backups/db/legacy-root-{{ ansible_date_time.iso8601_basic_short }}.sqlite.db"
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
mode: "0644"
when: aw_legacy_root_db.stat.exists | default(false)
- name: Merge legacy root DB в target DB
ansible.builtin.command:
argv:
- python3
- /usr/local/bin/merge_aw_server_dbs.py
- --base
- /root/.local/share/activitywatch/aw-server-rust/sqlite.db
- --overlay
- "{{ aw_server_db_path }}"
- --output
- "{{ aw_server_db_path }}.merged"
when:
- aw_legacy_root_db.stat.exists | default(false)
- aw_target_db.stat.exists | default(false)
- name: Install merged DB as active target DB
ansible.builtin.copy:
remote_src: true
src: "{{ aw_server_db_path }}.merged"
dest: "{{ aw_server_db_path }}"
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
mode: "0644"
when:
- aw_legacy_root_db.stat.exists | default(false)
- aw_target_db.stat.exists | default(false)
- name: Скопировать legacy root DB в target DB если target ещё не существует
ansible.builtin.copy:
remote_src: true
src: /root/.local/share/activitywatch/aw-server-rust/sqlite.db
dest: "{{ aw_server_db_path }}"
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
mode: "0644"
when:
- aw_legacy_root_db.stat.exists | default(false)
- not (aw_target_db.stat.exists | default(false))
- name: Записать aw-server-rust config.toml с разрешёнными CORS origin
ansible.builtin.copy:
dest: "{{ aw_server_data_dir }}/.config/activitywatch/aw-server-rust/config.toml"
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
mode: "0644"
content: |
cors = [
{% for origin in aw_server_cors_origins | default([]) %}
"{{ origin }}"{% if not loop.last %},{% endif %}
{% endfor %}
]
- name: Включить и запустить сервис
ansible.builtin.systemd:
name: activitywatch-server.service
enabled: true
state: restarted
daemon_reload: true
- name: Дождаться ответа 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
- name: Считать текущие server-side settings
ansible.builtin.uri:
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/"
method: GET
status_code: 200
register: aw_settings_current
when: aw_apply_worktime_settings | default(false) | bool
- name: Считать текущие server-side views
ansible.builtin.uri:
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/views"
method: GET
status_code: 200
register: aw_views_current
when: aw_apply_worktime_settings | default(false) | bool
- name: Считать текущие server-side classes
ansible.builtin.uri:
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/classes"
method: GET
status_code: 200
register: aw_classes_current
when: aw_apply_worktime_settings | default(false) | bool
- name: Создать backup текущих server-side settings/views/classes
- name: Copy WebUI index template from installed distribution
ansible.builtin.copy:
dest: "{{ aw_server_data_dir }}/backups/{{ item.name }}-{{ ansible_date_time.iso8601_basic_short }}.json"
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
remote_src: true
src: "/opt/activitywatch/aw-webui/index.html"
dest: "{{ aw_server_webui_dir }}/index.html"
mode: "0644"
content: "{{ item.payload | to_nice_json }}"
loop:
- name: settings
payload: "{{ aw_settings_current.json | default({}) }}"
- name: views
payload: "{{ aw_views_current.json | default(none) }}"
- name: classes
payload: "{{ aw_classes_current.json | default(none) }}"
when: aw_apply_worktime_settings | default(false) | bool
- name: Настроить DLP Aggregator (Phase 2)
block:
- name: Создать каталог для скриптов
ansible.builtin.file:
path: "/opt/activitywatch/scripts"
state: directory
owner: root
group: root
mode: "0755"
- 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={{ aw_sw_cleanup_cache_bust }}"></script></head>'
- name: Скопировать агрегатор событий DLP
ansible.builtin.copy:
src: "{{ aw_repo_root }}/scripts/aggregate_dlp_events.py"
dest: "/opt/activitywatch/scripts/aggregate_dlp_events.py"
owner: root
group: root
mode: "0755"
- 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={{ aw_ru_patch_cache_bust }}"></script></body>'
- name: Установить systemd unit для агрегатора
ansible.builtin.copy:
dest: /etc/systemd/system/activitywatch-dlp-aggregator.service
content: |
[Unit]
Description=ActivityWatch DLP Event Aggregator
After=activitywatch-server.service
- 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 }}
[Service]
Type=oneshot
User={{ aw_server_user }}
WorkingDirectory={{ aw_server_data_dir }}
ExecStart=/usr/bin/python3 /opt/activitywatch/scripts/aggregate_dlp_events.py \
--aw-url http://127.0.0.1:{{ aw_server_port }}/api/0 \
--sqlite-path {{ aw_server_data_dir }}/dlp_warehouse.sqlite \
--state-path {{ aw_server_data_dir }}/dlp-aggregator-state.json
- name: Enable and start service
ansible.builtin.systemd:
name: activitywatch-server.service
enabled: true
state: restarted
daemon_reload: true
[Install]
WantedBy=multi-user.target
- 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
- name: Установить systemd timer для агрегатора
ansible.builtin.copy:
dest: /etc/systemd/system/activitywatch-dlp-aggregator.timer
content: |
[Unit]
Description=Run ActivityWatch DLP Aggregator every 5 minutes
[Timer]
OnBootSec=1min
OnUnitActiveSec=5min
AccuracySec=1s
[Install]
WantedBy=timers.target
- name: Включить и запустить таймер агрегатора
ansible.builtin.systemd:
name: activitywatch-dlp-aggregator.timer
enabled: true
state: started
daemon_reload: true
- name: Применить базовые worktime settings (classes)
- name: Apply baseline worktime settings (classes)
ansible.builtin.uri:
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/classes"
method: POST
body: "{{ aw_worktime_classes }}"
body_format: json
status_code: [200, 201]
status_code: 201
when: aw_apply_worktime_settings | default(false) | bool
- name: Применить базовые views для DLP и worktime
- name: Apply baseline views (include DLP and worktime)
ansible.builtin.uri:
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/views"
method: POST
body: "{{ aw_default_views }}"
body_format: json
status_code: [200, 201]
status_code: 201
when: aw_apply_worktime_settings | default(false) | bool
- name: Вычислить worktime durationDefault из aw_worktime_from/to
- name: Derive worktime durationDefault from aw_worktime_from/to
ansible.builtin.set_fact:
aw_worktime_from_h: "{{ (aw_worktime_from | default('08:00')).split(':')[0] | int }}"
aw_worktime_from_m: "{{ (aw_worktime_from | default('08:00')).split(':')[1] | int }}"
aw_worktime_to_h: "{{ (aw_worktime_to | default('17:00')).split(':')[0] | int }}"
aw_worktime_to_m: "{{ (aw_worktime_to | default('17:00')).split(':')[1] | int }}"
aw_worktime_duration_default_derived: >-
{{
(
(
(((aw_worktime_to | default('17:00')).split(':')[0] | int) * 60 + ((aw_worktime_to | default('17:00')).split(':')[1] | int)) -
(((aw_worktime_from | default('08:00')).split(':')[0] | int) * 60 + ((aw_worktime_from | default('08:00')).split(':')[1] | int))
((aw_worktime_to_h | int) * 60 + (aw_worktime_to_m | int)) -
((aw_worktime_from_h | int) * 60 + (aw_worktime_from_m | int))
) * 60
)
}}
when: aw_apply_worktime_settings | default(false) | bool
- name: Нормализовать durationDefault для ночных смен
- name: Normalize derived durationDefault for overnight shifts
ansible.builtin.set_fact:
aw_worktime_duration_default_effective: >-
{{
@@ -673,64 +192,38 @@
}}
when: aw_apply_worktime_settings | default(false) | bool
- name: Проверить корректность durationDefault
- name: Validate derived durationDefault is sane
ansible.builtin.assert:
that:
- aw_worktime_duration_default_effective | int > 0
- aw_worktime_duration_default_effective | int <= 86400
fail_msg: "Некорректный интервал рабочего времени: {{ aw_worktime_from }}..{{ aw_worktime_to }}"
fail_msg: "Invalid worktime window: {{ aw_worktime_from }}..{{ aw_worktime_to }}"
when: aw_apply_worktime_settings | default(false) | bool
- name: Применить базовый период worktime (startOfDay)
- name: Apply baseline worktime period (startOfDay)
ansible.builtin.uri:
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/startOfDay"
method: POST
body: "\"{{ aw_worktime_start_of_day }}\""
headers:
Content-Type: application/json
status_code: [200, 201]
body: "{{ aw_worktime_start_of_day }}"
body_format: json
status_code: 200
when: aw_apply_worktime_settings | default(false) | bool
- name: Применить базовый период worktime (durationDefault seconds)
- name: Apply baseline worktime period (durationDefault seconds)
ansible.builtin.uri:
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/durationDefault"
method: POST
body: "{{ aw_worktime_duration_default_effective | string }}"
headers:
Content-Type: application/json
status_code: [200, 201]
body: "{{ aw_worktime_duration_default_effective }}"
body_format: json
status_code: 200
when: aw_apply_worktime_settings | default(false) | bool
- name: Применить always_active_pattern для fallback без AFK
ansible.builtin.uri:
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/always_active_pattern"
method: POST
body: "\"{{ aw_server_always_active_pattern }}\""
headers:
Content-Type: application/json
status_code: [200, 201]
when:
- aw_apply_worktime_settings | default(false) | bool
- (aw_server_always_active_pattern | default('') | string | length) > 0
- name: Применить landingpage профиля
ansible.builtin.uri:
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/landingpage"
method: POST
body: "\"{{ aw_server_landingpage }}\""
headers:
Content-Type: application/json
status_code: [200, 201]
when:
- aw_apply_worktime_settings | default(false) | bool
- (aw_server_landingpage | default('') | string | length) > 0
handlers:
- name: Перезагрузить systemd
- name: Reload systemd
ansible.builtin.systemd:
daemon_reload: true
- name: Перезапустить activitywatch
- name: Restart activitywatch
ansible.builtin.systemd:
name: activitywatch-server.service
state: restarted
-300
View File
@@ -1,300 +0,0 @@
---
- name: Развернуть Windows/RDP collector'ы AWatch-rus
hosts: aw_windows
gather_facts: false
vars:
aw_windows_repo_root: "{{ playbook_dir | dirname }}"
aw_windows_deploy_root: "C:\\Program Files\\AWatch-rus"
aw_windows_server_scheme: "http"
aw_windows_server_host: "10.10.10.13"
aw_windows_server_port: 5600
aw_windows_package_version: "v0.13.2"
aw_windows_package_url: "https://github.com/ActivityWatch/activitywatch/releases/download/v0.13.2/activitywatch-v0.13.2-windows-x86_64.zip"
aw_windows_package_zip_path: ""
aw_windows_domain: "SHARKON2025"
aw_windows_users:
- user1
- user2
- 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\\AWatch-rus\\bin"
aw_windows_state_root: "C:\\ProgramData\\AWatch-rus"
aw_windows_afk_enabled: true
aw_windows_window_enabled: true
aw_windows_file_ops_enabled: true
aw_windows_local_agent_logs_enabled: false
aw_windows_incident_capture_enabled: true
aw_windows_incident_screenshot_enabled: true
aw_windows_incident_artifacts_root: "{{ aw_windows_state_root }}\\incident-artifacts"
aw_windows_logon_marker_enabled: true
aw_windows_skip_hardening: false
aw_windows_rules_path: "{{ aw_windows_deploy_root }}\\windows\\web-category-rules.example.json"
aw_windows_policy_path: "{{ aw_windows_deploy_root }}\\windows\\dlp-policy.example.json"
aw_windows_validation_remote_path: "{{ aw_windows_state_root }}\\aw_validate_ansible.json"
aw_windows_validation_local_dir: "/tmp/aw-rus-validation"
aw_windows_launch_task_pattern: "ActivityWatch Launch *"
aw_windows_recovery_task_name: "ActivityWatch Recovery"
aw_windows_force_task_restart: true
aw_windows_api_smoke_check_enabled: true
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: Проверить обязательные переменные
ansible.builtin.assert:
that:
- aw_windows_server_host is defined
- aw_windows_server_port is defined
- aw_windows_server_scheme is defined
- aw_windows_domain is defined
- aw_windows_users_effective | length > 0
- aw_windows_install_root is defined
- aw_windows_state_root is defined
fail_msg: "Не заданы обязательные переменные Windows-развёртывания."
- name: Создать каталоги развёртывания
ansible.windows.win_file:
path: "{{ item }}"
state: directory
loop:
- "{{ aw_windows_deploy_root }}"
- "{{ aw_windows_deploy_root }}\\windows"
- name: Загрузить Windows toolkit развёртывания
ansible.windows.win_copy:
src: "{{ aw_windows_repo_root }}/windows/{{ item }}"
dest: "{{ aw_windows_deploy_root }}\\windows\\{{ item }}"
loop:
- ActivityWatch.Windows.Common.psd1
- ActivityWatch.Windows.Common.psm1
- browser-domains-native-collector.ps1
- dlp-endpoint-signals-collector.ps1
- email-outbound-collector.ps1
- file-operations-collector.ps1
- worktime-session-collector.ps1
- migrate-awatch-rus-paths.ps1
- deploy-domain-users.ps1
- deploy-ensemble.ps1
- hardening-recovery.ps1
- validate-deployment.ps1
- web-category-rules.example.json
- dlp-policy.example.json
- name: Нормализовать кодировку PowerShell файлов (UTF-8 BOM для Windows PowerShell)
ansible.windows.win_powershell:
script: |
$ErrorActionPreference = 'Stop'
$toolkitDir = "{{ aw_windows_deploy_root }}\windows"
$encIn = New-Object System.Text.UTF8Encoding($false)
$encOut = New-Object System.Text.UTF8Encoding($true)
Get-ChildItem -LiteralPath $toolkitDir -File -Include *.ps1,*.psm1,*.psd1 | ForEach-Object {
$text = [System.IO.File]::ReadAllText($_.FullName, $encIn)
[System.IO.File]::WriteAllText($_.FullName, $text, $encOut)
}
- name: Загрузить список пользователей для доменного развёртывания
ansible.windows.win_copy:
dest: "{{ aw_windows_deploy_root }}\\windows\\users.txt"
content: |
{% for user in aw_windows_users_effective -%}
{{ 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: |
$ErrorActionPreference = 'Stop'
$params = @{
ServerScheme = "{{ aw_windows_server_scheme }}"
ServerHost = "{{ aw_windows_server_host }}"
ServerPort = {{ aw_windows_server_port }}
Version = "{{ aw_windows_package_version }}"
Domain = "{{ aw_windows_domain }}"
UserListPath = "{{ aw_windows_deploy_root }}\windows\users.txt"
InstallRoot = "{{ aw_windows_install_root }}"
StateRoot = "{{ aw_windows_state_root }}"
AfkEnabled = {{ '$true' if (aw_windows_afk_enabled | bool) else '$false' }}
WindowEnabled = {{ '$true' if (aw_windows_window_enabled | bool) else '$false' }}
FileOpsEnabled = {{ '$true' if (aw_windows_file_ops_enabled | bool) else '$false' }}
LocalAgentLogsEnabled = {{ '$true' if (aw_windows_local_agent_logs_enabled | bool) else '$false' }}
IncidentCaptureEnabled = {{ '$true' if (aw_windows_incident_capture_enabled | bool) else '$false' }}
IncidentScreenshotEnabled = {{ '$true' if (aw_windows_incident_screenshot_enabled | bool) else '$false' }}
IncidentArtifactsRoot = "{{ aw_windows_incident_artifacts_root }}"
LogonMarkerEnabled = {{ '$true' if (aw_windows_logon_marker_enabled | bool) else '$false' }}
CustomRulesPath = "{{ aw_windows_rules_path }}"
CustomPolicyPath = "{{ aw_windows_policy_path }}"
}
{% if (aw_windows_package_url | default('') | string | length) > 0 %}
$params.PackageUrl = "{{ aw_windows_package_url }}"
{% endif %}
{% 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 %}
& "{{ aw_windows_deploy_root }}\windows\deploy-ensemble.ps1" @params
- name: Удалить лишние ActivityWatch Launch tasks вне текущего deployment-config
ansible.windows.win_powershell:
script: |
$ErrorActionPreference = 'Stop'
$config = Get-Content -Raw -LiteralPath "{{ aw_windows_state_root }}\deployment-config.json" | ConvertFrom-Json
$desired = @($config.userTasks | ForEach-Object { [string]$_.LaunchTaskName })
foreach ($task in @(Get-ScheduledTask | Where-Object { $_.TaskName -like 'ActivityWatch Launch *' })) {
if ($desired -notcontains [string]$task.TaskName) {
Unregister-ScheduledTask -TaskName $task.TaskName -Confirm:$false -ErrorAction SilentlyContinue
& cmd.exe /c "schtasks /Delete /TN `"$($task.TaskName)`" /F >nul 2>&1" | Out-Null
}
}
- name: Принудительно запустить ActivityWatch recovery и launch tasks
when: aw_windows_force_task_restart | bool
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 }}" |
ForEach-Object { Start-ScheduledTask -TaskName $_.TaskName }
- name: Получить Windows hostname для AW smoke-check bucket
when:
- aw_windows_api_smoke_check_enabled | bool
- aw_windows_afk_enabled | bool
ansible.windows.win_command: powershell.exe -NoProfile -Command "$env:COMPUTERNAME"
register: aw_windows_hostname_result
changed_when: false
- name: Вычислить AW AFK smoke-check bucket
when:
- aw_windows_api_smoke_check_enabled | bool
- aw_windows_afk_enabled | bool
- aw_windows_hostname_result.stdout is defined
ansible.builtin.set_fact:
aw_windows_api_smoke_check_bucket_effective: >-
{{
aw_windows_api_smoke_check_bucket
if (aw_windows_api_smoke_check_bucket | default('') | string | length) > 0
else 'aw-watcher-afk_' ~ (aw_windows_hostname_result.stdout | trim)
}}
- name: Выполнить AW API smoke-check (проверка наличия свежих событий в AFK бакете)
when:
- aw_windows_api_smoke_check_enabled | bool
- aw_windows_afk_enabled | bool
ansible.builtin.uri:
url: "{{ aw_windows_server_scheme }}://{{ aw_windows_server_host }}:{{ aw_windows_server_port }}/api/0/buckets/{{ aw_windows_api_smoke_check_bucket_effective }}/events?limit={{ aw_windows_api_smoke_check_limit }}"
method: GET
status_code: 200
register: aw_windows_api_smoke_result
until: aw_windows_api_smoke_result.json | length > 0
retries: 5
delay: 5
ignore_errors: true
- name: Валидировать развёртывание на эндпоинте
ansible.windows.win_powershell:
script: |
$ErrorActionPreference = 'Stop'
$result = & "{{ aw_windows_deploy_root }}\windows\validate-deployment.ps1" `
-ConfigPath "{{ aw_windows_state_root }}\deployment-config.json"
$result | ConvertTo-Json -Depth 8 | Out-File -FilePath "{{ aw_windows_validation_remote_path }}" -Encoding utf8
return $result
- name: Создать локальную директорию для отчётов валидации
ansible.builtin.file:
path: "{{ aw_windows_validation_local_dir }}"
state: directory
mode: "0755"
delegate_to: localhost
- name: Стянуть отчёт валидации с эндпоинта
ansible.builtin.fetch:
src: "{{ aw_windows_validation_remote_path }}"
dest: "{{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_ansible.json"
flat: true
- name: Проверить статус валидации
ansible.builtin.shell: |
python3 - <<'PY'
import json, sys
with open('{{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_ansible.json', 'r') as f:
data = json.load(f)
if not data.get('overallOk', False):
print(f"Validation failed for {{ inventory_hostname }}: {data.get('summary', 'Unknown error')}")
sys.exit(1)
PY
delegate_to: localhost
when: aw_windows_fail_on_validation_error | bool
+169
View File
@@ -0,0 +1,169 @@
---
- name: Deploy AWatch-rus Windows phase2 collectors
hosts: aw_windows
gather_facts: false
vars:
aw_windows_repo_root: "/home/igor/tmp/AWatch-rus"
aw_windows_deploy_root: "C:\\Deploy\\AWatch-rus"
aw_windows_server_host: "10.10.10.13"
aw_windows_server_port: 5600
aw_windows_domain: "SHARKON2025"
aw_windows_users:
- user1
- user2
- 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"
aw_windows_afk_enabled: true
aw_windows_window_enabled: true
aw_windows_local_agent_logs_enabled: false
aw_windows_incident_capture_enabled: true
aw_windows_incident_screenshot_enabled: true
aw_windows_incident_artifacts_root: "{{ aw_windows_state_root }}\\incident-artifacts"
aw_windows_logon_marker_enabled: true
aw_windows_skip_hardening: false
aw_windows_rules_path: "{{ aw_windows_deploy_root }}\\windows\\web-category-rules.example.json"
aw_windows_policy_path: "{{ aw_windows_deploy_root }}\\windows\\dlp-policy.example.json"
aw_windows_validation_remote_path: "C:\\Windows\\Temp\\aw_validate_phase2_ansible.json"
aw_windows_validation_local_dir: "/tmp/aw-rus-validation"
aw_windows_launch_task_pattern: "ActivityWatch Launch *"
aw_windows_recovery_task_name: "ActivityWatch Recovery"
aw_windows_force_task_restart: true
aw_windows_api_smoke_check_enabled: true
aw_windows_api_smoke_check_bucket: "aw-watcher-afk_SHARKON2025"
aw_windows_api_smoke_check_limit: 10
tasks:
- name: Validate required variables
ansible.builtin.assert:
that:
- aw_windows_server_host is defined
- aw_windows_server_port is defined
- aw_windows_domain is defined
- 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."
- name: Ensure deploy directories exist
ansible.windows.win_file:
path: "{{ item }}"
state: directory
loop:
- "{{ aw_windows_deploy_root }}"
- "{{ aw_windows_deploy_root }}\\windows"
- name: Upload Windows deployment toolkit
ansible.windows.win_copy:
src: "{{ aw_windows_repo_root }}/windows/{{ item }}"
dest: "{{ aw_windows_deploy_root }}\\windows\\{{ item }}"
loop:
- ActivityWatch.Windows.Common.psd1
- ActivityWatch.Windows.Common.psm1
- browser-domains-native-collector.ps1
- dlp-endpoint-signals-collector.ps1
- deploy-domain-users.ps1
- deploy-ensemble.ps1
- hardening-recovery.ps1
- validate-deployment.ps1
- web-category-rules.example.json
- dlp-policy.example.json
- name: Upload user list for domain deploy
ansible.windows.win_copy:
dest: "{{ aw_windows_deploy_root }}\\windows\\users.txt"
content: |
{% 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:
script: |
$ErrorActionPreference = 'Stop'
$params = @{
ServerHost = "{{ aw_windows_server_host }}"
ServerPort = {{ aw_windows_server_port }}
Domain = "{{ aw_windows_domain }}"
UserListPath = "{{ aw_windows_deploy_root }}\windows\users.txt"
InstallRoot = "{{ aw_windows_install_root }}"
StateRoot = "{{ aw_windows_state_root }}"
AfkEnabled = {{ '$true' if (aw_windows_afk_enabled | bool) else '$false' }}
WindowEnabled = {{ '$true' if (aw_windows_window_enabled | bool) else '$false' }}
LocalAgentLogsEnabled = {{ '$true' if (aw_windows_local_agent_logs_enabled | bool) else '$false' }}
IncidentCaptureEnabled = {{ '$true' if (aw_windows_incident_capture_enabled | bool) else '$false' }}
IncidentScreenshotEnabled = {{ '$true' if (aw_windows_incident_screenshot_enabled | bool) else '$false' }}
IncidentArtifactsRoot = "{{ aw_windows_incident_artifacts_root }}"
LogonMarkerEnabled = {{ '$true' if (aw_windows_logon_marker_enabled | bool) else '$false' }}
CustomRulesPath = "{{ aw_windows_rules_path }}"
CustomPolicyPath = "{{ aw_windows_policy_path }}"
}
{% if aw_windows_skip_hardening | bool %}
$params.SkipHardening = $true
{% endif %}
& "{{ aw_windows_deploy_root }}\windows\deploy-ensemble.ps1" @params
- name: Force start ActivityWatch recovery and launch tasks
when: aw_windows_force_task_restart | bool
ansible.windows.win_powershell:
script: |
$ErrorActionPreference = 'Stop'
Start-ScheduledTask -TaskName "{{ aw_windows_recovery_task_name }}"
Get-ScheduledTask |
Where-Object TaskName -like "{{ aw_windows_launch_task_pattern }}" |
ForEach-Object { Start-ScheduledTask -TaskName $_.TaskName }
- name: Wait for fresh AFK events to appear on AW server
when: aw_windows_api_smoke_check_enabled | bool
delegate_to: localhost
ansible.builtin.uri:
url: "http://{{ aw_windows_server_host }}:{{ aw_windows_server_port }}/api/0/buckets/{{ aw_windows_api_smoke_check_bucket }}/events?limit={{ aw_windows_api_smoke_check_limit }}"
method: GET
return_content: true
register: aw_windows_api_smoke
until: >
aw_windows_api_smoke.status == 200 and
(aw_windows_api_smoke.json | length) > 0 and
(
aw_windows_api_smoke.json
| selectattr('data.status', 'equalto', 'not-afk')
| list
| length
) > 0
retries: 10
delay: 6
- name: Run validation and store report on target
ansible.windows.win_powershell:
script: |
$ErrorActionPreference = 'Stop'
$report = & "{{ aw_windows_deploy_root }}\windows\validate-deployment.ps1" `
-ConfigPath "{{ aw_windows_state_root }}\deployment-config.json"
$report | ConvertTo-Json -Depth 12 | Out-File -FilePath "{{ aw_windows_validation_remote_path }}" -Encoding utf8
- name: Ensure local validation directory exists
ansible.builtin.file:
path: "{{ aw_windows_validation_local_dir }}"
state: directory
mode: "0755"
delegate_to: localhost
- name: Fetch validation report
ansible.builtin.fetch:
src: "{{ aw_windows_validation_remote_path }}"
dest: "{{ aw_windows_validation_local_dir }}/"
flat: false
- name: Show report location
ansible.builtin.debug:
msg:
- "Windows phase2 deploy completed on {{ inventory_hostname }}."
- "Validation report: {{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}/C$/Windows/Temp/aw_validate_phase2_ansible.json"
+8 -21
View File
@@ -4,34 +4,21 @@ 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_db_path: "/var/lib/activitywatch/.local/share/activitywatch/aw-server-rust/sqlite.db"
aw_server_log_dir: "/var/log/activitywatch"
aw_server_user: "activitywatch"
aw_server_group: "activitywatch"
aw_worktime_report_base: "http://10.10.10.13:5610"
aw_worktime_timezone: "Europe/Moscow"
aw_repo_root: "{{ playbook_dir | dirname }}"
aw_repo_root: "/home/igor/tmp/AWatch-rus"
# Применить базовые категории и views для рабочего времени через AW settings API.
# При прод-обновлениях это нужно оставлять включённым, иначе UI остаётся без views/classes.
aw_apply_worktime_settings: true
# Optional: apply a baseline worktime-focused categorization and views via AW settings API.
# WARNING: this overwrites existing server-side settings/classes/views.
aw_apply_worktime_settings: false
# Дополнительные origin для aw-server-rust CORS.
# Обязательно включите тот origin, с которого реально открывается Web UI.
aw_server_cors_origins:
- "http://127.0.0.1:5600"
- "http://localhost:5600"
- "http://10.10.10.13:5600"
- "http://aw-server:5600"
# Опциональные значения периода рабочего времени в Web UI.
# startOfDay задаёт границу дня и стартовое время окна отчёта.
# durationDefault задаёт диапазон по умолчанию в секундах.
# Optional defaults for the worktime period in Web UI.
# startOfDay controls day-boundary and default report window start.
# durationDefault controls default time range (seconds) shown in UI.
#
# Рекомендуется явно задать рабочий интервал и дать playbook вычислить duration.
# Recommended: set worktime window explicitly and let the playbook derive duration.
aw_worktime_from: "08:00"
aw_worktime_to: "17:00"
aw_worktime_start_of_day: "{{ aw_worktime_from }}"
aw_server_always_active_pattern: "aw-watcher-window"
aw_server_landingpage: "/activity/SHARKON2025/view/"
-27
View File
@@ -1,27 +0,0 @@
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_db_path: "/var/lib/activitywatch/aw-server-rust/sqlite.db"
aw_server_user: "activitywatch"
aw_server_group: "activitywatch"
aw_worktime_report_base: "http://10.10.10.13:5610"
aw_worktime_timezone: "Europe/Moscow"
aw_repo_root: "/mnt/usb_hdd2/Projects/ActivityWatch-Russian"
aw_server_cors_origins:
- "http://127.0.0.1:5600"
- "http://localhost:5600"
- "http://10.10.10.13:5600"
- "http://192.168.100.13:5600"
- "http://snb-live:5600"
aw_apply_worktime_settings: true
aw_worktime_from: "08:00"
aw_worktime_to: "17:00"
aw_worktime_start_of_day: "{{ aw_worktime_from }}"
-9
View File
@@ -1,9 +0,0 @@
# Secret handling:
# - put the real SSH password into env var before running Ansible:
# export AW_SSH_PASSWORD='...'
ansible_password: "{{ lookup('env', 'AW_SSH_PASSWORD') }}"
ansible_become: true
ansible_become_method: sudo
# If sudo password differs, set AW_SUDO_PASSWORD. Otherwise it will reuse AW_SSH_PASSWORD.
ansible_become_password: "{{ lookup('env', 'AW_SUDO_PASSWORD') | default(lookup('env', 'AW_SSH_PASSWORD'), true) }}"
-55
View File
@@ -1,55 +0,0 @@
# Secret handling:
# - put the real password into env var before running Ansible:
# export AW_WINRM_PASSWORD='...'
ansible_password: "{{ lookup('env', 'AW_WINRM_PASSWORD') }}"
aw_windows_repo_root: "{{ playbook_dir | dirname }}"
aw_windows_deploy_root: "C:\\Program Files\\AWatch-rus"
aw_windows_server_scheme: "http"
aw_windows_server_host: "10.10.10.13"
aw_windows_server_port: 5600
aw_windows_package_version: "v0.13.2"
aw_windows_package_url: "https://github.com/ActivityWatch/activitywatch/releases/download/v0.13.2/activitywatch-v0.13.2-windows-x86_64.zip"
aw_windows_package_zip_path: ""
aw_windows_domain: "SHARKON2025"
aw_windows_users:
- user1
- user2
- user3
- user4
- user5
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
aw_windows_file_ops_enabled: true
aw_windows_email_collector_enabled: true
aw_windows_email_collector_mode: "both"
aw_windows_local_agent_logs_enabled: false
aw_windows_incident_capture_enabled: true
aw_windows_incident_screenshot_enabled: true
aw_windows_incident_artifacts_root: "{{ aw_windows_state_root }}\\incident-artifacts"
aw_windows_logon_marker_enabled: true
aw_windows_skip_hardening: false
aw_windows_rules_path: "{{ aw_windows_deploy_root }}\\windows\\web-category-rules.example.json"
aw_windows_policy_path: "{{ aw_windows_deploy_root }}\\windows\\dlp-policy.example.json"
aw_windows_validation_remote_path: "{{ aw_windows_state_root }}\\aw_validate_ansible.json"
aw_windows_validation_local_dir: "/tmp/aw-rus-validation"
aw_windows_fail_on_validation_error: true
aw_windows_migration_enabled: false
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"
aw_windows_api_smoke_check_enabled: true
aw_windows_api_smoke_check_bucket: ""
aw_windows_api_smoke_check_limit: 10
+6 -25
View File
@@ -1,11 +1,7 @@
aw_windows_repo_root: "{{ playbook_dir | dirname }}"
aw_windows_deploy_root: "C:\\Program Files\\AWatch-rus"
aw_windows_server_scheme: "http"
aw_windows_repo_root: "/home/igor/tmp/AWatch-rus"
aw_windows_deploy_root: "C:\\Deploy\\AWatch-rus"
aw_windows_server_host: "10.10.10.13"
aw_windows_server_port: 5600
aw_windows_package_version: "v0.13.2"
aw_windows_package_url: "https://github.com/ActivityWatch/activitywatch/releases/download/v0.13.2/activitywatch-v0.13.2-windows-x86_64.zip"
aw_windows_package_zip_path: ""
aw_windows_domain: "SHARKON2025"
aw_windows_users:
- user1
@@ -18,13 +14,11 @@ aw_windows_extra_users: []
# 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_install_root: "C:\\Program Files\\ActivityWatch-Phase2"
aw_windows_state_root: "C:\\ProgramData\\ActivityWatch"
aw_windows_afk_enabled: true
aw_windows_window_enabled: true
aw_windows_file_ops_enabled: true
aw_windows_local_agent_logs_enabled: false
aw_windows_incident_capture_enabled: true
aw_windows_incident_screenshot_enabled: true
@@ -35,18 +29,5 @@ aw_windows_skip_hardening: false
aw_windows_rules_path: "{{ aw_windows_deploy_root }}\\windows\\web-category-rules.example.json"
aw_windows_policy_path: "{{ aw_windows_deploy_root }}\\windows\\dlp-policy.example.json"
aw_windows_validation_remote_path: "{{ aw_windows_state_root }}\\aw_validate_ansible.json"
aw_windows_validation_remote_path: "C:\\Windows\\Temp\\aw_validate_phase2_ansible.json"
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
aw_windows_api_smoke_check_bucket: ""
aw_windows_api_smoke_check_limit: 10
+10 -10
View File
@@ -1,16 +1,16 @@
---
# Полный установщик AWatch-rus.
# Выполняет развёртывание одной командой:
# 1) создание Proxmox CT + bootstrap AW (если в inventory есть [proxmox])
# 2) развёртывание AW server на хостах [aw_server]
# 3) развёртывание Windows/RDP collector'ов на [aw_windows]
# 4) развёртывание pfSense poller'а на [aw_pfsense_pollers]
# Full-stack installer for AWatch-rus.
# Runs end-to-end rollout in one command:
# 1) Proxmox CT provision + AW bootstrap (if [proxmox] exists in inventory)
# 2) AW server deploy on [aw_server] hosts
# 3) Windows phase2 rollout on [aw_windows] hosts
# 4) pfSense poller deploy on [aw_pfsense_pollers] hosts
#
# Примечания:
# - Заполняйте только нужные группы inventory для своего окружения.
# - Play без совпадающих host groups Ansible пропускает автоматически.
# Notes:
# - Keep only relevant inventory groups filled for your environment.
# - Plays with unmatched host groups are skipped automatically by Ansible.
- import_playbook: provision_proxmox_ct_and_deploy_aw.yml
- import_playbook: deploy_aw_server.yml
- import_playbook: deploy_aw_windows.yml
- import_playbook: deploy_aw_windows_phase2.yml
- import_playbook: deploy_aw_pfsense_poller.yml
+1 -4
View File
@@ -5,8 +5,5 @@ pve-main ansible_host=192.168.10.2 ansible_user=root ansible_port=22
aw-ct ansible_host=10.20.30.13 ansible_user=root ansible_port=22
[aw_windows]
# Примечание: в русифицированных Windows часто нужен "Администратор", а не "Administrator".
# NOTE: in RU-localized installs this account is often "Администратор" instead of "Administrator".
win-node1 ansible_host=192.168.100.21 ansible_user=Администратор ansible_password=CHANGE_ME ansible_connection=winrm ansible_winrm_transport=ntlm ansible_port=5985 ansible_winrm_server_cert_validation=ignore
[aw_pfsense_pollers]
# pfsense-poller1 ansible_host=192.168.100.30 ansible_user=root ansible_port=22
-5
View File
@@ -1,5 +0,0 @@
[aw_server]
localhost ansible_connection=local ansible_user=root
[aw_windows]
rdp-prod ansible_host=192.168.100.21 ansible_user=Администратор ansible_password=Sergei2009@ ansible_connection=winrm ansible_winrm_transport=ntlm ansible_port=5985 ansible_winrm_server_cert_validation=ignore
-98
View File
@@ -1,98 +0,0 @@
---
- name: Post-deploy validation for Windows/RDP AWatch-rus
hosts: aw_windows
gather_facts: false
vars:
aw_windows_deploy_root: "C:\\Program Files\\AWatch-rus"
aw_windows_state_root: "C:\\ProgramData\\AWatch-rus"
aw_windows_validation_remote_path: "{{ aw_windows_state_root }}\\aw_validate_ansible.json"
aw_windows_validation_local_dir: "/tmp/aw-rus-validation"
aw_windows_server_scheme: "http"
aw_windows_server_host: "10.10.10.13"
aw_windows_server_port: 5600
aw_windows_fail_on_validation_error: true
aw_windows_launch_task_pattern: "ActivityWatch Launch *"
aw_windows_recovery_task_name: "ActivityWatch Recovery"
aw_windows_force_task_restart: true
aw_windows_api_smoke_check_enabled: true
aw_windows_api_smoke_check_bucket: ""
aw_windows_api_smoke_check_limit: 10
tasks:
- name: Принудительно запустить ActivityWatch recovery и launch tasks
when: aw_windows_force_task_restart | bool
ansible.windows.win_powershell:
script: |
$ErrorActionPreference = 'Stop'
Start-ScheduledTask -TaskName "{{ aw_windows_recovery_task_name }}"
Get-ScheduledTask |
Where-Object TaskName -like "{{ aw_windows_launch_task_pattern }}" |
ForEach-Object { Start-ScheduledTask -TaskName $_.TaskName }
- name: Получить Windows hostname для AW smoke-check bucket
when: aw_windows_api_smoke_check_enabled | bool
ansible.windows.win_command: powershell.exe -NoProfile -Command "$env:COMPUTERNAME"
register: aw_windows_hostname_result
changed_when: false
- name: Вычислить AW AFK smoke-check bucket
when: aw_windows_api_smoke_check_enabled | bool
ansible.builtin.set_fact:
aw_windows_api_smoke_check_bucket_effective: >-
{{
aw_windows_api_smoke_check_bucket
if (aw_windows_api_smoke_check_bucket | default('') | string | length) > 0
else 'aw-watcher-afk_' ~ (aw_windows_hostname_result.stdout | trim)
}}
- name: Дождаться свежих AFK событий на AW server
when: aw_windows_api_smoke_check_enabled | bool
delegate_to: localhost
ansible.builtin.uri:
url: "{{ aw_windows_server_scheme }}://{{ aw_windows_server_host }}:{{ aw_windows_server_port }}/api/0/buckets/{{ aw_windows_api_smoke_check_bucket_effective }}/events?limit={{ aw_windows_api_smoke_check_limit }}"
method: GET
status_code: 200
return_content: true
register: aw_windows_api_smoke
until: >
aw_windows_api_smoke.status == 200 and
(aw_windows_api_smoke.json | length) > 0 and
(
aw_windows_api_smoke.json
| selectattr('data.status', 'equalto', 'not-afk')
| list
| length
) > 0
retries: 10
delay: 6
- name: Выполнить валидацию и сохранить отчёт на целевом Windows host
ansible.windows.win_powershell:
script: |
$ErrorActionPreference = 'Stop'
$report = & "{{ aw_windows_deploy_root }}\windows\validate-deployment.ps1" `
-ConfigPath "{{ aw_windows_state_root }}\deployment-config.json"
$report | ConvertTo-Json -Depth 12 | Out-File -FilePath "{{ aw_windows_validation_remote_path }}" -Encoding utf8
if ({{ '$true' if (aw_windows_fail_on_validation_error | bool) else '$false' }} -and -not [bool]$report.overallOk) {
throw "ActivityWatch validation failed. Report: {{ aw_windows_validation_remote_path }}"
}
- name: Создать локальный каталог для validation reports
ansible.builtin.file:
path: "{{ aw_windows_validation_local_dir }}"
state: directory
mode: "0755"
delegate_to: localhost
- name: Забрать validation report
ansible.builtin.fetch:
src: "{{ aw_windows_validation_remote_path }}"
dest: "{{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_ansible.json"
flat: true
- name: Показать путь к отчёту
ansible.builtin.debug:
msg:
- "Validation OK on {{ inventory_hostname }}."
- "Report: {{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_ansible.json"
@@ -1,5 +1,5 @@
---
- name: Создать один Proxmox CT и развернуть AWatch-rus
- name: Provision single Proxmox CT and deploy AWatch-rus
hosts: proxmox
gather_facts: false
@@ -9,12 +9,6 @@
- install_aw_server.sh
- apply_webui_ru_patch.sh
- activitywatch-server.service
- aw-worktime-api.py
- aw-worktime-api.service
- aw-worktime-ui-bridge.py
- aw-worktime-ui-bridge.service
- aw-worktime-ui-bridge.timer
- aw-worktime-panel.js
- aw-server.env.example
- aw-ru-patch.js
- aw-sw-cleanup.js
@@ -23,7 +17,7 @@
- settings/views-default.json
tasks:
- name: Выполнить workflow создания одного CT
- name: Execute single-CT provisioning workflow
ansible.builtin.include_tasks: tasks/provision_ct_and_deploy_aw.yml
vars:
ct_id: "{{ proxmox_ct_id }}"
@@ -1,5 +1,5 @@
---
- name: Создать матрицу Proxmox CT и развернуть AWatch-rus с RU patch
- name: Provision Proxmox CT matrix and deploy AWatch-rus with RU patch
hosts: proxmox
gather_facts: false
@@ -9,12 +9,6 @@
- install_aw_server.sh
- apply_webui_ru_patch.sh
- activitywatch-server.service
- aw-worktime-api.py
- aw-worktime-api.service
- aw-worktime-ui-bridge.py
- aw-worktime-ui-bridge.service
- aw-worktime-ui-bridge.timer
- aw-worktime-panel.js
- aw-server.env.example
- aw-ru-patch.js
- aw-sw-cleanup.js
@@ -23,14 +17,14 @@
- settings/views-default.json
tasks:
- name: Проверить, что матрица CT задана
- name: Validate CT matrix is provided
ansible.builtin.assert:
that:
- proxmox_ct_matrix is defined
- proxmox_ct_matrix | length > 0
fail_msg: "Задайте proxmox_ct_matrix в group_vars/proxmox-matrix.yml"
fail_msg: "Define proxmox_ct_matrix in group_vars/proxmox-matrix.yml"
- name: Выполнить workflow создания для каждого CT
- name: Execute provisioning workflow for each CT
ansible.builtin.include_tasks: tasks/provision_ct_and_deploy_aw.yml
vars:
ct_id: "{{ item.id }}"
+23 -35
View File
@@ -1,5 +1,5 @@
---
- name: Проверить обязательные переменные CT
- name: Validate required per-CT variables
ansible.builtin.assert:
that:
- ct_id is defined
@@ -27,14 +27,14 @@
- aw_server_log_dir is defined
- aw_server_user is defined
- aw_server_group is defined
fail_msg: "Не заданы обязательные переменные для создания CT и развёртывания."
fail_msg: "Missing required variables for CT provisioning/deploy."
- name: Сформировать сетевую строку CT
- name: Build CT network string
ansible.builtin.set_fact:
ct_net0: >-
name=eth0,bridge={{ ct_bridge }},ip={{ ct_ip }},gw={{ ct_gw }}{% if (ct_vlan | default('') | string | length) > 0 %},tag={{ ct_vlan }}{% endif %}
- name: Проверить, существует ли CT
- name: Check whether CT already exists
ansible.builtin.command:
argv:
- pct
@@ -44,7 +44,7 @@
failed_when: false
changed_when: false
- name: Создать CT, если он отсутствует
- name: Create CT when absent
ansible.builtin.command:
argv:
- pct
@@ -78,9 +78,8 @@
- --ostype
- debian
when: ct_status_check.rc != 0
no_log: true
- name: Проверить текущее состояние CT
- name: Check current CT runtime state
ansible.builtin.command:
argv:
- pct
@@ -89,7 +88,7 @@
register: ct_runtime_status
changed_when: false
- name: Запустить CT, если он остановлен
- name: Start CT when stopped
ansible.builtin.command:
argv:
- pct
@@ -97,23 +96,20 @@
- "{{ ct_id }}"
when: "'stopped' in ct_runtime_status.stdout"
- name: Создать bootstrap каталог на Proxmox host
- name: Ensure bootstrap directory on Proxmox host
ansible.builtin.file:
path: "{{ item }}"
path: "{{ proxmox_bootstrap_dir }}"
state: directory
mode: "0700"
loop:
- "{{ proxmox_bootstrap_dir }}"
- "{{ proxmox_bootstrap_dir }}/settings"
- name: Скопировать AW bootstrap файлы во временный каталог Proxmox host
- name: Copy AW bootstrap files to Proxmox host temp
ansible.builtin.copy:
src: "{{ aw_repo_root }}/aw-server/{{ item }}"
dest: "{{ proxmox_bootstrap_dir }}/{{ item }}"
mode: "0644"
loop: "{{ aw_bootstrap_files }}"
- name: Установить базовые зависимости ОС внутри CT
- name: Bootstrap CT OS dependencies
ansible.builtin.command:
argv:
- pct
@@ -126,16 +122,12 @@
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y curl ca-certificates bash unzip xz-utils jq rsync openssh-server python3
mkdir -p /root/bootstrap/settings /etc/activitywatch
apt-get install -y curl ca-certificates bash unzip xz-utils jq rsync openssh-server
mkdir -p /root/bootstrap /etc/activitywatch
systemctl enable ssh || true
systemctl restart ssh || true
register: ct_bootstrap_result
retries: 10
delay: 6
until: ct_bootstrap_result.rc == 0
- name: Передать bootstrap файлы внутрь CT
- name: Push bootstrap files into CT
ansible.builtin.command:
argv:
- pct
@@ -145,7 +137,7 @@
- "/root/bootstrap/{{ item }}"
loop: "{{ aw_bootstrap_files }}"
- name: Записать AW server env во временный каталог Proxmox host
- name: Write AW server env file on Proxmox host temp
ansible.builtin.copy:
dest: "{{ proxmox_bootstrap_dir }}/aw-server.env"
mode: "0600"
@@ -159,11 +151,8 @@
AW_SERVER_LOG_DIR={{ aw_server_log_dir }}
AW_SERVER_USER={{ aw_server_user }}
AW_SERVER_GROUP={{ aw_server_group }}
AW_WORKTIME_REPORT_BASE={{ aw_worktime_report_base }}
AW_WORKTIME_TZ={{ aw_worktime_timezone }}
no_log: true
- name: Передать AW server env внутрь CT
- name: Push AW server env into CT
ansible.builtin.command:
argv:
- pct
@@ -171,9 +160,8 @@
- "{{ ct_id }}"
- "{{ proxmox_bootstrap_dir }}/aw-server.env"
- /etc/activitywatch/aw-server.env
no_log: true
- name: Настроить права env файла внутри CT
- name: Set mode for env inside CT
ansible.builtin.command:
argv:
- pct
@@ -184,7 +172,7 @@
- "0600"
- /etc/activitywatch/aw-server.env
- name: Установить сервер и применить RU patch внутри CT
- name: Install server and apply RU patch inside CT
ansible.builtin.command:
argv:
- pct
@@ -200,7 +188,7 @@
bash /root/bootstrap/apply_webui_ru_patch.sh
systemctl restart activitywatch-server.service
- name: Проверить AW API изнутри CT
- name: Validate AW API from inside CT
ansible.builtin.command:
argv:
- pct
@@ -211,7 +199,7 @@
- -lc
- "curl -fsS http://127.0.0.1:{{ aw_server_port }}/api/0/info >/dev/null"
- name: Проверить hooks RU patch в index.html
- name: Validate RU patch hooks in index
ansible.builtin.command:
argv:
- pct
@@ -222,8 +210,8 @@
- -lc
- "grep -q 'ru-patch-v5.js' {{ aw_server_webui_dir }}/index.html && grep -q 'sw-cleanup.js' {{ aw_server_webui_dir }}/index.html"
- name: Показать итоговый endpoint
- name: Show final endpoint
ansible.builtin.debug:
msg:
- "CT {{ ct_id }} создан и настроен."
- "Endpoint ActivityWatch: http://{{ ct_ip | regex_replace('/[0-9]+$', '') }}:{{ aw_server_port }}"
- "CT {{ ct_id }} is provisioned and configured."
- "ActivityWatch endpoint: http://{{ ct_ip | regex_replace('/[0-9]+$', '') }}:{{ aw_server_port }}"
+2 -2
View File
@@ -9,7 +9,7 @@ EnvironmentFile=/etc/activitywatch/aw-server.env
User=__AW_SERVER_USER__
Group=__AW_SERVER_GROUP__
WorkingDirectory=__AW_SERVER_DATA_DIR__
ExecStart=/bin/sh -lc 'exec /opt/activitywatch/bin/aw-server-rust --host "$AW_SERVER_BIND_HOST" --port "$AW_SERVER_PORT" --dbpath "$AW_SERVER_DB_PATH" --webpath "$AW_SERVER_WEBUI_DIR"'
ExecStart=/bin/sh -lc 'exec /opt/activitywatch/bin/aw-server-rust --host "$AW_SERVER_BIND_HOST" --port "$AW_SERVER_PORT"'
Restart=on-failure
RestartSec=5s
StateDirectory=activitywatch
@@ -17,7 +17,7 @@ LogsDirectory=activitywatch
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=read-only
ProtectHome=true
LimitNOFILE=65535
[Install]
+4 -62
View File
@@ -10,97 +10,39 @@ fi
source "$ENV_FILE"
WEBUI_DIR="${AW_SERVER_WEBUI_DIR:-${AW_WEBUI_DIR:-/opt/activitywatch/webui-ru}}"
REPORT_BASE="${AW_WORKTIME_REPORT_BASE:-http://10.10.10.13:5610}"
PATCH_JS_SRC="/root/bootstrap/aw-ru-patch.js"
SW_CLEANUP_SRC="/root/bootstrap/aw-sw-cleanup.js"
WORKTIME_PANEL_SRC="/root/bootstrap/aw-worktime-panel.js"
HOST_GROUPS_SRC="/root/bootstrap/aw-host-groups.json"
INDEX_HTML="$WEBUI_DIR/index.html"
SERVICE_WORKER="$WEBUI_DIR/service-worker.js"
TS=$(date +%Y%m%d%H%M%S)
PATCH_TARGET="$WEBUI_DIR/js/ru-patch-v5.js"
SW_TARGET="$WEBUI_DIR/js/sw-cleanup.js"
WORKTIME_PANEL_TARGET="$WEBUI_DIR/js/aw-worktime-panel.js"
HOST_GROUPS_TARGET="$WEBUI_DIR/js/aw-host-groups.json"
TRENDS_NEEDLE='this.activityStore.query_category_time_by_period(r)'
TRENDS_REPLACEMENT='this.activityStore.ensure_loaded(r)'
TIMESPIRAL_NEEDLE='start:new Date("2022-08-08")'
TIMESPIRAL_REPLACEMENT='start:new Date(Date.now()-12*36e5)'
CATEGORY_HELPER_NEEDLE='hostname:t.hostnameChoices[0]'
CATEGORY_HELPER_REPLACEMENT='hostname:t.hostnameChoices.filter((function(t){return"unknown"!==t&&"undefined"!==t}))[0]||t.hostnameChoices[0]'
CATEGORY_HELPER_REPLACEMENT='hostname:t.hostnameChoices.filter((function(t){return"unknown"!==t}))[0]||t.hostnameChoices[0]'
[[ -f "$PATCH_JS_SRC" ]] || { echo "missing $PATCH_JS_SRC" >&2; exit 1; }
[[ -f "$SW_CLEANUP_SRC" ]] || { echo "missing $SW_CLEANUP_SRC" >&2; exit 1; }
[[ -f "$WORKTIME_PANEL_SRC" ]] || { echo "missing $WORKTIME_PANEL_SRC" >&2; exit 1; }
[[ -f "$HOST_GROUPS_SRC" ]] || { echo "missing $HOST_GROUPS_SRC" >&2; exit 1; }
[[ -f "$INDEX_HTML" ]] || { echo "missing $INDEX_HTML" >&2; exit 1; }
install -d "$WEBUI_DIR/js"
install -m 0644 "$PATCH_JS_SRC" "$PATCH_TARGET"
install -m 0644 "$SW_CLEANUP_SRC" "$SW_TARGET"
install -m 0644 "$WORKTIME_PANEL_SRC" "$WORKTIME_PANEL_TARGET"
install -m 0644 "$HOST_GROUPS_SRC" "$HOST_GROUPS_TARGET"
cp "$INDEX_HTML" "$INDEX_HTML.bak.$TS"
patch_hash="$(sha1sum "$PATCH_TARGET" | awk '{print substr($1,1,12)}')"
sw_hash="$(sha1sum "$SW_TARGET" | awk '{print substr($1,1,12)}')"
worktime_panel_hash="$(sha1sum "$WORKTIME_PANEL_TARGET" | awk '{print substr($1,1,12)}')"
python3 - "$WORKTIME_PANEL_TARGET" "$REPORT_BASE" <<'PY'
from pathlib import Path
import sys
path = Path(sys.argv[1])
report_base = sys.argv[2]
text = path.read_text()
text = text.replace("__AW_WORKTIME_REPORT_BASE__", report_base)
path.write_text(text)
PY
python3 - "$INDEX_HTML" "$sw_hash" "$patch_hash" "$worktime_panel_hash" "$REPORT_BASE" <<'PY'
from pathlib import Path
import re
import sys
path = Path(sys.argv[1])
sw_hash = sys.argv[2]
patch_hash = sys.argv[3]
panel_hash = sys.argv[4]
report_base = sys.argv[5]
content = path.read_text()
content = re.sub(
r'<script[^>]+(?:ru-patch-v5\.js|sw-cleanup\.js|aw-ru-patch\.js|aw-sw-cleanup\.js|aw-worktime-panel\.js)[^>]*></script>',
'',
content,
)
content = re.sub(r"; frame-src 'self' [^\";>]*", "", content)
content = content.replace(
"script-src 'self' 'unsafe-eval'",
f"script-src 'self' 'unsafe-eval'; frame-src 'self' {report_base}",
1,
)
content = content.replace(
"</head>",
f'<script src="/js/sw-cleanup.js?v={sw_hash}"></script></head>',
1,
)
content = content.replace(
"</body>",
(
f'<script defer="defer" src="/js/ru-patch-v5.js?v={patch_hash}"></script>'
f'<script defer="defer" src="/js/aw-worktime-panel.js?v={panel_hash}"></script></body>'
),
1,
)
if 'id="aw-report-links"' not in content:
content = content.replace(
"</body>",
'<div id="aw-report-links" style="position:fixed;right:12px;bottom:12px;z-index:99999;background:#111;color:#fff;padding:8px 10px;border-radius:8px;font:12px/1.4 sans-serif;opacity:.9">RDP report: loading...</div></body>',
1,
)
path.write_text(content)
PY
sed -i '/ru-patch-v5.js/d;/sw-cleanup.js/d;/aw-ru-patch.js/d;/aw-sw-cleanup.js/d' "$INDEX_HTML"
sed -i "s#</head>#<script src=\"/js/sw-cleanup.js?v=$sw_hash\"></script></head>#" "$INDEX_HTML"
sed -i "s#</body>#<script defer=\"defer\" src=\"/js/ru-patch-v5.js?v=$patch_hash\"></script></body>#" "$INDEX_HTML"
cp "$SW_CLEANUP_SRC" "$SERVICE_WORKER"
trends_chunk="$(grep -Rsl "$TRENDS_NEEDLE" "$WEBUI_DIR/js"/*.js 2>/dev/null | head -n 1 || true)"
-15
View File
@@ -29,21 +29,6 @@
{ "label": "DLP", "type": "bucket", "bucket_prefix": "aw-dlp-endpoint-signals_" }
]
},
{
"id": "linux-remote",
"name": "Linux remote workers",
"description": "Linux-хосты удалённых сотрудников: GUI активность, SSH/console и browser admin UI.",
"patterns": [
"^(LINUX-WS|LINUX-DESKTOP|LX-|DESKTOP-|ADMIN-|WORKSTATION-|DEVBOX-)"
],
"links": [
{ "label": "Активность", "type": "activity" },
{ "label": "SSH сессии", "type": "bucket", "bucket_prefix": "aw-ssh-sessions_" },
{ "label": "Команды shell", "type": "bucket", "bucket_prefix": "aw-console-commands_" },
{ "label": "Web категории", "type": "bucket", "bucket_prefix": "aw-detmir-web-category_" },
{ "label": "Все бакеты", "type": "buckets" }
]
},
{
"id": "virtual-infra",
"name": "Virtual servers + Proxmox",
+164 -90
View File
@@ -1,6 +1,6 @@
(function () {
window.__awRuPatchVersion = "template-v12-activity-heading-ru";
document.documentElement.setAttribute("data-aw-ru-patch", "template-v12-activity-heading-ru");
window.__awRuPatchVersion = "template-v13-pve-audit-day-filter";
document.documentElement.setAttribute("data-aw-ru-patch", "template-v13-pve-audit-day-filter");
const exact = new Map([
["ActivityWatch", "АктивВотч"],
@@ -333,6 +333,11 @@
'.aw-ru-host-item-title { font-weight: 600; margin-bottom: 6px; }',
'.aw-ru-host-links { display: flex; flex-wrap: wrap; gap: 6px; }',
'.aw-ru-host-links a { display: inline-block; padding: 4px 8px; border-radius: 999px; background: rgba(90,140,255,.15); text-decoration: none; }',
'.aw-ru-worktime-center { margin: 16px 0; padding: 16px; border: 1px solid rgba(120,120,120,.35); border-radius: 8px; background: rgba(20,20,20,.03); }',
'.aw-ru-worktime-controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: end; margin-bottom: 10px; }',
'.aw-ru-worktime-controls label { font-size: 12px; display:flex; flex-direction:column; gap:4px; }',
'.aw-ru-worktime-table { width: 100%; border-collapse: collapse; font-size: 13px; }',
'.aw-ru-worktime-table th, .aw-ru-worktime-table td { border: 1px solid rgba(120,120,120,.25); padding: 6px 8px; text-align: left; }',
'.aw-ru-pve-audit { margin: 16px 0; padding: 16px; border: 1px solid rgba(120,120,120,.35); border-radius: 8px; background: rgba(10,20,40,.04); }',
'.aw-ru-pve-audit-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; margin: 12px 0 16px; }',
'.aw-ru-pve-audit-card { border: 1px solid rgba(120,120,120,.22); border-radius: 8px; padding: 12px; background: rgba(255,255,255,.02); }',
@@ -370,16 +375,6 @@
return /^pve[-_]/i.test(String(host || ""));
}
function isLikelyClientHost(host) {
const value = String(host || "").trim();
if (!value) return false;
if (/^(?:unknown|undefined|null)$/i.test(value)) return false;
if (/^(?:localhost|127\.0\.0\.1|0\.0\.0\.0|::1)$/i.test(value)) return false;
if (/^(?:\d{1,3}\.){3}\d{1,3}$/.test(value)) return false;
if (value.indexOf(":") !== -1 && /^[0-9a-f:\[\]]+$/i.test(value)) return false;
return true;
}
function enforceSafeActivityViewForPveHost() {
const hash = window.location.hash || "";
const match = hash.match(/^#\/activity\/([^/]+)\/day\/([^/]+)\/view\/([^/?#]+)/i);
@@ -396,9 +391,9 @@
function getDlpHostFromSettings(settings) {
const routeHost = getCurrentHostFromHash();
if (isLikelyClientHost(routeHost)) return routeHost;
if (routeHost) return routeHost;
const bucketHost = getDlpHostFromBucketId(getDlpBucketIdFromHash());
if (isLikelyClientHost(bucketHost)) return bucketHost;
if (bucketHost) return bucketHost;
return getTrendsHostFromSettings(settings);
}
@@ -690,19 +685,6 @@
{ label: "DLP", type: "bucket", bucket_prefix: "aw-dlp-endpoint-signals_" }
]
},
{
id: "linux-remote",
name: "Linux remote workers",
description: "Linux-хосты удалённых сотрудников: GUI активность, SSH/console и browser admin UI.",
patterns: ["^(LINUX-WS|LINUX-DESKTOP|LX-|DESKTOP-|ADMIN-|WORKSTATION-|DEVBOX-)"],
links: [
{ label: "Активность", type: "activity" },
{ label: "SSH сессии", type: "bucket", bucket_prefix: "aw-ssh-sessions_" },
{ label: "Команды shell", type: "bucket", bucket_prefix: "aw-console-commands_" },
{ label: "Web категории", type: "bucket", bucket_prefix: "aw-detmir-web-category_" },
{ label: "Все бакеты", type: "buckets" }
]
},
{
id: "virtual-infra",
name: "Virtual servers + Proxmox",
@@ -758,20 +740,29 @@
return !!(match && isPveLikeHost(decodeURIComponent(match[1] || "")));
}
function getSelectedDayFromHash() {
const hash = window.location.hash || "";
const match = hash.match(/^#\/activity\/[^/]+\/day\/([^/?#]+)/i);
return match && match[1] ? decodeURIComponent(match[1]) : "";
}
function filterEventsBySelectedDay(events) {
const day = getSelectedDayFromHash();
if (!day) return Array.isArray(events) ? events : [];
const start = new Date(day + "T00:00:00");
const end = new Date(day + "T23:59:59.999");
return (Array.isArray(events) ? events : []).filter(function (event) {
const timestamp = event && event.timestamp ? new Date(event.timestamp) : null;
return timestamp && !isNaN(timestamp.getTime()) && timestamp >= start && timestamp <= end;
});
}
function extractHostFromBucket(bucketId, bucketMeta) {
if (bucketMeta && bucketMeta.hostname) return String(bucketMeta.hostname);
const prefixes = [
"aw-watcher-window_",
"aw-watcher-afk_",
"aw-console-commands_",
"aw-ssh-sessions_",
"aw-linux-web-context_",
"aw-detmir-web-category_",
"aw-dlp-endpoint-signals_",
"aw-session-events_",
"aw-worktime-sessions_",
"aw-pve-webadmin-events_",
"aw-pve-task-events_",
"aw-dlp-incidents_",
"aw-pfsense-health_",
"aw-pfsense-gateways_",
@@ -801,27 +792,7 @@
return result;
}
function hostHasBucketPrefix(hostBuckets, prefix) {
return (hostBuckets || []).some(function (bucketId) {
return String(bucketId || "").indexOf(prefix) === 0;
});
}
function matchHostGroup(host, groups, hostBuckets) {
const bucketList = hostBuckets || [];
if (hostHasBucketPrefix(bucketList, "aw-dlp-endpoint-signals_") || hostHasBucketPrefix(bucketList, "aw-session-events_")) {
return "windows-rdp";
}
if (
hostHasBucketPrefix(bucketList, "aw-console-commands_") ||
hostHasBucketPrefix(bucketList, "aw-ssh-sessions_") ||
hostHasBucketPrefix(bucketList, "aw-linux-web-context_") ||
hostHasBucketPrefix(bucketList, "aw-detmir-web-category_")
) {
if (!hostHasBucketPrefix(bucketList, "aw-pve-webadmin-events_") && !hostHasBucketPrefix(bucketList, "aw-pve-task-events_")) {
return "linux-remote";
}
}
function matchHostGroup(host, groups) {
for (const group of groups) {
const patterns = Array.isArray(group.patterns) ? group.patterns : [];
for (const pattern of patterns) {
@@ -864,7 +835,7 @@
grouped.set("__ungrouped__", []);
Array.from(hostBuckets.keys()).sort().forEach(function (host) {
const groupId = matchHostGroup(host, groups, hostBuckets.get(host) || []) || "__ungrouped__";
const groupId = matchHostGroup(host, groups) || "__ungrouped__";
grouped.get(groupId).push(host);
});
@@ -920,7 +891,7 @@
center.setAttribute("data-aw-ru-host-groups", "1");
center.innerHTML =
'<h4>Разделы хостов</h4>' +
'<p>Здесь хосты разделены на Windows RDP, Linux remote workers и инфраструктурные узлы.</p>' +
'<p>Здесь хосты разделены на пользовательские Windows RDP и инфраструктурные виртуальные серверы/Proxmox.</p>' +
'<div class="aw-ru-host-groups-grid" data-aw-ru-host-groups-grid><section class="aw-ru-host-group-card"><p>Загрузка...</p></section></div>';
heading.parentElement.insertBefore(center, heading.nextSibling);
}
@@ -1357,10 +1328,10 @@
loadBucketEvents("aw-console-commands_" + host, 50).catch(function () { return []; })
]);
const data = {
web: webEvents || [],
tasks: taskEvents || [],
ssh: sshEvents || [],
cmd: cmdEvents || []
web: filterEventsBySelectedDay(webEvents || []),
tasks: filterEventsBySelectedDay(taskEvents || []),
ssh: filterEventsBySelectedDay(sshEvents || []),
cmd: filterEventsBySelectedDay(cmdEvents || [])
};
center.querySelector("[data-aw-ru-pve-web-count]").textContent = String(data.web.length);
center.querySelector("[data-aw-ru-pve-task-count]").textContent = String(data.tasks.length);
@@ -1476,8 +1447,7 @@
if (!settings || typeof settings !== "object") return "";
const landingpage = typeof settings.landingpage === "string" ? settings.landingpage : "";
const match = landingpage.match(/\/activity\/([^/]+)/);
const host = match && match[1] ? decodeURIComponent(match[1]) : "";
return isLikelyClientHost(host) ? host : "";
return match && match[1] ? match[1] : "";
}
function getTrendsPath(hash) {
@@ -1544,35 +1514,22 @@
.map(function (bucketId) { return bucketId.replace(/^aw-watcher-window_/i, ""); })
.filter(Boolean)
.filter(function (host) { return !/^unknown$/i.test(host); });
if (isLikelyClientHost(settingsHost) && hosts.indexOf(settingsHost) >= 0) return settingsHost;
if (settingsHost && hosts.indexOf(settingsHost) >= 0) return settingsHost;
if (settingsHost) return settingsHost;
hosts.sort();
return hosts[0] || "";
}
function rewriteUnknownCategoryBuilderQueryBody(body) {
if (typeof body !== "string") return body;
if (body.indexOf("undefined") !== -1) {
body = body
.replace(/flood\(query_bucket\(find_bucket\(\\"undefined\\"\)\)\)/g, '[]')
.replace(/query_bucket\(find_bucket\(\\"undefined\\"\)\)/g, '[]')
.replace(/flood\(query_bucket\(\\"undefined\\"\)\)/g, '[]')
.replace(/query_bucket\(\\"undefined\\"\)/g, '[]');
const ph = getPreferredWindowHostFromBuckets();
if (ph) {
body = body
.replace(/aw-watcher-window_undefined/g, "aw-watcher-window_" + ph)
.replace(/aw-watcher-afk_undefined/g, "aw-watcher-afk_" + ph);
}
if (body.indexOf("aw-watcher-window_unknown") === -1 && body.indexOf("aw-watcher-afk_unknown") === -1) {
return body;
}
if (body.indexOf("aw-watcher-window_unknown") !== -1 || body.indexOf("aw-watcher-afk_unknown") !== -1) {
const preferredHost = getPreferredWindowHostFromBuckets();
if (preferredHost) {
body = body
.replace(/aw-watcher-window_unknown/g, "aw-watcher-window_" + preferredHost)
.replace(/aw-watcher-afk_unknown/g, "aw-watcher-afk_" + preferredHost);
}
}
return body;
const preferredHost = getPreferredWindowHostFromBuckets();
if (!preferredHost) return body;
return body
.replace(/aw-watcher-window_unknown/g, "aw-watcher-window_" + preferredHost)
.replace(/aw-watcher-afk_unknown/g, "aw-watcher-afk_" + preferredHost);
}
function installCategoryBuilderNetworkPatch() {
@@ -1584,8 +1541,7 @@
window.fetch = function (input, init) {
try {
const url = typeof input === "string" ? input : String(input && input.url || "");
const isCategoryBuilderRoute = /^#\/settings\/category-builder(?:[/?#]|$)/i.test(window.location.hash || "");
if (isCategoryBuilderRoute && /\/api\/0\/query\/?$/i.test(url) && init && typeof init.body === "string") {
if (/\/api\/0\/query\/?$/i.test(url) && init && typeof init.body === "string") {
init = Object.assign({}, init, {
body: rewriteUnknownCategoryBuilderQueryBody(init.body)
});
@@ -1609,8 +1565,7 @@
proto.send = function (body) {
try {
const url = String(this.__awRuUrl || "");
const isCategoryBuilderRoute = /^#\/settings\/category-builder(?:[/?#]|$)/i.test(window.location.hash || "");
if (isCategoryBuilderRoute && /\/api\/0\/query\/?$/i.test(url) && typeof body === "string") {
if (/\/api\/0\/query\/?$/i.test(url) && typeof body === "string") {
body = rewriteUnknownCategoryBuilderQueryBody(body);
}
} catch (error) {
@@ -1651,6 +1606,124 @@
});
}
function ms(v) {
return new Date(v).getTime();
}
function fmtHours(sec) {
return (Math.round((sec / 3600) * 100) / 100).toFixed(2);
}
function rangesIntersect(a0, a1, b0, b1) {
return Math.max(a0, b0) < Math.min(a1, b1);
}
function intersectDurationSeconds(a0, a1, b0, b1) {
return Math.max(0, (Math.min(a1, b1) - Math.max(a0, b0)) / 1000);
}
async function renderPersonalWorktime(host, fromIso, toIso, target) {
const sessionBucket = "aw-worktime-sessions_" + host;
const windowBucket = "aw-watcher-window_" + host;
const afkBucket = "aw-watcher-afk_" + host;
const [sessionEvents, windowEvents, afkEvents] = await Promise.all([
loadBucketEvents(sessionBucket, 5000).catch(function () { return []; }),
loadBucketEvents(windowBucket, 10000).catch(function () { return []; }),
loadBucketEvents(afkBucket, 10000).catch(function () { return []; })
]);
const fromMs = ms(fromIso);
const toMs = ms(toIso);
const userRanges = {};
(sessionEvents || []).forEach(function (e) {
const d = e && e.data ? e.data : {};
const u = String(d.username || "").trim();
if (!u) return;
const t0 = ms(e.timestamp);
const t1 = t0 + Math.max(1, Number(e.duration || 0)) * 1000;
if (!rangesIntersect(t0, t1, fromMs, toMs)) return;
userRanges[u] = userRanges[u] || [];
userRanges[u].push([t0, t1]);
});
const activeRanges = [];
const afkByTs = new Map();
(afkEvents || []).forEach(function (e) { afkByTs.set(String(e.timestamp || ""), e); });
(windowEvents || []).forEach(function (e) {
const t0 = ms(e.timestamp);
const t1 = t0 + Math.max(1, Number(e.duration || 0)) * 1000;
if (!rangesIntersect(t0, t1, fromMs, toMs)) return;
const afk = afkByTs.get(String(e.timestamp || ""));
const status = String(afk && afk.data && afk.data.status || "");
if (status && status.toLowerCase() === "afk") return;
activeRanges.push([t0, t1]);
});
const rows = Object.keys(userRanges).sort().map(function (u) {
let presentSec = 0;
let activeSec = 0;
userRanges[u].forEach(function (r) {
presentSec += intersectDurationSeconds(r[0], r[1], fromMs, toMs);
activeRanges.forEach(function (a) {
activeSec += intersectDurationSeconds(r[0], r[1], a[0], a[1]);
});
});
const idleSec = Math.max(0, presentSec - activeSec);
return { user: u, presentSec: presentSec, activeSec: activeSec, idleSec: idleSec };
});
if (!rows.length) {
target.innerHTML = '<div class="aw-ru-pve-audit-muted">Нет данных в aw-worktime-sessions_' + host + ' за выбранный интервал.</div>';
return;
}
target.innerHTML = '<table class="aw-ru-worktime-table"><thead><tr><th>Пользователь</th><th>Активно, ч</th><th>В сессии, ч</th><th>Простой, ч</th></tr></thead><tbody>' +
rows.map(function (r) {
return '<tr><td>' + r.user + '</td><td>' + fmtHours(r.activeSec) + '</td><td>' + fmtHours(r.presentSec) + '</td><td>' + fmtHours(r.idleSec) + '</td></tr>';
}).join("") +
'</tbody></table>';
}
async function injectPersonalWorktimeCenter(root) {
const isActivity = /^#\/activity\//i.test(window.location.hash || "");
if (!isActivity) return;
if (root.querySelector(".aw-ru-worktime-center")) return;
const host = getCurrentHostFromHash();
if (!host) return;
const container = document.createElement("div");
container.className = "aw-ru-worktime-center";
const now = new Date();
const day = now.toISOString().slice(0, 10);
container.innerHTML =
'<h3 style="margin-top:0">Персональный учёт рабочего времени</h3>' +
'<div class="aw-ru-worktime-controls">' +
'<label>Дата <input type="date" data-aw-wt-date value="' + day + '"></label>' +
'<label>С <input type="time" data-aw-wt-from value="09:00"></label>' +
'<label>По <input type="time" data-aw-wt-to value="18:00"></label>' +
'<button type="button" data-aw-wt-run>Рассчитать</button>' +
'</div>' +
'<div data-aw-wt-result class="aw-ru-pve-audit-muted">Нажмите "Рассчитать".</div>';
root.insertBefore(container, root.firstChild);
const run = async function () {
const date = container.querySelector("[data-aw-wt-date]").value;
const tFrom = container.querySelector("[data-aw-wt-from]").value || "09:00";
const tTo = container.querySelector("[data-aw-wt-to]").value || "18:00";
const result = container.querySelector("[data-aw-wt-result]");
result.textContent = "Расчёт...";
const fromIso = new Date(date + "T" + tFrom + ":00").toISOString();
const toIso = new Date(date + "T" + tTo + ":00").toISOString();
try {
await renderPersonalWorktime(host, fromIso, toIso, result);
} catch (e) {
result.textContent = "Ошибка расчёта: " + String(e.message || e);
}
};
container.querySelector("[data-aw-wt-run]").addEventListener("click", function () { run(); });
}
function applyPatch() {
enforceSafeActivityViewForPveHost();
ensureSettingsHost();
@@ -1666,6 +1739,7 @@
injectDlpNavigation(document.body);
injectDlpReviewCenter(document.body);
injectDlpAlertsCenter(document.body);
injectPersonalWorktimeCenter(document.body).catch(function () {});
injectHostGroupsCenter(document.body).catch(function () {});
redirectBareTrendsRoute();
}
-2
View File
@@ -9,5 +9,3 @@ AW_SERVER_DATA_DIR=/var/lib/activitywatch
AW_SERVER_LOG_DIR=/var/log/activitywatch
AW_SERVER_USER=activitywatch
AW_SERVER_GROUP=activitywatch
AW_WORKTIME_REPORT_BASE=http://10.10.10.13:5610
AW_WORKTIME_TZ=Europe/Moscow
-246
View File
@@ -1,246 +0,0 @@
#!/usr/bin/env python3
from http.server import BaseHTTPRequestHandler, HTTPServer
import csv
import io
import json
import os
import urllib.request
from datetime import datetime, timezone, timedelta
from zoneinfo import ZoneInfo
AW = "http://127.0.0.1:5600/api/0"
REPORT_TZ = ZoneInfo(os.environ.get("AW_WORKTIME_TZ", "Europe/Moscow"))
def get(u):
with urllib.request.urlopen(u, timeout=30) as r:
return json.loads(r.read().decode())
def pts(s):
return datetime.fromisoformat(s.replace("Z", "+00:00")).astimezone(timezone.utc)
def report_today():
now_local = datetime.now(REPORT_TZ)
start_local = datetime(now_local.year, now_local.month, now_local.day, tzinfo=REPORT_TZ)
end_local = start_local + timedelta(days=1) - timedelta(seconds=1)
start = start_local.astimezone(timezone.utc)
end = end_local.astimezone(timezone.utc)
b = get(AW + "/buckets")
sb = next((k for k in b if k.startswith("aw-worktime-sessions_")), None)
if not sb:
return []
ev = get(f"{AW}/buckets/{sb}/events?limit=50000")
by = {}
for e in ev:
ts = pts(e.get("timestamp"))
if ts < start or ts > end:
continue
d = e.get("data") or {}
user = (d.get("username") or "").strip()
if not user:
continue
state = (d.get("state") or "").lower()
active = ("актив" in state) or (state == "active")
row = by.setdefault(user, {"active": set(), "first": None, "last": None, "rows": 0})
row["rows"] += 1
if active:
second = ts.replace(microsecond=0)
row["active"].add(second)
row["first"] = second if row["first"] is None or second < row["first"] else row["first"]
row["last"] = second if row["last"] is None or second > row["last"] else row["last"]
rows = []
full = int((end_local - start_local).total_seconds())
for user in sorted(by):
row = by[user]
active_seconds = len(row["active"])
rows.append({
"user": user,
"active_seconds": active_seconds,
"active_hhmm": "%02d:%02d" % (active_seconds // 3600, (active_seconds % 3600) // 60),
"first_activity": row["first"].isoformat().replace("+00:00", "Z") if row["first"] else "",
"last_activity": row["last"].isoformat().replace("+00:00", "Z") if row["last"] else "",
"idle_seconds": max(0, full - active_seconds),
"sessions_count": row["rows"],
})
return rows
def render_html(rows):
generated = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
date_local = datetime.now(REPORT_TZ).strftime("%Y-%m-%d")
trs = []
for row in rows:
trs.append(
"<tr>"
f"<td>{row['user']}</td>"
f"<td>{row['active_hhmm']}</td>"
f"<td>{row['active_seconds']}</td>"
f"<td>{row['first_activity']}</td>"
f"<td>{row['last_activity']}</td>"
f"<td>{row['idle_seconds']}</td>"
f"<td>{row['sessions_count']}</td>"
"</tr>"
)
if not trs:
trs.append('<tr><td colspan="7">No data for today yet.</td></tr>')
return f"""<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>AW-rus Worktime</title>
<style>
:root {{
color-scheme: light;
--bg: #f4f7fb;
--card: #ffffff;
--line: #dbe3ee;
--text: #0f172a;
--muted: #475569;
--accent: #0f766e;
--accent-2: #1d4ed8;
}}
* {{ box-sizing: border-box; }}
body {{
margin: 0;
font: 14px/1.45 "Segoe UI", "Noto Sans", sans-serif;
color: var(--text);
background:
radial-gradient(circle at top left, rgba(29,78,216,.08), transparent 28%),
radial-gradient(circle at top right, rgba(15,118,110,.10), transparent 24%),
var(--bg);
}}
.wrap {{ max-width: 1180px; margin: 0 auto; padding: 24px; }}
.hero {{
background: linear-gradient(135deg, #0f172a, #1e293b 58%, #0f766e);
color: #fff;
border-radius: 18px;
padding: 20px 22px;
box-shadow: 0 22px 60px rgba(15,23,42,.22);
}}
.hero h1 {{ margin: 0 0 8px; font-size: 28px; }}
.meta {{ color: rgba(255,255,255,.84); }}
.actions {{ margin-top: 14px; display: flex; gap: 10px; flex-wrap: wrap; }}
.actions a {{
text-decoration: none;
color: #fff;
background: rgba(255,255,255,.12);
border: 1px solid rgba(255,255,255,.18);
padding: 8px 12px;
border-radius: 999px;
}}
.card {{
margin-top: 18px;
background: var(--card);
border: 1px solid var(--line);
border-radius: 16px;
overflow: hidden;
box-shadow: 0 16px 40px rgba(15,23,42,.08);
}}
table {{ width: 100%; border-collapse: collapse; }}
th, td {{ padding: 12px 14px; border-bottom: 1px solid var(--line); text-align: left; }}
th {{ background: #eef4fb; color: var(--muted); font-weight: 600; position: sticky; top: 0; }}
tr:nth-child(even) td {{ background: rgba(148,163,184,.06); }}
.num {{ font-variant-numeric: tabular-nums; }}
.good {{ color: var(--accent); font-weight: 700; }}
.muted {{ color: var(--muted); }}
@media (max-width: 900px) {{
.wrap {{ padding: 14px; }}
.hero h1 {{ font-size: 22px; }}
.card {{ overflow-x: auto; }}
table {{ min-width: 820px; }}
}}
</style>
</head>
<body>
<div class="wrap">
<section class="hero">
<h1>RDP Worktime Report</h1>
<div class="meta">Date: {date_local} · Timezone: {REPORT_TZ} · Generated UTC: {generated}</div>
<div class="actions">
<a href="/reports/worktime/today?format=csv">Download CSV</a>
<a href="/reports/worktime/today">View JSON</a>
</div>
</section>
<section class="card">
<table>
<thead>
<tr>
<th>User</th>
<th>Active</th>
<th>Active sec</th>
<th>First activity</th>
<th>Last activity</th>
<th>Idle sec</th>
<th>Samples</th>
</tr>
</thead>
<tbody>
{''.join(trs)}
</tbody>
</table>
</section>
</div>
</body>
</html>"""
class H(BaseHTTPRequestHandler):
def do_GET(self):
if not self.path.startswith("/reports/worktime/today"):
self.send_response(404)
self.end_headers()
return
fmt = "json"
if "format=csv" in self.path:
fmt = "csv"
elif "format=html" in self.path:
fmt = "html"
rows = report_today()
if fmt == "csv":
out = io.StringIO()
writer = csv.DictWriter(
out,
fieldnames=[
"user",
"active_seconds",
"active_hhmm",
"first_activity",
"last_activity",
"idle_seconds",
"sessions_count",
],
)
writer.writeheader()
writer.writerows(rows)
data = out.getvalue().encode()
self.send_response(200)
self.send_header("Content-Type", "text/csv; charset=utf-8")
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
return
if fmt == "html":
data = render_html(rows).encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
return
obj = {
"generated_at_utc": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
"report_timezone": str(REPORT_TZ),
"rows": rows,
}
data = json.dumps(obj, ensure_ascii=False, indent=2).encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
HTTPServer(("0.0.0.0", 5610), H).serve_forever()
-16
View File
@@ -1,16 +0,0 @@
[Unit]
Description=AW Worktime Report API
After=network.target activitywatch-server.service
Wants=activitywatch-server.service
[Service]
Type=simple
EnvironmentFile=/etc/activitywatch/aw-server.env
ExecStart=/usr/bin/python3 /usr/local/bin/aw-worktime-api.py
Restart=always
RestartSec=2
User=activitywatch
Group=activitywatch
[Install]
WantedBy=multi-user.target
-56
View File
@@ -1,56 +0,0 @@
(function () {
var reportBase = "__AW_WORKTIME_REPORT_BASE__";
var reportUrl = reportBase + "/reports/worktime/today?format=html";
var existing = document.getElementById("aw-report-links");
if (!existing) return;
existing.innerHTML =
'RDP report: ' +
'<a href="' + reportUrl + '" style="color:#fcd34d" target="_blank">HTML</a> | ' +
'<a href="' + reportBase + '/reports/worktime/today?format=csv" style="color:#7dd3fc" target="_blank">CSV</a> | ' +
'<a href="' + reportBase + '/reports/worktime/today" style="color:#86efac" target="_blank">JSON</a> | ' +
'<a href="#" id="aw-report-toggle" style="color:#f9fafb">Panel</a>';
var panel = document.createElement("div");
panel.id = "aw-report-panel";
panel.style.cssText = [
"position:fixed",
"top:16px",
"right:16px",
"width:min(980px,calc(100vw - 32px))",
"height:min(760px,calc(100vh - 32px))",
"background:#fff",
"border:1px solid rgba(15,23,42,.15)",
"border-radius:12px",
"box-shadow:0 24px 80px rgba(15,23,42,.28)",
"overflow:hidden",
"z-index:100000",
"display:none"
].join(";");
panel.innerHTML =
'<div style="display:flex;align-items:center;justify-content:space-between;padding:10px 14px;background:#0f172a;color:#fff;font:600 13px/1.2 sans-serif">' +
'<div>RDP Worktime Report</div>' +
'<div style="display:flex;gap:12px;align-items:center">' +
'<a href="' + reportUrl + '" target="_blank" style="color:#93c5fd;text-decoration:none">Open</a>' +
'<a href="#" id="aw-report-close" style="color:#fff;text-decoration:none">Close</a>' +
"</div></div>" +
'<iframe src="' + reportUrl + '" title="RDP Worktime Report" style="border:0;width:100%;height:calc(100% - 42px);background:#fff"></iframe>';
document.body.appendChild(panel);
function openPanel(ev) {
if (ev) ev.preventDefault();
panel.style.display = "block";
}
function closePanel(ev) {
if (ev) ev.preventDefault();
panel.style.display = "none";
}
var toggle = document.getElementById("aw-report-toggle");
if (toggle) toggle.addEventListener("click", openPanel);
var close = panel.querySelector("#aw-report-close");
if (close) close.addEventListener("click", closePanel);
})();
-172
View File
@@ -1,172 +0,0 @@
#!/usr/bin/env python3
import json
import os
import urllib.error
import urllib.request
from datetime import datetime, timedelta, timezone
AW_URL = os.environ.get("AW_SERVER_URL", "http://127.0.0.1:5600")
HOST = os.environ.get("AW_WORKTIME_HOST", "SHARKON2025")
STATE_PATH = os.environ.get(
"AW_WORKTIME_UI_BRIDGE_STATE",
"/var/lib/activitywatch/aw-worktime-ui-bridge-state.json",
)
TIMEOUT = float(os.environ.get("AW_WORKTIME_UI_BRIDGE_TIMEOUT", "20"))
SESSIONS_BUCKET = f"aw-worktime-sessions_{HOST}"
AFK_BUCKET = f"aw-watcher-afk_{HOST}"
WINDOW_BUCKET = f"aw-watcher-window_{HOST}"
def _req(method: str, path: str, payload=None):
data = None
headers = {}
if payload is not None:
data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
headers["Content-Type"] = "application/json"
req = urllib.request.Request(AW_URL + path, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=TIMEOUT) as r:
raw = r.read()
if not raw:
return None
return json.loads(raw.decode("utf-8"))
def ensure_bucket(bucket_id: str, event_type: str, client: str):
payload = {"client": client, "type": event_type, "hostname": HOST}
try:
_req("POST", f"/api/0/buckets/{bucket_id}", payload)
except urllib.error.HTTPError as e:
if e.code != 304:
raise
def load_state():
try:
with open(STATE_PATH, "r", encoding="utf-8") as f:
data = json.load(f)
if isinstance(data, dict) and "last_ts" in data:
return data
except FileNotFoundError:
pass
except json.JSONDecodeError:
pass
return {"last_ts": "1970-01-01T00:00:00Z"}
def save_state(state):
os.makedirs(os.path.dirname(STATE_PATH), exist_ok=True)
tmp = STATE_PATH + ".tmp"
with open(tmp, "w", encoding="utf-8") as f:
json.dump(state, f, ensure_ascii=False)
os.replace(tmp, STATE_PATH)
def to_iso_utc(ts):
if ts.endswith("Z"):
return ts
return ts.replace("+00:00", "Z")
def parse_iso_utc(ts: str):
if ts.endswith("Z"):
ts = ts[:-1] + "+00:00"
return datetime.fromisoformat(ts).astimezone(timezone.utc)
def build_window_title(users, active_count):
if not users:
return "RDP idle"
return f"RDP active ({active_count}): " + ", ".join(users)
def _is_session_active(row_data):
if isinstance(row_data.get("active"), bool):
return row_data.get("active")
state = str(row_data.get("state", "")).strip().lower()
return state in {"active", "активно"}
def transform(events):
out_afk = []
out_win = []
last_ts = None
grouped = {}
for e in events:
ts = e.get("timestamp")
if not ts:
continue
grouped.setdefault(ts, []).append(e)
for ts in sorted(grouped.keys()):
rows = grouped[ts]
duration = max(float(r.get("duration", 0.0)) for r in rows)
active_users = []
for r in rows:
data = r.get("data") or {}
user = str(data.get("username", "")).strip()
if user and _is_session_active(data):
active_users.append(user)
active_users = sorted(set(active_users))
active_count = len(active_users)
is_active = active_count > 0
afk_data = {"status": "not-afk" if is_active else "afk", "source": "aw-worktime-ui-bridge"}
out_afk.append({"timestamp": ts, "duration": duration, "data": afk_data})
win_data = {
"app": "RDP",
"title": build_window_title(active_users, active_count),
"source": "aw-worktime-ui-bridge",
}
out_win.append({"timestamp": ts, "duration": duration, "data": win_data})
last_ts = ts
return out_afk, out_win, last_ts
def main():
state = load_state()
last_ts = state.get("last_ts", "1970-01-01T00:00:00Z")
ensure_bucket(AFK_BUCKET, "afkstatus", "aw-worktime-ui-bridge")
ensure_bucket(WINDOW_BUCKET, "currentwindow", "aw-worktime-ui-bridge")
now_utc = datetime.now(timezone.utc)
recent = _req("GET", f"/api/0/buckets/{SESSIONS_BUCKET}/events?limit=5000") or []
if not recent:
return
try:
last_dt = parse_iso_utc(last_ts)
except Exception:
last_dt = datetime(1970, 1, 1, tzinfo=timezone.utc)
events = []
for e in recent:
ts = e.get("timestamp")
if not ts:
continue
try:
if parse_iso_utc(ts) > last_dt:
events.append(e)
except Exception:
continue
if not events:
return
afk_events, win_events, new_last_ts = transform(events)
if not afk_events or not win_events or not new_last_ts:
return
_req("POST", f"/api/0/buckets/{AFK_BUCKET}/events", afk_events)
_req("POST", f"/api/0/buckets/{WINDOW_BUCKET}/events", win_events)
save_state({"last_ts": new_last_ts})
print(f"posted_afk={len(afk_events)} posted_win={len(win_events)} last_ts={new_last_ts}")
if __name__ == "__main__":
main()
-15
View File
@@ -1,15 +0,0 @@
[Unit]
Description=AW Worktime UI bridge (sessions -> afk/window)
After=network-online.target activitywatch-server.service
Wants=network-online.target
[Service]
Type=oneshot
Environment=AW_SERVER_URL=http://127.0.0.1:5600
Environment=AW_WORKTIME_HOST=SHARKON2025
ExecStart=/usr/bin/python3 /usr/local/bin/aw-worktime-ui-bridge.py
User=root
Group=root
[Install]
WantedBy=multi-user.target
-11
View File
@@ -1,11 +0,0 @@
[Unit]
Description=Run AW Worktime UI bridge every 30 seconds
[Timer]
OnBootSec=20s
OnUnitActiveSec=30s
Unit=aw-worktime-ui-bridge.service
Persistent=true
[Install]
WantedBy=timers.target
-35
View File
@@ -24,11 +24,6 @@ required_vars=(
BOOTSTRAP_DIR="/root/bootstrap"
VIEWS_JSON="$BOOTSTRAP_DIR/settings/views-default.json"
CLASSES_JSON="$BOOTSTRAP_DIR/settings/classes-worktime.json"
WORKTIME_API_SRC="$BOOTSTRAP_DIR/aw-worktime-api.py"
WORKTIME_API_SERVICE_SRC="$BOOTSTRAP_DIR/aw-worktime-api.service"
WORKTIME_UI_BRIDGE_SRC="$BOOTSTRAP_DIR/aw-worktime-ui-bridge.py"
WORKTIME_UI_BRIDGE_SERVICE_SRC="$BOOTSTRAP_DIR/aw-worktime-ui-bridge.service"
WORKTIME_UI_BRIDGE_TIMER_SRC="$BOOTSTRAP_DIR/aw-worktime-ui-bridge.timer"
for var_name in "${required_vars[@]}"; do
if [[ -z "${!var_name:-}" ]]; then
@@ -94,36 +89,6 @@ systemctl enable activitywatch-server.service
systemctl restart activitywatch-server.service
systemctl --no-pager --full status activitywatch-server.service || true
if [[ -f "$WORKTIME_API_SRC" ]]; then
install -m 0755 "$WORKTIME_API_SRC" /usr/local/bin/aw-worktime-api.py
fi
if [[ -f "$WORKTIME_API_SERVICE_SRC" ]]; then
install -m 0644 "$WORKTIME_API_SERVICE_SRC" /etc/systemd/system/aw-worktime-api.service
systemctl daemon-reload
systemctl enable aw-worktime-api.service
systemctl restart aw-worktime-api.service
systemctl --no-pager --full status aw-worktime-api.service || true
fi
if [[ -f "$WORKTIME_UI_BRIDGE_SRC" ]]; then
install -m 0755 "$WORKTIME_UI_BRIDGE_SRC" /usr/local/bin/aw-worktime-ui-bridge.py
fi
if [[ -f "$WORKTIME_UI_BRIDGE_SERVICE_SRC" ]]; then
install -m 0644 "$WORKTIME_UI_BRIDGE_SERVICE_SRC" /etc/systemd/system/aw-worktime-ui-bridge.service
fi
if [[ -f "$WORKTIME_UI_BRIDGE_TIMER_SRC" ]]; then
install -m 0644 "$WORKTIME_UI_BRIDGE_TIMER_SRC" /etc/systemd/system/aw-worktime-ui-bridge.timer
systemctl daemon-reload
systemctl disable --now aw-worktime-afk-bridge.timer >/dev/null 2>&1 || true
systemctl enable aw-worktime-ui-bridge.timer
systemctl restart aw-worktime-ui-bridge.timer
systemctl start aw-worktime-ui-bridge.service || true
systemctl --no-pager --full status aw-worktime-ui-bridge.timer || true
fi
for _ in $(seq 1 20); do
if curl -fsS "http://127.0.0.1:${AW_SERVER_PORT}/api/0/info" >/dev/null 2>&1; then
break
+4 -4
View File
@@ -20,7 +20,7 @@
"name": ["Работа", "Документы"],
"rule": {
"type": "regex",
"regex": "\\b(winword|excel|powerpnt|outlook|acrord32|acrord64|libreoffice|writer|calc)\\.exe\\b|LibreOffice|OnlyOffice|Adobe Reader|Acrobat",
"regex": "\\b(winword|excel|powerpnt|outlook|acrord32|acrord64)\\.exe\\b|Adobe Reader|Acrobat",
"ignore_case": true
},
"data": { "color": "#2E7D32" }
@@ -40,7 +40,7 @@
"name": ["Работа", "Администрирование"],
"rule": {
"type": "regex",
"regex": "\\b(mstsc|putty|kitty|winscp|anydesk|teamviewer|vncviewer|mmc|regedit|services|control|powershell|cmd|gnome-terminal|gnome-terminal-server|xfce4-terminal|konsole|tilix|alacritty|xterm|remmina|virt-manager)\\.exe\\b|\\b(gnome-terminal|gnome-terminal-server|xfce4-terminal|konsole|tilix|alacritty|xterm|remmina|virt-manager)\\b|Proxmox Virtual Environment|\\bpfSense\\b|\\bGrafana\\b|\\bKibana\\b|\\bPortainer\\b",
"regex": "\\b(mstsc|putty|kitty|winscp|anydesk|teamviewer|vncviewer|mmc|regedit|services|control|powershell|cmd)\\.exe\\b",
"ignore_case": true
},
"data": { "color": "#6D4C41" }
@@ -56,7 +56,7 @@
"name": ["Интернет", "Браузер"],
"rule": {
"type": "regex",
"regex": "\\b(chrome|msedge|firefox|opera|brave|vivaldi|browser|chromium)\\.exe\\b|\\b(chrome|chromium|firefox|opera|brave|vivaldi)\\b",
"regex": "\\b(chrome|msedge|firefox|opera|brave|vivaldi|browser)\\.exe\\b",
"ignore_case": true
},
"data": { "color": "#00897B" }
@@ -82,7 +82,7 @@
"name": ["ActivityWatch"],
"rule": {
"type": "regex",
"regex": "ActivityWatch|\\baw-(watcher|qt)\\.exe\\b|\\baw-(watcher|qt)\\b",
"regex": "ActivityWatch|\\baw-(watcher|qt)\\.exe\\b",
"ignore_case": true
},
"data": {}
-95
View File
@@ -1,95 +0,0 @@
#!/bin/bash
# check-aw-data.sh - Проверка сбора данных ActivityWatch с RDP-сервера SHARKON2025
# Сервер: 10.10.10.13:5600
# Хост-источник: 192.168.100.21 (SHARKON2025)
SERVER="http://10.10.10.13:5600"
HOSTNAME_FILTER="SHARKON2025"
NOW=$(date -u +%s)
# Цвета
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Проверка доступности сервера
echo "=== ActivityWatch Data Check: $HOSTNAME_FILTER ==="
echo ""
echo -n "Server connectivity... "
RESP=$(no_proxy=10.10.10.13 curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/info" 2>&1)
if [ $? -eq 0 ] && echo "$RESP" | jq -e '.version' > /dev/null 2>&1; then
VERSION=$(echo "$RESP" | jq -r '.version')
echo -e "${GREEN}OK${NC} (aw-server v$VERSION)"
else
echo -e "${RED}FAILED${NC} (cannot reach $SERVER)"
exit 1
fi
echo ""
# Проверка бакетов
echo "--- Buckets ---"
printf "%-45s %-8s %-22s %s\n" "BUCKET" "EVENTS" "LAST EVENT" "STATUS"
printf "%-45s %-8s %-22s %s\n" "---------------------------------------------" "--------" "----------------------" "------"
BUCKETS=(
"aw-dlp-endpoint-signals"
"aw-dlp-incidents"
"aw-dlp-review"
"aw-dlp-rules"
"aw-watcher-afk"
"aw-watcher-window"
"aw-session-events"
"aw-worktime-sessions"
)
for bucket in "${BUCKETS[@]}"; do
bucket_full="${bucket}_${HOSTNAME_FILTER}"
# Получаем последний event
EVENT_DATA=$(no_proxy=10.10.10.13 curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/buckets/$bucket_full/events?limit=1" 2>&1)
LAST_ID=$(echo "$EVENT_DATA" | jq '.[0].id // 0')
LAST_TS=$(echo "$EVENT_DATA" | jq -r '.[0].timestamp // "no events"')
# Вычисляем возраст
if [ "$LAST_TS" != "no events" ] && [ -n "$LAST_TS" ]; then
EVENT_EPOCH=$(date -d "$LAST_TS" +%s 2>/dev/null || echo 0)
if [ "$EVENT_EPOCH" -gt 0 ]; then
AGE_SEC=$((NOW - EVENT_EPOCH))
if [ $AGE_SEC -lt 3600 ]; then
AGE="$((AGE_SEC / 60))m ago"
STATUS="${GREEN}FRESH${NC}"
elif [ $AGE_SEC -lt 86400 ]; then
AGE="$((AGE_SEC / 3600))h ago"
STATUS="${YELLOW}STALE${NC}"
else
AGE="$((AGE_SEC / 86400))d ago"
STATUS="${RED}DEAD${NC}"
fi
else
AGE="unknown"
STATUS="${RED}?${NC}"
fi
else
AGE="none"
LAST_ID="0"
STATUS="${RED}EMPTY${NC}"
fi
printf "%-45s %-8s %-22s %b\n" "$bucket_full" "$LAST_ID" "$LAST_TS ($AGE)" "$STATUS"
done
echo ""
# Проверка CORS
echo "--- CORS Check ---"
CORS_RESP=$(no_proxy=10.10.10.13 curl -s --connect-timeout 10 --max-time 15 -o /dev/null -w '%{http_code}' -H "Origin: http://10.10.10.13:5600" "$SERVER/api/0/settings/" 2>&1)
if [ "$CORS_RESP" = "200" ]; then
echo -e "${GREEN}CORS: OK${NC} (HTTP 200)"
else
echo -e "${RED}CORS: FAIL${NC} (HTTP $CORS_RESP)"
fi
echo ""
echo "=== Check Complete ==="
echo "Timestamp: $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
-156
View File
@@ -1,156 +0,0 @@
#!/bin/bash
# check-aw-full.sh - Полная проверка ActivityWatch: сервер + RDP-хост
# Сервер: 10.10.10.13:5600
# RDP-хост: 192.168.100.21 (SHARKON2025)
SERVER="http://10.10.10.13:5600"
HOSTNAME_FILTER="SHARKON2025"
RDP_HOST="192.168.100.21"
NOW=$(date -u +%s)
# Цвета
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
echo -e "${CYAN}=== ActivityWatch Full Check: $HOSTNAME_FILTER ===${NC}"
echo ""
# 1. Проверка сервера
echo -e "${CYAN}--- 1. AW Server ($SERVER) ---${NC}"
echo -n " Connectivity... "
RESP=$(no_proxy=10.10.10.13 curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/info" 2>&1)
if [ $? -eq 0 ] && echo "$RESP" | jq -e '.version' > /dev/null 2>&1; then
VERSION=$(echo "$RESP" | jq -r '.version')
echo -e " ${GREEN}OK${NC} (aw-server $VERSION)"
else
echo -e " ${RED}FAILED${NC}"
exit 1
fi
echo -n " CORS... "
CORS_RESP=$(no_proxy=10.10.10.13 curl -s --connect-timeout 10 --max-time 15 -o /dev/null -w '%{http_code}' -H "Origin: http://10.10.10.13:5600" "$SERVER/api/0/settings/" 2>&1)
if [ "$CORS_RESP" = "200" ]; then
echo -e "${GREEN}OK${NC}"
else
echo -e "${RED}FAIL${NC} (HTTP $CORS_RESP)"
fi
echo ""
# 2. Проверка бакетов
echo -e "${CYAN}--- 2. Data Buckets ---${NC}"
printf " %-42s %-8s %-20s %s\n" "BUCKET" "EVENTS" "LAST EVENT" "STATUS"
printf " %-42s %-8s %-20s %s\n" "------------------------------------------" "--------" "--------------------" "------"
BUCKETS=(
"aw-watcher-afk|AFK watcher"
"aw-watcher-window|Window watcher"
"aw-worktime-sessions|Worktime sessions"
"aw-session-events|Session events"
"aw-dlp-endpoint-signals|DLP signals"
"aw-dlp-incidents|DLP incidents"
"aw-dlp-review|DLP review"
"aw-dlp-rules|DLP rules"
)
for entry in "${BUCKETS[@]}"; do
bucket="${entry%%|*}"
label="${entry##*|}"
bucket_full="${bucket}_${HOSTNAME_FILTER}"
EVENT_DATA=$(no_proxy=10.10.10.13 curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/buckets/$bucket_full/events?limit=1" 2>&1)
LAST_ID=$(echo "$EVENT_DATA" | jq '.[0].id // 0')
LAST_TS=$(echo "$EVENT_DATA" | jq -r '.[0].timestamp // "no events"')
if [ "$LAST_TS" != "no events" ] && [ -n "$LAST_TS" ]; then
EVENT_EPOCH=$(date -d "$LAST_TS" +%s 2>/dev/null || echo 0)
if [ "$EVENT_EPOCH" -gt 0 ]; then
AGE_SEC=$((NOW - EVENT_EPOCH))
if [ $AGE_SEC -lt 3600 ]; then
AGE="$((AGE_SEC / 60))m"
STATUS="${GREEN}FRESH${NC}"
elif [ $AGE_SEC -lt 86400 ]; then
AGE="$((AGE_SEC / 3600))h"
STATUS="${YELLOW}STALE${NC}"
else
AGE="$((AGE_SEC / 86400))d"
STATUS="${RED}DEAD${NC}"
fi
else
AGE="?"
STATUS="${RED}?${NC}"
fi
else
AGE="none"
LAST_ID="0"
STATUS="${RED}EMPTY${NC}"
fi
printf " %-42s %-8s %-20s %b\n" "$label" "$LAST_ID" "$AGE" "$STATUS"
done
echo ""
# 3. Проверка RDP-хоста
echo -e "${CYAN}--- 3. RDP Host ($RDP_HOST) ---${NC}"
# Проверка WinRM
echo -n " WinRM (5985)... "
if timeout 5 bash -c "echo > /dev/tcp/$RDP_HOST/5985" 2>&1; then
echo -e "${GREEN}OK${NC}"
else
echo -e "${RED}UNREACHABLE${NC}"
fi
# Проверка SSH (для справки)
echo -n " SSH (22)... "
if timeout 5 bash -c "echo > /dev/tcp/$RDP_HOST/22" 2>&1; then
echo -e "${GREEN}OK${NC}"
else
echo -e "${YELLOW}CLOSED${NC} (normal for Windows)"
fi
echo ""
# 4. Сводка
echo -e "${CYAN}--- 4. Summary ---${NC}"
FRESH_COUNT=0
STALE_COUNT=0
DEAD_COUNT=0
for entry in "${BUCKETS[@]}"; do
bucket="${entry%%|*}"
bucket_full="${bucket}_${HOSTNAME_FILTER}"
EVENT_DATA=$(no_proxy=10.10.10.13 curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/buckets/$bucket_full/events?limit=1" 2>&1)
LAST_TS=$(echo "$EVENT_DATA" | jq -r '.[0].timestamp // "no events"')
if [ "$LAST_TS" != "no events" ] && [ -n "$LAST_TS" ]; then
EVENT_EPOCH=$(date -d "$LAST_TS" +%s 2>/dev/null || echo 0)
if [ "$EVENT_EPOCH" -gt 0 ]; then
AGE_SEC=$((NOW - EVENT_EPOCH))
if [ $AGE_SEC -lt 3600 ]; then
FRESH_COUNT=$((FRESH_COUNT + 1))
elif [ $AGE_SEC -lt 86400 ]; then
STALE_COUNT=$((STALE_COUNT + 1))
else
DEAD_COUNT=$((DEAD_COUNT + 1))
fi
fi
else
DEAD_COUNT=$((DEAD_COUNT + 1))
fi
done
echo -e " FRESH: ${GREEN}$FRESH_COUNT${NC}"
echo -e " STALE: ${YELLOW}$STALE_COUNT${NC}"
echo -e " DEAD: ${RED}$DEAD_COUNT${NC}"
if [ $DEAD_COUNT -gt 0 ] || [ $STALE_COUNT -gt 0 ]; then
echo ""
echo -e " ${RED}WARNING:${NC} Some collectors may need restart on RDP host"
echo -e " Run: ${CYAN}ansible -i ansible/inventory.ini rdp-prod -m win_shell -a 'schtasks /Run /TN \"ActivityWatch Recovery\"'${NC}"
fi
echo ""
echo -e "${CYAN}=== Check Complete ===${NC}"
echo " Timestamp: $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
+19 -32
View File
@@ -21,7 +21,7 @@
- `/home/igor/tmp/AWatch-rus/ansible/deploy_aw_server.yml`
- `/home/igor/tmp/AWatch-rus/ansible/provision_proxmox_ct_and_deploy_aw.yml`
- `/home/igor/tmp/AWatch-rus/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml`
- `/home/igor/tmp/AWatch-rus/ansible/deploy_aw_windows.yml`
- `/home/igor/tmp/AWatch-rus/ansible/deploy_aw_windows_phase2.yml`
---
@@ -177,7 +177,7 @@ grep -n 'aw-ru-patch\|aw-sw-cleanup' /opt/activitywatch/webui-ru/index.html
например в:
- `C:\Program Files\AWatch-rus\windows`
- `C:\Deploy\ActivityWatch-Russian\windows`
Откройте **elevated PowerShell**:
@@ -187,28 +187,15 @@ 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
C:\Program Files\AWatch-rus\windows\deploy-domain-users.ps1 `
C:\Deploy\ActivityWatch-Russian\windows\deploy-domain-users.ps1 `
-ServerHost aw.example.local `
-ServerPort 5600 `
-Domain CONTOSO `
-UserListPath C:\Deploy\aw-users.txt `
-CustomRulesPath C:\Program Files\AWatch-rus\windows\web-category-rules.example.json
-CustomRulesPath C:\Deploy\ActivityWatch-Russian\windows\web-category-rules.example.json
```
Поддерживаемые варианты:
@@ -220,7 +207,7 @@ C:\Program Files\AWatch-rus\windows\deploy-domain-users.ps1 `
### 3.2.1 Ensemble orchestration (рекомендуется для production)
```powershell
C:\Program Files\AWatch-rus\windows\deploy-ensemble.ps1 `
C:\Deploy\ActivityWatch-Russian\windows\deploy-ensemble.ps1 `
-ServerHost aw.example.local `
-ServerPort 5600 `
-Domain CONTOSO `
@@ -230,30 +217,30 @@ C:\Program Files\AWatch-rus\windows\deploy-ensemble.ps1 `
Отчёт сохраняется в:
- `C:\ProgramData\AWatch-rus\ensemble-report-YYYYMMDD-HHMMSS.json`
- `C:\ProgramData\ActivityWatch\ensemble-report-YYYYMMDD-HHMMSS.json`
### 3.3 Single-user развёртывание
```powershell
C:\Program Files\AWatch-rus\windows\deploy-single-user.ps1 `
C:\Deploy\ActivityWatch-Russian\windows\deploy-single-user.ps1 `
-ServerHost aw.example.local `
-ServerPort 5600 `
-TargetUser 'CONTOSO\user01' `
-CustomRulesPath C:\Program Files\AWatch-rus\windows\web-category-rules.example.json
-CustomRulesPath C:\Deploy\ActivityWatch-Russian\windows\web-category-rules.example.json
```
### 3.4 Recovery / hardening
```powershell
C:\Program Files\AWatch-rus\windows\hardening-recovery.ps1 `
-ConfigPath C:\ProgramData\AWatch-rus\deployment-config.json
C:\Deploy\ActivityWatch-Russian\windows\hardening-recovery.ps1 `
-ConfigPath C:\ProgramData\ActivityWatch\deployment-config.json
```
### 3.5 Валидация deployment-а (PowerShell report)
```powershell
$report = C:\Program Files\AWatch-rus\windows\validate-deployment.ps1 `
-ConfigPath C:\ProgramData\AWatch-rus\deployment-config.json
$report = C:\Deploy\ActivityWatch-Russian\windows\validate-deployment.ps1 `
-ConfigPath C:\ProgramData\ActivityWatch\deployment-config.json
$report | ConvertTo-Json -Depth 12
```
@@ -261,13 +248,13 @@ $report | ConvertTo-Json -Depth 12
## 4) Что должно появиться на Windows после установки
- `C:\Program Files\AWatch-rus\bin`
- `C:\ProgramData\AWatch-rus\deployment-config.json`
- `C:\ProgramData\AWatch-rus\launch-watchers.ps1`
- `C:\ProgramData\AWatch-rus\recovery-loop.ps1`
- `C:\ProgramData\AWatch-rus\browser-domains-native-collector.ps1`
- `C:\ProgramData\AWatch-rus\web-category-rules.json`
- `C:\ProgramData\AWatch-rus\logs\`
- `C:\Program Files\ActivityWatch`
- `C:\ProgramData\ActivityWatch\deployment-config.json`
- `C:\ProgramData\ActivityWatch\launch-watchers.ps1`
- `C:\ProgramData\ActivityWatch\recovery-loop.ps1`
- `C:\ProgramData\ActivityWatch\browser-domains-native-collector.ps1`
- `C:\ProgramData\ActivityWatch\web-category-rules.json`
- `C:\ProgramData\ActivityWatch\logs\`
Задачи планировщика:
-303
View File
@@ -1,303 +0,0 @@
# ActivityWatch-Russian Architecture Diagram
## High-Level Architecture
```mermaid
graph TB
subgraph "Windows Clients"
A[ActivityWatch Watchers]
B[DLP Endpoint Collector]
C[Browser Domains Collector]
D[Email Outbound Collector]
E[Worktime Session Collector]
end
subgraph "Linux Server"
F[ActivityWatch Server]
G[PostgreSQL Database]
H[Aggregation Scripts]
end
subgraph "Monitoring & Integration"
I[pfSense Firewall]
J[Prometheus Exporter]
end
subgraph "Visualization"
K[Grafana]
L[Prometheus]
M[ActivityWatch WebUI]
end
A -->|HTTP API| F
B -->|Events| F
C -->|Events| F
D -->|Events| F
E -->|Events| F
F -->|Store| G
F -->|API| M
I -->|Logs| F
H -->|Read/Write| G
H -->|Read| F
J -->|Metrics| L
L -->|Scrape| J
F -->|API| J
K -->|Query| L
K -->|Query| G
```
## Component Interactions
### Data Flow
```
Windows Collectors → ActivityWatch Server → PostgreSQL Database
Aggregation Scripts
Grafana Dashboards
```
### DLP Monitoring Flow
```
User Activity (clipboard/print/USB)
DLP Endpoint Collector (PowerShell)
DLP Policy Evaluation
ActivityWatch Events (via HTTP API)
PostgreSQL Database
Aggregation Scripts (Python)
Grafana DLP Dashboard
```
### Browser Monitoring Flow
```
User Browser Activity
Browser Domains Collector (PowerShell)
Domain Extraction & Categorization
DLP Rule Check
ActivityWatch Events
WebUI Domain Dashboard
```
### Email Monitoring Flow
```
Outlook / SMTP Activity
Email Outbound Collector (PowerShell)
Email Policy Evaluation
ActivityWatch Events
Grafana Email Dashboard
```
### pfSense Integration Flow
```
pfSense Firewall Logs
pfSense Poller (Python)
HTTP API Query
ActivityWatch Events
Network Activity Dashboard
```
### Metrics Flow
```
ActivityWatch Server
Prometheus Exporter (Python)
HTTP Metrics Endpoint (port 9398)
Prometheus Scraping
Grafana Dashboards
```
## Component Details
### Windows Collectors
| Component | Language | Purpose | Output |
|-----------|----------|---------|--------|
| DLP Endpoint Collector | PowerShell | Monitor clipboard, print, USB | ActivityWatch events |
| Browser Domains Collector | PowerShell | Track visited domains | ActivityWatch events |
| Email Outbound Collector | PowerShell | Monitor sent emails | ActivityWatch events |
| Worktime Session Collector | PowerShell | Track work sessions | ActivityWatch events |
### Server Components
| Component | Language | Purpose | Dependencies |
|-----------|----------|---------|--------------|
| ActivityWatch Server | Rust | Core monitoring platform | SQLite/PostgreSQL |
| Aggregation Scripts | Python | Process DLP events | psycopg2, requests |
| Prometheus Exporter | Python | Export metrics to Prometheus | prometheus_client |
### Integration Points
| Integration | Protocol | Purpose |
|-------------|----------|---------|
| pfSense → AW | HTTP API | Firewall log collection |
| AW → Grafana | PostgreSQL | Direct database access |
| AW → Prometheus | HTTP /metrics | Metrics scraping |
| Collectors → AW | HTTP /api/buckets | Event submission |
## Deployment Architecture
```
Domain Controller
↓ (GPO / Scheduled Tasks)
Windows Workstations (user1, user2, ...)
↓ (PowerShell Collectors)
ActivityWatch Server (Linux)
PostgreSQL Database
Grafana + Prometheus Stack
```
## File System Structure
```
ActivityWatch-Russian/
├── windows/ # Windows collectors
│ ├── dlp-endpoint-signals-collector.ps1
│ ├── browser-domains-native-collector.ps1
│ ├── email-outbound-collector.ps1
│ ├── worktime-session-collector.ps1
│ ├── deploy-domain-users.ps1 # Deployment script
│ └── ActivityWatch.Windows.Common.psm1
├── scripts/ # Server scripts
│ ├── aggregate_dlp_events.py
│ └── merge_aw_server_dbs.py
├── aw-server/ # WebUI patches
│ ├── aw-ru-patch.js
│ └── aw-sw-cleanup.js
├── pfsense/ # pfSense integration
│ └── pfsense-aw-poller.py
├── grafana-1c/ # Monitoring stack
│ ├── docker-compose.yml
│ ├── prometheus/
│ ├── grafana/
│ └── sql-exporter/
│ └── collectors/
│ └── aw_activitywatch.py
└── ansible/ # Automation
├── playbooks/
└── group_vars/
```
## Network Ports
| Service | Port | Protocol | Purpose |
|---------|------|----------|---------|
| ActivityWatch Server | 5600 | HTTP | WebUI & API |
| ActivityWatch Server | 5666 | HTTP | WebSocket |
| Prometheus | 9090 | HTTP | Metrics UI |
| Prometheus Exporter | 9398 | HTTP | AW metrics endpoint |
| Grafana | 3000 | HTTP | Dashboards |
| PostgreSQL | 5432 | TCP | Database |
## Data Models
### ActivityWatch Event Structure
```json
{
"timestamp": "2024-01-01T12:00:00Z",
"duration": 60.0,
"data": {
"title": "Window Title",
"url": "https://example.com",
"app": "chrome.exe",
"type": "activity"
}
}
```
### DLP Incident Structure
```json
{
"timestamp": "2024-01-01T12:00:00Z",
"type": "clipboard",
"rule": "credit_card_pattern",
"severity": "high",
"user": "user1",
"host": "WORKSTATION01",
"screenshot": "path/to/screenshot.png"
}
```
## Key Dependencies
### Windows Dependencies
- PowerShell 5.1+
- .NET Framework 4.8
- ActivityWatch Windows binaries
### Linux Dependencies
- Python 3.8+
- PostgreSQL 12+
- Docker & Docker Compose
- Rust (for AW server compilation)
### Python Dependencies
```txt
psycopg2-binary
requests
prometheus_client
```
## Security Considerations
### Data Flow Security
- Collectors → Server: HTTP (can be upgraded to HTTPS)
- Server → Database: Local connection or SSL
- pfSense → Server: HTTP over VPN
- Prometheus → Exporter: HTTP internal network
### Access Control
- Windows collectors run as user context
- Server runs as dedicated service user
- Database access restricted to specific users
- Grafana authenticated via LDAP/Local users
## Monitoring & Alerting
### Metrics Collected
- Events per bucket
- Active hosts count
- Collector heartbeat status
- DLP incident rate
- Database query performance
### Alerting Rules
- Collector offline > 30 minutes
- High DLP incident rate
- Database connection failures
- Disk space < 20%
-278
View File
@@ -1,278 +0,0 @@
# ActivityWatch-Russian - Простая архитектура
## Общая схема (сверху вниз)
```
┌─────────────────────────────────────────────────────────────────┐
│ ПОЛЬЗОВАТЕЛИ │
│ (Windows рабочие станции) │
└──────────────────────────┬──────────────────────────────────────┘
│ Коллекторы (PowerShell)
┌──────────────────────────▼──────────────────────────────────────┐
│ Windows Collectors (на каждой машине) │
├──────────────────────────────────────────────────────────────────┤
│ • DLP Endpoint Collector - буфер, печать, USB │
│ • Browser Domains Collector - посещаемые сайты │
│ • Email Outbound Collector - исходящая почта │
│ • Worktime Session Collector - рабочее время │
└──────────────────────────┬──────────────────────────────────────┘
│ HTTP API (отправка событий)
┌──────────────────────────▼──────────────────────────────────────┐
│ ActivityWatch Server (Linux) │
├──────────────────────────────────────────────────────────────────┤
│ • Принимает события от коллекторов │
│ • Хранит в базе данных │
│ • Предоставляет WebUI и API │
└──────────────────────────┬──────────────────────────────────────┘
│ Хранение данных
┌──────────────────────────▼──────────────────────────────────────┐
│ PostgreSQL Database │
├──────────────────────────────────────────────────────────────────┤
│ • События активности │
│ • DLP инциденты │
│ • Метаданные хостов и пользователей │
└──────────────────────────┬──────────────────────────────────────┘
│ Чтение/Обработка
┌──────────────────────────▼──────────────────────────────────────┐
│ Обработка и Агрегация │
├──────────────────────────────────────────────────────────────────┤
│ • aggregate_dlp_events.py - агрегация DLP событий │
│ • pfSense Poller - логи firewall │
│ • Prometheus Exporter - метрики для мониторинга │
└──────────────────────────┬──────────────────────────────────────┘
│ Визуализация
┌──────────────────────────▼──────────────────────────────────────┐
│ Grafana + Prometheus Stack │
├──────────────────────────────────────────────────────────────────┤
│ • Prometheus - сбор метрик │
│ • Grafana - дашборды и визуализация │
│ • SQL Exporter - прямой доступ к PostgreSQL │
└──────────────────────────────────────────────────────────────────┘
```
## Потоки данных (по направлениям)
### Поток 1: DLP мониторинг
```
Пользователь → DLP Collector → Проверка правил → AW Server → PostgreSQL → Grafana
```
### Поток 2: Мониторинг браузеров
```
Браузер → Browser Collector → Категоризация → AW Server → WebUI Dashboard
```
### Поток 3: Мониторинг почты
```
Outlook/SMTP → Email Collector → Проверка правил → AW Server → Grafana
```
### Поток 4: pfFirewall логи
```
pfSense → Poller (Python) → HTTP API → AW Server → Grafana
```
### Поток 5: Метрики
```
AW Server → Exporter (Python) → /metrics → Prometheus → Grafana
```
## Компоненты по уровням
### Уровень 1: Сбор данных (Windows)
```
┌─────────────────────────────────────┐
│ Windows Collectors (PowerShell) │
│ ┌───────────────────────────────┐ │
│ │ • dlp-endpoint-signals-collector│ │
│ │ • browser-domains-collector │ │
│ │ • email-outbound-collector │ │
│ │ • worktime-session-collector │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
```
### Уровень 2: Хранение и обработка (Linux Server)
```
┌─────────────────────────────────────┐
│ ActivityWatch Server + PostgreSQL │
│ ┌───────────────────────────────┐ │
│ │ • aw-server (Rust) │ │
│ │ • PostgreSQL Database │ │
│ │ • WebUI (с RU патчами) │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
```
### Уровень 3: Интеграции и обработка
```
┌─────────────────────────────────────┐
│ Интеграции и Скрипты (Python) │
│ ┌───────────────────────────────┐ │
│ │ • aggregate_dlp_events.py │ │
│ │ • pfsense-aw-poller.py │ │
│ │ • aw_activitywatch_exporter │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
```
### Уровень 4: Визуализация и мониторинг
```
┌─────────────────────────────────────┐
│ Grafana + Prometheus (Docker) │
│ ┌───────────────────────────────┐ │
│ │ • Prometheus (порт 9090) │ │
│ │ • Grafana (порт 3000) │ │
│ │ • SQL Exporter (порт 9398) │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
```
## Развертывание
### На Windows рабочих станциях
```
1. Установка ActivityWatch (через InnoSetup installer)
2. Развертывание коллекторов (deploy-domain-users.ps1)
3. Настройка scheduled tasks
4. Конфигурация DLP политик
```
### На Linux сервере
```
1. Установка ActivityWatch Server
2. Настройка PostgreSQL
3. Применение RU патчей к WebUI
4. Запуск скриптов агрегации
5. Запуск Prometheus Exporter
```
### Мониторинг стек
```
1. Docker Compose развертывание
2. Настройка Prometheus scrape config
3. Импорт Grafana дашбордов
4. Настройка алертов
```
## Ключевые файлы
```
ActivityWatch-Russian/
├── windows/ # Windows коллекторы
│ ├── dlp-endpoint-signals-collector.ps1
│ ├── browser-domains-native-collector.ps1
│ ├── email-outbound-collector.ps1
│ └── deploy-domain-users.ps1
├── scripts/ # Серверные скрипты
│ └── aggregate_dlp_events.py
├── aw-server/ # Патчи WebUI
│ └── aw-ru-patch.js
├── pfsense/ # Интеграция pfSense
│ └── pfsense-aw-poller.py
└── grafana-1c/ # Мониторинг стек
├── docker-compose.yml
├── prometheus/prometheus.yml
└── grafana/dashboards/
```
## Порты
| Компонент | Порт | Протокол |
|-----------|------|----------|
| ActivityWatch WebUI | 5600 | HTTP |
| ActivityWatch API | 5666 | WebSocket |
| Prometheus | 9090 | HTTP |
| Prometheus Exporter | 9398 | HTTP |
| Grafana | 3000 | HTTP |
| PostgreSQL | 5432 | TCP |
## Быстрый старт
### Запуск мониторинг стека
```bash
cd grafana-1c
docker-compose up -d
```
### Развертывание на Windows
```powersShell
.\windows\deploy-domain-users.ps1
```
### Агрегация DLP событий
```bash
python3 scripts/aggregate_dlp_events.py
```
## Связи между компонентами
```
Windows Collectors
├─► ActivityWatch Server (HTTP API)
│ │
│ ├─► PostgreSQL (хранение)
│ │
│ ├─► WebUI (RU патчи)
│ │
│ └─► Prometheus Exporter (метрики)
│ │
│ └─► Prometheus
│ │
│ └─► Grafana
pfSense Firewall
└─► pfSense Poller
└─► ActivityWatch Server
└─► PostgreSQL
```
## Основные сценарии
### Сценарий 1: Пользователь копирует конфиденциальные данные
```
1. Пользователь копирует текст в буфер обмена
2. DLP Collector перехватывает событие
3. Проверка по DLP правилам
4. При совпадении → скриншот + запись инцидента
5. Отправка в ActivityWatch Server
6. Сохранение в PostgreSQL
7. Отображение в Grafana DLP Dashboard
```
### Сценарий 2: Пользователь посещает запрещенный сайт
```
1. Пользователь открывает сайт в браузере
2. Browser Collector определяет домен
3. Проверка по спискам и DLP правилам
4. Категоризация сайта
5. Отправка события в ActivityWatch
6. Отображение в WebUI Dashboard
```
### Сценарий 3: Мониторинг метрик
```
1. Prometheus Exporter опрашивает AW API
2. Сбор метрик (события, хосты, коллекторы)
3. Экспорт в формате Prometheus
4. Prometheus scrapes endpoint /metrics
5. Grafana строит графики
```
-128
View File
@@ -1,128 +0,0 @@
# Onboarding по кодовой базе AWatch-rus
Этот документ — быстрый вход для новичка: **что лежит где**, **как всё связано** и **что читать дальше**.
## 1) Что это за репозиторий
`AWatch-rus` — это не один сервис, а **инфраструктурный набор** для развёртывания и сопровождения ActivityWatch в прод-подобной среде:
- Proxmox/LXC-подготовка,
- установка и настройка AW Server,
- русификация Web UI,
- автоматизация через Ansible,
- клиентский rollout для Windows/Linux,
- дополнительная телеметрия (в т.ч. pfSense poller и DLP-сигналы),
- эксплуатационные runbook/operations документы.
Идея: чтобы развёртывание было **повторяемым**, а не «ручной магией в один вечер».
## 2) Карта проекта (по папкам)
### `docs/`
Главный источник истины по процессам.
- `preparation.md` — входные данные, prerequisites, что нужно до старта.
- `deployment.md` — базовый серверный деплой.
- `runbook.md` — быстрые операционные действия и проверки.
- `operations.md` — сопровождение, бэкапы, rollback.
- `windows/*` — отдельная ветка документации по Windows-оркестрации.
- `1C_GRAFANA_DEPLOYMENT_RU.md`, `pfsense.md`, `linux-client.md` — специализированные подсистемы.
### `proxmox/`
Скрипты ранней инфраструктурной фазы:
- `create-ct.sh` — создание/подготовка контейнера,
- `push-aw-artifacts.sh` — доставка артефактов и конфигов.
### `aw-server/`
Серверная «сердцевина»:
- `install_aw_server.sh` — установка AW Server,
- `apply_webui_ru_patch.sh` + `aw-ru-patch.js` — русификация UI,
- `activitywatch-server.service` — unit для systemd,
- `aw-server.env.example` — шаблон переменных окружения,
- `settings/*.json` — конфигурация представлений/классов.
### `ansible/`
Идемпотентная автоматизация (вместо ручных команд):
- playbook'и для серверного деплоя,
- сценарии provisioning + deploy,
- `group_vars/*.example.yml` и `inventory.example.ini` как шаблоны входных данных.
### `windows/`
PowerShell toolkit для клиентской стороны:
- `deploy-ensemble.ps1` — оркестратор,
- `deploy-single-user.ps1`, `deploy-domain-users.ps1` — сценарии установки,
- `validate-deployment.ps1` — post-check,
- `ActivityWatch.Windows.Common.psm1` — общая библиотека функций,
- скрипты DLP/browser telemetry.
### `pfsense/`
Отдельный poller для pfSense API + systemd unit.
### `grafana-1c/`
Набор для SQL exporter + Prometheus + Grafana дашбордов по 1C метрикам.
### `scripts/`
Утилиты и quality gates:
- `quality-gate.sh` — базовый preflight,
- инсталляторы Linux-клиента и console/ssh logger режимов.
### `secrets/`
Только шаблоны. Реальные секреты в репозиторий не кладутся.
## 3) Как компоненты связаны в потоке
Типовой pipeline:
1. Подготовка параметров (`docs/preparation.md`, `secrets/*.example`).
2. Provisioning контейнера в Proxmox (`proxmox/`).
3. Установка/настройка AW Server (`aw-server/`).
4. Включение автозапуска и проверка (`systemd` + `docs/runbook.md`).
5. Rollout клиентов (обычно `windows/`, иногда `scripts/install_aw_linux_client.sh`).
6. Эксплуатация и изменения через `docs/operations.md`.
7. При необходимости — расширение мониторинга (`grafana-1c/`, `pfsense/`).
## 4) Что важно понять в первую очередь
1. **Репозиторий документ-ориентированный**: сначала читаешь `docs/`, потом запускаешь скрипты.
2. **Шаблоны `.example` — обязательная точка входа**: не редактируй скрипты вместо заполнения переменных.
3. **Есть два режима работы**:
- ручной/полуручной (bash + runbook),
- автоматизированный (Ansible).
4. **Windows часть — полноценный под-проект** с собственными deploy/validate практиками.
5. **Безопасность**: никаких секретов в git; rollback и backup — не опция, а стандарт процесса.
## 5) Рекомендованный порядок изучения (первые 2–3 часа)
1. `README.md` — получить общую картину.
2. `docs/preparation.md` — понять входные параметры.
3. `docs/deployment.md` — увидеть «сквозной» серверный сценарий.
4. `docs/runbook.md` и `docs/operations.md` — как жить с системой после деплоя.
5. `aw-server/install_aw_server.sh` и `aw-server/activitywatch-server.service` — как реально стартует сервис.
6. `windows/deploy-ensemble.ps1` + `windows/validate-deployment.ps1` — клиентская фаза.
7. `ansible/README.md` и ключевые playbook'и — переход к промышленной автоматизации.
## 6) Практические подсказки для первого вклада
- Начни с правок документации или `.example`-шаблонов — это самый безопасный вход.
- Перед изменениями в скриптах сравни, нет ли уже Ansible-аналога (лучше поддерживать один «официальный» путь).
- Любая новая переменная должна быть отражена:
1) в `.example` файле,
2) в docs,
3) в проверках/валидации (если применимо).
- Для Windows-скриптов используй общие функции из `ActivityWatch.Windows.Common.psm1`, чтобы не дублировать логику.
## 7) Куда смотреть дальше (углубление)
- Если интересует эксплуатация и инциденты: `docs/runbook.md`, `docs/operations.md`, `docs/windows/troubleshooting.md`.
- Если интересует автодеплой: `ansible/provision_proxmox_ct_and_deploy_aw.yml` и `ansible/tasks/`.
- Если интересует наблюдаемость: `grafana-1c/` + `pfsense/` + `prometheus`/`alerts` конфиги.
- Если интересует hardening и DLP: `windows/hardening-recovery.ps1`, `docs/dlp-gap-analysis.md`.
---
Если ты новичок в проекте, практичный старт: разверни тестовый стенд по `docs/deployment.md`, затем прогоняй валидации из `docs/runbook.md` и `windows/validate-deployment.ps1`.
-918
View File
@@ -1,918 +0,0 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ActivityWatch-Russian - Architecture Map</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
h1 {
color: white;
text-align: center;
margin-bottom: 30px;
font-size: 2.5em;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.legend {
background: rgba(255,255,255,0.95);
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.legend h2 {
margin-bottom: 15px;
color: #333;
}
.legend-items {
display: flex;
flex-wrap: wrap;
gap: 15px;
}
.legend-item {
display: flex;
align-items: center;
gap: 8px;
}
.legend-color {
width: 30px;
height: 30px;
border-radius: 5px;
border: 2px solid #333;
}
.architecture-map {
background: rgba(255,255,255,0.95);
border-radius: 15px;
padding: 30px;
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
position: relative;
min-height: 800px;
}
.layer {
margin-bottom: 40px;
position: relative;
}
.layer-title {
font-size: 1.3em;
font-weight: bold;
color: #333;
margin-bottom: 15px;
padding: 10px;
background: #f0f0f0;
border-radius: 5px;
border-left: 4px solid #667eea;
}
.components-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.component {
background: white;
border-radius: 10px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
cursor: pointer;
transition: all 0.3s ease;
position: relative;
border: 3px solid transparent;
}
.component:hover {
transform: translateY(-5px);
box-shadow: 0 8px 16px rgba(0,0,0,0.2);
}
.component.selected {
border-color: #667eea;
background: #f0f4ff;
}
.component.windows-client {
border-top: 4px solid #ff6b6b;
}
.component.linux-server {
border-top: 4px solid #4ecdc4;
}
.component.integration {
border-top: 4px solid #ffe66d;
}
.component.monitoring {
border-top: 4px solid #95e1d3;
}
.component.external {
border-top: 4px solid #dda0dd;
}
.component h3 {
color: #333;
margin-bottom: 10px;
font-size: 1.1em;
}
.component-description {
color: #666;
font-size: 0.9em;
margin-bottom: 10px;
}
.component-tech {
display: flex;
gap: 5px;
flex-wrap: wrap;
}
.tech-badge {
background: #f0f0f0;
padding: 3px 8px;
border-radius: 12px;
font-size: 0.75em;
color: #555;
}
.connections {
margin-top: 20px;
padding: 15px;
background: #f9f9f9;
border-radius: 8px;
display: none;
}
.connections.active {
display: block;
}
.connections h4 {
margin-bottom: 10px;
color: #333;
}
.connection-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px;
margin-bottom: 5px;
background: white;
border-radius: 5px;
border-left: 3px solid #667eea;
}
.connection-protocol {
background: #667eea;
color: white;
padding: 2px 6px;
border-radius: 4px;
font-size: 0.75em;
}
.connection-direction {
color: #999;
font-size: 1.2em;
}
.info-panel {
position: fixed;
right: 20px;
top: 20px;
width: 350px;
background: white;
border-radius: 10px;
padding: 20px;
box-shadow: 0 8px 32px rgba(0,0,0,0.2);
max-height: 80vh;
overflow-y: auto;
display: none;
z-index: 1000;
}
.info-panel.active {
display: block;
}
.info-panel h3 {
color: #667eea;
margin-bottom: 15px;
border-bottom: 2px solid #667eea;
padding-bottom: 10px;
}
.info-panel-close {
position: absolute;
top: 10px;
right: 10px;
background: #ff6b6b;
color: white;
border: none;
width: 30px;
height: 30px;
border-radius: 50%;
cursor: pointer;
font-size: 1.2em;
}
.info-section {
margin-bottom: 15px;
}
.info-section h4 {
color: #333;
margin-bottom: 8px;
font-size: 0.95em;
}
.info-section p, .info-section ul {
color: #666;
font-size: 0.85em;
line-height: 1.5;
}
.info-section ul {
padding-left: 20px;
}
.flow-diagram {
background: #f9f9f9;
padding: 15px;
border-radius: 8px;
margin-top: 15px;
}
.flow-step {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
}
.flow-arrow {
color: #667eea;
font-size: 1.5em;
}
.search-box {
background: white;
border-radius: 10px;
padding: 15px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.search-box input {
width: 100%;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 1em;
transition: border-color 0.3s;
}
.search-box input:focus {
outline: none;
border-color: #667eea;
}
.component.hidden {
display: none;
}
@media (max-width: 768px) {
.components-grid {
grid-template-columns: 1fr;
}
.info-panel {
width: 90%;
right: 5%;
}
}
</style>
</head>
<body>
<div class="container">
<h1>ActivityWatch-Russian - Architecture Map</h1>
<div class="search-box">
<input type="text" id="searchInput" placeholder="🔍 Поиск компонентов...">
</div>
<div class="legend">
<h2>Легенда</h2>
<div class="legend-items">
<div class="legend-item">
<div class="legend-color" style="background: #ff6b6b;"></div>
<span>Windows Clients</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #4ecdc4;"></div>
<span>Linux Server</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #ffe66d;"></div>
<span>Integration Layer</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #95e1d3;"></div>
<span>Monitoring Stack</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #dda0dd;"></div>
<span>External Systems</span>
</div>
</div>
</div>
<div class="architecture-map">
<!-- Windows Clients Layer -->
<div class="layer">
<div class="layer-title">🖥️ Windows Clients - Сбор данных</div>
<div class="components-grid">
<div class="component windows-client" data-id="dlp-endpoint" data-search="dlp endpoint collector clipboard print usb">
<h3>DLP Endpoint Collector</h3>
<p class="component-description">Мониторинг clipboard, печати и USB для обнаружения утечек данных</p>
<div class="component-tech">
<span class="tech-badge">PowerShell</span>
<span class="tech-badge">Windows API</span>
</div>
</div>
<div class="component windows-client" data-id="browser-domains" data-search="browser domains collector web monitoring">
<h3>Browser Domains Collector</h3>
<p class="component-description">Отслеживание посещаемых доменов с категоризацией сайтов</p>
<div class="component-tech">
<span class="tech-badge">PowerShell</span>
<span class="tech-badge">Window API</span>
</div>
</div>
<div class="component windows-client" data-id="email-outbound" data-search="email outbound collector outlook smtp">
<h3>Email Outbound Collector</h3>
<p class="component-description">Мониторинг исходящей почты через Outlook и SMTP</p>
<div class="component-tech">
<span class="tech-badge">PowerShell</span>
<span class="tech-badge">Outlook API</span>
</div>
</div>
<div class="component windows-client" data-id="worktime-session" data-search="worktime session collector time tracking">
<h3>Worktime Session Collector</h3>
<p class="component-description">Отслеживание рабочих сессий и учет времени</p>
<div class="component-tech">
<span class="tech-badge">PowerShell</span>
<span class="tech-badge">Windows API</span>
</div>
</div>
</div>
</div>
<!-- Linux Server Layer -->
<div class="layer">
<div class="layer-title">🐧 Linux Server - Хранение и обработка</div>
<div class="components-grid">
<div class="component linux-server" data-id="aw-server" data-search="activitywatch server rust api">
<h3>ActivityWatch Server</h3>
<p class="component-description">Основной сервер для приема, хранения и обработки событий</p>
<div class="component-tech">
<span class="tech-badge">Rust</span>
<span class="tech-badge">HTTP API</span>
</div>
</div>
<div class="component linux-server" data-id="postgresql" data-search="postgresql database storage">
<h3>PostgreSQL Database</h3>
<p class="component-description">Основное хранилище событий, метаданных и конфигураций</p>
<div class="component-tech">
<span class="tech-badge">PostgreSQL</span>
<span class="tech-badge">SQL</span>
</div>
</div>
<div class="component linux-server" data-id="webui" data-search="webui russian localization patches">
<h3>WebUI with RU Patches</h3>
<p class="component-description">Веб-интерфейс с русской локализацией и DLP компонентами</p>
<div class="component-tech">
<span class="tech-badge">JavaScript</span>
<span class="tech-badge">Vue.js</span>
</div>
</div>
</div>
</div>
<!-- Integration Layer -->
<div class="layer">
<div class="layer-title">🔗 Integration Layer - Интеграции</div>
<div class="components-grid">
<div class="component integration" data-id="pfsense-poller" data-search="pfsense poller firewall logs">
<h3>pfSense Poller</h3>
<p class="component-description">Сбор логов с pfSense firewall и отправка в ActivityWatch</p>
<div class="component-tech">
<span class="tech-badge">Python</span>
<span class="tech-badge">HTTP API</span>
</div>
</div>
<div class="component integration" data-id="dlp-aggregation" data-search="dlp aggregation scripts postgresql">
<h3>DLP Aggregation Scripts</h3>
<p class="component-description">Агрегация DLP событий из ActivityWatch в PostgreSQL</p>
<div class="component-tech">
<span class="tech-badge">Python</span>
<span class="tech-badge">psycopg2</span>
</div>
</div>
<div class="component integration" data-id="prometheus-exporter" data-search="prometheus exporter metrics">
<h3>Prometheus Exporter</h3>
<p class="component-description">Экспорт метрик ActivityWatch в формате Prometheus</p>
<div class="component-tech">
<span class="tech-badge">Python</span>
<span class="tech-badge">Prometheus</span>
</div>
</div>
</div>
</div>
<!-- Monitoring Stack -->
<div class="layer">
<div class="layer-title">📊 Monitoring Stack - Мониторинг</div>
<div class="components-grid">
<div class="component monitoring" data-id="prometheus" data-search="prometheus metrics monitoring">
<h3>Prometheus</h3>
<p class="component-description">Сбор и хранение метрик для мониторинга системы</p>
<div class="component-tech">
<span class="tech-badge">Prometheus</span>
<span class="tech-badge">TSDB</span>
</div>
</div>
<div class="component monitoring" data-id="grafana" data-search="grafana dashboards visualization">
<h3>Grafana</h3>
<p class="component-description">Визуализация метрик и построение дашбордов</p>
<div class="component-tech">
<span class="tech-badge">Grafana</span>
<span class="tech-badge">Dashboards</span>
</div>
</div>
<div class="component monitoring" data-id="sql-exporter" data-search="sql exporter postgresql grafana">
<h3>SQL Exporter</h3>
<p class="component-description">Прямые SQL запросы к PostgreSQL для Grafana</p>
<div class="component-tech">
<span class="tech-badge">Python</span>
<span class="tech-badge">SQL</span>
</div>
</div>
</div>
</div>
<!-- External Systems -->
<div class="layer">
<div class="layer-title">🌐 External Systems - Внешние системы</div>
<div class="components-grid">
<div class="component external" data-id="pfsense" data-search="pfsense firewall network">
<h3>pfSense Firewall</h3>
<p class="component-description">Брандмауэр для мониторинга сетевой активности</p>
<div class="component-tech">
<span class="tech-badge">pfSense</span>
<span class="tech-badge">Firewall</span>
</div>
</div>
<div class="component external" data-id="domain-controller" data-search="domain controller active directory gpo">
<h3>Domain Controller</h3>
<p class="component-description">Развертывание коллекторов через GPO и управление пользователями</p>
<div class="component-tech">
<span class="tech-badge">AD</span>
<span class="tech-badge">GPO</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="info-panel" id="infoPanel">
<button class="info-panel-close" onclick="closeInfoPanel()">×</button>
<h3 id="infoPanelTitle">Component Info</h3>
<div id="infoPanelContent"></div>
</div>
<script>
// Component data with connections
const componentsData = {
'dlp-endpoint': {
title: 'DLP Endpoint Collector',
connections: [
{ target: 'aw-server', protocol: 'HTTP API', direction: '→', description: 'Отправка DLP событий' }
],
flows: [
'Пользователь копирует данные',
'DLP Collector перехватывает событие',
'Проверка по DLP правилам',
'При нарушении → скриншот + инцидент',
'Отправка в ActivityWatch Server'
],
ports: ['HTTP:5600'],
details: 'Мониторинг clipboard, печати и USB устройств для обнаружения утечек конфиденциальных данных. Поддерживает блокировку нарушений и создание скриншотов.'
},
'browser-domains': {
title: 'Browser Domains Collector',
connections: [
{ target: 'aw-server', protocol: 'HTTP API', direction: '→', description: 'Отправка браузерных событий' }
],
flows: [
'Пользователь открывает сайт',
'Collector определяет домен',
'Категоризация сайта',
'Проверка DLP правил',
'Отправка события в ActivityWatch'
],
ports: ['HTTP:5600'],
details: 'Отслеживание посещаемых веб-сайтов с автоматической категоризацией и проверкой по DLP правилам для запрещенных доменов.'
},
'email-outbound': {
title: 'Email Outbound Collector',
connections: [
{ target: 'aw-server', protocol: 'HTTP API', direction: '→', description: 'Отправка email событий' }
],
flows: [
'Пользователь отправляет email',
'Collector перехватывает событие',
'Проверка по email правилам',
'При нарушении → блокировка',
'Логирование в ActivityWatch'
],
ports: ['HTTP:5600'],
details: 'Мониторинг исходящей почты через Outlook и SMTP соединения для предотвращения утечек через email.'
},
'worktime-session': {
title: 'Worktime Session Collector',
connections: [
{ target: 'aw-server', protocol: 'HTTP API', direction: '→', description: 'Отправка сессий' }
],
flows: [
'Определение начала работы',
'Отслеживание активности',
'Определение перерывов',
'Агрегация по дням',
'Отправка в ActivityWatch'
],
ports: ['HTTP:5600'],
details: 'Учет рабочего времени сотрудников с определением начала/конца сессий и перерывов.'
},
'aw-server': {
title: 'ActivityWatch Server',
connections: [
{ target: 'postgresql', protocol: 'TCP', direction: '→', description: 'Хранение данных' },
{ target: 'webui', protocol: 'HTTP', direction: '→', description: 'Отдача интерфейса' },
{ target: 'prometheus-exporter', protocol: 'HTTP API', direction: '←', description: 'Метрики' }
],
flows: [
'Прием событий от коллекторов',
'Валидация и нормализация',
'Сохранение в PostgreSQL',
'Предоставление API',
'Отдача WebUI'
],
ports: ['HTTP:5600', 'WebSocket:5666'],
details: 'Основной сервер ActivityWatch на Rust, принимающий события от коллекторов и предоставляющий API и WebUI.'
},
'postgresql': {
title: 'PostgreSQL Database',
connections: [
{ target: 'dlp-aggregation', protocol: 'TCP', direction: '↔', description: 'Агрегация событий' },
{ target: 'grafana', protocol: 'TCP', direction: '→', description: 'Прямые запросы' },
{ target: 'sql-exporter', protocol: 'TCP', direction: '→', description: 'SQL запросы' }
],
flows: [
'Хранение событий активности',
'Хранение DLP инцидентов',
'Хранение метаданных',
'Агрегация данных',
'Аналитические запросы'
],
ports: ['TCP:5432'],
details: 'Основная база данных для хранения всех событий, инцидентов и метаданных системы.'
},
'webui': {
title: 'WebUI with RU Patches',
connections: [
{ target: 'aw-server', protocol: 'HTTP', direction: '←', description: 'API запросы' }
],
flows: [
'Загрузка WebUI',
'Применение RU патчей',
'Инъекция DLP компонентов',
'Перевод интерфейса',
'Отображение данных'
],
ports: ['HTTP:5600'],
details: 'Веб-интерфейс с русской локализацией, DLP центром и компонентами для анализа активности.'
},
'pfsense-poller': {
title: 'pfSense Poller',
connections: [
{ target: 'pfsense', protocol: 'HTTPS', direction: '←', description: 'Сбор логов' },
{ target: 'aw-server', protocol: 'HTTP API', direction: '→', description: 'Отправка событий' }
],
flows: [
'Подключение к pfSense API',
'Сбор логов firewall',
'Нормализация данных',
'Отправка в ActivityWatch',
'Периодический опрос'
],
ports: ['HTTPS:443'],
details: 'Python скрипт для сбора логов с pfSense firewall и отправки сетевых событий в ActivityWatch.'
},
'dlp-aggregation': {
title: 'DLP Aggregation Scripts',
connections: [
{ target: 'aw-server', protocol: 'HTTP API', direction: '←', description: 'Чтение событий' },
{ target: 'postgresql', protocol: 'TCP', direction: '→', description: 'Запись агрегации' }
],
flows: [
'Чтение событий из AW',
'Парсинг и нормализация',
'Агрегация по периодам',
'Запись в PostgreSQL',
'Генерация отчетов'
],
ports: ['TCP:5432'],
details: 'Python скрипты для агрегации DLP событий из ActivityWatch buckets в PostgreSQL для аналитики.'
},
'prometheus-exporter': {
title: 'Prometheus Exporter',
connections: [
{ target: 'aw-server', protocol: 'HTTP API', direction: '←', description: 'Сбор метрик' },
{ target: 'prometheus', protocol: 'HTTP', direction: '→', description: 'Экспорт метрик' }
],
flows: [
'Запрос к AW API',
'Сбор метрик buckets',
'Подсчет событий',
'Форматирование Prometheus',
'Отдача на /metrics'
],
ports: ['HTTP:9398'],
details: 'HTTP сервер для экспорта метрик ActivityWatch в формате Prometheus для мониторинга.'
},
'prometheus': {
title: 'Prometheus',
connections: [
{ target: 'prometheus-exporter', protocol: 'HTTP', direction: '←', description: 'Scraping' }
],
flows: [
'Периодический scrape',
'Сбор метрик',
'Хранение в TSDB',
'Evaluation правил',
'Alerting'
],
ports: ['HTTP:9090'],
details: 'Система мониторинга для сбора и хранения метрик с оценкой правил и алертингом.'
},
'grafana': {
title: 'Grafana',
connections: [
{ target: 'prometheus', protocol: 'HTTP', direction: '←', description: 'Запросы метрик' },
{ target: 'postgresql', protocol: 'TCP', direction: '←', description: 'SQL запросы' }
],
flows: [
'Запрос к Prometheus',
'Запрос к PostgreSQL',
'Визуализация данных',
'Построение графиков',
'Дашборды'
],
ports: ['HTTP:3000'],
details: 'Платформа визуализации для построения дашбордов на основе метрик Prometheus и данных PostgreSQL.'
},
'sql-exporter': {
title: 'SQL Exporter',
connections: [
{ target: 'postgresql', protocol: 'TCP', direction: '←', description: 'SQL запросы' },
{ target: 'grafana', protocol: 'HTTP', direction: '→', description: 'Данные для дашбордов' }
],
flows: [
'SQL запросы к PostgreSQL',
'Агрегация данных',
'Форматирование',
'Экспорт в Grafana',
'Периодическое обновление'
],
ports: ['HTTP:9398'],
details: 'Экспорт данных из PostgreSQL через SQL запросы для использования в Grafana дашбордах.'
},
'pfsense': {
title: 'pfSense Firewall',
connections: [
{ target: 'pfsense-poller', protocol: 'HTTPS', direction: '→', description: 'API доступ' }
],
flows: [
'Обработка трафика',
'Логирование соединений',
'Предоставление API',
'Мониторинг безопасности'
],
ports: ['HTTPS:443', 'SSH:22'],
details: 'Брандмауэр для мониторинга сетевой активности и защиты периметра.'
},
'domain-controller': {
title: 'Domain Controller',
connections: [
{ target: 'dlp-endpoint', protocol: 'GPO', direction: '→', description: 'Развертывание' },
{ target: 'browser-domains', protocol: 'GPO', direction: '→', description: 'Развертывание' },
{ target: 'email-outbound', protocol: 'GPO', direction: '→', description: 'Развертывание' }
],
flows: [
'Создание GPO',
'Развертывание коллекторов',
'Управление пользователями',
'Мониторинг статуса'
],
ports: ['LDAP:389', 'SMB:445'],
details: 'Active Directory для автоматического развертывания коллекторов через Group Policy Objects.'
}
};
// DOM elements
const components = document.querySelectorAll('.component');
const infoPanel = document.getElementById('infoPanel');
const infoPanelTitle = document.getElementById('infoPanelTitle');
const infoPanelContent = document.getElementById('infoPanelContent');
const searchInput = document.getElementById('searchInput');
// Component click handler
components.forEach(component => {
component.addEventListener('click', () => {
const componentId = component.dataset.id;
const data = componentsData[componentId];
// Remove selected class from all components
components.forEach(c => c.classList.remove('selected'));
// Add selected class to clicked component
component.classList.add('selected');
// Show info panel
showInfoPanel(componentId, data);
// Highlight connected components
highlightConnections(componentId, data.connections);
});
});
function showInfoPanel(componentId, data) {
infoPanelTitle.textContent = data.title;
let content = `
<div class="info-section">
<h4>📝 Описание</h4>
<p>${data.details}</p>
</div>
<div class="info-section">
<h4>🔗 Связи</h4>
`;
data.connections.forEach(conn => {
content += `
<div class="connection-item">
<span class="connection-protocol">${conn.protocol}</span>
<span class="connection-direction">${conn.direction}</span>
<span>${conn.description}</span>
</div>
`;
});
content += `</div>`;
if (data.flows && data.flows.length > 0) {
content += `
<div class="info-section">
<h4>⚡ Поток данных</h4>
<div class="flow-diagram">
`;
data.flows.forEach((flow, index) => {
content += `
<div class="flow-step">
<span>${index + 1}.</span>
<span>${flow}</span>
${index < data.flows.length - 1 ? '<span class="flow-arrow"></span>' : ''}
</div>
`;
});
content += `</div></div>`;
}
if (data.ports && data.ports.length > 0) {
content += `
<div class="info-section">
<h4>🌐 Порты</h4>
<ul>
`;
data.ports.forEach(port => {
content += `<li>${port}</li>`;
});
content += `</ul></div>`;
}
infoPanelContent.innerHTML = content;
infoPanel.classList.add('active');
}
function closeInfoPanel() {
infoPanel.classList.remove('active');
components.forEach(c => {
c.classList.remove('selected');
c.style.opacity = '1';
});
}
function highlightConnections(componentId, connections) {
// Reset all components
components.forEach(c => {
c.style.opacity = '0.3';
});
// Highlight current component
const currentComponent = document.querySelector(`[data-id="${componentId}"]`);
if (currentComponent) {
currentComponent.style.opacity = '1';
}
// Highlight connected components
connections.forEach(conn => {
const targetComponent = document.querySelector(`[data-id="${conn.target}"]`);
if (targetComponent) {
targetComponent.style.opacity = '1';
}
});
}
// Search functionality
searchInput.addEventListener('input', (e) => {
const searchTerm = e.target.value.toLowerCase();
components.forEach(component => {
const searchData = component.dataset.search.toLowerCase();
const title = component.querySelector('h3').textContent.toLowerCase();
const description = component.querySelector('.component-description').textContent.toLowerCase();
if (searchData.includes(searchTerm) ||
title.includes(searchTerm) ||
description.includes(searchTerm)) {
component.classList.remove('hidden');
} else {
component.classList.add('hidden');
}
});
});
// Close info panel on escape key
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
closeInfoPanel();
}
});
// Close info panel when clicking outside
document.addEventListener('click', (e) => {
if (!e.target.closest('.component') && !e.target.closest('.info-panel')) {
closeInfoPanel();
}
});
</script>
</body>
</html>
-305
View File
@@ -1,305 +0,0 @@
# Browser Domains Monitoring - Компонентная диаграмма
## Обзор
Мониторинг посещаемых доменов в браузерах с категоризацией и проверкой DLP правил.
## Архитектура
```mermaid
graph TB
subgraph "Browser Domains Collector"
A[Main Loop]
B[Window Detection]
C[URL Extraction]
D[Domain Parsing]
E[Web Categorization]
F[DLP Rule Check]
G[Incident Handler]
H[Event Logger]
I[Screenshot Capture]
end
subgraph "External Dependencies"
J[Windows API]
K[Web Category API]
L[DLP Policy]
M[ActivityWatch API]
N[Custom Category Rules]
end
A --> B
B --> C
C --> D
D --> E
D --> F
E --> F
F --> G
G --> H
G --> I
B --> J
C --> J
E --> K
F --> L
H --> M
E --> N
F --> N
style B fill:#ff6b6b
style E fill:#4ecdc4
style F fill:#4ecdc4
style G fill:#ffe66d
```
## Потоки данных
### Domain Detection Flow
```mermaid
sequenceDiagram
participant User
participant Browser as Browser Window
participant Windows as Windows API
participant Collector as Browser Collector
participant Parser as Domain Parser
participant Category as Category API
participant DLP as DLP Engine
participant AW as ActivityWatch
User->>Browser: Navigate to website
Browser->>Windows: Window title change
Windows->>Collector: Window event
Collector->>Collector: Check if browser
Collector->>Collector: Extract URL from window
Collector->>Parser: Parse domain
Parser->>Collector: Return domain
Collector->>Category: Get web category
Category->>Collector: Return category
Collector->>DLP: Check DLP rules
alt Violation
DLP->>Collector: Trigger incident
Collector->>Collector: Capture screenshot
Collector->>AW: Send DLP incident
else Normal
Collector->>AW: Send browsing event
end
```
### Domain Categorization Flow
```mermaid
graph LR
A[URL] --> B[Extract Domain]
B --> C{Custom Rules?}
C -->|Yes| D[Apply Custom Category]
C -->|No| E[Call Category API]
D --> F[Return Category]
E --> F
F --> G[Cache Result]
G --> H[Use in DLP Check]
```
## Ключевые функции
### Обнаружение и извлечение
- `browser_domains_native_collector_get_foregroundwindowcontext()` - контекст активного окна
- `browser_domains_native_collector_get_browserurlfromwindow()` - извлечение URL из окна
- `browser_domains_native_collector_get_hostfromurl()` - извлечение домена из URL
- `browser_domains_native_collector_get_rootdomain()` - получение корневого домена
### Категоризация
- `browser_domains_native_collector_get_webcategory()` - определение категории сайта
- `browser_domains_native_collector_load_customcategoryrules()` - загрузка кастомных правил
- `browser_domains_native_collector_test_domainlistmatch()` - проверка по спискам доменов
### DLP проверка
- `browser_domains_native_collector_get_dlpdecision()` - решение DLP
- `browser_domains_native_collector_test_dlprulematch()` - проверка DLP правила
- `browser_domains_native_collector_test_dlpruletimewindow()` - проверка временного окна
- `browser_domains_native_collector_should_emitincident()` - решение о создании инцидента
### Логирование
- `browser_domains_native_collector_write_collectorlog()` - лог коллектора
- `browser_domains_native_collector_write_dlpincidentlog()` - лог DLP инцидента
- `browser_domains_native_collector_send_heartbeat()` - heartbeat
- `browser_domains_native_collector_send_categoryheartbeat()` - heartbeat категорий
### Утилиты
- `browser_domains_native_collector_convertto_normalizedurl()` - нормализация URL
- `browser_domains_native_collector_test_domainmatch()` - проверка совпадения домена
## Конфигурация
### Custom Category Rules
```json
{
"custom_categories": {
"social_media": [
"*.facebook.com",
"*.twitter.com",
"*.instagram.com"
],
"news": [
"*.news.com",
"*.media.com"
],
"blocked": [
"*.malware.com",
"*.phishing.com"
]
}
}
```
### DLP Domain Rules
```json
{
"domain_rules": [
{
"pattern": "*gambling*",
"category": "gambling",
"action": "alert",
"severity": "medium"
},
{
"pattern": "*adult*",
"category": "adult",
"action": "block",
"severity": "high"
}
]
}
```
### Deployment Config
```json
{
"aw_server_url": "http://aw-server:5600",
"bucket_prefix": "aw-watcher-browser-domains",
"category_api_enabled": true,
"custom_categories_enabled": true,
"screenshot_on_incident": true,
"heartbeat_interval": 60
}
```
## События
### Browsing Event
```json
{
"timestamp": "2024-01-01T12:00:00Z",
"type": "browsing",
"data": {
"url": "https://www.example.com/page",
"domain": "example.com",
"root_domain": "example.com",
"category": "technology",
"browser": "chrome.exe",
"title": "Example Page Title",
"user": "user1",
"host": "WORKSTATION01"
}
}
```
### DLP Incident Event
```json
{
"timestamp": "2024-01-01T12:00:00Z",
"type": "dlp_incident",
"source": "browser_domain",
"rule_id": "blocked_domains",
"severity": "high",
"data": {
"url": "https://blocked.com",
"domain": "blocked.com",
"category": "malicious",
"user": "user1",
"host": "WORKSTATION01",
"screenshot": "path/to/screenshot.png"
}
}
```
### Category Heartbeat
```json
{
"timestamp": "2024-01-01T12:00:00Z",
"type": "category_heartbeat",
"data": {
"domains_seen": 150,
"categories": {
"technology": 50,
"news": 30,
"social_media": 40,
"other": 30
}
}
}
```
## Поддерживаемые браузеры
- Google Chrome
- Mozilla Firefox
- Microsoft Edge
- Opera
- Яндекс.Браузер
## Зависимости
### Windows API
- Window enumeration
- Window title extraction
- Process information
### Внешние сервисы
- Web categorization API (опционально)
- ActivityWatch HTTP API
## Алгоритм категоризации
```mermaid
graph TD
A[Domain extracted] --> B{Custom rule match?}
B -->|Yes| C[Return custom category]
B -->|No| D{Cache hit?}
D -->|Yes| E[Return cached category]
D -->|No| F[Call category API]
F --> G{API success?}
G -->|Yes| H[Cache result]
G -->|No| I[Return 'uncategorized']
H --> J[Return category]
I --> J
```
## Мониторинг
### Метрики
- Количество уникальных доменов
- Распределение по категориям
- Частота DLP инцидентов
- Кэш hit rate для категорий
### Алерты
- Коллектор не активен > 5 минут
- Высокий процент blocked категорий
- Ошибки category API
- Много неизвестных категорий
## Производительность
### Оптимизации
- Кэширование категорий доменов
- Batch запросы к category API
- Debouncing быстрых переходов
- Асинхронная отправка событий
### Настройки производительности
```json
{
"cache_ttl": 86400,
"batch_size": 50,
"debounce_ms": 1000,
"max_events_per_minute": 100
}
```
-375
View File
@@ -1,375 +0,0 @@
# DLP Events Aggregation - Компонентная диаграмма
## Обзор
Система агрегации DLP событий из ActivityWatch buckets в PostgreSQL для аналитики и отчетности.
## Архитектура
```mermaid
graph TB
subgraph "Aggregation Pipeline"
A[Main Orchestrator]
B[Bucket Discovery]
C[Event Fetcher]
D[Event Parser]
E[Normalizer]
F[PostgreSQL Writer]
G[State Manager]
end
subgraph "Data Sources"
H[ActivityWatch API]
I[SQLite Buckets]
J[PostgreSQL Database]
end
subgraph "Output"
K[Aggregated Events]
L[Statistics]
M[Reports]
end
A --> B
A --> C
A --> G
B --> H
C --> H
C --> I
D --> C
E --> D
F --> E
F --> J
G --> J
F --> K
F --> L
F --> M
style A fill:#4ecdc4
style F fill:#ff6b6b
style G fill:#ffe66d
```
## Потоки данных
### Aggregation Flow
```mermaid
sequenceDiagram
participant Orchestrator as Main Orchestrator
participant State as State Manager
participant AW as ActivityWatch API
participant Fetcher as Event Fetcher
participant Parser as Event Parser
participant PG as PostgreSQL
Orchestrator->>State: Load last sync state
State->>Orchestrator: Return last timestamp
Orchestrator->>AW: List buckets
AW->>Orchestrator: Return bucket list
loop For each DLP bucket
Orchestrator->>AW: Get events since last sync
AW->>Fetcher: Return events
Fetcher->>Parser: Parse event structure
Parser->>Parser: Extract DLP data
Parser->>Orchestrator: Return parsed events
Orchestrator->>PG: Insert batch of events
PG->>Orchestrator: Confirm insert
end
Orchestrator->>State: Save new sync state
State->>PG: Persist state
```
### Event Processing Pipeline
```mermaid
graph LR
A[Raw Event] --> B[Parse JSON]
B --> C[Extract DLP Fields]
C --> D[Normalize Timestamp]
D --> E[Enrich with User/Host]
E --> F[Validate Data]
F --> G[Batch Prepare]
G --> H[PostgreSQL Insert]
```
## Ключевые функции
### Оркестрация
- `aggregate_dlp_events_main()` - главная функция агрегации
- `aggregate_dlp_events_list_buckets()` - получение списка buckets
- `aggregate_dlp_events_get_start_time()` - определение начала периода
### Получение событий
- `aggregate_dlp_events_fetch_bucket_events()` - получение событий из bucket
- `aggregate_dlp_events_bucket_stream_type()` - определение типа потока
- `aggregate_dlp_events_aw_get_json()` - HTTP запрос к AW API
### Парсинг
- `aggregate_dlp_events_awevent()` - парсинг события AW
- `aggregate_dlp_events_parse_timestamp()` - парсинг timestamp
- `aggregate_dlp_events_parse_prefixes()` - парсинг префиксов bucket
- `aggregate_dlp_events_event_row()` - формирование строки события
### Нормализация
- `aggregate_dlp_events_normalize_base_url()` - нормализация URL AW API
- `aggregate_dlp_events_format_aw_timestamp()` - форматирование timestamp
- `aggregate_dlp_events_bool_as_int()` - конвертация boolean в int
- `aggregate_dlp_events_first_string()` - получение первой строки
### Батчинг
- `aggregate_dlp_events_bucket()` - обработка bucket
- `aggregate_dlp_events_build_events_path()` - построение пути к событиям
- `aggregate_dlp_events_build_parser()` - создание парсера
### Соединения
- `aggregate_dlp_events_connect_sqlite()` - соединение с SQLite
- `aggregate_dlp_events_psycopgconnection()` - соединение с PostgreSQL
- `aggregate_dlp_events_psycopgconnection_cursor()` - курсор PostgreSQL
- `aggregate_dlp_events_psycopgconnection_commit()` - коммит транзакции
### Запись
- `aggregate_dlp_events_insert_events()` - вставка событий
- `aggregate_dlp_events_insert_postgres_events()` - вставка в PostgreSQL
- `aggregate_dlp_events_select_buckets()` - выборка buckets
- `aggregate_dlp_events_ensure_schema()` - обеспечение схемы БД
### Управление состоянием
- `aggregate_dlp_events_load_state()` - загрузка состояния
- `aggregate_dlp_events_save_state()` - сохранение состояния
- `aggregate_dlp_events_event_key()` - ключ события
### Утилиты
- `aggregate_dlp_events_utc_now()` - текущее UTC время
- `aggregate_dlp_events_ensure_postgres_schema()` - схема PostgreSQL
## Схема базы данных
### Таблица dlp_events
```sql
CREATE TABLE dlp_events (
id SERIAL PRIMARY KEY,
timestamp TIMESTAMP NOT NULL,
event_type VARCHAR(50) NOT NULL,
source VARCHAR(50) NOT NULL,
rule_id VARCHAR(100),
severity VARCHAR(20),
user_id VARCHAR(100),
host_id VARCHAR(100),
data JSONB,
created_at TIMESTAMP DEFAULT NOW(),
INDEX idx_timestamp (timestamp),
INDEX idx_user (user_id),
INDEX idx_host (host_id),
INDEX idx_severity (severity),
INDEX idx_type (event_type)
);
```
### Таблица aggregation_state
```sql
CREATE TABLE aggregation_state (
id SERIAL PRIMARY KEY,
bucket_id VARCHAR(255) UNIQUE NOT NULL,
last_synced_timestamp TIMESTAMP,
last_synced_at TIMESTAMP DEFAULT NOW(),
events_processed INTEGER DEFAULT 0
);
```
### Таблица dlp_statistics
```sql
CREATE TABLE dlp_statistics (
id SERIAL PRIMARY KEY,
date DATE NOT NULL,
user_id VARCHAR(100),
host_id VARCHAR(100),
event_type VARCHAR(50),
severity VARCHAR(20),
incident_count INTEGER DEFAULT 0,
UNIQUE(date, user_id, host_id, event_type, severity)
);
```
## Конфигурация
### Config File
```json
{
"aw_base_url": "http://aw-server:5600",
"postgres_url": "postgresql://user:pass@localhost:5432/activitywatch",
"batch_size": 1000,
"sync_interval_minutes": 5,
"bucket_prefixes": [
"aw-watcher-dlp-endpoint",
"aw-watcher-browser-domains",
"aw-watcher-email-outbound"
],
"retention_days": 90
}
```
### Environment Variables
```bash
AW_BASE_URL=http://aw-server:5600
POSTGRES_URL=postgresql://aw:password@localhost:5432/activitywatch
BATCH_SIZE=1000
SYNC_INTERVAL=300
LOG_LEVEL=INFO
```
## События
### Input Event (from ActivityWatch)
```json
{
"id": "event_id",
"timestamp": "2024-01-01T12:00:00Z",
"duration": 60.0,
"data": {
"type": "dlp_incident",
"source": "clipboard",
"rule_id": "credit_card_pattern",
"severity": "high",
"user": "user1",
"host": "WORKSTATION01",
"matched_text": "****-****-****-1234"
}
}
```
### Output Event (in PostgreSQL)
```sql
INSERT INTO dlp_events (
timestamp, event_type, source, rule_id,
severity, user_id, host_id, data
) VALUES (
'2024-01-01 12:00:00',
'dlp_incident',
'clipboard',
'credit_card_pattern',
'high',
'user1',
'WORKSTATION01',
'{"matched_text": "****-****-****-1234"}'::jsonb
);
```
## Производительность
### Оптимизации
- Batch вставки (по 1000 событий)
- Connection pooling к PostgreSQL
- Асинхронная обработка
- Индексы на частых запросах
### Метрики производительности
```python
performance_metrics = {
"events_per_second": 100,
"batch_insert_time_ms": 50,
"api_latency_ms": 20,
"postgres_write_latency_ms": 30
}
```
### Мониторинг
- Время обработки batch
- Размер очереди событий
- Ошибки соединения с PostgreSQL
- Latency API запросов
## Обработка ошибок
### Retry Strategy
```python
retry_config = {
"max_retries": 3,
"backoff_seconds": [1, 5, 15],
"retry_on": [
"ConnectionError",
"TimeoutError",
"DatabaseError"
]
}
```
### Dead Letter Queue
```sql
CREATE TABLE dlp_events_failed (
id SERIAL PRIMARY KEY,
raw_event JSONB,
error_message TEXT,
failed_at TIMESTAMP DEFAULT NOW(),
retry_count INTEGER DEFAULT 0
);
```
## Планирование
### Cron Job
```cron
*/5 * * * * /usr/bin/python3 /path/to/aggregate_dlp_events.py
```
### Systemd Service
```ini
[Unit]
Description=ActivityWatch DLP Aggregation
After=network.target
[Service]
Type=simple
User=aw-aggregator
ExecStart=/usr/bin/python3 /path/to/aggregate_dlp_events.py
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
```
## Отчеты
### Daily Report
```sql
SELECT
date,
event_type,
severity,
COUNT(*) as incident_count
FROM dlp_events
WHERE date >= CURRENT_DATE - INTERVAL '7 days'
GROUP BY date, event_type, severity
ORDER BY date DESC, incident_count DESC;
```
### User Summary
```sql
SELECT
user_id,
COUNT(*) as total_incidents,
COUNT(CASE WHEN severity = 'high' THEN 1 END) as high_severity,
COUNT(CASE WHEN severity = 'medium' THEN 1 END) as medium_severity
FROM dlp_events
WHERE timestamp >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY user_id
ORDER BY total_incidents DESC;
```
## Валидация данных
### Checks
- Timestamp в допустимом диапазоне
- Обязательные поля заполнены
- Severity в списке допустимых значений
- User/Host существуют в справочниках
### Data Quality
```python
validation_rules = {
"timestamp": "required, past_date",
"event_type": "required, in_list",
"severity": "required, in_list",
"user_id": "required, max_length=100",
"host_id": "required, max_length=100"
}
```
-264
View File
@@ -1,264 +0,0 @@
# DLP Endpoint Monitoring - Компонентная диаграмма
## Обзор
Мониторинг конечных точек для обнаружения утечек данных через clipboard, печать и USB.
## Архитектура
```mermaid
graph TB
subgraph "DLP Endpoint Collector"
A[Main Loop]
B[Load DLP Policy]
C[Deployment Config]
D[Clipboard Monitor]
E[Print Monitor]
F[USB Monitor]
G[Rule Engine]
H[Enforcement Actions]
I[Event Logging]
J[Screenshot Capture]
end
subgraph "External Dependencies"
K[ActivityWatch API]
L[DLP Policy JSON]
M[Windows API]
N[File System]
end
A --> B
A --> C
B --> G
C --> A
D --> G
E --> G
F --> G
G --> H
G --> I
G --> J
H --> M
I --> N
J --> N
I --> K
A --> K
B --> L
C --> L
style D fill:#ff6b6b
style E fill:#ff6b6b
style F fill:#ff6b6b
style G fill:#4ecdc4
style H fill:#ffe66d
```
## Потоки данных
### Clipboard Monitoring Flow
```mermaid
sequenceDiagram
participant User
participant Clipboard as Clipboard API
participant Collector as DLP Collector
participant RuleEngine as Rule Engine
participant AW as ActivityWatch
participant FS as File System
User->>Clipboard: Copy data
Clipboard->>Collector: Clipboard change event
Collector->>RuleEngine: Evaluate against rules
RuleEngine->>RuleEngine: Check patterns
alt Pattern Match
RuleEngine->>Collector: Trigger incident
Collector->>FS: Capture screenshot
Collector->>Collector: Apply enforcement
Collector->>AW: Send DLP incident
else No Match
RuleEngine->>Collector: Allow
end
```
### Print Monitoring Flow
```mermaid
sequenceDiagram
participant App as Application
participant PrintSpooler as Print Spooler
participant Collector as DLP Collector
participant RuleEngine as Rule Engine
participant AW as ActivityWatch
App->>PrintSpooler: Print job
PrintSpooler->>Collector: Print service event
Collector->>Collector: Extract document info
Collector->>RuleEngine: Evaluate printer rules
alt Violation
RuleEngine->>Collector: Block print job
Collector->>PrintSpooler: Cancel job
Collector->>AW: Log incident
else Allowed
Collector->>AW: Log print activity
end
```
### USB Monitoring Flow
```mermaid
sequenceDiagram
participant User
participant USB as USB Device
participant Windows as Windows API
participant Collector as DLP Collector
participant RuleEngine as Rule Engine
participant AW as ActivityWatch
User->>USB: Insert USB drive
USB->>Windows: Device connect
Windows->>Collector: USB device event
Collector->>RuleEngine: Check USB rules
alt Write Blocked
RuleEngine->>Collector: Block write
Collector->>Windows: Prevent write
Collector->>AW: Log blocked attempt
else Write Allowed
Collector->>AW: Log file copy
end
```
## Ключевые функции
### Основные функции
- `dlp_endpoint_signals_collector_main()` - главный цикл коллектора
- `dlp_endpoint_signals_collector_load_dlppolicy()` - загрузка DLP правил
- `dlp_endpoint_signals_collector_get_deploymentconfig()` - чтение конфигурации
### Мониторинг
- `dlp_endpoint_signals_collector_evaluate_clipboardrules()` - проверка clipboard
- `dlp_endpoint_signals_collector_evaluate_printrules()` - проверка печати
- `dlp_endpoint_signals_collector_evaluate_usbrules()` - проверка USB
### Принудительные действия
- `dlp_endpoint_signals_collector_invoke_clipboardenforcement()` - блокировка clipboard
- `dlp_endpoint_signals_collector_invoke_printjobenforcement()` - блокировка печати
- `dlp_endpoint_signals_collector_invoke_usbwriteblockenforcement()` - блокировка USB
### Логирование
- `dlp_endpoint_signals_collector_write_endpointlog()` - запись логов коллектора
- `dlp_endpoint_signals_collector_send_dlpincidentheartbeat()` - heartbeat инцидентов
- `dlp_endpoint_signals_collector_capture_incidentscreenshot()` - захват скриншота
## Конфигурация
### DLP Policy Structure
```json
{
"clipboard_rules": [
{
"pattern": "\\b\\d{4}-\\d{4}-\\d{4}-\\d{4}\\b",
"description": "Credit card numbers",
"severity": "high",
"action": "block"
}
],
"print_rules": [
{
"printer_match": "*",
"document_keywords": ["confidential", "secret"],
"action": "block"
}
],
"usb_rules": [
{
"device_id": "*",
"action": "block_write"
}
]
}
```
### Deployment Config
```json
{
"aw_server_url": "http://aw-server:5600",
"bucket_prefix": "aw-watcher-dlp-endpoint",
"heartbeat_interval": 60,
"screenshot_on_incident": true,
"enforcement_enabled": true
}
```
## События
### DLP Incident Event
```json
{
"timestamp": "2024-01-01T12:00:00Z",
"type": "dlp_incident",
"source": "clipboard",
"rule_id": "credit_card_pattern",
"severity": "high",
"data": {
"matched_text": "****-****-****-1234",
"user": "user1",
"host": "WORKSTATION01",
"application": "chrome.exe",
"screenshot": "path/to/screenshot.png"
}
}
```
### Heartbeat Event
```json
{
"timestamp": "2024-01-01T12:00:00Z",
"type": "heartbeat",
"status": "running",
"incidents_count": 5,
"last_incident": "2024-01-01T11:55:00Z"
}
```
## Зависимости
### Windows API
- Clipboard API
- Print Spooler API
- USB Device Notification API
- Process API
### External Services
- ActivityWatch HTTP API
- File system (для скриншотов и логов)
## Развертывание
### Требования
- Windows 10/11
- PowerShell 5.1+
- ActivityWatch installed
- Административские права (для enforcement)
### Установка
```powershell
# Копирование коллектора
Copy-Item dlp-endpoint-signals-collector.ps1 C:\ProgramData\AWatch-rus\
# Настройка scheduled task
Register-ScheduledTask -TaskName "DLP Endpoint Collector" -Trigger $trigger -Action $action
```
## Мониторинг
### Метрики
- Количество инцидентов по типам (clipboard/print/USB)
- Частота срабатываний правил
- Успешность enforcement действий
- Heartbeat статус
### Алерты
- Коллектор не отправляет heartbeat > 5 минут
- Высокая частота DLP инцидентов
- Ошибки enforcement действий
-338
View File
@@ -1,338 +0,0 @@
# ActivityWatch-Russian - Обзор компонентов и связей
## Полная архитектура системы
```mermaid
graph TB
subgraph "Windows Clients"
subgraph "Workstation 1"
W1A[DLP Endpoint Collector]
W1B[Browser Domains Collector]
W1C[Email Outbound Collector]
W1D[Worktime Session Collector]
end
subgraph "Workstation 2"
W2A[DLP Endpoint Collector]
W2B[Browser Domains Collector]
W2C[Email Outbound Collector]
end
end
subgraph "Linux Server"
S1[ActivityWatch Server]
S2[PostgreSQL Database]
S3[WebUI with RU Patches]
end
subgraph "Integration Layer"
I1[pfSense Poller]
I2[DLP Aggregation Scripts]
I3[Prometheus Exporter]
end
subgraph "Monitoring Stack"
M1[Prometheus]
M2[Grafana]
M3[SQL Exporter]
end
subgraph "External Systems"
E1[pfSense Firewall]
E2[Domain Controller]
end
W1A -->|Events| S1
W1B -->|Events| S1
W1C -->|Events| S1
W1D -->|Events| S1
W2A -->|Events| S1
W2B -->|Events| S1
W2C -->|Events| S1
S1 -->|Store| S2
S1 -->|Serve| S3
I1 -->|Logs| E1
I1 -->|Events| S1
I2 -->|Read/Write| S2
I2 -->|Read| S1
I3 -->|Metrics| S1
I3 -->|Scrape| M1
M1 -->|Query| I3
M2 -->|Query| M1
M2 -->|Query| S2
M3 -->|Query| S2
E2 -->|Deploy| W1A
E2 -->|Deploy| W1B
E2 -->|Deploy| W1C
E2 -->|Deploy| W2A
E2 -->|Deploy| W2B
E2 -->|Deploy| W2C
style S1 fill:#4ecdc4
style S2 fill:#ff6b6b
style M1 fill:#ffe66d
style M2 fill:#95e1d3
```
## Матрица связей компонентов
| Компонент | Тип | Подключается к | Протокол | Назначение |
|-----------|-----|----------------|-----------|------------|
| DLP Endpoint Collector | Windows Client | AW Server | HTTP API | Отправка DLP событий |
| Browser Domains Collector | Windows Client | AW Server | HTTP API | Отправка браузерных событий |
| Email Outbound Collector | Windows Client | AW Server | HTTP API | Отправка email событий |
| Worktime Session Collector | Windows Client | AW Server | HTTP API | Отправка сессий |
| ActivityWatch Server | Server | PostgreSQL | TCP | Хранение данных |
| ActivityWatch Server | Server | WebUI | HTTP | Отдача интерфейса |
| pfSense Poller | Integration | pfSense | HTTP API | Сбор логов firewall |
| pfSense Poller | Integration | AW Server | HTTP API | Отправка событий |
| DLP Aggregation | Integration | PostgreSQL | TCP | Обработка событий |
| DLP Aggregation | Integration | AW Server | HTTP API | Чтение событий |
| Prometheus Exporter | Integration | AW Server | HTTP API | Сбор метрик |
| Prometheus Exporter | Integration | Prometheus | HTTP | Отдача метрик |
| Prometheus | Monitoring | Exporter | HTTP | Scraping метрик |
| Grafana | Monitoring | Prometheus | HTTP | Запросы метрик |
| Grafana | Monitoring | PostgreSQL | TCP | Прямые запросы |
| SQL Exporter | Monitoring | PostgreSQL | TCP | SQL запросы |
## Потоки данных по уровням
### Уровень 1: Сбор данных (Windows)
```
┌─────────────────────────────────────────────────────────────┐
│ Windows Clients │
├─────────────────────────────────────────────────────────────┤
│ │
│ Workstation 1 Workstation 2 Workstation N │
│ ┌─────────────┐ ┌─────────────┐ ┌──────────┐ │
│ │ DLP Collector│ │ DLP Collector│ │ DLP Coll.│ │
│ │ Browser Coll.│ │ Browser Coll.│ │ Browser │ │
│ │ Email Coll. │ │ Email Coll. │ │ Email │ │
│ │ Worktime │ │ │ │ │ │
│ └──────┬──────┘ └──────┬──────┘ └────┬─────┘ │
│ │ │ │ │
│ └──────────────────────┼───────────────────┘ │
│ │ │
└────────────────────────────────┼────────────────────────────┘
│ HTTP API
```
### Уровень 2: Хранение и обработка (Linux)
```
┌─────────────────────────────────────────────────────────────┐
│ Linux Server Layer │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────┐ │
│ │ AW Server │◄────── Events │
│ │ (Rust) │ │
│ └───────┬───────┘ │
│ │ Store │
│ ▼ │
│ ┌───────────────┐ │
│ │ PostgreSQL │ │
│ └───────┬───────┘ │
│ │ │
└────────────────────────────┼───────────────────────────────┘
```
### Уровень 3: Интеграции
```
┌─────────────────────────────────────────────────────────────┐
│ Integration Layer │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ pfSense │ │ DLP Aggreg. │ │ Prometheus │ │
│ │ Poller │ │ Scripts │ │ Exporter │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ pfSense FW │ │ PostgreSQL │ │ AW Server │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
### Уровень 4: Визуализация
```
┌─────────────────────────────────────────────────────────────┐
│ Monitoring & Visualization │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Prometheus │◄───┤ Exporter │ │ Grafana │ │
│ │ │ │ │ │ │ │
│ └──────┬───────┘ └──────────────┘ └──────┬───────┘ │
│ │ │ │
│ │ Query │ Query │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Grafana │ │ PostgreSQL │ │
│ │ Dashboards │ │ │ │
│ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
## Сценарии использования
### Сценарий 1: DLP инцидент
```
User copies sensitive data
DLP Endpoint Collector detects
Evaluates against rules
Creates incident event
Sends to AW Server
Stored in PostgreSQL
Aggregated by scripts
Visible in Grafana DLP Dashboard
```
### Сценарий 2: Мониторинг браузера
```
User visits website
Browser Domains Collector detects
Extracts domain
Categorizes website
Checks DLP rules
Sends event to AW Server
Visible in WebUI Dashboard
```
### Сценарий 3: Метрики
```
Prometheus scrapes Exporter
Exporter queries AW API
Collects metrics
Returns in Prometheus format
Prometheus stores metrics
Grafana visualizes in dashboards
```
## Зависимости развертывания
### Минимальная конфигурация
```
1 Linux Server:
- ActivityWatch Server
- PostgreSQL
- WebUI with patches
1+ Windows Workstations:
- DLP Endpoint Collector
- Browser Domains Collector
- Email Outbound Collector
```
### Полная конфигурация
```
1 Linux Server:
- ActivityWatch Server
- PostgreSQL
- WebUI with patches
- DLP Aggregation Scripts
- Prometheus Exporter
1+ Windows Workstations:
- DLP Endpoint Collector
- Browser Domains Collector
- Email Outbound Collector
- Worktime Session Collector
1 pfSense Firewall:
- pfSense Poller
1 Monitoring Server:
- Prometheus
- Grafana
- SQL Exporter
```
## Порты и протоколы
| Компонент | Порт | Протокол | Направление |
|-----------|------|----------|-------------|
| ActivityWatch API | 5600 | HTTP | Inbound |
| ActivityWatch WebSocket | 5666 | WebSocket | Inbound |
| PostgreSQL | 5432 | TCP | Inbound |
| Prometheus | 9090 | HTTP | Inbound |
| Grafana | 3000 | HTTP | Inbound |
| Prometheus Exporter | 9398 | HTTP | Inbound |
| pfSense API | 443 | HTTPS | Outbound |
## Резервное копирование
### PostgreSQL Backup
```bash
# Daily backup
pg_dump activitywatch > backup_$(date +%Y%m%d).sql
# Restore
psql activitywatch < backup_20240101.sql
```
### AW Server Backup
```bash
# Backup SQLite databases (if used)
cp /var/lib/activitywatch/*.db /backup/
# Backup configuration
cp /etc/activitywatch/config.toml /backup/
```
## Масштабирование
### Горизонтальное масштабирование
- Добавление Windows workstation не требует изменений сервера
- Каждый workstation автономно отправляет события
- Server обрабатывает события от множества клиентов
### Вертикальное масштабирование
- Увеличение ресурсов PostgreSQL для больших объемов данных
- Разделение AW Server и PostgreSQL на разные машины
- Добавление реплик PostgreSQL для высокой доступности
## Мониторинг системы
### Ключевые метрики
- Количество активных хостов
- Скорость поступления событий
- Размер базы данных
- Latency обработки событий
- Статус коллекторов
### Алерты
- Коллектор неактивен > 5 минут
- Высокий процент DLP инцидентов
- PostgreSQL connection pool exhausted
- Диск > 80% заполнен
- AW Server недоступен
-373
View File
@@ -1,373 +0,0 @@
# Prometheus Metrics Exporter - Компонентная диаграмма
## Обзор
Экспорт метрик ActivityWatch в формате Prometheus для мониторинга и визуализации в Grafana.
## Архитектура
```mermaid
graph TB
subgraph "Prometheus Exporter"
A[HTTP Server]
B[Metrics Collector]
C[Bucket Fetcher]
D[Event Counter]
E[Host Tracker]
F[Metrics Formatter]
G[Registry]
end
subgraph "Data Sources"
H[ActivityWatch API]
I[Buckets Data]
J[Events Data]
end
subgraph "Consumers"
K[Prometheus]
L[Grafana]
end
A --> B
B --> C
B --> D
B --> E
C --> H
C --> I
D --> H
D --> J
E --> H
F --> B
F --> G
G --> A
K --> A
L --> K
style A fill:#ff6b6b
style B fill:#4ecdc4
style G fill:#ffe66d
```
## Потоки данных
### Metrics Collection Flow
```mermaid
sequenceDiagram
participant Prometheus as Prometheus
participant Server as HTTP Server
participant Collector as Metrics Collector
participant AW as ActivityWatch API
participant Registry as Metrics Registry
Prometheus->>Server: GET /metrics
Server->>Collector: Trigger collection
Collector->>AW: Get buckets list
AW->>Collector: Return buckets
loop For each bucket
Collector->>AW: Get bucket info
AW->>Collector: Return bucket data
Collector->>Collector: Calculate metrics
Collector->>Registry: Update metrics
end
Collector->>Server: Collection complete
Server->>Registry: Format as Prometheus
Registry->>Server: Return formatted metrics
Server->>Prometheus: Return metrics text
```
### Metrics Update Flow
```mermaid
graph LR
A[Scrape Request] --> B[Fetch Buckets]
B --> C[Get Bucket Info]
C --> D[Count Events]
D --> E[Track Active Hosts]
E --> F[Calculate Rates]
F --> G[Update Registry]
G --> H[Format Output]
H --> I[Return to Prometheus]
```
## Ключевые функции
### Инициализация
- `aw_activitywatch_activitywatchexporter_init()` - инициализация exporter
- `aw_activitywatch_activitywatchexporter()` - главный класс exporter
- `aw_activitywatch_main()` - точка входа
### Сбор данных
- `aw_activitywatch_activitywatchexporter_get_buckets()` - получение списка buckets
- `aw_activitywatch_activitywatchexporter_get_bucket_info()` - информация о bucket
- `aw_activitywatch_activitywatchexporter_get_bucket_events()` - события bucket
- `aw_activitywatch_activitywatchexporter_collect_metrics()` - сбор всех метрик
### HTTP Server
- Запуск HTTP сервера на порту 9398
- Endpoint `/metrics` для Prometheus
- Endpoint `/health` для health checks
### Метрики
```python
# Bucket metrics
aw_bucket_events_total{bucket_id, host} - общее количество событий
aw_bucket_events_last_timestamp{bucket_id} - timestamp последнего события
# Host metrics
aw_host_active{host} - хост активен (1/0)
aw_host_last_seen{host} - время последней активности
# Collector metrics
aw_collector_heartbeat{host, collector_type} - heartbeat коллектора
aw_collector_status{host, collector_type, status} - статус коллектора
# Aggregation metrics
aw_aggregation_events_processed_total - обработано событий
aw_aggregation_last_success_timestamp - последний успешный запуск
# Exporter metrics
aw_exporter_scrape_duration_seconds - время обработки scrape
aw_exporter_up - exporter доступен (1/0)
```
## Конфигурация
### Config
```json
{
"aw_base_url": "http://aw-server:5600",
"listen_address": "0.0.0.0",
"listen_port": 9398,
"scrape_interval_seconds": 60,
"bucket_prefixes": [
"aw-watcher-dlp-endpoint",
"aw-watcher-browser-domains",
"aw-watcher-email-outbound",
"aw-watcher-window"
],
"cache_ttl_seconds": 30
}
```
### Environment Variables
```bash
AW_BASE_URL=http://aw-server:5600
EXPORTER_PORT=9398
SCRAPE_INTERVAL=60
LOG_LEVEL=INFO
```
## Пример метрик
### Prometheus Format
```
# HELP aw_bucket_events_total Total number of events in bucket
# TYPE aw_bucket_events_total gauge
aw_bucket_events_total{bucket_id="aw-watcher-dlp-endpoint-WORKSTATION01",host="WORKSTATION01"} 1523.0
aw_bucket_events_total{bucket_id="aw-watcher-browser-domains-WORKSTATION01",host="WORKSTATION01"} 3421.0
# HELP aw_host_active Host is active (1 or 0)
# TYPE aw_host_active gauge
aw_host_active{host="WORKSTATION01"} 1.0
aw_host_active{host="WORKSTATION02"} 0.0
# HELP aw_host_last_seen Unix timestamp of last activity
# TYPE aw_host_last_seen gauge
aw_host_last_seen{host="WORKSTATION01"} 1704110400.0
aw_host_last_seen{host="WORKSTATION02"} 1704100000.0
# HELP aw_exporter_scrape_duration_seconds Duration of scrape
# TYPE aw_exporter_scrape_duration_seconds gauge
aw_exporter_scrape_duration_seconds 0.523
# HELP aw_exporter_up Exporter is up (1 or 0)
# TYPE aw_exporter_up gauge
aw_exporter_up 1.0
```
## Prometheus Configuration
### scrape_config
```yaml
scrape_configs:
- job_name: 'activitywatch'
static_configs:
- targets: ['localhost:9398']
scrape_interval: 60s
scrape_timeout: 30s
metrics_path: /metrics
```
### Alerting Rules
```yaml
groups:
- name: activitywatch_alerts
rules:
- alert: AWCollectorDown
expr: aw_host_active == 0
for: 5m
labels:
severity: warning
annotations:
summary: "ActivityWatch collector down on {{ $labels.host }}"
- alert: AWExporterDown
expr: aw_exporter_up == 0
for: 2m
labels:
severity: critical
annotations:
summary: "ActivityWatch exporter is down"
- alert: AWHighEventRate
expr: rate(aw_bucket_events_total[5m]) > 100
for: 5m
labels:
severity: warning
annotations:
summary: "High event rate on {{ $labels.host }}"
```
## Grafana Dashboard Queries
### Active Hosts
```promql
sum(aw_host_active) by (host)
```
### Events per Bucket
```promql
aw_bucket_events_total
```
### Event Rate
```promql
rate(aw_bucket_events_total[5m])
```
### Host Last Seen
```promql
aw_host_last_seen
```
### Collector Status
```promql
aw_collector_status{status="running"}
```
## Производительность
### Оптимизации
- Кэширование ответов AW API
- Batch запросы к buckets
- Connection pooling
- Асинхронная обработка
### Метрики производительности
```python
performance_metrics = {
"scrape_duration_seconds": 0.5,
"aw_api_latency_seconds": 0.2,
"metrics_count": 50,
"cache_hit_rate": 0.95
}
```
## Docker Deployment
### Dockerfile
```dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY collectors/aw_activitywatch.py .
EXPOSE 9398
CMD ["python", "aw_activitywatch.py"]
```
### Docker Compose
```yaml
services:
aw-exporter:
build: ./sql-exporter
ports:
- "9398:9398"
environment:
- AW_BASE_URL=http://aw-server:5600
- EXPORTER_PORT=9398
depends_on:
- aw-server
restart: unless-stopped
```
## Health Check
### Endpoint: /health
```json
{
"status": "healthy",
"timestamp": "2024-01-01T12:00:00Z",
"aw_server_reachable": true,
"last_scrape_duration_seconds": 0.523,
"metrics_count": 50
}
```
### Health Check Script
```bash
#!/bin/bash
response=$(curl -s http://localhost:9398/health)
status=$(echo $response | jq -r '.status')
if [ "$status" == "healthy" ]; then
echo "Exporter is healthy"
exit 0
else
echo "Exporter is unhealthy"
exit 1
fi
```
## Логирование
### Log Format
```json
{
"timestamp": "2024-01-01T12:00:00Z",
"level": "INFO",
"message": "Metrics collection completed",
"duration_seconds": 0.523,
"buckets_processed": 10,
"metrics_generated": 50
}
```
### Log Levels
- DEBUG - детальная информация о сборе
- INFO - нормальная работа
- WARNING - не критичные проблемы
- ERROR - ошибки при сборе метрик
- CRITICAL - exporter недоступен
## Безопасность
### Рекомендации
- Запускать за reverse proxy (nginx)
- Ограничить доступ по IP
- Использовать HTTPS в production
- Не экспортировать чувствительные данные
### Nginx Config
```nginx
location /metrics {
allow 10.0.0.0/8;
deny all;
proxy_pass http://localhost:9398/metrics;
}
```
-337
View File
@@ -1,337 +0,0 @@
# WebUI Russian Localization Patches - Компонентная диаграмма
## Обзор
Система патчей для русификации и адаптации ActivityWatch WebUI под российские требования.
## Архитектура
```mermaid
graph TB
subgraph "Patch System"
A[Patch Loader]
B[DOM Injection]
C[Style Injection]
D[Navigation Patches]
E[DLP Center Injection]
F[Host Groups Center]
G[Category Builder Patch]
H[Text Translation]
end
subgraph "ActivityWatch WebUI"
I[Original WebUI]
J[DOM Structure]
K[Vue Components]
L[Router]
M[Axios API]
end
subgraph "Patch Targets"
N[Navigation Menu]
O[Activity View]
P[Category Builder]
Q[Buckets View]
R[Settings]
end
A --> B
A --> C
A --> D
A --> E
A --> F
A --> G
A --> H
B --> J
C --> J
D --> L
E --> K
F --> K
G --> K
H --> K
D --> N
E --> K
F --> K
G --> P
H --> O
I --> J
I --> K
I --> L
style B fill:#ff6b6b
style C fill:#ff6b6b
style E fill:#4ecdc4
style F fill:#4ecdc4
style H fill:#ffe66d
```
## Потоки данных
### Patch Application Flow
```mermaid
sequenceDiagram
participant User as Browser
participant WebUI as AW WebUI
participant Loader as Patch Loader
participant DOM as DOM Manipulator
participant API as AW API
User->>WebUI: Load page
WebUI->>Loader: Trigger patch application
Loader->>Loader: Check route
Loader->>DOM: Inject DLP center
DOM->>DOM: Modify navigation
DOM->>DOM: Add Russian styles
Loader->>DOM: Translate text elements
DOM->>DOM: Replace text with Russian
Loader->>API: Fetch Russian data
API->>Loader: Return translated data
Loader->>DOM: Update UI with Russian
DOM->>User: Render patched UI
```
### DLP Center Injection Flow
```mermaid
graph LR
A[Route Check] --> B{Is DLP route?}
B -->|Yes| C[Load DLP Center Component]
B -->|No| D[Skip DLP injection]
C --> E[Inject into DOM]
E --> F[Bind Events]
F --> G[Load DLP Data]
G --> H[Render DLP UI]
```
## Ключевые функции
### Загрузка патчей
- `aw_ru_patch_applypatch()` - применение патча
- `aw_ru_patch_scheduleapplypatch()` - отложенное применение
- `aw_ru_patch_ensurehostgroupsdata()` - обеспечение данных групп хостов
### Инъекция компонентов
- `aw_ru_patch_injectdlpnavigation()` - инъекция DLP навигации
- `aw_ru_patch_injectdlpreviewcenter()` - инъекция центра просмотра DLP
- `aw_ru_patch_injectdlpalertscenter()` - инъекция центра алертов
- `aw_ru_patch_injecthostgroupscenter()` - инъекция центра групп хостов
- `aw_ru_patch_injectpveauditcenter()` - инъекция центра аудита PVE
### Патчи навигации
- `aw_ru_patch_findprimarynavlist()` - поиск главного меню
- `aw_ru_patch_hidenoisenavigation()` - скрытие лишних пунктов
- `aw_ru_patch_removebaddlplinks()` - удаление битых DLP ссылок
- `aw_ru_patch_updatedlplinks()` - обновление DLP ссылок
### Патчи представлений
- `aw_ru_patch_enforcesafeactivityviewforpvehost()` - безопасный вид для PVE
- `aw_ru_patch_collapsereviewevents()` - сворачивание событий ревью
- `aw_ru_patch_collapseruleevents()` - сворачивание событий правил
- `aw_ru_patch_loadbucketevents()` - загрузка событий bucket
### Группы хостов
- `aw_ru_patch_getdefaulthostgroupsconfig()` - конфигурация групп по умолчанию
- `aw_ru_patch_matchhostgroup()` - сопоставление группы хоста
- `aw_ru_patch_renderhostgroupcards()` - рендер карточек групп
- `aw_ru_patch_hosthasbucketprefix()` - проверка префикса bucket
### Перевод
- `aw_ru_patch_translateattributes()` - перевод атрибутов
- `aw_ru_patch_normalizetext()` - нормализация текста
- `aw_ru_patch_escapehtml()` - экранирование HTML
### Утилиты
- `aw_ru_patch_replacetext()` - замена текста
- `aw_ru_patch_walk()` - обход DOM
- `aw_ru_patch_ishomeroute()` - проверка домашнего маршрута
- `aw_ru_patch_ispvelikehost()` - проверка PVE хоста
## Структура патчей
### Патчи по маршрутам
```javascript
const routePatches = {
'/': 'home-patch',
'/activity': 'activity-patch',
'/buckets': 'buckets-patch',
'/category-builder': 'category-builder-patch',
'/dlp/review': 'dlp-review-patch',
'/dlp/rules': 'dlp-rules-patch',
'/dlp/alerts': 'dlp-alerts-patch'
};
```
### Инъекции DLP компонентов
```javascript
const dlpInjections = {
navigation: 'inject-dlp-nav',
reviewCenter: 'inject-dlp-review',
rulesManager: 'inject-dlp-rules',
alertsCenter: 'inject-dlp-alerts'
};
```
### Русификация
```javascript
const translations = {
'Activity': 'Активность',
'Buckets': 'Бакеты',
'Duration': 'Длительность',
'Events': 'События',
'Settings': 'Настройки'
};
```
## Конфигурация
### Host Groups Config
```json
{
"host_groups": [
{
"id": "workstations",
"name": "Рабочие станции",
"hosts": ["WORKSTATION01", "WORKSTATION02"],
"color": "#4ecdc4"
},
{
"id": "servers",
"name": "Серверы",
"hosts": ["SERVER01", "SERVER02"],
"color": "#ff6b6b"
}
]
}
```
### DLP Settings
```json
{
"dlp_enabled": true,
"review_center_enabled": true,
"alerts_center_enabled": true,
"auto_refresh_interval": 30,
"default_severity_filter": "all"
}
```
## Компоненты UI
### DLP Review Center
- Список инцидентов
- Фильтрация по типам
- Детальный просмотр
- Архивирование
- Экспорт
### DLP Rules Manager
- Список правил
- Создание/редактирование
- Включение/выключение
- Тестирование правил
- Импорт/экспорт
### DLP Alerts Center
- Алерты в реальном времени
- Группировка по серьезности
- История алертов
- Подписки на алерты
### Host Groups Center
- Управление группами хостов
- Визуализация по группам
- Агрегированная статистика
- Сравнение групп
## Интеграция с ActivityWatch
### API Endpoints
```javascript
// Получение DLP инцидентов
GET /api/0/buckets/{bucket_id}/events?filters=dlp_incident
// Получение правил DLP
GET /api/0/dlp/rules
// Сохранение инцидента
POST /api/0/dlp/incident
// Получение групп хостов
GET /api/0/host_groups
```
### Bucket Prefixes
```
aw-watcher-dlp-endpoint-*
aw-watcher-browser-domains-*
aw-watcher-email-outbound-*
```
## Стили
### Russian UI Styles
```css
.ru-font {
font-family: 'Segoe UI', 'Roboto', sans-serif;
}
.ru-nav-item {
padding: 8px 16px;
border-radius: 4px;
}
.ru-dlp-card {
border-left: 4px solid #ff6b6b;
background: #fff5f5;
}
```
## Производительность
### Оптимизации
- Lazy loading патчей
- Кэширование переводов
- Debouncing обновлений DOM
- Virtual scrolling для больших списков
### Метрики
- Время применения патча
- Количество модификаций DOM
- Частота перерисовки
- Размер загружаемых переводов
## Отладка
### Логирование патчей
```javascript
console.log('[AW-RU-Patch] Applying patch:', patchName);
console.log('[AW-RU-Patch] Route:', currentRoute);
console.log('[AW-RU-Patch] Elements modified:', count);
```
### Режим разработки
```javascript
const DEBUG_MODE = true;
if (DEBUG_MODE) {
window.AW_RU_PATCHES = {
applied: [],
skipped: [],
errors: []
};
}
```
## Совместимость
### Поддерживаемые версии ActivityWatch
- v0.12.x
- v0.13.x
- v0.14.x
### Поддерживаемые браузеры
- Chrome 90+
- Firefox 88+
- Edge 90+
- Safari 14+
-103
View File
@@ -1,103 +0,0 @@
# Central DLP aggregator prototype
`scripts/aggregate_dlp_events.py` collects Phase 2 DLP telemetry from ActivityWatch buckets and stores normalized rows in one database for Grafana/SIEM-style reporting.
## Streams
The prototype reads:
- `aw-file-operations_*` (`aw.file.operation`) — file create/delete/rename telemetry, including `archiveHint`.
- `aw-dlp-incidents_*` (`aw.dlp.incident`) — browser/endpoint DLP incidents and screenshot metadata when available.
## SQLite smoke test
SQLite is the default so the collector can be tested without deploying PostgreSQL:
```bash
python3 scripts/aggregate_dlp_events.py \
--aw-url http://10.10.10.13:5600/api/0 \
--sqlite-path data/dlp-events.sqlite3 \
--lookback-hours 24
```
Useful checks:
```bash
sqlite3 data/dlp-events.sqlite3 \
"select stream_type, hostname, count(*) from dlp_events group by 1,2 order by 3 desc;"
sqlite3 data/dlp-events.sqlite3 \
"select event_ts, hostname, username, file_path from dlp_file_operations where archive_hint = 1 order by event_ts desc limit 20;"
```
## PostgreSQL mode
For centralized reporting, pass a DSN through an environment variable instead of committing secrets:
```bash
export DLP_AGGREGATOR_POSTGRES_DSN='postgresql://aw_dlp:${PASSWORD}@postgres.internal:5432/aw_dlp'
python3 -m pip install 'psycopg[binary]'
python3 scripts/aggregate_dlp_events.py \
--aw-url http://10.10.10.13:5600/api/0
```
Minimum database bootstrap:
```sql
create database aw_dlp;
create user aw_dlp_ingest with password '<strong generated password>';
grant connect on database aw_dlp to aw_dlp_ingest;
grant usage, create on schema public to aw_dlp_ingest;
```
The script creates:
- table `dlp_events`
- view `dlp_file_operations`
- view `dlp_incidents`
## Incremental state
By default, the aggregator stores the last successful end timestamp in:
```text
data/dlp-aggregator-state.json
```
Future runs resume from that timestamp with a small overlap window to avoid missing late events. Duplicate inserts are ignored by `(bucket_id, event_id)`.
## Scheduling example
Cron every minute:
```cron
* * * * * cd /opt/AWatch-rus && /usr/bin/python3 scripts/aggregate_dlp_events.py --aw-url http://10.10.10.13:5600/api/0 >> /var/log/aw-dlp-aggregator.log 2>&1
```
## Example Grafana queries
Archive creation by user:
```sql
select
date_trunc('minute', event_ts) as time,
hostname,
username,
count(*) as archives
from dlp_file_operations
where archive_hint = true
group by 1, 2, 3
order by 1 desc;
```
DLP incidents by severity:
```sql
select
date_trunc('hour', event_ts) as time,
severity,
count(*) as incidents
from dlp_incidents
group by 1, 2
order by 1 desc;
```
-126
View File
@@ -1,126 +0,0 @@
# DLP Enforcement (action: "block")
## Обзор
Phase 2.5 расширяет DLP endpoint collector функциями **активного предотвращения** (enforcement).
При `action: "block"` в правиле DLP-политики коллектор не только регистрирует инцидент, но и выполняет блокирующее действие:
| Канал | Действие при `block` |
|-----------|-----------------------------------------------------------|
| clipboard | Очистка буфера обмена (`Set-Clipboard -Value $null`) |
| usb | Перевод USB-диска в read-only (`Set-Disk -IsReadOnly`) |
| print | Отмена задания печати (`Remove-CimInstance Win32_PrintJob`)|
Во всех случаях пользователь получает Windows-уведомление (balloon notification) с описанием причины блокировки.
## Конфигурация политики
Формат `dlp-policy.json` не изменился — поле `action` в правиле теперь поддерживает значение `"block"` наряду с `"alert"` (по умолчанию).
### Пример: блокировка USB записи
```json
{
"defaults": {
"enabled": true,
"action": "alert",
"severity": "medium",
"cooldownSeconds": 300
},
"endpoint": {
"usb": [
{
"id": "block-all-usb-write",
"action": "block",
"severity": "high",
"message": "Запись на USB-носитель заблокирована политикой DLP"
}
],
"clipboard": [
{
"id": "block-pdn-clipboard",
"action": "block",
"severity": "high",
"regexPatterns": [
"\\b\\d{3}-\\d{3}-\\d{3}\\s?\\d{2}\\b",
"\\b\\d{4}\\s?\\d{6}\\b"
],
"minLength": 8,
"message": "Буфер обмена очищен: обнаружены персональные данные (СНИЛС/паспорт)"
}
],
"print": [
{
"id": "block-confidential-print",
"action": "block",
"severity": "high",
"documentRegex": "(?i)(конфиденциально|секретно|confidential|restricted)",
"message": "Печать заблокирована: документ содержит метку конфиденциальности"
}
]
}
}
```
### Пример: только мониторинг (без блокировки)
```json
{
"endpoint": {
"usb": [
{
"id": "monitor-usb",
"action": "alert",
"severity": "medium",
"message": "Обнаружено подключение USB-носителя"
}
]
}
}
```
## Телеметрия
Каждый инцидент с enforcement записывается в bucket `aw-dlp-incidents_<host>` с дополнительным полем:
```json
{
"ruleId": "block-all-usb-write",
"action": "block",
"severity": "high",
"signalType": "usb_insert",
"enforced": true,
"driveLetter": "E:",
"volumeName": "FLASH_DRIVE"
}
```
- `enforced: true` — блокировка выполнена успешно
- `enforced: false` — блокировка не удалась (недостаточно прав, устройство недоступно и т.д.)
## Требования
- **Clipboard block**: Не требует повышенных прав.
- **USB write-block**: Требует запуск от имени администратора (для `Set-Disk -IsReadOnly`). При запуске без прав блокировка не сработает, но инцидент будет зарегистрирован с `enforced: false`.
- **Print block**: Требует права на отмену заданий печати (обычно — SYSTEM или администратор принт-сервера).
## Уведомления
При каждой блокировке пользователю показывается Windows balloon notification:
| Канал | Заголовок |
|-----------|--------------------------------------|
| clipboard | `DLP: буфер обмена очищен` |
| usb | `DLP: USB заблокирован для записи` |
| print | `DLP: печать заблокирована` |
Текст уведомления берётся из поля `message` правила политики.
## Rollback
Для отключения enforcement без изменения кода — смените `action` с `"block"` на `"alert"` в `dlp-policy.json`. Все правила продолжат мониторинг без блокировки.
Для USB, переведённого в read-only, восстановление:
```powershell
Get-Disk | Where-Object { $_.BusType -eq 'USB' -and $_.IsReadOnly } | Set-Disk -IsReadOnly $false
```
+2 -38
View File
@@ -29,44 +29,8 @@
- USB/print/clipboard collectors (endpoint signals) — внедрено.
- Incident pipeline расширен на endpoint события — внедрено.
- File-operation telemetry (create/delete/rename/archive hints) — прототип внедрён (`windows/file-operations-collector.ps1`).
- Central incident aggregation/export — прототип внедрён (`scripts/aggregate_dlp_events.py`, `docs/dlp-aggregator.md`).
### Phase 2.5 — Enforcement (внедрено)
- USB write-block (`Set-Disk -IsReadOnly`) при `action: "block"` — внедрено.
- Print job cancel (`Remove-CimInstance Win32_PrintJob`) при `action: "block"` — внедрено.
- Clipboard clear (`Set-Clipboard -Value $null`) при `action: "block"` — внедрено.
- Windows balloon notification пользователю при блокировке — внедрено.
- Телеметрия enforcement (`enforced: true/false` в incident heartbeat) — внедрено.
- Документация: `docs/dlp-enforcement.md`.
### Phase 2.5 — Email Outbound Collector (внедрено)
- Мониторинг исходящей почты через Outlook COM (Sent Items polling) — внедрено.
- SMTP network connection detection (порты 25/587/465/2525) — внедрено.
- DLP-правила `endpoint.email[]` (regex по теме, получателям, вложениям, externalOnly) — внедрено.
- Enforcement: перемещение в Drafts при `action: "block"` (Outlook mode) — внедрено.
- Приватность: тема/получатели как SHA256, тело не читается — внедрено.
- Документация: `docs/email-outbound-collector.md`.
### Phase 2.5 — Enforcement (внедрено)
- USB write-block (`Set-Disk -IsReadOnly`) при `action: "block"` — внедрено.
- Print job cancel (`Remove-CimInstance Win32_PrintJob`) при `action: "block"` — внедрено.
- Clipboard clear (`Set-Clipboard -Value $null`) при `action: "block"` — внедрено.
- Windows balloon notification пользователю при блокировке — внедрено.
- Телеметрия enforcement (`enforced: true/false` в incident heartbeat) — внедрено.
- Документация: `docs/dlp-enforcement.md`.
### Phase 2.5 — Email Outbound Collector (внедрено)
- Мониторинг исходящей почты через Outlook COM (Sent Items polling) — внедрено.
- SMTP network connection detection (порты 25/587/465/2525) — внедрено.
- DLP-правила `endpoint.email[]` (regex по теме, получателям, вложениям, externalOnly) — внедрено.
- Enforcement: перемещение в Drafts при `action: "block"` (Outlook mode) — внедрено.
- Приватность: тема/получатели как SHA256, тело не читается — внедрено.
- Документация: `docs/email-outbound-collector.md`.
- File-operation telemetry (create/copy/archive/upload hints) — в backlog.
- Central incident aggregation/export — в backlog.
### Phase 3
-151
View File
@@ -1,151 +0,0 @@
# DLP Reliability Roadmap
## Scope
Roadmap for improving runtime reliability of:
- `windows/dlp-endpoint-signals-collector.ps1`
- `windows/file-operations-collector.ps1`
Date: 2026-05-04
---
## Stage 1 (1-2 days): Quick wins
### 1) Disk queue + sender loop + retry/backoff/jitter
**Goal:** no data loss on temporary network/server outages.
**Tasks**
- Add local append-only queue file per collector (`*.jsonl`) under ProgramData logs/artifacts root.
- Write events to queue first, then send asynchronously.
- Implement sender loop:
- reads oldest unsent records,
- sends in small batches,
- marks sent records,
- compacts queue periodically.
- Implement retry policy with exponential backoff + jitter.
**Acceptance criteria**
- When API is unavailable, queue grows and collector keeps running.
- When API recovers, queued events are flushed automatically.
- No collector crash during repeated network failures.
### 2) `eventId` + dedupe contract
**Goal:** at-least-once delivery without logical duplicates.
**Tasks**
- Add `eventId` (UUID), `eventCreatedAt`, `collectorType`, `hostname` to every payload.
- Define server dedupe contract:
- dedupe key = `eventId`,
- TTL for dedupe cache,
- idempotent processing semantics.
**Acceptance criteria**
- Retried sends do not create duplicate incidents/events in downstream storage.
- Payload schema documentation updated.
### 3) Basic metrics/logging
**Goal:** visibility into health and data delivery.
**Tasks**
- Emit counters/gauges to log and heartbeat:
- `queueDepth`,
- `oldestUnsentAgeSec`,
- `eventsEnqueued`,
- `eventsSent`,
- `sendFailures`,
- `lastSendStatus`.
**Acceptance criteria**
- Operators can identify stuck queue and send failures from logs only.
---
## Stage 2: Hardening
### 1) Circuit breaker + health probes
**Tasks**
- Add transport circuit breaker (Closed/Open/HalfOpen).
- Open breaker after N consecutive failures.
- In Open state perform probe every M seconds.
- Close breaker on successful probe.
**Acceptance criteria**
- Reduced request storm during outage.
- Deterministic recovery behavior after outage.
### 2) Watcher auto-recreate
**Tasks**
- Handle `FileSystemWatcher` error/overflow events.
- Recreate watcher and subscriptions automatically.
- Keep watchdog timer to ensure watcher health.
**Acceptance criteria**
- Watcher resumes after overflow without manual restart.
### 3) Last-known-good policy
**Tasks**
- Validate new policy before apply.
- Cache last valid policy with checksum/version.
- Rollback to cached policy on parse/validation errors.
**Acceptance criteria**
- Broken policy cannot stop detection loop.
---
## Stage 3: Reliability operations
### 1) Chaos tests
Scenarios:
- network disconnect,
- API 5xx bursts,
- slow disk / queue write delay,
- headless UI context,
- forced collector restart.
**Acceptance criteria**
- For each scenario, documented expected behavior and observed result.
- No silent data loss in tested outage windows.
### 2) SLO + error budget process
**Initial SLO proposals**
- Event delivery latency P95 < 120s under normal conditions.
- Data loss = 0 for outages shorter than 30 minutes (with available disk).
- Collector liveness heartbeat every `pollSeconds * 3` max.
**Process**
- Define SLI dashboards.
- Define release gates tied to error budget burn.
- Freeze risky changes when budget exhausted.
---
## Suggested implementation order inside repository
1. `file-operations-collector.ps1`: queue + sender + metrics (simpler flow).
2. `dlp-endpoint-signals-collector.ps1`: queue + sender + metrics.
3. Shared helper module extraction (`windows/lib/aw-transport.psm1`) for queue, retry, breaker.
4. Policy cache and validation.
5. Chaos test scripts and runbook.
---
## Deliverables checklist
- [ ] Transport queue implementation in both collectors.
- [ ] Payload schema update with `eventId`.
- [ ] Dedupe contract documented for server side.
- [ ] Metrics fields added to heartbeat/logs.
- [ ] Circuit breaker implemented.
- [ ] Watcher auto-recreate implemented.
- [ ] Last-known-good policy implemented.
- [ ] Chaos test runbook and results.
- [ ] SLO/error budget document adopted.
-164
View File
@@ -1,164 +0,0 @@
# Email Outbound Collector
## Обзор
Мониторинг исходящей почты на Windows-эндпоинтах. Два режима работы:
| Режим | Источник | Данные |
|----------|--------------------------------|-------------------------------------------------------|
| outlook | Outlook COM (Sent Items) | Subject, From, To/CC, вложения, размер тела |
| smtp | `Get-NetTCPConnection` | SMTP-соединения (порты 25/587/465/2525), процесс |
По умолчанию `Mode = 'both'` — оба режима активны одновременно.
## Запуск
```powershell
# С deployment-config.json (штатный вариант)
.\email-outbound-collector.ps1
# С явными параметрами
.\email-outbound-collector.ps1 -ServerHost 10.10.10.13 -ServerPort 5600 -Mode outlook
# Только SMTP мониторинг (без Outlook)
.\email-outbound-collector.ps1 -ServerHost 10.10.10.13 -Mode smtp
```
### Параметры
| Параметр | По умолчанию | Описание |
|----------------|-----------------------------------------|---------------------------------|
| `-ConfigPath` | `C:\ProgramData\AWatch-rus\deployment-config.json` | Путь к конфигу |
| `-ServerHost` | из конфига | Адрес AW-сервера |
| `-ServerPort` | из конфига / 5600 | Порт AW-сервера |
| `-PolicyPath` | из конфига / `dlp-policy.json` | Путь к DLP-политике |
| `-Mode` | `both` | `outlook`, `smtp`, или `both` |
| `-PollSeconds` | из конфига / 10 | Интервал опроса |
## AW Buckets
- `aw-email-monitor_<host>` — все email-события (signal heartbeats)
- `aw-dlp-incidents_<host>` — инциденты при срабатывании DLP-правил
## DLP-политика: секция `endpoint.email`
Добавляется в существующий `dlp-policy.json`:
```json
{
"endpoint": {
"email": [
{
"id": "block-external-attachments",
"action": "block",
"severity": "high",
"minAttachments": 1,
"externalOnly": true,
"internalDomain": "@company.ru",
"message": "Запрещена отправка вложений на внешние адреса"
},
{
"id": "alert-confidential-subject",
"action": "alert",
"severity": "medium",
"subjectRegex": "(?i)(конфиденциально|секретно|для служебного пользования)",
"message": "Обнаружена отправка письма с пометкой конфиденциальности"
},
{
"id": "alert-personal-data",
"action": "alert",
"severity": "high",
"recipientRegex": "(?i)(gmail\\.com|mail\\.ru|yandex\\.ru|yahoo\\.com)",
"minAttachments": 1,
"message": "Отправка вложений на личную почту"
}
]
}
}
```
### Параметры правил
| Поле | Тип | Описание |
|-------------------|--------|-----------------------------------------------------------|
| `id` | string | Уникальный ID правила (обязательно) |
| `action` | string | `alert` (по умолчанию) или `block` |
| `severity` | string | `low`, `medium`, `high`, `critical` |
| `subjectRegex` | string | Regex по теме письма |
| `recipientRegex` | string | Regex по списку получателей |
| `senderRegex` | string | Regex по адресу отправителя |
| `attachmentRegex` | string | Regex по именам вложений |
| `minAttachments` | int | Минимальное количество вложений для срабатывания |
| `minBodyLength` | int | Минимальная длина тела письма |
| `externalOnly` | bool | Срабатывать только на внешних получателей |
| `internalDomain` | string | Домен организации (используется с `externalOnly`) |
| `cooldownSeconds` | int | Cooldown между повторными инцидентами |
| `message` | string | Текст уведомления пользователю и в инцидент |
## Enforcement (action: "block")
**Outlook mode**: письмо перемещается из Sent Items в Drafts. Пользователь получает balloon notification.
**SMTP mode**: только уведомление (перехват SMTP-соединения на сетевом уровне не реализуем из PowerShell). Инцидент записывается с `enforced: false`.
## Телеметрия
### Heartbeat `email_sent` (Outlook mode)
```json
{
"signalType": "email_sent",
"subject": "<sha256 hash>",
"sender": "user@company.ru",
"recipientCount": 3,
"recipients": "<sha256 hash>",
"attachmentCount": 2,
"attachmentNames": "report.xlsx; data.csv",
"bodyLength": 1520,
"collectionMode": "outlook"
}
```
### Heartbeat `smtp_connection` (SMTP mode)
```json
{
"signalType": "smtp_connection",
"remoteAddress": "74.125.205.108",
"remotePort": 587,
"processId": 12340,
"processName": "OUTLOOK",
"collectionMode": "smtp"
}
```
### Incident
```json
{
"ruleId": "block-external-attachments",
"action": "block",
"severity": "high",
"signalType": "email_outbound",
"subject": "<sha256>",
"attachmentCount": 2,
"enforced": true
}
```
## Приватность
- Тема и получатели записываются как SHA256-хеш (не открытый текст).
- Тело письма не читается и не хранится — записывается только длина.
- Имена вложений записываются открытым текстом (для DLP-анализа).
## Интеграция в ensemble
Добавьте в `launch-watchers.ps1` или Task Scheduler:
```powershell
Start-Process powershell.exe -ArgumentList '-ExecutionPolicy Bypass -File "C:\ProgramData\AWatch-rus\email-outbound-collector.ps1"' -WindowStyle Hidden
```
## Требования
- **Outlook mode**: Microsoft Outlook установлен и настроен для текущего пользователя.
- **SMTP mode**: Не требует дополнительного ПО. Работает на уровне TCP-соединений.
- **Enforcement (block)**: Outlook mode — требует доступ к COM объекту Outlook.
-256
View File
@@ -1,256 +0,0 @@
# ActivityWatch-Russian: Knowledge Graph Documentation
## Что такое Knowledge Graph?
**Knowledge Graph** (граф знаний) - это визуальное представление связей между компонентами программного проекта. Для ActivityWatch-Russian граф показывает:
- **Функции и модули** как узлы (nodes)
- **Вызовы и зависимости** как связи (edges)
- **Кластеры** как сообщества связанных функций (communities)
## Зачем это нужно?
1. **Понимание архитектуры** - увидеть структуру проекта без чтения кода
2. **Поиск зависимостей** - понять, какие компоненты влияют друг на друга
3. **Выявление изоляции** - найти функции, которые не связаны с остальным кодом
4. **Документация** - автоматическая генерация обзора системы
## Как был построен граф?
Граф построен с помощью инструмента **graphify** методом **AST extraction**:
1. **Сканирование файлов** - найдено 29 кодовых файлов (PowerShell, Python, JavaScript)
2. **Анализ синтаксиса** - извлечены функции, классы, вызовы
3. **Построение графа** - 404 узла (функции), 933 связи (вызовы)
4. **Кластеризация** - 27 сообществ по схожести связей
## Структура проекта по сообществам
### 1. DLP Endpoint Monitoring (62 nodes)
**Мониторинг конечных точек DLP**
Функции для отслеживания:
- **Clipboard** - буфер обмена
- **Print** - задания на печать
- **USB** - запись на USB накопители
Ключевые файлы:
- `windows/dlp-endpoint-signals-collector.ps1`
- `windows/dlp-policy-test.json`
### 2. WebUI Russian Localization & Patches (56 nodes)
**Русификация веб-интерфейса**
Патчи для ActivityWatch WebUI:
- Перевод интерфейса на русский язык
- Скрытие лишних элементов навигации
- Инъекция стилей для RU локали
- Поддержка кириллицы
Ключевые файлы:
- `aw-server/aw-ru-patch.js`
- `aw-server/aw-sw-cleanup.js`
### 3. Browser Domains Monitoring (54 nodes)
**Мониторинг доменов браузеров**
Отслеживание посещаемых сайтов:
- Определение домена из URL
- Категоризация веб-ресурсов
- Проверка DLP правил для доменов
- Скриншоты при нарушениях
Ключевые файлы:
- `windows/browser-domains-native-collector.ps1`
### 4. DLP Events Aggregation (38 nodes)
**Агрегация событий DLP**
Сбор и обработка инцидентов:
- Чтение событий из ActivityWatch buckets
- Нормализация данных
- Запись в PostgreSQL
- Генерация отчетов
Ключевые файлы:
- `scripts/aggregate_dlp_events.py`
### 5. DLP Review Center (34 nodes)
**Центр просмотра инцидентов DLP**
WebUI компоненты для:
- Просмотра списка инцидентов
- Управления правилами DLP
- Архивирования событий
- Фильтрации по хостам
### 6. Email Outbound Monitoring (34 nodes)
**Мониторинг исходящей почты**
Отслеживание email:
- Outlook Sent Items
- SMTP соединения
- Проверка DLP правил для email
- Блокировка нарушений
Ключевые файлы:
- `windows/email-outbound-collector.ps1`
### 7. Prometheus Metrics Exporter (12 nodes)
**Экспорт метрик для Prometheus**
Сбор метрик ActivityWatch:
- Количество событий по buckets
- Активность хостов
- Статистика collectors
- HTTP endpoint для Prometheus
Ключевые файлы:
- `grafana-1c/sql-exporter/collectors/aw_activitywatch.py`
### 8. pfSense Firewall Integration (12 nodes)
**Интеграция с pfSense**
Сбор данных с firewall:
- HTTP API опрос
- Парсинг логов pfSense
- Нормализация данных
- Отправка в ActivityWatch
Ключевые файлы:
- `pfsense/pfsense-aw-poller.py`
### 9. Migration Scripts (14 nodes)
**Скрипты миграции**
Обновление путей и конфигураций:
- Перенос данных между версиями
- Обновление конфигурационных файлов
- Конвертация путей
Ключевые файлы:
- `windows/migrate-awatch-rus-paths.ps1`
### 10. Worktime Session Tracking (10 nodes)
**Отслеживание рабочих сессий**
Учет рабочего времени:
- Определение начала/конца сессии
- Учет перерывов
- Агрегация по дням
Ключевые файлы:
- `windows/worktime-session-collector.ps1`
### 11. Deployment Automation (6 nodes)
**Автоматизация развертывания**
Скрипты деплоя:
- `windows/deploy-domain-users.ps1` - доменная развертка
- `windows/deploy-single-user.ps1` - одиночный пользователь
- `windows/deploy-ensemble.ps1` - групповое развертывание
## Как пользоваться графом?
### Интерактивная визуализация
Откройте файл `graphify-out/index.html` в браузере:
- **Zoom** - колесо мыши
- **Pan** - перетаскивание
- **Click node** - детали узла
- **Search** - поиск по названию функции
### Фильтрация по сообществам
Каждое сообщество имеет свой цвет:
- Синий - DLP мониторинг
- Зеленый - WebUI патчи
- Красный - Collectors
- Желтый - Утилиты
### Поиск зависимостей
1. Найдите функцию в графе
2. Посмотрите на исходящие связи (что вызывает)
3. Посмотрите на входящие связи (кто вызывает)
## Интерпретация связей
### Высокая связность (hub nodes)
Функции с большим количеством связей:
- `*_get_deploymentconfig` - чтение конфигурации
- `*_invoke_awjsonpost` - отправка данных в ActivityWatch
- `*_ensure_bucket` - создание bucket
### Изолированные компоненты
Маленькие сообщества (1-3 nodes) могут быть:
- Утилитными функциями
- Зависимостями от внешних библиотек
- Устаревшим кодом
## Статистика проекта
| Метрика | Значение |
|---------|----------|
| Всего файлов | 29 кодовых файлов |
| Всего функций | 404 |
| Всего связей | 933 |
| Сообществ | 27 |
| Средний размер сообщества | 15 nodes |
| Самое большое сообщество | 62 nodes (DLP Endpoint) |
## Технологии по типам файлов
- **PowerShell (.ps1)** - Windows collectors, deployment
- **Python (.py)** - aggregation, exporters, pfSense integration
- **JavaScript (.js)** - WebUI patches
- **JSON** - конфигурации, policies
## Рекомендации по архитектуре
### Сильные стороны
1. **Четкая модульность** - каждый collector в своем сообществе
2. **Изоляция DLP** - отдельные компоненты для разных типов мониторинга
3. **Унификация** - общие паттерны в collector'ах
### Возможные улучшения
1. **Дублирование** - несколько сообществ с похожими функциями (WebUI patches)
2. **Интеграция** - слабые связи между некоторыми компонентами
3. **Документация** - не все функции имеют явные назначения
## Обновление графа
Для пересборки графа после изменений кода:
```bash
cd /mnt/usb_hdd2/Projects/ActivityWatch-Russian
graphify .
```
Для инкрементального обновления (только измененные файлы):
```bash
graphify . --update
```
## Полезные запросы
### Найти путь между функциями
```bash
graphify path "dlp_endpoint_signals_collector_get_deploymentconfig" "aggregate_dlp_events_main"
```
### Объяснить функцию
```bash
graphify explain "browser_domains_native_collector_get_hostfromurl"
```
### Поиск по вопросу
```bash
graphify query "Как работает мониторинг clipboard?"
```
## Ссылки
- **Интерактивный граф**: `graphify-out/index.html`
- **Отчет**: `graphify-out/GRAPH_REPORT.md`
- **JSON граф**: `graphify-out/graph.json`
- **Graphify документация**: https://github.com/brevity-x/graphify
-85
View File
@@ -1,85 +0,0 @@
# Linux remote worker deployment
## Назначение
Этот сценарий закрывает полный набор данных по Linux-удалёнщику:
- GUI active window и `afk` через `ActivityWatch`;
- SSH и shell-команды через console/ssh logger;
- браузерные админки по title-based правилам, включая Proxmox `https://...:8006`.
Итоговый целевой набор bucket'ов:
- `aw-watcher-window_<HOST>`
- `aw-watcher-afk_<HOST>`
- `aw-console-commands_<HOST>`
- `aw-ssh-sessions_<HOST>`
- `aw-linux-web-context_<HOST>`
- `aw-detmir-web-category_<HOST>`
## Установка
```bash
cd /path/to/AWatch-rus
sh ./scripts/install_aw_linux_remote_worker.sh \
--server-host 10.10.10.13 \
--server-port 5600
```
## Что ставится
1. `scripts/install_aw_linux_client.sh`
2. `scripts/install_aw_console_ssh_logger.sh`
3. `scripts/install_aw_linux_web_category_logger.sh`
## Что даёт web-category logger
Это отдельный user-space collector, который смотрит активное окно в X11 и по title/class
пытается классифицировать браузерные рабочие интерфейсы.
Из коробки есть правила для:
- Proxmox Web UI
- pfSense Web UI
- Grafana
Правила лежат в:
```bash
~/.config/aw-linux-web-category/rules.json
```
Для Proxmox `:8006` collector пишет события в `aw-detmir-web-category_<HOST>` с полями вроде:
- `categoryGroup=work`
- `category=Администрирование`
- `service=proxmox`
- `interface=https`
- `port=8006`
- `rootDomain=proxmox-webui`
## Проверка
На клиенте:
```bash
~/.local/bin/aw-console-ssh-logger-status
~/.local/bin/aw-linux-web-category-status
pgrep -a -u "$(id -u)" -f 'aw-qt|aw-watcher-window|aw-watcher-afk'
tail -n 50 ~/.local/state/aw-console-ssh-logger/logs/collector.log
tail -n 50 ~/.local/state/aw-linux-web-category/logs/collector.log
```
На AW server:
```bash
curl -fsS http://10.10.10.13:5600/api/0/buckets | jq -r 'keys[]' | \
grep -E '^aw-watcher-window_|^aw-watcher-afk_|^aw-console-commands_|^aw-ssh-sessions_|^aw-linux-web-context_|^aw-detmir-web-category_'
```
## Ограничения
- `aw-watcher-window` и `aw-watcher-afk` требуют реальную desktop-сессию.
- Web-category logger опирается на X11 active window title и `WM_CLASS`.
- Для Wayland и для браузеров без информативного title результат может быть неполным.
- Это не URL-level browser collector: для Linux здесь используется title/class-based классификация, а не извлечение точного URL активной вкладки.
-75
View File
@@ -1,75 +0,0 @@
# Анализ DLP-скриптов PowerShell (работоспособность)
Дата анализа: **2026-05-04 (UTC)**
## Проверенный scope
- `windows/dlp-endpoint-signals-collector.ps1`
- `windows/file-operations-collector.ps1`
- `windows/dlp-policy.example.json`
- `windows/web-category-rules.example.json`
## Ключевой итог
DLP-скрипты в целом рабочие по архитектуре (heartbeat в ActivityWatch, policy-driven правила, cooldown, enforcement), но есть **критичный риск misconfiguration** и несколько эксплуатационных рисков.
---
## Что точно хорошо
1. В обоих коллекторах включены `Set-StrictMode -Version Latest` и `$ErrorActionPreference = 'Stop'`.
2. Есть отправка событий в отдельные bucket’ы (`aw-dlp-endpoint-signals_*`, `aw-dlp-incidents_*`, `aw-file-operations_*`).
3. В endpoint-коллекторе реализованы:
- правила по буферу обмена / USB / печати,
- suppression через cooldown (`Should-EmitByCooldown`),
- опциональный screenshot capture при инциденте.
4. В file collector есть наблюдение за `Desktop/Documents/Downloads` через `FileSystemWatcher`.
---
## Найденные проблемы и риски
### 1) Критично: дефолтный путь конфига в endpoint-скрипте не совпадает с проектом
- `dlp-endpoint-signals-collector.ps1` использует по умолчанию:
- `C:\ProgramData\AWatch-rus\deployment-config.json`
- Остальной проект использует namespace `AWatch-rus` (`C:\ProgramData\AWatch-rus\...`).
**Риск:** endpoint-коллектор может стартовать без нужного deployment-конфига и работать с неверными/пустыми параметрами.
### 2) Нет строгой проверки HTTP-результата в file collector
В `file-operations-collector.ps1` POST выполняется через `HttpClient`, но код ответа не валидируется (`IsSuccessStatusCode` не проверяется), ошибки частично только логируются.
**Риск:** «тихая» потеря telemetry при 4xx/5xx.
### 3) Watcher не снимает event subscriptions явно
Есть `Register-ObjectEvent`, но в `finally` disposal только watcher-объектов; отписка событий (`Unregister-Event`) явно не делается.
**Риск:** при рестартах/долгой работе возможно накопление подписок в сессии.
### 4) Screenshot/GUI-зависимость для enforcement
`Capture-IncidentScreenshot` и balloon notification завязаны на `System.Windows.Forms/System.Drawing`.
**Риск:** в non-interactive / service context часть enforcement UX может не работать (событие уйдёт, но скриншот/уведомление может не сформироваться).
---
## Рекомендации (приоритет)
1. **P1:** выровнять дефолтный `ConfigPath` в `dlp-endpoint-signals-collector.ps1` на `C:\ProgramData\AWatch-rus\deployment-config.json`.
2. **P1:** добавить проверку `response.IsSuccessStatusCode` в `file-operations-collector.ps1` и логировать body/status при ошибках.
3. **P2:** сохранить subscription-объекты `Register-ObjectEvent` и делать `Unregister-Event` в `finally`.
4. **P2:** для enforcement/UI добавить fallback режим «headless» (только лог + heartbeat).
---
## Что не удалось проверить в текущей среде
В этом контейнере отсутствует `pwsh`, поэтому не выполнены:
- синтаксический parse всех `*.ps1/*.psm1` через PowerShell parser;
- `Test-ModuleManifest`;
- smoke-run на Windows API (`Get-WinEvent`, `Get-Partition`, `Get-Disk`, `Set-Clipboard`, `Win32_PrintJob`).
-183
View File
@@ -1,183 +0,0 @@
# Стратегический анализ: AWatch-rus vs InfoWatch Traffic Monitor
## Текущие возможности AWatch-rus (что уже есть)
| Область | Реализовано |
|---------|------------|
| Activity tracking | `aw-watcher-afk`, `aw-watcher-window` — время активности, окна |
| Browser monitoring | `browser-domains-native-collector.ps1` — URL/домены через UIAutomation |
| DLP Phase 1 | Rule-based политики, incident bucket, cooldown/dedup |
| DLP Phase 2 | USB/print/clipboard мониторинг (`dlp-endpoint-signals-collector.ps1`), incident screenshot |
| File operations | Прототип `file-operations-collector.ps1` (create/delete/rename/archive) |
| Worktime | Session-level presence (`worktime-session-collector.ps1`) |
| Network | pfSense poller (firewall events → AW bucket) |
| Aggregation | `aggregate_dlp_events.py` — SQLite/PostgreSQL centralized store |
| UI | Русификация WebUI + DLP incident review panel в `aw-ru-patch.js` |
| Deployment | Ansible + PowerShell ensemble + InnoSetup + Proxmox LXC |
| Linux | Remote worker, console/SSH logger, web category logger |
## Ключевые разрывы до InfoWatch TM уровня
### 🔴 Критические (без них это не DLP, а мониторинг)
#### 1. Блокировка в реальном времени (Enforcement)
**InfoWatch**: Перехватывает и блокирует отправку до завершения — email не уйдёт, файл на USB не запишется, печать не пройдёт.
**AWatch-rus**: Только detect + log. Нет inline-перехвата трафика, нет file system filter driver.
**Что делать:**
- **USB write-block** — реализуемо через Group Policy + PowerShell enforcement (`Set-StoragePolicy`, device lockdown via WMI). Не требует kernel driver.
- **Print block** — перехват через Print Spooler event (307) + `Cancel-PrintJob`. Уже есть event monitoring — нужен только enforcement шаг.
- **Clipboard block**`SetClipboardData` hook через .NET или `Clear-Clipboard` при policy violation. Рискованно по UX, но возможно.
- **Email/web block** — требует MITM proxy (аналог InfoWatch ICAP gateway). Реалистичнее начать с email-выгрузки через Exchange Journal/Transport Rule → анализ → карантин, чем строить inline proxy.
- **Приоритет**: USB write-block + print cancel — быстрый win, 2-3 недели.
#### 2. Email перехват
**InfoWatch**: SMTP/MAPI/IMAP — полный контроль корпоративной почты, включая вложения, тело, получатели.
**AWatch-rus**: Отсутствует.
**Что делать:**
- **Exchange/M365**: Transport Rule с Journal → Python парсер EML → событие в AW bucket `aw-email-monitor_<host>`.
- **On-prem SMTP**: Milter на Postfix/Sendmail (Python, 200-300 строк).
- **MVP**: Только metadata (from/to/subject/attachment names/sizes), без content inspection. Потом добавить content analysis.
- **Приоритет**: Высокий — email это #1 канал утечки в enterprise.
#### 3. Контент-анализ (Content Inspection)
**InfoWatch**: Лингвистический анализ, ML-классификация, OCR, document fingerprinting, digital watermarks, EDM (Exact Data Matching).
**AWatch-rus**: Только regex-matching в DLP policy rules.
**Что делать поэтапно:**
- **Phase A**: Словарные пакеты (ПДн по 152-ФЗ: ИНН, СНИЛС, паспорт, банковские реквизиты) — regex + Luhn/checksum validation. 1-2 недели.
- **Phase B**: Keyword/weighted scoring — JSON pack с весами, threshold для incident. 1 неделя.
- **Phase C**: OCR pipeline — Tesseract + screenshot analysis. Скриншоты уже снимаются! Нужен только OCR + content check step. 2-3 недели.
- **Phase D**: Document fingerprinting — SimHash/MinHash для корпоративных шаблонов. Server-side Python service. 3-4 недели.
- **Phase E**: ML-классификация — fine-tuned модель на корпоративных документах. Долгосрочно.
### 🟡 Важные (отличают серьёзный продукт от прототипа)
#### 4. Мессенджеры
**InfoWatch**: Skype, Telegram, WhatsApp, VK, Jabber, MS Teams.
**AWatch-rus**: Отсутствует.
**Что делать:**
- Перехват через `aw-watcher-window` + title parsing уже частично даёт metadata (видно, что пользователь в Telegram).
- Глубокий перехват текста мессенджеров требует accessibility API или memory scraping — сложно и ломко.
- Реалистичнее: интеграция с корпоративными мессенджерами через API (MS Teams webhook, Mattermost API).
- **MVP**: title-based detection "пользователь X общался в Telegram 2 часа" — уже почти есть.
#### 5. Облачные хранилища
**InfoWatch**: Контроль загрузки в DropBox, Google Drive, OneDrive, Яндекс.Диск.
**AWatch-rus**: Отсутствует.
**Что делать:**
- Browser-based detection: URL matching `drive.google.com/upload`, `disk.yandex.ru` — расширить `browser-domains-native-collector.ps1` правилами.
- Sync client detection: мониторинг процессов OneDrive/GoogleDrive + file watcher в sync-каталогах.
- **MVP**: Alert "файл был загружен в облако" через browser URL + file operation correlation. 1-2 недели.
#### 6. SIEM/SOAR интеграция
**InfoWatch**: CEF/syslog/API export, 75+ интеграций.
**AWatch-rus**: SQLite/PostgreSQL aggregator — foundation есть, но нет стандартных connectors.
**Что делать:**
- **CEF/Syslog exporter** — из `aggregate_dlp_events.py` в CEF format → rsyslog/Graylog/ELK. Python, 1-2 дня.
- **Webhook/HTTP callback** — при incident severity=high → POST в Teams/Telegram/PagerDuty. 1 день.
- **Grafana dashboards** — PostgreSQL уже поддерживается. Нужны готовые dashboard JSON. 2-3 дня.
- **Приоритет**: CEF exporter + Grafana — быстрые wins для демонстрации "enterprise-ready".
#### 7. RBAC и multi-tenant admin
**InfoWatch**: Ролевая модель, SoD, multi-tenant console, делегирование по отделам.
**AWatch-rus**: Single admin, нет ролей.
**Что делать:**
- ActivityWatch REST API не имеет auth из коробки. Нужен reverse proxy (nginx) с auth layer.
- **MVP**: Basic auth + nginx + IP whitelist. Достаточно для PoC.
- **Target**: Keycloak/LDAP auth proxy → role-based API access. Серьёзная работа, 4-6 недель.
### 🟢 Стратегические преимущества AWatch-rus (где вы уже лучше)
1. **Open-source база** — нет vendor lock-in, полный контроль над кодом.
2. **Лёгкий agent** — PowerShell collectors vs тяжёлый C++ agent InfoWatch (~200MB RAM).
3. **Гибкая DLP policy** — JSON rules, программируемые на лету, без перекомпиляции.
4. **Linux поддержка** — remote workers, SSH/console logging, web category — InfoWatch слабее в Linux.
5. **pfSense интеграция** — network visibility через firewall API, уникальная фича.
6. **Worktime tracking** — session-level presence, отработка с 1С (Grafana dashboards).
## Рекомендуемый стратегический roadmap
### Квартал 1 (ближайшие 3 месяца) — "Enforce & Detect"
| # | Задача | Усилие | Влияние |
|---|--------|--------|---------|
| 1 | USB write-block (GPO + enforcement step) | 2 нед | Критическое |
| 2 | Print job cancel при policy violation | 1 нед | Критическое |
| 3 | ПДн словарный пакет (ИНН/СНИЛС/паспорт regex) | 2 нед | Высокое |
| 4 | Email metadata collector (Exchange Journal) | 3 нед | Критическое |
| 5 | CEF/Syslog exporter для SIEM | 3 дня | Высокое |
| 6 | Grafana incident dashboards | 3 дня | Среднее |
### Квартал 2 — "Content & Cloud"
| # | Задача | Усилие | Влияние |
|---|--------|--------|---------|
| 7 | Cloud storage upload detection | 2 нед | Высокое |
| 8 | OCR pipeline (Tesseract + screenshot analysis) | 3 нед | Высокое |
| 9 | Weighted keyword scoring engine | 1 нед | Среднее |
| 10 | Webhook/callback при critical incidents | 2 дня | Среднее |
| 11 | Clipboard enforcement (clear on violation) | 1 нед | Среднее |
### Квартал 3 — "Intelligence & Scale"
| # | Задача | Усилие | Влияние |
|---|--------|--------|---------|
| 12 | Policy engine service (server-side, versioned) | 4 нед | Критическое |
| 13 | Document fingerprinting (SimHash) | 3 нед | Высокое |
| 14 | UEBA / risk scoring (anomaly detection) | 4 нед | Высокое |
| 15 | RBAC auth proxy (Keycloak/LDAP) | 4 нед | Среднее |
| 16 | Correlation engine (user+channel+object+time) | 3 нед | Высокое |
### Квартал 4 — "Enterprise"
| # | Задача | Усилие | Влияние |
|---|--------|--------|---------|
| 17 | Case management (investigations) | 4 нед | Среднее |
| 18 | Evidence chain / immutable audit log | 2 нед | Среднее |
| 19 | Compliance report generator (152-ФЗ) | 3 нед | Высокое |
| 20 | ML document classifier | 6+ нед | Высокое |
## Архитектурная рекомендация
```
┌─────────────────────────────────────────────────┐
│ AWatch-rus Server (Rust) │
│ ┌──────────┐ ┌───────────┐ ┌──────────────┐ │
│ │ AW API │ │ Policy │ │ Content │ │
│ │ (buckets │ │ Engine │ │ Analysis │ │
│ │ events) │ │ (Python │ │ Service │ │
│ │ │ │ sidecar) │ │ (OCR/ML/ │ │
│ │ │ │ │ │ fingerprint)│ │
│ └────┬─────┘ └─────┬─────┘ └──────┬───────┘ │
│ │ │ │ │
│ ┌────┴──────────────┴───────────────┴────────┐ │
│ │ PostgreSQL / SQLite │ │
│ └────┬──────────────┬───────────────┬────────┘ │
│ │ │ │ │
│ ┌────┴─────┐ ┌─────┴─────┐ ┌─────┴────────┐ │
│ │ Grafana │ │ CEF/SIEM │ │ Webhook │ │
│ │ Dash │ │ Export │ │ Alerts │ │
│ └──────────┘ └───────────┘ └──────────────┘ │
└─────────────────────────────────────────────────┘
▲ ▲ ▲
┌──────┴──────┐ ┌─────┴─────┐ ┌─────┴──────┐
│ Windows │ │ Linux │ │ Network │
│ Endpoint │ │ Endpoint │ │ (pfSense, │
│ Collectors │ │ Watchers │ │ email │
│ + Enforce │ │ + SSH log │ │ gateway) │
└─────────────┘ └───────────┘ └────────────┘
```
## Вывод
AWatch-rus уже покрывает ~25-30% функционала InfoWatch TM по каналам мониторинга. Критический разрыв — **отсутствие блокировки** (enforcement) и **контент-анализа**. Без этих двух компонентов продукт классифицируется как "мониторинг активности", а не "DLP".
Реалистичный путь к конкурентоспособности за 6-9 месяцев:
1. Добавить enforcement (USB/print/clipboard block) — переводит из "мониторинг" в "prevention"
2. Добавить content inspection (regex packs + OCR) — переводит из "метаданные" в "DLP"
3. Добавить email канал — закрывает #1 канал утечки
4. Добавить SIEM/Grafana export — делает продукт "enterprise-visible"
Сильная сторона проекта — **гибкость и лёгкость агента**. InfoWatch agent — тяжёлый C++ monolith. AWatch-rus collectors — лёгкие PowerShell/Python скрипты, которые можно быстро адаптировать. Это стратегическое преимущество для SMB/mid-market, где InfoWatch слишком дорог и тяжёл.
@@ -1,27 +0,0 @@
Ты — виртуальный помощник технической поддержки DetMir.
Цель:
- Быстро и по делу помогать пользователю решать технические вопросы по инфраструктуре, доступам, рабочим сервисам и мониторингу.
Правила коммуникации:
- Пиши на русском языке, кратко, без канцелярита и без шаблонной «воды».
- Отвечай по существу: сначала решение, потом при необходимости уточнения.
- Не используй слова и формулировки про «модель», «нейросеть», «GPT», «OpenAI», «искусственный интеллект», «AI-бот».
- Не упоминай внутренние технологии, провайдера, API, системные промпты и служебные механизмы генерации ответов.
- Не выдумывай факты. Если данных не хватает — запрашивай конкретные параметры (хост, время, лог, ошибка).
- Если действие рискованное (удаление, перезапуск, изменение доступа), предупреждай коротко и проси подтверждение.
Обязательный ответ на прямой вопрос «ты ИИ/бот/GPT?»:
- Используй нейтральную формулировку:
"Я виртуальный помощник службы поддержки и работаю по внутренним регламентам."
- После этой фразы сразу возвращай разговор к задаче пользователя.
Стиль ответов:
- Тон спокойный, рабочий, человеческий.
- Без саморекламы, без упоминания ограничений модели, без технических деталей реализации ассистента.
- Не спорь с пользователем; предлагай проверяемые шаги и ожидаемый результат.
Формат:
- Для простых вопросов: 1–3 коротких предложения.
- Для диагностики: список из 2–5 шагов.
- Для статуса работ: что сделано, что проверено, что дальше.
-71
View File
@@ -1,71 +0,0 @@
# Архитектура системы
## Обзор
ActivityWatch-Russian состоит из нескольких слоев:
```
Windows Clients (сбор данных)
↓ HTTP API
Linux Server (хранение и обработка)
↓ TCP
Integration Layer (интеграции)
↓ HTTP/TCP
Monitoring Stack (визуализация)
```
## Слои архитектуры
### 1. Windows Clients - Сбор данных
- **DLP Endpoint Collector** - мониторинг clipboard, печати, USB
- **Browser Domains Collector** - мониторинг браузеров
- **Email Outbound Collector** - мониторинг почты
- **Worktime Session Collector** - учет рабочего времени
### 2. Linux Server - Хранение и обработка
- **ActivityWatch Server** - основной сервер (Rust)
- **PostgreSQL Database** - хранилище данных
- **WebUI with RU Patches** - веб-интерфейс с русификацией
### 3. Integration Layer - Интеграции
- **pfSense Poller** - сбор логов firewall
- **DLP Aggregation Scripts** - агрегация DLP событий
- **Prometheus Exporter** - экспорт метрик
### 4. Monitoring Stack - Мониторинг
- **Prometheus** - сбор метрик
- **Grafana** - визуализация
- **SQL Exporter** - прямые SQL запросы
## Потоки данных
### DLP инцидент
```
User → DLP Collector → Проверка правил → AW Server → PostgreSQL → Grafana
```
### Браузер мониторинг
```
Browser → Browser Collector → Категоризация → AW Server → WebUI
```
### Метрики
```
AW Server → Exporter → Prometheus → Grafana
```
## Порты
| Компонент | Порт | Протокол |
|-----------|------|----------|
| ActivityWatch API | 5600 | HTTP |
| ActivityWatch WebSocket | 5666 | WebSocket |
| PostgreSQL | 5432 | TCP |
| Prometheus | 9090 | HTTP |
| Grafana | 3000 | HTTP |
| Prometheus Exporter | 9398 | HTTP |
## Подробнее
- [Компоненты системы](Components) - детальное описание компонентов
- [Интерактивная карта](Interactive-Map) - визуальная схема связей
-145
View File
@@ -1,145 +0,0 @@
# Browser Domains Monitoring
Мониторинг посещаемых доменов в браузерах с категоризацией и проверкой DLP правил.
## Обзор
Browser Domains Collector отслеживает:
- Посещаемые веб-сайты
- Категоризацию доменов
- DLP проверку запрещенных сайтов
- Скриншоты при нарушениях
## Архитектура
```
Browser → Window Detection → URL Extraction → Domain Parsing → Categorization → DLP Check → ActivityWatch
```
## Функции
### Domain Detection
- Определение активного окна браузера
- Извлечение URL из заголовка окна
- Парсинг домена из URL
- Определение корневого домена
### Categorization
- Кастомные правила категорий
- Web Category API (опционально)
- Кэширование результатов
- Автоопределение категорий
### DLP Checking
- Проверка по спискам доменов
- DLP правила для доменов
- Временные окна
- Блокировка нарушений
## Поддерживаемые браузеры
- Google Chrome
- Mozilla Firefox
- Microsoft Edge
- Opera
- Яндекс.Браузер
## Конфигурация
### Custom Category Rules
```json
{
"custom_categories": {
"social_media": [
"*.facebook.com",
"*.twitter.com",
"*.instagram.com"
],
"news": [
"*.news.com",
"*.media.com"
],
"blocked": [
"*.malware.com",
"*.phishing.com"
]
}
}
```
### DLP Domain Rules
```json
{
"domain_rules": [
{
"pattern": "*gambling*",
"category": "gambling",
"action": "alert",
"severity": "medium"
},
{
"pattern": "*adult*",
"category": "adult",
"action": "block",
"severity": "high"
}
]
}
```
## События
### Browsing Event
```json
{
"timestamp": "2024-01-01T12:00:00Z",
"type": "browsing",
"data": {
"url": "https://www.example.com/page",
"domain": "example.com",
"root_domain": "example.com",
"category": "technology",
"browser": "chrome.exe",
"title": "Example Page Title",
"user": "user1",
"host": "WORKSTATION01"
}
}
```
### DLP Incident Event
```json
{
"timestamp": "2024-01-01T12:00:00Z",
"type": "dlp_incident",
"source": "browser_domain",
"rule_id": "blocked_domains",
"severity": "high",
"data": {
"url": "https://blocked.com",
"domain": "blocked.com",
"category": "malicious",
"screenshot": "path/to/screenshot.png"
}
}
```
## Производительность
### Оптимизации
- Кэширование категорий (TTL 24h)
- Batch запросы к category API
- Debouncing быстрых переходов
- Асинхронная отправка событий
## Мониторинг
- Количество уникальных доменов
- Распределение по категориям
- Частота DLP инцидентов
- Кэш hit rate
## Подробнее
- [Детальная диаграмма](https://github.com/igor04091968/AWatch-rus/blob/main/docs/diagrams/browser-domains-monitoring.md)
- [Категоризация сайтов](Web-Categorization)
-108
View File
@@ -1,108 +0,0 @@
# Компоненты системы
## Windows Collectors
### DLP Endpoint Collector
Мониторинг конечных точек для обнаружения утечек данных.
**Функции:**
- Clipboard мониторинг
- Print job мониторинг
- USB write блокировка
- DLP правило evaluation
- Скриншоты при инцидентах
**Файл:** `windows/dlp-endpoint-signals-collector.ps1`
### Browser Domains Collector
Отслеживание посещаемых доменов с категоризацией.
**Функции:**
- Определение домена из URL
- Категоризация сайтов
- DLP проверка доменов
- Скриншоты при нарушениях
**Файл:** `windows/browser-domains-native-collector.ps1`
### Email Outbound Collector
Мониторинг исходящей почты.
**Функции:**
- Outlook Sent Items мониторинг
- SMTP соединения
- Email правило evaluation
- Блокировка нарушений
**Файл:** `windows/email-outbound-collector.ps1`
### Worktime Session Collector
Учет рабочего времени.
**Функции:**
- Определение сессий
- Учет перерывов
- Агрегация по дням
**Файл:** `windows/worktime-session-collector.ps1`
## Server Components
### ActivityWatch Server
Основной сервер для приема и хранения событий.
**Технологии:** Rust
**API:** HTTP на порту 5600
### PostgreSQL Database
Основное хранилище данных.
**Таблицы:**
- `dlp_events` - DLP инциденты
- `aggregation_state` - состояние агрегации
- `dlp_statistics` - статистика
### WebUI with RU Patches
Веб-интерфейс с русской локализацией.
**Файлы:** `aw-server/aw-ru-patch.js`, `aw-server/aw-sw-cleanup.js`
## Integration Components
### pfSense Poller
Сбор логов с pfSense firewall.
**Файл:** `pfsense/pfsense-aw-poller.py`
### DLP Aggregation Scripts
Агрегация DLP событий в PostgreSQL.
**Файл:** `scripts/aggregate_dlp_events.py`
### Prometheus Exporter
Экспорт метрик в формате Prometheus.
**Файл:** `grafana-1c/sql-exporter/collectors/aw_activitywatch.py`
## Monitoring Components
### Prometheus
Сбор и хранение метрик.
**Порт:** 9090
### Grafana
Визуализация метрик и дашборды.
**Порт:** 3000
### SQL Exporter
Прямые SQL запросы к PostgreSQL.
**Порт:** 9398
## Подробнее
- [DLP Endpoint Monitoring](DLP-Endpoint-Monitoring) - детально о DLP коллекторе
- [Browser Domains Monitoring](Browser-Domains-Monitoring) - детально о браузерном коллекторе
- [WebUI Русификация](WebUI-Russian-Patches) - детально о патчах интерфейса
-124
View File
@@ -1,124 +0,0 @@
# DLP Endpoint Monitoring
Мониторинг конечных точек для обнаружения утечек данных через clipboard, печать и USB.
## Обзор
DLP Endpoint Collector отслеживает:
- **Clipboard** - буфер обмена
- **Print** - задания на печать
- **USB** - запись на USB накопители
## Архитектура
```
User Activity → DLP Collector → Rule Evaluation → Enforcement → ActivityWatch
```
## Функции
### Clipboard Monitoring
- Перехват clipboard событий
- Проверка по паттернам (credit cards, passport data, etc.)
- Блокировка копирования
- Скриншот при нарушении
### Print Monitoring
- Перехват print job событий
- Проверка принтеров и документов
- Блокировка печати
- Логирование попыток
### USB Monitoring
- Обнаружение USB устройств
- Блокировка записи
- Логирование подключений
- Политики по device ID
## Конфигурация
### DLP Policy
```json
{
"clipboard_rules": [
{
"pattern": "\\b\\d{4}-\\d{4}-\\d{4}-\\d{4}\\b",
"description": "Credit card numbers",
"severity": "high",
"action": "block"
}
],
"print_rules": [
{
"printer_match": "*",
"document_keywords": ["confidential", "secret"],
"action": "block"
}
],
"usb_rules": [
{
"device_id": "*",
"action": "block_write"
}
]
}
```
### Deployment Config
```json
{
"aw_server_url": "http://aw-server:5600",
"bucket_prefix": "aw-watcher-dlp-endpoint",
"heartbeat_interval": 60,
"screenshot_on_incident": true,
"enforcement_enabled": true
}
```
## Установка
```powershell
# Копирование коллектора
Copy-Item windows/dlp-endpoint-signals-collector.ps1 C:\ProgramData\AWatch-rus\
# Настройка scheduled task
Register-ScheduledTask -TaskName "DLP Endpoint Collector" -Trigger $trigger -Action $action
```
## События
### DLP Incident Event
```json
{
"timestamp": "2024-01-01T12:00:00Z",
"type": "dlp_incident",
"source": "clipboard",
"rule_id": "credit_card_pattern",
"severity": "high",
"data": {
"matched_text": "****-****-****-1234",
"user": "user1",
"host": "WORKSTATION01",
"screenshot": "path/to/screenshot.png"
}
}
```
## Требования
- Windows 10/11
- PowerShell 5.1+
- ActivityWatch installed
- Административные права (для enforcement)
## Мониторинг
- Количество инцидентов по типам
- Частота срабатываний правил
- Успешность enforcement действий
- Heartbeat статус
## Подробнее
- [Детальная диаграмма](https://github.com/igor04091968/AWatch-rus/blob/main/docs/diagrams/dlp-endpoint-monitoring.md)
- [DLP Правила](DLP-Rules)
-79
View File
@@ -1,79 +0,0 @@
# ActivityWatch-Russian Documentation
Добро пожаловать в документацию ActivityWatch-Russian - корпоративной системы мониторинга активности на базе ActivityWatch с русификацией и DLP функциями.
## 📚 Содержание
### Архитектура
- [Обзор архитектуры](Architecture) - высокоуровневая архитектура системы
- [Компоненты системы](Components) - описание всех компонентов
- [Интерактивная карта](Interactive-Map) - визуальная карта связей
### Компоненты
- [DLP Endpoint Monitoring](DLP-Endpoint-Monitoring) - мониторинг clipboard, печати, USB
- [Browser Domains Monitoring](Browser-Domains-Monitoring) - мониторинг браузеров
- [Email Outbound Monitoring](Email-Outbound-Monitoring) - мониторинг почты
- [WebUI Русификация](WebUI-Russian-Patches) - патчи интерфейса
- [DLP Агрегация](DLP-Aggregation) - обработка DLP событий
- [Prometheus Exporter](Prometheus-Exporter) - метрики для мониторинга
### Развертывание
- [Установка на Windows](Windows-Installation) - установка коллекторов
- [Настройка сервера](Server-Setup) - настройка Linux сервера
- [Grafana + Prometheus](Monitoring-Setup) - мониторинг стек
### Конфигурация
- [DLP Правила](DLP-Rules) - настройка DLP политик
- [Категоризация сайтов](Web-Categorization) - настройка категорий
- [Группы хостов](Host-Groups) - управление группами
## 🚀 Быстрый старт
### Минимальная конфигурация
```bash
# 1. Установка на Windows workstation
.\windows\deploy-domain-users.ps1
# 2. Запуск ActivityWatch Server
./aw-server/aw-server
# 3. Применение RU патчей
node aw-server/aw-ru-patch.js
```
### Полная конфигурация
```bash
# 1. Развертывание на Windows
.\windows\deploy-domain-users.ps1
# 2. Настройка сервера
cd ansible
ansible-playbook server-setup.yml
# 3. Запуск мониторинга стека
cd ../grafana-1c
docker-compose up -d
# 4. Агрегация DLP событий
python3 scripts/aggregate_dlp_events.py
```
## 📊 Обзор системы
ActivityWatch-Russian - это корпоративная система мониторинга активности пользователей с:
- **DLP мониторинг** - clipboard, печать, USB, браузеры, email
- **Русификация** - полный перевод интерфейса на русский
- **Аналитика** - агрегация данных и отчеты
- **Мониторинг** - Prometheus + Grafana дашборды
- **Автоматизация** - Ansible деплой на Windows и Linux
## 🔗 Ссылки
- [GitHub Repository](https://github.com/igor04091968/AWatch-rus)
- [ActivityWatch Official](https://activitywatch.net/)
- [Примеры конфигураций](https://github.com/igor04091968/AWatch-rus/tree/main/grafana-1c)
## 📝 Поддержка
Для вопросов и предложений используйте [Issues](https://github.com/igor04091968/AWatch-rus/issues).
-78
View File
@@ -1,78 +0,0 @@
# Интерактивная карта архитектуры
Для визуального исследования связей между компонентами используйте интерактивную HTML карту.
## Как использовать
### Локально
```bash
# Открыть в браузере
xdg-open docs/diagrams/architecture-map.html
# Или
file:///path/to/AWatch-rus/docs/diagrams/architecture-map.html
```
### С GitHub
1. Скачайте файл: [architecture-map.html](https://github.com/igor04091968/AWatch-rus/blob/main/docs/diagrams/architecture-map.html)
2. Откройте в браузере
## Функции карты
### Визуальные компоненты
- **Windows Clients** (красный) - коллекторы данных
- **Linux Server** (бирюзовый) - сервер и БД
- **Integration** (желтый) - интеграции
- **Monitoring** (зеленый) - мониторинг стек
- **External** (фиолетовый) - внешние системы
### Интерактивность
- **Клик по компоненту** - показывает детальную информацию
- **Подсветка связей** - выделяет связанные компоненты
- **Поиск** - быстрый поиск по названию
- **Потоки данных** - визуализация процесса
### Информация о компоненте
При клике на компонент показывается:
- Описание
- Связи с другими компонентами
- Потоки данных
- Порты и протоколы
## Слои архитектуры
### 1. Windows Clients
- DLP Endpoint Collector
- Browser Domains Collector
- Email Outbound Collector
- Worktime Session Collector
### 2. Linux Server
- ActivityWatch Server
- PostgreSQL Database
- WebUI with RU Patches
### 3. Integration Layer
- pfSense Poller
- DLP Aggregation Scripts
- Prometheus Exporter
### 4. Monitoring Stack
- Prometheus
- Grafana
- SQL Exporter
### 5. External Systems
- pfSense Firewall
- Domain Controller
## Альтернативные визуализации
### ASCII схема
Для быстрого понимания: [architecture-simple.md](https://github.com/igor04091968/AWatch-rus/blob/main/docs/architecture-simple.md)
### Mermaid диаграммы
Для технических деталей: [architecture-diagram.md](https://github.com/igor04091968/AWatch-rus/blob/main/docs/architecture-diagram.md)
### Graphify knowledge graph
Для анализа кода: [graphify-out/index.html](https://github.com/igor04091968/AWatch-rus/blob/main/graphify-out/index.html)
-292
View File
@@ -1,292 +0,0 @@
# Monitoring Setup
Настройка Prometheus + Grafana стека для мониторинга ActivityWatch.
## Требования
- Docker & Docker Compose
- 2GB+ RAM
- 20GB+ disk space
## Установка через Docker Compose
```bash
cd grafana-1c
docker-compose up -d
```
## Компоненты
### Prometheus
Сбор и хранение метрик.
**Порт:** 9090
**Конфиг:** `prometheus/prometheus.yml`
### Grafana
Визуализация метрик и дашборды.
**Порт:** 3000
**Конфиг:** `grafana/provisioning/`
### SQL Exporter
Прямые SQL запросы к PostgreSQL.
**Порт:** 9398
**Конфиг:** `sql-exporter/`
### AW Exporter
Экспорт метрик ActivityWatch.
**Порт:** 9398
**Файл:** `sql-exporter/collectors/aw_activitywatch.py`
## Конфигурация Prometheus
```yaml
# prometheus/prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'activitywatch'
static_configs:
- targets: ['aw-exporter:9398']
scrape_interval: 60s
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
```
## Конфигурация Grafana
### Datasources
```yaml
# grafana/provisioning/datasources/prometheus.yml
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
```
```yaml
# grafana/provisioning/datasources/postgres.yml
apiVersion: 1
datasources:
- name: PostgreSQL
type: postgres
access: proxy
url: postgres:5432
database: activitywatch
user: aw
password: aw_password
```
### Dashboards
```yaml
# grafana/provisioning/dashboards/dashboards.yml
apiVersion: 1
providers:
- name: 'ActivityWatch'
orgId: 1
folder: 'ActivityWatch'
type: file
disableDeletion: false
updateIntervalSeconds: 10
allowUiUpdates: true
options:
path: /etc/grafana/provisioning/dashboards
```
## Дашборды
### ActivityWatch Overview
- Активные хосты
- События по buckets
- Скорость поступления событий
- Статус коллекторов
**Файл:** `grafana/grafana/dashboards/aw_overview.json`
### DLP Incidents
- Количество инцидентов по типам
- Инциденты по пользователям
- Инциденты по серьезности
- Временные тренды
### Browser Monitoring
- Посещаемые домены
- Категории сайтов
- DLP нарушения
## Запуск
```bash
# Запуск всех сервисов
docker-compose up -d
# Проверка статуса
docker-compose ps
# Просмотр логов
docker-compose logs -f
# Остановка
docker-compose down
```
## Доступ
- **Grafana:** http://localhost:3000 (admin/admin)
- **Prometheus:** http://localhost:9090
- **AW Exporter:** http://localhost:9398/metrics
## Настройка алертов
### Prometheus Alerting Rules
```yaml
groups:
- name: activitywatch_alerts
rules:
- alert: AWCollectorDown
expr: aw_host_active == 0
for: 5m
labels:
severity: warning
annotations:
summary: "ActivityWatch collector down on {{ $labels.host }}"
- alert: AWExporterDown
expr: aw_exporter_up == 0
for: 2m
labels:
severity: critical
annotations:
summary: "ActivityWatch exporter is down"
```
### Grafana Notification Channels
1. Откройте Grafana
2. Configuration → Alerting → Notification channels
3. Add new channel (Email, Slack, etc.)
4. Настройте правила алертов в дашбордах
## Мониторинг
### Ключевые метрики
**ActivityWatch:**
- `aw_bucket_events_total` - общее количество событий
- `aw_host_active` - активность хостов
- `aw_exporter_scrape_duration_seconds` - время scrape
**Prometheus:**
- `up` - статус целей
- `scrape_duration_seconds` - время сбора
**Grafana:**
- `grafana_statistic` - статистика Grafana
### Health Checks
```bash
# Prometheus
curl http://localhost:9090/-/healthy
# Grafana
curl http://localhost:3000/api/health
# AW Exporter
curl http://localhost:9398/health
```
## Backup
```bash
# Backup Grafana dashboards
docker exec grafana grafana-cli admin export-dashboard > dashboards_backup.json
# Backup Prometheus data
docker exec prometheus tar -czf /tmp/prometheus_data.tar.gz /prometheus
docker cp prometheus:/tmp/prometheus_data.tar.gz ./backup/
```
## Устранение проблем
### Grafana не доступна
```bash
# Проверьте контейнер
docker-compose ps grafana
# Проверьте логи
docker-compose logs grafana
# Перезапустите
docker-compose restart grafana
```
### Prometheus не собирает метрики
```bash
# Проверьте конфиг
docker exec prometheus promtool check config /etc/prometheus/prometheus.yml
# Проверьте цели
curl http://localhost:9090/api/v1/targets
# Проверьте логи
docker-compose logs prometheus
```
### AW Exporter не работает
```bash
# Проверьте соединение с AW Server
docker exec aw-exporter curl http://aw-server:5600/api/0/buckets
# Проверьте логи
docker-compose logs aw-exporter
# Проверьте метрики
curl http://localhost:9398/metrics
```
## Производительность
### Оптимизация Prometheus
```yaml
# prometheus/prometheus.yml
global:
scrape_interval: 30s # Увеличьте интервал
evaluation_interval: 30s
storage:
tsdb:
retention.time: 30d # Уменьшите retention
```
### Оптимизация Grafana
```bash
# Увеличьте память
# docker-compose.yml
grafana:
environment:
- GF_INSTALL_PLUGINS=
- GF_SERVER_ROOT_URL=http://localhost:3000
- GF_ANALYTICS_ENABLED=false
```
## Подробнее
- [Prometheus Exporter](Prometheus-Exporter)
- [Компоненты](Components)
-248
View File
@@ -1,248 +0,0 @@
# Настройка сервера
Настройка Linux сервера для ActivityWatch-Russian.
## Требования
- Linux (Ubuntu 20.04+, Debian 11+, CentOS 8+)
- Python 3.8+
- PostgreSQL 12+
- 4GB+ RAM
- 50GB+ disk space
## Установка через Ansible
```bash
cd ansible
ansible-playbook server-setup.yml
```
## Ручная установка
### 1. Установка ActivityWatch Server
```bash
# Скачайте binaries
wget https://github.com/ActivityWatch/activitywatch/releases/download/v0.13.1/activitywatch-v0.13.1-linux-x86_64.zip
# Распакуйте
unzip activitywatch-v0.13.1-linux-x86_64.zip
cd activitywatch-v0.13.1-linux-x86_64
# Запустите сервер
./aw-server
```
### 2. Установка PostgreSQL
```bash
# Ubuntu/Debian
sudo apt update
sudo apt install postgresql postgresql-contrib
# CentOS/RHEL
sudo yum install postgresql-server postgresql-contrib
sudo postgresql-setup initdb
sudo systemctl start postgresql
sudo systemctl enable postgresql
# Создайте базу данных
sudo -u postgres psql
CREATE DATABASE activitywatch;
CREATE USER aw WITH PASSWORD 'your_password';
GRANT ALL PRIVILEGES ON DATABASE activitywatch TO aw;
\q
```
### 3. Применение RU патчей
```bash
# Скопируйте патчи
cp aw-server/aw-ru-patch.js /path/to/aw-server/
cp aw-server/aw-sw-cleanup.js /path/to/aw-server/
# Примените патчи
cd /path/to/aw-server/
node aw-ru-patch.js
```
### 4. Настройка systemd service
```bash
sudo nano /etc/systemd/system/aw-server.service
```
```ini
[Unit]
Description=ActivityWatch Server
After=network.target
[Service]
Type=simple
User=aw
WorkingDirectory=/opt/activitywatch
ExecStart=/opt/activitywatch/aw-server
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
```
```bash
sudo systemctl enable aw-server
sudo systemctl start aw-server
```
## Конфигурация
### ActivityWatch Config
```toml
# /opt/activitywatch/server-config.toml
[server]
host = "0.0.0.0"
port = 5600
[logging]
level = "INFO"
file = "/var/log/activitywatch/aw-server.log"
```
### PostgreSQL Config
```bash
# /etc/postgresql/12/main/postgresql.conf
listen_addresses = '*'
max_connections = 100
shared_buffers = 256MB
```
## Установка Python зависимостей
```bash
# Создайте virtual environment
python3 -m venv /opt/aw-venv
source /opt/aw-venv/bin/activate
# Установите зависимости
pip install psycopg2-binary requests prometheus_client
```
## Настройка скриптов агрегации
```bash
# Скопируйте скрипты
cp scripts/aggregate_dlp_events.py /opt/activitywatch/
# Настройте cron job
crontab -e
```
```cron
*/5 * * * * /opt/aw-venv/bin/python /opt/activitywatch/aggregate_dlp_events.py
```
## Firewall
```bash
# Ubuntu/Debian (ufw)
sudo ufw allow 5600/tcp
sudo ufw allow 5432/tcp
sudo ufw enable
# CentOS/RHEL (firewalld)
sudo firewall-cmd --permanent --add-port=5600/tcp
sudo firewall-cmd --permanent --add-port=5432/tcp
sudo firewall-cmd --reload
```
## Проверка
```bash
# Проверка ActivityWatch Server
curl http://localhost:5600/api/0/buckets
# Проверка PostgreSQL
sudo -u postgres psql -c "SELECT version();"
# Проверка логов
sudo journalctl -u aw-server -f
```
## Мониторинг
```bash
# Проверка статуса
sudo systemctl status aw-server
# Проверка ресурсов
htop
# Проверка диска
df -h
# Проверка PostgreSQL
sudo -u postgres psql -c "SELECT pg_stat_database.datname, pg_stat_database.numbackends FROM pg_stat_database;"
```
## Backup
```bash
# Backup PostgreSQL
sudo -u postgres pg_dump activitywatch > backup_$(date +%Y%m%d).sql
# Backup ActivityWatch data
sudo tar -czf aw-backup_$(date +%Y%m%d).tar.gz /opt/activitywatch/data
# Restore
sudo -u postgres psql activitywatch < backup_20240101.sql
```
## Обновление
```bash
# Остановите сервер
sudo systemctl stop aw-server
# Backup
sudo cp -r /opt/activitywatch /opt/activitywatch.backup
# Обновите binaries
wget https://github.com/ActivityWatch/activitywatch/releases/download/v0.14.0/activitywatch-v0.14.0-linux-x86_64.zip
unzip activitywatch-v0.14.0-linux-x86_64.zip -d /opt/activitywatch/
# Запустите сервер
sudo systemctl start aw-server
```
## Устранение проблем
### Server не запускается
```bash
# Проверьте логи
sudo journalctl -u aw-server -n 50
# Проверьте порт
sudo netstat -tlnp | grep 5600
# Проверьте права
sudo ls -la /opt/activitywatch
```
### PostgreSQL connection refused
```bash
# Проверьте статус
sudo systemctl status postgresql
# Проверьте конфиг
sudo cat /etc/postgresql/12/main/postgresql.conf | grep listen_addresses
# Проверьте firewall
sudo ufw status
```
## Подробнее
- [Мониторинг стек](Monitoring-Setup)
- [DLP Агрегация](DLP-Aggregation)
-131
View File
@@ -1,131 +0,0 @@
# WebUI Русификация
Система патчей для русификации и адаптации ActivityWatch WebUI под российские требования.
## Обзор
WebUI патчи добавляют:
- Полный перевод интерфейса на русский
- DLP центр для просмотра инцидентов
- Центр управления правилами
- Группы хостов
- Русские стили и шрифты
## Архитектура
```
Original WebUI → Patch Loader → DOM Injection → Style Injection → Translation → RU WebUI
```
## Компоненты патчей
### Navigation Patches
- Скрытие лишних пунктов меню
- Добавление DLP навигации
- Обновление ссылок
### DLP Center Injection
- Центр просмотра инцидентов
- Менеджер правил
- Центр алертов
- Архивирование
### Host Groups Center
- Управление группами хостов
- Визуализация по группам
- Агрегированная статистика
### Translation
- Перевод всех текстовых элементов
- Русские форматы дат/чисел
- Кириллические шрифты
## Установка
```bash
# Применение патчей
node aw-server/aw-ru-patch.js
# Очистка старых патчей
node aw-server/aw-sw-cleanup.js
```
## Конфигурация
### Host Groups Config
```json
{
"host_groups": [
{
"id": "workstations",
"name": "Рабочие станции",
"hosts": ["WORKSTATION01", "WORKSTATION02"],
"color": "#4ecdc4"
},
{
"id": "servers",
"name": "Серверы",
"hosts": ["SERVER01", "SERVER02"],
"color": "#ff6b6b"
}
]
}
```
### DLP Settings
```json
{
"dlp_enabled": true,
"review_center_enabled": true,
"alerts_center_enabled": true,
"auto_refresh_interval": 30,
"default_severity_filter": "all"
}
```
## Функции WebUI
### DLP Review Center
- Список инцидентов
- Фильтрация по типам
- Детальный просмотр
- Архивирование
- Экспорт
### DLP Rules Manager
- Список правил
- Создание/редактирование
- Включение/выключение
- Тестирование правил
### Host Groups Center
- Управление группами
- Визуализация
- Статистика по группам
## Совместимость
- ActivityWatch v0.12.x - v0.14.x
- Chrome 90+, Firefox 88+, Edge 90+, Safari 14+
## Производительность
- Lazy loading патчей
- Кэширование переводов
- Debouncing обновлений DOM
- Virtual scrolling
## Отладка
```javascript
// Режим разработки
const DEBUG_MODE = true;
// Логирование патчей
console.log('[AW-RU-Patch] Applying patch:', patchName);
```
## Подробнее
- [Детальная диаграмма](https://github.com/igor04091968/AWatch-rus/blob/main/docs/diagrams/webui-patches.md)
- [Группы хостов](Host-Groups)
-172
View File
@@ -1,172 +0,0 @@
# Установка на Windows
Установка ActivityWatch коллекторов на Windows рабочие станции.
## Требования
- Windows 10/11
- PowerShell 5.1+
- Административские права
- Доступ к ActivityWatch Server
## Автоматическая установка
### Через Domain Controller (GPO)
```powershell
# На Domain Controller
.\windows\deploy-domain-users.ps1
```
Этот скрипт:
1. Создает GPO для развертывания
2. Копирует коллекторы на рабочие станции
3. Настраивает scheduled tasks
4. Конфигурирует DLP политики
### Для одиночного пользователя
```powershell
# Локально на workstation
.\windows\deploy-single-user.ps1
```
## Ручная установка
### 1. Установка ActivityWatch
```powershell
# Скачайте installer с сайта ActivityWatch
# Запустите installer
# Или через InnoSetup installer
.\install-kit-awindows-*.exe
```
### 2. Копирование коллекторов
```powershell
# Создайте директорию
New-Item -ItemType Directory -Path "C:\ProgramData\AWatch-rus" -Force
# Скопируйте коллекторы
Copy-Item windows\dlp-endpoint-signals-collector.ps1 "C:\ProgramData\AWatch-rus\"
Copy-Item windows\browser-domains-native-collector.ps1 "C:\ProgramData\AWatch-rus\"
Copy-Item windows\email-outbound-collector.ps1 "C:\ProgramData\AWatch-rus\"
```
### 3. Настройка конфигурации
```powershell
# Создайте конфигурационный файл
@{
aw_server_url = "http://aw-server:5600"
bucket_prefix = "aw-watcher-dlp-endpoint"
heartbeat_interval = 60
} | ConvertTo-Json | Out-File "C:\ProgramData\AWatch-rus\deployment-config.json"
```
### 4. Настройка Scheduled Task
```powershell
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 5)
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-File C:\ProgramData\AWatch-rus\dlp-endpoint-signals-collector.ps1"
Register-ScheduledTask -TaskName "DLP Endpoint Collector" -Trigger $trigger -Action $action -RunLevel Highest
```
## Конфигурация DLP политик
### Создание файла политик
```powershell
@{
clipboard_rules = @(
@{
pattern = "\b\d{4}-\d{4}-\d{4}-\d{4}\b"
description = "Credit card numbers"
severity = "high"
action = "block"
}
)
print_rules = @(
@{
printer_match = "*"
document_keywords = @("confidential", "secret")
action = "block"
}
)
} | ConvertTo-Json -Depth 10 | Out-File "C:\ProgramData\AWatch-rus\dlp-policy.json"
```
## Проверка установки
```powershell
# Проверка статуса службы
Get-ScheduledTask | Where-Object {$_.TaskName -like "*DLP*"}
# Проверка логов
Get-Content "C:\ProgramData\AWatch-rus\logs\*.log" -Tail 50
# Проверка соединения с сервером
Test-NetConnection -ComputerName aw-server -Port 5600
```
## Устранение проблем
### Коллектор не запускается
```powershell
# Проверьте execution policy
Get-ExecutionPolicy
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
# Проверьте права
# Запустите PowerShell от администратора
```
### Нет соединения с сервером
```powershell
# Проверьте firewall
New-NetFirewallRule -DisplayName "Allow AW Server" -Direction Outbound -Protocol TCP -RemotePort 5600 -Action Allow
# Проверьте URL в конфиге
Get-Content "C:\ProgramData\AWatch-rus\deployment-config.json"
```
### DLP не работает
```powershell
# Проверьте файл политик
Test-Path "C:\ProgramData\AWatch-rus\dlp-policy.json"
# Проверьте формат JSON
Get-Content "C:\ProgramData\AWatch-rus\dlp-policy.json" | ConvertFrom-Json
```
## Обновление
```powershell
# Остановите задачи
Unregister-ScheduledTask -TaskName "DLP Endpoint Collector" -Confirm:$false
# Обновите файлы
Copy-Item windows\dlp-endpoint-signals-collector.ps1 "C:\ProgramData\AWatch-rus\" -Force
# Перезапустите задачи
Register-ScheduledTask -TaskName "DLP Endpoint Collector" -Trigger $trigger -Action $action
```
## Удаление
```powershell
# Остановите задачи
Get-ScheduledTask | Where-Object {$_.TaskName -like "*DLP*"} | Unregister-ScheduledTask -Confirm:$false
# Удалите файлы
Remove-Item "C:\ProgramData\AWatch-rus" -Recurse -Force
# Удалите ActivityWatch (через Programs and Features)
```
## Подробнее
- [DLP Правила](DLP-Rules)
- [Компоненты](Components)
+27 -67
View File
@@ -8,37 +8,25 @@
- `windows/hardening-recovery.ps1` — повторная регистрация задач, ACL и recovery-loop.
- `windows/validate-deployment.ps1` — машинная проверка состояния и JSON-отчёт.
- `windows/browser-domains-native-collector.ps1` — native collector доменов браузера с категоризацией.
- `windows/dlp-endpoint-signals-collector.ps1`Windows/RDP collector (clipboard/USB/print signals).
- `windows/file-operations-collector.ps1` — collector файловых операций (create/delete/rename/archive hints).
- `windows/worktime-session-collector.ps1` — collector RDP-сессий и активности.
- `windows/install-standalone-service.ps1` — standalone установка агента как Windows Service (без Task Scheduler).
- `windows/aw-standalone-service.ps1` — service wrapper для поддержания collector-процессов.
- `windows/dlp-endpoint-signals-collector.ps1`phase-2 collector (clipboard/USB/print signals).
- `windows/web-category-rules.example.json` — пример кастомных правил категоризации.
- `windows/dlp-policy.example.json` — пример DLP-политики (phase-1: alerting incidents).
## Что делает пакет
- Ставит `aw-watcher-afk` и `aw-watcher-window` из официального Windows ZIP ActivityWatch.
- Копирует browser-domain collector в `C:\ProgramData\AWatch-rus`.
- Копирует DLP policy в `C:\ProgramData\AWatch-rus\dlp-policy.json`.
- Копирует browser-domain collector в `C:\ProgramData\ActivityWatch`.
- Копирует DLP policy в `C:\ProgramData\ActivityWatch\dlp-policy.json`.
- Включает `incidentCapture` в `deployment-config.json` для DLP-инцидентов:
- `incidentCapture.screenshotEnabled = true`
- `incidentCapture.artifactsRoot = <StateRoot>\incident-artifacts`
- Создаёт per-user задачи `ActivityWatch Launch [...]` с запуском при логоне.
- Создаёт системную задачу `ActivityWatch Recovery`, которая циклически перезапускает per-user launch tasks.
- Применяет ACL к `C:\Program Files\AWatch-rus\bin`, `C:\ProgramData\AWatch-rus` и каталогу логов.
- Применяет ACL к `C:\Program Files\ActivityWatch`, `C:\ProgramData\ActivityWatch` и каталогу логов.
- Не содержит хардкодов инфраструктуры: сервер, домен, список пользователей и правила передаются параметрами.
- Корректно регистрирует задачи через `-LogonType Interactive` (совместимо с Windows Server, где `InteractiveToken` не поддерживается).
- Поддерживает отключение шумных watcher'ов через `-AfkEnabled:$false` и `-WindowEnabled:$false`.
### Standalone InnoSetup (без Ansible, без Task Scheduler)
- InnoSetup запускает `install-standalone-service.ps1`.
- Мастер спрашивает только `ServerHost` и `ServerPort`.
- Создаётся сервис `AWatchRusStandaloneAgent` (auto-start, restart-on-failure).
- Сервис управляет collector-скриптами и держит по одной рабочей копии каждого коллектора.
- `deployment-config.json` формируется в `C:\ProgramData\AWatch-rus\deployment-config.json`.
Важно:
- Скриншот делается только при DLP-инциденте (`Send-DlpIncidentHeartbeat`), не по таймеру и не на обычной активности.
@@ -155,37 +143,9 @@ CSV-формат: колонка `User`, `Username`, `SamAccountName` или `Lo
Если список уже содержит `DOMAIN\user`, параметр `-Domain` не нужен.
## Безопасная миграция текущего production
## Рекомендуемый phased rollout (изолированный профиль)
Если текущий 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
Для запуска после миграции используйте единые пути:
Для безопасного параллельного запуска рядом с legacy-инсталляцией используйте отдельные пути:
```powershell
.\windows\deploy-domain-users.ps1 `
@@ -193,10 +153,10 @@ Ansible playbook `ansible/deploy_aw_windows.yml` выполняет этот mig
-ServerPort 5600 `
-Domain SHARKON2025 `
-Users user2,user3,user4,user5 `
-InstallRoot 'C:\Program Files\AWatch-rus\bin' `
-StateRoot 'C:\ProgramData\AWatch-rus' `
-CustomRulesPath C:\Program Files\AWatch-rus\windows\web-category-rules.example.json `
-CustomPolicyPath C:\Program Files\AWatch-rus\windows\dlp-policy.example.json
-InstallRoot 'C:\Program Files\ActivityWatch-Phase2-u2u5' `
-StateRoot 'C:\ProgramData\ActivityWatch-Phase2-u2u5' `
-CustomRulesPath C:\Deploy\AWatch-rus\windows\web-category-rules.example.json `
-CustomPolicyPath C:\Deploy\AWatch-rus\windows\dlp-policy.example.json
```
Single-user pilot в таком же стиле:
@@ -206,10 +166,10 @@ Single-user pilot в таком же стиле:
-ServerHost 10.10.10.13 `
-ServerPort 5600 `
-TargetUser 'SHARKON2025\user1' `
-InstallRoot 'C:\Program Files\AWatch-rus\bin' `
-StateRoot 'C:\ProgramData\AWatch-rus' `
-CustomRulesPath C:\Program Files\AWatch-rus\windows\web-category-rules.example.json `
-CustomPolicyPath C:\Program Files\AWatch-rus\windows\dlp-policy.example.json
-InstallRoot 'C:\Program Files\ActivityWatch-Phase2' `
-StateRoot 'C:\ProgramData\ActivityWatch-Phase2-user1' `
-CustomRulesPath C:\Deploy\AWatch-rus\windows\web-category-rules.example.json `
-CustomPolicyPath C:\Deploy\AWatch-rus\windows\dlp-policy.example.json
```
## Ensemble deploy (production workflow)
@@ -226,31 +186,31 @@ Single-user pilot в таком же стиле:
Итоговый отчёт:
- `C:\ProgramData\AWatch-rus\ensemble-report-YYYYMMDD-HHMMSS.json`
- `C:\ProgramData\ActivityWatch\ensemble-report-YYYYMMDD-HHMMSS.json`
## Категоризация доменов
- Встроенные категории покрывают базовые рабочие, нейтральные и личные домены.
- Для кастомизации скопируйте `windows/web-category-rules.example.json` и отредактируйте домены.
- Передайте файл через `-CustomRulesPath`; он будет сохранён как `C:\ProgramData\AWatch-rus\web-category-rules.json`.
- Передайте файл через `-CustomRulesPath`; он будет сохранён как `C:\ProgramData\ActivityWatch\web-category-rules.json`.
- Пользовательские правила имеют приоритет над встроенными.
## Структура после установки
- `C:\Program Files\AWatch-rus\bin` — бинарники watcher'ов.
- `C:\ProgramData\AWatch-rus\deployment-config.json` — итоговая конфигурация.
- `C:\ProgramData\AWatch-rus\incident-artifacts\` — скриншоты DLP-инцидентов (если `incidentCapture.screenshotEnabled=true`).
- `C:\ProgramData\AWatch-rus\launch-watchers.ps1` — per-user launcher.
- `C:\ProgramData\AWatch-rus\recovery-loop.ps1` — system recovery loop.
- `C:\ProgramData\AWatch-rus\browser-domains-native-collector.ps1` — runtime collector.
- `C:\ProgramData\AWatch-rus\dlp-endpoint-signals-collector.ps1` — runtime endpoint collector.
- `C:\ProgramData\AWatch-rus\dlp-policy.json` — активная DLP-политика.
- `C:\ProgramData\AWatch-rus\logs\` — логи collector'а.
- `C:\Program Files\ActivityWatch` — бинарники watcher'ов.
- `C:\ProgramData\ActivityWatch\deployment-config.json` — итоговая конфигурация.
- `C:\ProgramData\ActivityWatch\incident-artifacts\` — скриншоты DLP-инцидентов (если `incidentCapture.screenshotEnabled=true`).
- `C:\ProgramData\ActivityWatch\launch-watchers.ps1` — per-user launcher.
- `C:\ProgramData\ActivityWatch\recovery-loop.ps1` — system recovery loop.
- `C:\ProgramData\ActivityWatch\browser-domains-native-collector.ps1` — runtime collector.
- `C:\ProgramData\ActivityWatch\dlp-endpoint-signals-collector.ps1` — runtime endpoint collector.
- `C:\ProgramData\ActivityWatch\dlp-policy.json` — активная DLP-политика.
- `C:\ProgramData\ActivityWatch\logs\` — логи collector'а.
При переопределении `StateRoot` те же файлы формируются в указанном каталоге.
Для phased rollout те же файлы формируются в каталоге `StateRoot`, переданном параметром.
## Повторный прогон
- Скрипты идемпотентны: переустанавливают задачи и обновляют runtime-файлы.
- Предыдущая установка ActivityWatch бэкапится в `C:\ProgramData\AWatch-rus\backups\install-YYYYMMDD-HHMMSS`.
- Предыдущая установка ActivityWatch бэкапится в `C:\ProgramData\ActivityWatch\backups\install-YYYYMMDD-HHMMSS`.
- Для жёсткого восстановления запускайте `windows/hardening-recovery.ps1`.
+6 -6
View File
@@ -17,15 +17,15 @@
```powershell
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process
C:\Program Files\AWatch-rus\windows\deploy-ensemble.ps1 `
C:\Deploy\AWatch-rus\windows\deploy-ensemble.ps1 `
-ServerHost 10.10.10.13 `
-ServerPort 5600 `
-Domain SHARKON2025 `
-Users user1,user2,user3,user4,user5 `
-InstallRoot 'C:\Program Files\AWatch-rus\bin' `
-StateRoot 'C:\ProgramData\AWatch-rus' `
-InstallRoot 'C:\Program Files\ActivityWatch-Phase2' `
-StateRoot 'C:\ProgramData\ActivityWatch-Phase2' `
-AfkEnabled:$false `
-CustomPolicyPath C:\Program Files\AWatch-rus\windows\dlp-policy.example.json `
-CustomPolicyPath C:\Deploy\AWatch-rus\windows\dlp-policy.example.json `
-ValidateAfterDeploy
```
@@ -42,8 +42,8 @@ C:\Program Files\AWatch-rus\windows\deploy-ensemble.ps1 `
## Быстрый health-check
```powershell
$report = C:\Program Files\AWatch-rus\windows\validate-deployment.ps1 `
-ConfigPath C:\ProgramData\AWatch-rus\deployment-config.json
$report = C:\Deploy\AWatch-rus\windows\validate-deployment.ps1 `
-ConfigPath C:\ProgramData\ActivityWatch-Phase2\deployment-config.json
$report | ConvertTo-Json -Depth 12
```
@@ -1,7 +0,0 @@
# Moved
Документ перенесён в новую структуру install-kit:
- `windows/installkit/innosetup/innosetup-rdp-package-filelist.md`
Этот файл оставлен как совместимый указатель, чтобы не ломать существующие ссылки в документации/автоматизации.
+3 -32
View File
@@ -23,7 +23,7 @@ Start-ScheduledTask -TaskName 'ActivityWatch Launch [CONTOSO_user01]'
- Скрипт работает через UI Automation и foreground window.
- Некоторые браузеры/страницы могут скрывать адресную строку или блокировать UIA.
- Проверьте лог `C:\ProgramData\AWatch-rus\logs\browser-domains-<user>.log`.
- Проверьте лог `C:\ProgramData\ActivityWatch\logs\browser-domains-<user>.log`.
- Убедитесь, что активное окно — поддерживаемый браузер: Edge, Chrome, Brave, Vivaldi, Opera, Firefox.
### Сервер недоступен
@@ -44,7 +44,7 @@ Invoke-WebRequest http://aw.example.local:5600/api/0/info
### Неправильная категоризация домена
- Проверьте содержимое `C:\ProgramData\AWatch-rus\web-category-rules.json`.
- Проверьте содержимое `C:\ProgramData\ActivityWatch\web-category-rules.json`.
- Пользовательские правила должны быть валидным JSON.
- Один и тот же домен лучше определять только в одной категории.
- После изменения правил достаточно перезапустить collector или задачу пользователя:
@@ -88,35 +88,6 @@ Start-ScheduledTask -TaskName 'ActivityWatch Launch [CONTOSO_user01]'
## Диагностика
### Standalone service не работает
Проверить сервис:
```powershell
Get-Service AWatchRusStandaloneAgent
sc.exe query AWatchRusStandaloneAgent
```
Перезапуск:
```powershell
Restart-Service AWatchRusStandaloneAgent
```
Лог service wrapper:
```powershell
Get-Content C:\ProgramData\AWatch-rus\logs\standalone-agent-service.log -Tail 200
```
Проверить дочерние collector-процессы:
```powershell
Get-CimInstance Win32_Process |
Where-Object { $_.Name -eq 'powershell.exe' -and $_.CommandLine -like '*AWatch-rus*collector*.ps1*' } |
Select-Object ProcessId, SessionId, CommandLine
```
Проверить задачи:
```powershell
@@ -141,7 +112,7 @@ Get-CimInstance Win32_Process |
Проверить конфиг:
```powershell
Get-Content C:\ProgramData\AWatch-rus\deployment-config.json -Raw
Get-Content C:\ProgramData\ActivityWatch\deployment-config.json -Raw
```
## Когда запускать hardening/recovery
+13 -13
View File
@@ -4,7 +4,7 @@
```powershell
$report = .\windows\validate-deployment.ps1 `
-ConfigPath C:\ProgramData\AWatch-rus\deployment-config.json
-ConfigPath C:\ProgramData\ActivityWatch\deployment-config.json
$report | ConvertTo-Json -Depth 12
```
@@ -19,11 +19,11 @@ $report | ConvertTo-Json -Depth 12
### 1. Проверить установленные файлы
```powershell
Test-Path 'C:\Program Files\AWatch-rus\bin\aw-watcher-afk\aw-watcher-afk.exe'
Test-Path 'C:\Program Files\AWatch-rus\bin\aw-watcher-window\aw-watcher-window.exe'
Test-Path 'C:\ProgramData\AWatch-rus\browser-domains-native-collector.ps1'
Test-Path 'C:\ProgramData\AWatch-rus\dlp-policy.json'
Test-Path 'C:\ProgramData\AWatch-rus\deployment-config.json'
Test-Path 'C:\Program Files\ActivityWatch\aw-watcher-afk\aw-watcher-afk.exe'
Test-Path 'C:\Program Files\ActivityWatch\aw-watcher-window\aw-watcher-window.exe'
Test-Path 'C:\ProgramData\ActivityWatch\browser-domains-native-collector.ps1'
Test-Path 'C:\ProgramData\ActivityWatch\dlp-policy.json'
Test-Path 'C:\ProgramData\ActivityWatch\deployment-config.json'
```
Ожидаемый результат — везде `True`.
@@ -50,7 +50,7 @@ Get-ScheduledTask | Where-Object TaskName -eq 'ActivityWatch Recovery'
### 2.1 Проверить incidentCapture в конфиге
```powershell
$cfg = Get-Content 'C:\ProgramData\AWatch-rus\deployment-config.json' -Raw | ConvertFrom-Json
$cfg = Get-Content 'C:\ProgramData\ActivityWatch\deployment-config.json' -Raw | ConvertFrom-Json
$cfg.incidentCapture
```
@@ -119,7 +119,7 @@ Invoke-WebRequest http://aw.example.local:5600/api/0/buckets | Select-Object -Ex
4. Проверьте локальный лог:
```powershell
Get-Content "C:\ProgramData\AWatch-rus\logs\dlp-incidents-$env:USERNAME.log" -Tail 50
Get-Content "C:\ProgramData\ActivityWatch\logs\dlp-incidents-$env:USERNAME.log" -Tail 50
```
Если `screenshotEnabled = True`, проверьте наличие скриншота в инциденте:
@@ -175,7 +175,7 @@ Invoke-RestMethod -Method Post `
- в API появился `ruleId=selftest-dlp-incident`;
- в UI (`#/buckets/aw-dlp-incidents_<hostname>`) событие видно в `Events`.
## Проверка endpoint signals
## Проверка phase-2 endpoint signals
1. Скопируйте любой текст в буфер обмена.
2. Отправьте тестовую печать (любой принтер/виртуальный PDF).
@@ -188,7 +188,7 @@ Invoke-WebRequest http://aw.example.local:5600/api/0/buckets/aw-dlp-endpoint-sig
4. Проверьте локальный лог:
```powershell
Get-Content "C:\ProgramData\AWatch-rus\logs\endpoint-signals-$env:USERNAME.log" -Tail 50
Get-Content "C:\ProgramData\ActivityWatch\logs\endpoint-signals-$env:USERNAME.log" -Tail 50
```
## Проверка восстановления
@@ -206,9 +206,9 @@ Start-ScheduledTask -TaskName 'ActivityWatch Recovery'
## Проверка ACL
```powershell
icacls 'C:\Program Files\AWatch-rus\bin'
icacls 'C:\ProgramData\AWatch-rus'
icacls 'C:\ProgramData\AWatch-rus\logs'
icacls 'C:\Program Files\ActivityWatch'
icacls 'C:\ProgramData\ActivityWatch'
icacls 'C:\ProgramData\ActivityWatch\logs'
```
Ожидаемо:
-25
View File
@@ -36,10 +36,6 @@ RETURN = sort_by_duration(work);
Требует, чтобы на клиенте работал browser collector и писал в:
`aw-detmir-web-category_<HOST>` поля `categoryGroup`, `rootDomain`.
Для Linux-удалёнщиков это может быть не URL-level collector, а title/class-based web-category logger.
Например, работа через Proxmox Web UI `https://...:8006` может попадать сюда как
`rootDomain=proxmox-webui`, `categoryGroup=work`, `category=Администрирование`.
```javascript
web = flood(query_bucket("aw-detmir-web-category_SHARKON2025"));
not_afk = flood(query_bucket("aw-watcher-afk_SHARKON2025"));
@@ -72,24 +68,3 @@ RETURN = sort_by_duration(events);
- Если web-поток пустой, рабочее время в браузере корректно посчитать по доменам не получится. Тогда либо:
- чинить/запускать browser collector;
- либо временно считать браузер в `window` как «Интернет/Браузер» без разделения на work/personal.
## Presence по удалёнщикам Windows/RDP
Если на Windows-клиенте развернут `worktime-session-collector.ps1`, то появляется bucket
`aw-worktime-sessions_<HOST>` с heartbeat по `quser`/RDP session state.
Это не замена `afk/window`, а отдельный канал для ответа на вопрос:
«кто и когда вообще был в активной удалённой сессии».
```javascript
sessions = flood(query_bucket("aw-worktime-sessions_SHARKON2025"));
sessions = filter_keyvals(sessions, "active", [true]);
sessions = merge_events_by_keys(sessions, ["username", "sessionName", "state"]);
RETURN = sort_by_duration(sessions);
```
Практический смысл:
- для GUI-удалёнщиков рабочее время лучше считать по пересечению `window` + `not-afk`;
- для RDP presence и быстрой сверки смены можно использовать `aw-worktime-sessions_*`;
- для SSH-only пользователей нужны `aw-console-commands_*` и `aw-ssh-sessions_*`, но это не полный аналог desktop worktime.
-5
View File
@@ -4,9 +4,4 @@ GRAFANA_ADMIN_PASSWORD=change_me_now
GRAFANA_PORT=3000
PROMETHEUS_PORT=9090
SQL_EXPORTER_PORT=9399
AW_EXPORTER_PORT=9398
AW_SERVER_HOST=10.10.10.13
AW_SERVER_PORT=5600
AW_SERVER_SCHEME=http
AW_SCRAPE_INTERVAL_SECONDS=30
ONEC_DSN=postgres://onec_reader:change_me@10.10.10.20:5432/onec_db?sslmode=disable
+21 -29
View File
@@ -3,46 +3,44 @@
Готовый каркас для непрерывного сбора KPI из 1С и анализа в Grafana:
- `sql-exporter` читает SQL-представления KPI из БД 1С;
- `aw-exporter` собирает метрики ActivityWatch и отдает их Prometheus;
- `prometheus` собирает метрики и применяет alert-rules;
- `grafana` поднимает datasource и дашборд автоматически.
## Полные пути
- `./.env.example`
- `./docker-compose.yml`
- `./sql-exporter/sql_exporter.yml`
- `./sql-exporter/collectors/onec_accounting_kpi.collector.yml`
- `./prometheus/prometheus.yml`
- `./prometheus/alerts.yml`
- `./prometheus/recording_rules.yml`
- `./grafana/dashboards/1c-accounting-overview.json`
- `./grafana/dashboards/1c-accounting-sre.json`
- `./sql/postgres_views_template.sql`
- `./sql/mssql_views_template.sql`
- `./tools/discover_postgres_1c.sh`
- `./tools/validate_kpi_views.sh`
- `./tools/check_pipeline.sh`
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/.env.example`
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/docker-compose.yml`
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/sql-exporter/sql_exporter.yml`
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/sql-exporter/collectors/onec_accounting_kpi.collector.yml`
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/prometheus/prometheus.yml`
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/prometheus/alerts.yml`
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/prometheus/recording_rules.yml`
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/grafana/dashboards/1c-accounting-overview.json`
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/grafana/dashboards/1c-accounting-sre.json`
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/sql/postgres_views_template.sql`
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/sql/mssql_views_template.sql`
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/tools/discover_postgres_1c.sh`
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/tools/validate_kpi_views.sh`
- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/tools/check_pipeline.sh`
## Быстрый запуск
1. Подготовьте env:
```bash
cd grafana-1c
cd /mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c
cp .env.example .env
```
2. В `.env` задайте:
- `GRAFANA_ADMIN_USER`, `GRAFANA_ADMIN_PASSWORD`;
- `ONEC_DSN` (DSN read-only пользователя в БД 1С);
- при необходимости `AW_SERVER_HOST`, `AW_SERVER_PORT`, `AW_SERVER_SCHEME`, `AW_EXPORTER_PORT` и `AW_SCRAPE_INTERVAL_SECONDS` для ActivityWatch exporter.
- `ONEC_DSN` (DSN read-only пользователя в БД 1С).
3. В БД 1С создайте KPI-представления:
- для PostgreSQL возьмите `./sql/postgres_views_template.sql`;
- для MS SQL возьмите `./sql/mssql_views_template.sql`.
- для PostgreSQL возьмите `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/sql/postgres_views_template.sql`;
- для MS SQL возьмите `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/sql/mssql_views_template.sql`.
4. Поднимите стек:
@@ -54,9 +52,7 @@ docker compose up -d
```bash
curl -fsS http://127.0.0.1:9399/metrics | head
curl -fsS http://127.0.0.1:9398/metrics | head
curl -fsS http://127.0.0.1:9090/-/healthy
curl -fsS http://127.0.0.1:3000/api/health
```
Откройте Grafana: `http://<host>:3000`.
@@ -66,25 +62,23 @@ curl -fsS http://127.0.0.1:3000/api/health
Профилирование структуры 1С (PostgreSQL):
```bash
sh ./tools/discover_postgres_1c.sh \
sh /mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/tools/discover_postgres_1c.sh \
"postgres://user:pass@db-host:5432/db?sslmode=disable"
```
Проверка KPI views:
```bash
sh ./tools/validate_kpi_views.sh \
sh /mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/tools/validate_kpi_views.sh \
"postgres://user:pass@db-host:5432/db?sslmode=disable"
```
Проверка end-to-end пайплайна:
```bash
sh ./tools/check_pipeline.sh
sh /mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c/tools/check_pipeline.sh
```
Скрипт проверяет полный путь сбора данных: `sql-exporter` и `aw-exporter` отдают обязательные метрики, Prometheus успешно выполняет запросы по scrape-targets, а Grafana отвечает на health/API, видит datasource `prometheus` и provisioned dashboards. Если стек запущен не из каталога репозитория, передайте путь к каталогу `grafana-1c` первым аргументом.
## Что контролируется
- Непроведенные документы (`onec_unposted_documents_total`)
@@ -92,8 +86,6 @@ sh ./tools/check_pipeline.sh
- Просроченная дебиторка (`onec_overdue_receivables_total`)
- Ошибки проведения за 24ч (`onec_posting_errors_total`)
- Свежесть данных из 1С (`onec_data_freshness_seconds`)
- Доступность ActivityWatch API (`aw_up`)
- Количество bucket/events ActivityWatch (`aw_buckets_total`, `aw_bucket_events_count`)
## Принципы безопасности
-21
View File
@@ -1,24 +1,4 @@
services:
aw-exporter:
build:
context: ./sql-exporter
dockerfile: Dockerfile
container_name: awrus-aw-exporter
restart: unless-stopped
environment:
- AW_SERVER_HOST=${AW_SERVER_HOST:-10.10.10.13}
- AW_SERVER_PORT=${AW_SERVER_PORT:-5600}
- AW_SERVER_SCHEME=${AW_SERVER_SCHEME:-http}
- EXPORTER_PORT=9398
- SCRAPE_INTERVAL_SECONDS=${AW_SCRAPE_INTERVAL_SECONDS:-30}
volumes:
- ./sql-exporter/collectors/aw_activitywatch.py:/app/aw_activitywatch.py:ro
ports:
- "${AW_EXPORTER_PORT:-9398}:9398"
command:
- "python3"
- "/app/aw_activitywatch.py"
sql-exporter:
image: ghcr.io/burningalchemist/sql_exporter:latest
container_name: awrus-sql-exporter
@@ -50,7 +30,6 @@ services:
ports:
- "${PROMETHEUS_PORT:-9090}:9090"
depends_on:
- aw-exporter
- sql-exporter
grafana:
@@ -1,209 +0,0 @@
{
"title": "ActivityWatch Overview",
"tags": [
"activitywatch",
"monitoring"
],
"timezone": "browser",
"panels": [
{
"id": 1,
"title": "Total Buckets",
"type": "stat",
"targets": [
{
"expr": "aw_buckets_total",
"refId": "A",
"legendFormat": "Total Buckets",
"datasource": {
"type": "prometheus",
"uid": "prometheus"
}
}
],
"options": {
"colorMode": "value",
"graphMode": "area"
},
"fieldConfig": {
"defaults": {
"unit": "short",
"min": 0
}
},
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"gridPos": {
"h": 8,
"w": 6,
"x": 0,
"y": 0
}
},
{
"id": 2,
"title": "Events per Bucket",
"type": "table",
"targets": [
{
"expr": "aw_bucket_events_count",
"format": "table",
"instant": true,
"refId": "B",
"datasource": {
"type": "prometheus",
"uid": "prometheus"
}
}
],
"transformations": [
{
"id": "organize",
"options": {
"excludeByName": {
"Time": true,
"Value": true
}
}
}
],
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"gridPos": {
"h": 8,
"w": 12,
"x": 6,
"y": 0
}
},
{
"id": 3,
"title": "Collector Status",
"type": "stat",
"targets": [
{
"expr": "aw_collector_status",
"format": "table",
"instant": true,
"refId": "C",
"datasource": {
"type": "prometheus",
"uid": "prometheus"
}
}
],
"options": {
"colorMode": "value"
},
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"gridPos": {
"h": 8,
"w": 6,
"x": 18,
"y": 0
}
},
{
"id": 4,
"title": "Events Timeline",
"type": "graph",
"targets": [
{
"expr": "rate(aw_events_total[5m])",
"legendFormat": "{{bucket}} - {{event_type}}",
"refId": "D",
"datasource": {
"type": "prometheus",
"uid": "prometheus"
}
}
],
"fieldConfig": {
"defaults": {
"custom": {
"lineWidth": 2,
"fillOpacity": 10
}
}
},
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"gridPos": {
"h": 9,
"w": 12,
"x": 0,
"y": 8
}
},
{
"id": 5,
"title": "Last Event Timestamp",
"type": "gauge",
"targets": [
{
"expr": "aw_events_last_timestamp",
"legendFormat": "{{bucket}}",
"refId": "E",
"datasource": {
"type": "prometheus",
"uid": "prometheus"
}
}
],
"options": {
"orientation": "horizontal"
},
"fieldConfig": {
"defaults": {
"unit": "s",
"custom": {
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": 0
},
{
"color": "yellow",
"value": 3600
},
{
"color": "green",
"value": 86400
}
]
}
}
}
},
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"gridPos": {
"h": 9,
"w": 12,
"x": 12,
"y": 8
}
}
],
"uid": "activitywatch-overview",
"schemaVersion": 39,
"version": 1,
"refresh": "30s",
"time": {
"from": "now-6h",
"to": "now"
}
}
@@ -1,9 +1,9 @@
apiVersion: 1
providers:
- name: awatch-rus
- name: 1C-Buhgalteria
orgId: 1
folder: "AWatch-rus"
folder: "1C"
type: file
disableDeletion: true
editable: false
-5
View File
@@ -11,11 +11,6 @@ scrape_configs:
static_configs:
- targets: ["localhost:9090"]
- job_name: aw_activitywatch_exporter
static_configs:
- targets: ["aw-exporter:9398"]
scrape_interval: 30s
- job_name: onec_sql_exporter
static_configs:
- targets: ["sql-exporter:9399"]
-9
View File
@@ -1,9 +0,0 @@
FROM python:3.11-slim
WORKDIR /app
RUN pip install prometheus_client requests
COPY collectors/aw_activitywatch.py /app/aw_activitywatch.py
CMD ["python3", "/app/aw_activitywatch.py"]
@@ -1,141 +0,0 @@
#!/usr/bin/env python3
"""
ActivityWatch Prometheus Exporter
Собирает метрики из ActivityWatch API и экспонирует их в формате Prometheus.
"""
import logging
import os
import time
from datetime import datetime
import requests
from prometheus_client import Counter, Gauge, Info, start_http_server
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Configuration
AW_SERVER_HOST = os.getenv("AW_SERVER_HOST", "10.10.10.13")
AW_SERVER_PORT = int(os.getenv("AW_SERVER_PORT", "5600"))
AW_SERVER_SCHEME = os.getenv("AW_SERVER_SCHEME", "http")
AW_API_BASE = os.getenv(
"AW_API_BASE",
f"{AW_SERVER_SCHEME}://{AW_SERVER_HOST}:{AW_SERVER_PORT}/api/0",
)
EXPORTER_PORT = int(os.getenv("EXPORTER_PORT", "9398"))
SCRAPE_INTERVAL_SECONDS = int(os.getenv("SCRAPE_INTERVAL_SECONDS", "30"))
# Metrics
aw_up = Gauge("aw_up", "ActivityWatch API availability: 1 if the last scrape succeeded, 0 otherwise")
aw_buckets_total = Gauge("aw_buckets_total", "Total number of ActivityWatch buckets")
aw_events_total = Counter("aw_events_total", "Total number of ActivityWatch events observed", ["bucket", "event_type"])
aw_events_last_timestamp = Gauge("aw_events_last_timestamp", "Timestamp of last event in bucket", ["bucket"])
aw_bucket_events_count = Gauge("aw_bucket_events_count", "Number of events sampled from bucket", ["bucket"])
aw_collector_status = Gauge(
"aw_collector_status",
"ActivityWatch bucket collector status: 1 if bucket was observed during the last scrape",
["bucket", "client", "hostname", "type"],
)
aw_server_info = Info("aw_server", "ActivityWatch server information")
class ActivityWatchExporter:
def __init__(self, api_base):
self.api_base = api_base.rstrip("/")
self.session = requests.Session()
self.session.headers.update({"Accept": "application/json"})
self.bucket_event_counts = {}
def get_buckets(self):
"""Get all buckets from ActivityWatch API."""
response = self.session.get(f"{self.api_base}/buckets", timeout=10)
response.raise_for_status()
return response.json()
def get_bucket_events(self, bucket_id, limit=1000):
"""Get events from a specific bucket."""
response = self.session.get(
f"{self.api_base}/buckets/{bucket_id}/events",
params={"limit": limit},
timeout=10,
)
response.raise_for_status()
return response.json()
@staticmethod
def event_type(event):
data = event.get("data") or {}
return str(data.get("app") or data.get("title") or event.get("$schema") or "unknown")
@staticmethod
def event_timestamp(event):
timestamp = event.get("timestamp", 0)
if isinstance(timestamp, str):
return datetime.fromisoformat(timestamp.replace("Z", "+00:00")).timestamp()
return float(timestamp or 0)
def collect_metrics(self):
"""Collect metrics from ActivityWatch."""
try:
buckets = self.get_buckets()
aw_up.set(1)
except Exception as exc:
logger.error("Failed to get buckets: %s", exc)
aw_up.set(0)
return
aw_buckets_total.set(len(buckets))
aw_server_info.info(
{
"host": AW_SERVER_HOST,
"port": str(AW_SERVER_PORT),
"scheme": AW_SERVER_SCHEME,
"api_base": self.api_base,
}
)
aw_collector_status.clear()
for bucket_id, bucket_data in buckets.items():
client = str(bucket_data.get("client", "unknown"))
hostname = str(bucket_data.get("hostname", "unknown"))
bucket_type = str(bucket_data.get("type", "unknown"))
try:
events = self.get_bucket_events(bucket_id)
except Exception as exc:
logger.error("Failed to get events for %s: %s", bucket_id, exc)
events = []
event_count = len(events)
aw_bucket_events_count.labels(bucket=bucket_id).set(event_count)
aw_collector_status.labels(bucket=bucket_id, client=client, hostname=hostname, type=bucket_type).set(1)
previous_count = self.bucket_event_counts.get(bucket_id)
if previous_count is not None and event_count > previous_count:
for event in events[: event_count - previous_count]:
aw_events_total.labels(bucket=bucket_id, event_type=self.event_type(event)).inc()
self.bucket_event_counts[bucket_id] = event_count
if events:
try:
aw_events_last_timestamp.labels(bucket=bucket_id).set(self.event_timestamp(events[0]))
except Exception as exc:
logger.warning("Failed to parse last event timestamp for %s: %s", bucket_id, exc)
def main():
exporter = ActivityWatchExporter(AW_API_BASE)
exporter.collect_metrics()
start_http_server(EXPORTER_PORT)
logger.info("ActivityWatch exporter started on port %s", EXPORTER_PORT)
logger.info("Scraping ActivityWatch API at %s", AW_API_BASE)
while True:
time.sleep(SCRAPE_INTERVAL_SECONDS)
exporter.collect_metrics()
if __name__ == "__main__":
main()
+13 -88
View File
@@ -1,95 +1,20 @@
#!/bin/sh
set -eu
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
STACK_DIR="${1:-$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)}"
ENV_FILE="$STACK_DIR/.env"
STACK_DIR="${1:-/mnt/usb_hdd2/Projects/ActivityWatch-Russian/grafana-1c}"
env_value() {
key="$1"
default="$2"
current=$(eval "printf '%s' \"\${$key:-}\"")
if [ -n "$current" ]; then
printf '%s' "$current"
return
fi
if [ -f "$ENV_FILE" ]; then
value=$(sed -n "s/^$key=//p" "$ENV_FILE" | tail -n 1)
if [ -n "$value" ]; then
printf '%s' "$value"
return
fi
fi
printf '%s' "$default"
}
echo "[*] Checking endpoints"
curl -fsS http://127.0.0.1:9399/metrics >/tmp/awrus-onec-metrics.out
curl -fsS http://127.0.0.1:9090/-/healthy >/tmp/awrus-prom-healthy.out
curl -fsS "http://127.0.0.1:9090/api/v1/query?query=up%7Bjob%3D%22onec_sql_exporter%22%7D" >/tmp/awrus-prom-up.json
curl -fsS "http://127.0.0.1:9090/api/v1/query?query=onec_data_freshness_seconds" >/tmp/awrus-prom-freshness.json
GRAFANA_PORT=$(env_value GRAFANA_PORT 3000)
PROMETHEUS_PORT=$(env_value PROMETHEUS_PORT 9090)
SQL_EXPORTER_PORT=$(env_value SQL_EXPORTER_PORT 9399)
AW_EXPORTER_PORT=$(env_value AW_EXPORTER_PORT 9398)
GRAFANA_ADMIN_USER=$(env_value GRAFANA_ADMIN_USER admin)
GRAFANA_ADMIN_PASSWORD=$(env_value GRAFANA_ADMIN_PASSWORD change_me_now)
TMP_DIR="${TMPDIR:-/tmp}"
METRICS_OUT="$TMP_DIR/awrus-onec-metrics.out"
AW_METRICS_OUT="$TMP_DIR/awrus-aw-metrics.out"
PROM_HEALTH_OUT="$TMP_DIR/awrus-prom-healthy.out"
PROM_UP_OUT="$TMP_DIR/awrus-prom-up.json"
PROM_FRESHNESS_OUT="$TMP_DIR/awrus-prom-freshness.json"
GRAFANA_HEALTH_OUT="$TMP_DIR/awrus-grafana-health.json"
GRAFANA_DS_OUT="$TMP_DIR/awrus-grafana-datasources.json"
GRAFANA_DASH_OUT="$TMP_DIR/awrus-grafana-dashboards.json"
require_metric() {
metric_name="$1"
metrics_file="$2"
if ! grep -q "^$metric_name" "$metrics_file"; then
echo "[!] Required metric '$metric_name' was not found in $metrics_file" >&2
exit 1
fi
}
require_prometheus_success() {
file="$1"
if ! grep -q '"status":"success"' "$file"; then
echo "[!] Prometheus query did not return status=success: $file" >&2
cat "$file" >&2
exit 1
fi
}
echo "[*] Checking exporter endpoints"
curl -fsS "http://127.0.0.1:$SQL_EXPORTER_PORT/metrics" >"$METRICS_OUT"
curl -fsS "http://127.0.0.1:$AW_EXPORTER_PORT/metrics" >"$AW_METRICS_OUT"
require_metric "onec_data_freshness_seconds" "$METRICS_OUT"
require_metric "aw_up" "$AW_METRICS_OUT"
echo "[*] Checking Prometheus health and scrape targets"
curl -fsS "http://127.0.0.1:$PROMETHEUS_PORT/-/healthy" >"$PROM_HEALTH_OUT"
curl -fsS "http://127.0.0.1:$PROMETHEUS_PORT/api/v1/query?query=up%7Bjob%3D~%22onec_sql_exporter%7Caw_activitywatch_exporter%22%7D" >"$PROM_UP_OUT"
curl -fsS "http://127.0.0.1:$PROMETHEUS_PORT/api/v1/query?query=onec_data_freshness_seconds" >"$PROM_FRESHNESS_OUT"
require_prometheus_success "$PROM_UP_OUT"
require_prometheus_success "$PROM_FRESHNESS_OUT"
echo "[*] Checking Grafana health, datasource and dashboards"
curl -fsS "http://127.0.0.1:$GRAFANA_PORT/api/health" >"$GRAFANA_HEALTH_OUT"
curl -fsS -u "$GRAFANA_ADMIN_USER:$GRAFANA_ADMIN_PASSWORD" "http://127.0.0.1:$GRAFANA_PORT/api/datasources/uid/prometheus" >"$GRAFANA_DS_OUT"
curl -fsS -u "$GRAFANA_ADMIN_USER:$GRAFANA_ADMIN_PASSWORD" "http://127.0.0.1:$GRAFANA_PORT/api/search?type=dash-db&query=" >"$GRAFANA_DASH_OUT"
if command -v docker >/dev/null 2>&1; then
echo "[*] Checking container status"
cd "$STACK_DIR"
docker compose ps
else
echo "[*] docker command not found; skipping container status"
fi
echo "[*] Checking container status"
cd "$STACK_DIR"
docker compose ps
echo "[+] Pipeline health artifacts:"
echo " $METRICS_OUT"
echo " $AW_METRICS_OUT"
echo " $PROM_HEALTH_OUT"
echo " $PROM_UP_OUT"
echo " $PROM_FRESHNESS_OUT"
echo " $GRAFANA_HEALTH_OUT"
echo " $GRAFANA_DS_OUT"
echo " $GRAFANA_DASH_OUT"
echo " /tmp/awrus-onec-metrics.out"
echo " /tmp/awrus-prom-healthy.out"
echo " /tmp/awrus-prom-up.json"
echo " /tmp/awrus-prom-freshness.json"
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
/home/igor/.local/share/uv/tools/graphifyy/bin/python3
-101
View File
@@ -1,101 +0,0 @@
# ActivityWatch-Russian Knowledge Graph Report
## Overview
- **Total Nodes**: 404
- **Total Edges**: 933
- **Communities**: 27
- **Source**: AST extraction (code-only corpus)
## Communities by Size
### Community 1 (62 nodes)
- install_kit_awindows_20260427_211240_windows_dlp_endpoint_signals_collector_ps1
- dlp_endpoint_signals_collector_get_deploymentconfig
- dlp_endpoint_signals_collector_write_endpointlog
- dlp_endpoint_signals_collector_invoke_awjsonpost
- dlp_endpoint_signals_collector_ensure_bucket
- ... and 57 more
### Community 2 (56 nodes)
- aw_server_aw_ru_patch_js
- aw_ru_patch_injectstyles
- aw_ru_patch_hidenoisenavigation
- aw_ru_patch_getcurrenthostfromhash
- aw_ru_patch_ispvelikehost
- ... and 51 more
### Community 3 (54 nodes)
- install_kit_awindows_20260427_211240_windows_browser_domains_native_collector_ps1
- browser_domains_native_collector_get_deploymentconfig
- browser_domains_native_collector_write_collectorlog
- browser_domains_native_collector_write_dlpincidentlog
- browser_domains_native_collector_test_domainmatch
- ... and 49 more
### Community 0 (38 nodes)
- scripts_aggregate_dlp_events_py
- aggregate_dlp_events_bucket
- aggregate_dlp_events_awevent
- aggregate_dlp_events_psycopgconnection
- protocol
- ... and 33 more
### Community 5 (34 nodes)
- aw_ru_patch_isdlpsignalbucketroute
- aw_ru_patch_getdlphostfrombucketid
- aw_ru_patch_builddlpkey
- aw_ru_patch_loadbucketevents
- aw_ru_patch_serializerulematch
- ... and 29 more
### Community 4 (34 nodes)
- install_kit_awindows_20260427_211240_windows_email_outbound_collector_ps1
- email_outbound_collector_get_deploymentconfig
- email_outbound_collector_write_collectorlog
- email_outbound_collector_invoke_awjsonpost
- email_outbound_collector_ensure_bucket
- ... and 29 more
### Community 8 (28 nodes)
- aw_ru_patch_replacetext
- aw_ru_patch_walk
- aw_ru_patch_translateattributes
- aw_ru_patch_ishomeroute
- aw_ru_patch_getdefaulthostgroupsconfig
- ... and 23 more
### Community 9 (18 nodes)
- aw_ru_patch_getdlpbucketidfromhash
- aw_ru_patch_generatedlpid
- aw_ru_patch_awapijson
- aw_ru_patch_ensureawbucket
- aw_ru_patch_saveawheartbeat
- ... and 13 more
### Community 10 (14 nodes)
- install_kit_awindows_20260427_211240_windows_migrate_awatch_rus_paths_ps1
- migrate_awatch_rus_paths_copy_directorycontents
- migrate_awatch_rus_paths_copy_ifexists
- migrate_awatch_rus_paths_convert_pathvalue
- migrate_awatch_rus_paths_stop_awatchtaskset
- ... and 9 more
### Community 6 (12 nodes)
- grafana_1c_sql_exporter_collectors_aw_activitywatch_py
- aw_activitywatch_activitywatchexporter
- aw_activitywatch_activitywatchexporter_init
- aw_activitywatch_activitywatchexporter_get_buckets
- aw_activitywatch_activitywatchexporter_get_bucket_events
- ... and 7 more
## File Types
The graph was built from code files including:
- PowerShell scripts (.ps1)
- Python scripts (.py)
- JavaScript patches (.js)
- Configuration files
## Notes
- This is a structural (AST-based) graph showing code relationships
- No semantic extraction was performed (no docs/papers in corpus)
- Communities represent clusters of related functions and modules
@@ -1 +0,0 @@
{"nodes": [{"id": "mnt_usb_hdd2_projects_activitywatch_russian_install_kit_awindows_20260427_211240_aw_server_aw_sw_cleanup_js", "label": "aw-sw-cleanup.js", "file_type": "code", "source_file": "/mnt/usb_hdd2/Projects/ActivityWatch-Russian/install-kit-awindows-20260427-211240/aw-server/aw-sw-cleanup.js", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -1 +0,0 @@
{"nodes": [{"id": "mnt_usb_hdd2_projects_activitywatch_russian_install_kit_awindows_20260427_211240_windows_deploy_domain_users_ps1", "label": "deploy-domain-users.ps1", "file_type": "code", "source_file": "/mnt/usb_hdd2/Projects/ActivityWatch-Russian/install-kit-awindows-20260427-211240/windows/deploy-domain-users.ps1", "source_location": "L1"}], "edges": [], "raw_calls": []}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
{"nodes": [{"id": "mnt_usb_hdd2_projects_activitywatch_russian_windows_deploy_ensemble_ps1", "label": "deploy-ensemble.ps1", "file_type": "code", "source_file": "/mnt/usb_hdd2/Projects/ActivityWatch-Russian/windows/deploy-ensemble.ps1", "source_location": "L1"}], "edges": [], "raw_calls": []}

Some files were not shown because too many files have changed in this diff Show More