Compare commits

..
Author SHA1 Message Date
igor04091968 f5f4f84bef feat(installer): add standalone Windows service deployment for DLP agent 2026-05-08 00:39:28 +03:00
igor04091968 693e6832a6 fix(windows-deploy): enforce awHostname override and safe single-instance collector restart 2026-05-08 00:34:52 +03:00
igor04091968 538ff74611 feat(dlp): add health status and graceful shutdown for browser collector 2026-05-07 23:54:52 +03:00
igor04091968 b992ad2234 feat(dlp): graceful shutdown and COM cleanup for email collector 2026-05-07 23:52:03 +03:00
igor04091968 ac59d44719 feat(dlp): add WAL buffering and health snapshots for collectors 2026-05-07 23:47:03 +03:00
igor04091968 f3c5e9ea53 feat(dlp-hardening): secure inventory and extend aggregator sources 2026-05-07 23:38:20 +03:00
igor04091968 38107b3c84 ansible: remove per-host passwords from inventory\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> 2026-05-07 23:37:47 +03:00
igor04091968 8ad02ae194 docs(gsd): bootstrap planning and phase-1 discussion artifacts 2026-05-07 21:39:32 +03:00
igor04091968 2fd0ca8eda docs: add copilot instructions 2026-05-07 21:37:42 +03:00
igor04091968 9c59f74928 chore(install-kit): sync package payload with current repo baseline 2026-05-07 19:50:07 +03:00
igor04091968 0c5069c255 chore(ops): harden rollout gates and sanitize generated artifacts 2026-05-07 19:50:02 +03:00
igor04091968andCopilot f0db2a227b revert(pssa): restore validate-deployment.ps1 from commit 63904d2 (stable)
Restored clean version to fix corruption introduced earlier.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-07 19:22:05 +03:00
igor04091968andCopilot e37c3886ba revert(pssa): restore files corrupted by automatic fixes (ActivityWatch.Windows.Common.psm1, email-outbound-collector.ps1, file-operations-collector.ps1)
Restored previous versions for files that were corrupted by the automated PSScriptAnalyzer fixes. Please review and reapply safe fixes manually.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-07 18:10:12 +03:00
igor04091968andCopilot 429501d4fe chore(pssa): apply safe PSScriptAnalyzer fixes (BOM, empty catch -> Write-Error, Write-Host -> Write-Output)
Applied automatic, low-risk fixes for PSScriptAnalyzer warnings. Please review changes for behavior-sensitive code.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-07 12:50:35 +03:00
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
Devin AI 2e49310023 Добавить безопасную миграцию prod в AWatch-rus 2026-05-02 11:58:28 +00:00
Devin AI 2ec90b05ae Унифицировать Windows пути AWatch-rus 2026-05-02 09:44:27 +00:00
Devin AI 21f0184115 Согласовать Windows пути с InnoSetup 2026-05-02 09:20:28 +00:00
Devin AI 342ab77f44 Исправить recovery config и разбор RDP sessions 2026-05-02 09:08:14 +00:00
Devin AI 1e92b8b679 Русифицировать и доработать Ansible развёртывание 2026-05-02 09:07:08 +00:00
Devin AI 8c9dfffc7d Ограничить PSScriptAnalyzer ошибками 2026-05-02 08:35:23 +00:00
Devin AI 087883b663 Уточнить настройки PSScriptAnalyzer 2026-05-02 08:34:21 +00:00
Devin AI 4bbf0b8b73 Исправить CI-проверки shellcheck и PSScriptAnalyzer 2026-05-02 08:32:53 +00:00
Devin AI da99d1ac98 Русифицировать DLP PowerShell и исправить имя документа печати 2026-05-02 08:30:37 +00:00
igor04091968 f7cf5556a0 Ofline install InnoSetup 2026-05-02 10:01:39 +03:00
igor04091968 65da55be7a Patch files Innosetup 2026-05-02 04:12:14 +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
igor04091968 8278d51840 feat(worktime): add linux remote worker and pve headless tracking 2026-04-30 15:48:09 +03:00
IgorRachkov 9bdf28ed2b Add newcomer onboarding guide for repository structure 2026-04-29 13:09:23 +03:00
210 changed files with 58045 additions and 1935 deletions
+89
View File
@@ -0,0 +1,89 @@
# Copilot instructions for ActivityWatch-Russian
Purpose: help future Copilot sessions quickly understand how to build, validate, and modify this repo.
---
## Build / test / lint (how-to)
- Shell script checks (CI & local):
- Full: ./scripts/quality-gate.sh
- Single file (syntax): bash -n <script>. Example: bash -n scripts/install_aw_linux_client.sh
- Run shellcheck locally (same checks as CI): install shellcheck then run:
find . -type f -name "*.sh" -print0 | xargs -0 -r shellcheck -e SC1007,SC1090,SC2016
- PowerShell checks (Windows / CI):
- Single-file analysis (locally in PowerShell):
Invoke-ScriptAnalyzer -Path windows/deploy-ensemble.ps1
- CI installs PSScriptAnalyzer and runs against windows/*.ps1, *.psm1, *.psd1
- Python scripts / utilities:
- Run a single utility: python3 scripts/aggregate_dlp_events.py
- Many scripts are helpers for operations; no test harness in repo.
- Monitoring stack (Docker Compose):
- Start: cd grafana-1c && docker-compose up -d
- Start a single service: docker-compose up -d grafana
- Server install / deploy helpers:
- AW server install: aw-server/install_aw_server.sh
- Apply RU WebUI patch: aw-server/apply_webui_ru_patch.sh
- Windows deploy/validation: windows/deploy-ensemble.ps1 and windows/validate-deployment.ps1
Notes: there is no unified unit-test suite. Use the script checks and CI pipeline (.github/workflows/ci.yml) as the canonical validation steps.
---
## High-level architecture (short)
- Windows collectors (PowerShell) run on endpoints and POST events to the ActivityWatch Server HTTP API.
- ActivityWatch Server (deployed on Linux CT/LXC via Proxmox or Debian VM) stores events in PostgreSQL and serves WebUI.
- Integration layer: pollers and aggregators (Python) for pfSense, DLP aggregation, Prometheus exporter.
- Monitoring: Prometheus + Grafana (docker-compose in grafana-1c) and a SQL exporter for direct DB dashboards.
Key ports: AW API 5600/5666, PostgreSQL 5432, Prometheus 9090, Grafana 3000, exporter 9398.
---
## Key repository conventions
- Branching / commits:
- Use feature branches. Commit style follows Conventional Commits (feat/fix/docs/chore).
- Secrets and envs:
- Secrets live in secrets/*.env templates and must NOT be committed. Use secrets/deploy.secrets.env locally; CI and scripts expect templates (.example).
- Preflight / PR checks:
- Run bash -n for shell scripts and Invoke-ScriptAnalyzer for PowerShell before opening PRs.
- Update docs/runbook.md and related runbooks when behavior changes.
- RU patching:
- WebUI localization is applied via aw-server/aw-ru-patch.js and aw-server/apply_webui_ru_patch.sh — treat these as idempotent patch steps during deploy.
- Systemd / deploy units:
- activitywatch-server.service / aw-worktime-api.service / aw-worktime-ui-bridge.service are included in aw-server/ for production use.
- CI expectations:
- .github/workflows/ci.yml runs shellcheck and PSScriptAnalyzer. Use scripts/quality-gate.sh locally to replicate preflight.
---
## Important files & quick references
- docs/ (onboarding, deployment, runbook) — start here for operational context.
- aw-server/ — server install script, env template, RU patch, systemd units.
- ansible/ — automated provisioning playbooks for CT/Proxmox and Windows deploys.
- windows/ — PowerShell collectors and orchestration; validation scripts are here.
- scripts/ — helpers (aggregate_dlp_events.py, installers, quality-gate.sh).
- grafana-1c/ — docker-compose monitoring stack and dashboards.
---
## AI assistant & other tool configs to check
- No Copilot-specific instruction file existed before this addition.
- No CLAUDE.md, .cursorrules, AGENTS.md, .windsurfrules, CONVENTIONS.md, or AIDER_CONVENTIONS.md detected at repo root. If adding automated assistant rules, place them in repo root or .github and document cross-references here.
---
If you need the Copilot instructions extended (e.g., adding run examples for specific scripts, more detailed CI breakdown, or mapping tests to files), say which area to expand.
-34
View File
@@ -1,34 +0,0 @@
name: Build Windows installer
on:
workflow_dispatch:
push:
paths:
- 'windows/**'
- '.github/workflows/build-installer.yml'
jobs:
build:
runs-on: windows-latest
env:
SIGNTOOL_CMD: ${{ secrets.SIGNTOOL_CMD }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Inno
shell: pwsh
run: |
choco install innosetup --no-progress -y
- name: Build installer
shell: pwsh
run: |
./windows/installer/build-installer.ps1
- name: Upload Setup.exe artifact
uses: actions/upload-artifact@v4
with:
name: awatch-rus-setup
path: windows/installer/output/*.exe
if-no-files-found: error
+4 -2
View File
@@ -18,7 +18,7 @@ jobs:
- name: Run shellcheck
run: |
find . -type f -name "*.sh" -print0 | xargs -0 -r shellcheck
find . -type f -name "*.sh" -print0 | xargs -0 -r shellcheck -e SC1007,SC1090,SC2016
powershell-analyzer:
runs-on: ubuntu-latest
@@ -40,7 +40,9 @@ jobs:
"windows/*.psm1",
"windows/*.psd1"
)
$issues = Invoke-ScriptAnalyzer -Path $targets -Recurse -Severity Error,Warning
$issues = $targets | ForEach-Object {
Invoke-ScriptAnalyzer -Path $_ -Recurse -Severity Error
}
if ($issues) {
$issues | Format-Table -AutoSize
throw "PSScriptAnalyzer detected issues."
+25
View File
@@ -1,11 +1,36 @@
# Local secrets
secrets/deploy.secrets.env
secrets/runtime.env
# Runtime / reports
*.log
*.tmp
*.bak
windows/*.report.json
.rollout-logs/
reports/
tmp/
graphify-out/cache/
graphify-out/powershell-parse-results*.json
graphify-out/powershell-pssa-warn-results.json
graphify-out/shellcheck-*.txt
graphify-out/validate_dryrun_out*.txt
graphify-out/pssa_diffs.txt
.graphify_analysis.json
.graphify_ast.json
.graphify_cached.json
.graphify_chunk_list_*.txt
.graphify_detect.json
.graphify_extract.json
.graphify_labels.json
.graphify_python
.graphify_semantic.json
.graphify_uncached.txt
graphify-out/GRAPH_REPORT.md
graphify-out/graph.html
graphify-out/graph.json
.pssa_run.ps1
data/
# IDE
.idea/
+14
View File
@@ -0,0 +1,14 @@
# ActivityWatch-Russian
## Current Milestone: v1.0 Data Pipeline Stability
**Goal:** Ensure stable collection and rendering of endpoint activity data in AW Web UI.
**Target features:**
- Reliable ingestion from Windows collectors (`endpoint-signals`, `browser-domains`).
- Stable AW server query/render path for worktime/activity pages.
- Deployment/runbook consistency (Ansible + install kit + rollback notes).
## Evolution
This document evolves at phase transitions and milestone boundaries.
+14
View File
@@ -0,0 +1,14 @@
# ROADMAP
## 🚧 v1.0 Data Pipeline Stability
- [ ] Phase 1: Collectors and API flow hardening
- [ ] Phase 2: UI reports consistency and release proof
### Phase 1: Collectors and API flow hardening
**Goal:** Stabilize collectors-to-server pipeline and remove known transport/runtime failure modes.
**Depends on:** none
### Phase 2: UI reports consistency and release proof
**Goal:** Validate report links/output, enforce rollout checks, and freeze reproducible release state.
**Depends on:** Phase 1
+26
View File
@@ -0,0 +1,26 @@
---
milestone: v1.0
milestone_name: Data Pipeline Stability
phase: "0"
phase_name: Not started
current_plan: 0
total_plans_in_phase: 0
status: planning
progress_percent: 0
last_activity: 2026-05-07
---
## Current Position
Phase: Not started (defining requirements)
Plan: —
Status: Defining requirements
Last activity: 2026-05-07 — Milestone v1.0 started
## Decisions
- Prioritize reliable data presence in activity/worktime views over UI extensions.
## Blockers
- None.
@@ -0,0 +1,17 @@
# Phase 1 Context
## Phase
Phase 1: Collectors and API flow hardening
## Focus
- Endpoint collectors must continuously send data without silent hangs.
- AW server must accept/query data for UI pages consistently.
- Failure points around transport/CORS/runtime must be explicitly checked.
## Initial Acceptance Targets
- Endpoint collector heartbeats arrive regularly.
- Browser domains and endpoint signals appear in corresponding buckets.
- Activity page for target host shows non-zero timeline/events for active period.
@@ -0,0 +1,42 @@
# PLAN — Phase 01: collectors-and-api-flow-hardening
## Goal
Deliver stable collector-to-server data flow so activity/worktime pages have consistent data.
## Work Items
1. Validate collector runtime and log rotation behavior.
2. Validate server ingest endpoints and bucket write/read checks.
3. Validate CORS/origin and report link consistency.
4. Add/adjust scripts or runbook checks to detect zero-data regressions early.
## Verification
- Manual and scripted checks show fresh events in target buckets.
- Host activity page reflects real activity (not `0s`) for active sessions.
- No repeating transport errors in collector logs during test window.
## Status
Planned.
## 2. Варианты доработки DLP
### Вариант A: “Hardening” — Стабилизация текущего
Цель: довести текущие коллекторы до production-grade уровня надёжности.
| # | Задача | Усилие | Влияние |
|---|---|---|---|
| A1 | HTTP retry + exponential backoff во всех коллекторах | 3-5 дней | Высокое — перестанут теряться события |
| A2 | Локальный WAL (Write-Ahead Log) — буферизация событий при недоступности сервера | 1-2 нед | Критическое — гарантия доставки |
| A3 | Healthcheck endpoint и self-diagnostics в каждом коллекторе | 3-5 дней | Среднее — видимость состояния агентов |
| A4 | Расширить aggregator: добавить `aw-email-monitor_` и `aw-dlp-endpoint-signals_` в сбор | 1 день | Среднее |
| A5 | Systemd timer / Windows Task для aggregator (автоматический запуск) | 1 день | Среднее |
| A6 | Убрать пароль из `inventory.ini` → использовать Ansible Vault или env var | 1 час | Критическое (безопасность) |
| A7 | Graceful shutdown и cleanup event subscriptions во всех коллекторах | 2-3 дня | Среднее |
Общее усилие: ~3-4 недели.
Рекомендация: обязательно сделать перед любым масштабированием. Без этого DLP — “best effort” мониторинг, а не надёжная система.
+17 -3
View File
@@ -5,21 +5,30 @@
## Что входит
- `docs/preparation.md` — подготовка инфраструктуры и входных параметров.
- `docs/codebase-onboarding.md` — обзор структуры репозитория и маршрут изучения для новичка.
- `docs/deployment.md` — пошаговый деплой LXC и ActivityWatch Server.
- `docs/runbook.md` — быстрый runbook для оператора.
- `docs/operations.md` — регламент сопровождения, бэкапов, обновлений и rollback.
- `docs/artifacts-policy.md` — политика generated-артефактов и rollout-gates.
- `docs/windows/ensemble.md` — orchestration-пакет для Windows-деплоя и проверки.
- `docs/linux-client.md` — user-space rollout Linux-клиента ActivityWatch на удалённый `AW server`.
- `docs/linux-remote-worker.md` — полный Linux remote-worker stack: GUI, SSH/console и browser admin UI вроде Proxmox `:8006`.
- `docs/console-ssh-logger.md` — логирование только консольных команд и SSH-сессий в AW.
- `docs/dlp-gap-analysis.md` — разрыв до enterprise DLP и roadmap.
- `docs/dlp-aggregator.md` — прототип централизованной агрегации DLP/file-operation событий.
- `docs/dlp-reliability-roadmap.md` — roadmap повышения надёжности DLP-коллекторов.
- `docs/powershell-analysis.md` — статический анализ работоспособности DLP PowerShell-скриптов.
- `proxmox/` — шаблонные скрипты подготовки и наполнения CT на стороне Proxmox.
- `aw-server/` — установочные скрипты, env-шаблон, systemd unit и RU patch для Web UI.
- `ansible/` — Ansible-ensemble для автоматизированного сервера (Debian/CT).
- `pfsense/` — внешний poller для pfSense API и systemd unit под Debian/Ubuntu utility VM.
- `windows/` — PowerShell toolkit: single-user, domain-users, ensemble orchestration, hardening/recovery, validation, phase-2 DLP telemetry (`aw-dlp-incidents_*`, `aw-dlp-endpoint-signals_*`).
- `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.
- `scripts/install_aw_linux_remote_worker.sh` — полный Linux remote-worker installer.
## Базовый сценарий
@@ -38,9 +47,9 @@
- `ansible/provision_proxmox_ct_and_deploy_aw.yml`
- `ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml` (массово по матрице CT)
Для централизованного phase-2 деплоя Windows-клиентов через WinRM:
Для централизованного деплоя Windows/RDP-клиентов через WinRM:
- `ansible/deploy_aw_windows_phase2.yml`
- `ansible/deploy_aw_windows.yml`
Для внешнего pfSense poller'а:
@@ -51,6 +60,11 @@
- `docs/linux-client.md`
- `scripts/install_aw_linux_client.sh`
Для полного Linux remote-worker сценария:
- `docs/linux-remote-worker.md`
- `scripts/install_aw_linux_remote_worker.sh`
Для режима “только консоль/ssh” без GUI watcher'ов:
- `docs/console-ssh-logger.md`
+18
View File
@@ -0,0 +1,18 @@
#!/bin/sh
# shellcheck disable=SC1007
set -eu
REPO_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
cd "$REPO_DIR"
echo "==> Fetch origin"
git fetch origin
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
echo "==> Pull origin/$CURRENT_BRANCH"
git pull --ff-only origin "$CURRENT_BRANCH"
echo "==> Done"
git status --short --branch
Executable
+35
View File
@@ -0,0 +1,35 @@
#!/bin/sh
# shellcheck disable=SC1007
set -eu
REPO_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
cd "$REPO_DIR"
BRANCH=$(git rev-parse --abbrev-ref HEAD)
if [ "${1:-}" = "" ]; then
echo "Usage: $0 \"commit message\"" >&2
exit 1
fi
COMMIT_MESSAGE=$1
echo "==> Git status"
git status --short --branch
echo "==> Stage changes"
git add -A
if git diff --cached --quiet; then
echo "No staged changes to commit."
exit 0
fi
echo "==> Commit"
git commit -m "$COMMIT_MESSAGE"
echo "==> Push origin/$BRANCH"
git push origin "$BRANCH"
echo "==> Done"
+67 -39
View File
@@ -1,46 +1,51 @@
# Ansible ensemble for AWatch-rus
Эта директория содержит Ansible-ensemble для двух сценариев:
Эта директория содержит Ansible-ensemble для полного развёртывания AWatch-rus:
- деплой на уже существующий Debian host/CT;
- полный цикл с нуля в Proxmox: создание CT + bootstrap + установка ActivityWatch + RU patch.
- централизованный деплой Windows phase-2 collectors по WinRM.
- deployment внешнего pfSense poller'а на Debian/Ubuntu utility VM.
- полный цикл с нуля в Proxmox: создание CT + bootstrap + установка ActivityWatch + RU patch;
- централизованное развёртывание Windows/RDP collector'ов по WinRM;
- развёртывание внешнего pfSense poller'а на Debian/Ubuntu utility VM.
## Файлы
- `/home/igor/tmp/AWatch-rus/ansible/deploy_aw_server.yml` — основной playbook.
- `/home/igor/tmp/AWatch-rus/ansible/provision_proxmox_ct_and_deploy_aw.yml`full-stack playbook для Proxmox.
- `/home/igor/tmp/AWatch-rus/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml` — массовый full-stack playbook (несколько CT).
- `/home/igor/tmp/AWatch-rus/ansible/deploy_aw_windows_phase2.yml` — WinRM playbook для развёртывания phase-2 Windows collector'ов.
- `/home/igor/tmp/AWatch-rus/ansible/deploy_aw_pfsense_poller.yml`deployment pfSense poller'а.
- `/home/igor/tmp/AWatch-rus/ansible/install_full_stack.yml` — полный установочный playbook (оркестратор всех этапов).
- `/home/igor/tmp/AWatch-rus/ansible/inventory.example.ini` — шаблон inventory.
- `/home/igor/tmp/AWatch-rus/ansible/group_vars/all.example.yml` — шаблон переменных.
- `/home/igor/tmp/AWatch-rus/ansible/group_vars/proxmox.example.yml` — шаблон переменных CT в Proxmox.
- `/home/igor/tmp/AWatch-rus/ansible/group_vars/proxmox-matrix.example.yml` — шаблон матрицы CT.
- `/home/igor/tmp/AWatch-rus/ansible/group_vars/windows.example.yml` — шаблон переменных Windows phase-2.
- `/home/igor/tmp/AWatch-rus/ansible/group_vars/pfsense-poller.example.yml` — шаблон переменных pfSense poller'а.
- `ansible/deploy_aw_server.yml` — основной playbook для уже существующего Debian/CT host.
- `ansible/provision_proxmox_ct_and_deploy_aw.yml`полный playbook для Proxmox.
- `ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml` — массовый полный playbook (несколько CT).
- `ansible/deploy_aw_windows.yml` — WinRM playbook для развёртывания Windows/RDP collector'ов.
- `ansible/deploy_aw_pfsense_poller.yml`развёртывание pfSense poller'а.
- `ansible/install_full_stack.yml` — полный установочный playbook (оркестратор всех этапов).
- `ansible/inventory.example.ini` — шаблон inventory.
- `ansible/group_vars/*.example.yml` — шаблоны переменных.
## Быстрый запуск
1. Скопируйте шаблоны:
- `cp /home/igor/tmp/AWatch-rus/ansible/inventory.example.ini /home/igor/tmp/AWatch-rus/ansible/inventory.ini`
- `cp /home/igor/tmp/AWatch-rus/ansible/group_vars/all.example.yml /home/igor/tmp/AWatch-rus/ansible/group_vars/all.yml`
- `cp ansible/inventory.example.ini ansible/inventory.ini`
- `cp ansible/group_vars/all.example.yml ansible/group_vars/all.yml`
2. Заполните значения в `inventory.ini` и `group_vars/all.yml`.
3. Запустите:
```bash
cd /home/igor/tmp/AWatch-rus/ansible
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 (всё за один запуск)
Если нужно прогнать полный цикл одной командой:
```bash
cd /home/igor/tmp/AWatch-rus/ansible
cd ansible
ansible-playbook -i inventory.ini install_full_stack.yml
```
@@ -48,7 +53,7 @@ ansible-playbook -i inventory.ini install_full_stack.yml
- `provision_proxmox_ct_and_deploy_aw.yml` (если есть хосты в группе `[proxmox]`);
- `deploy_aw_server.yml` (группа `[aw_server]`);
- `deploy_aw_windows_phase2.yml` (группа `[aw_windows]`);
- `deploy_aw_windows.yml` (группа `[aw_windows]`);
- `deploy_aw_pfsense_poller.yml` (группа `[aw_pfsense_pollers]`).
Пустые группы в `inventory.ini` безопасны: соответствующий play будет пропущен.
@@ -56,50 +61,51 @@ ansible-playbook -i inventory.ini install_full_stack.yml
## Полный запуск с нуля в Proxmox
1. Подготовьте inventory и vars:
- `cp /home/igor/tmp/AWatch-rus/ansible/inventory.example.ini /home/igor/tmp/AWatch-rus/ansible/inventory.ini`
- `cp /home/igor/tmp/AWatch-rus/ansible/group_vars/all.example.yml /home/igor/tmp/AWatch-rus/ansible/group_vars/all.yml`
- `cp /home/igor/tmp/AWatch-rus/ansible/group_vars/proxmox.example.yml /home/igor/tmp/AWatch-rus/ansible/group_vars/proxmox.yml`
- `cp ansible/inventory.example.ini ansible/inventory.ini`
- `cp ansible/group_vars/all.example.yml ansible/group_vars/all.yml`
- `cp ansible/group_vars/proxmox.example.yml ansible/group_vars/proxmox.yml`
2. Заполните `group_vars/proxmox.yml` и `group_vars/all.yml`.
3. Запустите playbook:
```bash
cd /home/igor/tmp/AWatch-rus/ansible
cd ansible
ansible-playbook -i inventory.ini provision_proxmox_ct_and_deploy_aw.yml
```
## Массовый запуск (матрица CT)
1. Подготовьте матрицу:
- `cp /home/igor/tmp/AWatch-rus/ansible/group_vars/proxmox-matrix.example.yml /home/igor/tmp/AWatch-rus/ansible/group_vars/proxmox-matrix.yml`
- `cp ansible/group_vars/proxmox-matrix.example.yml ansible/group_vars/proxmox-matrix.yml`
2. Заполните `proxmox-matrix.yml`.
3. Запустите:
```bash
cd /home/igor/tmp/AWatch-rus/ansible
cd ansible
ansible-playbook -i inventory.ini provision_proxmox_ct_matrix_and_deploy_aw.yml
```
## Windows phase-2 rollout (WinRM)
## Windows/RDP rollout (WinRM)
1. Подготовьте inventory и vars:
- `cp /home/igor/tmp/AWatch-rus/ansible/inventory.example.ini /home/igor/tmp/AWatch-rus/ansible/inventory.ini`
- `cp /home/igor/tmp/AWatch-rus/ansible/group_vars/windows.example.yml /home/igor/tmp/AWatch-rus/ansible/group_vars/windows.yml`
- `cp ansible/inventory.example.ini ansible/inventory.ini`
- `cp ansible/group_vars/windows.example.yml ansible/group_vars/windows.yml`
2. Заполните `inventory.ini` (секция `[aw_windows]`) и `group_vars/windows.yml`.
- Для русской локализации Windows часто нужен `ansible_user=Администратор` (а не `Administrator`).
- Если WinRM закрыт, playbook не сможет стартовать и нужно сначала открыть `5985/5986` и `wsman`.
3. Запустите:
```bash
cd /home/igor/tmp/AWatch-rus/ansible
ansible-playbook -i inventory.ini deploy_aw_windows_phase2.yml
cd ansible
ansible-playbook -i inventory.ini deploy_aw_windows.yml
```
Playbook:
- выгружает `windows/*` toolkit на целевой хост в `C:\Deploy\AWatch-rus\windows`;
- выполняет `deploy-ensemble.ps1` (deploy + hardening/recovery) с phase-2 policy/rules;
- выгружает полный `windows/*` toolkit на целевой хост в InnoSetup-compatible каталог `C:\Program Files\AWatch-rus\windows`, включая DLP и `worktime-session-collector.ps1`;
- если найден legacy config `C:\ProgramData\ActivityWatch-Phase2\deployment-config.json`, выполняет безопасную миграцию через `migrate-awatch-rus-paths.ps1`: backup, остановка задач, перенос данных, переписывание путей, пересоздание scheduled tasks и validation;
- выполняет `deploy-ensemble.ps1` (deploy + hardening/recovery) с policy/rules из AWatch-rus toolkit;
- после deploy принудительно запускает `ActivityWatch Recovery` и все `ActivityWatch Launch *` задачи;
- выполняет API smoke-check bucket `aw-watcher-afk_SHARKON2025` и ожидает свежие `not-afk` события;
- выполняет API smoke-check bucket `aw-watcher-afk_<COMPUTERNAME>` и ожидает свежие `not-afk` события;
- запускает `validate-deployment.ps1`;
- забирает JSON-отчёт в локальную директорию (`/tmp/aw-rus-validation` по умолчанию).
@@ -110,17 +116,27 @@ Playbook:
- `aw_windows_incident_capture_enabled: false` — отключить блок incidentCapture;
- `aw_windows_incident_screenshot_enabled: false` — не делать скриншот при DLP-инциденте;
- `aw_windows_incident_artifacts_root: 'C:\...\incident-artifacts'` — переопределить путь артефактов;
- `aw_windows_deploy_root: 'C:\Program Files\AWatch-rus'` — каталог toolkit, совпадает с InnoSetup `{app}`;
- `aw_windows_install_root: 'C:\Program Files\AWatch-rus\bin'` — каталог бинарников, совпадает с InnoSetup `AwDefaultInstallRoot`;
- `aw_windows_state_root: 'C:\ProgramData\AWatch-rus'` — каталог состояния/отчётов, совпадает с InnoSetup `AwDefaultStateRoot`;
- `aw_windows_validation_remote_path: '{{ aw_windows_state_root }}\aw_validate_ansible.json'` — отчёт Ansible-валидации хранится рядом с `ensemble-report-*.json`;
- `aw_windows_migration_enabled: true` — включить guard миграции текущего production из `ActivityWatch-Phase2` в единый `AWatch-rus`;
- `aw_windows_legacy_install_root` / `aw_windows_legacy_state_root` — старые production paths, откуда выполняется перенос;
- `aw_windows_migration_report_remote_path` — JSON-отчёт о миграции на Windows-хосте;
- `aw_windows_package_version`, `aw_windows_package_url`, `aw_windows_package_zip_path` — версия и источник Windows-пакета ActivityWatch;
- `aw_windows_api_smoke_check_bucket: ""` — автоматически использовать `aw-watcher-afk_<COMPUTERNAME>`;
- `aw_windows_fail_on_validation_error: true` — завершать playbook ошибкой, если `validate-deployment.ps1` возвращает `overallOk=false`;
- `aw_windows_skip_hardening: true` — пропустить `hardening-recovery.ps1` внутри ensemble-скрипта.
## pfSense poller rollout
## Развёртывание pfSense poller
1. Подготовьте vars:
- `cp /home/igor/tmp/AWatch-rus/ansible/group_vars/pfsense-poller.example.yml /home/igor/tmp/AWatch-rus/ansible/group_vars/pfsense-poller.yml`
- `cp ansible/group_vars/pfsense-poller.example.yml ansible/group_vars/pfsense-poller.yml`
2. Добавьте inventory group `[aw_pfsense_pollers]`.
3. Запустите:
```bash
cd /home/igor/tmp/AWatch-rus/ansible
cd ansible
ansible-playbook -i inventory.ini deploy_aw_pfsense_poller.yml
```
@@ -139,4 +155,16 @@ Playbook:
- Для Web UI используется checksum-based cache-bust для `ru-patch-v5.js` и `sw-cleanup.js`, чтобы браузер не держал старую DLP/русскую статику после деплоя.
- На `#/home` Web UI делит хосты на `Windows RDP` и `Virtual servers + Proxmox`.
- Выполнена валидация API `http://127.0.0.1:5600/api/0/info`.
- Для full-stack сценария CT создаётся автоматически через `pct create`.
- Для полного сценария 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/`.
+11 -11
View File
@@ -1,5 +1,5 @@
---
- name: Deploy pfSense ActivityWatch poller
- name: Развернуть pfSense ActivityWatch poller
hosts: aw_pfsense_pollers
become: true
gather_facts: true
@@ -10,14 +10,14 @@
aw_pfsense_service_name: "aw-pfsense-poller.service"
tasks:
- name: Install required packages
- name: Установить обязательные пакеты
ansible.builtin.apt:
name:
- python3
state: present
update_cache: true
- name: Ensure directories exist
- name: Создать каталоги
ansible.builtin.file:
path: "{{ item }}"
state: directory
@@ -26,29 +26,29 @@
- "{{ aw_pfsense_install_root }}"
- "{{ aw_pfsense_config_dir }}"
- name: Install pfSense poller script
- name: Установить скрипт pfSense poller
ansible.builtin.copy:
src: "{{ aw_repo_root }}/pfsense/pfsense-aw-poller.py"
dest: "{{ aw_pfsense_install_root }}/pfsense-aw-poller.py"
mode: "0755"
- name: Install systemd service
- name: Установить systemd service
ansible.builtin.copy:
src: "{{ aw_repo_root }}/pfsense/pfsense-aw-poller.service"
dest: "/etc/systemd/system/{{ aw_pfsense_service_name }}"
mode: "0644"
notify:
- Reload systemd
- Перезагрузить systemd
- name: Write pfSense poller config
- name: Записать конфигурацию pfSense poller
ansible.builtin.copy:
dest: "{{ aw_pfsense_config_dir }}/poller.json"
mode: "0600"
content: "{{ aw_pfsense_poller_config | to_nice_json }}"
notify:
- Restart pfSense poller
- Перезапустить pfSense poller
- name: Enable and start pfSense poller
- name: Включить и запустить pfSense poller
ansible.builtin.systemd:
name: "{{ aw_pfsense_service_name }}"
enabled: true
@@ -56,11 +56,11 @@
daemon_reload: true
handlers:
- name: Reload systemd
- name: Перезагрузить systemd
ansible.builtin.systemd:
daemon_reload: true
- name: Restart pfSense poller
- name: Перезапустить pfSense poller
ansible.builtin.systemd:
name: "{{ aw_pfsense_service_name }}"
state: restarted
+618 -111
View File
@@ -1,5 +1,5 @@
---
- name: Deploy AWatch-rus server
- name: Развернуть сервер AWatch-rus
hosts: aw_server
become: true
gather_facts: true
@@ -9,22 +9,29 @@
aw_release_dir: "{{ aw_release_root }}/{{ aw_server_version }}"
aw_archive_path: "/tmp/activitywatch-{{ aw_server_version }}.zip"
aw_bootstrap_dir: "/tmp/aw-rus-bootstrap"
aw_release_install_dir: "{{ aw_release_root }}/aw-server-rust-{{ aw_server_version }}"
aw_ru_patch_cache_bust: "{{ lookup('file', aw_repo_root + '/aw-server/aw-ru-patch.js') | hash('sha1') | truncate(12, true, '') }}"
aw_sw_cleanup_cache_bust: "{{ lookup('file', aw_repo_root + '/aw-server/aw-sw-cleanup.js') | hash('sha1') | truncate(12, true, '') }}"
aw_host_groups_cache_bust: "{{ lookup('file', aw_repo_root + '/aw-server/aw-host-groups.json') | hash('sha1') | truncate(12, true, '') }}"
aw_worktime_classes: "{{ lookup('file', aw_repo_root + '/aw-server/settings/classes-worktime.json') | from_json }}"
aw_default_views: "{{ lookup('file', aw_repo_root + '/aw-server/settings/views-default.json') | from_json }}"
tasks:
- name: Install base packages
- name: Установить базовые пакеты
ansible.builtin.apt:
name:
- curl
- rsync
- unzip
state: present
update_cache: true
- name: Ensure service account exists
- name: Создать системную группу сервиса
ansible.builtin.group:
name: "{{ aw_server_group }}"
system: true
state: present
- name: Создать системную учётную запись сервиса
ansible.builtin.user:
name: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
@@ -33,7 +40,30 @@
system: true
create_home: false
- name: Ensure required directories
- name: Создать обязательные каталоги
ansible.builtin.file:
path: "{{ item }}"
state: directory
mode: "0755"
loop:
- "{{ aw_release_root }}"
- "{{ aw_release_dir }}"
- "{{ aw_release_install_dir }}"
- /opt/activitywatch
- /opt/activitywatch/bin
- "{{ aw_server_webui_dir }}"
- "{{ aw_server_webui_dir }}/js"
- "{{ aw_server_data_dir }}"
- "{{ aw_server_db_path | dirname }}"
- "{{ aw_server_data_dir }}/.config"
- "{{ aw_server_data_dir }}/.config/activitywatch"
- "{{ aw_server_data_dir }}/.config/activitywatch/aw-server-rust"
- "{{ aw_server_data_dir }}/backups"
- "{{ aw_server_log_dir }}"
- /etc/activitywatch
- "{{ aw_bootstrap_dir }}"
- name: Настроить каталоги ActivityWatch с владельцем сервиса
ansible.builtin.file:
path: "{{ item }}"
state: directory
@@ -41,148 +71,599 @@
group: "{{ aw_server_group }}"
mode: "0755"
loop:
- /opt/activitywatch
- /opt/activitywatch/bin
- "{{ aw_release_root }}"
- "{{ aw_release_dir }}"
- "{{ aw_release_install_dir }}"
- "{{ aw_server_webui_dir }}"
- "{{ aw_server_webui_dir }}/js"
- "{{ aw_server_data_dir }}"
- "{{ aw_server_db_path | dirname }}"
- "{{ aw_server_data_dir }}/.config"
- "{{ aw_server_data_dir }}/.config/activitywatch"
- "{{ aw_server_data_dir }}/.config/activitywatch/aw-server-rust"
- "{{ aw_server_data_dir }}/backups"
- "{{ aw_server_log_dir }}"
- /etc/activitywatch
- "{{ aw_bootstrap_dir }}"
- name: Download ActivityWatch release archive
ansible.builtin.get_url:
url: "{{ aw_server_download_url }}"
dest: "{{ aw_archive_path }}"
mode: "0644"
- name: (Check mode) Пропустить установку релиза ActivityWatch
ansible.builtin.debug:
msg: "ansible_check_mode=true: download/unarchive/install of ActivityWatch release is skipped."
when: ansible_check_mode
- name: Unpack ActivityWatch release
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: Discover extracted AW directory
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: Set release extracted path
ansible.builtin.set_fact:
aw_release_extracted: "{{ (aw_release_find.files | sort(attribute='path') | map(attribute='path') | list | first) }}"
- name: Найти распакованный каталог ActivityWatch
ansible.builtin.find:
paths: "{{ aw_release_dir }}"
recurse: true
file_type: directory
patterns: "activitywatch*"
register: aw_release_find
- name: Verify extracted directory exists
ansible.builtin.assert:
that:
- aw_release_extracted is defined
- aw_release_extracted | length > 0
fail_msg: "Cannot locate extracted ActivityWatch release directory."
- name: Найти бинарный файл 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: Sync release content to /opt/activitywatch
ansible.builtin.command:
cmd: "rsync -a --delete {{ aw_release_extracted }}/ /opt/activitywatch/"
- 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: Copy bootstrap files from repository
- name: Сохранить пути распакованного релиза (binary + webui index)
ansible.builtin.set_fact:
aw_release_extracted: "{{ (aw_release_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list | first) | default('') }}"
aw_server_binary_path: >-
{{
(
(
(aw_server_binary_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list)
| select('match', '.*/aw-server-rust$') | list | first
)
| default(
(
(aw_server_binary_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list | first)
),
true
)
) | default('')
}}
aw_webui_index_path: >-
{{
(
(
(aw_webui_index_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list)
| select('search', '/static/index\\.html$') | list | first
)
| default(
(
(aw_webui_index_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list | first)
),
true
)
) | default('')
}}
- name: Сохранить каталог WebUI (dirname index.html)
ansible.builtin.set_fact:
aw_webui_source_path: "{{ aw_webui_index_path | dirname }}"
- name: Проверить, что компоненты релиза найдены
ansible.builtin.assert:
that:
- aw_release_extracted is defined
- aw_release_extracted | length > 0
- aw_server_binary_path is defined
- aw_server_binary_path | length > 0
- aw_webui_source_path is defined
- aw_webui_source_path | length > 0
fail_msg: "Не удалось найти бинарный файл или WebUI в распакованном релизе ActivityWatch."
- name: Создать каталог установленного релиза
ansible.builtin.file:
path: "{{ aw_release_install_dir }}"
state: directory
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
mode: "0755"
- name: Установить бинарный файл AW server
ansible.builtin.copy:
remote_src: true
src: "{{ aw_server_binary_path }}"
dest: "{{ aw_release_install_dir }}/aw-server-rust"
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
mode: "0755"
- name: Создать ссылку на активный бинарный файл AW server
ansible.builtin.file:
src: "{{ aw_release_install_dir }}/aw-server-rust"
dest: /opt/activitywatch/bin/aw-server-rust
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
state: link
force: true
- name: Синхронизировать WebUI в RU каталог
ansible.builtin.command:
cmd: "rsync -a {{ aw_webui_source_path }}/ {{ aw_server_webui_dir }}/"
- name: Настроить владельца файлов /opt/activitywatch
ansible.builtin.file:
path: /opt/activitywatch
state: directory
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
recurse: true
- name: Установить systemd service из шаблона репозитория
ansible.builtin.copy:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
mode: "{{ item.mode }}"
loop:
- { src: "{{ aw_repo_root }}/aw-server/activitywatch-server.service", dest: "/etc/systemd/system/activitywatch-server.service", mode: "0644" }
- { src: "{{ aw_repo_root }}/aw-server/aw-ru-patch.js", dest: "{{ aw_server_webui_dir }}/js/ru-patch-v5.js", mode: "0644" }
- { src: "{{ aw_repo_root }}/aw-server/aw-sw-cleanup.js", dest: "{{ aw_server_webui_dir }}/js/sw-cleanup.js", mode: "0644" }
- { src: "{{ aw_repo_root }}/aw-server/aw-host-groups.json", dest: "{{ aw_server_webui_dir }}/js/aw-host-groups.json", mode: "0644" }
dest: /etc/systemd/system/activitywatch-server.service
mode: "0644"
content: >-
{{
lookup('file', aw_repo_root + '/aw-server/activitywatch-server.service')
| replace('__AW_SERVER_USER__', aw_server_user)
| replace('__AW_SERVER_GROUP__', aw_server_group)
| replace('__AW_SERVER_DATA_DIR__', aw_server_data_dir)
}}
notify:
- Reload systemd
- Restart activitywatch
- Перезагрузить systemd
- Перезапустить activitywatch
- name: Copy WebUI index template from installed distribution
ansible.builtin.copy:
remote_src: true
src: "/opt/activitywatch/aw-webui/index.html"
dest: "{{ aw_server_webui_dir }}/index.html"
mode: "0644"
- name: (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: Insert RU patch scripts into index.html
ansible.builtin.replace:
path: "{{ aw_server_webui_dir }}/index.html"
regexp: '</head>'
replace: '<script src="/js/sw-cleanup.js?v={{ aw_sw_cleanup_cache_bust }}"></script></head>'
- name: Применить 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: Insert RU patch loader before body end
ansible.builtin.replace:
path: "{{ aw_server_webui_dir }}/index.html"
regexp: '</body>'
replace: '<script defer="defer" src="/js/ru-patch-v5.js?v={{ aw_ru_patch_cache_bust }}"></script></body>'
- name: Создать каталог /root/bootstrap для apply_webui_ru_patch.sh
ansible.builtin.file:
path: /root/bootstrap
state: directory
mode: "0755"
- name: Write /etc/activitywatch/aw-server.env
ansible.builtin.copy:
dest: /etc/activitywatch/aw-server.env
mode: "0640"
content: |
AW_SERVER_HOST={{ aw_server_bind_host }}
AW_SERVER_PORT={{ aw_server_port }}
AW_DATA_DIR={{ aw_server_data_dir }}
AW_LOG_DIR={{ aw_server_log_dir }}
AW_WEBUI_DIR={{ aw_server_webui_dir }}
AW_SERVER_USER={{ aw_server_user }}
AW_SERVER_GROUP={{ aw_server_group }}
- name: Скопировать 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: Enable and start service
ansible.builtin.systemd:
name: activitywatch-server.service
enabled: true
state: restarted
daemon_reload: true
- 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: Wait for API
- name: Записать /etc/activitywatch/aw-server.env перед хотфиксами
ansible.builtin.copy:
dest: /etc/activitywatch/aw-server.env
mode: "0640"
owner: root
group: root
content: |
AW_SERVER_BIND_HOST={{ aw_server_bind_host }}
AW_SERVER_PORT={{ aw_server_port }}
AW_SERVER_DATA_DIR={{ aw_server_data_dir }}
AW_SERVER_DB_PATH={{ aw_server_db_path }}
AW_SERVER_LOG_DIR={{ aw_server_log_dir }}
AW_SERVER_WEBUI_DIR={{ aw_server_webui_dir }}
AW_SERVER_USER={{ aw_server_user }}
AW_SERVER_GROUP={{ aw_server_group }}
AW_WORKTIME_REPORT_BASE={{ aw_worktime_report_base }}
AW_WORKTIME_TZ={{ aw_worktime_timezone }}
XDG_DATA_HOME={{ aw_server_data_dir }}/.local/share
XDG_CONFIG_HOME={{ aw_server_data_dir }}/.config
- name: Установить скрипт AW worktime API
ansible.builtin.copy:
src: "{{ aw_repo_root }}/aw-server/aw-worktime-api.py"
dest: /usr/local/bin/aw-worktime-api.py
owner: root
group: root
mode: "0755"
- name: Установить systemd unit AW worktime API
ansible.builtin.copy:
src: "{{ aw_repo_root }}/aw-server/aw-worktime-api.service"
dest: /etc/systemd/system/aw-worktime-api.service
owner: root
group: root
mode: "0644"
- name: Установить скрипт AW worktime UI bridge
ansible.builtin.copy:
src: "{{ aw_repo_root }}/aw-server/aw-worktime-ui-bridge.py"
dest: /usr/local/bin/aw-worktime-ui-bridge.py
owner: root
group: root
mode: "0755"
- name: Установить systemd unit AW worktime UI bridge
ansible.builtin.copy:
src: "{{ aw_repo_root }}/aw-server/aw-worktime-ui-bridge.service"
dest: /etc/systemd/system/aw-worktime-ui-bridge.service
owner: root
group: root
mode: "0644"
- name: Установить systemd timer AW worktime UI bridge
ansible.builtin.copy:
src: "{{ aw_repo_root }}/aw-server/aw-worktime-ui-bridge.timer"
dest: /etc/systemd/system/aw-worktime-ui-bridge.timer
owner: root
group: root
mode: "0644"
- name: Перезагрузить systemd после установки AW worktime API
ansible.builtin.systemd:
daemon_reload: true
- name: Включить и перезапустить AW worktime API
ansible.builtin.systemd:
name: aw-worktime-api.service
enabled: true
state: restarted
- name: Отключить legacy timer aw-worktime-afk-bridge (если есть)
ansible.builtin.systemd:
name: aw-worktime-afk-bridge.timer
enabled: false
state: stopped
failed_when: false
- name: Включить и перезапустить AW worktime UI bridge timer
ansible.builtin.systemd:
name: aw-worktime-ui-bridge.timer
enabled: true
state: restarted
- name: Выполнить разовый прогон AW worktime UI bridge
ansible.builtin.systemd:
name: aw-worktime-ui-bridge.service
state: started
failed_when: false
- name: Применить хотфиксы compiled JS чанков (Trends, Timespiral, Category helper)
ansible.builtin.command:
cmd: "/opt/activitywatch/aw-server/apply_webui_ru_patch.sh"
register: apply_ru_patch_result
failed_when: false
- name: Вывести результат применения хотфиксов
ansible.builtin.debug:
msg: "apply_webui_ru_patch.sh: {{ apply_ru_patch_result.stdout }}"
- name: Проверить наличие index.html после копирования
ansible.builtin.stat:
path: "{{ aw_server_webui_dir }}/index.html"
register: aw_webui_ru_index
- name: Проверить, что index.html доступен для RU patch
ansible.builtin.assert:
that:
- aw_webui_ru_index.stat.exists
fail_msg: "Не найден index.html WebUI для применения RU patch."
- name: Удалить старые теги RU patch из index.html
ansible.builtin.replace:
path: "{{ aw_server_webui_dir }}/index.html"
regexp: '<script[^>]+(?:ru-patch-v5\.js|sw-cleanup\.js|aw-ru-patch\.js|aw-sw-cleanup\.js)[^>]*></script>'
replace: ''
- name: Добавить cleanup script RU patch в index.html
ansible.builtin.replace:
path: "{{ aw_server_webui_dir }}/index.html"
regexp: '</head>'
replace: '<script src="/js/sw-cleanup.js?v={{ aw_sw_cleanup_cache_bust }}"></script></head>'
- name: Добавить загрузчик RU patch перед закрытием body
ansible.builtin.replace:
path: "{{ aw_server_webui_dir }}/index.html"
regexp: '</body>'
replace: '<script defer="defer" src="/js/ru-patch-v5.js?v={{ aw_ru_patch_cache_bust }}"></script></body>'
- name: Скопировать merge script AW DB на сервер
ansible.builtin.copy:
src: "{{ aw_repo_root }}/scripts/merge_aw_server_dbs.py"
dest: /usr/local/bin/merge_aw_server_dbs.py
owner: root
group: root
mode: "0755"
- name: Проверить наличие legacy root DB
ansible.builtin.stat:
path: /root/.local/share/activitywatch/aw-server-rust/sqlite.db
register: aw_legacy_root_db
- name: Проверить наличие target DB
ansible.builtin.stat:
path: "{{ aw_server_db_path }}"
register: aw_target_db
- name: Остановить сервис перед merge server DB
ansible.builtin.systemd:
name: activitywatch-server.service
state: stopped
when: aw_legacy_root_db.stat.exists | default(false)
- name: Создать backup каталоги server DB
ansible.builtin.file:
path: "{{ aw_server_data_dir }}/backups/db"
state: directory
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
mode: "0755"
when: aw_legacy_root_db.stat.exists | default(false)
- name: Backup target DB перед merge
ansible.builtin.copy:
remote_src: true
src: "{{ aw_server_db_path }}"
dest: "{{ aw_server_data_dir }}/backups/db/target-before-merge-{{ ansible_date_time.iso8601_basic_short }}.sqlite.db"
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
mode: "0644"
when:
- aw_legacy_root_db.stat.exists | default(false)
- aw_target_db.stat.exists | default(false)
- name: Backup legacy root DB перед merge
ansible.builtin.copy:
remote_src: true
src: /root/.local/share/activitywatch/aw-server-rust/sqlite.db
dest: "{{ aw_server_data_dir }}/backups/db/legacy-root-{{ ansible_date_time.iso8601_basic_short }}.sqlite.db"
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
mode: "0644"
when: aw_legacy_root_db.stat.exists | default(false)
- name: Merge legacy root DB в target DB
ansible.builtin.command:
argv:
- python3
- /usr/local/bin/merge_aw_server_dbs.py
- --base
- /root/.local/share/activitywatch/aw-server-rust/sqlite.db
- --overlay
- "{{ aw_server_db_path }}"
- --output
- "{{ aw_server_db_path }}.merged"
when:
- aw_legacy_root_db.stat.exists | default(false)
- aw_target_db.stat.exists | default(false)
- name: Install merged DB as active target DB
ansible.builtin.copy:
remote_src: true
src: "{{ aw_server_db_path }}.merged"
dest: "{{ aw_server_db_path }}"
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
mode: "0644"
when:
- aw_legacy_root_db.stat.exists | default(false)
- aw_target_db.stat.exists | default(false)
- name: Скопировать legacy root DB в target DB если target ещё не существует
ansible.builtin.copy:
remote_src: true
src: /root/.local/share/activitywatch/aw-server-rust/sqlite.db
dest: "{{ aw_server_db_path }}"
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
mode: "0644"
when:
- aw_legacy_root_db.stat.exists | default(false)
- not (aw_target_db.stat.exists | default(false))
- name: Записать aw-server-rust config.toml с разрешёнными CORS origin
ansible.builtin.copy:
dest: "{{ aw_server_data_dir }}/.config/activitywatch/aw-server-rust/config.toml"
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
mode: "0644"
content: |
cors = [
{% for origin in aw_server_cors_origins | default([]) %}
"{{ origin }}"{% if not loop.last %},{% endif %}
{% endfor %}
]
- name: Включить и запустить сервис
ansible.builtin.systemd:
name: activitywatch-server.service
enabled: true
state: restarted
daemon_reload: true
- name: Дождаться ответа API
ansible.builtin.uri:
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/info"
method: GET
status_code: 200
register: aw_api
retries: 10
delay: 3
until: aw_api.status == 200
- name: Считать текущие server-side settings
ansible.builtin.uri:
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/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: Apply baseline worktime settings (classes)
- 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:
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/classes"
method: POST
body: "{{ aw_worktime_classes }}"
body_format: json
status_code: 201
status_code: [200, 201]
when: aw_apply_worktime_settings | default(false) | bool
- name: Apply baseline views (include DLP and worktime)
- name: Применить базовые views для DLP и worktime
ansible.builtin.uri:
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/views"
method: POST
body: "{{ aw_default_views }}"
body_format: json
status_code: 201
status_code: [200, 201]
when: aw_apply_worktime_settings | default(false) | bool
- name: Derive worktime durationDefault from aw_worktime_from/to
- 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
)
}}
when: aw_apply_worktime_settings | default(false) | bool
- name: Normalize derived durationDefault for overnight shifts
- name: Нормализовать durationDefault для ночных смен
ansible.builtin.set_fact:
aw_worktime_duration_default_effective: >-
{{
@@ -192,38 +673,64 @@
}}
when: aw_apply_worktime_settings | default(false) | bool
- name: Validate derived durationDefault is sane
- name: Проверить корректность durationDefault
ansible.builtin.assert:
that:
- aw_worktime_duration_default_effective | int > 0
- aw_worktime_duration_default_effective | int <= 86400
fail_msg: "Invalid worktime window: {{ aw_worktime_from }}..{{ aw_worktime_to }}"
fail_msg: "Некорректный интервал рабочего времени: {{ aw_worktime_from }}..{{ aw_worktime_to }}"
when: aw_apply_worktime_settings | default(false) | bool
- name: Apply baseline worktime period (startOfDay)
- name: Применить базовый период worktime (startOfDay)
ansible.builtin.uri:
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/startOfDay"
method: POST
body: "{{ aw_worktime_start_of_day }}"
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: Apply baseline worktime period (durationDefault seconds)
- 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: Reload systemd
- name: Перезагрузить systemd
ansible.builtin.systemd:
daemon_reload: true
- name: Restart activitywatch
- name: Перезапустить activitywatch
ansible.builtin.systemd:
name: activitywatch-server.service
state: restarted
+300
View File
@@ -0,0 +1,300 @@
---
- name: Развернуть Windows/RDP collector'ы AWatch-rus
hosts: aw_windows
gather_facts: false
vars:
aw_windows_repo_root: "{{ playbook_dir | dirname }}"
aw_windows_deploy_root: "C:\\Program Files\\AWatch-rus"
aw_windows_server_scheme: "http"
aw_windows_server_host: "10.10.10.13"
aw_windows_server_port: 5600
aw_windows_package_version: "v0.13.2"
aw_windows_package_url: "https://github.com/ActivityWatch/activitywatch/releases/download/v0.13.2/activitywatch-v0.13.2-windows-x86_64.zip"
aw_windows_package_zip_path: ""
aw_windows_domain: "SHARKON2025"
aw_windows_users:
- user1
- user2
- user3
- user4
- user5
aw_windows_extra_users: []
aw_windows_users_effective: "{{ (aw_windows_users + aw_windows_extra_users) | unique }}"
aw_windows_install_root: "C:\\Program Files\\AWatch-rus\\bin"
aw_windows_state_root: "C:\\ProgramData\\AWatch-rus"
aw_windows_afk_enabled: true
aw_windows_window_enabled: true
aw_windows_file_ops_enabled: true
aw_windows_local_agent_logs_enabled: false
aw_windows_incident_capture_enabled: true
aw_windows_incident_screenshot_enabled: true
aw_windows_incident_artifacts_root: "{{ aw_windows_state_root }}\\incident-artifacts"
aw_windows_logon_marker_enabled: true
aw_windows_skip_hardening: false
aw_windows_rules_path: "{{ aw_windows_deploy_root }}\\windows\\web-category-rules.example.json"
aw_windows_policy_path: "{{ aw_windows_deploy_root }}\\windows\\dlp-policy.example.json"
aw_windows_validation_remote_path: "{{ aw_windows_state_root }}\\aw_validate_ansible.json"
aw_windows_validation_local_dir: "/tmp/aw-rus-validation"
aw_windows_launch_task_pattern: "ActivityWatch Launch *"
aw_windows_recovery_task_name: "ActivityWatch Recovery"
aw_windows_force_task_restart: true
aw_windows_api_smoke_check_enabled: true
aw_windows_api_smoke_check_bucket: ""
aw_windows_api_smoke_check_limit: 10
aw_windows_fail_on_validation_error: true
aw_windows_migration_enabled: true
aw_windows_legacy_install_root: "C:\\Program Files\\ActivityWatch-Phase2"
aw_windows_legacy_state_root: "C:\\ProgramData\\ActivityWatch-Phase2"
aw_windows_migration_report_remote_path: "{{ aw_windows_state_root }}\\aw_migration_ansible.json"
tasks:
- name: Проверить обязательные переменные
ansible.builtin.assert:
that:
- aw_windows_server_host is defined
- aw_windows_server_port is defined
- aw_windows_server_scheme is defined
- aw_windows_domain is defined
- aw_windows_users_effective | length > 0
- aw_windows_install_root is defined
- aw_windows_state_root is defined
fail_msg: "Не заданы обязательные переменные Windows-развёртывания."
- name: Создать каталоги развёртывания
ansible.windows.win_file:
path: "{{ item }}"
state: directory
loop:
- "{{ aw_windows_deploy_root }}"
- "{{ aw_windows_deploy_root }}\\windows"
- name: Загрузить Windows toolkit развёртывания
ansible.windows.win_copy:
src: "{{ aw_windows_repo_root }}/windows/{{ item }}"
dest: "{{ aw_windows_deploy_root }}\\windows\\{{ item }}"
loop:
- ActivityWatch.Windows.Common.psd1
- ActivityWatch.Windows.Common.psm1
- browser-domains-native-collector.ps1
- dlp-endpoint-signals-collector.ps1
- email-outbound-collector.ps1
- file-operations-collector.ps1
- worktime-session-collector.ps1
- migrate-awatch-rus-paths.ps1
- deploy-domain-users.ps1
- deploy-ensemble.ps1
- hardening-recovery.ps1
- validate-deployment.ps1
- web-category-rules.example.json
- dlp-policy.example.json
- name: Нормализовать кодировку PowerShell файлов (UTF-8 BOM для Windows PowerShell)
ansible.windows.win_powershell:
script: |
$ErrorActionPreference = 'Stop'
$toolkitDir = "{{ aw_windows_deploy_root }}\windows"
$encIn = New-Object System.Text.UTF8Encoding($false)
$encOut = New-Object System.Text.UTF8Encoding($true)
Get-ChildItem -LiteralPath $toolkitDir -File -Include *.ps1,*.psm1,*.psd1 | ForEach-Object {
$text = [System.IO.File]::ReadAllText($_.FullName, $encIn)
[System.IO.File]::WriteAllText($_.FullName, $text, $encOut)
}
- name: Загрузить список пользователей для доменного развёртывания
ansible.windows.win_copy:
dest: "{{ aw_windows_deploy_root }}\\windows\\users.txt"
content: |
{% for user in aw_windows_users_effective -%}
{{ user }}
{% endfor -%}
- name: Проверить нужен ли migration с legacy ActivityWatch путей
when: aw_windows_migration_enabled | bool
ansible.windows.win_stat:
path: "{{ aw_windows_legacy_state_root }}\\deployment-config.json"
register: aw_windows_legacy_config
- name: Выполнить безопасную migration legacy prod в AWatch-rus
when:
- aw_windows_migration_enabled | bool
- aw_windows_legacy_config.stat.exists | default(false)
ansible.windows.win_powershell:
script: |
$ErrorActionPreference = 'Stop'
$result = & "{{ aw_windows_deploy_root }}\windows\migrate-awatch-rus-paths.ps1" `
-OldInstallRoot "{{ aw_windows_legacy_install_root }}" `
-OldStateRoot "{{ aw_windows_legacy_state_root }}" `
-NewInstallRoot "{{ aw_windows_install_root }}" `
-NewStateRoot "{{ aw_windows_state_root }}" `
-ToolkitRoot "{{ aw_windows_deploy_root }}\windows"
$result | ConvertTo-Json -Depth 8 | Out-File -FilePath "{{ aw_windows_migration_report_remote_path }}" -Encoding utf8
- name: Запустить Windows/RDP ensemble развёртывание
ansible.windows.win_powershell:
script: |
$ErrorActionPreference = 'Stop'
$params = @{
ServerScheme = "{{ aw_windows_server_scheme }}"
ServerHost = "{{ aw_windows_server_host }}"
ServerPort = {{ aw_windows_server_port }}
Version = "{{ aw_windows_package_version }}"
Domain = "{{ aw_windows_domain }}"
UserListPath = "{{ aw_windows_deploy_root }}\windows\users.txt"
InstallRoot = "{{ aw_windows_install_root }}"
StateRoot = "{{ aw_windows_state_root }}"
AfkEnabled = {{ '$true' if (aw_windows_afk_enabled | bool) else '$false' }}
WindowEnabled = {{ '$true' if (aw_windows_window_enabled | bool) else '$false' }}
FileOpsEnabled = {{ '$true' if (aw_windows_file_ops_enabled | bool) else '$false' }}
LocalAgentLogsEnabled = {{ '$true' if (aw_windows_local_agent_logs_enabled | bool) else '$false' }}
IncidentCaptureEnabled = {{ '$true' if (aw_windows_incident_capture_enabled | bool) else '$false' }}
IncidentScreenshotEnabled = {{ '$true' if (aw_windows_incident_screenshot_enabled | bool) else '$false' }}
IncidentArtifactsRoot = "{{ aw_windows_incident_artifacts_root }}"
LogonMarkerEnabled = {{ '$true' if (aw_windows_logon_marker_enabled | bool) else '$false' }}
CustomRulesPath = "{{ aw_windows_rules_path }}"
CustomPolicyPath = "{{ aw_windows_policy_path }}"
}
{% if (aw_windows_package_url | default('') | string | length) > 0 %}
$params.PackageUrl = "{{ aw_windows_package_url }}"
{% endif %}
{% if (aw_windows_package_zip_path | default('') | string | length) > 0 %}
$params.PackageZipPath = "{{ aw_windows_package_zip_path }}"
{% endif %}
{% if (aw_windows_hostname_override | default('') | string | length) > 0 %}
$params.AwHostname = "{{ aw_windows_hostname_override }}"
{% endif %}
{% if aw_windows_skip_hardening | bool %}
$params.SkipHardening = $true
{% endif %}
& "{{ aw_windows_deploy_root }}\windows\deploy-ensemble.ps1" @params
- name: Удалить лишние ActivityWatch Launch tasks вне текущего deployment-config
ansible.windows.win_powershell:
script: |
$ErrorActionPreference = 'Stop'
$config = Get-Content -Raw -LiteralPath "{{ aw_windows_state_root }}\deployment-config.json" | ConvertFrom-Json
$desired = @($config.userTasks | ForEach-Object { [string]$_.LaunchTaskName })
foreach ($task in @(Get-ScheduledTask | Where-Object { $_.TaskName -like 'ActivityWatch Launch *' })) {
if ($desired -notcontains [string]$task.TaskName) {
Unregister-ScheduledTask -TaskName $task.TaskName -Confirm:$false -ErrorAction SilentlyContinue
& cmd.exe /c "schtasks /Delete /TN `"$($task.TaskName)`" /F >nul 2>&1" | Out-Null
}
}
- name: Принудительно запустить ActivityWatch recovery и launch tasks
when: aw_windows_force_task_restart | bool
ansible.windows.win_powershell:
script: |
$ErrorActionPreference = 'Stop'
function Get-CollectorKey {
param([string]$CommandLine)
if (-not $CommandLine) { return $null }
$cl = $CommandLine.ToLowerInvariant()
if ($cl -like '*browser-domains-native-collector.ps1*') { return 'browser' }
if ($cl -like '*file-operations-collector.ps1*') { return 'fileops' }
if ($cl -like '*dlp-endpoint-signals-collector.ps1*') { return 'endpoint' }
if ($cl -like '*email-outbound-collector.ps1*') { return 'email' }
if ($cl -like '*worktime-session-collector.ps1*') { return 'worktime' }
return $null
}
$collectorProcs = Get-CimInstance Win32_Process |
Where-Object { $_.Name -eq 'powershell.exe' -and $_.CommandLine } |
ForEach-Object {
$key = Get-CollectorKey -CommandLine $_.CommandLine
if ($key) {
[pscustomobject]@{
ProcessId = [int]$_.ProcessId
SessionId = [int]$_.SessionId
CreationDate = $_.CreationDate
CollectorKey = $key
}
}
} |
Where-Object { $_ -ne $null }
# Keep only one process per (collector, session): newest survives, older duplicates are stopped.
foreach ($group in ($collectorProcs | Group-Object CollectorKey, SessionId)) {
$ordered = @($group.Group | Sort-Object CreationDate -Descending)
if ($ordered.Count -le 1) { continue }
foreach ($dup in $ordered | Select-Object -Skip 1) {
Stop-Process -Id $dup.ProcessId -Force -ErrorAction SilentlyContinue
}
}
Start-Sleep -Seconds 2
Start-ScheduledTask -TaskName "{{ aw_windows_recovery_task_name }}"
Get-ScheduledTask |
Where-Object TaskName -like "{{ aw_windows_launch_task_pattern }}" |
ForEach-Object { Start-ScheduledTask -TaskName $_.TaskName }
- name: Получить Windows hostname для AW smoke-check bucket
when:
- aw_windows_api_smoke_check_enabled | bool
- aw_windows_afk_enabled | bool
ansible.windows.win_command: powershell.exe -NoProfile -Command "$env:COMPUTERNAME"
register: aw_windows_hostname_result
changed_when: false
- name: Вычислить AW AFK smoke-check bucket
when:
- aw_windows_api_smoke_check_enabled | bool
- aw_windows_afk_enabled | bool
- aw_windows_hostname_result.stdout is defined
ansible.builtin.set_fact:
aw_windows_api_smoke_check_bucket_effective: >-
{{
aw_windows_api_smoke_check_bucket
if (aw_windows_api_smoke_check_bucket | default('') | string | length) > 0
else 'aw-watcher-afk_' ~ (aw_windows_hostname_result.stdout | trim)
}}
- name: Выполнить AW API smoke-check (проверка наличия свежих событий в AFK бакете)
when:
- aw_windows_api_smoke_check_enabled | bool
- aw_windows_afk_enabled | bool
ansible.builtin.uri:
url: "{{ aw_windows_server_scheme }}://{{ aw_windows_server_host }}:{{ aw_windows_server_port }}/api/0/buckets/{{ aw_windows_api_smoke_check_bucket_effective }}/events?limit={{ aw_windows_api_smoke_check_limit }}"
method: GET
status_code: 200
register: aw_windows_api_smoke_result
until: aw_windows_api_smoke_result.json | length > 0
retries: 5
delay: 5
ignore_errors: true
- name: Валидировать развёртывание на эндпоинте
ansible.windows.win_powershell:
script: |
$ErrorActionPreference = 'Stop'
$result = & "{{ aw_windows_deploy_root }}\windows\validate-deployment.ps1" `
-ConfigPath "{{ aw_windows_state_root }}\deployment-config.json"
$result | ConvertTo-Json -Depth 8 | Out-File -FilePath "{{ aw_windows_validation_remote_path }}" -Encoding utf8
return $result
- name: Создать локальную директорию для отчётов валидации
ansible.builtin.file:
path: "{{ aw_windows_validation_local_dir }}"
state: directory
mode: "0755"
delegate_to: localhost
- name: Стянуть отчёт валидации с эндпоинта
ansible.builtin.fetch:
src: "{{ aw_windows_validation_remote_path }}"
dest: "{{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_ansible.json"
flat: true
- name: Проверить статус валидации
ansible.builtin.shell: |
python3 - <<'PY'
import json, sys
with open('{{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_ansible.json', 'r') as f:
data = json.load(f)
if not data.get('overallOk', False):
print(f"Validation failed for {{ inventory_hostname }}: {data.get('summary', 'Unknown error')}")
sys.exit(1)
PY
delegate_to: localhost
when: aw_windows_fail_on_validation_error | bool
-169
View File
@@ -1,169 +0,0 @@
---
- name: Deploy AWatch-rus Windows phase2 collectors
hosts: aw_windows
gather_facts: false
vars:
aw_windows_repo_root: "/home/igor/tmp/AWatch-rus"
aw_windows_deploy_root: "C:\\Deploy\\AWatch-rus"
aw_windows_server_host: "10.10.10.13"
aw_windows_server_port: 5600
aw_windows_domain: "SHARKON2025"
aw_windows_users:
- user1
- user2
- user3
- user4
- user5
aw_windows_extra_users: []
aw_windows_users_effective: "{{ (aw_windows_users + aw_windows_extra_users) | unique }}"
aw_windows_install_root: "C:\\Program Files\\ActivityWatch-Phase2"
aw_windows_state_root: "C:\\ProgramData\\ActivityWatch"
aw_windows_afk_enabled: true
aw_windows_window_enabled: true
aw_windows_local_agent_logs_enabled: false
aw_windows_incident_capture_enabled: true
aw_windows_incident_screenshot_enabled: true
aw_windows_incident_artifacts_root: "{{ aw_windows_state_root }}\\incident-artifacts"
aw_windows_logon_marker_enabled: true
aw_windows_skip_hardening: false
aw_windows_rules_path: "{{ aw_windows_deploy_root }}\\windows\\web-category-rules.example.json"
aw_windows_policy_path: "{{ aw_windows_deploy_root }}\\windows\\dlp-policy.example.json"
aw_windows_validation_remote_path: "C:\\Windows\\Temp\\aw_validate_phase2_ansible.json"
aw_windows_validation_local_dir: "/tmp/aw-rus-validation"
aw_windows_launch_task_pattern: "ActivityWatch Launch *"
aw_windows_recovery_task_name: "ActivityWatch Recovery"
aw_windows_force_task_restart: true
aw_windows_api_smoke_check_enabled: true
aw_windows_api_smoke_check_bucket: "aw-watcher-afk_SHARKON2025"
aw_windows_api_smoke_check_limit: 10
tasks:
- name: Validate required variables
ansible.builtin.assert:
that:
- aw_windows_server_host is defined
- aw_windows_server_port is defined
- aw_windows_domain is defined
- aw_windows_users_effective | length > 0
- aw_windows_install_root is defined
- aw_windows_state_root is defined
fail_msg: "Missing required Windows deployment variables."
- name: Ensure deploy directories exist
ansible.windows.win_file:
path: "{{ item }}"
state: directory
loop:
- "{{ aw_windows_deploy_root }}"
- "{{ aw_windows_deploy_root }}\\windows"
- name: Upload Windows deployment toolkit
ansible.windows.win_copy:
src: "{{ aw_windows_repo_root }}/windows/{{ item }}"
dest: "{{ aw_windows_deploy_root }}\\windows\\{{ item }}"
loop:
- ActivityWatch.Windows.Common.psd1
- ActivityWatch.Windows.Common.psm1
- browser-domains-native-collector.ps1
- dlp-endpoint-signals-collector.ps1
- deploy-domain-users.ps1
- deploy-ensemble.ps1
- hardening-recovery.ps1
- validate-deployment.ps1
- web-category-rules.example.json
- dlp-policy.example.json
- name: Upload user list for domain deploy
ansible.windows.win_copy:
dest: "{{ aw_windows_deploy_root }}\\windows\\users.txt"
content: |
{% for user in aw_windows_users -%}
{{ user }}
{% endfor -%}
{% for user in aw_windows_extra_users -%}
{{ user }}
{% endfor -%}
- name: Run phase2 ensemble deployment
ansible.windows.win_powershell:
script: |
$ErrorActionPreference = 'Stop'
$params = @{
ServerHost = "{{ aw_windows_server_host }}"
ServerPort = {{ aw_windows_server_port }}
Domain = "{{ aw_windows_domain }}"
UserListPath = "{{ aw_windows_deploy_root }}\windows\users.txt"
InstallRoot = "{{ aw_windows_install_root }}"
StateRoot = "{{ aw_windows_state_root }}"
AfkEnabled = {{ '$true' if (aw_windows_afk_enabled | bool) else '$false' }}
WindowEnabled = {{ '$true' if (aw_windows_window_enabled | bool) else '$false' }}
LocalAgentLogsEnabled = {{ '$true' if (aw_windows_local_agent_logs_enabled | bool) else '$false' }}
IncidentCaptureEnabled = {{ '$true' if (aw_windows_incident_capture_enabled | bool) else '$false' }}
IncidentScreenshotEnabled = {{ '$true' if (aw_windows_incident_screenshot_enabled | bool) else '$false' }}
IncidentArtifactsRoot = "{{ aw_windows_incident_artifacts_root }}"
LogonMarkerEnabled = {{ '$true' if (aw_windows_logon_marker_enabled | bool) else '$false' }}
CustomRulesPath = "{{ aw_windows_rules_path }}"
CustomPolicyPath = "{{ aw_windows_policy_path }}"
}
{% if aw_windows_skip_hardening | bool %}
$params.SkipHardening = $true
{% endif %}
& "{{ aw_windows_deploy_root }}\windows\deploy-ensemble.ps1" @params
- name: Force start ActivityWatch recovery and launch tasks
when: aw_windows_force_task_restart | bool
ansible.windows.win_powershell:
script: |
$ErrorActionPreference = 'Stop'
Start-ScheduledTask -TaskName "{{ aw_windows_recovery_task_name }}"
Get-ScheduledTask |
Where-Object TaskName -like "{{ aw_windows_launch_task_pattern }}" |
ForEach-Object { Start-ScheduledTask -TaskName $_.TaskName }
- name: Wait for fresh AFK events to appear on AW server
when: aw_windows_api_smoke_check_enabled | bool
delegate_to: localhost
ansible.builtin.uri:
url: "http://{{ aw_windows_server_host }}:{{ aw_windows_server_port }}/api/0/buckets/{{ aw_windows_api_smoke_check_bucket }}/events?limit={{ aw_windows_api_smoke_check_limit }}"
method: GET
return_content: true
register: aw_windows_api_smoke
until: >
aw_windows_api_smoke.status == 200 and
(aw_windows_api_smoke.json | length) > 0 and
(
aw_windows_api_smoke.json
| selectattr('data.status', 'equalto', 'not-afk')
| list
| length
) > 0
retries: 10
delay: 6
- name: Run validation and store report on target
ansible.windows.win_powershell:
script: |
$ErrorActionPreference = 'Stop'
$report = & "{{ aw_windows_deploy_root }}\windows\validate-deployment.ps1" `
-ConfigPath "{{ aw_windows_state_root }}\deployment-config.json"
$report | ConvertTo-Json -Depth 12 | Out-File -FilePath "{{ aw_windows_validation_remote_path }}" -Encoding utf8
- name: Ensure local validation directory exists
ansible.builtin.file:
path: "{{ aw_windows_validation_local_dir }}"
state: directory
mode: "0755"
delegate_to: localhost
- name: Fetch validation report
ansible.builtin.fetch:
src: "{{ aw_windows_validation_remote_path }}"
dest: "{{ aw_windows_validation_local_dir }}/"
flat: false
- name: Show report location
ansible.builtin.debug:
msg:
- "Windows phase2 deploy completed on {{ inventory_hostname }}."
- "Validation report: {{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}/C$/Windows/Temp/aw_validate_phase2_ansible.json"
+21 -8
View File
@@ -4,21 +4,34 @@ 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: "/home/igor/tmp/AWatch-rus"
aw_repo_root: "{{ playbook_dir | dirname }}"
# Optional: apply a baseline worktime-focused categorization and views via AW settings API.
# WARNING: this overwrites existing server-side settings/classes/views.
aw_apply_worktime_settings: false
# Применить базовые категории и views для рабочего времени через AW settings API.
# При прод-обновлениях это нужно оставлять включённым, иначе UI остаётся без views/classes.
aw_apply_worktime_settings: true
# Optional defaults for the worktime period in Web UI.
# startOfDay controls day-boundary and default report window start.
# durationDefault controls default time range (seconds) shown in UI.
# Дополнительные origin для aw-server-rust CORS.
# Обязательно включите тот origin, с которого реально открывается Web UI.
aw_server_cors_origins:
- "http://127.0.0.1:5600"
- "http://localhost:5600"
- "http://10.10.10.13:5600"
- "http://aw-server:5600"
# Опциональные значения периода рабочего времени в Web UI.
# startOfDay задаёт границу дня и стартовое время окна отчёта.
# durationDefault задаёт диапазон по умолчанию в секундах.
#
# Recommended: set worktime window explicitly and let the playbook derive duration.
# Рекомендуется явно задать рабочий интервал и дать playbook вычислить duration.
aw_worktime_from: "08:00"
aw_worktime_to: "17:00"
aw_worktime_start_of_day: "{{ aw_worktime_from }}"
aw_server_always_active_pattern: "aw-watcher-window"
aw_server_landingpage: "/activity/SHARKON2025/view/"
+27
View File
@@ -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) }}"
+55
View File
@@ -0,0 +1,55 @@
# Secret handling:
# - put the real password into env var before running Ansible:
# export AW_WINRM_PASSWORD='...'
ansible_password: "{{ lookup('env', 'AW_WINRM_PASSWORD') }}"
aw_windows_repo_root: "{{ playbook_dir | dirname }}"
aw_windows_deploy_root: "C:\\Program Files\\AWatch-rus"
aw_windows_server_scheme: "http"
aw_windows_server_host: "10.10.10.13"
aw_windows_server_port: 5600
aw_windows_package_version: "v0.13.2"
aw_windows_package_url: "https://github.com/ActivityWatch/activitywatch/releases/download/v0.13.2/activitywatch-v0.13.2-windows-x86_64.zip"
aw_windows_package_zip_path: ""
aw_windows_domain: "SHARKON2025"
aw_windows_users:
- user1
- user2
- user3
- user4
- user5
aw_windows_extra_users: []
aw_windows_install_root: "C:\\Program Files\\AWatch-rus\\bin"
aw_windows_state_root: "C:\\ProgramData\\AWatch-rus"
aw_windows_hostname_override: ""
aw_windows_afk_enabled: true
aw_windows_window_enabled: true
aw_windows_file_ops_enabled: true
aw_windows_email_collector_enabled: true
aw_windows_email_collector_mode: "both"
aw_windows_local_agent_logs_enabled: false
aw_windows_incident_capture_enabled: true
aw_windows_incident_screenshot_enabled: true
aw_windows_incident_artifacts_root: "{{ aw_windows_state_root }}\\incident-artifacts"
aw_windows_logon_marker_enabled: true
aw_windows_skip_hardening: false
aw_windows_rules_path: "{{ aw_windows_deploy_root }}\\windows\\web-category-rules.example.json"
aw_windows_policy_path: "{{ aw_windows_deploy_root }}\\windows\\dlp-policy.example.json"
aw_windows_validation_remote_path: "{{ aw_windows_state_root }}\\aw_validate_ansible.json"
aw_windows_validation_local_dir: "/tmp/aw-rus-validation"
aw_windows_fail_on_validation_error: true
aw_windows_migration_enabled: false
aw_windows_legacy_install_root: "C:\\Program Files\\ActivityWatch-Phase2"
aw_windows_legacy_state_root: "C:\\ProgramData\\ActivityWatch-Phase2"
aw_windows_migration_report_remote_path: "{{ aw_windows_state_root }}\\aw_migration_ansible.json"
aw_windows_api_smoke_check_enabled: true
aw_windows_api_smoke_check_bucket: ""
aw_windows_api_smoke_check_limit: 10
+25 -6
View File
@@ -1,7 +1,11 @@
aw_windows_repo_root: "/home/igor/tmp/AWatch-rus"
aw_windows_deploy_root: "C:\\Deploy\\AWatch-rus"
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
@@ -14,11 +18,13 @@ aw_windows_extra_users: []
# aw_windows_extra_users:
# - Администратор
# Рекомендуемый изолированный профиль для фазового раската.
aw_windows_install_root: "C:\\Program Files\\ActivityWatch-Phase2"
aw_windows_state_root: "C:\\ProgramData\\ActivityWatch"
# Единые Windows/RDP пути: те же, что использует InnoSetup.
aw_windows_install_root: "C:\\Program Files\\AWatch-rus\\bin"
aw_windows_state_root: "C:\\ProgramData\\AWatch-rus"
aw_windows_hostname_override: "" # Например: SHARKON2025
aw_windows_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
@@ -29,5 +35,18 @@ aw_windows_skip_hardening: false
aw_windows_rules_path: "{{ aw_windows_deploy_root }}\\windows\\web-category-rules.example.json"
aw_windows_policy_path: "{{ aw_windows_deploy_root }}\\windows\\dlp-policy.example.json"
aw_windows_validation_remote_path: "C:\\Windows\\Temp\\aw_validate_phase2_ansible.json"
aw_windows_validation_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
# Безопасная миграция текущего прода со старых путей в единый профиль AWatch-rus.
aw_windows_migration_enabled: true
aw_windows_legacy_install_root: "C:\\Program Files\\ActivityWatch-Phase2"
aw_windows_legacy_state_root: "C:\\ProgramData\\ActivityWatch-Phase2"
aw_windows_migration_report_remote_path: "{{ aw_windows_state_root }}\\aw_migration_ansible.json"
# По умолчанию AFK bucket вычисляется как aw-watcher-afk_<COMPUTERNAME>.
# Задайте явное значение только если watcher пишет в нестандартный bucket.
aw_windows_api_smoke_check_enabled: true
aw_windows_api_smoke_check_bucket: ""
aw_windows_api_smoke_check_limit: 10
+10 -10
View File
@@ -1,16 +1,16 @@
---
# Full-stack installer for AWatch-rus.
# Runs end-to-end rollout in one command:
# 1) Proxmox CT provision + AW bootstrap (if [proxmox] exists in inventory)
# 2) AW server deploy on [aw_server] hosts
# 3) Windows phase2 rollout on [aw_windows] hosts
# 4) pfSense poller deploy on [aw_pfsense_pollers] hosts
# Полный установщик AWatch-rus.
# Выполняет развёртывание одной командой:
# 1) создание Proxmox CT + bootstrap AW (если в inventory есть [proxmox])
# 2) развёртывание AW server на хостах [aw_server]
# 3) развёртывание Windows/RDP collector'ов на [aw_windows]
# 4) развёртывание pfSense poller'а на [aw_pfsense_pollers]
#
# Notes:
# - Keep only relevant inventory groups filled for your environment.
# - Plays with unmatched host groups are skipped automatically by Ansible.
# Примечания:
# - Заполняйте только нужные группы inventory для своего окружения.
# - Play без совпадающих host groups Ansible пропускает автоматически.
- import_playbook: provision_proxmox_ct_and_deploy_aw.yml
- import_playbook: deploy_aw_server.yml
- import_playbook: deploy_aw_windows_phase2.yml
- import_playbook: deploy_aw_windows.yml
- import_playbook: deploy_aw_pfsense_poller.yml
+5 -2
View File
@@ -5,5 +5,8 @@ pve-main ansible_host=192.168.10.2 ansible_user=root ansible_port=22
aw-ct ansible_host=10.20.30.13 ansible_user=root ansible_port=22
[aw_windows]
# NOTE: in RU-localized installs this account is often "Администратор" instead of "Administrator".
win-node1 ansible_host=192.168.100.21 ansible_user=Администратор ansible_password=CHANGE_ME ansible_connection=winrm ansible_winrm_transport=ntlm ansible_port=5985 ansible_winrm_server_cert_validation=ignore
# Примечание: в русифицированных Windows часто нужен "Администратор", а не "Administrator".
win-node1 ansible_host=192.168.100.21 ansible_user=Администратор ansible_connection=winrm ansible_winrm_transport=ntlm ansible_port=5985 ansible_winrm_server_cert_validation=ignore
[aw_pfsense_pollers]
# pfsense-poller1 ansible_host=192.168.100.30 ansible_user=root ansible_port=22
+5
View File
@@ -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_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"
@@ -1,5 +1,5 @@
---
- name: Provision single Proxmox CT and deploy AWatch-rus
- name: Создать один Proxmox CT и развернуть AWatch-rus
hosts: proxmox
gather_facts: false
@@ -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
@@ -17,7 +23,7 @@
- settings/views-default.json
tasks:
- name: Execute single-CT provisioning workflow
- name: Выполнить workflow создания одного CT
ansible.builtin.include_tasks: tasks/provision_ct_and_deploy_aw.yml
vars:
ct_id: "{{ proxmox_ct_id }}"
@@ -1,5 +1,5 @@
---
- name: Provision Proxmox CT matrix and deploy AWatch-rus with RU patch
- name: Создать матрицу Proxmox CT и развернуть AWatch-rus с RU patch
hosts: proxmox
gather_facts: false
@@ -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
@@ -17,14 +23,14 @@
- settings/views-default.json
tasks:
- name: Validate CT matrix is provided
- name: Проверить, что матрица CT задана
ansible.builtin.assert:
that:
- proxmox_ct_matrix is defined
- proxmox_ct_matrix | length > 0
fail_msg: "Define proxmox_ct_matrix in group_vars/proxmox-matrix.yml"
fail_msg: "Задайте proxmox_ct_matrix в group_vars/proxmox-matrix.yml"
- name: Execute provisioning workflow for each CT
- name: Выполнить workflow создания для каждого CT
ansible.builtin.include_tasks: tasks/provision_ct_and_deploy_aw.yml
vars:
ct_id: "{{ item.id }}"
+35 -23
View File
@@ -1,5 +1,5 @@
---
- name: Validate required per-CT variables
- name: Проверить обязательные переменные CT
ansible.builtin.assert:
that:
- ct_id is defined
@@ -27,14 +27,14 @@
- aw_server_log_dir is defined
- aw_server_user is defined
- aw_server_group is defined
fail_msg: "Missing required variables for CT provisioning/deploy."
fail_msg: "Не заданы обязательные переменные для создания CT и развёртывания."
- name: Build CT network string
- name: Сформировать сетевую строку CT
ansible.builtin.set_fact:
ct_net0: >-
name=eth0,bridge={{ ct_bridge }},ip={{ ct_ip }},gw={{ ct_gw }}{% if (ct_vlan | default('') | string | length) > 0 %},tag={{ ct_vlan }}{% endif %}
- name: Check whether CT already exists
- name: Проверить, существует ли CT
ansible.builtin.command:
argv:
- pct
@@ -44,7 +44,7 @@
failed_when: false
changed_when: false
- name: Create CT when absent
- name: Создать CT, если он отсутствует
ansible.builtin.command:
argv:
- pct
@@ -78,8 +78,9 @@
- --ostype
- debian
when: ct_status_check.rc != 0
no_log: true
- name: Check current CT runtime state
- name: Проверить текущее состояние CT
ansible.builtin.command:
argv:
- pct
@@ -88,7 +89,7 @@
register: ct_runtime_status
changed_when: false
- name: Start CT when stopped
- name: Запустить CT, если он остановлен
ansible.builtin.command:
argv:
- pct
@@ -96,20 +97,23 @@
- "{{ ct_id }}"
when: "'stopped' in ct_runtime_status.stdout"
- name: Ensure bootstrap directory on Proxmox host
- name: Создать bootstrap каталог на Proxmox host
ansible.builtin.file:
path: "{{ proxmox_bootstrap_dir }}"
path: "{{ item }}"
state: directory
mode: "0700"
loop:
- "{{ proxmox_bootstrap_dir }}"
- "{{ proxmox_bootstrap_dir }}/settings"
- name: Copy AW bootstrap files to Proxmox host temp
- name: Скопировать AW bootstrap файлы во временный каталог Proxmox host
ansible.builtin.copy:
src: "{{ aw_repo_root }}/aw-server/{{ item }}"
dest: "{{ proxmox_bootstrap_dir }}/{{ item }}"
mode: "0644"
loop: "{{ aw_bootstrap_files }}"
- name: Bootstrap CT OS dependencies
- name: Установить базовые зависимости ОС внутри CT
ansible.builtin.command:
argv:
- pct
@@ -122,12 +126,16 @@
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y curl ca-certificates bash unzip xz-utils jq rsync openssh-server
mkdir -p /root/bootstrap /etc/activitywatch
apt-get install -y curl ca-certificates bash unzip xz-utils jq rsync openssh-server python3
mkdir -p /root/bootstrap/settings /etc/activitywatch
systemctl enable ssh || true
systemctl restart ssh || true
register: ct_bootstrap_result
retries: 10
delay: 6
until: ct_bootstrap_result.rc == 0
- name: Push bootstrap files into CT
- name: Передать bootstrap файлы внутрь CT
ansible.builtin.command:
argv:
- pct
@@ -137,7 +145,7 @@
- "/root/bootstrap/{{ item }}"
loop: "{{ aw_bootstrap_files }}"
- name: Write AW server env file on Proxmox host temp
- name: Записать AW server env во временный каталог Proxmox host
ansible.builtin.copy:
dest: "{{ proxmox_bootstrap_dir }}/aw-server.env"
mode: "0600"
@@ -151,8 +159,11 @@
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: Push AW server env into CT
- name: Передать AW server env внутрь CT
ansible.builtin.command:
argv:
- pct
@@ -160,8 +171,9 @@
- "{{ ct_id }}"
- "{{ proxmox_bootstrap_dir }}/aw-server.env"
- /etc/activitywatch/aw-server.env
no_log: true
- name: Set mode for env inside CT
- name: Настроить права env файла внутри CT
ansible.builtin.command:
argv:
- pct
@@ -172,7 +184,7 @@
- "0600"
- /etc/activitywatch/aw-server.env
- name: Install server and apply RU patch inside CT
- name: Установить сервер и применить RU patch внутри CT
ansible.builtin.command:
argv:
- pct
@@ -188,7 +200,7 @@
bash /root/bootstrap/apply_webui_ru_patch.sh
systemctl restart activitywatch-server.service
- name: Validate AW API from inside CT
- name: Проверить AW API изнутри CT
ansible.builtin.command:
argv:
- pct
@@ -199,7 +211,7 @@
- -lc
- "curl -fsS http://127.0.0.1:{{ aw_server_port }}/api/0/info >/dev/null"
- name: Validate RU patch hooks in index
- name: Проверить hooks RU patch в index.html
ansible.builtin.command:
argv:
- pct
@@ -210,8 +222,8 @@
- -lc
- "grep -q 'ru-patch-v5.js' {{ aw_server_webui_dir }}/index.html && grep -q 'sw-cleanup.js' {{ aw_server_webui_dir }}/index.html"
- name: Show final endpoint
- name: Показать итоговый endpoint
ansible.builtin.debug:
msg:
- "CT {{ ct_id }} is provisioned and configured."
- "ActivityWatch endpoint: http://{{ ct_ip | regex_replace('/[0-9]+$', '') }}:{{ aw_server_port }}"
- "CT {{ ct_id }} создан и настроен."
- "Endpoint ActivityWatch: http://{{ ct_ip | regex_replace('/[0-9]+$', '') }}:{{ aw_server_port }}"
+2 -2
View File
@@ -9,7 +9,7 @@ EnvironmentFile=/etc/activitywatch/aw-server.env
User=__AW_SERVER_USER__
Group=__AW_SERVER_GROUP__
WorkingDirectory=__AW_SERVER_DATA_DIR__
ExecStart=/bin/sh -lc 'exec /opt/activitywatch/bin/aw-server-rust --host "$AW_SERVER_BIND_HOST" --port "$AW_SERVER_PORT"'
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)"
+15
View File
@@ -29,6 +29,21 @@
{ "label": "DLP", "type": "bucket", "bucket_prefix": "aw-dlp-endpoint-signals_" }
]
},
{
"id": "linux-remote",
"name": "Linux remote workers",
"description": "Linux-хосты удалённых сотрудников: GUI активность, SSH/console и browser admin UI.",
"patterns": [
"^(LINUX-WS|LINUX-DESKTOP|LX-|DESKTOP-|ADMIN-|WORKSTATION-|DEVBOX-)"
],
"links": [
{ "label": "Активность", "type": "activity" },
{ "label": "SSH сессии", "type": "bucket", "bucket_prefix": "aw-ssh-sessions_" },
{ "label": "Команды shell", "type": "bucket", "bucket_prefix": "aw-console-commands_" },
{ "label": "Web категории", "type": "bucket", "bucket_prefix": "aw-detmir-web-category_" },
{ "label": "Все бакеты", "type": "buckets" }
]
},
{
"id": "virtual-infra",
"name": "Virtual servers + Proxmox",
+90 -164
View File
@@ -1,6 +1,6 @@
(function () {
window.__awRuPatchVersion = "template-v13-pve-audit-day-filter";
document.documentElement.setAttribute("data-aw-ru-patch", "template-v13-pve-audit-day-filter");
window.__awRuPatchVersion = "template-v12-activity-heading-ru";
document.documentElement.setAttribute("data-aw-ru-patch", "template-v12-activity-heading-ru");
const exact = new Map([
["ActivityWatch", "АктивВотч"],
@@ -333,11 +333,6 @@
'.aw-ru-host-item-title { font-weight: 600; margin-bottom: 6px; }',
'.aw-ru-host-links { display: flex; flex-wrap: wrap; gap: 6px; }',
'.aw-ru-host-links a { display: inline-block; padding: 4px 8px; border-radius: 999px; background: rgba(90,140,255,.15); text-decoration: none; }',
'.aw-ru-worktime-center { margin: 16px 0; padding: 16px; border: 1px solid rgba(120,120,120,.35); border-radius: 8px; background: rgba(20,20,20,.03); }',
'.aw-ru-worktime-controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: end; margin-bottom: 10px; }',
'.aw-ru-worktime-controls label { font-size: 12px; display:flex; flex-direction:column; gap:4px; }',
'.aw-ru-worktime-table { width: 100%; border-collapse: collapse; font-size: 13px; }',
'.aw-ru-worktime-table th, .aw-ru-worktime-table td { border: 1px solid rgba(120,120,120,.25); padding: 6px 8px; text-align: left; }',
'.aw-ru-pve-audit { margin: 16px 0; padding: 16px; border: 1px solid rgba(120,120,120,.35); border-radius: 8px; background: rgba(10,20,40,.04); }',
'.aw-ru-pve-audit-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; margin: 12px 0 16px; }',
'.aw-ru-pve-audit-card { border: 1px solid rgba(120,120,120,.22); border-radius: 8px; padding: 12px; background: rgba(255,255,255,.02); }',
@@ -375,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);
@@ -391,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);
}
@@ -685,6 +690,19 @@
{ label: "DLP", type: "bucket", bucket_prefix: "aw-dlp-endpoint-signals_" }
]
},
{
id: "linux-remote",
name: "Linux remote workers",
description: "Linux-хосты удалённых сотрудников: GUI активность, SSH/console и browser admin UI.",
patterns: ["^(LINUX-WS|LINUX-DESKTOP|LX-|DESKTOP-|ADMIN-|WORKSTATION-|DEVBOX-)"],
links: [
{ label: "Активность", type: "activity" },
{ label: "SSH сессии", type: "bucket", bucket_prefix: "aw-ssh-sessions_" },
{ label: "Команды shell", type: "bucket", bucket_prefix: "aw-console-commands_" },
{ label: "Web категории", type: "bucket", bucket_prefix: "aw-detmir-web-category_" },
{ label: "Все бакеты", type: "buckets" }
]
},
{
id: "virtual-infra",
name: "Virtual servers + Proxmox",
@@ -740,29 +758,20 @@
return !!(match && isPveLikeHost(decodeURIComponent(match[1] || "")));
}
function getSelectedDayFromHash() {
const hash = window.location.hash || "";
const match = hash.match(/^#\/activity\/[^/]+\/day\/([^/?#]+)/i);
return match && match[1] ? decodeURIComponent(match[1]) : "";
}
function filterEventsBySelectedDay(events) {
const day = getSelectedDayFromHash();
if (!day) return Array.isArray(events) ? events : [];
const start = new Date(day + "T00:00:00");
const end = new Date(day + "T23:59:59.999");
return (Array.isArray(events) ? events : []).filter(function (event) {
const timestamp = event && event.timestamp ? new Date(event.timestamp) : null;
return timestamp && !isNaN(timestamp.getTime()) && timestamp >= start && timestamp <= end;
});
}
function extractHostFromBucket(bucketId, bucketMeta) {
if (bucketMeta && bucketMeta.hostname) return String(bucketMeta.hostname);
const prefixes = [
"aw-watcher-window_",
"aw-watcher-afk_",
"aw-console-commands_",
"aw-ssh-sessions_",
"aw-linux-web-context_",
"aw-detmir-web-category_",
"aw-dlp-endpoint-signals_",
"aw-session-events_",
"aw-worktime-sessions_",
"aw-pve-webadmin-events_",
"aw-pve-task-events_",
"aw-dlp-incidents_",
"aw-pfsense-health_",
"aw-pfsense-gateways_",
@@ -792,7 +801,27 @@
return result;
}
function matchHostGroup(host, groups) {
function hostHasBucketPrefix(hostBuckets, prefix) {
return (hostBuckets || []).some(function (bucketId) {
return String(bucketId || "").indexOf(prefix) === 0;
});
}
function matchHostGroup(host, groups, hostBuckets) {
const bucketList = hostBuckets || [];
if (hostHasBucketPrefix(bucketList, "aw-dlp-endpoint-signals_") || hostHasBucketPrefix(bucketList, "aw-session-events_")) {
return "windows-rdp";
}
if (
hostHasBucketPrefix(bucketList, "aw-console-commands_") ||
hostHasBucketPrefix(bucketList, "aw-ssh-sessions_") ||
hostHasBucketPrefix(bucketList, "aw-linux-web-context_") ||
hostHasBucketPrefix(bucketList, "aw-detmir-web-category_")
) {
if (!hostHasBucketPrefix(bucketList, "aw-pve-webadmin-events_") && !hostHasBucketPrefix(bucketList, "aw-pve-task-events_")) {
return "linux-remote";
}
}
for (const group of groups) {
const patterns = Array.isArray(group.patterns) ? group.patterns : [];
for (const pattern of patterns) {
@@ -835,7 +864,7 @@
grouped.set("__ungrouped__", []);
Array.from(hostBuckets.keys()).sort().forEach(function (host) {
const groupId = matchHostGroup(host, groups) || "__ungrouped__";
const groupId = matchHostGroup(host, groups, hostBuckets.get(host) || []) || "__ungrouped__";
grouped.get(groupId).push(host);
});
@@ -891,7 +920,7 @@
center.setAttribute("data-aw-ru-host-groups", "1");
center.innerHTML =
'<h4>Разделы хостов</h4>' +
'<p>Здесь хосты разделены на пользовательские Windows RDP и инфраструктурные виртуальные серверы/Proxmox.</p>' +
'<p>Здесь хосты разделены на Windows RDP, Linux remote workers и инфраструктурные узлы.</p>' +
'<div class="aw-ru-host-groups-grid" data-aw-ru-host-groups-grid><section class="aw-ru-host-group-card"><p>Загрузка...</p></section></div>';
heading.parentElement.insertBefore(center, heading.nextSibling);
}
@@ -1328,10 +1357,10 @@
loadBucketEvents("aw-console-commands_" + host, 50).catch(function () { return []; })
]);
const data = {
web: filterEventsBySelectedDay(webEvents || []),
tasks: filterEventsBySelectedDay(taskEvents || []),
ssh: filterEventsBySelectedDay(sshEvents || []),
cmd: filterEventsBySelectedDay(cmdEvents || [])
web: webEvents || [],
tasks: taskEvents || [],
ssh: sshEvents || [],
cmd: cmdEvents || []
};
center.querySelector("[data-aw-ru-pve-web-count]").textContent = String(data.web.length);
center.querySelector("[data-aw-ru-pve-task-count]").textContent = String(data.tasks.length);
@@ -1447,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) {
@@ -1514,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() {
@@ -1541,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)
});
@@ -1565,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) {
@@ -1606,124 +1651,6 @@
});
}
function ms(v) {
return new Date(v).getTime();
}
function fmtHours(sec) {
return (Math.round((sec / 3600) * 100) / 100).toFixed(2);
}
function rangesIntersect(a0, a1, b0, b1) {
return Math.max(a0, b0) < Math.min(a1, b1);
}
function intersectDurationSeconds(a0, a1, b0, b1) {
return Math.max(0, (Math.min(a1, b1) - Math.max(a0, b0)) / 1000);
}
async function renderPersonalWorktime(host, fromIso, toIso, target) {
const sessionBucket = "aw-worktime-sessions_" + host;
const windowBucket = "aw-watcher-window_" + host;
const afkBucket = "aw-watcher-afk_" + host;
const [sessionEvents, windowEvents, afkEvents] = await Promise.all([
loadBucketEvents(sessionBucket, 5000).catch(function () { return []; }),
loadBucketEvents(windowBucket, 10000).catch(function () { return []; }),
loadBucketEvents(afkBucket, 10000).catch(function () { return []; })
]);
const fromMs = ms(fromIso);
const toMs = ms(toIso);
const userRanges = {};
(sessionEvents || []).forEach(function (e) {
const d = e && e.data ? e.data : {};
const u = String(d.username || "").trim();
if (!u) return;
const t0 = ms(e.timestamp);
const t1 = t0 + Math.max(1, Number(e.duration || 0)) * 1000;
if (!rangesIntersect(t0, t1, fromMs, toMs)) return;
userRanges[u] = userRanges[u] || [];
userRanges[u].push([t0, t1]);
});
const activeRanges = [];
const afkByTs = new Map();
(afkEvents || []).forEach(function (e) { afkByTs.set(String(e.timestamp || ""), e); });
(windowEvents || []).forEach(function (e) {
const t0 = ms(e.timestamp);
const t1 = t0 + Math.max(1, Number(e.duration || 0)) * 1000;
if (!rangesIntersect(t0, t1, fromMs, toMs)) return;
const afk = afkByTs.get(String(e.timestamp || ""));
const status = String(afk && afk.data && afk.data.status || "");
if (status && status.toLowerCase() === "afk") return;
activeRanges.push([t0, t1]);
});
const rows = Object.keys(userRanges).sort().map(function (u) {
let presentSec = 0;
let activeSec = 0;
userRanges[u].forEach(function (r) {
presentSec += intersectDurationSeconds(r[0], r[1], fromMs, toMs);
activeRanges.forEach(function (a) {
activeSec += intersectDurationSeconds(r[0], r[1], a[0], a[1]);
});
});
const idleSec = Math.max(0, presentSec - activeSec);
return { user: u, presentSec: presentSec, activeSec: activeSec, idleSec: idleSec };
});
if (!rows.length) {
target.innerHTML = '<div class="aw-ru-pve-audit-muted">Нет данных в aw-worktime-sessions_' + host + ' за выбранный интервал.</div>';
return;
}
target.innerHTML = '<table class="aw-ru-worktime-table"><thead><tr><th>Пользователь</th><th>Активно, ч</th><th>В сессии, ч</th><th>Простой, ч</th></tr></thead><tbody>' +
rows.map(function (r) {
return '<tr><td>' + r.user + '</td><td>' + fmtHours(r.activeSec) + '</td><td>' + fmtHours(r.presentSec) + '</td><td>' + fmtHours(r.idleSec) + '</td></tr>';
}).join("") +
'</tbody></table>';
}
async function injectPersonalWorktimeCenter(root) {
const isActivity = /^#\/activity\//i.test(window.location.hash || "");
if (!isActivity) return;
if (root.querySelector(".aw-ru-worktime-center")) return;
const host = getCurrentHostFromHash();
if (!host) return;
const container = document.createElement("div");
container.className = "aw-ru-worktime-center";
const now = new Date();
const day = now.toISOString().slice(0, 10);
container.innerHTML =
'<h3 style="margin-top:0">Персональный учёт рабочего времени</h3>' +
'<div class="aw-ru-worktime-controls">' +
'<label>Дата <input type="date" data-aw-wt-date value="' + day + '"></label>' +
'<label>С <input type="time" data-aw-wt-from value="09:00"></label>' +
'<label>По <input type="time" data-aw-wt-to value="18:00"></label>' +
'<button type="button" data-aw-wt-run>Рассчитать</button>' +
'</div>' +
'<div data-aw-wt-result class="aw-ru-pve-audit-muted">Нажмите "Рассчитать".</div>';
root.insertBefore(container, root.firstChild);
const run = async function () {
const date = container.querySelector("[data-aw-wt-date]").value;
const tFrom = container.querySelector("[data-aw-wt-from]").value || "09:00";
const tTo = container.querySelector("[data-aw-wt-to]").value || "18:00";
const result = container.querySelector("[data-aw-wt-result]");
result.textContent = "Расчёт...";
const fromIso = new Date(date + "T" + tFrom + ":00").toISOString();
const toIso = new Date(date + "T" + tTo + ":00").toISOString();
try {
await renderPersonalWorktime(host, fromIso, toIso, result);
} catch (e) {
result.textContent = "Ошибка расчёта: " + String(e.message || e);
}
};
container.querySelector("[data-aw-wt-run]").addEventListener("click", function () { run(); });
}
function applyPatch() {
enforceSafeActivityViewForPveHost();
ensureSettingsHost();
@@ -1739,7 +1666,6 @@
injectDlpNavigation(document.body);
injectDlpReviewCenter(document.body);
injectDlpAlertsCenter(document.body);
injectPersonalWorktimeCenter(document.body).catch(function () {});
injectHostGroupsCenter(document.body).catch(function () {});
redirectBareTrendsRoute();
}
+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
+4 -4
View File
@@ -20,7 +20,7 @@
"name": ["Работа", "Документы"],
"rule": {
"type": "regex",
"regex": "\\b(winword|excel|powerpnt|outlook|acrord32|acrord64)\\.exe\\b|Adobe Reader|Acrobat",
"regex": "\\b(winword|excel|powerpnt|outlook|acrord32|acrord64|libreoffice|writer|calc)\\.exe\\b|LibreOffice|OnlyOffice|Adobe Reader|Acrobat",
"ignore_case": true
},
"data": { "color": "#2E7D32" }
@@ -40,7 +40,7 @@
"name": ["Работа", "Администрирование"],
"rule": {
"type": "regex",
"regex": "\\b(mstsc|putty|kitty|winscp|anydesk|teamviewer|vncviewer|mmc|regedit|services|control|powershell|cmd)\\.exe\\b",
"regex": "\\b(mstsc|putty|kitty|winscp|anydesk|teamviewer|vncviewer|mmc|regedit|services|control|powershell|cmd|gnome-terminal|gnome-terminal-server|xfce4-terminal|konsole|tilix|alacritty|xterm|remmina|virt-manager)\\.exe\\b|\\b(gnome-terminal|gnome-terminal-server|xfce4-terminal|konsole|tilix|alacritty|xterm|remmina|virt-manager)\\b|Proxmox Virtual Environment|\\bpfSense\\b|\\bGrafana\\b|\\bKibana\\b|\\bPortainer\\b",
"ignore_case": true
},
"data": { "color": "#6D4C41" }
@@ -56,7 +56,7 @@
"name": ["Интернет", "Браузер"],
"rule": {
"type": "regex",
"regex": "\\b(chrome|msedge|firefox|opera|brave|vivaldi|browser)\\.exe\\b",
"regex": "\\b(chrome|msedge|firefox|opera|brave|vivaldi|browser|chromium)\\.exe\\b|\\b(chrome|chromium|firefox|opera|brave|vivaldi)\\b",
"ignore_case": true
},
"data": { "color": "#00897B" }
@@ -82,7 +82,7 @@
"name": ["ActivityWatch"],
"rule": {
"type": "regex",
"regex": "ActivityWatch|\\baw-(watcher|qt)\\.exe\\b",
"regex": "ActivityWatch|\\baw-(watcher|qt)\\.exe\\b|\\baw-(watcher|qt)\\b",
"ignore_case": true
},
"data": {}
+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')"
+32 -19
View File
@@ -21,7 +21,7 @@
- `/home/igor/tmp/AWatch-rus/ansible/deploy_aw_server.yml`
- `/home/igor/tmp/AWatch-rus/ansible/provision_proxmox_ct_and_deploy_aw.yml`
- `/home/igor/tmp/AWatch-rus/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml`
- `/home/igor/tmp/AWatch-rus/ansible/deploy_aw_windows_phase2.yml`
- `/home/igor/tmp/AWatch-rus/ansible/deploy_aw_windows.yml`
---
@@ -177,7 +177,7 @@ grep -n 'aw-ru-patch\|aw-sw-cleanup' /opt/activitywatch/webui-ru/index.html
например в:
- `C:\Deploy\ActivityWatch-Russian\windows`
- `C:\Program Files\AWatch-rus\windows`
Откройте **elevated PowerShell**:
@@ -187,15 +187,28 @@ Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process
### 3.2 Массовое доменное развёртывание (рекомендуется)
Если текущий production ещё работает в старых каталогах
`C:\Program Files\ActivityWatch-Phase2` и `C:\ProgramData\ActivityWatch-Phase2`,
сначала выполните безопасную миграцию:
```powershell
C:\Program Files\AWatch-rus\windows\migrate-awatch-rus-paths.ps1 -WhatIf
C:\Program Files\AWatch-rus\windows\migrate-awatch-rus-paths.ps1
```
Скрипт остановит `ActivityWatch Recovery`/`ActivityWatch Launch *`, создаст backup в
`C:\ProgramData\AWatch-rus\migration-backups\...`, перенесёт файлы в единые пути,
пересоздаст `deployment-config.json`/scheduled tasks и запустит validation.
Пример со списком пользователей:
```powershell
C:\Deploy\ActivityWatch-Russian\windows\deploy-domain-users.ps1 `
C:\Program Files\AWatch-rus\windows\deploy-domain-users.ps1 `
-ServerHost aw.example.local `
-ServerPort 5600 `
-Domain CONTOSO `
-UserListPath C:\Deploy\aw-users.txt `
-CustomRulesPath C:\Deploy\ActivityWatch-Russian\windows\web-category-rules.example.json
-CustomRulesPath C:\Program Files\AWatch-rus\windows\web-category-rules.example.json
```
Поддерживаемые варианты:
@@ -207,7 +220,7 @@ C:\Deploy\ActivityWatch-Russian\windows\deploy-domain-users.ps1 `
### 3.2.1 Ensemble orchestration (рекомендуется для production)
```powershell
C:\Deploy\ActivityWatch-Russian\windows\deploy-ensemble.ps1 `
C:\Program Files\AWatch-rus\windows\deploy-ensemble.ps1 `
-ServerHost aw.example.local `
-ServerPort 5600 `
-Domain CONTOSO `
@@ -217,30 +230,30 @@ C:\Deploy\ActivityWatch-Russian\windows\deploy-ensemble.ps1 `
Отчёт сохраняется в:
- `C:\ProgramData\ActivityWatch\ensemble-report-YYYYMMDD-HHMMSS.json`
- `C:\ProgramData\AWatch-rus\ensemble-report-YYYYMMDD-HHMMSS.json`
### 3.3 Single-user развёртывание
```powershell
C:\Deploy\ActivityWatch-Russian\windows\deploy-single-user.ps1 `
C:\Program Files\AWatch-rus\windows\deploy-single-user.ps1 `
-ServerHost aw.example.local `
-ServerPort 5600 `
-TargetUser 'CONTOSO\user01' `
-CustomRulesPath C:\Deploy\ActivityWatch-Russian\windows\web-category-rules.example.json
-CustomRulesPath C:\Program Files\AWatch-rus\windows\web-category-rules.example.json
```
### 3.4 Recovery / hardening
```powershell
C:\Deploy\ActivityWatch-Russian\windows\hardening-recovery.ps1 `
-ConfigPath C:\ProgramData\ActivityWatch\deployment-config.json
C:\Program Files\AWatch-rus\windows\hardening-recovery.ps1 `
-ConfigPath C:\ProgramData\AWatch-rus\deployment-config.json
```
### 3.5 Валидация deployment-а (PowerShell report)
```powershell
$report = C:\Deploy\ActivityWatch-Russian\windows\validate-deployment.ps1 `
-ConfigPath C:\ProgramData\ActivityWatch\deployment-config.json
$report = C:\Program Files\AWatch-rus\windows\validate-deployment.ps1 `
-ConfigPath C:\ProgramData\AWatch-rus\deployment-config.json
$report | ConvertTo-Json -Depth 12
```
@@ -248,13 +261,13 @@ $report | ConvertTo-Json -Depth 12
## 4) Что должно появиться на Windows после установки
- `C:\Program Files\ActivityWatch`
- `C:\ProgramData\ActivityWatch\deployment-config.json`
- `C:\ProgramData\ActivityWatch\launch-watchers.ps1`
- `C:\ProgramData\ActivityWatch\recovery-loop.ps1`
- `C:\ProgramData\ActivityWatch\browser-domains-native-collector.ps1`
- `C:\ProgramData\ActivityWatch\web-category-rules.json`
- `C:\ProgramData\ActivityWatch\logs\`
- `C:\Program Files\AWatch-rus\bin`
- `C:\ProgramData\AWatch-rus\deployment-config.json`
- `C:\ProgramData\AWatch-rus\launch-watchers.ps1`
- `C:\ProgramData\AWatch-rus\recovery-loop.ps1`
- `C:\ProgramData\AWatch-rus\browser-domains-native-collector.ps1`
- `C:\ProgramData\AWatch-rus\web-category-rules.json`
- `C:\ProgramData\AWatch-rus\logs\`
Задачи планировщика:
+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 строит графики
```
+50
View File
@@ -0,0 +1,50 @@
# Artifacts Policy
## Purpose
Define which files are source-of-truth and which are generated runtime/research artifacts that must not block or pollute production rollouts.
## Source of Truth
Tracked and reviewable:
- `ansible/`
- `aw-server/`
- `windows/`
- `scripts/`
- `docs/`
- install-kit templates and manifests under `windows/installkit/innosetup/`
## Generated / Volatile Artifacts
Not for production commits:
- `.graphify_*` cache/analysis outputs
- `graphify-out/cache/*`
- `graphify-out/shellcheck-*.txt`
- `graphify-out/validate_dryrun_out*.txt`
- `graphify-out/powershell-parse-results*.json`
- `graphify-out/powershell-pssa-warn-results.json`
- `graphify-out/pssa_diffs.txt`
- `reports/*`
- `tmp/*`
These paths are ignored by `.gitignore` and additionally guarded by `scripts/quality-gate.sh`.
## Rollout Gate
`scripts/prod_rollout.sh` must run only when:
1. `AW_MAINTENANCE_ACK=YES` is set.
2. `scripts/quality-gate.sh` passes.
3. Preflight checks pass:
- `ansible ping`/`win_ping`
- `./check-aw-data.sh`
- `./check-aw-full.sh`
If any gate fails, rollout stops.
## Notes
- Secrets policy remains temporary by operator choice; credentials may still exist in local `inventory.ini` during this phase.
- Dedicated secrets hardening (vault/env-only enforcement) is a separate follow-up track.
+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+
+105
View File
@@ -0,0 +1,105 @@
# 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.
- `aw-dlp-endpoint-signals_*` (`aw.dlp.endpoint.signal`) — endpoint signal heartbeats/events.
- `aw-email-monitor_*` (`aw.email.signal`) — outbound email signal stream.
## 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
+85
View File
@@ -0,0 +1,85 @@
# Linux remote worker deployment
## Назначение
Этот сценарий закрывает полный набор данных по Linux-удалёнщику:
- GUI active window и `afk` через `ActivityWatch`;
- SSH и shell-команды через console/ssh logger;
- браузерные админки по title-based правилам, включая Proxmox `https://...:8006`.
Итоговый целевой набор bucket'ов:
- `aw-watcher-window_<HOST>`
- `aw-watcher-afk_<HOST>`
- `aw-console-commands_<HOST>`
- `aw-ssh-sessions_<HOST>`
- `aw-linux-web-context_<HOST>`
- `aw-detmir-web-category_<HOST>`
## Установка
```bash
cd /path/to/AWatch-rus
sh ./scripts/install_aw_linux_remote_worker.sh \
--server-host 10.10.10.13 \
--server-port 5600
```
## Что ставится
1. `scripts/install_aw_linux_client.sh`
2. `scripts/install_aw_console_ssh_logger.sh`
3. `scripts/install_aw_linux_web_category_logger.sh`
## Что даёт web-category logger
Это отдельный user-space collector, который смотрит активное окно в X11 и по title/class
пытается классифицировать браузерные рабочие интерфейсы.
Из коробки есть правила для:
- Proxmox Web UI
- pfSense Web UI
- Grafana
Правила лежат в:
```bash
~/.config/aw-linux-web-category/rules.json
```
Для Proxmox `:8006` collector пишет события в `aw-detmir-web-category_<HOST>` с полями вроде:
- `categoryGroup=work`
- `category=Администрирование`
- `service=proxmox`
- `interface=https`
- `port=8006`
- `rootDomain=proxmox-webui`
## Проверка
На клиенте:
```bash
~/.local/bin/aw-console-ssh-logger-status
~/.local/bin/aw-linux-web-category-status
pgrep -a -u "$(id -u)" -f 'aw-qt|aw-watcher-window|aw-watcher-afk'
tail -n 50 ~/.local/state/aw-console-ssh-logger/logs/collector.log
tail -n 50 ~/.local/state/aw-linux-web-category/logs/collector.log
```
На AW server:
```bash
curl -fsS http://10.10.10.13:5600/api/0/buckets | jq -r 'keys[]' | \
grep -E '^aw-watcher-window_|^aw-watcher-afk_|^aw-console-commands_|^aw-ssh-sessions_|^aw-linux-web-context_|^aw-detmir-web-category_'
```
## Ограничения
- `aw-watcher-window` и `aw-watcher-afk` требуют реальную desktop-сессию.
- Web-category logger опирается на X11 active window title и `WM_CLASS`.
- Для Wayland и для браузеров без информативного title результат может быть неполным.
- Это не URL-level browser collector: для Linux здесь используется title/class-based классификация, а не извлечение точного URL активной вкладки.
+2
View File
@@ -17,6 +17,7 @@
- секреты не хранить в git;
- каждое изменение фиксировать в ticket/run log;
- публичную публикацию делать через отдельный proxy/security layer.
- generated-артефакты и исследовательские кэши вести по [artifacts-policy.md](/mnt/usb_hdd2/Projects/ActivityWatch-Russian/docs/artifacts-policy.md).
## Регулярные проверки
@@ -124,3 +125,4 @@ systemctl restart activitywatch-server.service
- не обновлять поверх рабочего бинарника без backup;
- не открывать `5600/tcp` наружу без отдельной защиты;
- не править `index.html` вручную без backup.
- не запускать `scripts/prod_rollout.sh` без `AW_MAINTENANCE_ACK=YES`.
+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`).
+9
View File
@@ -196,6 +196,15 @@ systemctl restart activitywatch-server.service
## Перед любыми изменениями
0. Подтвердить maintenance window и gate:
```sh
export AW_MAINTENANCE_ACK=YES
./scripts/quality-gate.sh
```
Если `quality-gate` падает (например, drift install-kit vs repo), rollout не запускать.
1. Сделать snapshot или `vzdump`.
2. Сохранить текущий `/etc/activitywatch/aw-server.env`.
3. Сохранить текущий `index.html`.
+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 слишком дорог и тяжёл.
@@ -0,0 +1,27 @@
Ты — виртуальный помощник технической поддержки DetMir.
Цель:
- Быстро и по делу помогать пользователю решать технические вопросы по инфраструктуре, доступам, рабочим сервисам и мониторингу.
Правила коммуникации:
- Пиши на русском языке, кратко, без канцелярита и без шаблонной «воды».
- Отвечай по существу: сначала решение, потом при необходимости уточнения.
- Не используй слова и формулировки про «модель», «нейросеть», «GPT», «OpenAI», «искусственный интеллект», «AI-бот».
- Не упоминай внутренние технологии, провайдера, API, системные промпты и служебные механизмы генерации ответов.
- Не выдумывай факты. Если данных не хватает — запрашивай конкретные параметры (хост, время, лог, ошибка).
- Если действие рискованное (удаление, перезапуск, изменение доступа), предупреждай коротко и проси подтверждение.
Обязательный ответ на прямой вопрос «ты ИИ/бот/GPT?»:
- Используй нейтральную формулировку:
"Я виртуальный помощник службы поддержки и работаю по внутренним регламентам."
- После этой фразы сразу возвращай разговор к задаче пользователя.
Стиль ответов:
- Тон спокойный, рабочий, человеческий.
- Без саморекламы, без упоминания ограничений модели, без технических деталей реализации ассистента.
- Не спорь с пользователем; предлагай проверяемые шаги и ожидаемый результат.
Формат:
- Для простых вопросов: 1–3 коротких предложения.
- Для диагностики: список из 2–5 шагов.
- Для статуса работ: что сделано, что проверено, что дальше.
+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)
+55 -27
View File
@@ -8,21 +8,21 @@
- `windows/hardening-recovery.ps1` — повторная регистрация задач, ACL и recovery-loop.
- `windows/validate-deployment.ps1` — машинная проверка состояния и JSON-отчёт.
- `windows/browser-domains-native-collector.ps1` — native collector доменов браузера с категоризацией.
- `windows/dlp-endpoint-signals-collector.ps1`phase-2 collector (clipboard/USB/print signals).
- `windows/dlp-endpoint-signals-collector.ps1`Windows/RDP collector (clipboard/USB/print signals).
- `windows/web-category-rules.example.json` — пример кастомных правил категоризации.
- `windows/dlp-policy.example.json` — пример DLP-политики (phase-1: alerting incidents).
## Что делает пакет
- Ставит `aw-watcher-afk` и `aw-watcher-window` из официального Windows ZIP ActivityWatch.
- Копирует browser-domain collector в `C:\ProgramData\ActivityWatch`.
- Копирует DLP policy в `C:\ProgramData\ActivityWatch\dlp-policy.json`.
- Копирует browser-domain collector в `C:\ProgramData\AWatch-rus`.
- Копирует DLP policy в `C:\ProgramData\AWatch-rus\dlp-policy.json`.
- Включает `incidentCapture` в `deployment-config.json` для DLP-инцидентов:
- `incidentCapture.screenshotEnabled = true`
- `incidentCapture.artifactsRoot = <StateRoot>\incident-artifacts`
- Создаёт per-user задачи `ActivityWatch Launch [...]` с запуском при логоне.
- Создаёт системную задачу `ActivityWatch Recovery`, которая циклически перезапускает per-user launch tasks.
- Применяет ACL к `C:\Program Files\ActivityWatch`, `C:\ProgramData\ActivityWatch` и каталогу логов.
- Применяет ACL к `C:\Program Files\AWatch-rus\bin`, `C:\ProgramData\AWatch-rus` и каталогу логов.
- Не содержит хардкодов инфраструктуры: сервер, домен, список пользователей и правила передаются параметрами.
- Корректно регистрирует задачи через `-LogonType Interactive` (совместимо с Windows Server, где `InteractiveToken` не поддерживается).
- Поддерживает отключение шумных watcher'ов через `-AfkEnabled:$false` и `-WindowEnabled:$false`.
@@ -143,9 +143,37 @@ CSV-формат: колонка `User`, `Username`, `SamAccountName` или `Lo
Если список уже содержит `DOMAIN\user`, параметр `-Domain` не нужен.
## Рекомендуемый phased rollout (изолированный профиль)
## Безопасная миграция текущего production
Для безопасного параллельного запуска рядом с legacy-инсталляцией используйте отдельные пути:
Если текущий RDP production уже работает в `C:\Program Files\ActivityWatch-Phase2` и
`C:\ProgramData\ActivityWatch-Phase2`, не запускайте обычный update без миграции.
Сначала выполните перенос в единый профиль AWatch-rus:
```powershell
C:\Program Files\AWatch-rus\windows\migrate-awatch-rus-paths.ps1 -WhatIf
C:\Program Files\AWatch-rus\windows\migrate-awatch-rus-paths.ps1
```
Скрипт делает безопасный порядок:
1. Находит старый `deployment-config.json`.
2. Останавливает `ActivityWatch Recovery` и `ActivityWatch Launch *`.
3. Создаёт backup старых и новых каталогов в
`C:\ProgramData\AWatch-rus\migration-backups\YYYYMMDD-HHMMSS`.
4. Копирует бинарники/состояние в единые пути:
- `C:\Program Files\AWatch-rus\bin`
- `C:\ProgramData\AWatch-rus`
5. Переписывает пути в `deployment-config.json`.
6. Пересоздаёт launcher/recovery scripts и scheduled tasks.
7. Запускает `validate-deployment.ps1`; при ошибке оставляет backup path в сообщении.
Ansible playbook `ansible/deploy_aw_windows.yml` выполняет этот migration guard
автоматически, если на хосте найден
`C:\ProgramData\ActivityWatch-Phase2\deployment-config.json`.
## Рекомендуемый rollout
Для запуска после миграции используйте единые пути:
```powershell
.\windows\deploy-domain-users.ps1 `
@@ -153,10 +181,10 @@ CSV-формат: колонка `User`, `Username`, `SamAccountName` или `Lo
-ServerPort 5600 `
-Domain SHARKON2025 `
-Users user2,user3,user4,user5 `
-InstallRoot 'C:\Program Files\ActivityWatch-Phase2-u2u5' `
-StateRoot 'C:\ProgramData\ActivityWatch-Phase2-u2u5' `
-CustomRulesPath C:\Deploy\AWatch-rus\windows\web-category-rules.example.json `
-CustomPolicyPath C:\Deploy\AWatch-rus\windows\dlp-policy.example.json
-InstallRoot 'C:\Program Files\AWatch-rus\bin' `
-StateRoot 'C:\ProgramData\AWatch-rus' `
-CustomRulesPath C:\Program Files\AWatch-rus\windows\web-category-rules.example.json `
-CustomPolicyPath C:\Program Files\AWatch-rus\windows\dlp-policy.example.json
```
Single-user pilot в таком же стиле:
@@ -166,10 +194,10 @@ Single-user pilot в таком же стиле:
-ServerHost 10.10.10.13 `
-ServerPort 5600 `
-TargetUser 'SHARKON2025\user1' `
-InstallRoot 'C:\Program Files\ActivityWatch-Phase2' `
-StateRoot 'C:\ProgramData\ActivityWatch-Phase2-user1' `
-CustomRulesPath C:\Deploy\AWatch-rus\windows\web-category-rules.example.json `
-CustomPolicyPath C:\Deploy\AWatch-rus\windows\dlp-policy.example.json
-InstallRoot 'C:\Program Files\AWatch-rus\bin' `
-StateRoot 'C:\ProgramData\AWatch-rus' `
-CustomRulesPath C:\Program Files\AWatch-rus\windows\web-category-rules.example.json `
-CustomPolicyPath C:\Program Files\AWatch-rus\windows\dlp-policy.example.json
```
## Ensemble deploy (production workflow)
@@ -186,31 +214,31 @@ Single-user pilot в таком же стиле:
Итоговый отчёт:
- `C:\ProgramData\ActivityWatch\ensemble-report-YYYYMMDD-HHMMSS.json`
- `C:\ProgramData\AWatch-rus\ensemble-report-YYYYMMDD-HHMMSS.json`
## Категоризация доменов
- Встроенные категории покрывают базовые рабочие, нейтральные и личные домены.
- Для кастомизации скопируйте `windows/web-category-rules.example.json` и отредактируйте домены.
- Передайте файл через `-CustomRulesPath`; он будет сохранён как `C:\ProgramData\ActivityWatch\web-category-rules.json`.
- Передайте файл через `-CustomRulesPath`; он будет сохранён как `C:\ProgramData\AWatch-rus\web-category-rules.json`.
- Пользовательские правила имеют приоритет над встроенными.
## Структура после установки
- `C:\Program Files\ActivityWatch` — бинарники watcher'ов.
- `C:\ProgramData\ActivityWatch\deployment-config.json` — итоговая конфигурация.
- `C:\ProgramData\ActivityWatch\incident-artifacts\` — скриншоты DLP-инцидентов (если `incidentCapture.screenshotEnabled=true`).
- `C:\ProgramData\ActivityWatch\launch-watchers.ps1` — per-user launcher.
- `C:\ProgramData\ActivityWatch\recovery-loop.ps1` — system recovery loop.
- `C:\ProgramData\ActivityWatch\browser-domains-native-collector.ps1` — runtime collector.
- `C:\ProgramData\ActivityWatch\dlp-endpoint-signals-collector.ps1` — runtime endpoint collector.
- `C:\ProgramData\ActivityWatch\dlp-policy.json` — активная DLP-политика.
- `C:\ProgramData\ActivityWatch\logs\` — логи collector'а.
- `C:\Program Files\AWatch-rus\bin` — бинарники watcher'ов.
- `C:\ProgramData\AWatch-rus\deployment-config.json` — итоговая конфигурация.
- `C:\ProgramData\AWatch-rus\incident-artifacts\` — скриншоты DLP-инцидентов (если `incidentCapture.screenshotEnabled=true`).
- `C:\ProgramData\AWatch-rus\launch-watchers.ps1` — per-user launcher.
- `C:\ProgramData\AWatch-rus\recovery-loop.ps1` — system recovery loop.
- `C:\ProgramData\AWatch-rus\browser-domains-native-collector.ps1` — runtime collector.
- `C:\ProgramData\AWatch-rus\dlp-endpoint-signals-collector.ps1` — runtime endpoint collector.
- `C:\ProgramData\AWatch-rus\dlp-policy.json` — активная DLP-политика.
- `C:\ProgramData\AWatch-rus\logs\` — логи collector'а.
Для phased rollout те же файлы формируются в каталоге `StateRoot`, переданном параметром.
При переопределении `StateRoot` те же файлы формируются в указанном каталоге.
## Повторный прогон
- Скрипты идемпотентны: переустанавливают задачи и обновляют runtime-файлы.
- Предыдущая установка ActivityWatch бэкапится в `C:\ProgramData\ActivityWatch\backups\install-YYYYMMDD-HHMMSS`.
- Предыдущая установка ActivityWatch бэкапится в `C:\ProgramData\AWatch-rus\backups\install-YYYYMMDD-HHMMSS`.
- Для жёсткого восстановления запускайте `windows/hardening-recovery.ps1`.
+6 -6
View File
@@ -17,15 +17,15 @@
```powershell
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process
C:\Deploy\AWatch-rus\windows\deploy-ensemble.ps1 `
C:\Program Files\AWatch-rus\windows\deploy-ensemble.ps1 `
-ServerHost 10.10.10.13 `
-ServerPort 5600 `
-Domain SHARKON2025 `
-Users user1,user2,user3,user4,user5 `
-InstallRoot 'C:\Program Files\ActivityWatch-Phase2' `
-StateRoot 'C:\ProgramData\ActivityWatch-Phase2' `
-InstallRoot 'C:\Program Files\AWatch-rus\bin' `
-StateRoot 'C:\ProgramData\AWatch-rus' `
-AfkEnabled:$false `
-CustomPolicyPath C:\Deploy\AWatch-rus\windows\dlp-policy.example.json `
-CustomPolicyPath C:\Program Files\AWatch-rus\windows\dlp-policy.example.json `
-ValidateAfterDeploy
```
@@ -42,8 +42,8 @@ C:\Deploy\AWatch-rus\windows\deploy-ensemble.ps1 `
## Быстрый health-check
```powershell
$report = C:\Deploy\AWatch-rus\windows\validate-deployment.ps1 `
-ConfigPath C:\ProgramData\ActivityWatch-Phase2\deployment-config.json
$report = C:\Program Files\AWatch-rus\windows\validate-deployment.ps1 `
-ConfigPath C:\ProgramData\AWatch-rus\deployment-config.json
$report | ConvertTo-Json -Depth 12
```
@@ -0,0 +1,7 @@
# Moved
Документ перенесён в новую структуру install-kit:
- `windows/installkit/innosetup/innosetup-rdp-package-filelist.md`
Этот файл оставлен как совместимый указатель, чтобы не ломать существующие ссылки в документации/автоматизации.
+3 -3
View File
@@ -23,7 +23,7 @@ Start-ScheduledTask -TaskName 'ActivityWatch Launch [CONTOSO_user01]'
- Скрипт работает через UI Automation и foreground window.
- Некоторые браузеры/страницы могут скрывать адресную строку или блокировать UIA.
- Проверьте лог `C:\ProgramData\ActivityWatch\logs\browser-domains-<user>.log`.
- Проверьте лог `C:\ProgramData\AWatch-rus\logs\browser-domains-<user>.log`.
- Убедитесь, что активное окно — поддерживаемый браузер: Edge, Chrome, Brave, Vivaldi, Opera, Firefox.
### Сервер недоступен
@@ -44,7 +44,7 @@ Invoke-WebRequest http://aw.example.local:5600/api/0/info
### Неправильная категоризация домена
- Проверьте содержимое `C:\ProgramData\ActivityWatch\web-category-rules.json`.
- Проверьте содержимое `C:\ProgramData\AWatch-rus\web-category-rules.json`.
- Пользовательские правила должны быть валидным JSON.
- Один и тот же домен лучше определять только в одной категории.
- После изменения правил достаточно перезапустить collector или задачу пользователя:
@@ -112,7 +112,7 @@ Get-CimInstance Win32_Process |
Проверить конфиг:
```powershell
Get-Content C:\ProgramData\ActivityWatch\deployment-config.json -Raw
Get-Content C:\ProgramData\AWatch-rus\deployment-config.json -Raw
```
## Когда запускать hardening/recovery
+13 -13
View File
@@ -4,7 +4,7 @@
```powershell
$report = .\windows\validate-deployment.ps1 `
-ConfigPath C:\ProgramData\ActivityWatch\deployment-config.json
-ConfigPath C:\ProgramData\AWatch-rus\deployment-config.json
$report | ConvertTo-Json -Depth 12
```
@@ -19,11 +19,11 @@ $report | ConvertTo-Json -Depth 12
### 1. Проверить установленные файлы
```powershell
Test-Path 'C:\Program Files\ActivityWatch\aw-watcher-afk\aw-watcher-afk.exe'
Test-Path 'C:\Program Files\ActivityWatch\aw-watcher-window\aw-watcher-window.exe'
Test-Path 'C:\ProgramData\ActivityWatch\browser-domains-native-collector.ps1'
Test-Path 'C:\ProgramData\ActivityWatch\dlp-policy.json'
Test-Path 'C:\ProgramData\ActivityWatch\deployment-config.json'
Test-Path 'C:\Program Files\AWatch-rus\bin\aw-watcher-afk\aw-watcher-afk.exe'
Test-Path 'C:\Program Files\AWatch-rus\bin\aw-watcher-window\aw-watcher-window.exe'
Test-Path 'C:\ProgramData\AWatch-rus\browser-domains-native-collector.ps1'
Test-Path 'C:\ProgramData\AWatch-rus\dlp-policy.json'
Test-Path 'C:\ProgramData\AWatch-rus\deployment-config.json'
```
Ожидаемый результат — везде `True`.
@@ -50,7 +50,7 @@ Get-ScheduledTask | Where-Object TaskName -eq 'ActivityWatch Recovery'
### 2.1 Проверить incidentCapture в конфиге
```powershell
$cfg = Get-Content 'C:\ProgramData\ActivityWatch\deployment-config.json' -Raw | ConvertFrom-Json
$cfg = Get-Content 'C:\ProgramData\AWatch-rus\deployment-config.json' -Raw | ConvertFrom-Json
$cfg.incidentCapture
```
@@ -119,7 +119,7 @@ Invoke-WebRequest http://aw.example.local:5600/api/0/buckets | Select-Object -Ex
4. Проверьте локальный лог:
```powershell
Get-Content "C:\ProgramData\ActivityWatch\logs\dlp-incidents-$env:USERNAME.log" -Tail 50
Get-Content "C:\ProgramData\AWatch-rus\logs\dlp-incidents-$env:USERNAME.log" -Tail 50
```
Если `screenshotEnabled = True`, проверьте наличие скриншота в инциденте:
@@ -175,7 +175,7 @@ Invoke-RestMethod -Method Post `
- в API появился `ruleId=selftest-dlp-incident`;
- в UI (`#/buckets/aw-dlp-incidents_<hostname>`) событие видно в `Events`.
## Проверка phase-2 endpoint signals
## Проверка endpoint signals
1. Скопируйте любой текст в буфер обмена.
2. Отправьте тестовую печать (любой принтер/виртуальный PDF).
@@ -188,7 +188,7 @@ Invoke-WebRequest http://aw.example.local:5600/api/0/buckets/aw-dlp-endpoint-sig
4. Проверьте локальный лог:
```powershell
Get-Content "C:\ProgramData\ActivityWatch\logs\endpoint-signals-$env:USERNAME.log" -Tail 50
Get-Content "C:\ProgramData\AWatch-rus\logs\endpoint-signals-$env:USERNAME.log" -Tail 50
```
## Проверка восстановления
@@ -206,9 +206,9 @@ Start-ScheduledTask -TaskName 'ActivityWatch Recovery'
## Проверка ACL
```powershell
icacls 'C:\Program Files\ActivityWatch'
icacls 'C:\ProgramData\ActivityWatch'
icacls 'C:\ProgramData\ActivityWatch\logs'
icacls 'C:\Program Files\AWatch-rus\bin'
icacls 'C:\ProgramData\AWatch-rus'
icacls 'C:\ProgramData\AWatch-rus\logs'
```
Ожидаемо:
+25
View File
@@ -36,6 +36,10 @@ RETURN = sort_by_duration(work);
Требует, чтобы на клиенте работал browser collector и писал в:
`aw-detmir-web-category_<HOST>` поля `categoryGroup`, `rootDomain`.
Для Linux-удалёнщиков это может быть не URL-level collector, а title/class-based web-category logger.
Например, работа через Proxmox Web UI `https://...:8006` может попадать сюда как
`rootDomain=proxmox-webui`, `categoryGroup=work`, `category=Администрирование`.
```javascript
web = flood(query_bucket("aw-detmir-web-category_SHARKON2025"));
not_afk = flood(query_bucket("aw-watcher-afk_SHARKON2025"));
@@ -68,3 +72,24 @@ RETURN = sort_by_duration(events);
- Если web-поток пустой, рабочее время в браузере корректно посчитать по доменам не получится. Тогда либо:
- чинить/запускать browser collector;
- либо временно считать браузер в `window` как «Интернет/Браузер» без разделения на work/personal.
## Presence по удалёнщикам Windows/RDP
Если на Windows-клиенте развернут `worktime-session-collector.ps1`, то появляется bucket
`aw-worktime-sessions_<HOST>` с heartbeat по `quser`/RDP session state.
Это не замена `afk/window`, а отдельный канал для ответа на вопрос:
«кто и когда вообще был в активной удалённой сессии».
```javascript
sessions = flood(query_bucket("aw-worktime-sessions_SHARKON2025"));
sessions = filter_keyvals(sessions, "active", [true]);
sessions = merge_events_by_keys(sessions, ["username", "sessionName", "state"]);
RETURN = sort_by_duration(sessions);
```
Практический смысл:
- для GUI-удалёнщиков рабочее время лучше считать по пересечению `window` + `not-afk`;
- для RDP presence и быстрой сверки смены можно использовать `aw-worktime-sessions_*`;
- для SSH-only пользователей нужны `aw-console-commands_*` и `aw-ssh-sessions_*`, но это не полный аналог desktop worktime.
+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
@@ -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

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