Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78488f004c | ||
|
|
54e1867bfc | ||
|
|
e3341f9de3 |
@@ -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
|
||||
@@ -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."
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
-20
@@ -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.
|
||||
@@ -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`
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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`.
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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/"
|
||||
|
||||
@@ -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 }}"
|
||||
@@ -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) }}"
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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 }}"
|
||||
|
||||
@@ -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 }}"
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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);
|
||||
})();
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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": {}
|
||||
|
||||
@@ -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')"
|
||||
@@ -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')"
|
||||
@@ -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\`
|
||||
|
||||
Задачи планировщика:
|
||||
|
||||
|
||||
@@ -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%
|
||||
@@ -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 строит графики
|
||||
```
|
||||
@@ -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`.
|
||||
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
```
|
||||
@@ -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"
|
||||
}
|
||||
```
|
||||
@@ -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 действий
|
||||
@@ -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 недоступен
|
||||
@@ -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;
|
||||
}
|
||||
```
|
||||
@@ -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+
|
||||
@@ -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;
|
||||
```
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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 активной вкладки.
|
||||
@@ -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`).
|
||||
@@ -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 шагов.
|
||||
- Для статуса работ: что сделано, что проверено, что дальше.
|
||||
@@ -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) - визуальная схема связей
|
||||
@@ -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)
|
||||
@@ -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) - детально о патчах интерфейса
|
||||
@@ -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)
|
||||
@@ -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).
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
@@ -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`.
|
||||
|
||||
@@ -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`
|
||||
|
||||
Этот файл оставлен как совместимый указатель, чтобы не ломать существующие ссылки в документации/автоматизации.
|
||||
@@ -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
@@ -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'
|
||||
```
|
||||
|
||||
Ожидаемо:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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`)
|
||||
|
||||
## Принципы безопасности
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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()
|
||||
@@ -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 +0,0 @@
|
||||
/home/igor/.local/share/uv/tools/graphifyy/bin/python3
|
||||
@@ -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
@@ -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
@@ -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": []}
|
||||
-1
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
-1
@@ -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
Reference in New Issue
Block a user