Compare commits

...
Author SHA1 Message Date
igor04091968 3e8a6981f5 fix(shell): silence remaining ShellCheck warnings for validate_install_kit and install_aw_linux_remote_worker\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> 2026-05-07 12:39:32 +03:00
igor04091968 c9f3aad89c fix(shell): address ShellCheck warnings (remove SC2181 checks, safer prompt_secret, add selective suppressions)\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> 2026-05-07 12:35:23 +03:00
igor04091968 669501f20a fix(worktime): treat russian session state 'Активно' as active 2026-05-07 09:07:19 +03:00
igor04091968 cd5fd95faf fix(aw): stabilize ui bridge and restore dlp endpoint collector 2026-05-07 08:57:36 +03:00
IgorRachkovandGitHub 5e5cb30fab Merge pull request #15 from igor04091968/devin/1777897921-print-docname-ansible-innosetup
fix(print): исправление имени документа «Печать документа» + интеграция email collector в деплой
2026-05-07 07:34:42 +03:00
igor04091968 8b92b1136f chore: merge main into PR #15 2026-05-07 07:34:36 +03:00
IgorRachkovandGitHub 083b05d096 Merge pull request #19 from igor04091968/codex/-rdp
Fix Windows/RDP collectors startup and make bucket creation idempotent
2026-05-07 07:34:20 +03:00
igor04091968 8571832615 chore: merge main into PR #19 2026-05-07 07:33:53 +03:00
IgorRachkovandGitHub eb25a230fc Merge pull request #3 from igor04091968/codex/explain-codebase-structure-to-beginner
Add newcomer onboarding guide and link it from README
2026-05-07 07:32:03 +03:00
IgorRachkovandGitHub 96ee17f156 Merge pull request #6 from igor04091968/codex/validate-ansible-playbooks-in-repository
Add install-kit validation tools and add Ansible syntax check to quality gate
2026-05-07 07:31:54 +03:00
IgorRachkovandGitHub 66a8c47319 Merge pull request #7 from igor04091968/codex/find-and-fix-error-in-powershell-dlp-script
Refine detection of masked Cyrillic print titles and normalize printer names
2026-05-07 07:31:50 +03:00
IgorRachkovandGitHub 5a89a79803 Merge pull request #13 from igor04091968/devin/1777839496-strategic-dlp-roadmap
docs: стратегический DLP roadmap — AWatch-rus vs InfoWatch Traffic Monitor
2026-05-07 07:31:44 +03:00
IgorRachkovandGitHub a00bbdee06 Merge pull request #16 from igor04091968/codex/analyze-powershell-scripts-for-functionality
Fix DLP PowerShell collectors: config default, HTTP handling, event cleanup, headless fallback
2026-05-07 07:31:38 +03:00
IgorRachkovandGitHub 9ef09cb892 Merge pull request #18 from igor04091968/codex/review-dlp-related-code
Add print-job dedupe, improve clipboard guards and add polling error logs
2026-05-07 07:31:33 +03:00
igor04091968 8849354dda fix(worktime): codify ui-bridge in ansible and install flow 2026-05-07 07:20:14 +03:00
igor04091968 d91a396c00 feat(webui): add HTML and panel view for worktime reports 2026-05-07 06:41:42 +03:00
igor04091968 8a91734a27 fix(worktime): use Europe/Moscow day boundary for aw-rus today reports 2026-05-07 06:30:12 +03:00
igor04091968 1df0e18a95 fix(collectors): stabilize browser+DLP collectors and add rdp worktime report script 2026-05-06 23:32:03 +03:00
igor04091968 a6a05b82a9 fix(webui): persist worktime report links to :5610 in ansible/installkit 2026-05-06 23:24:52 +03:00
igor04091968 03fefcc257 fix(deploy): harden windows rollout, validation and installkit payload 2026-05-06 20:14:51 +03:00
igor04091968 9eb02185dd fix(windows): stabilize AW deploy/validate and watcher launch script 2026-05-06 20:05:43 +03:00
IgorRachkov b2a1a6289c Fix Windows RDP collectors startup 2026-05-06 17:23:47 +03:00
IgorRachkov 945efd203f Harden DLP endpoint collector error handling and print dedupe 2026-05-05 07:55:03 +03:00
igor04091968 82ee9ab4fa tools: add AW health checks and test DLP policy 2026-05-05 06:38:04 +03:00
igor04091968andDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> eb83a8b08f docs(wiki): add GitHub wiki pages for documentation
- Home page with navigation and quick start
- Architecture overview with layers and data flows
- Components documentation for all system parts
- Interactive map guide with usage instructions
- DLP Endpoint Monitoring detailed guide
- Browser Domains Monitoring guide
- WebUI Russian Patches documentation
- Windows Installation guide
- Server Setup guide
- Monitoring Setup with Prometheus/Grafana

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-05 01:56:30 +03:00
igor04091968andDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> 7a89f33e3b docs(diagrams): add interactive architecture map HTML
- Interactive component map with visual connections
- Click to see component details and data flows
- Search functionality for quick component lookup
- Connection highlighting when selecting components
- Responsive design with color-coded layers
- Component information panel with ports, protocols, flows

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-05 01:51:25 +03:00
igor04091968andDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> d8320799e6 docs(diagrams): add component-level architecture diagrams
- DLP Endpoint Monitoring diagram with data flows
- Browser Domains Monitoring with categorization
- WebUI Russian Localization patches structure
- DLP Events Aggregation pipeline
- Prometheus Metrics Exporter architecture
- System overview with all components and connections

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-05 01:49:55 +03:00
igor04091968andDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> db1998c428 docs(architecture): add readable architecture diagrams
- Add Mermaid diagram for high-level architecture
- Add simple ASCII architecture for quick understanding
- Document data flows for all monitoring scenarios
- Include deployment steps and port mappings
- Add quick start guide and key scenarios

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-05 01:46:50 +03:00
igor04091968andDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> f6074facf5 docs(graphify): add detailed knowledge graph documentation for wiki
- Explain what knowledge graph is and why it's useful
- Document all 27 communities with purposes
- Describe key components: DLP monitoring, WebUI patches, collectors
- Provide usage guide for interactive visualization
- Include statistics and architecture recommendations

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-05 01:45:32 +03:00
igor04091968andDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> 3f3e3a953f docs(graphify): add ActivityWatch knowledge graph visualization
- Generate AST-based knowledge graph with 404 nodes and 933 edges
- 27 communities showing code structure and relationships
- Interactive HTML visualization for exploring code architecture
- Report documenting main functional clusters

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-05 01:44:27 +03:00
igor04091968andDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> d5735ba127 feat(grafana): add ActivityWatch Prometheus exporter and dashboards
- Add Python exporter for ActivityWatch API metrics collection
- Configure Prometheus to scrape ActivityWatch exporter
- Create Grafana dashboard for ActivityWatch overview
- Integrate ActivityWatch monitoring into existing docker-compose stack
- Add Prometheus datasource for ActivityWatch metrics

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-05 01:32:50 +03:00
igor04091968andDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> 10c22a6c88 fix(windows): add STA parameter for DLP collector and use TEMP for lock files
- Add -STA parameter when launching endpoint-signals collector for clipboard access
- Move launch-watchers lock files from ProgramData to TEMP to avoid permission issues
- This fixes DLP endpoint collector startup in RDP sessions

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-05 00:55:04 +03:00
igor04091968andDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> 9e5cb144d4 fix(windows): standardize config paths to AWatch-rus and add bucket hostname filter
- Replace default config paths from C:\ProgramData\ActivityWatch to C:\ProgramData\AWatch-rus
  in dlp-endpoint-signals-collector.ps1 and email-outbound-collector.ps1
- Add isLikelyClientHost() function to reject IP/localhost as valid hostname
  for bucket selection in aw-ru-patch.js
- Add docs/dlp-reliability-roadmap.md and docs/powershell-analysis.md
- Update README.md with links to new documentation

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-04 23:24:49 +03:00
IgorRachkov d88ef78184 Fix DLP collector defaults, HTTP error handling, and cleanup 2026-05-04 21:47:03 +03:00
Devin AIandFashion Lisa a35a8a51bd fix(print): detect generic doc names (Печать документа) + integrate email collector into deploy
Print document naming:
- Add Test-IsGenericDocumentName() to detect placeholder names like
  'Печать документа', 'Print Document', 'Remote Downlevel Document', etc.
- Add Test-NeedsBetterDocumentName() that combines mojibake, generic and
  numeric-only checks into a single gate for PrintService event log fallback
- Replace Test-LooksLikeMojibakeQuestionMarks with Test-NeedsBetterDocumentName
  in Win32_PrintJob polling and Get-PrintServiceDocumentFallback

Email collector deployment integration:
- Ansible: add email-outbound-collector.ps1 to deploy_aw_windows.yml file list
- Ansible: add aw_windows_email_collector_enabled/mode vars
- InnoSetup: add email-outbound-collector.ps1 to [Files] section
- Common module: add EmailCollectorScript to Copy-ActivityWatchCollectorAssets,
  New-ActivityWatchDeploymentConfig, launch-watchers script
- Deploy scripts: pass EmailCollectorScript through pipeline
- Deployment configs: add emailCollectorScript path
- Sync all changes to install-kit copy

Co-Authored-By: Fashion Lisa <igor04091968@gmail.com>
2026-05-04 18:07:14 +00:00
igor04091968 bfee99d679 fix(windows): prevent collector process storms and scope query patch 2026-05-04 21:05:44 +03:00
igor04091968 5a2684fb3b fix: proper single UTF-8 BOM 2026-05-04 07:24:14 +03:00
igor04091968 e2b2f805fb Merge remote-tracking branch 'origin/devin/1777752962-file-collector-bucket' 2026-05-04 06:05:39 +03:00
IgorRachkovGitHubFashion LisaDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
eea851141f feat(dlp): enforcement + email outbound collector (#14)
* feat(dlp): add enforcement — USB write-block, print cancel, clipboard clear

Phase 2.5: when DLP policy rule has action="block", the collector
now actively prevents the action instead of just logging:

- USB: Set-Disk -IsReadOnly via Get-Partition/Get-Disk pipeline
- Print: Remove-CimInstance Win32_PrintJob for matching jobs
- Clipboard: Set-Clipboard -Value $null to clear sensitive content

Each enforcement adds enforced=true/false to incident telemetry.
Windows balloon notification shown to user on every block action.
Backward-compatible: existing action="alert" rules unchanged.

Co-Authored-By: Fashion Lisa <igor04091968@gmail.com>

* feat(dlp): add email outbound collector — Outlook COM + SMTP monitor

Two collection modes:
- outlook: polls Sent Items via COM, extracts metadata (subject hash,
  recipients hash, attachment names, body length)
- smtp: monitors SMTP connections (25/587/465/2525) via Get-NetTCPConnection

DLP policy rules: endpoint.email[] with regex matching on subject,
recipients, sender, attachments, externalOnly flag.

Enforcement: action=block moves mail to Drafts (Outlook mode).
Privacy: subject/recipients stored as SHA256, body never read.
Co-Authored-By: Fashion Lisa <igor04091968@gmail.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-04 00:23:06 +03:00
igor04091968 6578f6341f fix: add UTF-8 BOM for PS 5.1 2026-05-04 00:22:15 +03:00
igor04091968 cc33ffa3dc fix: add UTF-8 BOM for PowerShell 5.1 compatibility 2026-05-04 00:14:32 +03:00
igor04091968 ff548a5840 Merge PR #14: feat(dlp) enforcement + email outbound collector 2026-05-03 23:41:00 +03:00
Devin AIandFashion Lisa f916764d53 feat(dlp): add email outbound collector — Outlook COM + SMTP monitor
Two collection modes:
- outlook: polls Sent Items via COM, extracts metadata (subject hash,
  recipients hash, attachment names, body length)
- smtp: monitors SMTP connections (25/587/465/2525) via Get-NetTCPConnection

DLP policy rules: endpoint.email[] with regex matching on subject,
recipients, sender, attachments, externalOnly flag.

Enforcement: action=block moves mail to Drafts (Outlook mode).
Privacy: subject/recipients stored as SHA256, body never read.
Co-Authored-By: Fashion Lisa <igor04091968@gmail.com>
2026-05-03 20:30:18 +00:00
Devin AIandFashion Lisa 2bab84f9f9 feat(dlp): add enforcement — USB write-block, print cancel, clipboard clear
Phase 2.5: when DLP policy rule has action="block", the collector
now actively prevents the action instead of just logging:

- USB: Set-Disk -IsReadOnly via Get-Partition/Get-Disk pipeline
- Print: Remove-CimInstance Win32_PrintJob for matching jobs
- Clipboard: Set-Clipboard -Value $null to clear sensitive content

Each enforcement adds enforced=true/false to incident telemetry.
Windows balloon notification shown to user on every block action.
Backward-compatible: existing action="alert" rules unchanged.

Co-Authored-By: Fashion Lisa <igor04091968@gmail.com>
2026-05-03 20:21:39 +00:00
Devin AIandFashion Lisa 6640299a48 docs: add strategic DLP roadmap vs InfoWatch Traffic Monitor
Co-Authored-By: Fashion Lisa <igor04091968@gmail.com>
2026-05-03 20:18:19 +00:00
igor04091968 3e565b7a2e fix: create /root/bootstrap directory before copying files
Add ansible.builtin.file task to ensure /root/bootstrap exists
before copying RU patch files to it (prevents first-deploy failure)
2026-05-03 23:08:48 +03:00
igor04091968 3fa15f826d fix: env file before hotfixes + improved error handling
- Move env file creation before apply_webui_ru_patch.sh execution
- Replace ignore_errors with failed_when: false + register + debug output
- Provides visible feedback on hotfix script execution result
2026-05-03 22:56:31 +03:00
igor04091968 08ba731345 fix: apply WebUI hotfixes via apply_webui_ru_patch.sh + filter undefined hostname
- Add CATEGORY_HELPER filter for 'undefined' in addition to 'unknown'
- Add copy of apply_webui_ru_patch.sh to /opt/activitywatch/aw-server/
- Add task to run apply_webui_ru_patch.sh for Trends/Timespiral/Category helper hotfixes
- Fix in both deploy_aw_server.yml (ansible and install-kit)
2026-05-03 22:52:39 +03:00
igor04091968 df497839f6 Merge remote-tracking branch 'origin/main' into devin/1777752962-file-collector-bucket 2026-05-03 22:28:37 +03:00
IgorRachkovGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
36e4255ad9 fix: handle undefined bucket filters in AQL query rewriter (#12)
The network patch that intercepts /api/0/query/ requests only handled
'unknown' hostnames in bucket IDs. When the WebUI activity store has
uninitialized bucket IDs (e.g. browser watcher not installed on a host),
find_bucket("undefined") or query_bucket("undefined") calls reach the
server and fail with BucketQueryError.

Extend rewriteUnknownCategoryBuilderQueryBody to:
- Replace query_bucket(find_bucket("undefined")) and flood() wrappers
  with empty arrays ([]) so the query continues without missing data.
- Rewrite aw-watcher-{window,afk}_undefined to the preferred host,
  matching the existing 'unknown' hostname logic.

Applied to both aw-server/ and install-kit copies of aw-ru-patch.js.

Fixes: BucketЗапросError on Trends page for host SHARKON2025

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-03 22:27:50 +03:00
igor04091968 3971c459ef Feat: implement automated DLP incident aggregation on server (timer + service) 2026-05-03 01:50:17 +03:00
Devin AI b7a7ac42e4 Improve print DLP telemetry reliability 2026-05-02 22:45:48 +00:00
Devin AI 4359f6d5eb Fix Windows file telemetry playbook wiring 2026-05-02 21:44:18 +00:00
Devin AI 7ea4ebd463 Merge PR #11 DLP incident aggregation prototype 2026-05-02 21:33:42 +00:00
Devin AI 5a4064dc0f Add DLP incident aggregation prototype 2026-05-02 20:55:44 +00:00
igor04091968 c97ffe2cbd Fix: ensure file collector robustness (HttpClient, TLS 1.2, English logs) 2026-05-02 23:42:14 +03:00
Devin AI f45ef0038d Use string JSON body for file collector posts 2026-05-02 20:34:07 +00:00
Devin AI 8088b19dc7 Create file operation bucket before path checks 2026-05-02 20:28:03 +00:00
Devin AI 046aa3ed1d Create file operations bucket on startup 2026-05-02 20:17:43 +00:00
igor04091968 b6f019982d gemeni-3-light 2026-05-02 23:09:54 +03:00
igor04091968andDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> fae2e2ca14 Feat & Fix: implement File Telemetry, restore DB history, and stabilize production
- Added File Operations Collector (Plan A) for Windows endpoints
- Restored historical server DB via merging and moved to durable /var/lib/activitywatch path
- Forced XDG_DATA_HOME and XDG_CONFIG_HOME for aw-server-rust in environment and systemd
- Updated Ansible playbooks to handle new file collector and durable server paths
- Added DB merge and backup-restore automation scripts
- Fixed CORS and RU WebUI persistence in production deployment

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-02 20:59:08 +03:00
igor04091968 f436950bda Update after codex restore 2026-05-02 19:36:25 +03:00
igor04091968 d7fedde69d After deploy from hand restore 2026-05-02 17:54:36 +03:00
igor04091968 7f58a49c0a Разворачивание деплоя 2026-05-02 17:28:49 +03:00
IgorRachkov 8f10bef904 Limit print title fallback to masked Russian document names 2026-05-01 15:50:02 +03:00
IgorRachkov aca42caf0a Add install-kit to repo drift checker 2026-04-30 20:59:40 +03:00
IgorRachkov 9bdf28ed2b Add newcomer onboarding guide for repository structure 2026-04-29 13:09:23 +03:00
161 changed files with 58888 additions and 599 deletions
+2
View File
@@ -1,11 +1,13 @@
# Local secrets
secrets/deploy.secrets.env
secrets/runtime.env
# Runtime / reports
*.log
*.tmp
*.bak
windows/*.report.json
.rollout-logs/
# IDE
.idea/
+11236
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+5
View File
@@ -5,6 +5,7 @@
## Что входит
- `docs/preparation.md` — подготовка инфраструктуры и входных параметров.
- `docs/codebase-onboarding.md` — обзор структуры репозитория и маршрут изучения для новичка.
- `docs/deployment.md` — пошаговый деплой LXC и ActivityWatch Server.
- `docs/runbook.md` — быстрый runbook для оператора.
- `docs/operations.md` — регламент сопровождения, бэкапов, обновлений и rollback.
@@ -13,12 +14,16 @@
- `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_*`).
- `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.
+1
View File
@@ -1,4 +1,5 @@
#!/bin/sh
# shellcheck disable=SC1007
set -eu
REPO_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
+1
View File
@@ -1,4 +1,5 @@
#!/bin/sh
# shellcheck disable=SC1007
set -eu
REPO_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
+19
View File
@@ -31,6 +31,15 @@ cd 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 (всё за один запуск)
Если нужно прогнать полный цикл одной командой:
@@ -149,3 +158,13 @@ Playbook:
- Для полного сценария 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/`.
+551 -154
View File
@@ -54,6 +54,11 @@
- "{{ 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 }}"
@@ -74,100 +79,147 @@
- "{{ 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: Скачать архив релиза ActivityWatch
ansible.builtin.get_url:
url: "{{ aw_server_download_url }}"
dest: "{{ aw_archive_path }}"
mode: "0644"
- 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
ansible.builtin.unarchive:
src: "{{ aw_archive_path }}"
dest: "{{ aw_release_dir }}"
remote_src: true
extra_opts: ["-o"]
- 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.find:
paths: "{{ aw_release_dir }}"
file_type: directory
patterns: "activitywatch*"
register: aw_release_find
- name: Распаковать релиз ActivityWatch
ansible.builtin.unarchive:
src: "{{ aw_archive_path }}"
dest: "{{ aw_release_dir }}"
remote_src: true
extra_opts: ["-o"]
- name: Найти бинарный файл AW server
ansible.builtin.find:
paths: "{{ aw_release_dir }}"
file_type: file
patterns:
- aw-server-rust
- aw-server
register: aw_server_binary_find
- name: Найти распакованный каталог ActivityWatch
ansible.builtin.find:
paths: "{{ aw_release_dir }}"
recurse: true
file_type: directory
patterns: "activitywatch*"
register: aw_release_find
- name: Найти каталог WebUI
ansible.builtin.find:
paths: "{{ aw_release_dir }}"
file_type: directory
patterns:
- aw-webui
- webui
register: aw_webui_dir_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: Сохранить пути распакованного релиза
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 | first) | default('') }}"
aw_webui_source_path: "{{ (aw_webui_dir_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list | first) | default('') }}"
- 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: Проверить, что компоненты релиза найдены
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: Сохранить пути распакованного релиза (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: Создать каталог установленного релиза
ansible.builtin.file:
path: "{{ aw_release_install_dir }}"
state: directory
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
mode: "0755"
- name: Сохранить каталог WebUI (dirname index.html)
ansible.builtin.set_fact:
aw_webui_source_path: "{{ aw_webui_index_path | dirname }}"
- 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: Проверить, что компоненты релиза найдены
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: Создать ссылку на активный бинарный файл 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: Создать каталог установленного релиза
ansible.builtin.file:
path: "{{ aw_release_install_dir }}"
state: directory
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
mode: "0755"
- name: Синхронизировать WebUI в RU каталог
ansible.builtin.command:
cmd: "rsync -a {{ aw_webui_source_path }}/ {{ aw_server_webui_dir }}/"
- 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: Настроить владельца файлов /opt/activitywatch
ansible.builtin.file:
path: /opt/activitywatch
state: directory
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
recurse: true
- 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:
@@ -184,78 +236,401 @@
- Перезагрузить systemd
- Перезапустить activitywatch
- 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-host-groups.json", dest: "{{ aw_server_webui_dir }}/js/aw-host-groups.json", mode: "0644" }
- 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: Проверить наличие index.html после копирования
ansible.builtin.stat:
path: "{{ aw_server_webui_dir }}/index.html"
register: aw_webui_ru_index
- 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: Проверить, что index.html доступен для RU patch
ansible.builtin.assert:
that:
- aw_webui_ru_index.stat.exists
fail_msg: "Не найден index.html WebUI для применения RU patch."
- name: Создать каталог /root/bootstrap для apply_webui_ru_patch.sh
ansible.builtin.file:
path: /root/bootstrap
state: directory
mode: "0755"
- 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: Скопировать 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: Добавить 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: Скопировать 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: Добавить загрузчик 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: Записать /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: Записать /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_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 }}
- 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: Включить и запустить сервис
ansible.builtin.systemd:
name: activitywatch-server.service
enabled: true
state: restarted
daemon_reload: true
- 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: Дождаться ответа API
- 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/info"
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/"
method: GET
status_code: 200
register: aw_api
retries: 10
delay: 3
until: aw_api.status == 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
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 }}"
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: Скопировать агрегатор событий 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: Установить systemd unit для агрегатора
ansible.builtin.copy:
dest: /etc/systemd/system/activitywatch-dlp-aggregator.service
content: |
[Unit]
Description=ActivityWatch DLP Event Aggregator
After=activitywatch-server.service
[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
[Install]
WantedBy=multi-user.target
- 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)
ansible.builtin.uri:
@@ -277,16 +652,12 @@
- name: Вычислить worktime durationDefault из 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_h | int) * 60 + (aw_worktime_to_m | int)) -
((aw_worktime_from_h | int) * 60 + (aw_worktime_from_m | int))
(((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))
) * 60
)
}}
@@ -314,20 +685,46 @@
ansible.builtin.uri:
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/startOfDay"
method: POST
body: "{{ aw_worktime_start_of_day }}"
body_format: json
status_code: 200
body: "\"{{ aw_worktime_start_of_day }}\""
headers:
Content-Type: application/json
status_code: [200, 201]
when: aw_apply_worktime_settings | default(false) | bool
- name: Применить базовый период worktime (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 }}"
body_format: json
status_code: 200
body: "{{ aw_worktime_duration_default_effective | string }}"
headers:
Content-Type: application/json
status_code: [200, 201]
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
ansible.builtin.systemd:
+55 -28
View File
@@ -25,6 +25,7 @@
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
@@ -77,6 +78,8 @@
- 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
@@ -86,6 +89,18 @@
- 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"
@@ -130,6 +145,7 @@
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' }}
@@ -149,6 +165,19 @@
{% 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:
@@ -171,6 +200,7 @@
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: >-
{{
@@ -179,54 +209,51 @@
else 'aw-watcher-afk_' ~ (aw_windows_hostname_result.stdout | trim)
}}
- name: Дождаться свежих AFK событий на AW server
- name: Выполнить AW API smoke-check (проверка наличия свежих событий в AFK бакете)
when:
- aw_windows_api_smoke_check_enabled | bool
- aw_windows_afk_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
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
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: Выполнить валидацию и сохранить отчёт на целевом Windows host
- name: Валидировать развёртывание на эндпоинте
ansible.windows.win_powershell:
script: |
$ErrorActionPreference = 'Stop'
$report = & "{{ aw_windows_deploy_root }}\windows\validate-deployment.ps1" `
$result = & "{{ 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 завершилась ошибкой. Отчёт: {{ aw_windows_validation_remote_path }}"
}
$result | ConvertTo-Json -Depth 8 | Out-File -FilePath "{{ aw_windows_validation_remote_path }}" -Encoding utf8
return $result
- name: Создать локальный каталог для validation reports
- name: Создать локальную директорию для отчётов валидации
ansible.builtin.file:
path: "{{ aw_windows_validation_local_dir }}"
state: directory
mode: "0755"
delegate_to: localhost
- name: Забрать validation report
- 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.debug:
msg:
- "Windows/RDP развёртывание завершено на {{ inventory_hostname }}."
- "Отчёт проверки: {{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_ansible.json"
- 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
+16 -3
View File
@@ -4,15 +4,26 @@ 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 }}"
# Опционально: применить базовые категории и views для рабочего времени через AW settings API.
# Внимание: это перезаписывает существующие server-side settings/classes/views.
aw_apply_worktime_settings: false
# Применить базовые категории и views для рабочего времени через AW settings API.
# При прод-обновлениях это нужно оставлять включённым, иначе UI остаётся без views/classes.
aw_apply_worktime_settings: true
# Дополнительные 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 задаёт границу дня и стартовое время окна отчёта.
@@ -22,3 +33,5 @@ aw_apply_worktime_settings: false
aw_worktime_from: "08:00"
aw_worktime_to: "17:00"
aw_worktime_start_of_day: "{{ aw_worktime_from }}"
aw_server_always_active_pattern: "aw-watcher-window"
aw_server_landingpage: "/activity/SHARKON2025/view/"
+27
View File
@@ -0,0 +1,27 @@
aw_server_version: "v0.13.2"
aw_server_download_url: "https://github.com/ActivityWatch/activitywatch/releases/download/v0.13.2/activitywatch-v0.13.2-linux-x86_64.zip"
aw_server_bind_host: "0.0.0.0"
aw_server_port: 5600
aw_server_webui_dir: "/opt/activitywatch/webui-ru"
aw_server_data_dir: "/var/lib/activitywatch"
aw_server_log_dir: "/var/log/activitywatch"
aw_server_db_path: "/var/lib/activitywatch/aw-server-rust/sqlite.db"
aw_server_user: "activitywatch"
aw_server_group: "activitywatch"
aw_worktime_report_base: "http://10.10.10.13:5610"
aw_worktime_timezone: "Europe/Moscow"
aw_repo_root: "/mnt/usb_hdd2/Projects/ActivityWatch-Russian"
aw_server_cors_origins:
- "http://127.0.0.1:5600"
- "http://localhost:5600"
- "http://10.10.10.13:5600"
- "http://192.168.100.13:5600"
- "http://snb-live:5600"
aw_apply_worktime_settings: true
aw_worktime_from: "08:00"
aw_worktime_to: "17:00"
aw_worktime_start_of_day: "{{ aw_worktime_from }}"
+9
View File
@@ -0,0 +1,9 @@
# 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) }}"
+54
View File
@@ -0,0 +1,54 @@
# 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_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
View File
@@ -23,6 +23,7 @@ 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
+5
View File
@@ -0,0 +1,5 @@
[aw_server]
localhost ansible_connection=local ansible_user=root
[aw_windows]
rdp-prod ansible_host=192.168.100.21 ansible_user=Администратор ansible_password=Sergei2009@ ansible_connection=winrm ansible_winrm_transport=ntlm ansible_port=5985 ansible_winrm_server_cert_validation=ignore
+98
View File
@@ -0,0 +1,98 @@
---
- 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"
@@ -9,6 +9,12 @@
- 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
@@ -9,6 +9,12 @@
- 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
@@ -159,6 +159,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
+2 -2
View File
@@ -9,7 +9,7 @@ EnvironmentFile=/etc/activitywatch/aw-server.env
User=__AW_SERVER_USER__
Group=__AW_SERVER_GROUP__
WorkingDirectory=__AW_SERVER_DATA_DIR__
ExecStart=/bin/sh -lc 'exec /opt/activitywatch/bin/aw-server-rust --host "$AW_SERVER_BIND_HOST" --port "$AW_SERVER_PORT"'
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"'
Restart=on-failure
RestartSec=5s
StateDirectory=activitywatch
@@ -17,7 +17,7 @@ LogsDirectory=activitywatch
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
ProtectHome=read-only
LimitNOFILE=65535
[Install]
+62 -4
View File
@@ -10,39 +10,97 @@ 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}))[0]||t.hostnameChoices[0]'
CATEGORY_HELPER_REPLACEMENT='hostname:t.hostnameChoices.filter((function(t){return"unknown"!==t&&"undefined"!==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)}')"
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"
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
cp "$SW_CLEANUP_SRC" "$SERVICE_WORKER"
trends_chunk="$(grep -Rsl "$TRENDS_NEEDLE" "$WEBUI_DIR/js"/*.js 2>/dev/null | head -n 1 || true)"
+40 -14
View File
@@ -370,6 +370,16 @@
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);
@@ -386,9 +396,9 @@
function getDlpHostFromSettings(settings) {
const routeHost = getCurrentHostFromHash();
if (routeHost) return routeHost;
if (isLikelyClientHost(routeHost)) return routeHost;
const bucketHost = getDlpHostFromBucketId(getDlpBucketIdFromHash());
if (bucketHost) return bucketHost;
if (isLikelyClientHost(bucketHost)) return bucketHost;
return getTrendsHostFromSettings(settings);
}
@@ -1466,7 +1476,8 @@
if (!settings || typeof settings !== "object") return "";
const landingpage = typeof settings.landingpage === "string" ? settings.landingpage : "";
const match = landingpage.match(/\/activity\/([^/]+)/);
return match && match[1] ? match[1] : "";
const host = match && match[1] ? decodeURIComponent(match[1]) : "";
return isLikelyClientHost(host) ? host : "";
}
function getTrendsPath(hash) {
@@ -1533,22 +1544,35 @@
.map(function (bucketId) { return bucketId.replace(/^aw-watcher-window_/i, ""); })
.filter(Boolean)
.filter(function (host) { return !/^unknown$/i.test(host); });
if (settingsHost && hosts.indexOf(settingsHost) >= 0) return settingsHost;
if (settingsHost) return settingsHost;
if (isLikelyClientHost(settingsHost) && hosts.indexOf(settingsHost) >= 0) return settingsHost;
hosts.sort();
return hosts[0] || "";
}
function rewriteUnknownCategoryBuilderQueryBody(body) {
if (typeof body !== "string") return body;
if (body.indexOf("aw-watcher-window_unknown") === -1 && body.indexOf("aw-watcher-afk_unknown") === -1) {
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);
}
}
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);
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;
}
function installCategoryBuilderNetworkPatch() {
@@ -1560,7 +1584,8 @@
window.fetch = function (input, init) {
try {
const url = typeof input === "string" ? input : String(input && input.url || "");
if (/\/api\/0\/query\/?$/i.test(url) && init && typeof init.body === "string") {
const isCategoryBuilderRoute = /^#\/settings\/category-builder(?:[/?#]|$)/i.test(window.location.hash || "");
if (isCategoryBuilderRoute && /\/api\/0\/query\/?$/i.test(url) && init && typeof init.body === "string") {
init = Object.assign({}, init, {
body: rewriteUnknownCategoryBuilderQueryBody(init.body)
});
@@ -1584,7 +1609,8 @@
proto.send = function (body) {
try {
const url = String(this.__awRuUrl || "");
if (/\/api\/0\/query\/?$/i.test(url) && typeof body === "string") {
const isCategoryBuilderRoute = /^#\/settings\/category-builder(?:[/?#]|$)/i.test(window.location.hash || "");
if (isCategoryBuilderRoute && /\/api\/0\/query\/?$/i.test(url) && typeof body === "string") {
body = rewriteUnknownCategoryBuilderQueryBody(body);
}
} catch (error) {
+2
View File
@@ -9,3 +9,5 @@ AW_SERVER_DATA_DIR=/var/lib/activitywatch
AW_SERVER_LOG_DIR=/var/log/activitywatch
AW_SERVER_USER=activitywatch
AW_SERVER_GROUP=activitywatch
AW_WORKTIME_REPORT_BASE=http://10.10.10.13:5610
AW_WORKTIME_TZ=Europe/Moscow
+246
View File
@@ -0,0 +1,246 @@
#!/usr/bin/env python3
from http.server import BaseHTTPRequestHandler, HTTPServer
import csv
import io
import json
import os
import urllib.request
from datetime import datetime, timezone, timedelta
from zoneinfo import ZoneInfo
AW = "http://127.0.0.1:5600/api/0"
REPORT_TZ = ZoneInfo(os.environ.get("AW_WORKTIME_TZ", "Europe/Moscow"))
def get(u):
with urllib.request.urlopen(u, timeout=30) as r:
return json.loads(r.read().decode())
def pts(s):
return datetime.fromisoformat(s.replace("Z", "+00:00")).astimezone(timezone.utc)
def report_today():
now_local = datetime.now(REPORT_TZ)
start_local = datetime(now_local.year, now_local.month, now_local.day, tzinfo=REPORT_TZ)
end_local = start_local + timedelta(days=1) - timedelta(seconds=1)
start = start_local.astimezone(timezone.utc)
end = end_local.astimezone(timezone.utc)
b = get(AW + "/buckets")
sb = next((k for k in b if k.startswith("aw-worktime-sessions_")), None)
if not sb:
return []
ev = get(f"{AW}/buckets/{sb}/events?limit=50000")
by = {}
for e in ev:
ts = pts(e.get("timestamp"))
if ts < start or ts > end:
continue
d = e.get("data") or {}
user = (d.get("username") or "").strip()
if not user:
continue
state = (d.get("state") or "").lower()
active = ("актив" in state) or (state == "active")
row = by.setdefault(user, {"active": set(), "first": None, "last": None, "rows": 0})
row["rows"] += 1
if active:
second = ts.replace(microsecond=0)
row["active"].add(second)
row["first"] = second if row["first"] is None or second < row["first"] else row["first"]
row["last"] = second if row["last"] is None or second > row["last"] else row["last"]
rows = []
full = int((end_local - start_local).total_seconds())
for user in sorted(by):
row = by[user]
active_seconds = len(row["active"])
rows.append({
"user": user,
"active_seconds": active_seconds,
"active_hhmm": "%02d:%02d" % (active_seconds // 3600, (active_seconds % 3600) // 60),
"first_activity": row["first"].isoformat().replace("+00:00", "Z") if row["first"] else "",
"last_activity": row["last"].isoformat().replace("+00:00", "Z") if row["last"] else "",
"idle_seconds": max(0, full - active_seconds),
"sessions_count": row["rows"],
})
return rows
def render_html(rows):
generated = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
date_local = datetime.now(REPORT_TZ).strftime("%Y-%m-%d")
trs = []
for row in rows:
trs.append(
"<tr>"
f"<td>{row['user']}</td>"
f"<td>{row['active_hhmm']}</td>"
f"<td>{row['active_seconds']}</td>"
f"<td>{row['first_activity']}</td>"
f"<td>{row['last_activity']}</td>"
f"<td>{row['idle_seconds']}</td>"
f"<td>{row['sessions_count']}</td>"
"</tr>"
)
if not trs:
trs.append('<tr><td colspan="7">No data for today yet.</td></tr>')
return f"""<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>AW-rus Worktime</title>
<style>
:root {{
color-scheme: light;
--bg: #f4f7fb;
--card: #ffffff;
--line: #dbe3ee;
--text: #0f172a;
--muted: #475569;
--accent: #0f766e;
--accent-2: #1d4ed8;
}}
* {{ box-sizing: border-box; }}
body {{
margin: 0;
font: 14px/1.45 "Segoe UI", "Noto Sans", sans-serif;
color: var(--text);
background:
radial-gradient(circle at top left, rgba(29,78,216,.08), transparent 28%),
radial-gradient(circle at top right, rgba(15,118,110,.10), transparent 24%),
var(--bg);
}}
.wrap {{ max-width: 1180px; margin: 0 auto; padding: 24px; }}
.hero {{
background: linear-gradient(135deg, #0f172a, #1e293b 58%, #0f766e);
color: #fff;
border-radius: 18px;
padding: 20px 22px;
box-shadow: 0 22px 60px rgba(15,23,42,.22);
}}
.hero h1 {{ margin: 0 0 8px; font-size: 28px; }}
.meta {{ color: rgba(255,255,255,.84); }}
.actions {{ margin-top: 14px; display: flex; gap: 10px; flex-wrap: wrap; }}
.actions a {{
text-decoration: none;
color: #fff;
background: rgba(255,255,255,.12);
border: 1px solid rgba(255,255,255,.18);
padding: 8px 12px;
border-radius: 999px;
}}
.card {{
margin-top: 18px;
background: var(--card);
border: 1px solid var(--line);
border-radius: 16px;
overflow: hidden;
box-shadow: 0 16px 40px rgba(15,23,42,.08);
}}
table {{ width: 100%; border-collapse: collapse; }}
th, td {{ padding: 12px 14px; border-bottom: 1px solid var(--line); text-align: left; }}
th {{ background: #eef4fb; color: var(--muted); font-weight: 600; position: sticky; top: 0; }}
tr:nth-child(even) td {{ background: rgba(148,163,184,.06); }}
.num {{ font-variant-numeric: tabular-nums; }}
.good {{ color: var(--accent); font-weight: 700; }}
.muted {{ color: var(--muted); }}
@media (max-width: 900px) {{
.wrap {{ padding: 14px; }}
.hero h1 {{ font-size: 22px; }}
.card {{ overflow-x: auto; }}
table {{ min-width: 820px; }}
}}
</style>
</head>
<body>
<div class="wrap">
<section class="hero">
<h1>RDP Worktime Report</h1>
<div class="meta">Date: {date_local} · Timezone: {REPORT_TZ} · Generated UTC: {generated}</div>
<div class="actions">
<a href="/reports/worktime/today?format=csv">Download CSV</a>
<a href="/reports/worktime/today">View JSON</a>
</div>
</section>
<section class="card">
<table>
<thead>
<tr>
<th>User</th>
<th>Active</th>
<th>Active sec</th>
<th>First activity</th>
<th>Last activity</th>
<th>Idle sec</th>
<th>Samples</th>
</tr>
</thead>
<tbody>
{''.join(trs)}
</tbody>
</table>
</section>
</div>
</body>
</html>"""
class H(BaseHTTPRequestHandler):
def do_GET(self):
if not self.path.startswith("/reports/worktime/today"):
self.send_response(404)
self.end_headers()
return
fmt = "json"
if "format=csv" in self.path:
fmt = "csv"
elif "format=html" in self.path:
fmt = "html"
rows = report_today()
if fmt == "csv":
out = io.StringIO()
writer = csv.DictWriter(
out,
fieldnames=[
"user",
"active_seconds",
"active_hhmm",
"first_activity",
"last_activity",
"idle_seconds",
"sessions_count",
],
)
writer.writeheader()
writer.writerows(rows)
data = out.getvalue().encode()
self.send_response(200)
self.send_header("Content-Type", "text/csv; charset=utf-8")
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
return
if fmt == "html":
data = render_html(rows).encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
return
obj = {
"generated_at_utc": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
"report_timezone": str(REPORT_TZ),
"rows": rows,
}
data = json.dumps(obj, ensure_ascii=False, indent=2).encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
HTTPServer(("0.0.0.0", 5610), H).serve_forever()
+16
View File
@@ -0,0 +1,16 @@
[Unit]
Description=AW Worktime Report API
After=network.target activitywatch-server.service
Wants=activitywatch-server.service
[Service]
Type=simple
EnvironmentFile=/etc/activitywatch/aw-server.env
ExecStart=/usr/bin/python3 /usr/local/bin/aw-worktime-api.py
Restart=always
RestartSec=2
User=activitywatch
Group=activitywatch
[Install]
WantedBy=multi-user.target
+56
View File
@@ -0,0 +1,56 @@
(function () {
var reportBase = "__AW_WORKTIME_REPORT_BASE__";
var reportUrl = reportBase + "/reports/worktime/today?format=html";
var existing = document.getElementById("aw-report-links");
if (!existing) return;
existing.innerHTML =
'RDP report: ' +
'<a href="' + reportUrl + '" style="color:#fcd34d" target="_blank">HTML</a> | ' +
'<a href="' + reportBase + '/reports/worktime/today?format=csv" style="color:#7dd3fc" target="_blank">CSV</a> | ' +
'<a href="' + reportBase + '/reports/worktime/today" style="color:#86efac" target="_blank">JSON</a> | ' +
'<a href="#" id="aw-report-toggle" style="color:#f9fafb">Panel</a>';
var panel = document.createElement("div");
panel.id = "aw-report-panel";
panel.style.cssText = [
"position:fixed",
"top:16px",
"right:16px",
"width:min(980px,calc(100vw - 32px))",
"height:min(760px,calc(100vh - 32px))",
"background:#fff",
"border:1px solid rgba(15,23,42,.15)",
"border-radius:12px",
"box-shadow:0 24px 80px rgba(15,23,42,.28)",
"overflow:hidden",
"z-index:100000",
"display:none"
].join(";");
panel.innerHTML =
'<div style="display:flex;align-items:center;justify-content:space-between;padding:10px 14px;background:#0f172a;color:#fff;font:600 13px/1.2 sans-serif">' +
'<div>RDP Worktime Report</div>' +
'<div style="display:flex;gap:12px;align-items:center">' +
'<a href="' + reportUrl + '" target="_blank" style="color:#93c5fd;text-decoration:none">Open</a>' +
'<a href="#" id="aw-report-close" style="color:#fff;text-decoration:none">Close</a>' +
"</div></div>" +
'<iframe src="' + reportUrl + '" title="RDP Worktime Report" style="border:0;width:100%;height:calc(100% - 42px);background:#fff"></iframe>';
document.body.appendChild(panel);
function openPanel(ev) {
if (ev) ev.preventDefault();
panel.style.display = "block";
}
function closePanel(ev) {
if (ev) ev.preventDefault();
panel.style.display = "none";
}
var toggle = document.getElementById("aw-report-toggle");
if (toggle) toggle.addEventListener("click", openPanel);
var close = panel.querySelector("#aw-report-close");
if (close) close.addEventListener("click", closePanel);
})();
+172
View File
@@ -0,0 +1,172 @@
#!/usr/bin/env python3
import json
import os
import urllib.error
import urllib.request
from datetime import datetime, timedelta, timezone
AW_URL = os.environ.get("AW_SERVER_URL", "http://127.0.0.1:5600")
HOST = os.environ.get("AW_WORKTIME_HOST", "SHARKON2025")
STATE_PATH = os.environ.get(
"AW_WORKTIME_UI_BRIDGE_STATE",
"/var/lib/activitywatch/aw-worktime-ui-bridge-state.json",
)
TIMEOUT = float(os.environ.get("AW_WORKTIME_UI_BRIDGE_TIMEOUT", "20"))
SESSIONS_BUCKET = f"aw-worktime-sessions_{HOST}"
AFK_BUCKET = f"aw-watcher-afk_{HOST}"
WINDOW_BUCKET = f"aw-watcher-window_{HOST}"
def _req(method: str, path: str, payload=None):
data = None
headers = {}
if payload is not None:
data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
headers["Content-Type"] = "application/json"
req = urllib.request.Request(AW_URL + path, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=TIMEOUT) as r:
raw = r.read()
if not raw:
return None
return json.loads(raw.decode("utf-8"))
def ensure_bucket(bucket_id: str, event_type: str, client: str):
payload = {"client": client, "type": event_type, "hostname": HOST}
try:
_req("POST", f"/api/0/buckets/{bucket_id}", payload)
except urllib.error.HTTPError as e:
if e.code != 304:
raise
def load_state():
try:
with open(STATE_PATH, "r", encoding="utf-8") as f:
data = json.load(f)
if isinstance(data, dict) and "last_ts" in data:
return data
except FileNotFoundError:
pass
except json.JSONDecodeError:
pass
return {"last_ts": "1970-01-01T00:00:00Z"}
def save_state(state):
os.makedirs(os.path.dirname(STATE_PATH), exist_ok=True)
tmp = STATE_PATH + ".tmp"
with open(tmp, "w", encoding="utf-8") as f:
json.dump(state, f, ensure_ascii=False)
os.replace(tmp, STATE_PATH)
def to_iso_utc(ts):
if ts.endswith("Z"):
return ts
return ts.replace("+00:00", "Z")
def parse_iso_utc(ts: str):
if ts.endswith("Z"):
ts = ts[:-1] + "+00:00"
return datetime.fromisoformat(ts).astimezone(timezone.utc)
def build_window_title(users, active_count):
if not users:
return "RDP idle"
return f"RDP active ({active_count}): " + ", ".join(users)
def _is_session_active(row_data):
if isinstance(row_data.get("active"), bool):
return row_data.get("active")
state = str(row_data.get("state", "")).strip().lower()
return state in {"active", "активно"}
def transform(events):
out_afk = []
out_win = []
last_ts = None
grouped = {}
for e in events:
ts = e.get("timestamp")
if not ts:
continue
grouped.setdefault(ts, []).append(e)
for ts in sorted(grouped.keys()):
rows = grouped[ts]
duration = max(float(r.get("duration", 0.0)) for r in rows)
active_users = []
for r in rows:
data = r.get("data") or {}
user = str(data.get("username", "")).strip()
if user and _is_session_active(data):
active_users.append(user)
active_users = sorted(set(active_users))
active_count = len(active_users)
is_active = active_count > 0
afk_data = {"status": "not-afk" if is_active else "afk", "source": "aw-worktime-ui-bridge"}
out_afk.append({"timestamp": ts, "duration": duration, "data": afk_data})
win_data = {
"app": "RDP",
"title": build_window_title(active_users, active_count),
"source": "aw-worktime-ui-bridge",
}
out_win.append({"timestamp": ts, "duration": duration, "data": win_data})
last_ts = ts
return out_afk, out_win, last_ts
def main():
state = load_state()
last_ts = state.get("last_ts", "1970-01-01T00:00:00Z")
ensure_bucket(AFK_BUCKET, "afkstatus", "aw-worktime-ui-bridge")
ensure_bucket(WINDOW_BUCKET, "currentwindow", "aw-worktime-ui-bridge")
now_utc = datetime.now(timezone.utc)
recent = _req("GET", f"/api/0/buckets/{SESSIONS_BUCKET}/events?limit=5000") or []
if not recent:
return
try:
last_dt = parse_iso_utc(last_ts)
except Exception:
last_dt = datetime(1970, 1, 1, tzinfo=timezone.utc)
events = []
for e in recent:
ts = e.get("timestamp")
if not ts:
continue
try:
if parse_iso_utc(ts) > last_dt:
events.append(e)
except Exception:
continue
if not events:
return
afk_events, win_events, new_last_ts = transform(events)
if not afk_events or not win_events or not new_last_ts:
return
_req("POST", f"/api/0/buckets/{AFK_BUCKET}/events", afk_events)
_req("POST", f"/api/0/buckets/{WINDOW_BUCKET}/events", win_events)
save_state({"last_ts": new_last_ts})
print(f"posted_afk={len(afk_events)} posted_win={len(win_events)} last_ts={new_last_ts}")
if __name__ == "__main__":
main()
+15
View File
@@ -0,0 +1,15 @@
[Unit]
Description=AW Worktime UI bridge (sessions -> afk/window)
After=network-online.target activitywatch-server.service
Wants=network-online.target
[Service]
Type=oneshot
Environment=AW_SERVER_URL=http://127.0.0.1:5600
Environment=AW_WORKTIME_HOST=SHARKON2025
ExecStart=/usr/bin/python3 /usr/local/bin/aw-worktime-ui-bridge.py
User=root
Group=root
[Install]
WantedBy=multi-user.target
+11
View File
@@ -0,0 +1,11 @@
[Unit]
Description=Run AW Worktime UI bridge every 30 seconds
[Timer]
OnBootSec=20s
OnUnitActiveSec=30s
Unit=aw-worktime-ui-bridge.service
Persistent=true
[Install]
WantedBy=timers.target
+35
View File
@@ -24,6 +24,11 @@ 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
@@ -89,6 +94,36 @@ 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
+95
View File
@@ -0,0 +1,95 @@
#!/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 echo "$RESP" | jq -e '.version' > /dev/null 2>&1; then
VERSION=$(echo "$RESP" | jq -r '.version')
echo -e "${GREEN}OK${NC} (aw-server v$VERSION)"
else
echo -e "${RED}FAILED${NC} (cannot reach $SERVER)"
exit 1
fi
echo ""
# Проверка бакетов
echo "--- Buckets ---"
printf "%-45s %-8s %-22s %s\n" "BUCKET" "EVENTS" "LAST EVENT" "STATUS"
printf "%-45s %-8s %-22s %s\n" "---------------------------------------------" "--------" "----------------------" "------"
BUCKETS=(
"aw-dlp-endpoint-signals"
"aw-dlp-incidents"
"aw-dlp-review"
"aw-dlp-rules"
"aw-watcher-afk"
"aw-watcher-window"
"aw-session-events"
"aw-worktime-sessions"
)
for bucket in "${BUCKETS[@]}"; do
bucket_full="${bucket}_${HOSTNAME_FILTER}"
# Получаем последний event
EVENT_DATA=$(no_proxy=10.10.10.13 curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/buckets/$bucket_full/events?limit=1" 2>&1)
LAST_ID=$(echo "$EVENT_DATA" | jq '.[0].id // 0')
LAST_TS=$(echo "$EVENT_DATA" | jq -r '.[0].timestamp // "no events"')
# Вычисляем возраст
if [ "$LAST_TS" != "no events" ] && [ -n "$LAST_TS" ]; then
EVENT_EPOCH=$(date -d "$LAST_TS" +%s 2>/dev/null || echo 0)
if [ "$EVENT_EPOCH" -gt 0 ]; then
AGE_SEC=$((NOW - EVENT_EPOCH))
if [ $AGE_SEC -lt 3600 ]; then
AGE="$((AGE_SEC / 60))m ago"
STATUS="${GREEN}FRESH${NC}"
elif [ $AGE_SEC -lt 86400 ]; then
AGE="$((AGE_SEC / 3600))h ago"
STATUS="${YELLOW}STALE${NC}"
else
AGE="$((AGE_SEC / 86400))d ago"
STATUS="${RED}DEAD${NC}"
fi
else
AGE="unknown"
STATUS="${RED}?${NC}"
fi
else
AGE="none"
LAST_ID="0"
STATUS="${RED}EMPTY${NC}"
fi
printf "%-45s %-8s %-22s %b\n" "$bucket_full" "$LAST_ID" "$LAST_TS ($AGE)" "$STATUS"
done
echo ""
# Проверка CORS
echo "--- CORS Check ---"
CORS_RESP=$(no_proxy=10.10.10.13 curl -s --connect-timeout 10 --max-time 15 -o /dev/null -w '%{http_code}' -H "Origin: http://10.10.10.13:5600" "$SERVER/api/0/settings/" 2>&1)
if [ "$CORS_RESP" = "200" ]; then
echo -e "${GREEN}CORS: OK${NC} (HTTP 200)"
else
echo -e "${RED}CORS: FAIL${NC} (HTTP $CORS_RESP)"
fi
echo ""
echo "=== Check Complete ==="
echo "Timestamp: $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
+156
View File
@@ -0,0 +1,156 @@
#!/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 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')"
+303
View File
@@ -0,0 +1,303 @@
# ActivityWatch-Russian Architecture Diagram
## High-Level Architecture
```mermaid
graph TB
subgraph "Windows Clients"
A[ActivityWatch Watchers]
B[DLP Endpoint Collector]
C[Browser Domains Collector]
D[Email Outbound Collector]
E[Worktime Session Collector]
end
subgraph "Linux Server"
F[ActivityWatch Server]
G[PostgreSQL Database]
H[Aggregation Scripts]
end
subgraph "Monitoring & Integration"
I[pfSense Firewall]
J[Prometheus Exporter]
end
subgraph "Visualization"
K[Grafana]
L[Prometheus]
M[ActivityWatch WebUI]
end
A -->|HTTP API| F
B -->|Events| F
C -->|Events| F
D -->|Events| F
E -->|Events| F
F -->|Store| G
F -->|API| M
I -->|Logs| F
H -->|Read/Write| G
H -->|Read| F
J -->|Metrics| L
L -->|Scrape| J
F -->|API| J
K -->|Query| L
K -->|Query| G
```
## Component Interactions
### Data Flow
```
Windows Collectors → ActivityWatch Server → PostgreSQL Database
Aggregation Scripts
Grafana Dashboards
```
### DLP Monitoring Flow
```
User Activity (clipboard/print/USB)
DLP Endpoint Collector (PowerShell)
DLP Policy Evaluation
ActivityWatch Events (via HTTP API)
PostgreSQL Database
Aggregation Scripts (Python)
Grafana DLP Dashboard
```
### Browser Monitoring Flow
```
User Browser Activity
Browser Domains Collector (PowerShell)
Domain Extraction & Categorization
DLP Rule Check
ActivityWatch Events
WebUI Domain Dashboard
```
### Email Monitoring Flow
```
Outlook / SMTP Activity
Email Outbound Collector (PowerShell)
Email Policy Evaluation
ActivityWatch Events
Grafana Email Dashboard
```
### pfSense Integration Flow
```
pfSense Firewall Logs
pfSense Poller (Python)
HTTP API Query
ActivityWatch Events
Network Activity Dashboard
```
### Metrics Flow
```
ActivityWatch Server
Prometheus Exporter (Python)
HTTP Metrics Endpoint (port 9398)
Prometheus Scraping
Grafana Dashboards
```
## Component Details
### Windows Collectors
| Component | Language | Purpose | Output |
|-----------|----------|---------|--------|
| DLP Endpoint Collector | PowerShell | Monitor clipboard, print, USB | ActivityWatch events |
| Browser Domains Collector | PowerShell | Track visited domains | ActivityWatch events |
| Email Outbound Collector | PowerShell | Monitor sent emails | ActivityWatch events |
| Worktime Session Collector | PowerShell | Track work sessions | ActivityWatch events |
### Server Components
| Component | Language | Purpose | Dependencies |
|-----------|----------|---------|--------------|
| ActivityWatch Server | Rust | Core monitoring platform | SQLite/PostgreSQL |
| Aggregation Scripts | Python | Process DLP events | psycopg2, requests |
| Prometheus Exporter | Python | Export metrics to Prometheus | prometheus_client |
### Integration Points
| Integration | Protocol | Purpose |
|-------------|----------|---------|
| pfSense → AW | HTTP API | Firewall log collection |
| AW → Grafana | PostgreSQL | Direct database access |
| AW → Prometheus | HTTP /metrics | Metrics scraping |
| Collectors → AW | HTTP /api/buckets | Event submission |
## Deployment Architecture
```
Domain Controller
↓ (GPO / Scheduled Tasks)
Windows Workstations (user1, user2, ...)
↓ (PowerShell Collectors)
ActivityWatch Server (Linux)
PostgreSQL Database
Grafana + Prometheus Stack
```
## File System Structure
```
ActivityWatch-Russian/
├── windows/ # Windows collectors
│ ├── dlp-endpoint-signals-collector.ps1
│ ├── browser-domains-native-collector.ps1
│ ├── email-outbound-collector.ps1
│ ├── worktime-session-collector.ps1
│ ├── deploy-domain-users.ps1 # Deployment script
│ └── ActivityWatch.Windows.Common.psm1
├── scripts/ # Server scripts
│ ├── aggregate_dlp_events.py
│ └── merge_aw_server_dbs.py
├── aw-server/ # WebUI patches
│ ├── aw-ru-patch.js
│ └── aw-sw-cleanup.js
├── pfsense/ # pfSense integration
│ └── pfsense-aw-poller.py
├── grafana-1c/ # Monitoring stack
│ ├── docker-compose.yml
│ ├── prometheus/
│ ├── grafana/
│ └── sql-exporter/
│ └── collectors/
│ └── aw_activitywatch.py
└── ansible/ # Automation
├── playbooks/
└── group_vars/
```
## Network Ports
| Service | Port | Protocol | Purpose |
|---------|------|----------|---------|
| ActivityWatch Server | 5600 | HTTP | WebUI & API |
| ActivityWatch Server | 5666 | HTTP | WebSocket |
| Prometheus | 9090 | HTTP | Metrics UI |
| Prometheus Exporter | 9398 | HTTP | AW metrics endpoint |
| Grafana | 3000 | HTTP | Dashboards |
| PostgreSQL | 5432 | TCP | Database |
## Data Models
### ActivityWatch Event Structure
```json
{
"timestamp": "2024-01-01T12:00:00Z",
"duration": 60.0,
"data": {
"title": "Window Title",
"url": "https://example.com",
"app": "chrome.exe",
"type": "activity"
}
}
```
### DLP Incident Structure
```json
{
"timestamp": "2024-01-01T12:00:00Z",
"type": "clipboard",
"rule": "credit_card_pattern",
"severity": "high",
"user": "user1",
"host": "WORKSTATION01",
"screenshot": "path/to/screenshot.png"
}
```
## Key Dependencies
### Windows Dependencies
- PowerShell 5.1+
- .NET Framework 4.8
- ActivityWatch Windows binaries
### Linux Dependencies
- Python 3.8+
- PostgreSQL 12+
- Docker & Docker Compose
- Rust (for AW server compilation)
### Python Dependencies
```txt
psycopg2-binary
requests
prometheus_client
```
## Security Considerations
### Data Flow Security
- Collectors → Server: HTTP (can be upgraded to HTTPS)
- Server → Database: Local connection or SSL
- pfSense → Server: HTTP over VPN
- Prometheus → Exporter: HTTP internal network
### Access Control
- Windows collectors run as user context
- Server runs as dedicated service user
- Database access restricted to specific users
- Grafana authenticated via LDAP/Local users
## Monitoring & Alerting
### Metrics Collected
- Events per bucket
- Active hosts count
- Collector heartbeat status
- DLP incident rate
- Database query performance
### Alerting Rules
- Collector offline > 30 minutes
- High DLP incident rate
- Database connection failures
- Disk space < 20%
+278
View File
@@ -0,0 +1,278 @@
# ActivityWatch-Russian - Простая архитектура
## Общая схема (сверху вниз)
```
┌─────────────────────────────────────────────────────────────────┐
│ ПОЛЬЗОВАТЕЛИ │
│ (Windows рабочие станции) │
└──────────────────────────┬──────────────────────────────────────┘
│ Коллекторы (PowerShell)
┌──────────────────────────▼──────────────────────────────────────┐
│ Windows Collectors (на каждой машине) │
├──────────────────────────────────────────────────────────────────┤
│ • DLP Endpoint Collector - буфер, печать, USB │
│ • Browser Domains Collector - посещаемые сайты │
│ • Email Outbound Collector - исходящая почта │
│ • Worktime Session Collector - рабочее время │
└──────────────────────────┬──────────────────────────────────────┘
│ HTTP API (отправка событий)
┌──────────────────────────▼──────────────────────────────────────┐
│ ActivityWatch Server (Linux) │
├──────────────────────────────────────────────────────────────────┤
│ • Принимает события от коллекторов │
│ • Хранит в базе данных │
│ • Предоставляет WebUI и API │
└──────────────────────────┬──────────────────────────────────────┘
│ Хранение данных
┌──────────────────────────▼──────────────────────────────────────┐
│ PostgreSQL Database │
├──────────────────────────────────────────────────────────────────┤
│ • События активности │
│ • DLP инциденты │
│ • Метаданные хостов и пользователей │
└──────────────────────────┬──────────────────────────────────────┘
│ Чтение/Обработка
┌──────────────────────────▼──────────────────────────────────────┐
│ Обработка и Агрегация │
├──────────────────────────────────────────────────────────────────┤
│ • aggregate_dlp_events.py - агрегация DLP событий │
│ • pfSense Poller - логи firewall │
│ • Prometheus Exporter - метрики для мониторинга │
└──────────────────────────┬──────────────────────────────────────┘
│ Визуализация
┌──────────────────────────▼──────────────────────────────────────┐
│ Grafana + Prometheus Stack │
├──────────────────────────────────────────────────────────────────┤
│ • Prometheus - сбор метрик │
│ • Grafana - дашборды и визуализация │
│ • SQL Exporter - прямой доступ к PostgreSQL │
└──────────────────────────────────────────────────────────────────┘
```
## Потоки данных (по направлениям)
### Поток 1: DLP мониторинг
```
Пользователь → DLP Collector → Проверка правил → AW Server → PostgreSQL → Grafana
```
### Поток 2: Мониторинг браузеров
```
Браузер → Browser Collector → Категоризация → AW Server → WebUI Dashboard
```
### Поток 3: Мониторинг почты
```
Outlook/SMTP → Email Collector → Проверка правил → AW Server → Grafana
```
### Поток 4: pfFirewall логи
```
pfSense → Poller (Python) → HTTP API → AW Server → Grafana
```
### Поток 5: Метрики
```
AW Server → Exporter (Python) → /metrics → Prometheus → Grafana
```
## Компоненты по уровням
### Уровень 1: Сбор данных (Windows)
```
┌─────────────────────────────────────┐
│ Windows Collectors (PowerShell) │
│ ┌───────────────────────────────┐ │
│ │ • dlp-endpoint-signals-collector│ │
│ │ • browser-domains-collector │ │
│ │ • email-outbound-collector │ │
│ │ • worktime-session-collector │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
```
### Уровень 2: Хранение и обработка (Linux Server)
```
┌─────────────────────────────────────┐
│ ActivityWatch Server + PostgreSQL │
│ ┌───────────────────────────────┐ │
│ │ • aw-server (Rust) │ │
│ │ • PostgreSQL Database │ │
│ │ • WebUI (с RU патчами) │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
```
### Уровень 3: Интеграции и обработка
```
┌─────────────────────────────────────┐
│ Интеграции и Скрипты (Python) │
│ ┌───────────────────────────────┐ │
│ │ • aggregate_dlp_events.py │ │
│ │ • pfsense-aw-poller.py │ │
│ │ • aw_activitywatch_exporter │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
```
### Уровень 4: Визуализация и мониторинг
```
┌─────────────────────────────────────┐
│ Grafana + Prometheus (Docker) │
│ ┌───────────────────────────────┐ │
│ │ • Prometheus (порт 9090) │ │
│ │ • Grafana (порт 3000) │ │
│ │ • SQL Exporter (порт 9398) │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
```
## Развертывание
### На Windows рабочих станциях
```
1. Установка ActivityWatch (через InnoSetup installer)
2. Развертывание коллекторов (deploy-domain-users.ps1)
3. Настройка scheduled tasks
4. Конфигурация DLP политик
```
### На Linux сервере
```
1. Установка ActivityWatch Server
2. Настройка PostgreSQL
3. Применение RU патчей к WebUI
4. Запуск скриптов агрегации
5. Запуск Prometheus Exporter
```
### Мониторинг стек
```
1. Docker Compose развертывание
2. Настройка Prometheus scrape config
3. Импорт Grafana дашбордов
4. Настройка алертов
```
## Ключевые файлы
```
ActivityWatch-Russian/
├── windows/ # Windows коллекторы
│ ├── dlp-endpoint-signals-collector.ps1
│ ├── browser-domains-native-collector.ps1
│ ├── email-outbound-collector.ps1
│ └── deploy-domain-users.ps1
├── scripts/ # Серверные скрипты
│ └── aggregate_dlp_events.py
├── aw-server/ # Патчи WebUI
│ └── aw-ru-patch.js
├── pfsense/ # Интеграция pfSense
│ └── pfsense-aw-poller.py
└── grafana-1c/ # Мониторинг стек
├── docker-compose.yml
├── prometheus/prometheus.yml
└── grafana/dashboards/
```
## Порты
| Компонент | Порт | Протокол |
|-----------|------|----------|
| ActivityWatch WebUI | 5600 | HTTP |
| ActivityWatch API | 5666 | WebSocket |
| Prometheus | 9090 | HTTP |
| Prometheus Exporter | 9398 | HTTP |
| Grafana | 3000 | HTTP |
| PostgreSQL | 5432 | TCP |
## Быстрый старт
### Запуск мониторинг стека
```bash
cd grafana-1c
docker-compose up -d
```
### Развертывание на Windows
```powersShell
.\windows\deploy-domain-users.ps1
```
### Агрегация DLP событий
```bash
python3 scripts/aggregate_dlp_events.py
```
## Связи между компонентами
```
Windows Collectors
├─► ActivityWatch Server (HTTP API)
│ │
│ ├─► PostgreSQL (хранение)
│ │
│ ├─► WebUI (RU патчи)
│ │
│ └─► Prometheus Exporter (метрики)
│ │
│ └─► Prometheus
│ │
│ └─► Grafana
pfSense Firewall
└─► pfSense Poller
└─► ActivityWatch Server
└─► PostgreSQL
```
## Основные сценарии
### Сценарий 1: Пользователь копирует конфиденциальные данные
```
1. Пользователь копирует текст в буфер обмена
2. DLP Collector перехватывает событие
3. Проверка по DLP правилам
4. При совпадении → скриншот + запись инцидента
5. Отправка в ActivityWatch Server
6. Сохранение в PostgreSQL
7. Отображение в Grafana DLP Dashboard
```
### Сценарий 2: Пользователь посещает запрещенный сайт
```
1. Пользователь открывает сайт в браузере
2. Browser Collector определяет домен
3. Проверка по спискам и DLP правилам
4. Категоризация сайта
5. Отправка события в ActivityWatch
6. Отображение в WebUI Dashboard
```
### Сценарий 3: Мониторинг метрик
```
1. Prometheus Exporter опрашивает AW API
2. Сбор метрик (события, хосты, коллекторы)
3. Экспорт в формате Prometheus
4. Prometheus scrapes endpoint /metrics
5. Grafana строит графики
```
+128
View File
@@ -0,0 +1,128 @@
# Onboarding по кодовой базе AWatch-rus
Этот документ — быстрый вход для новичка: **что лежит где**, **как всё связано** и **что читать дальше**.
## 1) Что это за репозиторий
`AWatch-rus` — это не один сервис, а **инфраструктурный набор** для развёртывания и сопровождения ActivityWatch в прод-подобной среде:
- Proxmox/LXC-подготовка,
- установка и настройка AW Server,
- русификация Web UI,
- автоматизация через Ansible,
- клиентский rollout для Windows/Linux,
- дополнительная телеметрия (в т.ч. pfSense poller и DLP-сигналы),
- эксплуатационные runbook/operations документы.
Идея: чтобы развёртывание было **повторяемым**, а не «ручной магией в один вечер».
## 2) Карта проекта (по папкам)
### `docs/`
Главный источник истины по процессам.
- `preparation.md` — входные данные, prerequisites, что нужно до старта.
- `deployment.md` — базовый серверный деплой.
- `runbook.md` — быстрые операционные действия и проверки.
- `operations.md` — сопровождение, бэкапы, rollback.
- `windows/*` — отдельная ветка документации по Windows-оркестрации.
- `1C_GRAFANA_DEPLOYMENT_RU.md`, `pfsense.md`, `linux-client.md` — специализированные подсистемы.
### `proxmox/`
Скрипты ранней инфраструктурной фазы:
- `create-ct.sh` — создание/подготовка контейнера,
- `push-aw-artifacts.sh` — доставка артефактов и конфигов.
### `aw-server/`
Серверная «сердцевина»:
- `install_aw_server.sh` — установка AW Server,
- `apply_webui_ru_patch.sh` + `aw-ru-patch.js` — русификация UI,
- `activitywatch-server.service` — unit для systemd,
- `aw-server.env.example` — шаблон переменных окружения,
- `settings/*.json` — конфигурация представлений/классов.
### `ansible/`
Идемпотентная автоматизация (вместо ручных команд):
- playbook'и для серверного деплоя,
- сценарии provisioning + deploy,
- `group_vars/*.example.yml` и `inventory.example.ini` как шаблоны входных данных.
### `windows/`
PowerShell toolkit для клиентской стороны:
- `deploy-ensemble.ps1` — оркестратор,
- `deploy-single-user.ps1`, `deploy-domain-users.ps1` — сценарии установки,
- `validate-deployment.ps1` — post-check,
- `ActivityWatch.Windows.Common.psm1` — общая библиотека функций,
- скрипты DLP/browser telemetry.
### `pfsense/`
Отдельный poller для pfSense API + systemd unit.
### `grafana-1c/`
Набор для SQL exporter + Prometheus + Grafana дашбордов по 1C метрикам.
### `scripts/`
Утилиты и quality gates:
- `quality-gate.sh` — базовый preflight,
- инсталляторы Linux-клиента и console/ssh logger режимов.
### `secrets/`
Только шаблоны. Реальные секреты в репозиторий не кладутся.
## 3) Как компоненты связаны в потоке
Типовой pipeline:
1. Подготовка параметров (`docs/preparation.md`, `secrets/*.example`).
2. Provisioning контейнера в Proxmox (`proxmox/`).
3. Установка/настройка AW Server (`aw-server/`).
4. Включение автозапуска и проверка (`systemd` + `docs/runbook.md`).
5. Rollout клиентов (обычно `windows/`, иногда `scripts/install_aw_linux_client.sh`).
6. Эксплуатация и изменения через `docs/operations.md`.
7. При необходимости — расширение мониторинга (`grafana-1c/`, `pfsense/`).
## 4) Что важно понять в первую очередь
1. **Репозиторий документ-ориентированный**: сначала читаешь `docs/`, потом запускаешь скрипты.
2. **Шаблоны `.example` — обязательная точка входа**: не редактируй скрипты вместо заполнения переменных.
3. **Есть два режима работы**:
- ручной/полуручной (bash + runbook),
- автоматизированный (Ansible).
4. **Windows часть — полноценный под-проект** с собственными deploy/validate практиками.
5. **Безопасность**: никаких секретов в git; rollback и backup — не опция, а стандарт процесса.
## 5) Рекомендованный порядок изучения (первые 2–3 часа)
1. `README.md` — получить общую картину.
2. `docs/preparation.md` — понять входные параметры.
3. `docs/deployment.md` — увидеть «сквозной» серверный сценарий.
4. `docs/runbook.md` и `docs/operations.md` — как жить с системой после деплоя.
5. `aw-server/install_aw_server.sh` и `aw-server/activitywatch-server.service` — как реально стартует сервис.
6. `windows/deploy-ensemble.ps1` + `windows/validate-deployment.ps1` — клиентская фаза.
7. `ansible/README.md` и ключевые playbook'и — переход к промышленной автоматизации.
## 6) Практические подсказки для первого вклада
- Начни с правок документации или `.example`-шаблонов — это самый безопасный вход.
- Перед изменениями в скриптах сравни, нет ли уже Ansible-аналога (лучше поддерживать один «официальный» путь).
- Любая новая переменная должна быть отражена:
1) в `.example` файле,
2) в docs,
3) в проверках/валидации (если применимо).
- Для Windows-скриптов используй общие функции из `ActivityWatch.Windows.Common.psm1`, чтобы не дублировать логику.
## 7) Куда смотреть дальше (углубление)
- Если интересует эксплуатация и инциденты: `docs/runbook.md`, `docs/operations.md`, `docs/windows/troubleshooting.md`.
- Если интересует автодеплой: `ansible/provision_proxmox_ct_and_deploy_aw.yml` и `ansible/tasks/`.
- Если интересует наблюдаемость: `grafana-1c/` + `pfsense/` + `prometheus`/`alerts` конфиги.
- Если интересует hardening и DLP: `windows/hardening-recovery.ps1`, `docs/dlp-gap-analysis.md`.
---
Если ты новичок в проекте, практичный старт: разверни тестовый стенд по `docs/deployment.md`, затем прогоняй валидации из `docs/runbook.md` и `windows/validate-deployment.ps1`.
+918
View File
@@ -0,0 +1,918 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ActivityWatch-Russian - Architecture Map</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
h1 {
color: white;
text-align: center;
margin-bottom: 30px;
font-size: 2.5em;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.legend {
background: rgba(255,255,255,0.95);
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.legend h2 {
margin-bottom: 15px;
color: #333;
}
.legend-items {
display: flex;
flex-wrap: wrap;
gap: 15px;
}
.legend-item {
display: flex;
align-items: center;
gap: 8px;
}
.legend-color {
width: 30px;
height: 30px;
border-radius: 5px;
border: 2px solid #333;
}
.architecture-map {
background: rgba(255,255,255,0.95);
border-radius: 15px;
padding: 30px;
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
position: relative;
min-height: 800px;
}
.layer {
margin-bottom: 40px;
position: relative;
}
.layer-title {
font-size: 1.3em;
font-weight: bold;
color: #333;
margin-bottom: 15px;
padding: 10px;
background: #f0f0f0;
border-radius: 5px;
border-left: 4px solid #667eea;
}
.components-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.component {
background: white;
border-radius: 10px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
cursor: pointer;
transition: all 0.3s ease;
position: relative;
border: 3px solid transparent;
}
.component:hover {
transform: translateY(-5px);
box-shadow: 0 8px 16px rgba(0,0,0,0.2);
}
.component.selected {
border-color: #667eea;
background: #f0f4ff;
}
.component.windows-client {
border-top: 4px solid #ff6b6b;
}
.component.linux-server {
border-top: 4px solid #4ecdc4;
}
.component.integration {
border-top: 4px solid #ffe66d;
}
.component.monitoring {
border-top: 4px solid #95e1d3;
}
.component.external {
border-top: 4px solid #dda0dd;
}
.component h3 {
color: #333;
margin-bottom: 10px;
font-size: 1.1em;
}
.component-description {
color: #666;
font-size: 0.9em;
margin-bottom: 10px;
}
.component-tech {
display: flex;
gap: 5px;
flex-wrap: wrap;
}
.tech-badge {
background: #f0f0f0;
padding: 3px 8px;
border-radius: 12px;
font-size: 0.75em;
color: #555;
}
.connections {
margin-top: 20px;
padding: 15px;
background: #f9f9f9;
border-radius: 8px;
display: none;
}
.connections.active {
display: block;
}
.connections h4 {
margin-bottom: 10px;
color: #333;
}
.connection-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px;
margin-bottom: 5px;
background: white;
border-radius: 5px;
border-left: 3px solid #667eea;
}
.connection-protocol {
background: #667eea;
color: white;
padding: 2px 6px;
border-radius: 4px;
font-size: 0.75em;
}
.connection-direction {
color: #999;
font-size: 1.2em;
}
.info-panel {
position: fixed;
right: 20px;
top: 20px;
width: 350px;
background: white;
border-radius: 10px;
padding: 20px;
box-shadow: 0 8px 32px rgba(0,0,0,0.2);
max-height: 80vh;
overflow-y: auto;
display: none;
z-index: 1000;
}
.info-panel.active {
display: block;
}
.info-panel h3 {
color: #667eea;
margin-bottom: 15px;
border-bottom: 2px solid #667eea;
padding-bottom: 10px;
}
.info-panel-close {
position: absolute;
top: 10px;
right: 10px;
background: #ff6b6b;
color: white;
border: none;
width: 30px;
height: 30px;
border-radius: 50%;
cursor: pointer;
font-size: 1.2em;
}
.info-section {
margin-bottom: 15px;
}
.info-section h4 {
color: #333;
margin-bottom: 8px;
font-size: 0.95em;
}
.info-section p, .info-section ul {
color: #666;
font-size: 0.85em;
line-height: 1.5;
}
.info-section ul {
padding-left: 20px;
}
.flow-diagram {
background: #f9f9f9;
padding: 15px;
border-radius: 8px;
margin-top: 15px;
}
.flow-step {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
}
.flow-arrow {
color: #667eea;
font-size: 1.5em;
}
.search-box {
background: white;
border-radius: 10px;
padding: 15px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.search-box input {
width: 100%;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 1em;
transition: border-color 0.3s;
}
.search-box input:focus {
outline: none;
border-color: #667eea;
}
.component.hidden {
display: none;
}
@media (max-width: 768px) {
.components-grid {
grid-template-columns: 1fr;
}
.info-panel {
width: 90%;
right: 5%;
}
}
</style>
</head>
<body>
<div class="container">
<h1>ActivityWatch-Russian - Architecture Map</h1>
<div class="search-box">
<input type="text" id="searchInput" placeholder="🔍 Поиск компонентов...">
</div>
<div class="legend">
<h2>Легенда</h2>
<div class="legend-items">
<div class="legend-item">
<div class="legend-color" style="background: #ff6b6b;"></div>
<span>Windows Clients</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #4ecdc4;"></div>
<span>Linux Server</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #ffe66d;"></div>
<span>Integration Layer</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #95e1d3;"></div>
<span>Monitoring Stack</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #dda0dd;"></div>
<span>External Systems</span>
</div>
</div>
</div>
<div class="architecture-map">
<!-- Windows Clients Layer -->
<div class="layer">
<div class="layer-title">🖥️ Windows Clients - Сбор данных</div>
<div class="components-grid">
<div class="component windows-client" data-id="dlp-endpoint" data-search="dlp endpoint collector clipboard print usb">
<h3>DLP Endpoint Collector</h3>
<p class="component-description">Мониторинг clipboard, печати и USB для обнаружения утечек данных</p>
<div class="component-tech">
<span class="tech-badge">PowerShell</span>
<span class="tech-badge">Windows API</span>
</div>
</div>
<div class="component windows-client" data-id="browser-domains" data-search="browser domains collector web monitoring">
<h3>Browser Domains Collector</h3>
<p class="component-description">Отслеживание посещаемых доменов с категоризацией сайтов</p>
<div class="component-tech">
<span class="tech-badge">PowerShell</span>
<span class="tech-badge">Window API</span>
</div>
</div>
<div class="component windows-client" data-id="email-outbound" data-search="email outbound collector outlook smtp">
<h3>Email Outbound Collector</h3>
<p class="component-description">Мониторинг исходящей почты через Outlook и SMTP</p>
<div class="component-tech">
<span class="tech-badge">PowerShell</span>
<span class="tech-badge">Outlook API</span>
</div>
</div>
<div class="component windows-client" data-id="worktime-session" data-search="worktime session collector time tracking">
<h3>Worktime Session Collector</h3>
<p class="component-description">Отслеживание рабочих сессий и учет времени</p>
<div class="component-tech">
<span class="tech-badge">PowerShell</span>
<span class="tech-badge">Windows API</span>
</div>
</div>
</div>
</div>
<!-- Linux Server Layer -->
<div class="layer">
<div class="layer-title">🐧 Linux Server - Хранение и обработка</div>
<div class="components-grid">
<div class="component linux-server" data-id="aw-server" data-search="activitywatch server rust api">
<h3>ActivityWatch Server</h3>
<p class="component-description">Основной сервер для приема, хранения и обработки событий</p>
<div class="component-tech">
<span class="tech-badge">Rust</span>
<span class="tech-badge">HTTP API</span>
</div>
</div>
<div class="component linux-server" data-id="postgresql" data-search="postgresql database storage">
<h3>PostgreSQL Database</h3>
<p class="component-description">Основное хранилище событий, метаданных и конфигураций</p>
<div class="component-tech">
<span class="tech-badge">PostgreSQL</span>
<span class="tech-badge">SQL</span>
</div>
</div>
<div class="component linux-server" data-id="webui" data-search="webui russian localization patches">
<h3>WebUI with RU Patches</h3>
<p class="component-description">Веб-интерфейс с русской локализацией и DLP компонентами</p>
<div class="component-tech">
<span class="tech-badge">JavaScript</span>
<span class="tech-badge">Vue.js</span>
</div>
</div>
</div>
</div>
<!-- Integration Layer -->
<div class="layer">
<div class="layer-title">🔗 Integration Layer - Интеграции</div>
<div class="components-grid">
<div class="component integration" data-id="pfsense-poller" data-search="pfsense poller firewall logs">
<h3>pfSense Poller</h3>
<p class="component-description">Сбор логов с pfSense firewall и отправка в ActivityWatch</p>
<div class="component-tech">
<span class="tech-badge">Python</span>
<span class="tech-badge">HTTP API</span>
</div>
</div>
<div class="component integration" data-id="dlp-aggregation" data-search="dlp aggregation scripts postgresql">
<h3>DLP Aggregation Scripts</h3>
<p class="component-description">Агрегация DLP событий из ActivityWatch в PostgreSQL</p>
<div class="component-tech">
<span class="tech-badge">Python</span>
<span class="tech-badge">psycopg2</span>
</div>
</div>
<div class="component integration" data-id="prometheus-exporter" data-search="prometheus exporter metrics">
<h3>Prometheus Exporter</h3>
<p class="component-description">Экспорт метрик ActivityWatch в формате Prometheus</p>
<div class="component-tech">
<span class="tech-badge">Python</span>
<span class="tech-badge">Prometheus</span>
</div>
</div>
</div>
</div>
<!-- Monitoring Stack -->
<div class="layer">
<div class="layer-title">📊 Monitoring Stack - Мониторинг</div>
<div class="components-grid">
<div class="component monitoring" data-id="prometheus" data-search="prometheus metrics monitoring">
<h3>Prometheus</h3>
<p class="component-description">Сбор и хранение метрик для мониторинга системы</p>
<div class="component-tech">
<span class="tech-badge">Prometheus</span>
<span class="tech-badge">TSDB</span>
</div>
</div>
<div class="component monitoring" data-id="grafana" data-search="grafana dashboards visualization">
<h3>Grafana</h3>
<p class="component-description">Визуализация метрик и построение дашбордов</p>
<div class="component-tech">
<span class="tech-badge">Grafana</span>
<span class="tech-badge">Dashboards</span>
</div>
</div>
<div class="component monitoring" data-id="sql-exporter" data-search="sql exporter postgresql grafana">
<h3>SQL Exporter</h3>
<p class="component-description">Прямые SQL запросы к PostgreSQL для Grafana</p>
<div class="component-tech">
<span class="tech-badge">Python</span>
<span class="tech-badge">SQL</span>
</div>
</div>
</div>
</div>
<!-- External Systems -->
<div class="layer">
<div class="layer-title">🌐 External Systems - Внешние системы</div>
<div class="components-grid">
<div class="component external" data-id="pfsense" data-search="pfsense firewall network">
<h3>pfSense Firewall</h3>
<p class="component-description">Брандмауэр для мониторинга сетевой активности</p>
<div class="component-tech">
<span class="tech-badge">pfSense</span>
<span class="tech-badge">Firewall</span>
</div>
</div>
<div class="component external" data-id="domain-controller" data-search="domain controller active directory gpo">
<h3>Domain Controller</h3>
<p class="component-description">Развертывание коллекторов через GPO и управление пользователями</p>
<div class="component-tech">
<span class="tech-badge">AD</span>
<span class="tech-badge">GPO</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="info-panel" id="infoPanel">
<button class="info-panel-close" onclick="closeInfoPanel()">×</button>
<h3 id="infoPanelTitle">Component Info</h3>
<div id="infoPanelContent"></div>
</div>
<script>
// Component data with connections
const componentsData = {
'dlp-endpoint': {
title: 'DLP Endpoint Collector',
connections: [
{ target: 'aw-server', protocol: 'HTTP API', direction: '→', description: 'Отправка DLP событий' }
],
flows: [
'Пользователь копирует данные',
'DLP Collector перехватывает событие',
'Проверка по DLP правилам',
'При нарушении → скриншот + инцидент',
'Отправка в ActivityWatch Server'
],
ports: ['HTTP:5600'],
details: 'Мониторинг clipboard, печати и USB устройств для обнаружения утечек конфиденциальных данных. Поддерживает блокировку нарушений и создание скриншотов.'
},
'browser-domains': {
title: 'Browser Domains Collector',
connections: [
{ target: 'aw-server', protocol: 'HTTP API', direction: '→', description: 'Отправка браузерных событий' }
],
flows: [
'Пользователь открывает сайт',
'Collector определяет домен',
'Категоризация сайта',
'Проверка DLP правил',
'Отправка события в ActivityWatch'
],
ports: ['HTTP:5600'],
details: 'Отслеживание посещаемых веб-сайтов с автоматической категоризацией и проверкой по DLP правилам для запрещенных доменов.'
},
'email-outbound': {
title: 'Email Outbound Collector',
connections: [
{ target: 'aw-server', protocol: 'HTTP API', direction: '→', description: 'Отправка email событий' }
],
flows: [
'Пользователь отправляет email',
'Collector перехватывает событие',
'Проверка по email правилам',
'При нарушении → блокировка',
'Логирование в ActivityWatch'
],
ports: ['HTTP:5600'],
details: 'Мониторинг исходящей почты через Outlook и SMTP соединения для предотвращения утечек через email.'
},
'worktime-session': {
title: 'Worktime Session Collector',
connections: [
{ target: 'aw-server', protocol: 'HTTP API', direction: '→', description: 'Отправка сессий' }
],
flows: [
'Определение начала работы',
'Отслеживание активности',
'Определение перерывов',
'Агрегация по дням',
'Отправка в ActivityWatch'
],
ports: ['HTTP:5600'],
details: 'Учет рабочего времени сотрудников с определением начала/конца сессий и перерывов.'
},
'aw-server': {
title: 'ActivityWatch Server',
connections: [
{ target: 'postgresql', protocol: 'TCP', direction: '→', description: 'Хранение данных' },
{ target: 'webui', protocol: 'HTTP', direction: '→', description: 'Отдача интерфейса' },
{ target: 'prometheus-exporter', protocol: 'HTTP API', direction: '←', description: 'Метрики' }
],
flows: [
'Прием событий от коллекторов',
'Валидация и нормализация',
'Сохранение в PostgreSQL',
'Предоставление API',
'Отдача WebUI'
],
ports: ['HTTP:5600', 'WebSocket:5666'],
details: 'Основной сервер ActivityWatch на Rust, принимающий события от коллекторов и предоставляющий API и WebUI.'
},
'postgresql': {
title: 'PostgreSQL Database',
connections: [
{ target: 'dlp-aggregation', protocol: 'TCP', direction: '↔', description: 'Агрегация событий' },
{ target: 'grafana', protocol: 'TCP', direction: '→', description: 'Прямые запросы' },
{ target: 'sql-exporter', protocol: 'TCP', direction: '→', description: 'SQL запросы' }
],
flows: [
'Хранение событий активности',
'Хранение DLP инцидентов',
'Хранение метаданных',
'Агрегация данных',
'Аналитические запросы'
],
ports: ['TCP:5432'],
details: 'Основная база данных для хранения всех событий, инцидентов и метаданных системы.'
},
'webui': {
title: 'WebUI with RU Patches',
connections: [
{ target: 'aw-server', protocol: 'HTTP', direction: '←', description: 'API запросы' }
],
flows: [
'Загрузка WebUI',
'Применение RU патчей',
'Инъекция DLP компонентов',
'Перевод интерфейса',
'Отображение данных'
],
ports: ['HTTP:5600'],
details: 'Веб-интерфейс с русской локализацией, DLP центром и компонентами для анализа активности.'
},
'pfsense-poller': {
title: 'pfSense Poller',
connections: [
{ target: 'pfsense', protocol: 'HTTPS', direction: '←', description: 'Сбор логов' },
{ target: 'aw-server', protocol: 'HTTP API', direction: '→', description: 'Отправка событий' }
],
flows: [
'Подключение к pfSense API',
'Сбор логов firewall',
'Нормализация данных',
'Отправка в ActivityWatch',
'Периодический опрос'
],
ports: ['HTTPS:443'],
details: 'Python скрипт для сбора логов с pfSense firewall и отправки сетевых событий в ActivityWatch.'
},
'dlp-aggregation': {
title: 'DLP Aggregation Scripts',
connections: [
{ target: 'aw-server', protocol: 'HTTP API', direction: '←', description: 'Чтение событий' },
{ target: 'postgresql', protocol: 'TCP', direction: '→', description: 'Запись агрегации' }
],
flows: [
'Чтение событий из AW',
'Парсинг и нормализация',
'Агрегация по периодам',
'Запись в PostgreSQL',
'Генерация отчетов'
],
ports: ['TCP:5432'],
details: 'Python скрипты для агрегации DLP событий из ActivityWatch buckets в PostgreSQL для аналитики.'
},
'prometheus-exporter': {
title: 'Prometheus Exporter',
connections: [
{ target: 'aw-server', protocol: 'HTTP API', direction: '←', description: 'Сбор метрик' },
{ target: 'prometheus', protocol: 'HTTP', direction: '→', description: 'Экспорт метрик' }
],
flows: [
'Запрос к AW API',
'Сбор метрик buckets',
'Подсчет событий',
'Форматирование Prometheus',
'Отдача на /metrics'
],
ports: ['HTTP:9398'],
details: 'HTTP сервер для экспорта метрик ActivityWatch в формате Prometheus для мониторинга.'
},
'prometheus': {
title: 'Prometheus',
connections: [
{ target: 'prometheus-exporter', protocol: 'HTTP', direction: '←', description: 'Scraping' }
],
flows: [
'Периодический scrape',
'Сбор метрик',
'Хранение в TSDB',
'Evaluation правил',
'Alerting'
],
ports: ['HTTP:9090'],
details: 'Система мониторинга для сбора и хранения метрик с оценкой правил и алертингом.'
},
'grafana': {
title: 'Grafana',
connections: [
{ target: 'prometheus', protocol: 'HTTP', direction: '←', description: 'Запросы метрик' },
{ target: 'postgresql', protocol: 'TCP', direction: '←', description: 'SQL запросы' }
],
flows: [
'Запрос к Prometheus',
'Запрос к PostgreSQL',
'Визуализация данных',
'Построение графиков',
'Дашборды'
],
ports: ['HTTP:3000'],
details: 'Платформа визуализации для построения дашбордов на основе метрик Prometheus и данных PostgreSQL.'
},
'sql-exporter': {
title: 'SQL Exporter',
connections: [
{ target: 'postgresql', protocol: 'TCP', direction: '←', description: 'SQL запросы' },
{ target: 'grafana', protocol: 'HTTP', direction: '→', description: 'Данные для дашбордов' }
],
flows: [
'SQL запросы к PostgreSQL',
'Агрегация данных',
'Форматирование',
'Экспорт в Grafana',
'Периодическое обновление'
],
ports: ['HTTP:9398'],
details: 'Экспорт данных из PostgreSQL через SQL запросы для использования в Grafana дашбордах.'
},
'pfsense': {
title: 'pfSense Firewall',
connections: [
{ target: 'pfsense-poller', protocol: 'HTTPS', direction: '→', description: 'API доступ' }
],
flows: [
'Обработка трафика',
'Логирование соединений',
'Предоставление API',
'Мониторинг безопасности'
],
ports: ['HTTPS:443', 'SSH:22'],
details: 'Брандмауэр для мониторинга сетевой активности и защиты периметра.'
},
'domain-controller': {
title: 'Domain Controller',
connections: [
{ target: 'dlp-endpoint', protocol: 'GPO', direction: '→', description: 'Развертывание' },
{ target: 'browser-domains', protocol: 'GPO', direction: '→', description: 'Развертывание' },
{ target: 'email-outbound', protocol: 'GPO', direction: '→', description: 'Развертывание' }
],
flows: [
'Создание GPO',
'Развертывание коллекторов',
'Управление пользователями',
'Мониторинг статуса'
],
ports: ['LDAP:389', 'SMB:445'],
details: 'Active Directory для автоматического развертывания коллекторов через Group Policy Objects.'
}
};
// DOM elements
const components = document.querySelectorAll('.component');
const infoPanel = document.getElementById('infoPanel');
const infoPanelTitle = document.getElementById('infoPanelTitle');
const infoPanelContent = document.getElementById('infoPanelContent');
const searchInput = document.getElementById('searchInput');
// Component click handler
components.forEach(component => {
component.addEventListener('click', () => {
const componentId = component.dataset.id;
const data = componentsData[componentId];
// Remove selected class from all components
components.forEach(c => c.classList.remove('selected'));
// Add selected class to clicked component
component.classList.add('selected');
// Show info panel
showInfoPanel(componentId, data);
// Highlight connected components
highlightConnections(componentId, data.connections);
});
});
function showInfoPanel(componentId, data) {
infoPanelTitle.textContent = data.title;
let content = `
<div class="info-section">
<h4>📝 Описание</h4>
<p>${data.details}</p>
</div>
<div class="info-section">
<h4>🔗 Связи</h4>
`;
data.connections.forEach(conn => {
content += `
<div class="connection-item">
<span class="connection-protocol">${conn.protocol}</span>
<span class="connection-direction">${conn.direction}</span>
<span>${conn.description}</span>
</div>
`;
});
content += `</div>`;
if (data.flows && data.flows.length > 0) {
content += `
<div class="info-section">
<h4>⚡ Поток данных</h4>
<div class="flow-diagram">
`;
data.flows.forEach((flow, index) => {
content += `
<div class="flow-step">
<span>${index + 1}.</span>
<span>${flow}</span>
${index < data.flows.length - 1 ? '<span class="flow-arrow"></span>' : ''}
</div>
`;
});
content += `</div></div>`;
}
if (data.ports && data.ports.length > 0) {
content += `
<div class="info-section">
<h4>🌐 Порты</h4>
<ul>
`;
data.ports.forEach(port => {
content += `<li>${port}</li>`;
});
content += `</ul></div>`;
}
infoPanelContent.innerHTML = content;
infoPanel.classList.add('active');
}
function closeInfoPanel() {
infoPanel.classList.remove('active');
components.forEach(c => {
c.classList.remove('selected');
c.style.opacity = '1';
});
}
function highlightConnections(componentId, connections) {
// Reset all components
components.forEach(c => {
c.style.opacity = '0.3';
});
// Highlight current component
const currentComponent = document.querySelector(`[data-id="${componentId}"]`);
if (currentComponent) {
currentComponent.style.opacity = '1';
}
// Highlight connected components
connections.forEach(conn => {
const targetComponent = document.querySelector(`[data-id="${conn.target}"]`);
if (targetComponent) {
targetComponent.style.opacity = '1';
}
});
}
// Search functionality
searchInput.addEventListener('input', (e) => {
const searchTerm = e.target.value.toLowerCase();
components.forEach(component => {
const searchData = component.dataset.search.toLowerCase();
const title = component.querySelector('h3').textContent.toLowerCase();
const description = component.querySelector('.component-description').textContent.toLowerCase();
if (searchData.includes(searchTerm) ||
title.includes(searchTerm) ||
description.includes(searchTerm)) {
component.classList.remove('hidden');
} else {
component.classList.add('hidden');
}
});
});
// Close info panel on escape key
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
closeInfoPanel();
}
});
// Close info panel when clicking outside
document.addEventListener('click', (e) => {
if (!e.target.closest('.component') && !e.target.closest('.info-panel')) {
closeInfoPanel();
}
});
</script>
</body>
</html>
+305
View File
@@ -0,0 +1,305 @@
# Browser Domains Monitoring - Компонентная диаграмма
## Обзор
Мониторинг посещаемых доменов в браузерах с категоризацией и проверкой DLP правил.
## Архитектура
```mermaid
graph TB
subgraph "Browser Domains Collector"
A[Main Loop]
B[Window Detection]
C[URL Extraction]
D[Domain Parsing]
E[Web Categorization]
F[DLP Rule Check]
G[Incident Handler]
H[Event Logger]
I[Screenshot Capture]
end
subgraph "External Dependencies"
J[Windows API]
K[Web Category API]
L[DLP Policy]
M[ActivityWatch API]
N[Custom Category Rules]
end
A --> B
B --> C
C --> D
D --> E
D --> F
E --> F
F --> G
G --> H
G --> I
B --> J
C --> J
E --> K
F --> L
H --> M
E --> N
F --> N
style B fill:#ff6b6b
style E fill:#4ecdc4
style F fill:#4ecdc4
style G fill:#ffe66d
```
## Потоки данных
### Domain Detection Flow
```mermaid
sequenceDiagram
participant User
participant Browser as Browser Window
participant Windows as Windows API
participant Collector as Browser Collector
participant Parser as Domain Parser
participant Category as Category API
participant DLP as DLP Engine
participant AW as ActivityWatch
User->>Browser: Navigate to website
Browser->>Windows: Window title change
Windows->>Collector: Window event
Collector->>Collector: Check if browser
Collector->>Collector: Extract URL from window
Collector->>Parser: Parse domain
Parser->>Collector: Return domain
Collector->>Category: Get web category
Category->>Collector: Return category
Collector->>DLP: Check DLP rules
alt Violation
DLP->>Collector: Trigger incident
Collector->>Collector: Capture screenshot
Collector->>AW: Send DLP incident
else Normal
Collector->>AW: Send browsing event
end
```
### Domain Categorization Flow
```mermaid
graph LR
A[URL] --> B[Extract Domain]
B --> C{Custom Rules?}
C -->|Yes| D[Apply Custom Category]
C -->|No| E[Call Category API]
D --> F[Return Category]
E --> F
F --> G[Cache Result]
G --> H[Use in DLP Check]
```
## Ключевые функции
### Обнаружение и извлечение
- `browser_domains_native_collector_get_foregroundwindowcontext()` - контекст активного окна
- `browser_domains_native_collector_get_browserurlfromwindow()` - извлечение URL из окна
- `browser_domains_native_collector_get_hostfromurl()` - извлечение домена из URL
- `browser_domains_native_collector_get_rootdomain()` - получение корневого домена
### Категоризация
- `browser_domains_native_collector_get_webcategory()` - определение категории сайта
- `browser_domains_native_collector_load_customcategoryrules()` - загрузка кастомных правил
- `browser_domains_native_collector_test_domainlistmatch()` - проверка по спискам доменов
### DLP проверка
- `browser_domains_native_collector_get_dlpdecision()` - решение DLP
- `browser_domains_native_collector_test_dlprulematch()` - проверка DLP правила
- `browser_domains_native_collector_test_dlpruletimewindow()` - проверка временного окна
- `browser_domains_native_collector_should_emitincident()` - решение о создании инцидента
### Логирование
- `browser_domains_native_collector_write_collectorlog()` - лог коллектора
- `browser_domains_native_collector_write_dlpincidentlog()` - лог DLP инцидента
- `browser_domains_native_collector_send_heartbeat()` - heartbeat
- `browser_domains_native_collector_send_categoryheartbeat()` - heartbeat категорий
### Утилиты
- `browser_domains_native_collector_convertto_normalizedurl()` - нормализация URL
- `browser_domains_native_collector_test_domainmatch()` - проверка совпадения домена
## Конфигурация
### Custom Category Rules
```json
{
"custom_categories": {
"social_media": [
"*.facebook.com",
"*.twitter.com",
"*.instagram.com"
],
"news": [
"*.news.com",
"*.media.com"
],
"blocked": [
"*.malware.com",
"*.phishing.com"
]
}
}
```
### DLP Domain Rules
```json
{
"domain_rules": [
{
"pattern": "*gambling*",
"category": "gambling",
"action": "alert",
"severity": "medium"
},
{
"pattern": "*adult*",
"category": "adult",
"action": "block",
"severity": "high"
}
]
}
```
### Deployment Config
```json
{
"aw_server_url": "http://aw-server:5600",
"bucket_prefix": "aw-watcher-browser-domains",
"category_api_enabled": true,
"custom_categories_enabled": true,
"screenshot_on_incident": true,
"heartbeat_interval": 60
}
```
## События
### Browsing Event
```json
{
"timestamp": "2024-01-01T12:00:00Z",
"type": "browsing",
"data": {
"url": "https://www.example.com/page",
"domain": "example.com",
"root_domain": "example.com",
"category": "technology",
"browser": "chrome.exe",
"title": "Example Page Title",
"user": "user1",
"host": "WORKSTATION01"
}
}
```
### DLP Incident Event
```json
{
"timestamp": "2024-01-01T12:00:00Z",
"type": "dlp_incident",
"source": "browser_domain",
"rule_id": "blocked_domains",
"severity": "high",
"data": {
"url": "https://blocked.com",
"domain": "blocked.com",
"category": "malicious",
"user": "user1",
"host": "WORKSTATION01",
"screenshot": "path/to/screenshot.png"
}
}
```
### Category Heartbeat
```json
{
"timestamp": "2024-01-01T12:00:00Z",
"type": "category_heartbeat",
"data": {
"domains_seen": 150,
"categories": {
"technology": 50,
"news": 30,
"social_media": 40,
"other": 30
}
}
}
```
## Поддерживаемые браузеры
- Google Chrome
- Mozilla Firefox
- Microsoft Edge
- Opera
- Яндекс.Браузер
## Зависимости
### Windows API
- Window enumeration
- Window title extraction
- Process information
### Внешние сервисы
- Web categorization API (опционально)
- ActivityWatch HTTP API
## Алгоритм категоризации
```mermaid
graph TD
A[Domain extracted] --> B{Custom rule match?}
B -->|Yes| C[Return custom category]
B -->|No| D{Cache hit?}
D -->|Yes| E[Return cached category]
D -->|No| F[Call category API]
F --> G{API success?}
G -->|Yes| H[Cache result]
G -->|No| I[Return 'uncategorized']
H --> J[Return category]
I --> J
```
## Мониторинг
### Метрики
- Количество уникальных доменов
- Распределение по категориям
- Частота DLP инцидентов
- Кэш hit rate для категорий
### Алерты
- Коллектор не активен > 5 минут
- Высокий процент blocked категорий
- Ошибки category API
- Много неизвестных категорий
## Производительность
### Оптимизации
- Кэширование категорий доменов
- Batch запросы к category API
- Debouncing быстрых переходов
- Асинхронная отправка событий
### Настройки производительности
```json
{
"cache_ttl": 86400,
"batch_size": 50,
"debounce_ms": 1000,
"max_events_per_minute": 100
}
```
+375
View File
@@ -0,0 +1,375 @@
# DLP Events Aggregation - Компонентная диаграмма
## Обзор
Система агрегации DLP событий из ActivityWatch buckets в PostgreSQL для аналитики и отчетности.
## Архитектура
```mermaid
graph TB
subgraph "Aggregation Pipeline"
A[Main Orchestrator]
B[Bucket Discovery]
C[Event Fetcher]
D[Event Parser]
E[Normalizer]
F[PostgreSQL Writer]
G[State Manager]
end
subgraph "Data Sources"
H[ActivityWatch API]
I[SQLite Buckets]
J[PostgreSQL Database]
end
subgraph "Output"
K[Aggregated Events]
L[Statistics]
M[Reports]
end
A --> B
A --> C
A --> G
B --> H
C --> H
C --> I
D --> C
E --> D
F --> E
F --> J
G --> J
F --> K
F --> L
F --> M
style A fill:#4ecdc4
style F fill:#ff6b6b
style G fill:#ffe66d
```
## Потоки данных
### Aggregation Flow
```mermaid
sequenceDiagram
participant Orchestrator as Main Orchestrator
participant State as State Manager
participant AW as ActivityWatch API
participant Fetcher as Event Fetcher
participant Parser as Event Parser
participant PG as PostgreSQL
Orchestrator->>State: Load last sync state
State->>Orchestrator: Return last timestamp
Orchestrator->>AW: List buckets
AW->>Orchestrator: Return bucket list
loop For each DLP bucket
Orchestrator->>AW: Get events since last sync
AW->>Fetcher: Return events
Fetcher->>Parser: Parse event structure
Parser->>Parser: Extract DLP data
Parser->>Orchestrator: Return parsed events
Orchestrator->>PG: Insert batch of events
PG->>Orchestrator: Confirm insert
end
Orchestrator->>State: Save new sync state
State->>PG: Persist state
```
### Event Processing Pipeline
```mermaid
graph LR
A[Raw Event] --> B[Parse JSON]
B --> C[Extract DLP Fields]
C --> D[Normalize Timestamp]
D --> E[Enrich with User/Host]
E --> F[Validate Data]
F --> G[Batch Prepare]
G --> H[PostgreSQL Insert]
```
## Ключевые функции
### Оркестрация
- `aggregate_dlp_events_main()` - главная функция агрегации
- `aggregate_dlp_events_list_buckets()` - получение списка buckets
- `aggregate_dlp_events_get_start_time()` - определение начала периода
### Получение событий
- `aggregate_dlp_events_fetch_bucket_events()` - получение событий из bucket
- `aggregate_dlp_events_bucket_stream_type()` - определение типа потока
- `aggregate_dlp_events_aw_get_json()` - HTTP запрос к AW API
### Парсинг
- `aggregate_dlp_events_awevent()` - парсинг события AW
- `aggregate_dlp_events_parse_timestamp()` - парсинг timestamp
- `aggregate_dlp_events_parse_prefixes()` - парсинг префиксов bucket
- `aggregate_dlp_events_event_row()` - формирование строки события
### Нормализация
- `aggregate_dlp_events_normalize_base_url()` - нормализация URL AW API
- `aggregate_dlp_events_format_aw_timestamp()` - форматирование timestamp
- `aggregate_dlp_events_bool_as_int()` - конвертация boolean в int
- `aggregate_dlp_events_first_string()` - получение первой строки
### Батчинг
- `aggregate_dlp_events_bucket()` - обработка bucket
- `aggregate_dlp_events_build_events_path()` - построение пути к событиям
- `aggregate_dlp_events_build_parser()` - создание парсера
### Соединения
- `aggregate_dlp_events_connect_sqlite()` - соединение с SQLite
- `aggregate_dlp_events_psycopgconnection()` - соединение с PostgreSQL
- `aggregate_dlp_events_psycopgconnection_cursor()` - курсор PostgreSQL
- `aggregate_dlp_events_psycopgconnection_commit()` - коммит транзакции
### Запись
- `aggregate_dlp_events_insert_events()` - вставка событий
- `aggregate_dlp_events_insert_postgres_events()` - вставка в PostgreSQL
- `aggregate_dlp_events_select_buckets()` - выборка buckets
- `aggregate_dlp_events_ensure_schema()` - обеспечение схемы БД
### Управление состоянием
- `aggregate_dlp_events_load_state()` - загрузка состояния
- `aggregate_dlp_events_save_state()` - сохранение состояния
- `aggregate_dlp_events_event_key()` - ключ события
### Утилиты
- `aggregate_dlp_events_utc_now()` - текущее UTC время
- `aggregate_dlp_events_ensure_postgres_schema()` - схема PostgreSQL
## Схема базы данных
### Таблица dlp_events
```sql
CREATE TABLE dlp_events (
id SERIAL PRIMARY KEY,
timestamp TIMESTAMP NOT NULL,
event_type VARCHAR(50) NOT NULL,
source VARCHAR(50) NOT NULL,
rule_id VARCHAR(100),
severity VARCHAR(20),
user_id VARCHAR(100),
host_id VARCHAR(100),
data JSONB,
created_at TIMESTAMP DEFAULT NOW(),
INDEX idx_timestamp (timestamp),
INDEX idx_user (user_id),
INDEX idx_host (host_id),
INDEX idx_severity (severity),
INDEX idx_type (event_type)
);
```
### Таблица aggregation_state
```sql
CREATE TABLE aggregation_state (
id SERIAL PRIMARY KEY,
bucket_id VARCHAR(255) UNIQUE NOT NULL,
last_synced_timestamp TIMESTAMP,
last_synced_at TIMESTAMP DEFAULT NOW(),
events_processed INTEGER DEFAULT 0
);
```
### Таблица dlp_statistics
```sql
CREATE TABLE dlp_statistics (
id SERIAL PRIMARY KEY,
date DATE NOT NULL,
user_id VARCHAR(100),
host_id VARCHAR(100),
event_type VARCHAR(50),
severity VARCHAR(20),
incident_count INTEGER DEFAULT 0,
UNIQUE(date, user_id, host_id, event_type, severity)
);
```
## Конфигурация
### Config File
```json
{
"aw_base_url": "http://aw-server:5600",
"postgres_url": "postgresql://user:pass@localhost:5432/activitywatch",
"batch_size": 1000,
"sync_interval_minutes": 5,
"bucket_prefixes": [
"aw-watcher-dlp-endpoint",
"aw-watcher-browser-domains",
"aw-watcher-email-outbound"
],
"retention_days": 90
}
```
### Environment Variables
```bash
AW_BASE_URL=http://aw-server:5600
POSTGRES_URL=postgresql://aw:password@localhost:5432/activitywatch
BATCH_SIZE=1000
SYNC_INTERVAL=300
LOG_LEVEL=INFO
```
## События
### Input Event (from ActivityWatch)
```json
{
"id": "event_id",
"timestamp": "2024-01-01T12:00:00Z",
"duration": 60.0,
"data": {
"type": "dlp_incident",
"source": "clipboard",
"rule_id": "credit_card_pattern",
"severity": "high",
"user": "user1",
"host": "WORKSTATION01",
"matched_text": "****-****-****-1234"
}
}
```
### Output Event (in PostgreSQL)
```sql
INSERT INTO dlp_events (
timestamp, event_type, source, rule_id,
severity, user_id, host_id, data
) VALUES (
'2024-01-01 12:00:00',
'dlp_incident',
'clipboard',
'credit_card_pattern',
'high',
'user1',
'WORKSTATION01',
'{"matched_text": "****-****-****-1234"}'::jsonb
);
```
## Производительность
### Оптимизации
- Batch вставки (по 1000 событий)
- Connection pooling к PostgreSQL
- Асинхронная обработка
- Индексы на частых запросах
### Метрики производительности
```python
performance_metrics = {
"events_per_second": 100,
"batch_insert_time_ms": 50,
"api_latency_ms": 20,
"postgres_write_latency_ms": 30
}
```
### Мониторинг
- Время обработки batch
- Размер очереди событий
- Ошибки соединения с PostgreSQL
- Latency API запросов
## Обработка ошибок
### Retry Strategy
```python
retry_config = {
"max_retries": 3,
"backoff_seconds": [1, 5, 15],
"retry_on": [
"ConnectionError",
"TimeoutError",
"DatabaseError"
]
}
```
### Dead Letter Queue
```sql
CREATE TABLE dlp_events_failed (
id SERIAL PRIMARY KEY,
raw_event JSONB,
error_message TEXT,
failed_at TIMESTAMP DEFAULT NOW(),
retry_count INTEGER DEFAULT 0
);
```
## Планирование
### Cron Job
```cron
*/5 * * * * /usr/bin/python3 /path/to/aggregate_dlp_events.py
```
### Systemd Service
```ini
[Unit]
Description=ActivityWatch DLP Aggregation
After=network.target
[Service]
Type=simple
User=aw-aggregator
ExecStart=/usr/bin/python3 /path/to/aggregate_dlp_events.py
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
```
## Отчеты
### Daily Report
```sql
SELECT
date,
event_type,
severity,
COUNT(*) as incident_count
FROM dlp_events
WHERE date >= CURRENT_DATE - INTERVAL '7 days'
GROUP BY date, event_type, severity
ORDER BY date DESC, incident_count DESC;
```
### User Summary
```sql
SELECT
user_id,
COUNT(*) as total_incidents,
COUNT(CASE WHEN severity = 'high' THEN 1 END) as high_severity,
COUNT(CASE WHEN severity = 'medium' THEN 1 END) as medium_severity
FROM dlp_events
WHERE timestamp >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY user_id
ORDER BY total_incidents DESC;
```
## Валидация данных
### Checks
- Timestamp в допустимом диапазоне
- Обязательные поля заполнены
- Severity в списке допустимых значений
- User/Host существуют в справочниках
### Data Quality
```python
validation_rules = {
"timestamp": "required, past_date",
"event_type": "required, in_list",
"severity": "required, in_list",
"user_id": "required, max_length=100",
"host_id": "required, max_length=100"
}
```
+264
View File
@@ -0,0 +1,264 @@
# DLP Endpoint Monitoring - Компонентная диаграмма
## Обзор
Мониторинг конечных точек для обнаружения утечек данных через clipboard, печать и USB.
## Архитектура
```mermaid
graph TB
subgraph "DLP Endpoint Collector"
A[Main Loop]
B[Load DLP Policy]
C[Deployment Config]
D[Clipboard Monitor]
E[Print Monitor]
F[USB Monitor]
G[Rule Engine]
H[Enforcement Actions]
I[Event Logging]
J[Screenshot Capture]
end
subgraph "External Dependencies"
K[ActivityWatch API]
L[DLP Policy JSON]
M[Windows API]
N[File System]
end
A --> B
A --> C
B --> G
C --> A
D --> G
E --> G
F --> G
G --> H
G --> I
G --> J
H --> M
I --> N
J --> N
I --> K
A --> K
B --> L
C --> L
style D fill:#ff6b6b
style E fill:#ff6b6b
style F fill:#ff6b6b
style G fill:#4ecdc4
style H fill:#ffe66d
```
## Потоки данных
### Clipboard Monitoring Flow
```mermaid
sequenceDiagram
participant User
participant Clipboard as Clipboard API
participant Collector as DLP Collector
participant RuleEngine as Rule Engine
participant AW as ActivityWatch
participant FS as File System
User->>Clipboard: Copy data
Clipboard->>Collector: Clipboard change event
Collector->>RuleEngine: Evaluate against rules
RuleEngine->>RuleEngine: Check patterns
alt Pattern Match
RuleEngine->>Collector: Trigger incident
Collector->>FS: Capture screenshot
Collector->>Collector: Apply enforcement
Collector->>AW: Send DLP incident
else No Match
RuleEngine->>Collector: Allow
end
```
### Print Monitoring Flow
```mermaid
sequenceDiagram
participant App as Application
participant PrintSpooler as Print Spooler
participant Collector as DLP Collector
participant RuleEngine as Rule Engine
participant AW as ActivityWatch
App->>PrintSpooler: Print job
PrintSpooler->>Collector: Print service event
Collector->>Collector: Extract document info
Collector->>RuleEngine: Evaluate printer rules
alt Violation
RuleEngine->>Collector: Block print job
Collector->>PrintSpooler: Cancel job
Collector->>AW: Log incident
else Allowed
Collector->>AW: Log print activity
end
```
### USB Monitoring Flow
```mermaid
sequenceDiagram
participant User
participant USB as USB Device
participant Windows as Windows API
participant Collector as DLP Collector
participant RuleEngine as Rule Engine
participant AW as ActivityWatch
User->>USB: Insert USB drive
USB->>Windows: Device connect
Windows->>Collector: USB device event
Collector->>RuleEngine: Check USB rules
alt Write Blocked
RuleEngine->>Collector: Block write
Collector->>Windows: Prevent write
Collector->>AW: Log blocked attempt
else Write Allowed
Collector->>AW: Log file copy
end
```
## Ключевые функции
### Основные функции
- `dlp_endpoint_signals_collector_main()` - главный цикл коллектора
- `dlp_endpoint_signals_collector_load_dlppolicy()` - загрузка DLP правил
- `dlp_endpoint_signals_collector_get_deploymentconfig()` - чтение конфигурации
### Мониторинг
- `dlp_endpoint_signals_collector_evaluate_clipboardrules()` - проверка clipboard
- `dlp_endpoint_signals_collector_evaluate_printrules()` - проверка печати
- `dlp_endpoint_signals_collector_evaluate_usbrules()` - проверка USB
### Принудительные действия
- `dlp_endpoint_signals_collector_invoke_clipboardenforcement()` - блокировка clipboard
- `dlp_endpoint_signals_collector_invoke_printjobenforcement()` - блокировка печати
- `dlp_endpoint_signals_collector_invoke_usbwriteblockenforcement()` - блокировка USB
### Логирование
- `dlp_endpoint_signals_collector_write_endpointlog()` - запись логов коллектора
- `dlp_endpoint_signals_collector_send_dlpincidentheartbeat()` - heartbeat инцидентов
- `dlp_endpoint_signals_collector_capture_incidentscreenshot()` - захват скриншота
## Конфигурация
### DLP Policy Structure
```json
{
"clipboard_rules": [
{
"pattern": "\\b\\d{4}-\\d{4}-\\d{4}-\\d{4}\\b",
"description": "Credit card numbers",
"severity": "high",
"action": "block"
}
],
"print_rules": [
{
"printer_match": "*",
"document_keywords": ["confidential", "secret"],
"action": "block"
}
],
"usb_rules": [
{
"device_id": "*",
"action": "block_write"
}
]
}
```
### Deployment Config
```json
{
"aw_server_url": "http://aw-server:5600",
"bucket_prefix": "aw-watcher-dlp-endpoint",
"heartbeat_interval": 60,
"screenshot_on_incident": true,
"enforcement_enabled": true
}
```
## События
### DLP Incident Event
```json
{
"timestamp": "2024-01-01T12:00:00Z",
"type": "dlp_incident",
"source": "clipboard",
"rule_id": "credit_card_pattern",
"severity": "high",
"data": {
"matched_text": "****-****-****-1234",
"user": "user1",
"host": "WORKSTATION01",
"application": "chrome.exe",
"screenshot": "path/to/screenshot.png"
}
}
```
### Heartbeat Event
```json
{
"timestamp": "2024-01-01T12:00:00Z",
"type": "heartbeat",
"status": "running",
"incidents_count": 5,
"last_incident": "2024-01-01T11:55:00Z"
}
```
## Зависимости
### Windows API
- Clipboard API
- Print Spooler API
- USB Device Notification API
- Process API
### External Services
- ActivityWatch HTTP API
- File system (для скриншотов и логов)
## Развертывание
### Требования
- Windows 10/11
- PowerShell 5.1+
- ActivityWatch installed
- Административские права (для enforcement)
### Установка
```powershell
# Копирование коллектора
Copy-Item dlp-endpoint-signals-collector.ps1 C:\ProgramData\AWatch-rus\
# Настройка scheduled task
Register-ScheduledTask -TaskName "DLP Endpoint Collector" -Trigger $trigger -Action $action
```
## Мониторинг
### Метрики
- Количество инцидентов по типам (clipboard/print/USB)
- Частота срабатываний правил
- Успешность enforcement действий
- Heartbeat статус
### Алерты
- Коллектор не отправляет heartbeat > 5 минут
- Высокая частота DLP инцидентов
- Ошибки enforcement действий
+338
View File
@@ -0,0 +1,338 @@
# ActivityWatch-Russian - Обзор компонентов и связей
## Полная архитектура системы
```mermaid
graph TB
subgraph "Windows Clients"
subgraph "Workstation 1"
W1A[DLP Endpoint Collector]
W1B[Browser Domains Collector]
W1C[Email Outbound Collector]
W1D[Worktime Session Collector]
end
subgraph "Workstation 2"
W2A[DLP Endpoint Collector]
W2B[Browser Domains Collector]
W2C[Email Outbound Collector]
end
end
subgraph "Linux Server"
S1[ActivityWatch Server]
S2[PostgreSQL Database]
S3[WebUI with RU Patches]
end
subgraph "Integration Layer"
I1[pfSense Poller]
I2[DLP Aggregation Scripts]
I3[Prometheus Exporter]
end
subgraph "Monitoring Stack"
M1[Prometheus]
M2[Grafana]
M3[SQL Exporter]
end
subgraph "External Systems"
E1[pfSense Firewall]
E2[Domain Controller]
end
W1A -->|Events| S1
W1B -->|Events| S1
W1C -->|Events| S1
W1D -->|Events| S1
W2A -->|Events| S1
W2B -->|Events| S1
W2C -->|Events| S1
S1 -->|Store| S2
S1 -->|Serve| S3
I1 -->|Logs| E1
I1 -->|Events| S1
I2 -->|Read/Write| S2
I2 -->|Read| S1
I3 -->|Metrics| S1
I3 -->|Scrape| M1
M1 -->|Query| I3
M2 -->|Query| M1
M2 -->|Query| S2
M3 -->|Query| S2
E2 -->|Deploy| W1A
E2 -->|Deploy| W1B
E2 -->|Deploy| W1C
E2 -->|Deploy| W2A
E2 -->|Deploy| W2B
E2 -->|Deploy| W2C
style S1 fill:#4ecdc4
style S2 fill:#ff6b6b
style M1 fill:#ffe66d
style M2 fill:#95e1d3
```
## Матрица связей компонентов
| Компонент | Тип | Подключается к | Протокол | Назначение |
|-----------|-----|----------------|-----------|------------|
| DLP Endpoint Collector | Windows Client | AW Server | HTTP API | Отправка DLP событий |
| Browser Domains Collector | Windows Client | AW Server | HTTP API | Отправка браузерных событий |
| Email Outbound Collector | Windows Client | AW Server | HTTP API | Отправка email событий |
| Worktime Session Collector | Windows Client | AW Server | HTTP API | Отправка сессий |
| ActivityWatch Server | Server | PostgreSQL | TCP | Хранение данных |
| ActivityWatch Server | Server | WebUI | HTTP | Отдача интерфейса |
| pfSense Poller | Integration | pfSense | HTTP API | Сбор логов firewall |
| pfSense Poller | Integration | AW Server | HTTP API | Отправка событий |
| DLP Aggregation | Integration | PostgreSQL | TCP | Обработка событий |
| DLP Aggregation | Integration | AW Server | HTTP API | Чтение событий |
| Prometheus Exporter | Integration | AW Server | HTTP API | Сбор метрик |
| Prometheus Exporter | Integration | Prometheus | HTTP | Отдача метрик |
| Prometheus | Monitoring | Exporter | HTTP | Scraping метрик |
| Grafana | Monitoring | Prometheus | HTTP | Запросы метрик |
| Grafana | Monitoring | PostgreSQL | TCP | Прямые запросы |
| SQL Exporter | Monitoring | PostgreSQL | TCP | SQL запросы |
## Потоки данных по уровням
### Уровень 1: Сбор данных (Windows)
```
┌─────────────────────────────────────────────────────────────┐
│ Windows Clients │
├─────────────────────────────────────────────────────────────┤
│ │
│ Workstation 1 Workstation 2 Workstation N │
│ ┌─────────────┐ ┌─────────────┐ ┌──────────┐ │
│ │ DLP Collector│ │ DLP Collector│ │ DLP Coll.│ │
│ │ Browser Coll.│ │ Browser Coll.│ │ Browser │ │
│ │ Email Coll. │ │ Email Coll. │ │ Email │ │
│ │ Worktime │ │ │ │ │ │
│ └──────┬──────┘ └──────┬──────┘ └────┬─────┘ │
│ │ │ │ │
│ └──────────────────────┼───────────────────┘ │
│ │ │
└────────────────────────────────┼────────────────────────────┘
│ HTTP API
```
### Уровень 2: Хранение и обработка (Linux)
```
┌─────────────────────────────────────────────────────────────┐
│ Linux Server Layer │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────┐ │
│ │ AW Server │◄────── Events │
│ │ (Rust) │ │
│ └───────┬───────┘ │
│ │ Store │
│ ▼ │
│ ┌───────────────┐ │
│ │ PostgreSQL │ │
│ └───────┬───────┘ │
│ │ │
└────────────────────────────┼───────────────────────────────┘
```
### Уровень 3: Интеграции
```
┌─────────────────────────────────────────────────────────────┐
│ Integration Layer │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ pfSense │ │ DLP Aggreg. │ │ Prometheus │ │
│ │ Poller │ │ Scripts │ │ Exporter │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ pfSense FW │ │ PostgreSQL │ │ AW Server │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
### Уровень 4: Визуализация
```
┌─────────────────────────────────────────────────────────────┐
│ Monitoring & Visualization │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Prometheus │◄───┤ Exporter │ │ Grafana │ │
│ │ │ │ │ │ │ │
│ └──────┬───────┘ └──────────────┘ └──────┬───────┘ │
│ │ │ │
│ │ Query │ Query │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Grafana │ │ PostgreSQL │ │
│ │ Dashboards │ │ │ │
│ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
## Сценарии использования
### Сценарий 1: DLP инцидент
```
User copies sensitive data
DLP Endpoint Collector detects
Evaluates against rules
Creates incident event
Sends to AW Server
Stored in PostgreSQL
Aggregated by scripts
Visible in Grafana DLP Dashboard
```
### Сценарий 2: Мониторинг браузера
```
User visits website
Browser Domains Collector detects
Extracts domain
Categorizes website
Checks DLP rules
Sends event to AW Server
Visible in WebUI Dashboard
```
### Сценарий 3: Метрики
```
Prometheus scrapes Exporter
Exporter queries AW API
Collects metrics
Returns in Prometheus format
Prometheus stores metrics
Grafana visualizes in dashboards
```
## Зависимости развертывания
### Минимальная конфигурация
```
1 Linux Server:
- ActivityWatch Server
- PostgreSQL
- WebUI with patches
1+ Windows Workstations:
- DLP Endpoint Collector
- Browser Domains Collector
- Email Outbound Collector
```
### Полная конфигурация
```
1 Linux Server:
- ActivityWatch Server
- PostgreSQL
- WebUI with patches
- DLP Aggregation Scripts
- Prometheus Exporter
1+ Windows Workstations:
- DLP Endpoint Collector
- Browser Domains Collector
- Email Outbound Collector
- Worktime Session Collector
1 pfSense Firewall:
- pfSense Poller
1 Monitoring Server:
- Prometheus
- Grafana
- SQL Exporter
```
## Порты и протоколы
| Компонент | Порт | Протокол | Направление |
|-----------|------|----------|-------------|
| ActivityWatch API | 5600 | HTTP | Inbound |
| ActivityWatch WebSocket | 5666 | WebSocket | Inbound |
| PostgreSQL | 5432 | TCP | Inbound |
| Prometheus | 9090 | HTTP | Inbound |
| Grafana | 3000 | HTTP | Inbound |
| Prometheus Exporter | 9398 | HTTP | Inbound |
| pfSense API | 443 | HTTPS | Outbound |
## Резервное копирование
### PostgreSQL Backup
```bash
# Daily backup
pg_dump activitywatch > backup_$(date +%Y%m%d).sql
# Restore
psql activitywatch < backup_20240101.sql
```
### AW Server Backup
```bash
# Backup SQLite databases (if used)
cp /var/lib/activitywatch/*.db /backup/
# Backup configuration
cp /etc/activitywatch/config.toml /backup/
```
## Масштабирование
### Горизонтальное масштабирование
- Добавление Windows workstation не требует изменений сервера
- Каждый workstation автономно отправляет события
- Server обрабатывает события от множества клиентов
### Вертикальное масштабирование
- Увеличение ресурсов PostgreSQL для больших объемов данных
- Разделение AW Server и PostgreSQL на разные машины
- Добавление реплик PostgreSQL для высокой доступности
## Мониторинг системы
### Ключевые метрики
- Количество активных хостов
- Скорость поступления событий
- Размер базы данных
- Latency обработки событий
- Статус коллекторов
### Алерты
- Коллектор неактивен > 5 минут
- Высокий процент DLP инцидентов
- PostgreSQL connection pool exhausted
- Диск > 80% заполнен
- AW Server недоступен
+373
View File
@@ -0,0 +1,373 @@
# Prometheus Metrics Exporter - Компонентная диаграмма
## Обзор
Экспорт метрик ActivityWatch в формате Prometheus для мониторинга и визуализации в Grafana.
## Архитектура
```mermaid
graph TB
subgraph "Prometheus Exporter"
A[HTTP Server]
B[Metrics Collector]
C[Bucket Fetcher]
D[Event Counter]
E[Host Tracker]
F[Metrics Formatter]
G[Registry]
end
subgraph "Data Sources"
H[ActivityWatch API]
I[Buckets Data]
J[Events Data]
end
subgraph "Consumers"
K[Prometheus]
L[Grafana]
end
A --> B
B --> C
B --> D
B --> E
C --> H
C --> I
D --> H
D --> J
E --> H
F --> B
F --> G
G --> A
K --> A
L --> K
style A fill:#ff6b6b
style B fill:#4ecdc4
style G fill:#ffe66d
```
## Потоки данных
### Metrics Collection Flow
```mermaid
sequenceDiagram
participant Prometheus as Prometheus
participant Server as HTTP Server
participant Collector as Metrics Collector
participant AW as ActivityWatch API
participant Registry as Metrics Registry
Prometheus->>Server: GET /metrics
Server->>Collector: Trigger collection
Collector->>AW: Get buckets list
AW->>Collector: Return buckets
loop For each bucket
Collector->>AW: Get bucket info
AW->>Collector: Return bucket data
Collector->>Collector: Calculate metrics
Collector->>Registry: Update metrics
end
Collector->>Server: Collection complete
Server->>Registry: Format as Prometheus
Registry->>Server: Return formatted metrics
Server->>Prometheus: Return metrics text
```
### Metrics Update Flow
```mermaid
graph LR
A[Scrape Request] --> B[Fetch Buckets]
B --> C[Get Bucket Info]
C --> D[Count Events]
D --> E[Track Active Hosts]
E --> F[Calculate Rates]
F --> G[Update Registry]
G --> H[Format Output]
H --> I[Return to Prometheus]
```
## Ключевые функции
### Инициализация
- `aw_activitywatch_activitywatchexporter_init()` - инициализация exporter
- `aw_activitywatch_activitywatchexporter()` - главный класс exporter
- `aw_activitywatch_main()` - точка входа
### Сбор данных
- `aw_activitywatch_activitywatchexporter_get_buckets()` - получение списка buckets
- `aw_activitywatch_activitywatchexporter_get_bucket_info()` - информация о bucket
- `aw_activitywatch_activitywatchexporter_get_bucket_events()` - события bucket
- `aw_activitywatch_activitywatchexporter_collect_metrics()` - сбор всех метрик
### HTTP Server
- Запуск HTTP сервера на порту 9398
- Endpoint `/metrics` для Prometheus
- Endpoint `/health` для health checks
### Метрики
```python
# Bucket metrics
aw_bucket_events_total{bucket_id, host} - общее количество событий
aw_bucket_events_last_timestamp{bucket_id} - timestamp последнего события
# Host metrics
aw_host_active{host} - хост активен (1/0)
aw_host_last_seen{host} - время последней активности
# Collector metrics
aw_collector_heartbeat{host, collector_type} - heartbeat коллектора
aw_collector_status{host, collector_type, status} - статус коллектора
# Aggregation metrics
aw_aggregation_events_processed_total - обработано событий
aw_aggregation_last_success_timestamp - последний успешный запуск
# Exporter metrics
aw_exporter_scrape_duration_seconds - время обработки scrape
aw_exporter_up - exporter доступен (1/0)
```
## Конфигурация
### Config
```json
{
"aw_base_url": "http://aw-server:5600",
"listen_address": "0.0.0.0",
"listen_port": 9398,
"scrape_interval_seconds": 60,
"bucket_prefixes": [
"aw-watcher-dlp-endpoint",
"aw-watcher-browser-domains",
"aw-watcher-email-outbound",
"aw-watcher-window"
],
"cache_ttl_seconds": 30
}
```
### Environment Variables
```bash
AW_BASE_URL=http://aw-server:5600
EXPORTER_PORT=9398
SCRAPE_INTERVAL=60
LOG_LEVEL=INFO
```
## Пример метрик
### Prometheus Format
```
# HELP aw_bucket_events_total Total number of events in bucket
# TYPE aw_bucket_events_total gauge
aw_bucket_events_total{bucket_id="aw-watcher-dlp-endpoint-WORKSTATION01",host="WORKSTATION01"} 1523.0
aw_bucket_events_total{bucket_id="aw-watcher-browser-domains-WORKSTATION01",host="WORKSTATION01"} 3421.0
# HELP aw_host_active Host is active (1 or 0)
# TYPE aw_host_active gauge
aw_host_active{host="WORKSTATION01"} 1.0
aw_host_active{host="WORKSTATION02"} 0.0
# HELP aw_host_last_seen Unix timestamp of last activity
# TYPE aw_host_last_seen gauge
aw_host_last_seen{host="WORKSTATION01"} 1704110400.0
aw_host_last_seen{host="WORKSTATION02"} 1704100000.0
# HELP aw_exporter_scrape_duration_seconds Duration of scrape
# TYPE aw_exporter_scrape_duration_seconds gauge
aw_exporter_scrape_duration_seconds 0.523
# HELP aw_exporter_up Exporter is up (1 or 0)
# TYPE aw_exporter_up gauge
aw_exporter_up 1.0
```
## Prometheus Configuration
### scrape_config
```yaml
scrape_configs:
- job_name: 'activitywatch'
static_configs:
- targets: ['localhost:9398']
scrape_interval: 60s
scrape_timeout: 30s
metrics_path: /metrics
```
### Alerting Rules
```yaml
groups:
- name: activitywatch_alerts
rules:
- alert: AWCollectorDown
expr: aw_host_active == 0
for: 5m
labels:
severity: warning
annotations:
summary: "ActivityWatch collector down on {{ $labels.host }}"
- alert: AWExporterDown
expr: aw_exporter_up == 0
for: 2m
labels:
severity: critical
annotations:
summary: "ActivityWatch exporter is down"
- alert: AWHighEventRate
expr: rate(aw_bucket_events_total[5m]) > 100
for: 5m
labels:
severity: warning
annotations:
summary: "High event rate on {{ $labels.host }}"
```
## Grafana Dashboard Queries
### Active Hosts
```promql
sum(aw_host_active) by (host)
```
### Events per Bucket
```promql
aw_bucket_events_total
```
### Event Rate
```promql
rate(aw_bucket_events_total[5m])
```
### Host Last Seen
```promql
aw_host_last_seen
```
### Collector Status
```promql
aw_collector_status{status="running"}
```
## Производительность
### Оптимизации
- Кэширование ответов AW API
- Batch запросы к buckets
- Connection pooling
- Асинхронная обработка
### Метрики производительности
```python
performance_metrics = {
"scrape_duration_seconds": 0.5,
"aw_api_latency_seconds": 0.2,
"metrics_count": 50,
"cache_hit_rate": 0.95
}
```
## Docker Deployment
### Dockerfile
```dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY collectors/aw_activitywatch.py .
EXPOSE 9398
CMD ["python", "aw_activitywatch.py"]
```
### Docker Compose
```yaml
services:
aw-exporter:
build: ./sql-exporter
ports:
- "9398:9398"
environment:
- AW_BASE_URL=http://aw-server:5600
- EXPORTER_PORT=9398
depends_on:
- aw-server
restart: unless-stopped
```
## Health Check
### Endpoint: /health
```json
{
"status": "healthy",
"timestamp": "2024-01-01T12:00:00Z",
"aw_server_reachable": true,
"last_scrape_duration_seconds": 0.523,
"metrics_count": 50
}
```
### Health Check Script
```bash
#!/bin/bash
response=$(curl -s http://localhost:9398/health)
status=$(echo $response | jq -r '.status')
if [ "$status" == "healthy" ]; then
echo "Exporter is healthy"
exit 0
else
echo "Exporter is unhealthy"
exit 1
fi
```
## Логирование
### Log Format
```json
{
"timestamp": "2024-01-01T12:00:00Z",
"level": "INFO",
"message": "Metrics collection completed",
"duration_seconds": 0.523,
"buckets_processed": 10,
"metrics_generated": 50
}
```
### Log Levels
- DEBUG - детальная информация о сборе
- INFO - нормальная работа
- WARNING - не критичные проблемы
- ERROR - ошибки при сборе метрик
- CRITICAL - exporter недоступен
## Безопасность
### Рекомендации
- Запускать за reverse proxy (nginx)
- Ограничить доступ по IP
- Использовать HTTPS в production
- Не экспортировать чувствительные данные
### Nginx Config
```nginx
location /metrics {
allow 10.0.0.0/8;
deny all;
proxy_pass http://localhost:9398/metrics;
}
```
+337
View File
@@ -0,0 +1,337 @@
# WebUI Russian Localization Patches - Компонентная диаграмма
## Обзор
Система патчей для русификации и адаптации ActivityWatch WebUI под российские требования.
## Архитектура
```mermaid
graph TB
subgraph "Patch System"
A[Patch Loader]
B[DOM Injection]
C[Style Injection]
D[Navigation Patches]
E[DLP Center Injection]
F[Host Groups Center]
G[Category Builder Patch]
H[Text Translation]
end
subgraph "ActivityWatch WebUI"
I[Original WebUI]
J[DOM Structure]
K[Vue Components]
L[Router]
M[Axios API]
end
subgraph "Patch Targets"
N[Navigation Menu]
O[Activity View]
P[Category Builder]
Q[Buckets View]
R[Settings]
end
A --> B
A --> C
A --> D
A --> E
A --> F
A --> G
A --> H
B --> J
C --> J
D --> L
E --> K
F --> K
G --> K
H --> K
D --> N
E --> K
F --> K
G --> P
H --> O
I --> J
I --> K
I --> L
style B fill:#ff6b6b
style C fill:#ff6b6b
style E fill:#4ecdc4
style F fill:#4ecdc4
style H fill:#ffe66d
```
## Потоки данных
### Patch Application Flow
```mermaid
sequenceDiagram
participant User as Browser
participant WebUI as AW WebUI
participant Loader as Patch Loader
participant DOM as DOM Manipulator
participant API as AW API
User->>WebUI: Load page
WebUI->>Loader: Trigger patch application
Loader->>Loader: Check route
Loader->>DOM: Inject DLP center
DOM->>DOM: Modify navigation
DOM->>DOM: Add Russian styles
Loader->>DOM: Translate text elements
DOM->>DOM: Replace text with Russian
Loader->>API: Fetch Russian data
API->>Loader: Return translated data
Loader->>DOM: Update UI with Russian
DOM->>User: Render patched UI
```
### DLP Center Injection Flow
```mermaid
graph LR
A[Route Check] --> B{Is DLP route?}
B -->|Yes| C[Load DLP Center Component]
B -->|No| D[Skip DLP injection]
C --> E[Inject into DOM]
E --> F[Bind Events]
F --> G[Load DLP Data]
G --> H[Render DLP UI]
```
## Ключевые функции
### Загрузка патчей
- `aw_ru_patch_applypatch()` - применение патча
- `aw_ru_patch_scheduleapplypatch()` - отложенное применение
- `aw_ru_patch_ensurehostgroupsdata()` - обеспечение данных групп хостов
### Инъекция компонентов
- `aw_ru_patch_injectdlpnavigation()` - инъекция DLP навигации
- `aw_ru_patch_injectdlpreviewcenter()` - инъекция центра просмотра DLP
- `aw_ru_patch_injectdlpalertscenter()` - инъекция центра алертов
- `aw_ru_patch_injecthostgroupscenter()` - инъекция центра групп хостов
- `aw_ru_patch_injectpveauditcenter()` - инъекция центра аудита PVE
### Патчи навигации
- `aw_ru_patch_findprimarynavlist()` - поиск главного меню
- `aw_ru_patch_hidenoisenavigation()` - скрытие лишних пунктов
- `aw_ru_patch_removebaddlplinks()` - удаление битых DLP ссылок
- `aw_ru_patch_updatedlplinks()` - обновление DLP ссылок
### Патчи представлений
- `aw_ru_patch_enforcesafeactivityviewforpvehost()` - безопасный вид для PVE
- `aw_ru_patch_collapsereviewevents()` - сворачивание событий ревью
- `aw_ru_patch_collapseruleevents()` - сворачивание событий правил
- `aw_ru_patch_loadbucketevents()` - загрузка событий bucket
### Группы хостов
- `aw_ru_patch_getdefaulthostgroupsconfig()` - конфигурация групп по умолчанию
- `aw_ru_patch_matchhostgroup()` - сопоставление группы хоста
- `aw_ru_patch_renderhostgroupcards()` - рендер карточек групп
- `aw_ru_patch_hosthasbucketprefix()` - проверка префикса bucket
### Перевод
- `aw_ru_patch_translateattributes()` - перевод атрибутов
- `aw_ru_patch_normalizetext()` - нормализация текста
- `aw_ru_patch_escapehtml()` - экранирование HTML
### Утилиты
- `aw_ru_patch_replacetext()` - замена текста
- `aw_ru_patch_walk()` - обход DOM
- `aw_ru_patch_ishomeroute()` - проверка домашнего маршрута
- `aw_ru_patch_ispvelikehost()` - проверка PVE хоста
## Структура патчей
### Патчи по маршрутам
```javascript
const routePatches = {
'/': 'home-patch',
'/activity': 'activity-patch',
'/buckets': 'buckets-patch',
'/category-builder': 'category-builder-patch',
'/dlp/review': 'dlp-review-patch',
'/dlp/rules': 'dlp-rules-patch',
'/dlp/alerts': 'dlp-alerts-patch'
};
```
### Инъекции DLP компонентов
```javascript
const dlpInjections = {
navigation: 'inject-dlp-nav',
reviewCenter: 'inject-dlp-review',
rulesManager: 'inject-dlp-rules',
alertsCenter: 'inject-dlp-alerts'
};
```
### Русификация
```javascript
const translations = {
'Activity': 'Активность',
'Buckets': 'Бакеты',
'Duration': 'Длительность',
'Events': 'События',
'Settings': 'Настройки'
};
```
## Конфигурация
### Host Groups Config
```json
{
"host_groups": [
{
"id": "workstations",
"name": "Рабочие станции",
"hosts": ["WORKSTATION01", "WORKSTATION02"],
"color": "#4ecdc4"
},
{
"id": "servers",
"name": "Серверы",
"hosts": ["SERVER01", "SERVER02"],
"color": "#ff6b6b"
}
]
}
```
### DLP Settings
```json
{
"dlp_enabled": true,
"review_center_enabled": true,
"alerts_center_enabled": true,
"auto_refresh_interval": 30,
"default_severity_filter": "all"
}
```
## Компоненты UI
### DLP Review Center
- Список инцидентов
- Фильтрация по типам
- Детальный просмотр
- Архивирование
- Экспорт
### DLP Rules Manager
- Список правил
- Создание/редактирование
- Включение/выключение
- Тестирование правил
- Импорт/экспорт
### DLP Alerts Center
- Алерты в реальном времени
- Группировка по серьезности
- История алертов
- Подписки на алерты
### Host Groups Center
- Управление группами хостов
- Визуализация по группам
- Агрегированная статистика
- Сравнение групп
## Интеграция с ActivityWatch
### API Endpoints
```javascript
// Получение DLP инцидентов
GET /api/0/buckets/{bucket_id}/events?filters=dlp_incident
// Получение правил DLP
GET /api/0/dlp/rules
// Сохранение инцидента
POST /api/0/dlp/incident
// Получение групп хостов
GET /api/0/host_groups
```
### Bucket Prefixes
```
aw-watcher-dlp-endpoint-*
aw-watcher-browser-domains-*
aw-watcher-email-outbound-*
```
## Стили
### Russian UI Styles
```css
.ru-font {
font-family: 'Segoe UI', 'Roboto', sans-serif;
}
.ru-nav-item {
padding: 8px 16px;
border-radius: 4px;
}
.ru-dlp-card {
border-left: 4px solid #ff6b6b;
background: #fff5f5;
}
```
## Производительность
### Оптимизации
- Lazy loading патчей
- Кэширование переводов
- Debouncing обновлений DOM
- Virtual scrolling для больших списков
### Метрики
- Время применения патча
- Количество модификаций DOM
- Частота перерисовки
- Размер загружаемых переводов
## Отладка
### Логирование патчей
```javascript
console.log('[AW-RU-Patch] Applying patch:', patchName);
console.log('[AW-RU-Patch] Route:', currentRoute);
console.log('[AW-RU-Patch] Elements modified:', count);
```
### Режим разработки
```javascript
const DEBUG_MODE = true;
if (DEBUG_MODE) {
window.AW_RU_PATCHES = {
applied: [],
skipped: [],
errors: []
};
}
```
## Совместимость
### Поддерживаемые версии ActivityWatch
- v0.12.x
- v0.13.x
- v0.14.x
### Поддерживаемые браузеры
- Chrome 90+
- Firefox 88+
- Edge 90+
- Safari 14+
+103
View File
@@ -0,0 +1,103 @@
# Central DLP aggregator prototype
`scripts/aggregate_dlp_events.py` collects Phase 2 DLP telemetry from ActivityWatch buckets and stores normalized rows in one database for Grafana/SIEM-style reporting.
## Streams
The prototype reads:
- `aw-file-operations_*` (`aw.file.operation`) — file create/delete/rename telemetry, including `archiveHint`.
- `aw-dlp-incidents_*` (`aw.dlp.incident`) — browser/endpoint DLP incidents and screenshot metadata when available.
## SQLite smoke test
SQLite is the default so the collector can be tested without deploying PostgreSQL:
```bash
python3 scripts/aggregate_dlp_events.py \
--aw-url http://10.10.10.13:5600/api/0 \
--sqlite-path data/dlp-events.sqlite3 \
--lookback-hours 24
```
Useful checks:
```bash
sqlite3 data/dlp-events.sqlite3 \
"select stream_type, hostname, count(*) from dlp_events group by 1,2 order by 3 desc;"
sqlite3 data/dlp-events.sqlite3 \
"select event_ts, hostname, username, file_path from dlp_file_operations where archive_hint = 1 order by event_ts desc limit 20;"
```
## PostgreSQL mode
For centralized reporting, pass a DSN through an environment variable instead of committing secrets:
```bash
export DLP_AGGREGATOR_POSTGRES_DSN='postgresql://aw_dlp:${PASSWORD}@postgres.internal:5432/aw_dlp'
python3 -m pip install 'psycopg[binary]'
python3 scripts/aggregate_dlp_events.py \
--aw-url http://10.10.10.13:5600/api/0
```
Minimum database bootstrap:
```sql
create database aw_dlp;
create user aw_dlp_ingest with password '<strong generated password>';
grant connect on database aw_dlp to aw_dlp_ingest;
grant usage, create on schema public to aw_dlp_ingest;
```
The script creates:
- table `dlp_events`
- view `dlp_file_operations`
- view `dlp_incidents`
## Incremental state
By default, the aggregator stores the last successful end timestamp in:
```text
data/dlp-aggregator-state.json
```
Future runs resume from that timestamp with a small overlap window to avoid missing late events. Duplicate inserts are ignored by `(bucket_id, event_id)`.
## Scheduling example
Cron every minute:
```cron
* * * * * cd /opt/AWatch-rus && /usr/bin/python3 scripts/aggregate_dlp_events.py --aw-url http://10.10.10.13:5600/api/0 >> /var/log/aw-dlp-aggregator.log 2>&1
```
## Example Grafana queries
Archive creation by user:
```sql
select
date_trunc('minute', event_ts) as time,
hostname,
username,
count(*) as archives
from dlp_file_operations
where archive_hint = true
group by 1, 2, 3
order by 1 desc;
```
DLP incidents by severity:
```sql
select
date_trunc('hour', event_ts) as time,
severity,
count(*) as incidents
from dlp_incidents
group by 1, 2
order by 1 desc;
```
+126
View File
@@ -0,0 +1,126 @@
# 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
```
+38 -2
View File
@@ -29,8 +29,44 @@
- USB/print/clipboard collectors (endpoint signals) — внедрено.
- Incident pipeline расширен на endpoint события — внедрено.
- File-operation telemetry (create/copy/archive/upload hints) — в backlog.
- Central incident aggregation/export — в backlog.
- 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`.
### Phase 3
+151
View File
@@ -0,0 +1,151 @@
# DLP Reliability Roadmap
## Scope
Roadmap for improving runtime reliability of:
- `windows/dlp-endpoint-signals-collector.ps1`
- `windows/file-operations-collector.ps1`
Date: 2026-05-04
---
## Stage 1 (1-2 days): Quick wins
### 1) Disk queue + sender loop + retry/backoff/jitter
**Goal:** no data loss on temporary network/server outages.
**Tasks**
- Add local append-only queue file per collector (`*.jsonl`) under ProgramData logs/artifacts root.
- Write events to queue first, then send asynchronously.
- Implement sender loop:
- reads oldest unsent records,
- sends in small batches,
- marks sent records,
- compacts queue periodically.
- Implement retry policy with exponential backoff + jitter.
**Acceptance criteria**
- When API is unavailable, queue grows and collector keeps running.
- When API recovers, queued events are flushed automatically.
- No collector crash during repeated network failures.
### 2) `eventId` + dedupe contract
**Goal:** at-least-once delivery without logical duplicates.
**Tasks**
- Add `eventId` (UUID), `eventCreatedAt`, `collectorType`, `hostname` to every payload.
- Define server dedupe contract:
- dedupe key = `eventId`,
- TTL for dedupe cache,
- idempotent processing semantics.
**Acceptance criteria**
- Retried sends do not create duplicate incidents/events in downstream storage.
- Payload schema documentation updated.
### 3) Basic metrics/logging
**Goal:** visibility into health and data delivery.
**Tasks**
- Emit counters/gauges to log and heartbeat:
- `queueDepth`,
- `oldestUnsentAgeSec`,
- `eventsEnqueued`,
- `eventsSent`,
- `sendFailures`,
- `lastSendStatus`.
**Acceptance criteria**
- Operators can identify stuck queue and send failures from logs only.
---
## Stage 2: Hardening
### 1) Circuit breaker + health probes
**Tasks**
- Add transport circuit breaker (Closed/Open/HalfOpen).
- Open breaker after N consecutive failures.
- In Open state perform probe every M seconds.
- Close breaker on successful probe.
**Acceptance criteria**
- Reduced request storm during outage.
- Deterministic recovery behavior after outage.
### 2) Watcher auto-recreate
**Tasks**
- Handle `FileSystemWatcher` error/overflow events.
- Recreate watcher and subscriptions automatically.
- Keep watchdog timer to ensure watcher health.
**Acceptance criteria**
- Watcher resumes after overflow without manual restart.
### 3) Last-known-good policy
**Tasks**
- Validate new policy before apply.
- Cache last valid policy with checksum/version.
- Rollback to cached policy on parse/validation errors.
**Acceptance criteria**
- Broken policy cannot stop detection loop.
---
## Stage 3: Reliability operations
### 1) Chaos tests
Scenarios:
- network disconnect,
- API 5xx bursts,
- slow disk / queue write delay,
- headless UI context,
- forced collector restart.
**Acceptance criteria**
- For each scenario, documented expected behavior and observed result.
- No silent data loss in tested outage windows.
### 2) SLO + error budget process
**Initial SLO proposals**
- Event delivery latency P95 < 120s under normal conditions.
- Data loss = 0 for outages shorter than 30 minutes (with available disk).
- Collector liveness heartbeat every `pollSeconds * 3` max.
**Process**
- Define SLI dashboards.
- Define release gates tied to error budget burn.
- Freeze risky changes when budget exhausted.
---
## Suggested implementation order inside repository
1. `file-operations-collector.ps1`: queue + sender + metrics (simpler flow).
2. `dlp-endpoint-signals-collector.ps1`: queue + sender + metrics.
3. Shared helper module extraction (`windows/lib/aw-transport.psm1`) for queue, retry, breaker.
4. Policy cache and validation.
5. Chaos test scripts and runbook.
---
## Deliverables checklist
- [ ] Transport queue implementation in both collectors.
- [ ] Payload schema update with `eventId`.
- [ ] Dedupe contract documented for server side.
- [ ] Metrics fields added to heartbeat/logs.
- [ ] Circuit breaker implemented.
- [ ] Watcher auto-recreate implemented.
- [ ] Last-known-good policy implemented.
- [ ] Chaos test runbook and results.
- [ ] SLO/error budget document adopted.
+164
View File
@@ -0,0 +1,164 @@
# Email Outbound Collector
## Обзор
Мониторинг исходящей почты на Windows-эндпоинтах. Два режима работы:
| Режим | Источник | Данные |
|----------|--------------------------------|-------------------------------------------------------|
| outlook | Outlook COM (Sent Items) | Subject, From, To/CC, вложения, размер тела |
| smtp | `Get-NetTCPConnection` | SMTP-соединения (порты 25/587/465/2525), процесс |
По умолчанию `Mode = 'both'` — оба режима активны одновременно.
## Запуск
```powershell
# С deployment-config.json (штатный вариант)
.\email-outbound-collector.ps1
# С явными параметрами
.\email-outbound-collector.ps1 -ServerHost 10.10.10.13 -ServerPort 5600 -Mode outlook
# Только SMTP мониторинг (без Outlook)
.\email-outbound-collector.ps1 -ServerHost 10.10.10.13 -Mode smtp
```
### Параметры
| Параметр | По умолчанию | Описание |
|----------------|-----------------------------------------|---------------------------------|
| `-ConfigPath` | `C:\ProgramData\AWatch-rus\deployment-config.json` | Путь к конфигу |
| `-ServerHost` | из конфига | Адрес AW-сервера |
| `-ServerPort` | из конфига / 5600 | Порт AW-сервера |
| `-PolicyPath` | из конфига / `dlp-policy.json` | Путь к DLP-политике |
| `-Mode` | `both` | `outlook`, `smtp`, или `both` |
| `-PollSeconds` | из конфига / 10 | Интервал опроса |
## AW Buckets
- `aw-email-monitor_<host>` — все email-события (signal heartbeats)
- `aw-dlp-incidents_<host>` — инциденты при срабатывании DLP-правил
## DLP-политика: секция `endpoint.email`
Добавляется в существующий `dlp-policy.json`:
```json
{
"endpoint": {
"email": [
{
"id": "block-external-attachments",
"action": "block",
"severity": "high",
"minAttachments": 1,
"externalOnly": true,
"internalDomain": "@company.ru",
"message": "Запрещена отправка вложений на внешние адреса"
},
{
"id": "alert-confidential-subject",
"action": "alert",
"severity": "medium",
"subjectRegex": "(?i)(конфиденциально|секретно|для служебного пользования)",
"message": "Обнаружена отправка письма с пометкой конфиденциальности"
},
{
"id": "alert-personal-data",
"action": "alert",
"severity": "high",
"recipientRegex": "(?i)(gmail\\.com|mail\\.ru|yandex\\.ru|yahoo\\.com)",
"minAttachments": 1,
"message": "Отправка вложений на личную почту"
}
]
}
}
```
### Параметры правил
| Поле | Тип | Описание |
|-------------------|--------|-----------------------------------------------------------|
| `id` | string | Уникальный ID правила (обязательно) |
| `action` | string | `alert` (по умолчанию) или `block` |
| `severity` | string | `low`, `medium`, `high`, `critical` |
| `subjectRegex` | string | Regex по теме письма |
| `recipientRegex` | string | Regex по списку получателей |
| `senderRegex` | string | Regex по адресу отправителя |
| `attachmentRegex` | string | Regex по именам вложений |
| `minAttachments` | int | Минимальное количество вложений для срабатывания |
| `minBodyLength` | int | Минимальная длина тела письма |
| `externalOnly` | bool | Срабатывать только на внешних получателей |
| `internalDomain` | string | Домен организации (используется с `externalOnly`) |
| `cooldownSeconds` | int | Cooldown между повторными инцидентами |
| `message` | string | Текст уведомления пользователю и в инцидент |
## Enforcement (action: "block")
**Outlook mode**: письмо перемещается из Sent Items в Drafts. Пользователь получает balloon notification.
**SMTP mode**: только уведомление (перехват SMTP-соединения на сетевом уровне не реализуем из PowerShell). Инцидент записывается с `enforced: false`.
## Телеметрия
### Heartbeat `email_sent` (Outlook mode)
```json
{
"signalType": "email_sent",
"subject": "<sha256 hash>",
"sender": "user@company.ru",
"recipientCount": 3,
"recipients": "<sha256 hash>",
"attachmentCount": 2,
"attachmentNames": "report.xlsx; data.csv",
"bodyLength": 1520,
"collectionMode": "outlook"
}
```
### Heartbeat `smtp_connection` (SMTP mode)
```json
{
"signalType": "smtp_connection",
"remoteAddress": "74.125.205.108",
"remotePort": 587,
"processId": 12340,
"processName": "OUTLOOK",
"collectionMode": "smtp"
}
```
### Incident
```json
{
"ruleId": "block-external-attachments",
"action": "block",
"severity": "high",
"signalType": "email_outbound",
"subject": "<sha256>",
"attachmentCount": 2,
"enforced": true
}
```
## Приватность
- Тема и получатели записываются как SHA256-хеш (не открытый текст).
- Тело письма не читается и не хранится — записывается только длина.
- Имена вложений записываются открытым текстом (для DLP-анализа).
## Интеграция в ensemble
Добавьте в `launch-watchers.ps1` или Task Scheduler:
```powershell
Start-Process powershell.exe -ArgumentList '-ExecutionPolicy Bypass -File "C:\ProgramData\AWatch-rus\email-outbound-collector.ps1"' -WindowStyle Hidden
```
## Требования
- **Outlook mode**: Microsoft Outlook установлен и настроен для текущего пользователя.
- **SMTP mode**: Не требует дополнительного ПО. Работает на уровне TCP-соединений.
- **Enforcement (block)**: Outlook mode — требует доступ к COM объекту Outlook.
+256
View File
@@ -0,0 +1,256 @@
# 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
+75
View File
@@ -0,0 +1,75 @@
# Анализ DLP-скриптов PowerShell (работоспособность)
Дата анализа: **2026-05-04 (UTC)**
## Проверенный scope
- `windows/dlp-endpoint-signals-collector.ps1`
- `windows/file-operations-collector.ps1`
- `windows/dlp-policy.example.json`
- `windows/web-category-rules.example.json`
## Ключевой итог
DLP-скрипты в целом рабочие по архитектуре (heartbeat в ActivityWatch, policy-driven правила, cooldown, enforcement), но есть **критичный риск misconfiguration** и несколько эксплуатационных рисков.
---
## Что точно хорошо
1. В обоих коллекторах включены `Set-StrictMode -Version Latest` и `$ErrorActionPreference = 'Stop'`.
2. Есть отправка событий в отдельные bucket’ы (`aw-dlp-endpoint-signals_*`, `aw-dlp-incidents_*`, `aw-file-operations_*`).
3. В endpoint-коллекторе реализованы:
- правила по буферу обмена / USB / печати,
- suppression через cooldown (`Should-EmitByCooldown`),
- опциональный screenshot capture при инциденте.
4. В file collector есть наблюдение за `Desktop/Documents/Downloads` через `FileSystemWatcher`.
---
## Найденные проблемы и риски
### 1) Критично: дефолтный путь конфига в endpoint-скрипте не совпадает с проектом
- `dlp-endpoint-signals-collector.ps1` использует по умолчанию:
- `C:\ProgramData\AWatch-rus\deployment-config.json`
- Остальной проект использует namespace `AWatch-rus` (`C:\ProgramData\AWatch-rus\...`).
**Риск:** endpoint-коллектор может стартовать без нужного deployment-конфига и работать с неверными/пустыми параметрами.
### 2) Нет строгой проверки HTTP-результата в file collector
В `file-operations-collector.ps1` POST выполняется через `HttpClient`, но код ответа не валидируется (`IsSuccessStatusCode` не проверяется), ошибки частично только логируются.
**Риск:** «тихая» потеря telemetry при 4xx/5xx.
### 3) Watcher не снимает event subscriptions явно
Есть `Register-ObjectEvent`, но в `finally` disposal только watcher-объектов; отписка событий (`Unregister-Event`) явно не делается.
**Риск:** при рестартах/долгой работе возможно накопление подписок в сессии.
### 4) Screenshot/GUI-зависимость для enforcement
`Capture-IncidentScreenshot` и balloon notification завязаны на `System.Windows.Forms/System.Drawing`.
**Риск:** в non-interactive / service context часть enforcement UX может не работать (событие уйдёт, но скриншот/уведомление может не сформироваться).
---
## Рекомендации (приоритет)
1. **P1:** выровнять дефолтный `ConfigPath` в `dlp-endpoint-signals-collector.ps1` на `C:\ProgramData\AWatch-rus\deployment-config.json`.
2. **P1:** добавить проверку `response.IsSuccessStatusCode` в `file-operations-collector.ps1` и логировать body/status при ошибках.
3. **P2:** сохранить subscription-объекты `Register-ObjectEvent` и делать `Unregister-Event` в `finally`.
4. **P2:** для enforcement/UI добавить fallback режим «headless» (только лог + heartbeat).
---
## Что не удалось проверить в текущей среде
В этом контейнере отсутствует `pwsh`, поэтому не выполнены:
- синтаксический parse всех `*.ps1/*.psm1` через PowerShell parser;
- `Test-ModuleManifest`;
- smoke-run на Windows API (`Get-WinEvent`, `Get-Partition`, `Get-Disk`, `Set-Clipboard`, `Win32_PrintJob`).
+183
View File
@@ -0,0 +1,183 @@
# Стратегический анализ: 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 слишком дорог и тяжёл.
+71
View File
@@ -0,0 +1,71 @@
# Архитектура системы
## Обзор
ActivityWatch-Russian состоит из нескольких слоев:
```
Windows Clients (сбор данных)
↓ HTTP API
Linux Server (хранение и обработка)
↓ TCP
Integration Layer (интеграции)
↓ HTTP/TCP
Monitoring Stack (визуализация)
```
## Слои архитектуры
### 1. Windows Clients - Сбор данных
- **DLP Endpoint Collector** - мониторинг clipboard, печати, USB
- **Browser Domains Collector** - мониторинг браузеров
- **Email Outbound Collector** - мониторинг почты
- **Worktime Session Collector** - учет рабочего времени
### 2. Linux Server - Хранение и обработка
- **ActivityWatch Server** - основной сервер (Rust)
- **PostgreSQL Database** - хранилище данных
- **WebUI with RU Patches** - веб-интерфейс с русификацией
### 3. Integration Layer - Интеграции
- **pfSense Poller** - сбор логов firewall
- **DLP Aggregation Scripts** - агрегация DLP событий
- **Prometheus Exporter** - экспорт метрик
### 4. Monitoring Stack - Мониторинг
- **Prometheus** - сбор метрик
- **Grafana** - визуализация
- **SQL Exporter** - прямые SQL запросы
## Потоки данных
### DLP инцидент
```
User → DLP Collector → Проверка правил → AW Server → PostgreSQL → Grafana
```
### Браузер мониторинг
```
Browser → Browser Collector → Категоризация → AW Server → WebUI
```
### Метрики
```
AW Server → Exporter → Prometheus → Grafana
```
## Порты
| Компонент | Порт | Протокол |
|-----------|------|----------|
| ActivityWatch API | 5600 | HTTP |
| ActivityWatch WebSocket | 5666 | WebSocket |
| PostgreSQL | 5432 | TCP |
| Prometheus | 9090 | HTTP |
| Grafana | 3000 | HTTP |
| Prometheus Exporter | 9398 | HTTP |
## Подробнее
- [Компоненты системы](Components) - детальное описание компонентов
- [Интерактивная карта](Interactive-Map) - визуальная схема связей
+145
View File
@@ -0,0 +1,145 @@
# Browser Domains Monitoring
Мониторинг посещаемых доменов в браузерах с категоризацией и проверкой DLP правил.
## Обзор
Browser Domains Collector отслеживает:
- Посещаемые веб-сайты
- Категоризацию доменов
- DLP проверку запрещенных сайтов
- Скриншоты при нарушениях
## Архитектура
```
Browser → Window Detection → URL Extraction → Domain Parsing → Categorization → DLP Check → ActivityWatch
```
## Функции
### Domain Detection
- Определение активного окна браузера
- Извлечение URL из заголовка окна
- Парсинг домена из URL
- Определение корневого домена
### Categorization
- Кастомные правила категорий
- Web Category API (опционально)
- Кэширование результатов
- Автоопределение категорий
### DLP Checking
- Проверка по спискам доменов
- DLP правила для доменов
- Временные окна
- Блокировка нарушений
## Поддерживаемые браузеры
- Google Chrome
- Mozilla Firefox
- Microsoft Edge
- Opera
- Яндекс.Браузер
## Конфигурация
### Custom Category Rules
```json
{
"custom_categories": {
"social_media": [
"*.facebook.com",
"*.twitter.com",
"*.instagram.com"
],
"news": [
"*.news.com",
"*.media.com"
],
"blocked": [
"*.malware.com",
"*.phishing.com"
]
}
}
```
### DLP Domain Rules
```json
{
"domain_rules": [
{
"pattern": "*gambling*",
"category": "gambling",
"action": "alert",
"severity": "medium"
},
{
"pattern": "*adult*",
"category": "adult",
"action": "block",
"severity": "high"
}
]
}
```
## События
### Browsing Event
```json
{
"timestamp": "2024-01-01T12:00:00Z",
"type": "browsing",
"data": {
"url": "https://www.example.com/page",
"domain": "example.com",
"root_domain": "example.com",
"category": "technology",
"browser": "chrome.exe",
"title": "Example Page Title",
"user": "user1",
"host": "WORKSTATION01"
}
}
```
### DLP Incident Event
```json
{
"timestamp": "2024-01-01T12:00:00Z",
"type": "dlp_incident",
"source": "browser_domain",
"rule_id": "blocked_domains",
"severity": "high",
"data": {
"url": "https://blocked.com",
"domain": "blocked.com",
"category": "malicious",
"screenshot": "path/to/screenshot.png"
}
}
```
## Производительность
### Оптимизации
- Кэширование категорий (TTL 24h)
- Batch запросы к category API
- Debouncing быстрых переходов
- Асинхронная отправка событий
## Мониторинг
- Количество уникальных доменов
- Распределение по категориям
- Частота DLP инцидентов
- Кэш hit rate
## Подробнее
- [Детальная диаграмма](https://github.com/igor04091968/AWatch-rus/blob/main/docs/diagrams/browser-domains-monitoring.md)
- [Категоризация сайтов](Web-Categorization)
+108
View File
@@ -0,0 +1,108 @@
# Компоненты системы
## Windows Collectors
### DLP Endpoint Collector
Мониторинг конечных точек для обнаружения утечек данных.
**Функции:**
- Clipboard мониторинг
- Print job мониторинг
- USB write блокировка
- DLP правило evaluation
- Скриншоты при инцидентах
**Файл:** `windows/dlp-endpoint-signals-collector.ps1`
### Browser Domains Collector
Отслеживание посещаемых доменов с категоризацией.
**Функции:**
- Определение домена из URL
- Категоризация сайтов
- DLP проверка доменов
- Скриншоты при нарушениях
**Файл:** `windows/browser-domains-native-collector.ps1`
### Email Outbound Collector
Мониторинг исходящей почты.
**Функции:**
- Outlook Sent Items мониторинг
- SMTP соединения
- Email правило evaluation
- Блокировка нарушений
**Файл:** `windows/email-outbound-collector.ps1`
### Worktime Session Collector
Учет рабочего времени.
**Функции:**
- Определение сессий
- Учет перерывов
- Агрегация по дням
**Файл:** `windows/worktime-session-collector.ps1`
## Server Components
### ActivityWatch Server
Основной сервер для приема и хранения событий.
**Технологии:** Rust
**API:** HTTP на порту 5600
### PostgreSQL Database
Основное хранилище данных.
**Таблицы:**
- `dlp_events` - DLP инциденты
- `aggregation_state` - состояние агрегации
- `dlp_statistics` - статистика
### WebUI with RU Patches
Веб-интерфейс с русской локализацией.
**Файлы:** `aw-server/aw-ru-patch.js`, `aw-server/aw-sw-cleanup.js`
## Integration Components
### pfSense Poller
Сбор логов с pfSense firewall.
**Файл:** `pfsense/pfsense-aw-poller.py`
### DLP Aggregation Scripts
Агрегация DLP событий в PostgreSQL.
**Файл:** `scripts/aggregate_dlp_events.py`
### Prometheus Exporter
Экспорт метрик в формате Prometheus.
**Файл:** `grafana-1c/sql-exporter/collectors/aw_activitywatch.py`
## Monitoring Components
### Prometheus
Сбор и хранение метрик.
**Порт:** 9090
### Grafana
Визуализация метрик и дашборды.
**Порт:** 3000
### SQL Exporter
Прямые SQL запросы к PostgreSQL.
**Порт:** 9398
## Подробнее
- [DLP Endpoint Monitoring](DLP-Endpoint-Monitoring) - детально о DLP коллекторе
- [Browser Domains Monitoring](Browser-Domains-Monitoring) - детально о браузерном коллекторе
- [WebUI Русификация](WebUI-Russian-Patches) - детально о патчах интерфейса
+124
View File
@@ -0,0 +1,124 @@
# DLP Endpoint Monitoring
Мониторинг конечных точек для обнаружения утечек данных через clipboard, печать и USB.
## Обзор
DLP Endpoint Collector отслеживает:
- **Clipboard** - буфер обмена
- **Print** - задания на печать
- **USB** - запись на USB накопители
## Архитектура
```
User Activity → DLP Collector → Rule Evaluation → Enforcement → ActivityWatch
```
## Функции
### Clipboard Monitoring
- Перехват clipboard событий
- Проверка по паттернам (credit cards, passport data, etc.)
- Блокировка копирования
- Скриншот при нарушении
### Print Monitoring
- Перехват print job событий
- Проверка принтеров и документов
- Блокировка печати
- Логирование попыток
### USB Monitoring
- Обнаружение USB устройств
- Блокировка записи
- Логирование подключений
- Политики по device ID
## Конфигурация
### DLP Policy
```json
{
"clipboard_rules": [
{
"pattern": "\\b\\d{4}-\\d{4}-\\d{4}-\\d{4}\\b",
"description": "Credit card numbers",
"severity": "high",
"action": "block"
}
],
"print_rules": [
{
"printer_match": "*",
"document_keywords": ["confidential", "secret"],
"action": "block"
}
],
"usb_rules": [
{
"device_id": "*",
"action": "block_write"
}
]
}
```
### Deployment Config
```json
{
"aw_server_url": "http://aw-server:5600",
"bucket_prefix": "aw-watcher-dlp-endpoint",
"heartbeat_interval": 60,
"screenshot_on_incident": true,
"enforcement_enabled": true
}
```
## Установка
```powershell
# Копирование коллектора
Copy-Item windows/dlp-endpoint-signals-collector.ps1 C:\ProgramData\AWatch-rus\
# Настройка scheduled task
Register-ScheduledTask -TaskName "DLP Endpoint Collector" -Trigger $trigger -Action $action
```
## События
### DLP Incident Event
```json
{
"timestamp": "2024-01-01T12:00:00Z",
"type": "dlp_incident",
"source": "clipboard",
"rule_id": "credit_card_pattern",
"severity": "high",
"data": {
"matched_text": "****-****-****-1234",
"user": "user1",
"host": "WORKSTATION01",
"screenshot": "path/to/screenshot.png"
}
}
```
## Требования
- Windows 10/11
- PowerShell 5.1+
- ActivityWatch installed
- Административные права (для enforcement)
## Мониторинг
- Количество инцидентов по типам
- Частота срабатываний правил
- Успешность enforcement действий
- Heartbeat статус
## Подробнее
- [Детальная диаграмма](https://github.com/igor04091968/AWatch-rus/blob/main/docs/diagrams/dlp-endpoint-monitoring.md)
- [DLP Правила](DLP-Rules)
+79
View File
@@ -0,0 +1,79 @@
# ActivityWatch-Russian Documentation
Добро пожаловать в документацию ActivityWatch-Russian - корпоративной системы мониторинга активности на базе ActivityWatch с русификацией и DLP функциями.
## 📚 Содержание
### Архитектура
- [Обзор архитектуры](Architecture) - высокоуровневая архитектура системы
- [Компоненты системы](Components) - описание всех компонентов
- [Интерактивная карта](Interactive-Map) - визуальная карта связей
### Компоненты
- [DLP Endpoint Monitoring](DLP-Endpoint-Monitoring) - мониторинг clipboard, печати, USB
- [Browser Domains Monitoring](Browser-Domains-Monitoring) - мониторинг браузеров
- [Email Outbound Monitoring](Email-Outbound-Monitoring) - мониторинг почты
- [WebUI Русификация](WebUI-Russian-Patches) - патчи интерфейса
- [DLP Агрегация](DLP-Aggregation) - обработка DLP событий
- [Prometheus Exporter](Prometheus-Exporter) - метрики для мониторинга
### Развертывание
- [Установка на Windows](Windows-Installation) - установка коллекторов
- [Настройка сервера](Server-Setup) - настройка Linux сервера
- [Grafana + Prometheus](Monitoring-Setup) - мониторинг стек
### Конфигурация
- [DLP Правила](DLP-Rules) - настройка DLP политик
- [Категоризация сайтов](Web-Categorization) - настройка категорий
- [Группы хостов](Host-Groups) - управление группами
## 🚀 Быстрый старт
### Минимальная конфигурация
```bash
# 1. Установка на Windows workstation
.\windows\deploy-domain-users.ps1
# 2. Запуск ActivityWatch Server
./aw-server/aw-server
# 3. Применение RU патчей
node aw-server/aw-ru-patch.js
```
### Полная конфигурация
```bash
# 1. Развертывание на Windows
.\windows\deploy-domain-users.ps1
# 2. Настройка сервера
cd ansible
ansible-playbook server-setup.yml
# 3. Запуск мониторинга стека
cd ../grafana-1c
docker-compose up -d
# 4. Агрегация DLP событий
python3 scripts/aggregate_dlp_events.py
```
## 📊 Обзор системы
ActivityWatch-Russian - это корпоративная система мониторинга активности пользователей с:
- **DLP мониторинг** - clipboard, печать, USB, браузеры, email
- **Русификация** - полный перевод интерфейса на русский
- **Аналитика** - агрегация данных и отчеты
- **Мониторинг** - Prometheus + Grafana дашборды
- **Автоматизация** - Ansible деплой на Windows и Linux
## 🔗 Ссылки
- [GitHub Repository](https://github.com/igor04091968/AWatch-rus)
- [ActivityWatch Official](https://activitywatch.net/)
- [Примеры конфигураций](https://github.com/igor04091968/AWatch-rus/tree/main/grafana-1c)
## 📝 Поддержка
Для вопросов и предложений используйте [Issues](https://github.com/igor04091968/AWatch-rus/issues).
+78
View File
@@ -0,0 +1,78 @@
# Интерактивная карта архитектуры
Для визуального исследования связей между компонентами используйте интерактивную HTML карту.
## Как использовать
### Локально
```bash
# Открыть в браузере
xdg-open docs/diagrams/architecture-map.html
# Или
file:///path/to/AWatch-rus/docs/diagrams/architecture-map.html
```
### С GitHub
1. Скачайте файл: [architecture-map.html](https://github.com/igor04091968/AWatch-rus/blob/main/docs/diagrams/architecture-map.html)
2. Откройте в браузере
## Функции карты
### Визуальные компоненты
- **Windows Clients** (красный) - коллекторы данных
- **Linux Server** (бирюзовый) - сервер и БД
- **Integration** (желтый) - интеграции
- **Monitoring** (зеленый) - мониторинг стек
- **External** (фиолетовый) - внешние системы
### Интерактивность
- **Клик по компоненту** - показывает детальную информацию
- **Подсветка связей** - выделяет связанные компоненты
- **Поиск** - быстрый поиск по названию
- **Потоки данных** - визуализация процесса
### Информация о компоненте
При клике на компонент показывается:
- Описание
- Связи с другими компонентами
- Потоки данных
- Порты и протоколы
## Слои архитектуры
### 1. Windows Clients
- DLP Endpoint Collector
- Browser Domains Collector
- Email Outbound Collector
- Worktime Session Collector
### 2. Linux Server
- ActivityWatch Server
- PostgreSQL Database
- WebUI with RU Patches
### 3. Integration Layer
- pfSense Poller
- DLP Aggregation Scripts
- Prometheus Exporter
### 4. Monitoring Stack
- Prometheus
- Grafana
- SQL Exporter
### 5. External Systems
- pfSense Firewall
- Domain Controller
## Альтернативные визуализации
### ASCII схема
Для быстрого понимания: [architecture-simple.md](https://github.com/igor04091968/AWatch-rus/blob/main/docs/architecture-simple.md)
### Mermaid диаграммы
Для технических деталей: [architecture-diagram.md](https://github.com/igor04091968/AWatch-rus/blob/main/docs/architecture-diagram.md)
### Graphify knowledge graph
Для анализа кода: [graphify-out/index.html](https://github.com/igor04091968/AWatch-rus/blob/main/graphify-out/index.html)
+292
View File
@@ -0,0 +1,292 @@
# Monitoring Setup
Настройка Prometheus + Grafana стека для мониторинга ActivityWatch.
## Требования
- Docker & Docker Compose
- 2GB+ RAM
- 20GB+ disk space
## Установка через Docker Compose
```bash
cd grafana-1c
docker-compose up -d
```
## Компоненты
### Prometheus
Сбор и хранение метрик.
**Порт:** 9090
**Конфиг:** `prometheus/prometheus.yml`
### Grafana
Визуализация метрик и дашборды.
**Порт:** 3000
**Конфиг:** `grafana/provisioning/`
### SQL Exporter
Прямые SQL запросы к PostgreSQL.
**Порт:** 9398
**Конфиг:** `sql-exporter/`
### AW Exporter
Экспорт метрик ActivityWatch.
**Порт:** 9398
**Файл:** `sql-exporter/collectors/aw_activitywatch.py`
## Конфигурация Prometheus
```yaml
# prometheus/prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'activitywatch'
static_configs:
- targets: ['aw-exporter:9398']
scrape_interval: 60s
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
```
## Конфигурация Grafana
### Datasources
```yaml
# grafana/provisioning/datasources/prometheus.yml
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
```
```yaml
# grafana/provisioning/datasources/postgres.yml
apiVersion: 1
datasources:
- name: PostgreSQL
type: postgres
access: proxy
url: postgres:5432
database: activitywatch
user: aw
password: aw_password
```
### Dashboards
```yaml
# grafana/provisioning/dashboards/dashboards.yml
apiVersion: 1
providers:
- name: 'ActivityWatch'
orgId: 1
folder: 'ActivityWatch'
type: file
disableDeletion: false
updateIntervalSeconds: 10
allowUiUpdates: true
options:
path: /etc/grafana/provisioning/dashboards
```
## Дашборды
### ActivityWatch Overview
- Активные хосты
- События по buckets
- Скорость поступления событий
- Статус коллекторов
**Файл:** `grafana/grafana/dashboards/aw_overview.json`
### DLP Incidents
- Количество инцидентов по типам
- Инциденты по пользователям
- Инциденты по серьезности
- Временные тренды
### Browser Monitoring
- Посещаемые домены
- Категории сайтов
- DLP нарушения
## Запуск
```bash
# Запуск всех сервисов
docker-compose up -d
# Проверка статуса
docker-compose ps
# Просмотр логов
docker-compose logs -f
# Остановка
docker-compose down
```
## Доступ
- **Grafana:** http://localhost:3000 (admin/admin)
- **Prometheus:** http://localhost:9090
- **AW Exporter:** http://localhost:9398/metrics
## Настройка алертов
### Prometheus Alerting Rules
```yaml
groups:
- name: activitywatch_alerts
rules:
- alert: AWCollectorDown
expr: aw_host_active == 0
for: 5m
labels:
severity: warning
annotations:
summary: "ActivityWatch collector down on {{ $labels.host }}"
- alert: AWExporterDown
expr: aw_exporter_up == 0
for: 2m
labels:
severity: critical
annotations:
summary: "ActivityWatch exporter is down"
```
### Grafana Notification Channels
1. Откройте Grafana
2. Configuration → Alerting → Notification channels
3. Add new channel (Email, Slack, etc.)
4. Настройте правила алертов в дашбордах
## Мониторинг
### Ключевые метрики
**ActivityWatch:**
- `aw_bucket_events_total` - общее количество событий
- `aw_host_active` - активность хостов
- `aw_exporter_scrape_duration_seconds` - время scrape
**Prometheus:**
- `up` - статус целей
- `scrape_duration_seconds` - время сбора
**Grafana:**
- `grafana_statistic` - статистика Grafana
### Health Checks
```bash
# Prometheus
curl http://localhost:9090/-/healthy
# Grafana
curl http://localhost:3000/api/health
# AW Exporter
curl http://localhost:9398/health
```
## Backup
```bash
# Backup Grafana dashboards
docker exec grafana grafana-cli admin export-dashboard > dashboards_backup.json
# Backup Prometheus data
docker exec prometheus tar -czf /tmp/prometheus_data.tar.gz /prometheus
docker cp prometheus:/tmp/prometheus_data.tar.gz ./backup/
```
## Устранение проблем
### Grafana не доступна
```bash
# Проверьте контейнер
docker-compose ps grafana
# Проверьте логи
docker-compose logs grafana
# Перезапустите
docker-compose restart grafana
```
### Prometheus не собирает метрики
```bash
# Проверьте конфиг
docker exec prometheus promtool check config /etc/prometheus/prometheus.yml
# Проверьте цели
curl http://localhost:9090/api/v1/targets
# Проверьте логи
docker-compose logs prometheus
```
### AW Exporter не работает
```bash
# Проверьте соединение с AW Server
docker exec aw-exporter curl http://aw-server:5600/api/0/buckets
# Проверьте логи
docker-compose logs aw-exporter
# Проверьте метрики
curl http://localhost:9398/metrics
```
## Производительность
### Оптимизация Prometheus
```yaml
# prometheus/prometheus.yml
global:
scrape_interval: 30s # Увеличьте интервал
evaluation_interval: 30s
storage:
tsdb:
retention.time: 30d # Уменьшите retention
```
### Оптимизация Grafana
```bash
# Увеличьте память
# docker-compose.yml
grafana:
environment:
- GF_INSTALL_PLUGINS=
- GF_SERVER_ROOT_URL=http://localhost:3000
- GF_ANALYTICS_ENABLED=false
```
## Подробнее
- [Prometheus Exporter](Prometheus-Exporter)
- [Компоненты](Components)
+248
View File
@@ -0,0 +1,248 @@
# Настройка сервера
Настройка Linux сервера для ActivityWatch-Russian.
## Требования
- Linux (Ubuntu 20.04+, Debian 11+, CentOS 8+)
- Python 3.8+
- PostgreSQL 12+
- 4GB+ RAM
- 50GB+ disk space
## Установка через Ansible
```bash
cd ansible
ansible-playbook server-setup.yml
```
## Ручная установка
### 1. Установка ActivityWatch Server
```bash
# Скачайте binaries
wget https://github.com/ActivityWatch/activitywatch/releases/download/v0.13.1/activitywatch-v0.13.1-linux-x86_64.zip
# Распакуйте
unzip activitywatch-v0.13.1-linux-x86_64.zip
cd activitywatch-v0.13.1-linux-x86_64
# Запустите сервер
./aw-server
```
### 2. Установка PostgreSQL
```bash
# Ubuntu/Debian
sudo apt update
sudo apt install postgresql postgresql-contrib
# CentOS/RHEL
sudo yum install postgresql-server postgresql-contrib
sudo postgresql-setup initdb
sudo systemctl start postgresql
sudo systemctl enable postgresql
# Создайте базу данных
sudo -u postgres psql
CREATE DATABASE activitywatch;
CREATE USER aw WITH PASSWORD 'your_password';
GRANT ALL PRIVILEGES ON DATABASE activitywatch TO aw;
\q
```
### 3. Применение RU патчей
```bash
# Скопируйте патчи
cp aw-server/aw-ru-patch.js /path/to/aw-server/
cp aw-server/aw-sw-cleanup.js /path/to/aw-server/
# Примените патчи
cd /path/to/aw-server/
node aw-ru-patch.js
```
### 4. Настройка systemd service
```bash
sudo nano /etc/systemd/system/aw-server.service
```
```ini
[Unit]
Description=ActivityWatch Server
After=network.target
[Service]
Type=simple
User=aw
WorkingDirectory=/opt/activitywatch
ExecStart=/opt/activitywatch/aw-server
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
```
```bash
sudo systemctl enable aw-server
sudo systemctl start aw-server
```
## Конфигурация
### ActivityWatch Config
```toml
# /opt/activitywatch/server-config.toml
[server]
host = "0.0.0.0"
port = 5600
[logging]
level = "INFO"
file = "/var/log/activitywatch/aw-server.log"
```
### PostgreSQL Config
```bash
# /etc/postgresql/12/main/postgresql.conf
listen_addresses = '*'
max_connections = 100
shared_buffers = 256MB
```
## Установка Python зависимостей
```bash
# Создайте virtual environment
python3 -m venv /opt/aw-venv
source /opt/aw-venv/bin/activate
# Установите зависимости
pip install psycopg2-binary requests prometheus_client
```
## Настройка скриптов агрегации
```bash
# Скопируйте скрипты
cp scripts/aggregate_dlp_events.py /opt/activitywatch/
# Настройте cron job
crontab -e
```
```cron
*/5 * * * * /opt/aw-venv/bin/python /opt/activitywatch/aggregate_dlp_events.py
```
## Firewall
```bash
# Ubuntu/Debian (ufw)
sudo ufw allow 5600/tcp
sudo ufw allow 5432/tcp
sudo ufw enable
# CentOS/RHEL (firewalld)
sudo firewall-cmd --permanent --add-port=5600/tcp
sudo firewall-cmd --permanent --add-port=5432/tcp
sudo firewall-cmd --reload
```
## Проверка
```bash
# Проверка ActivityWatch Server
curl http://localhost:5600/api/0/buckets
# Проверка PostgreSQL
sudo -u postgres psql -c "SELECT version();"
# Проверка логов
sudo journalctl -u aw-server -f
```
## Мониторинг
```bash
# Проверка статуса
sudo systemctl status aw-server
# Проверка ресурсов
htop
# Проверка диска
df -h
# Проверка PostgreSQL
sudo -u postgres psql -c "SELECT pg_stat_database.datname, pg_stat_database.numbackends FROM pg_stat_database;"
```
## Backup
```bash
# Backup PostgreSQL
sudo -u postgres pg_dump activitywatch > backup_$(date +%Y%m%d).sql
# Backup ActivityWatch data
sudo tar -czf aw-backup_$(date +%Y%m%d).tar.gz /opt/activitywatch/data
# Restore
sudo -u postgres psql activitywatch < backup_20240101.sql
```
## Обновление
```bash
# Остановите сервер
sudo systemctl stop aw-server
# Backup
sudo cp -r /opt/activitywatch /opt/activitywatch.backup
# Обновите binaries
wget https://github.com/ActivityWatch/activitywatch/releases/download/v0.14.0/activitywatch-v0.14.0-linux-x86_64.zip
unzip activitywatch-v0.14.0-linux-x86_64.zip -d /opt/activitywatch/
# Запустите сервер
sudo systemctl start aw-server
```
## Устранение проблем
### Server не запускается
```bash
# Проверьте логи
sudo journalctl -u aw-server -n 50
# Проверьте порт
sudo netstat -tlnp | grep 5600
# Проверьте права
sudo ls -la /opt/activitywatch
```
### PostgreSQL connection refused
```bash
# Проверьте статус
sudo systemctl status postgresql
# Проверьте конфиг
sudo cat /etc/postgresql/12/main/postgresql.conf | grep listen_addresses
# Проверьте firewall
sudo ufw status
```
## Подробнее
- [Мониторинг стек](Monitoring-Setup)
- [DLP Агрегация](DLP-Aggregation)
+131
View File
@@ -0,0 +1,131 @@
# WebUI Русификация
Система патчей для русификации и адаптации ActivityWatch WebUI под российские требования.
## Обзор
WebUI патчи добавляют:
- Полный перевод интерфейса на русский
- DLP центр для просмотра инцидентов
- Центр управления правилами
- Группы хостов
- Русские стили и шрифты
## Архитектура
```
Original WebUI → Patch Loader → DOM Injection → Style Injection → Translation → RU WebUI
```
## Компоненты патчей
### Navigation Patches
- Скрытие лишних пунктов меню
- Добавление DLP навигации
- Обновление ссылок
### DLP Center Injection
- Центр просмотра инцидентов
- Менеджер правил
- Центр алертов
- Архивирование
### Host Groups Center
- Управление группами хостов
- Визуализация по группам
- Агрегированная статистика
### Translation
- Перевод всех текстовых элементов
- Русские форматы дат/чисел
- Кириллические шрифты
## Установка
```bash
# Применение патчей
node aw-server/aw-ru-patch.js
# Очистка старых патчей
node aw-server/aw-sw-cleanup.js
```
## Конфигурация
### Host Groups Config
```json
{
"host_groups": [
{
"id": "workstations",
"name": "Рабочие станции",
"hosts": ["WORKSTATION01", "WORKSTATION02"],
"color": "#4ecdc4"
},
{
"id": "servers",
"name": "Серверы",
"hosts": ["SERVER01", "SERVER02"],
"color": "#ff6b6b"
}
]
}
```
### DLP Settings
```json
{
"dlp_enabled": true,
"review_center_enabled": true,
"alerts_center_enabled": true,
"auto_refresh_interval": 30,
"default_severity_filter": "all"
}
```
## Функции WebUI
### DLP Review Center
- Список инцидентов
- Фильтрация по типам
- Детальный просмотр
- Архивирование
- Экспорт
### DLP Rules Manager
- Список правил
- Создание/редактирование
- Включение/выключение
- Тестирование правил
### Host Groups Center
- Управление группами
- Визуализация
- Статистика по группам
## Совместимость
- ActivityWatch v0.12.x - v0.14.x
- Chrome 90+, Firefox 88+, Edge 90+, Safari 14+
## Производительность
- Lazy loading патчей
- Кэширование переводов
- Debouncing обновлений DOM
- Virtual scrolling
## Отладка
```javascript
// Режим разработки
const DEBUG_MODE = true;
// Логирование патчей
console.log('[AW-RU-Patch] Applying patch:', patchName);
```
## Подробнее
- [Детальная диаграмма](https://github.com/igor04091968/AWatch-rus/blob/main/docs/diagrams/webui-patches.md)
- [Группы хостов](Host-Groups)
+172
View File
@@ -0,0 +1,172 @@
# Установка на 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)
+20
View File
@@ -1,4 +1,23 @@
services:
aw-exporter:
build:
context: ./sql-exporter
dockerfile: Dockerfile
container_name: awrus-aw-exporter
restart: unless-stopped
environment:
- AW_SERVER_HOST=10.10.10.13
- AW_SERVER_PORT=5600
- AW_SERVER_SCHEME=http
- EXPORTER_PORT=9398
volumes:
- ./sql-exporter/collectors/aw_activitywatch.py:/app/aw_activitywatch.py:ro
ports:
- "9398:9398"
command:
- "python3"
- "/app/aw_activitywatch.py"
sql-exporter:
image: ghcr.io/burningalchemist/sql_exporter:latest
container_name: awrus-sql-exporter
@@ -30,6 +49,7 @@ services:
ports:
- "${PROMETHEUS_PORT:-9090}:9090"
depends_on:
- aw-exporter
- sql-exporter
grafana:
@@ -0,0 +1,121 @@
{
"dashboard": {
"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"
}
],
"options": {
"colorMode": "value",
"graphMode": "area"
},
"fieldConfig": {
"defaults": {
"unit": "short",
"min": 0
}
}
},
{
"id": 2,
"title": "Events per Bucket",
"type": "table",
"targets": [
{
"expr": "aw_bucket_events_count",
"format": "table",
"instant": true,
"refId": "B"
}
],
"transformations": [
{
"id": "organize",
"options": {
"excludeByName": {
"Time": true,
"Value": true
}
}
}
]
},
{
"id": 3,
"title": "Collector Status",
"type": "stat",
"targets": [
{
"expr": "aw_collector_status",
"format": "table",
"instant": true,
"refId": "C"
}
],
"options": {
"colorMode": "value"
}
},
{
"id": 4,
"title": "Events Timeline",
"type": "graph",
"targets": [
{
"expr": "rate(aw_events_total[5m])",
"legendFormat": "{{bucket}} - {{event_type}}",
"refId": "D"
}
],
"fieldConfig": {
"defaults": {
"custom": {
"lineWidth": 2,
"fillOpacity": 10
}
}
}
},
{
"id": 5,
"title": "Last Event Timestamp",
"type": "gauge",
"targets": [
{
"expr": "aw_events_last_timestamp",
"legendFormat": "{{bucket}}",
"refId": "E"
}
],
"options": {
"orientation": "horizontal"
},
"fieldConfig": {
"defaults": {
"unit": "s",
"custom": {
"thresholds": {
"mode": "absolute",
"steps": [
{"color": "red", "value": 0},
{"color": "yellow", "value": 3600},
{"color": "green", "value": 86400}
]
}
}
}
}
}
]
}
}
@@ -9,3 +9,12 @@ providers:
editable: false
options:
path: /var/lib/grafana/dashboards
- name: ActivityWatch
orgId: 2
folder: "ActivityWatch"
type: file
disableDeletion: false
editable: true
options:
path: /var/lib/grafana/dashboards
@@ -0,0 +1,9 @@
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
editable: true
@@ -0,0 +1,9 @@
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
editable: true
+5
View File
@@ -11,6 +11,11 @@ scrape_configs:
static_configs:
- targets: ["localhost:9090"]
- job_name: aw_activitywatch_exporter
static_configs:
- targets: ["aw-exporter:9398"]
scrape_interval: 30s
- job_name: onec_sql_exporter
static_configs:
- targets: ["sql-exporter:9399"]
+9
View File
@@ -0,0 +1,9 @@
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"]
@@ -0,0 +1,142 @@
#!/usr/bin/env python3
"""
ActivityWatch Prometheus Exporter
Собирает метрики из ActivityWatch API и экспонирует их в формате Prometheus.
"""
import time
import logging
import requests
from prometheus_client import start_http_server, Gauge, Counter, Histogram, Info
from datetime import datetime, timedelta
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Configuration
AW_SERVER_HOST = "10.10.10.13"
AW_SERVER_PORT = 5600
AW_SERVER_SCHEME = "http"
AW_API_BASE = f"{AW_SERVER_SCHEME}://{AW_SERVER_HOST}:{AW_SERVER_PORT}/api/0"
EXPORTER_PORT = 9398
# Metrics
aw_buckets_total = Gauge('aw_buckets_total', 'Total number of ActivityWatch buckets')
aw_events_total = Counter('aw_events_total', 'Total number of ActivityWatch events', ['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 in bucket', ['bucket'])
aw_collector_status = Info('aw_collector_status', 'Status of ActivityWatch collectors')
aw_server_info = Info('aw_server_info', 'ActivityWatch server information')
class ActivityWatchExporter:
def __init__(self, api_base):
self.api_base = api_base
self.session = requests.Session()
self.session.headers.update({'Accept': 'application/json'})
self.bucket_cache = {}
def get_buckets(self):
"""Get all buckets from ActivityWatch API."""
try:
response = self.session.get(f"{self.api_base}/buckets", timeout=10)
response.raise_for_status()
return response.json()
except Exception as e:
logger.error(f"Failed to get buckets: {e}")
return {}
def get_bucket_events(self, bucket_id, limit=1):
"""Get events from a specific bucket."""
try:
response = self.session.get(
f"{self.api_base}/buckets/{bucket_id}/events",
params={'limit': limit},
timeout=10
)
response.raise_for_status()
return response.json()
except Exception as e:
logger.error(f"Failed to get events for {bucket_id}: {e}")
return []
def get_bucket_info(self, bucket_id):
"""Get detailed info about a bucket."""
try:
response = self.session.get(f"{self.api_base}/buckets/{bucket_id}", timeout=10)
response.raise_for_status()
return response.json()
except Exception as e:
logger.error(f"Failed to get info for {bucket_id}: {e}")
return {}
def collect_metrics(self):
"""Collect metrics from ActivityWatch."""
buckets = self.get_buckets()
# Update bucket count
aw_buckets_total.set(len(buckets))
# Server info
aw_server_info.info({
'host': AW_SERVER_HOST,
'port': AW_SERVER_PORT,
'scheme': AW_SERVER_SCHEME,
'api_base': self.api_base
})
# Collector status
collectors = {}
for bucket_id, bucket_data in buckets.items():
client = bucket_data.get('client', 'unknown')
hostname = bucket_data.get('hostname', 'unknown')
bucket_type = bucket_data.get('type', 'unknown')
# Count events
events = self.get_bucket_events(bucket_id, limit=1000)
event_count = len(events)
aw_bucket_events_count.labels(bucket=bucket_id).set(event_count)
# Last event timestamp
if events:
last_event = events[0]
timestamp = last_event.get('timestamp', 0)
try:
# Convert to Unix timestamp if needed
if isinstance(timestamp, str):
dt = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
unix_ts = dt.timestamp()
else:
unix_ts = timestamp
aw_events_last_timestamp.labels(bucket=bucket_id).set(unix_ts)
except:
pass
# Collector status
collector_key = f"{hostname}_{client}"
collectors[collector_key] = {
'status': 'active',
'bucket': bucket_id,
'type': bucket_type,
'events': event_count
}
aw_collector_status.info(collectors)
def main():
exporter = ActivityWatchExporter(AW_API_BASE)
# Initial collection
exporter.collect_metrics()
# Start HTTP server
start_http_server(EXPORTER_PORT)
logger.info(f"ActivityWatch exporter started on port {EXPORTER_PORT}")
logger.info(f"Scraping ActivityWatch API at {AW_API_BASE}")
# Collect metrics every 30 seconds
while True:
time.sleep(30)
exporter.collect_metrics()
if __name__ == '__main__':
main()
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
/home/igor/.local/share/uv/tools/graphifyy/bin/python3
+101
View File
@@ -0,0 +1,101 @@
# 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
@@ -0,0 +1 @@
{"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": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "mnt_usb_hdd2_projects_activitywatch_russian_install_kit_awindows_20260427_211240_windows_deploy_domain_users_ps1", "label": "deploy-domain-users.ps1", "file_type": "code", "source_file": "/mnt/usb_hdd2/Projects/ActivityWatch-Russian/install-kit-awindows-20260427-211240/windows/deploy-domain-users.ps1", "source_location": "L1"}], "edges": [], "raw_calls": []}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"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": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "mnt_usb_hdd2_projects_activitywatch_russian_install_kit_awindows_20260427_211240_windows_deploy_single_user_ps1", "label": "deploy-single-user.ps1", "file_type": "code", "source_file": "/mnt/usb_hdd2/Projects/ActivityWatch-Russian/install-kit-awindows-20260427-211240/windows/deploy-single-user.ps1", "source_location": "L1"}], "edges": [], "raw_calls": []}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "mnt_usb_hdd2_projects_activitywatch_russian_windows_validate_deployment_ps1", "label": "validate-deployment.ps1", "file_type": "code", "source_file": "/mnt/usb_hdd2/Projects/ActivityWatch-Russian/windows/validate-deployment.ps1", "source_location": "L1"}], "edges": [], "raw_calls": []}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "mnt_usb_hdd2_projects_activitywatch_russian_windows_hardening_recovery_ps1", "label": "hardening-recovery.ps1", "file_type": "code", "source_file": "/mnt/usb_hdd2/Projects/ActivityWatch-Russian/windows/hardening-recovery.ps1", "source_location": "L1"}], "edges": [], "raw_calls": []}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "mnt_usb_hdd2_projects_activitywatch_russian_install_kit_awindows_20260427_211240_windows_hardening_recovery_ps1", "label": "hardening-recovery.ps1", "file_type": "code", "source_file": "/mnt/usb_hdd2/Projects/ActivityWatch-Russian/install-kit-awindows-20260427-211240/windows/hardening-recovery.ps1", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "mnt_usb_hdd2_projects_activitywatch_russian_windows_deploy_single_user_ps1", "label": "deploy-single-user.ps1", "file_type": "code", "source_file": "/mnt/usb_hdd2/Projects/ActivityWatch-Russian/windows/deploy-single-user.ps1", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "mnt_usb_hdd2_projects_activitywatch_russian_install_kit_awindows_20260427_211240_windows_validate_deployment_ps1", "label": "validate-deployment.ps1", "file_type": "code", "source_file": "/mnt/usb_hdd2/Projects/ActivityWatch-Russian/install-kit-awindows-20260427-211240/windows/validate-deployment.ps1", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "mnt_usb_hdd2_projects_activitywatch_russian_install_kit_awindows_20260427_211240_windows_deploy_ensemble_ps1", "label": "deploy-ensemble.ps1", "file_type": "code", "source_file": "/mnt/usb_hdd2/Projects/ActivityWatch-Russian/install-kit-awindows-20260427-211240/windows/deploy-ensemble.ps1", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "mnt_usb_hdd2_projects_activitywatch_russian_aw_server_aw_sw_cleanup_js", "label": "aw-sw-cleanup.js", "file_type": "code", "source_file": "/mnt/usb_hdd2/Projects/ActivityWatch-Russian/aw-server/aw-sw-cleanup.js", "source_location": "L1"}], "edges": [], "raw_calls": []}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long

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