chore(install-kit): rebuild awindows package
This commit is contained in:
@@ -0,0 +1 @@
|
||||
windows/installkit/innosetup/AWatch-rus-InstallKit.exe filter=lfs diff=lfs merge=lfs -text
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,47 +1,47 @@
|
||||
f3dde1e6d1532804379faf7e395deaf95cf3e0b97769d425f8a69f4572de2a2f install-kit-awindows-20260427-211240/README-INSTALL-KIT.txt
|
||||
a08ccceada7a21e4396a640e54a354e4d4d760980ec3f18f1bc7f543cd8f4cc6 install-kit-awindows-20260427-211240/ansible/README.md
|
||||
edc20460f7be2ec676a6fd7c9687f808507b364b32915eb78b7a63b441b1ff84 install-kit-awindows-20260427-211240/ansible/README.md
|
||||
412bb766bbf0791c3593f38daa771d5d0aa58cc1f2d3c9010fcd4588d0fe87df install-kit-awindows-20260427-211240/ansible/deploy_aw_pfsense_poller.yml
|
||||
00d16de62df9d91cd375cbe70034ec97da14601ab5285ca93e5b297150f02d34 install-kit-awindows-20260427-211240/ansible/deploy_aw_server.yml
|
||||
ce1cbc35087006292e93a7e0d1706bc0ac71f8d9f2e60bbdc1c3a8ecea0d34f0 install-kit-awindows-20260427-211240/ansible/deploy_aw_windows.yml
|
||||
eedb12a2be920c6bec267162c91e106365af5c009426cefe84c032c2f9d9339d install-kit-awindows-20260427-211240/ansible/group_vars/all.example.yml
|
||||
95696c243ab331f06e77a40a9800c4b6668de77675ebbdf2ef54ae49e1b18874 install-kit-awindows-20260427-211240/ansible/group_vars/pfsense-poller.example.yml
|
||||
0188480546b4b18937194f2b05a0f23da5dd8536eed4d60b7499a8170c36d08e install-kit-awindows-20260427-211240/ansible/deploy_aw_server.yml
|
||||
953bb39572c520d0438a621c2b4c08315bf887b84ee9e795554e5592b54a1229 install-kit-awindows-20260427-211240/ansible/deploy_aw_windows.yml
|
||||
0054e7ba5f342cd10db08f6bab4784d4816179d69f76898a4de293a4063ea14a install-kit-awindows-20260427-211240/ansible/group_vars/all.example.yml
|
||||
a896676303be9a47ed6b0c8cc07deb5a5432aebc97231e801fda985121c539eb install-kit-awindows-20260427-211240/ansible/group_vars/pfsense-poller.example.yml
|
||||
c5cab36645065815571c99f6d360f910dcccbb54b780c8bfd526a6cdc3684e19 install-kit-awindows-20260427-211240/ansible/group_vars/proxmox-matrix.example.yml
|
||||
35a33c8a1c75ded5e85c6b79e0b3efde07959ff61ee5f66d83b7e0c2abe87fc5 install-kit-awindows-20260427-211240/ansible/group_vars/proxmox.example.yml
|
||||
5da847b74fac52e0fec2f60e134f4377cfa1581e026b291d3d3ac362371f6e49 install-kit-awindows-20260427-211240/ansible/group_vars/windows.example.yml
|
||||
a1921715cf9904b7e3b61a1a5c4300382efe2f3bd70d73e4c8e995318fdd55e9 install-kit-awindows-20260427-211240/ansible/group_vars/windows.example.yml
|
||||
7189b5205bd25313db54e5be027b0d066199e6ae34ad74be2095a1691adaf5e3 install-kit-awindows-20260427-211240/ansible/install_full_stack.yml
|
||||
fea0574d7eb98ce24a1e7025afb9837c6241180095d21eaa74892225305d05a3 install-kit-awindows-20260427-211240/ansible/inventory.example.ini
|
||||
8a5e4923c0f581dd4fbb32549ee7ab45ba506260056da923ba86d9f1b1081714 install-kit-awindows-20260427-211240/ansible/provision_proxmox_ct_and_deploy_aw.yml
|
||||
18928adcaef5d01b4c621b48f5559383c8b749ef182fd3710f10b222a164f8b7 install-kit-awindows-20260427-211240/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml
|
||||
a74a49371e889dc3ea404534a939f32f2dac940d8902d20770590951ab67d532 install-kit-awindows-20260427-211240/ansible/tasks/provision_ct_and_deploy_aw.yml
|
||||
ba16fe9e267194459a6082045a387acb828a1d39a98e66b401ece5069ea62e64 install-kit-awindows-20260427-211240/aw-server/activitywatch-server.service
|
||||
bed7495c277970a37d1c467e81592417f955914d4c714ac21397083b56f1bba8 install-kit-awindows-20260427-211240/aw-server/apply_webui_ru_patch.sh
|
||||
db81f6209e14f2efd123cd91fac74e37a68e5ef54d00b8b6f6612404a78ffdaa install-kit-awindows-20260427-211240/aw-server/aw-host-groups.json
|
||||
6eafd2d7a43a9fd146fdc6686ae306cb92abfa2c273bbb1ed03b67fb1277defe install-kit-awindows-20260427-211240/aw-server/aw-ru-patch.js
|
||||
9e6254c726dd4a26578a60b6bf5acee84066f931ae395115a80cf622a6ff2732 install-kit-awindows-20260427-211240/aw-server/aw-rus-healthd.py
|
||||
fea5734c99b516b01bfe8ebbef59972d0ef553c09d7691790b08472ecfb9602d install-kit-awindows-20260427-211240/aw-server/apply_webui_ru_patch.sh
|
||||
ba88cc284d047f521427ac038c266624a6fe8493ce3e79bc27c172a2e70ac54a install-kit-awindows-20260427-211240/aw-server/aw-host-groups.json
|
||||
509759461ce0918a2dc839832812cf5f4c77b1cea1e6ffdcab0146e02598df79 install-kit-awindows-20260427-211240/aw-server/aw-ru-patch.js
|
||||
22d4ee27ffc73dcec3ba73c8ee65d12353ded1403f592ff52ef578e09dad4c43 install-kit-awindows-20260427-211240/aw-server/aw-rus-healthd.py
|
||||
05b04b5f49e9c7783917e0861e3edd63f5d6db8638d6b33dbfd4dc0f1c16040f install-kit-awindows-20260427-211240/aw-server/aw-rus-healthd.service
|
||||
bb0f1de91862da66b0b6d9bd41e8dfe181710196141cb43b00d9b41dab6caf96 install-kit-awindows-20260427-211240/aw-server/aw-rus-healthd.timer
|
||||
0d2b978833b27a2a041508b49ffd07ca045127ddf3b09c71d3787d0bf1224473 install-kit-awindows-20260427-211240/aw-server/aw-server.env.example
|
||||
b493fbf4cc15ccba44ea78d3ed17a0a6db31617f22d6aeb671677cbf11a36921 install-kit-awindows-20260427-211240/aw-server/aw-server.env.example
|
||||
98c0bed353bbda0fa7a69df23f3b008cb0e8e70cdff6cc63330d4caf79fd3280 install-kit-awindows-20260427-211240/aw-server/aw-sw-cleanup.js
|
||||
d8d6be450a726f51f87415eaebd67396468c73ddda9b942481d56b4d33d68bb7 install-kit-awindows-20260427-211240/aw-server/aw-worktime-api.py
|
||||
e220a2eb830a018fa28c02872fabda54aed7f87dbb0e14141f1a8933a98064fe install-kit-awindows-20260427-211240/aw-server/aw-worktime-api.py
|
||||
f764e566d70952acc1b4f2baeccd6b7888905bc6b98b36333336c0dc77c66694 install-kit-awindows-20260427-211240/aw-server/aw-worktime-api.service
|
||||
8af41d20f01dfffe6b8c64bd5dbe24468f297035760050647bfeb53b704c0d4d install-kit-awindows-20260427-211240/aw-server/aw-worktime-panel.js
|
||||
3262b356dc4cd940b66f27d47cfe437dcd26d8d2cb49d1dc0a00b933e380a376 install-kit-awindows-20260427-211240/aw-server/install_aw_server.sh
|
||||
afbb4be301b4940ed9b7671be3441a48d53bee968d8870749442f81c6d066650 install-kit-awindows-20260427-211240/aw-server/aw-worktime-panel.js
|
||||
ca7d1ed7665e225824d4ed7ef8456c81a6d97ef2c90cb3aa799d72e07b6bcbb8 install-kit-awindows-20260427-211240/aw-server/install_aw_server.sh
|
||||
aedffecfa24834968742cb2477faef80bf794345275a9679ac12c5a1f609acc2 install-kit-awindows-20260427-211240/aw-server/settings/classes-worktime.json
|
||||
38fd98fe5816fb87055a9ad1fc570a65052785c829ecd8359fe8305949ab32ca install-kit-awindows-20260427-211240/aw-server/settings/views-default.json
|
||||
47c50054515506b72af2d6bca0ae959ca57861803e528991c230611e6a8893d6 install-kit-awindows-20260427-211240/aw-server/settings/views-default.json
|
||||
dd2389e9cb199ed86d219120294fdaa64415cfbc683004d5f5db5d52aa758a92 install-kit-awindows-20260427-211240/server-configs-192.168.100.18/awatch-rus-admin.deployment-config.json
|
||||
5279f9d677faed76a5f0248f9217ecc29eac977ab752552cab852f5b4b6715df install-kit-awindows-20260427-211240/server-configs-192.168.100.18/awatch-rus-u2u5.deployment-config.json
|
||||
333fe336e51f9c69bd2559d18763da2b83df400fa374e540ed128ffb6765ab7e install-kit-awindows-20260427-211240/server-configs-192.168.100.18/awatch-rus-user1.deployment-config.json
|
||||
33aa34b89246d6c079ef9afe2f5cd153bd9d5946b69a175ff6fd678c77f61da5 install-kit-awindows-20260427-211240/windows/ActivityWatch.Windows.Common.psd1
|
||||
0e5ac8bc0571f154190202504e02710ac931b8015cb01ec93e82defed9fc2f4f install-kit-awindows-20260427-211240/windows/ActivityWatch.Windows.Common.psm1
|
||||
130ae4c137f7d6951cd8c247c0a8fbf999f4c244e879c180e1441b781e758228 install-kit-awindows-20260427-211240/windows/browser-domains-native-collector.ps1
|
||||
831edc097f0621ae940db5064c949b4a83ed6e4f25265875f0cdd6bba0ba4c51 install-kit-awindows-20260427-211240/windows/deploy-domain-users.ps1
|
||||
f9992b3c9c075755e6ffcf82385b9abd01e768a0c3fb7fe01afae5d7b65d04db install-kit-awindows-20260427-211240/windows/deploy-ensemble.ps1
|
||||
f940b40600d57f2d12f44a32aa88e5591b7569ce01c06911ff8f4e27b0e1649f install-kit-awindows-20260427-211240/windows/deploy-single-user.ps1
|
||||
35a0dcc90459d4af39998c8a8bd534826e0b463dfbf3f8547444a96204f0ef4b install-kit-awindows-20260427-211240/windows/dlp-endpoint-signals-collector.ps1
|
||||
9e5e6a30bfb7789ee91ae656e306586a50509d9d416fc044408e9e6c19c61b37 install-kit-awindows-20260427-211240/windows/ActivityWatch.Windows.Common.psm1
|
||||
81e95c7e4b7336a2e1f0caed3ea99f6cdb9696b99380dc2148667b5fba577f68 install-kit-awindows-20260427-211240/windows/browser-domains-native-collector.ps1
|
||||
e390e00a4deff5ce45a08f5252f23c56b2655c8611e42bc7b2b6992f60bde038 install-kit-awindows-20260427-211240/windows/deploy-domain-users.ps1
|
||||
2074d9ebdda069c8bd579e688473656259714fe7e13b7537a7c464143195b525 install-kit-awindows-20260427-211240/windows/deploy-ensemble.ps1
|
||||
9f2637ede66b61a12e9edafa0b248a5fb496dd5175b288ebb71718330cac68d5 install-kit-awindows-20260427-211240/windows/deploy-single-user.ps1
|
||||
dc74947393b1851ca233b559ea77f29b1b12a1c4480fbb865f2b58b25f3ea7d3 install-kit-awindows-20260427-211240/windows/dlp-endpoint-signals-collector.ps1
|
||||
a4dad0745da95a69ee55b0216d4bde39c58acef8642380092938465653c61cf4 install-kit-awindows-20260427-211240/windows/dlp-policy.example.json
|
||||
863727465497b474d13d2270d443ff96ccb6076f90a5ce3eb270bdf8088e02dc install-kit-awindows-20260427-211240/windows/email-outbound-collector.ps1
|
||||
e29fd9ed3510429372126d94c54beb30dc6424b5b22eb012829b99c3cb07ea60 install-kit-awindows-20260427-211240/windows/hardening-recovery.ps1
|
||||
44d941322d618d92c72c3d24fe619d71e7551b9cdb35abe19f700c0b74da3eb1 install-kit-awindows-20260427-211240/windows/hardening-recovery.ps1
|
||||
5ef21a25d5e2da4eeaef17126e60f96f195f90f9dc17a776f8629334b904d096 install-kit-awindows-20260427-211240/windows/migrate-awatch-rus-paths.ps1
|
||||
d01edd14b2c839ae171006fd3345dbddf1b683b1adcac885b6eabc52e3baeb79 install-kit-awindows-20260427-211240/windows/validate-deployment.ps1
|
||||
672d76824b1ca17d4c65e3097cf9895160a7bbce30323ef02d195fc9624538da install-kit-awindows-20260427-211240/windows/validate-deployment.ps1
|
||||
731098681d89b9af6f3872abd586ac3b1faba2d7f9340211e503f52ad0243b3f install-kit-awindows-20260427-211240/windows/web-category-rules.example.json
|
||||
945ccfffd56697ed328b82e82d1cffbc83fa4e50a6120b27e3948f3d13fa8a33 install-kit-awindows-20260427-211240/windows/worktime-session-collector.ps1
|
||||
1b7c337967236474484e781dc8ac37543509b051513f01e1a3145369262f5389 install-kit-awindows-20260427-211240/windows/worktime-session-collector.ps1
|
||||
|
||||
@@ -89,6 +89,12 @@ ansible-playbook -i inventory.ini provision_proxmox_ct_matrix_and_deploy_aw.yml
|
||||
|
||||
## Windows/RDP rollout (WinRM)
|
||||
|
||||
Важно:
|
||||
|
||||
- `WinRM` здесь остаётся транспортом для `Ansible deploy` и `validation`;
|
||||
- для интерактивной PowerShell-работы из Linux/Codex по DetMir используйте project MCP-over-SSH путь, а не `WSMan`;
|
||||
- каноника лежит в `docs/DETMIR_POWERSHELL_MCP_REMOTE_RU.md` и `scripts/install_detmir_powershell_mcp.sh`.
|
||||
|
||||
1. Подготовьте inventory и vars:
|
||||
- `cp ansible/inventory.example.ini ansible/inventory.ini`
|
||||
- `cp ansible/group_vars/windows.example.yml ansible/group_vars/windows.yml`
|
||||
@@ -122,6 +128,7 @@ Playbook:
|
||||
- выполняет API smoke-check bucket `aw-watcher-window_<COMPUTERNAME>` и ожидает свежие события (по умолчанию включено);
|
||||
- запускает `validate-deployment.ps1`;
|
||||
- забирает JSON-отчёт в локальную директорию (`/tmp/aw-rus-validation-<USER>` по умолчанию).
|
||||
- настраивает scheduled task `ActivityWatch Hayabusa Upload` с периодом и lookback по vars.
|
||||
|
||||
Дополнительные флаги:
|
||||
|
||||
@@ -144,6 +151,38 @@ Playbook:
|
||||
- `aw_windows_api_smoke_check_min_events: 1` — минимум событий, ожидаемых в smoke-check;
|
||||
- `aw_windows_fail_on_validation_error: true` — завершать playbook ошибкой, если `validate-deployment.ps1` возвращает `overallOk=false`;
|
||||
- `aw_windows_skip_hardening: true` — пропустить `hardening-recovery.ps1` внутри ensemble-скрипта.
|
||||
- `aw_windows_hayabusa_auto_upload_enabled: true` — включить авто-upload EVTX на AW-server;
|
||||
- `aw_windows_hayabusa_auto_upload_interval_hours: 6` — период scheduled task;
|
||||
- `aw_windows_hayabusa_auto_upload_hours_back: 6` — lookback для каждого запуска;
|
||||
- `aw_windows_hayabusa_auto_upload_mode: "incident"` — mode для server-side processing;
|
||||
- `aw_windows_hayabusa_auto_upload_task_name: "ActivityWatch Hayabusa Upload"` — имя scheduled task.
|
||||
|
||||
## Server-side Hayabusa auto-case и Telegram alerting
|
||||
|
||||
На стороне `deploy_aw_server.yml` теперь есть server-side контур:
|
||||
|
||||
- `aw-hayabusa-drop.path`
|
||||
- `aw-hayabusa-drop.service`
|
||||
- `aw-hayabusa-autoprocess`
|
||||
- `aw-hayabusa-case-alert`
|
||||
|
||||
Что делает контур:
|
||||
|
||||
- автоматически подхватывает `zip` из `/opt/activitywatch/aw-rus-ops/drop`;
|
||||
- запускает `aw-hayabusa`;
|
||||
- считает severity/score по `timeline.jsonl`;
|
||||
- создаёт или обновляет case;
|
||||
- пишет bounded metadata в `forensics.hayabusa`;
|
||||
- отправляет Telegram alert.
|
||||
|
||||
Основные vars:
|
||||
|
||||
- `aw_hayabusa_auto_case_enabled: true`
|
||||
- `aw_hayabusa_auto_case_min_severity: "medium"`
|
||||
- `aw_hayabusa_telegram_enabled: true`
|
||||
- `aw_hayabusa_telegram_min_severity: "high"`
|
||||
- `aw_hayabusa_telegram_bot_token`
|
||||
- `aw_hayabusa_telegram_chat_ids`
|
||||
|
||||
## Развёртывание pfSense poller
|
||||
|
||||
|
||||
@@ -94,6 +94,89 @@
|
||||
- "{{ aw_rus_health_validation_dir }}"
|
||||
- "{{ aw_server_log_dir }}"
|
||||
|
||||
- name: Установить prune script для локального state
|
||||
ansible.builtin.copy:
|
||||
src: "{{ aw_repo_root }}/aw-server/aw-prune-local-state.sh"
|
||||
dest: /usr/local/bin/aw-prune-local-state.sh
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Ограничить рост journald на aw-server
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/systemd/journald.conf.d/aw-rus-retention.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: |
|
||||
[Journal]
|
||||
SystemMaxUse={{ aw_server_journal_system_max_use }}
|
||||
RuntimeMaxUse={{ aw_server_journal_runtime_max_use }}
|
||||
SystemKeepFree={{ aw_server_journal_system_keep_free }}
|
||||
register: aw_journald_dropin
|
||||
|
||||
- name: Установить systemd service prune локального state
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/systemd/system/aw-prune-local-state.service
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: |
|
||||
[Unit]
|
||||
Description=Prune ActivityWatch local backups and temp state
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
Environment=AW_DATA_DIR={{ aw_server_data_dir }}
|
||||
Environment=AW_BACKUP_RETENTION_DAYS={{ aw_server_backup_retention_days }}
|
||||
Environment=AW_BACKUP_KEEP_LAST_DB={{ aw_server_backup_keep_last_db }}
|
||||
Environment=AW_BACKUP_KEEP_LAST_JSON={{ aw_server_backup_keep_last_json }}
|
||||
ExecStart=/usr/local/bin/aw-prune-local-state.sh
|
||||
|
||||
- name: Установить systemd timer prune локального state
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/systemd/system/aw-prune-local-state.timer
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: |
|
||||
[Unit]
|
||||
Description=Daily prune of ActivityWatch local backups and temp state
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 04:40:00
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
|
||||
- name: Перечитать systemd после retention unit/drop-in
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
|
||||
- name: Включить и запустить timer prune локального state
|
||||
ansible.builtin.systemd:
|
||||
name: aw-prune-local-state.timer
|
||||
enabled: true
|
||||
state: started
|
||||
|
||||
- name: Применить journald retention без простоя
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- systemctl
|
||||
- restart
|
||||
- systemd-journald
|
||||
when: aw_journald_dropin.changed
|
||||
failed_when: false
|
||||
|
||||
- name: Сжать существующий journald до нового лимита
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- journalctl
|
||||
- --vacuum-size={{ aw_server_journal_system_max_use }}
|
||||
changed_when: true
|
||||
failed_when: false
|
||||
|
||||
- name: (Check mode) Пропустить установку релиза ActivityWatch
|
||||
ansible.builtin.debug:
|
||||
msg: "ansible_check_mode=true: download/unarchive/install of ActivityWatch release is skipped."
|
||||
@@ -115,6 +198,11 @@
|
||||
remote_src: true
|
||||
extra_opts: ["-o"]
|
||||
|
||||
- name: Удалить временный архив ActivityWatch после распаковки
|
||||
ansible.builtin.file:
|
||||
path: "{{ aw_archive_path }}"
|
||||
state: absent
|
||||
|
||||
- name: Найти распакованный каталог ActivityWatch
|
||||
ansible.builtin.find:
|
||||
paths: "{{ aw_release_dir }}"
|
||||
@@ -288,6 +376,22 @@
|
||||
dest: /opt/activitywatch/aw-server/apply_webui_ru_patch.sh
|
||||
mode: "0755"
|
||||
|
||||
- name: Проверить Influx token для AW worktime exporter
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- aw_worktime_influx_token is defined
|
||||
- aw_worktime_influx_token | length > 0
|
||||
fail_msg: "aw_worktime_influx_enabled=true, но aw_worktime_influx_token пуст. Exporter будет падать и Grafana не получит worktime-ряды."
|
||||
when: aw_worktime_influx_enabled | default(false) | bool
|
||||
|
||||
- name: Проверить Influx token для AW DLP exporter
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- aw_dlp_influx_token is defined
|
||||
- aw_dlp_influx_token | length > 0
|
||||
fail_msg: "aw_dlp_influx_enabled=true, но aw_dlp_influx_token пуст. Exporter будет падать и Grafana не получит DLP-ряды."
|
||||
when: aw_dlp_influx_enabled | default(false) | bool
|
||||
|
||||
- name: Записать /etc/activitywatch/aw-server.env перед хотфиксами
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/activitywatch/aw-server.env
|
||||
@@ -340,8 +444,16 @@
|
||||
AW_HEALTH_STRICT_FILEOPS={{ aw_health_strict_fileops | default(0) }}
|
||||
AW_MONITORED_WINDOWS_HOST={{ aw_monitored_windows_host }}
|
||||
AW_MONITORED_WINDOWS_HOSTNAME={{ aw_monitored_windows_hostname }}
|
||||
AW_RUS_HEALTH_WORKTIME_API={{ aw_rus_health_worktime_api_base | default('http://127.0.0.1:5610') }}
|
||||
AW_RUS_HEALTH_STATE_DIR={{ aw_rus_health_state_dir }}
|
||||
AW_RUS_HEALTH_VALIDATION_DIR={{ aw_rus_health_validation_dir }}
|
||||
AW_RUS_HEALTH_SESSION_EVENTS_MAX_AGE_SECONDS={{ aw_rus_health_session_events_max_age_seconds | default(86400) }}
|
||||
AW_HAYABUSA_AUTO_CASE_ENABLED={{ 'true' if (aw_hayabusa_auto_case_enabled | default(true) | bool) else 'false' }}
|
||||
AW_HAYABUSA_AUTO_CASE_MIN_SEVERITY={{ aw_hayabusa_auto_case_min_severity | default('medium') }}
|
||||
AW_HAYABUSA_TELEGRAM_ENABLED={{ 'true' if (aw_hayabusa_telegram_enabled | default(false) | bool) else 'false' }}
|
||||
AW_HAYABUSA_TELEGRAM_MIN_SEVERITY={{ aw_hayabusa_telegram_min_severity | default('high') }}
|
||||
AW_HAYABUSA_TELEGRAM_BOT_TOKEN={{ aw_hayabusa_telegram_bot_token | default('') }}
|
||||
AW_HAYABUSA_TELEGRAM_CHAT_IDS={{ aw_hayabusa_telegram_chat_ids | default('') }}
|
||||
|
||||
- name: Создать каталог DLP policy engine
|
||||
ansible.builtin.file:
|
||||
@@ -914,7 +1026,6 @@
|
||||
ansible.builtin.systemd:
|
||||
name: aw-worktime-influx-exporter.service
|
||||
state: started
|
||||
failed_when: false
|
||||
when: aw_worktime_influx_enabled | default(false) | bool
|
||||
|
||||
- name: Включить и перезапустить AW DLP Influx exporter timer
|
||||
@@ -928,7 +1039,6 @@
|
||||
ansible.builtin.systemd:
|
||||
name: aw-dlp-influx-exporter.service
|
||||
state: started
|
||||
failed_when: false
|
||||
when: aw_dlp_influx_enabled | default(false) | bool
|
||||
|
||||
- name: Применить хотфиксы compiled JS чанков (Trends, Timespiral, Category helper)
|
||||
@@ -1506,6 +1616,11 @@
|
||||
remote_src: true
|
||||
creates: "{{ aw_hayabusa_release_dir }}/{{ aw_hayabusa_binary_name }}"
|
||||
|
||||
- name: Удалить временный архив Hayabusa после распаковки
|
||||
ansible.builtin.file:
|
||||
path: "{{ aw_hayabusa_archive_path }}"
|
||||
state: absent
|
||||
|
||||
- name: Нормализовать права release Hayabusa
|
||||
ansible.builtin.file:
|
||||
path: "{{ aw_hayabusa_release_dir }}"
|
||||
@@ -1545,6 +1660,124 @@
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Создать server-side ops bundle для Hayabusa
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
loop:
|
||||
- /opt/activitywatch/aw-rus-ops
|
||||
- /opt/activitywatch/aw-rus-ops/hayabusa
|
||||
- /opt/activitywatch/aw-rus-ops/ansible
|
||||
- /opt/activitywatch/aw-rus-ops/drop
|
||||
|
||||
- name: Положить исходный wrapper в server-side ops bundle
|
||||
ansible.builtin.copy:
|
||||
src: "{{ aw_repo_root }}/aw-server/hayabusa/aw-hayabusa.sh"
|
||||
dest: /opt/activitywatch/aw-rus-ops/hayabusa/aw-hayabusa.sh
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Установить helper link-case для Hayabusa
|
||||
ansible.builtin.copy:
|
||||
src: "{{ aw_repo_root }}/aw-server/hayabusa/aw-hayabusa-link-case.py"
|
||||
dest: /usr/local/bin/aw-hayabusa-link-case
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Положить helper link-case в server-side ops bundle
|
||||
ansible.builtin.copy:
|
||||
src: "{{ aw_repo_root }}/aw-server/hayabusa/aw-hayabusa-link-case.py"
|
||||
dest: /opt/activitywatch/aw-rus-ops/hayabusa/aw-hayabusa-link-case.py
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Установить helper from-windows для Hayabusa
|
||||
ansible.builtin.copy:
|
||||
src: "{{ aw_repo_root }}/aw-server/hayabusa/aw-hayabusa-from-windows.py"
|
||||
dest: /usr/local/bin/aw-hayabusa-from-windows
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Положить helper from-windows в server-side ops bundle
|
||||
ansible.builtin.copy:
|
||||
src: "{{ aw_repo_root }}/aw-server/hayabusa/aw-hayabusa-from-windows.py"
|
||||
dest: /opt/activitywatch/aw-rus-ops/hayabusa/aw-hayabusa-from-windows.py
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Положить README Hayabusa в server-side ops bundle
|
||||
ansible.builtin.copy:
|
||||
src: "{{ aw_repo_root }}/aw-server/hayabusa/README.md"
|
||||
dest: /opt/activitywatch/aw-rus-ops/hayabusa/README.md
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
|
||||
- name: Установить helper autoprocess для Hayabusa
|
||||
ansible.builtin.copy:
|
||||
src: "{{ aw_repo_root }}/aw-server/hayabusa/aw-hayabusa-autoprocess.py"
|
||||
dest: /usr/local/bin/aw-hayabusa-autoprocess
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Положить helper autoprocess в server-side ops bundle
|
||||
ansible.builtin.copy:
|
||||
src: "{{ aw_repo_root }}/aw-server/hayabusa/aw-hayabusa-autoprocess.py"
|
||||
dest: /opt/activitywatch/aw-rus-ops/hayabusa/aw-hayabusa-autoprocess.py
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Установить helper case-alert для Hayabusa
|
||||
ansible.builtin.copy:
|
||||
src: "{{ aw_repo_root }}/aw-server/hayabusa/aw-hayabusa-case-alert.py"
|
||||
dest: /usr/local/bin/aw-hayabusa-case-alert
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Положить helper case-alert в server-side ops bundle
|
||||
ansible.builtin.copy:
|
||||
src: "{{ aw_repo_root }}/aw-server/hayabusa/aw-hayabusa-case-alert.py"
|
||||
dest: /opt/activitywatch/aw-rus-ops/hayabusa/aw-hayabusa-case-alert.py
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Установить systemd unit aw-hayabusa-drop.service
|
||||
ansible.builtin.copy:
|
||||
src: "{{ aw_repo_root }}/aw-server/aw-hayabusa-drop.service"
|
||||
dest: /etc/systemd/system/aw-hayabusa-drop.service
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Перезагрузить systemd
|
||||
|
||||
- name: Установить systemd unit aw-hayabusa-drop.path
|
||||
ansible.builtin.copy:
|
||||
src: "{{ aw_repo_root }}/aw-server/aw-hayabusa-drop.path"
|
||||
dest: /etc/systemd/system/aw-hayabusa-drop.path
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Перезагрузить systemd
|
||||
|
||||
- name: Включить и запустить aw-hayabusa-drop.path
|
||||
ansible.builtin.systemd:
|
||||
name: aw-hayabusa-drop.path
|
||||
enabled: true
|
||||
state: started
|
||||
daemon_reload: true
|
||||
|
||||
- name: Проверить server-side runner через doctor
|
||||
ansible.builtin.command:
|
||||
cmd: /usr/local/bin/aw-hayabusa doctor
|
||||
@@ -1572,6 +1805,7 @@
|
||||
cmd: /usr/local/bin/aw-rus-healthd.py --json
|
||||
register: aw_post_deploy_health
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Показать результат aw-rus-healthd
|
||||
ansible.builtin.debug:
|
||||
|
||||
@@ -9,12 +9,12 @@
|
||||
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_builtin_administrator_name: "Администратор"
|
||||
aw_windows_users:
|
||||
- Администратор
|
||||
- user1
|
||||
@@ -29,9 +29,18 @@
|
||||
aw_windows_policy_mode: "server"
|
||||
aw_windows_policy_refresh_seconds: 300
|
||||
aw_windows_policy_engine_enabled: true
|
||||
aw_windows_policy_engine_host: "{{ aw_windows_server_host }}"
|
||||
aw_windows_policy_engine_port: 5601
|
||||
aw_windows_policy_engine_scheme: "http"
|
||||
aw_windows_hayabusa_auto_upload_enabled: true
|
||||
aw_windows_hayabusa_auto_upload_interval_hours: 6
|
||||
aw_windows_hayabusa_auto_upload_hours_back: 6
|
||||
aw_windows_hayabusa_auto_upload_mode: "incident"
|
||||
aw_windows_hayabusa_auto_upload_task_name: "ActivityWatch Hayabusa Upload"
|
||||
aw_windows_file_1c_auto_upload_enabled: true
|
||||
aw_windows_file_1c_auto_upload_interval_hours: 6
|
||||
aw_windows_file_1c_auto_upload_task_name: "ActivityWatch File1C Upload"
|
||||
aw_windows_file_1c_target_user: "igor"
|
||||
aw_windows_file_1c_registry_workbook_path: "E:\\USER1\\СПИСОК ПРЕДПРИЯТИЙ И ИХ РАСПРЕДЕЛЕНИЕ.xlsx"
|
||||
aw_windows_afk_enabled_default: true
|
||||
aw_windows_window_enabled_default: true
|
||||
aw_windows_file_ops_enabled: true
|
||||
@@ -41,6 +50,7 @@
|
||||
aw_windows_incident_artifacts_root: "{{ aw_windows_state_root }}\\incident-artifacts"
|
||||
aw_windows_forensics_root: "{{ aw_windows_state_root }}\\forensics\\evtx-exports"
|
||||
aw_windows_logon_marker_enabled: true
|
||||
aw_windows_process_events_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"
|
||||
@@ -62,16 +72,79 @@
|
||||
aw_windows_migration_report_remote_path: "{{ aw_windows_state_root }}\\aw_migration_ansible.json"
|
||||
|
||||
tasks:
|
||||
- name: Вычислить inventory host AW server по умолчанию
|
||||
ansible.builtin.set_fact:
|
||||
aw_server_inventory_host_effective: "{{ (groups['aw_server'] | default([]) | first) | default('', true) }}"
|
||||
|
||||
- name: Вычислить inventory host analytics node по умолчанию
|
||||
ansible.builtin.set_fact:
|
||||
aw_analytics_inventory_host_effective: "{{ (groups['proxmox'] | default([]) | first) | default('', true) }}"
|
||||
|
||||
- name: Вычислить effective host для AW server
|
||||
ansible.builtin.set_fact:
|
||||
aw_windows_server_host_effective: >-
|
||||
{{
|
||||
aw_windows_server_host
|
||||
| default(
|
||||
(
|
||||
hostvars[aw_server_inventory_host_effective].ansible_host
|
||||
| default(aw_server_inventory_host_effective, true)
|
||||
)
|
||||
if (aw_server_inventory_host_effective | length) > 0
|
||||
else '',
|
||||
true
|
||||
)
|
||||
}}
|
||||
|
||||
- name: Вычислить effective каталог health validation на AW server
|
||||
ansible.builtin.set_fact:
|
||||
aw_windows_health_validation_dir_effective: >-
|
||||
{{
|
||||
(
|
||||
hostvars[aw_server_inventory_host_effective].aw_rus_health_validation_dir
|
||||
| default('/var/lib/activitywatch/health/windows-validation', true)
|
||||
)
|
||||
if (aw_server_inventory_host_effective | length) > 0
|
||||
else ''
|
||||
}}
|
||||
|
||||
- name: Вычислить effective host для policy engine
|
||||
ansible.builtin.set_fact:
|
||||
aw_windows_policy_engine_host_effective: >-
|
||||
{{
|
||||
aw_windows_policy_engine_host
|
||||
| default(aw_windows_server_host_effective, true)
|
||||
}}
|
||||
|
||||
- name: Вычислить effective host для file-1C analytics
|
||||
ansible.builtin.set_fact:
|
||||
aw_windows_file_1c_target_host_effective: >-
|
||||
{{
|
||||
aw_windows_file_1c_target_host
|
||||
| default(
|
||||
(
|
||||
hostvars[aw_analytics_inventory_host_effective].ansible_host
|
||||
| default(aw_analytics_inventory_host_effective, true)
|
||||
)
|
||||
if (aw_analytics_inventory_host_effective | length) > 0
|
||||
else '',
|
||||
true
|
||||
)
|
||||
}}
|
||||
|
||||
- name: Проверить обязательные переменные
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- aw_windows_server_host is defined
|
||||
- aw_windows_server_host_effective | length > 0
|
||||
- aw_windows_server_port is defined
|
||||
- aw_windows_server_scheme is defined
|
||||
- aw_windows_domain is defined
|
||||
- aw_windows_builtin_administrator_name is defined
|
||||
- aw_windows_builtin_administrator_name | length > 0
|
||||
- aw_windows_users_effective | length > 0
|
||||
- aw_windows_install_root is defined
|
||||
- aw_windows_state_root is defined
|
||||
- (not (aw_windows_file_1c_auto_upload_enabled | bool)) or (aw_windows_file_1c_target_host_effective | length > 0)
|
||||
fail_msg: "Не заданы обязательные переменные Windows-развёртывания."
|
||||
|
||||
- name: Нормализовать effective флаги collector'ов и smoke-check
|
||||
@@ -102,11 +175,14 @@
|
||||
- file-operations-collector.ps1
|
||||
- worktime-session-collector.ps1
|
||||
- export-evtx-for-hayabusa.ps1
|
||||
- export-upload-hayabusa-to-aw-server.ps1
|
||||
- export-upload-file-1c-telemetry.ps1
|
||||
- migrate-awatch-rus-paths.ps1
|
||||
- deploy-domain-users.ps1
|
||||
- deploy-ensemble.ps1
|
||||
- hardening-recovery.ps1
|
||||
- rebuild-worktime-tasks.ps1
|
||||
- audit-cryptopro.ps1
|
||||
- validate-deployment.ps1
|
||||
- web-category-rules.example.json
|
||||
- dlp-policy.example.json
|
||||
@@ -156,9 +232,10 @@
|
||||
ansible.windows.win_powershell:
|
||||
script: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$env:AWATCH_RUS_BUILTIN_ADMINISTRATOR_NAME = "{{ aw_windows_builtin_administrator_name }}"
|
||||
$params = @{
|
||||
ServerScheme = "{{ aw_windows_server_scheme }}"
|
||||
ServerHost = "{{ aw_windows_server_host }}"
|
||||
ServerHost = "{{ aw_windows_server_host_effective }}"
|
||||
ServerPort = {{ aw_windows_server_port }}
|
||||
Version = "{{ aw_windows_package_version }}"
|
||||
Domain = "{{ aw_windows_domain }}"
|
||||
@@ -175,12 +252,24 @@
|
||||
EvtxExportRoot = "{{ aw_windows_forensics_root }}"
|
||||
EvtxRetentionDays = {{ aw_windows_evtx_retention_days | int }}
|
||||
LogonMarkerEnabled = {{ '$true' if (aw_windows_logon_marker_enabled | bool) else '$false' }}
|
||||
ProcessEventsEnabled = {{ '$true' if (aw_windows_process_events_enabled | bool) else '$false' }}
|
||||
PolicyMode = "{{ aw_windows_policy_mode }}"
|
||||
PolicyEngineEnabled = {{ '$true' if (aw_windows_policy_engine_enabled | bool) else '$false' }}
|
||||
PolicyEngineHost = "{{ aw_windows_policy_engine_host }}"
|
||||
PolicyEngineHost = "{{ aw_windows_policy_engine_host_effective }}"
|
||||
PolicyEnginePort = {{ aw_windows_policy_engine_port }}
|
||||
PolicyEngineScheme = "{{ aw_windows_policy_engine_scheme }}"
|
||||
PolicyRefreshSeconds = {{ aw_windows_policy_refresh_seconds }}
|
||||
HayabusaAutoUploadEnabled = {{ '$true' if (aw_windows_hayabusa_auto_upload_enabled | bool) else '$false' }}
|
||||
HayabusaAutoUploadIntervalHours = {{ aw_windows_hayabusa_auto_upload_interval_hours | int }}
|
||||
HayabusaAutoUploadHoursBack = {{ aw_windows_hayabusa_auto_upload_hours_back | int }}
|
||||
HayabusaAutoUploadMode = "{{ aw_windows_hayabusa_auto_upload_mode }}"
|
||||
HayabusaAutoUploadTaskName = "{{ aw_windows_hayabusa_auto_upload_task_name }}"
|
||||
File1CAutoUploadEnabled = {{ '$true' if (aw_windows_file_1c_auto_upload_enabled | bool) else '$false' }}
|
||||
File1CAutoUploadIntervalHours = {{ aw_windows_file_1c_auto_upload_interval_hours | int }}
|
||||
File1CAutoUploadTaskName = "{{ aw_windows_file_1c_auto_upload_task_name }}"
|
||||
File1CTargetHost = "{{ aw_windows_file_1c_target_host_effective }}"
|
||||
File1CTargetUser = "{{ aw_windows_file_1c_target_user }}"
|
||||
File1CRegistryWorkbookPath = "{{ aw_windows_file_1c_registry_workbook_path }}"
|
||||
CustomRulesPath = "{{ aw_windows_rules_path }}"
|
||||
CustomPolicyPath = "{{ aw_windows_policy_path }}"
|
||||
}
|
||||
@@ -340,7 +429,7 @@
|
||||
when:
|
||||
- aw_windows_api_smoke_check_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 }}"
|
||||
url: "{{ aw_windows_server_scheme }}://{{ aw_windows_server_host_effective }}:{{ 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
|
||||
@@ -367,7 +456,7 @@
|
||||
- aw_windows_api_smoke_check_window_enabled_effective | bool
|
||||
- aw_windows_window_enabled_effective | 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_window_bucket_effective }}/events?limit={{ aw_windows_api_smoke_check_limit }}"
|
||||
url: "{{ aw_windows_server_scheme }}://{{ aw_windows_server_host_effective }}:{{ aw_windows_server_port }}/api/0/buckets/{{ aw_windows_api_smoke_check_window_bucket_effective }}/events?limit={{ aw_windows_api_smoke_check_limit }}"
|
||||
method: GET
|
||||
status_code: 200
|
||||
return_content: true
|
||||
@@ -404,6 +493,26 @@
|
||||
dest: "{{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_ansible.json"
|
||||
flat: true
|
||||
|
||||
- name: Убедиться, что каталог Windows validation существует на AW server
|
||||
when:
|
||||
- aw_server_inventory_host_effective | length > 0
|
||||
- aw_windows_health_validation_dir_effective | length > 0
|
||||
ansible.builtin.file:
|
||||
path: "{{ aw_windows_health_validation_dir_effective }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
delegate_to: "{{ aw_server_inventory_host_effective }}"
|
||||
|
||||
- name: Опубликовать validation report на AW server для aw-rus-healthd
|
||||
when:
|
||||
- aw_server_inventory_host_effective | length > 0
|
||||
- aw_windows_health_validation_dir_effective | length > 0
|
||||
ansible.builtin.copy:
|
||||
src: "{{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_ansible.json"
|
||||
dest: "{{ aw_windows_health_validation_dir_effective }}/{{ inventory_hostname }}-aw_validate_ansible.json"
|
||||
mode: "0644"
|
||||
delegate_to: "{{ aw_server_inventory_host_effective }}"
|
||||
|
||||
- name: Проверить статус валидации
|
||||
ansible.builtin.shell: |
|
||||
python3 - <<'PY'
|
||||
|
||||
@@ -8,7 +8,9 @@ aw_server_db_path: "/var/lib/activitywatch/.local/share/activitywatch/aw-server-
|
||||
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_server_inventory_host: "{{ (groups['aw_server'] | default([]) | first) | default('aw-server', true) }}"
|
||||
aw_server_public_host: "{{ (hostvars[aw_server_inventory_host].ansible_host | default(aw_server_inventory_host, true)) if (aw_server_inventory_host | length) > 0 else 'aw-server' }}"
|
||||
aw_worktime_report_base: "http://{{ aw_server_public_host }}:5610"
|
||||
aw_worktime_timezone: "Europe/Moscow"
|
||||
aw_worktime_influx_enabled: false
|
||||
aw_worktime_influx_url: "http://10.10.10.10:8086"
|
||||
@@ -27,8 +29,15 @@ aw_dlp_influx_event_limit: 2000
|
||||
aw_dlp_influx_token: ""
|
||||
aw_monitored_windows_host: "192.168.100.18"
|
||||
aw_monitored_windows_hostname: "SHARKON2025"
|
||||
aw_rus_health_worktime_api_base: "http://127.0.0.1:5610"
|
||||
aw_rus_health_state_dir: "{{ aw_server_data_dir }}/health"
|
||||
aw_rus_health_validation_dir: "{{ aw_rus_health_state_dir }}/windows-validation"
|
||||
aw_hayabusa_auto_case_enabled: true
|
||||
aw_hayabusa_auto_case_min_severity: "medium"
|
||||
aw_hayabusa_telegram_enabled: true
|
||||
aw_hayabusa_telegram_min_severity: "high"
|
||||
aw_hayabusa_telegram_bot_token: ""
|
||||
aw_hayabusa_telegram_chat_ids: ""
|
||||
|
||||
aw_repo_root: "{{ playbook_dir | dirname }}"
|
||||
|
||||
@@ -41,7 +50,7 @@ aw_apply_worktime_settings: true
|
||||
aw_server_cors_origins:
|
||||
- "http://127.0.0.1:5600"
|
||||
- "http://localhost:5600"
|
||||
- "http://10.10.10.13:5600"
|
||||
- "http://{{ aw_server_public_host }}:5600"
|
||||
- "http://aw-server:5600"
|
||||
|
||||
# Опциональные значения периода рабочего времени в Web UI.
|
||||
|
||||
@@ -12,16 +12,16 @@ aw_pfsense_poller_config:
|
||||
scheme: "https"
|
||||
verify_tls: false
|
||||
timeout_seconds: 15
|
||||
headers:
|
||||
X-API-Key: "replace-me"
|
||||
X-API-Secret: "replace-me"
|
||||
auth:
|
||||
api_key: "replace-me"
|
||||
api_secret: "replace-me"
|
||||
endpoints:
|
||||
- name: "system-status"
|
||||
path: "/api/v2/status/system"
|
||||
bucket_prefix: "aw-pfsense-health"
|
||||
bucket_type: "aw.pfsense.health"
|
||||
- name: "interfaces"
|
||||
path: "/api/v2/interface"
|
||||
path: "/api/v2/interfaces"
|
||||
bucket_prefix: "aw-pfsense-interfaces"
|
||||
bucket_type: "aw.pfsense.interfaces"
|
||||
- name: "gateways"
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
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"
|
||||
# Leave empty to derive from the first host in [aw_server] inventory.
|
||||
aw_windows_server_host: ""
|
||||
aw_windows_server_port: 5600
|
||||
aw_windows_hayabusa_auto_upload_enabled: false
|
||||
aw_windows_hayabusa_auto_upload_interval_hours: 6
|
||||
aw_windows_hayabusa_auto_upload_hours_back: 6
|
||||
aw_windows_hayabusa_auto_upload_mode: "incident"
|
||||
aw_windows_hayabusa_auto_upload_task_name: "ActivityWatch Hayabusa Upload"
|
||||
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"
|
||||
# Localized name of the built-in local Administrator account (SID ending in -500).
|
||||
# On the current Russian Windows host this must stay "Администратор";
|
||||
# do not replace it with "Administrator" unless the target OS account is actually named that way.
|
||||
aw_windows_builtin_administrator_name: "Администратор"
|
||||
aw_windows_users:
|
||||
- Администратор
|
||||
- user1
|
||||
@@ -40,6 +50,7 @@ aw_windows_evtx_channels:
|
||||
- Microsoft-Windows-TerminalServices-LocalSessionManager/Operational
|
||||
- Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational
|
||||
aw_windows_logon_marker_enabled: true
|
||||
aw_windows_process_events_enabled: true
|
||||
aw_windows_skip_hardening: false
|
||||
|
||||
aw_windows_rules_path: "{{ aw_windows_deploy_root }}\\windows\\web-category-rules.example.json"
|
||||
|
||||
@@ -10,7 +10,8 @@ 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}"
|
||||
SERVER_PUBLIC_HOST="${AW_SERVER_PUBLIC_HOST:-${AW_SERVER_HOST:-$(hostname -f 2>/dev/null || hostname)}}"
|
||||
REPORT_BASE="${AW_WORKTIME_REPORT_BASE:-http://${SERVER_PUBLIC_HOST}:5610}"
|
||||
CASE_PORT="${AW_DLP_CASE_PORT:-5602}"
|
||||
CASE_BASE="${AW_DLP_CASE_PUBLIC_BASE:-}"
|
||||
PATCH_JS_SRC="/root/bootstrap/aw-ru-patch.js"
|
||||
@@ -62,12 +63,19 @@ worktime_panel_hash="$(sha1sum "$WORKTIME_PANEL_TARGET" | awk '{print substr($1,
|
||||
if [[ -z "$CASE_BASE" ]]; then
|
||||
CASE_BASE="$(python3 - "$REPORT_BASE" "$CASE_PORT" <<'PY'
|
||||
from urllib.parse import urlsplit, urlunsplit
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
|
||||
report_base = sys.argv[1]
|
||||
case_port = sys.argv[2]
|
||||
parts = urlsplit(report_base)
|
||||
hostname = parts.hostname or "10.10.10.13"
|
||||
hostname = (
|
||||
parts.hostname
|
||||
or os.environ.get("AW_SERVER_PUBLIC_HOST")
|
||||
or os.environ.get("AW_SERVER_HOST")
|
||||
or socket.getfqdn()
|
||||
)
|
||||
scheme = parts.scheme or "http"
|
||||
print(urlunsplit((scheme, f"{hostname}:{case_port}", "", "", "")))
|
||||
PY
|
||||
|
||||
@@ -47,13 +47,11 @@
|
||||
{
|
||||
"id": "virtual-infra",
|
||||
"name": "Virtual servers + Proxmox",
|
||||
"description": "Инфраструктурные VM и сетевые узлы. Здесь должны лежать Proxmox, pfSense, Debian и Ubuntu серверы.",
|
||||
"description": "Инфраструктурные VM и серверы Proxmox, Debian и Ubuntu.",
|
||||
"patterns": [
|
||||
"^(PFSENSE|PVE|PROXMOX|DEBIAN|UBUNTU|LINUX|VM-|SRV-|INFRA-)"
|
||||
"^(PVE|PROXMOX|DEBIAN|UBUNTU|LINUX|VM-|SRV-|INFRA-)"
|
||||
],
|
||||
"links": [
|
||||
{ "label": "pfSense health", "type": "bucket", "bucket_prefix": "aw-pfsense-health_" },
|
||||
{ "label": "pfSense gateways", "type": "bucket", "bucket_prefix": "aw-pfsense-gateways_" },
|
||||
{ "label": "Все бакеты", "type": "buckets" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
return;
|
||||
}
|
||||
window.__awRuPatchBootstrapped = true;
|
||||
window.__awRuPatchVersion = "template-v13-category-builder-early-fix";
|
||||
document.documentElement.setAttribute("data-aw-ru-patch", "template-v13-category-builder-early-fix");
|
||||
window.__awRuPatchVersion = "template-v14-dlp-route-lite";
|
||||
document.documentElement.setAttribute("data-aw-ru-patch", "template-v14-dlp-route-lite");
|
||||
|
||||
const exact = new Map([
|
||||
["ActivityWatch", "АктивВотч"],
|
||||
@@ -24,6 +24,7 @@
|
||||
["Tools", "Инструменты"],
|
||||
["Raw Data", "Сырые данные"],
|
||||
["Summary", "Сводка"],
|
||||
["Worktime", "Рабочее время"],
|
||||
["All", "Все"],
|
||||
["None", "Нет"],
|
||||
["Date", "Дата"],
|
||||
@@ -236,6 +237,7 @@
|
||||
['Common words in "Uncategorized" events', 'Частые слова в событиях "Без категории"'],
|
||||
["No words with significant duration. You're good to go!", "Нет слов со значимой длительностью. Здесь всё в порядке."],
|
||||
["Top apps", "Топ приложений"],
|
||||
["Top Applications", "Топ приложений"],
|
||||
["Top titles", "Топ заголовков"],
|
||||
["Top URLs", "Топ URL"],
|
||||
["Top domains", "Топ доменов"],
|
||||
@@ -344,7 +346,16 @@
|
||||
'.aw-ru-pve-audit-value { font-size: 24px; font-weight: 700; }',
|
||||
'.aw-ru-pve-audit-table { width: 100%; border-collapse: collapse; margin-top: 8px; }',
|
||||
'.aw-ru-pve-audit-table th, .aw-ru-pve-audit-table td { padding: 6px 8px; border-bottom: 1px solid rgba(120,120,120,.18); vertical-align: top; text-align: left; font-size: 13px; }',
|
||||
'.aw-ru-pve-audit-muted { opacity: .72; font-size: 13px; }'
|
||||
'.aw-ru-pve-audit-muted { opacity: .72; font-size: 13px; }',
|
||||
'.aw-ru-rdp-center { margin: 16px 0; padding: 16px; border: 1px solid rgba(120,120,120,.35); border-radius: 8px; background: rgba(10,20,40,.04); }',
|
||||
'.aw-ru-rdp-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; margin: 12px 0 16px; }',
|
||||
'.aw-ru-rdp-card { border: 1px solid rgba(120,120,120,.22); border-radius: 8px; padding: 12px; background: rgba(255,255,255,.02); }',
|
||||
'.aw-ru-rdp-card h5 { margin: 0 0 6px; font-size: 13px; opacity: .8; }',
|
||||
'.aw-ru-rdp-value { font-size: 24px; font-weight: 700; }',
|
||||
'.aw-ru-rdp-table { width: 100%; border-collapse: collapse; margin-top: 8px; }',
|
||||
'.aw-ru-rdp-table th, .aw-ru-rdp-table td { padding: 6px 8px; border-bottom: 1px solid rgba(120,120,120,.18); vertical-align: top; text-align: left; font-size: 13px; }',
|
||||
'.aw-ru-rdp-links { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }',
|
||||
'.aw-ru-rdp-links a { display: inline-block; padding: 4px 8px; border-radius: 999px; background: rgba(90,140,255,.15); text-decoration: none; }'
|
||||
].join("\n");
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
@@ -370,6 +381,12 @@
|
||||
return "";
|
||||
}
|
||||
|
||||
function getCurrentActivityDayFromHash() {
|
||||
const hash = window.location.hash || "";
|
||||
const match = hash.match(/#\/activity\/[^/]+\/day\/([^/?#]+)/i);
|
||||
return match && match[1] ? decodeURIComponent(match[1]) : "today";
|
||||
}
|
||||
|
||||
function isPveLikeHost(host) {
|
||||
return /^pve[-_]/i.test(String(host || ""));
|
||||
}
|
||||
@@ -384,6 +401,230 @@
|
||||
return true;
|
||||
}
|
||||
|
||||
function isClientActivityRoute() {
|
||||
const hash = window.location.hash || "";
|
||||
const match = hash.match(/^#\/activity\/([^/]+)(?:\/day\/([^/]+))?\/view\/([^/?#]+)/i);
|
||||
if (!match) return false;
|
||||
const host = decodeURIComponent(match[1] || "");
|
||||
return isLikelyClientHost(host) && !isPveLikeHost(host);
|
||||
}
|
||||
|
||||
function getRdpReportBaseUrl() {
|
||||
const url = new URL(window.location.href);
|
||||
url.hash = "";
|
||||
url.search = "";
|
||||
url.pathname = "/reports/worktime/today";
|
||||
url.port = "5610";
|
||||
return url;
|
||||
}
|
||||
|
||||
function buildRdpReportUrl(format, day) {
|
||||
const url = getRdpReportBaseUrl();
|
||||
url.searchParams.set("day", day || "today");
|
||||
if (format) url.searchParams.set("format", format);
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
function normalizeActivityDay(day) {
|
||||
if (day && day !== "today") return day;
|
||||
const now = new Date();
|
||||
return [
|
||||
now.getFullYear(),
|
||||
String(now.getMonth() + 1).padStart(2, "0"),
|
||||
String(now.getDate()).padStart(2, "0")
|
||||
].join("-");
|
||||
}
|
||||
|
||||
function getActivityDayRange(day) {
|
||||
const normalizedDay = normalizeActivityDay(day);
|
||||
const start = new Date(normalizedDay + "T00:00:00");
|
||||
const end = new Date(normalizedDay + "T23:59:59");
|
||||
return { normalizedDay: normalizedDay, start: start, end: end };
|
||||
}
|
||||
|
||||
function formatActiveHhmm(totalSeconds) {
|
||||
const seconds = Math.max(0, Number(totalSeconds) || 0);
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
return String(hours).padStart(2, "0") + ":" + String(minutes).padStart(2, "0");
|
||||
}
|
||||
|
||||
function isWorktimeRowActive(data) {
|
||||
if (!data || typeof data !== "object") return false;
|
||||
if (typeof data.active === "boolean") return data.active;
|
||||
const state = String(data.state || "").trim().toLowerCase();
|
||||
return state === "active" || state === "активно";
|
||||
}
|
||||
|
||||
function formatDurationSeconds(totalSeconds) {
|
||||
const seconds = Math.max(0, Number(totalSeconds) || 0);
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
const secs = Math.floor(seconds % 60);
|
||||
if (hours > 0) return hours + "ч " + String(minutes).padStart(2, "0") + "м";
|
||||
if (minutes > 0) return minutes + "м " + String(secs).padStart(2, "0") + "с";
|
||||
return secs + "с";
|
||||
}
|
||||
|
||||
function formatIsoForUi(value) {
|
||||
if (!value) return "—";
|
||||
try {
|
||||
return new Date(value).toLocaleString();
|
||||
} catch (error) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchRdpWorktimeReport(host, day) {
|
||||
if (!host) return null;
|
||||
const cacheKey = host + "|" + (day || "today");
|
||||
if (!window.__awRuRdpReportCache) window.__awRuRdpReportCache = {};
|
||||
if (window.__awRuRdpReportCache[cacheKey]) return window.__awRuRdpReportCache[cacheKey];
|
||||
const range = getActivityDayRange(day);
|
||||
const bucketId = "aw-worktime-sessions_" + host;
|
||||
const params = new URLSearchParams();
|
||||
params.set("start", range.start.toISOString());
|
||||
params.set("end", new Date(range.end.getTime() + 1000).toISOString());
|
||||
params.set("limit", "100000");
|
||||
const response = await fetch("/api/0/buckets/" + encodeURIComponent(bucketId) + "/events?" + params.toString(), { credentials: "same-origin" });
|
||||
if (!response.ok) throw new Error("rdp-report-fetch-failed");
|
||||
const events = await response.json();
|
||||
if (!Array.isArray(events)) return null;
|
||||
const rowsByUser = new Map();
|
||||
events.forEach(function (event) {
|
||||
const data = event && event.data ? event.data : {};
|
||||
const ts = event && event.timestamp ? String(event.timestamp) : "";
|
||||
if (!ts) return;
|
||||
const tsDate = new Date(ts);
|
||||
if (Number.isNaN(tsDate.getTime())) return;
|
||||
const tsDay = [
|
||||
tsDate.getFullYear(),
|
||||
String(tsDate.getMonth() + 1).padStart(2, "0"),
|
||||
String(tsDate.getDate()).padStart(2, "0")
|
||||
].join("-");
|
||||
if (tsDay !== range.normalizedDay) return;
|
||||
const userId = String(data.userId || "");
|
||||
const userName = String(data.username || userId || "").trim();
|
||||
if (!userName) return;
|
||||
const key = userId || userName;
|
||||
if (!rowsByUser.has(key)) {
|
||||
rowsByUser.set(key, {
|
||||
user: userName,
|
||||
user_id: userId || userName,
|
||||
active_seconds: 0,
|
||||
first_activity: "",
|
||||
last_activity: "",
|
||||
sessions_count: new Set(),
|
||||
samples_count: 0,
|
||||
active_samples: 0
|
||||
});
|
||||
}
|
||||
const row = rowsByUser.get(key);
|
||||
row.samples_count += 1;
|
||||
if (data.sessionId !== undefined && data.sessionId !== null) row.sessions_count.add(String(data.sessionId));
|
||||
if (isWorktimeRowActive(data)) {
|
||||
const sampleSeconds = Math.max(0, Number(data.sampleSeconds || event.duration || 0));
|
||||
row.active_seconds += sampleSeconds;
|
||||
row.active_samples += 1;
|
||||
if (!row.first_activity || ts < row.first_activity) row.first_activity = ts;
|
||||
if (!row.last_activity || ts > row.last_activity) row.last_activity = ts;
|
||||
}
|
||||
});
|
||||
const payload = {
|
||||
host: host,
|
||||
report_date: range.normalizedDay,
|
||||
rows: Array.from(rowsByUser.values()).map(function (row) {
|
||||
return {
|
||||
user: row.user,
|
||||
user_id: row.user_id,
|
||||
active_seconds: row.active_seconds,
|
||||
active_hhmm: formatActiveHhmm(row.active_seconds),
|
||||
first_activity: row.first_activity,
|
||||
last_activity: row.last_activity,
|
||||
sessions_count: row.sessions_count.size,
|
||||
samples_count: row.samples_count,
|
||||
active_samples: row.active_samples
|
||||
};
|
||||
})
|
||||
};
|
||||
window.__awRuRdpReportCache[cacheKey] = payload;
|
||||
return payload;
|
||||
}
|
||||
|
||||
async function injectRdpWorktimeCenter(root) {
|
||||
if (!isClientActivityRoute()) return;
|
||||
const host = getCurrentHostFromHash();
|
||||
const day = getCurrentActivityDayFromHash();
|
||||
const report = await fetchRdpWorktimeReport(host, day);
|
||||
if (!report || !Array.isArray(report.rows) || !report.rows.length) return;
|
||||
|
||||
const totalActiveSeconds = report.rows.reduce(function (sum, row) {
|
||||
return sum + Math.max(0, Number(row && row.active_seconds || 0));
|
||||
}, 0);
|
||||
const activeUsers = report.rows.filter(function (row) {
|
||||
return Number(row && row.active_seconds || 0) > 0;
|
||||
});
|
||||
const topRows = activeUsers
|
||||
.slice()
|
||||
.sort(function (left, right) {
|
||||
return Number(right.active_seconds || 0) - Number(left.active_seconds || 0);
|
||||
})
|
||||
.slice(0, 5);
|
||||
|
||||
Array.from(root.querySelectorAll("li")).forEach(function (item) {
|
||||
const text = (item.textContent || "").trim();
|
||||
if (/^(?:Активное время|Time active):/i.test(text)) {
|
||||
item.textContent = "Активное время: " + formatDurationSeconds(totalActiveSeconds);
|
||||
}
|
||||
});
|
||||
|
||||
const heading = root.querySelector("h3");
|
||||
if (!heading || !heading.parentElement) return;
|
||||
|
||||
let center = root.querySelector("[data-aw-ru-rdp-center='1']");
|
||||
if (!center) {
|
||||
center = document.createElement("section");
|
||||
center.className = "aw-ru-rdp-center";
|
||||
center.setAttribute("data-aw-ru-rdp-center", "1");
|
||||
const anchor = heading.parentElement.querySelector("img") || null;
|
||||
heading.parentElement.insertBefore(center, anchor);
|
||||
}
|
||||
|
||||
const latestActivity = topRows.reduce(function (latest, row) {
|
||||
const value = row && row.last_activity ? String(row.last_activity) : "";
|
||||
if (!value) return latest;
|
||||
if (!latest) return value;
|
||||
return value > latest ? value : latest;
|
||||
}, "");
|
||||
|
||||
center.innerHTML =
|
||||
'<h4>RDP сводка</h4>' +
|
||||
'<p>Этот блок строится из bucket <code>aw-worktime-sessions</code> через AW API и показывает сводку по RDP-сессиям выбранного хоста.</p>' +
|
||||
'<div class="aw-ru-rdp-grid">' +
|
||||
'<section class="aw-ru-rdp-card"><h5>Активное время</h5><div class="aw-ru-rdp-value">' + escapeHtml(formatDurationSeconds(totalActiveSeconds)) + '</div></section>' +
|
||||
'<section class="aw-ru-rdp-card"><h5>Активных пользователей</h5><div class="aw-ru-rdp-value">' + escapeHtml(String(activeUsers.length)) + '</div></section>' +
|
||||
'<section class="aw-ru-rdp-card"><h5>Последняя активность</h5><div class="aw-ru-rdp-value" style="font-size:16px;">' + escapeHtml(formatIsoForUi(latestActivity)) + '</div></section>' +
|
||||
'</div>' +
|
||||
'<table class="aw-ru-rdp-table">' +
|
||||
'<thead><tr><th>Пользователь</th><th>Активное время</th><th>Первая активность</th><th>Последняя активность</th></tr></thead>' +
|
||||
'<tbody>' +
|
||||
(topRows.length ? topRows.map(function (row) {
|
||||
return '<tr>' +
|
||||
'<td>' + escapeHtml(row.user || row.user_id || "") + '</td>' +
|
||||
'<td>' + escapeHtml(row.active_hhmm || formatDurationSeconds(row.active_seconds || 0)) + '</td>' +
|
||||
'<td>' + escapeHtml(formatIsoForUi(row.first_activity || "")) + '</td>' +
|
||||
'<td>' + escapeHtml(formatIsoForUi(row.last_activity || "")) + '</td>' +
|
||||
'</tr>';
|
||||
}).join("") : '<tr><td colspan="4">Нет активных пользователей в отчёте.</td></tr>') +
|
||||
'</tbody>' +
|
||||
'</table>' +
|
||||
'<div class="aw-ru-rdp-links">' +
|
||||
'<a href="' + escapeHtml(buildRdpReportUrl("html", day)) + '">HTML</a>' +
|
||||
'<a href="' + escapeHtml(buildRdpReportUrl("csv", day)) + '">CSV</a>' +
|
||||
'<a href="' + escapeHtml(buildRdpReportUrl("", day)) + '">JSON</a>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
function enforceSafeActivityViewForPveHost() {
|
||||
const hash = window.location.hash || "";
|
||||
const match = hash.match(/^#\/activity\/([^/]+)(?:\/day\/([^/]+))?\/view\/([^/?#]+)/i);
|
||||
@@ -605,9 +846,15 @@
|
||||
}
|
||||
|
||||
function removeBadDlpLinks(root) {
|
||||
const badLinks = root.querySelectorAll("a[href*='/view/DLP']");
|
||||
badLinks.forEach(function (link) {
|
||||
const links = Array.from(root.querySelectorAll("a[href], [role='link']"));
|
||||
links.forEach(function (link) {
|
||||
const href = String(link.getAttribute("href") || "");
|
||||
const label = normalizeText(link.textContent || "");
|
||||
const isBrokenActivityDlpLink = /\/view\/dlp(?:[/?#]|$)/i.test(href);
|
||||
const isActivityTabDlpLabel = label === "DLP" && !!link.closest("li");
|
||||
if (!isBrokenActivityDlpLink && !isActivityTabDlpLabel) return;
|
||||
const item = link.closest("li") || link;
|
||||
if (item && item.getAttribute && item.getAttribute("data-aw-ru-dlp-item") === "1") return;
|
||||
item.remove();
|
||||
});
|
||||
}
|
||||
@@ -718,11 +965,9 @@
|
||||
{
|
||||
id: "virtual-infra",
|
||||
name: "Virtual servers + Proxmox",
|
||||
description: "Инфраструктурные VM, Proxmox и сетевые узлы.",
|
||||
patterns: ["^(PFSENSE|PVE|PROXMOX|DEBIAN|UBUNTU|LINUX|VM-|SRV-|INFRA-)"],
|
||||
description: "Инфраструктурные VM и узлы Proxmox.",
|
||||
patterns: ["^(PVE|PROXMOX|DEBIAN|UBUNTU|LINUX|VM-|SRV-|INFRA-)"],
|
||||
links: [
|
||||
{ label: "pfSense health", type: "bucket", bucket_prefix: "aw-pfsense-health_" },
|
||||
{ label: "pfSense gateways", type: "bucket", bucket_prefix: "aw-pfsense-gateways_" },
|
||||
{ label: "Все бакеты", type: "buckets" }
|
||||
]
|
||||
}
|
||||
@@ -785,9 +1030,6 @@
|
||||
"aw-pve-webadmin-events_",
|
||||
"aw-pve-task-events_",
|
||||
"aw-dlp-incidents_",
|
||||
"aw-pfsense-health_",
|
||||
"aw-pfsense-gateways_",
|
||||
"aw-pfsense-interfaces_"
|
||||
];
|
||||
for (const prefix of prefixes) {
|
||||
if (bucketId.indexOf(prefix) === 0) {
|
||||
@@ -1129,7 +1371,9 @@
|
||||
try {
|
||||
await saveDlpReview(host, event, row);
|
||||
state.reviews = collapseReviewEvents(await loadBucketEvents("aw-dlp-review_" + host, 200));
|
||||
renderDlpTableRows(center, host);
|
||||
renderDlpReviewManager(center, host);
|
||||
center.querySelector("[data-aw-ru-dlp-status]").textContent =
|
||||
"Событий: " + state.events.length + " · правил: " + state.activeRules.length + "/" + state.rules.length + " · review: " + state.reviews.filter(function (review) { return !(review.data && review.data.review && review.data.review.archived); }).length + "/" + state.reviews.length;
|
||||
message.textContent = "Review сохранен.";
|
||||
} catch (error) {
|
||||
message.textContent = "Ошибка сохранения review: " + error.message;
|
||||
@@ -1170,8 +1414,17 @@
|
||||
if (!hayabusa) return "";
|
||||
const status = String(hayabusa.status || "");
|
||||
const mode = String(hayabusa.mode || "");
|
||||
const caseHost = normalizeText(c && c.host);
|
||||
const forensicHost = normalizeText(hayabusa.host);
|
||||
const reportDir = String(hayabusa.report_dir || "");
|
||||
const title = reportDir ? ' title="' + escapeHtml(reportDir) + '"' : "";
|
||||
const hostMismatch = caseHost && forensicHost && caseHost !== forensicHost;
|
||||
const titleParts = [];
|
||||
if (reportDir) titleParts.push(reportDir);
|
||||
if (hostMismatch) titleParts.push("host mismatch: case=" + caseHost + " forensic=" + forensicHost);
|
||||
const title = titleParts.length ? ' title="' + escapeHtml(titleParts.join(" | ")) + '"' : "";
|
||||
if (hostMismatch) {
|
||||
return '<span' + title + '>Hayabusa host-mismatch · ' + escapeHtml(forensicHost) + '</span>';
|
||||
}
|
||||
return '<span' + title + '>Hayabusa ' + escapeHtml(status) + (mode ? " · " + escapeHtml(mode) : "") + '</span>';
|
||||
}
|
||||
const rows = (cases || []).map(function (c) {
|
||||
@@ -1868,6 +2121,16 @@
|
||||
});
|
||||
}
|
||||
|
||||
function applyTextAndNavigationPatches(root) {
|
||||
if (!root) return;
|
||||
walk(root);
|
||||
translateAttributes(root);
|
||||
hideNoiseNavigation(root);
|
||||
hidePveAuditTabForRegularHost(root);
|
||||
patchActivityHeading(root);
|
||||
patchCategoryBuilderHostLabel(root);
|
||||
}
|
||||
|
||||
function detachObserver() {
|
||||
if (!observerAttached) return;
|
||||
observer.disconnect();
|
||||
@@ -1887,24 +2150,13 @@
|
||||
try {
|
||||
const routeKey = window.location.hash || "#";
|
||||
const routeChanged = routeKey !== staticPatchRouteKey;
|
||||
enforceSafeActivityViewForPveHost();
|
||||
ensureSettingsHost();
|
||||
ensureHostGroupsData().catch(function () {});
|
||||
normalizeCategoryBuilderUnknownHostRefs();
|
||||
const dlpRoute = isDlpSignalBucketRoute();
|
||||
installCategoryBuilderNetworkPatch();
|
||||
injectStyles();
|
||||
if (routeChanged) {
|
||||
walk(document.body);
|
||||
translateAttributes(document.body);
|
||||
hideNoiseNavigation(document.body);
|
||||
hidePveAuditTabForRegularHost(document.body);
|
||||
patchActivityHeading(document.body);
|
||||
patchCategoryBuilderHostLabel(document.body);
|
||||
staticPatchRouteKey = routeKey;
|
||||
}
|
||||
injectPveAuditCenter(document.body);
|
||||
if (dlpRoute) {
|
||||
ensureSettingsHost();
|
||||
injectDlpNavigation(document.body);
|
||||
if (isDlpSignalBucketRoute() && dlpOverlayFailureCount === 0) {
|
||||
if (dlpOverlayFailureCount === 0) {
|
||||
try {
|
||||
injectDlpReviewCenter(document.body);
|
||||
} catch (error) {
|
||||
@@ -1912,9 +2164,23 @@
|
||||
const existing = document.body.querySelector("[data-aw-ru-dlp-center='1']");
|
||||
if (existing && existing.parentElement) existing.parentElement.removeChild(existing);
|
||||
}
|
||||
} else if (!isDlpSignalBucketRoute()) {
|
||||
injectDlpReviewCenter(document.body);
|
||||
}
|
||||
applyTextAndNavigationPatches(document.body);
|
||||
staticPatchRouteKey = routeKey;
|
||||
return;
|
||||
}
|
||||
enforceSafeActivityViewForPveHost();
|
||||
ensureSettingsHost();
|
||||
ensureHostGroupsData().catch(function () {});
|
||||
normalizeCategoryBuilderUnknownHostRefs();
|
||||
applyTextAndNavigationPatches(document.body);
|
||||
if (routeChanged) {
|
||||
staticPatchRouteKey = routeKey;
|
||||
}
|
||||
injectPveAuditCenter(document.body);
|
||||
injectRdpWorktimeCenter(document.body).catch(function () {});
|
||||
injectDlpNavigation(document.body);
|
||||
injectDlpReviewCenter(document.body);
|
||||
injectDlpAlertsCenter(document.body);
|
||||
injectHostGroupsCenter(document.body).catch(function () {});
|
||||
redirectBareTrendsRoute();
|
||||
@@ -1947,6 +2213,7 @@
|
||||
});
|
||||
window.addEventListener("hashchange", function () {
|
||||
redirectBareTrendsRoute();
|
||||
dlpOverlayFailureCount = 0;
|
||||
staticPatchRouteKey = "";
|
||||
scheduleApplyPatch();
|
||||
});
|
||||
|
||||
@@ -229,14 +229,17 @@ def main() -> int:
|
||||
|
||||
parser = argparse.ArgumentParser(description="Unified AW-RUS health orchestrator")
|
||||
parser.add_argument("--aw-server", default=env("AW_SERVER_URL", "http://127.0.0.1:5600"))
|
||||
parser.add_argument("--worktime-api", default=env("AW_WORKTIME_REPORT_BASE", "http://127.0.0.1:5610"))
|
||||
parser.add_argument(
|
||||
"--worktime-api",
|
||||
default=env("AW_RUS_HEALTH_WORKTIME_API", env("AW_WORKTIME_REPORT_BASE", "http://127.0.0.1:5610")),
|
||||
)
|
||||
parser.add_argument("--rdp-host", default=env("AW_MONITORED_WINDOWS_HOST", "192.168.100.18"))
|
||||
parser.add_argument("--rdp-hostname", default=env("AW_MONITORED_WINDOWS_HOSTNAME", "SHARKON2025"))
|
||||
parser.add_argument("--state-dir", default=env("AW_RUS_HEALTH_STATE_DIR", "/var/lib/activitywatch/health"))
|
||||
parser.add_argument("--validation-dir", default=env("AW_RUS_HEALTH_VALIDATION_DIR", "/var/lib/activitywatch/health/windows-validation"))
|
||||
parser.add_argument("--session-max-age-seconds", type=int, default=int(env("AW_RUS_HEALTH_SESSION_MAX_AGE_SECONDS", "900")))
|
||||
parser.add_argument("--interactive-max-age-seconds", type=int, default=int(env("AW_RUS_HEALTH_INTERACTIVE_MAX_AGE_SECONDS", "900")))
|
||||
parser.add_argument("--session-events-max-age-seconds", type=int, default=int(env("AW_RUS_HEALTH_SESSION_EVENTS_MAX_AGE_SECONDS", "604800")))
|
||||
parser.add_argument("--session-events-max-age-seconds", type=int, default=int(env("AW_RUS_HEALTH_SESSION_EVENTS_MAX_AGE_SECONDS", "86400")))
|
||||
parser.add_argument("--validation-max-age-seconds", type=int, default=int(env("AW_RUS_HEALTH_VALIDATION_MAX_AGE_SECONDS", "259200")))
|
||||
parser.add_argument("--tcp-timeout-seconds", type=float, default=float(env("AW_RUS_HEALTH_TCP_TIMEOUT_SECONDS", "3")))
|
||||
parser.add_argument("--json", action="store_true")
|
||||
@@ -257,9 +260,8 @@ def main() -> int:
|
||||
report.add("http:aw-server", "fail", f"activitywatch API failed: {exc}", url=f"{aw_api_base}/info")
|
||||
|
||||
try:
|
||||
payload = http_json(args.worktime_api.rstrip("/") + "/reports/worktime/today")
|
||||
rows = len(payload) if isinstance(payload, list) else None
|
||||
report.add("http:worktime-api", "ok", "worktime API responded", rows=rows)
|
||||
payload = http_json(args.worktime_api.rstrip("/") + "/health")
|
||||
report.add("http:worktime-api", "ok", "worktime API responded", payload=payload if isinstance(payload, dict) else {})
|
||||
except Exception as exc:
|
||||
report.add("http:worktime-api", "fail", f"worktime API failed: {exc}", url=args.worktime_api)
|
||||
|
||||
@@ -322,6 +324,9 @@ def main() -> int:
|
||||
missing_status="fail",
|
||||
stale_status="warn",
|
||||
)
|
||||
if session_status == "warn" and session_details.get("age_seconds") is not None:
|
||||
session_status = "ok"
|
||||
session_summary = f"event-driven ({session_details['age_seconds']}s since last logon marker)"
|
||||
report.add("bucket:session-events", session_status, session_summary, **session_details)
|
||||
|
||||
validation_dir = Path(args.validation_dir)
|
||||
|
||||
@@ -13,9 +13,17 @@ AW_SERVER_USER=activitywatch
|
||||
AW_SERVER_GROUP=activitywatch
|
||||
|
||||
# Worktime API Configuration
|
||||
AW_WORKTIME_REPORT_BASE=http://10.10.10.13:5610
|
||||
AW_SERVER_PUBLIC_HOST=aw-server
|
||||
AW_WORKTIME_REPORT_BASE=http://aw-server:5610
|
||||
AW_WORKTIME_TZ=Europe/Moscow
|
||||
AW_SERVER_URL=http://127.0.0.1:5600
|
||||
AW_DLP_AW_API_BASE=http://127.0.0.1:5600/api/0
|
||||
AW_WORKTIME_MANAGER_CACHE_TTL_SECONDS=300
|
||||
AW_WORKTIME_MANAGER_ALIASES_JSON=/etc/activitywatch/worktime-manager-aliases.json
|
||||
AW_WORKTIME_MANAGER_EXCLUDE_USERS=
|
||||
AW_WORKTIME_MANAGEMENT_WARM_ENABLED=1
|
||||
AW_WORKTIME_MANAGEMENT_WARM_URL=http://127.0.0.1:5610/reports/worktime/management?day=today&format=json
|
||||
AW_WORKTIME_MANAGEMENT_WARM_TIMEOUT_SECONDS=70
|
||||
|
||||
# DLP IOC Configuration
|
||||
AW_DLP_IOC_DIR=/opt/activitywatch/dlp-ioc/output
|
||||
@@ -39,8 +47,18 @@ AW_EXPECT_LANDINGPAGE=/activity/SHARKON2025/view/
|
||||
AW_HEALTH_STRICT_FILEOPS=0
|
||||
AW_MONITORED_WINDOWS_HOST=192.168.100.18
|
||||
AW_MONITORED_WINDOWS_HOSTNAME=SHARKON2025
|
||||
AW_RUS_HEALTH_WORKTIME_API=http://127.0.0.1:5610
|
||||
AW_RUS_HEALTH_STATE_DIR=/var/lib/activitywatch/health
|
||||
AW_RUS_HEALTH_VALIDATION_DIR=/var/lib/activitywatch/health/windows-validation
|
||||
AW_RUS_HEALTH_SESSION_EVENTS_MAX_AGE_SECONDS=86400
|
||||
|
||||
# Hayabusa auto-case / alerting
|
||||
AW_HAYABUSA_AUTO_CASE_ENABLED=true
|
||||
AW_HAYABUSA_AUTO_CASE_MIN_SEVERITY=medium
|
||||
AW_HAYABUSA_TELEGRAM_ENABLED=true
|
||||
AW_HAYABUSA_TELEGRAM_MIN_SEVERITY=high
|
||||
AW_HAYABUSA_TELEGRAM_BOT_TOKEN=
|
||||
AW_HAYABUSA_TELEGRAM_CHAT_IDS=
|
||||
|
||||
# Integration Test Configuration
|
||||
AW_INTEGRATION_TEST_ENABLED=false
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,15 @@
|
||||
(function () {
|
||||
var reportBase = "__AW_WORKTIME_REPORT_BASE__";
|
||||
function defaultDayQuery() {
|
||||
var now = new Date();
|
||||
return now.getHours() < 6 ? "day=yesterday" : "day=today";
|
||||
return "day=today";
|
||||
}
|
||||
|
||||
var dayQuery = defaultDayQuery();
|
||||
var htmlUrl = reportBase + "/reports/worktime/today?format=html&" + dayQuery;
|
||||
var csvUrl = reportBase + "/reports/worktime/today?format=csv&" + dayQuery;
|
||||
var jsonUrl = reportBase + "/reports/worktime/today?" + dayQuery;
|
||||
var managerHtmlUrl = reportBase + "/reports/worktime/management?format=html&" + dayQuery;
|
||||
var managerJsonUrl = reportBase + "/reports/worktime/management?" + dayQuery;
|
||||
var existing = document.getElementById("aw-report-links");
|
||||
if (!existing) return;
|
||||
|
||||
@@ -17,6 +18,8 @@
|
||||
'<a href="' + htmlUrl + '" style="color:#fcd34d" target="_blank">HTML</a> | ' +
|
||||
'<a href="' + csvUrl + '" style="color:#7dd3fc" target="_blank">CSV</a> | ' +
|
||||
'<a href="' + jsonUrl + '" style="color:#86efac" target="_blank">JSON</a> | ' +
|
||||
'<a href="' + managerHtmlUrl + '" style="color:#fca5a5" target="_blank">Менеджмент</a> | ' +
|
||||
'<a href="' + managerJsonUrl + '" style="color:#c4b5fd" target="_blank">Mgmt JSON</a> | ' +
|
||||
'<a href="#" id="aw-report-toggle" style="color:#f9fafb">Панель</a>';
|
||||
|
||||
var panel = document.createElement("div");
|
||||
@@ -40,10 +43,11 @@
|
||||
'<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</div>' +
|
||||
'<div style="display:flex;gap:12px;align-items:center">' +
|
||||
'<a href="' + htmlUrl + '" target="_blank" style="color:#93c5fd;text-decoration:none">Открыть</a>' +
|
||||
'<a href="' + managerHtmlUrl + '" target="_blank" style="color:#fca5a5;text-decoration:none">Открыть менеджмент</a>' +
|
||||
'<a href="' + htmlUrl + '" target="_blank" style="color:#93c5fd;text-decoration:none">Открыть RDP</a>' +
|
||||
'<a href="#" id="aw-report-close" style="color:#fff;text-decoration:none">Закрыть</a>' +
|
||||
"</div></div>" +
|
||||
'<iframe src="' + htmlUrl + '" title="Отчёт по работе в RDP" style="border:0;width:100%;height:calc(100% - 42px);background:#fff"></iframe>';
|
||||
'<iframe src="' + managerHtmlUrl + '" title="Управленческий отчёт по работе в RDP" style="border:0;width:100%;height:calc(100% - 42px);background:#fff"></iframe>';
|
||||
|
||||
document.body.appendChild(panel);
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ 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_ALIASES_SRC="$BOOTSTRAP_DIR/worktime-manager-aliases.example.json"
|
||||
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"
|
||||
@@ -110,6 +111,14 @@ if [[ -f "$WORKTIME_API_SERVICE_SRC" ]]; then
|
||||
systemctl --no-pager --full status aw-worktime-api.service || true
|
||||
fi
|
||||
|
||||
if [[ -f "$WORKTIME_ALIASES_SRC" ]]; then
|
||||
install -d -m 0755 /etc/activitywatch
|
||||
install -m 0644 "$WORKTIME_ALIASES_SRC" /etc/activitywatch/worktime-manager-aliases.json.example
|
||||
if [[ ! -f /etc/activitywatch/worktime-manager-aliases.json ]]; then
|
||||
install -m 0644 "$WORKTIME_ALIASES_SRC" /etc/activitywatch/worktime-manager-aliases.json
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -f "$WORKTIME_UI_BRIDGE_SRC" ]]; then
|
||||
install -m 0755 "$WORKTIME_UI_BRIDGE_SRC" /usr/local/bin/aw-worktime-ui-bridge.py
|
||||
fi
|
||||
|
||||
@@ -16,11 +16,6 @@
|
||||
{ "type": "top_apps", "size": 3, "props": {} }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "DLP",
|
||||
"name": "DLP",
|
||||
"elements": []
|
||||
},
|
||||
{
|
||||
"id": "worktime",
|
||||
"name": "Worktime",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,7 @@ $ErrorActionPreference = 'Stop'
|
||||
|
||||
Add-Type -AssemblyName UIAutomationClient
|
||||
Add-Type -AssemblyName UIAutomationTypes
|
||||
Add-Type -AssemblyName System.Net.Http
|
||||
|
||||
Add-Type @"
|
||||
using System;
|
||||
@@ -48,6 +49,35 @@ function Get-DeploymentConfig {
|
||||
return $null
|
||||
}
|
||||
|
||||
function Invoke-AwJsonPost {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Uri,
|
||||
[Parameter(Mandatory = $true)][string]$Json
|
||||
)
|
||||
|
||||
$httpClient = $null
|
||||
$content = $null
|
||||
try {
|
||||
$httpClient = New-Object System.Net.Http.HttpClient
|
||||
$content = New-Object System.Net.Http.StringContent($Json, [System.Text.Encoding]::UTF8, "application/json")
|
||||
$response = $httpClient.PostAsync($Uri, $content).Result
|
||||
if (-not $response.IsSuccessStatusCode) {
|
||||
$status = [int]$response.StatusCode
|
||||
$reason = [string]$response.ReasonPhrase
|
||||
$body = $response.Content.ReadAsStringAsync().Result
|
||||
throw "HTTP POST failed status=$status reason=$reason body=$body"
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if ($null -ne $content) {
|
||||
$content.Dispose()
|
||||
}
|
||||
if ($null -ne $httpClient) {
|
||||
$httpClient.Dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$deploymentConfig = Get-DeploymentConfig -Path $ConfigPath
|
||||
$resolvedServerHost = if ($ServerHost) { $ServerHost } elseif ($deploymentConfig) { [string]$deploymentConfig.server.host } else { throw 'Укажите ServerHost или подготовьте deployment-config.json.' }
|
||||
$resolvedServerPort = if ($PSBoundParameters.ContainsKey('ServerPort')) { $ServerPort } elseif ($deploymentConfig) { [int]$deploymentConfig.server.port } else { 5600 }
|
||||
@@ -542,7 +572,7 @@ function Send-DlpIncidentHeartbeat {
|
||||
} + $captureData
|
||||
} | ConvertTo-Json -Depth 5 -Compress
|
||||
|
||||
Invoke-RestMethod -Method Post -Uri "$($script:ApiBase)/buckets/$bucketId/heartbeat?pulsetime=$resolvedPulseSeconds" -ContentType 'application/json' -Body $event -TimeoutSec 15 -DisableKeepAlive | Out-Null
|
||||
Invoke-AwJsonPost -Uri "$($script:ApiBase)/buckets/$bucketId/heartbeat?pulsetime=$resolvedPulseSeconds" -Json $event
|
||||
}
|
||||
|
||||
function Get-FileSha256Hex {
|
||||
@@ -717,7 +747,7 @@ function Ensure-Bucket {
|
||||
} | ConvertTo-Json -Compress
|
||||
|
||||
try {
|
||||
Invoke-RestMethod -Method Post -Uri "$($script:ApiBase)/buckets/$BucketId" -ContentType 'application/json; charset=utf-8' -Body ([Text.Encoding]::UTF8.GetBytes($body)) | Out-Null
|
||||
Invoke-AwJsonPost -Uri "$($script:ApiBase)/buckets/$BucketId" -Json $body
|
||||
}
|
||||
catch {
|
||||
Invoke-RestMethod -Method Get -Uri "$($script:ApiBase)/buckets/$BucketId" | Out-Null
|
||||
@@ -747,7 +777,40 @@ function Send-Heartbeat {
|
||||
}
|
||||
} | ConvertTo-Json -Depth 4 -Compress
|
||||
|
||||
Invoke-RestMethod -Method Post -Uri "$($script:ApiBase)/buckets/$BucketId/heartbeat?pulsetime=$resolvedPulseSeconds" -ContentType 'application/json' -Body $event -TimeoutSec 15 -DisableKeepAlive | Out-Null
|
||||
Invoke-AwJsonPost -Uri "$($script:ApiBase)/buckets/$BucketId/heartbeat?pulsetime=$resolvedPulseSeconds" -Json $event
|
||||
}
|
||||
|
||||
function Send-WindowHeartbeat {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[pscustomobject]$Context
|
||||
)
|
||||
|
||||
if (-not $Context) {
|
||||
return
|
||||
}
|
||||
|
||||
$processName = [string]$Context.ProcessName
|
||||
$title = [string]$Context.Title
|
||||
if ([string]::IsNullOrWhiteSpace($processName) -and [string]::IsNullOrWhiteSpace($title)) {
|
||||
return
|
||||
}
|
||||
|
||||
$bucketId = 'aw-watcher-window_' + $script:Hostname
|
||||
Ensure-Bucket -BucketId $bucketId -ClientName 'aw-watcher-window' -BucketType 'currentwindow'
|
||||
|
||||
$event = @{
|
||||
timestamp = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ')
|
||||
duration = 0
|
||||
data = @{
|
||||
app = if ([string]::IsNullOrWhiteSpace($processName)) { 'unknown.exe' } else { "$processName.exe" }
|
||||
title = $title
|
||||
source = 'uia-native'
|
||||
sessionId = $script:SessionId
|
||||
}
|
||||
} | ConvertTo-Json -Depth 4 -Compress
|
||||
|
||||
Invoke-AwJsonPost -Uri "$($script:ApiBase)/buckets/$bucketId/heartbeat?pulsetime=$resolvedPulseSeconds" -Json $event
|
||||
}
|
||||
|
||||
function Send-CategoryHeartbeat {
|
||||
@@ -784,19 +847,62 @@ function Send-CategoryHeartbeat {
|
||||
}
|
||||
} | ConvertTo-Json -Depth 4 -Compress
|
||||
|
||||
Invoke-RestMethod -Method Post -Uri "$($script:ApiBase)/buckets/$bucketId/heartbeat?pulsetime=$resolvedPulseSeconds" -ContentType 'application/json' -Body $event -TimeoutSec 15 -DisableKeepAlive | Out-Null
|
||||
Invoke-AwJsonPost -Uri "$($script:ApiBase)/buckets/$bucketId/heartbeat?pulsetime=$resolvedPulseSeconds" -Json $event
|
||||
}
|
||||
|
||||
function Send-CollectorHealthHeartbeat {
|
||||
param(
|
||||
$Context = $null,
|
||||
[string]$DetectedUrl = $null
|
||||
)
|
||||
|
||||
$bucketId = 'aw-detmir-web-category_' + $script:Hostname
|
||||
Ensure-Bucket -BucketId $bucketId -ClientName 'aw-detmir-web-category' -BucketType 'aw.web.category'
|
||||
|
||||
$foregroundProcess = ''
|
||||
$foregroundTitle = ''
|
||||
$browserDetected = $false
|
||||
if ($Context) {
|
||||
$foregroundProcess = [string]$Context.ProcessName
|
||||
$foregroundTitle = [string]$Context.Title
|
||||
$browserDetected = $script:BrowserMap.ContainsKey($foregroundProcess)
|
||||
}
|
||||
|
||||
$event = @{
|
||||
timestamp = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ')
|
||||
duration = 0
|
||||
data = @{
|
||||
signalType = 'collector_health'
|
||||
username = $env:USERNAME
|
||||
hostname = $script:Hostname
|
||||
sessionId = $script:SessionId
|
||||
foregroundProcess = $foregroundProcess
|
||||
foregroundTitle = $foregroundTitle
|
||||
browserDetected = $browserDetected
|
||||
urlDetected = -not [string]::IsNullOrWhiteSpace($DetectedUrl)
|
||||
}
|
||||
} | ConvertTo-Json -Depth 5 -Compress
|
||||
|
||||
Invoke-AwJsonPost -Uri "$($script:ApiBase)/buckets/$bucketId/heartbeat?pulsetime=$resolvedPulseSeconds" -Json $event
|
||||
}
|
||||
|
||||
Load-CustomCategoryRules -Path $resolvedRulesPath
|
||||
Load-DlpPolicy -Path $resolvedPolicyPath
|
||||
Write-CollectorLog ("коллектор запущен для {0}" -f $script:ApiBase)
|
||||
|
||||
$lastHealth = [datetime]::MinValue
|
||||
while ($true) {
|
||||
$context = $null
|
||||
$detectedUrl = $null
|
||||
try {
|
||||
$context = Get-ForegroundWindowContext
|
||||
if ($context) {
|
||||
Send-WindowHeartbeat -Context $context
|
||||
}
|
||||
if ($context -and $script:BrowserMap.ContainsKey($context.ProcessName)) {
|
||||
$url = Get-BrowserUrlFromWindow -Handle $context.Handle
|
||||
if ($url) {
|
||||
$detectedUrl = $url
|
||||
$browserKey = $script:BrowserMap[$context.ProcessName]
|
||||
$domain = Get-HostFromUrl -Url $url
|
||||
if (-not $domain) {
|
||||
@@ -832,5 +938,16 @@ while ($true) {
|
||||
Write-CollectorLog ("ошибка коллектора: {0}" -f $_.Exception.Message)
|
||||
}
|
||||
|
||||
$nowUtc = (Get-Date).ToUniversalTime()
|
||||
if (($nowUtc - $lastHealth).TotalSeconds -ge [Math]::Max($resolvedPulseSeconds, $resolvedPollSeconds)) {
|
||||
try {
|
||||
Send-CollectorHealthHeartbeat -Context $context -DetectedUrl $detectedUrl
|
||||
$lastHealth = $nowUtc
|
||||
}
|
||||
catch {
|
||||
Write-CollectorLog ("ошибка heartbeat: {0}" -f $_.Exception.Message)
|
||||
}
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds $resolvedPollSeconds
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ param(
|
||||
[int]$EvtxRetentionDays = 14,
|
||||
[string[]]$EvtxChannels = @(),
|
||||
[bool]$LogonMarkerEnabled = $true,
|
||||
[bool]$ProcessEventsEnabled = $true,
|
||||
[string]$AwHostname,
|
||||
[string]$CustomRulesPath,
|
||||
[string]$CustomPolicyPath,
|
||||
@@ -39,6 +40,17 @@ param(
|
||||
[string]$PolicyEngineScheme = 'http',
|
||||
[int]$PolicyRefreshSeconds = 300,
|
||||
[string]$PolicyCachePath,
|
||||
[bool]$HayabusaAutoUploadEnabled = $true,
|
||||
[int]$HayabusaAutoUploadIntervalHours = 6,
|
||||
[int]$HayabusaAutoUploadHoursBack = 6,
|
||||
[string]$HayabusaAutoUploadMode = 'incident',
|
||||
[string]$HayabusaAutoUploadTaskName = 'ActivityWatch Hayabusa Upload',
|
||||
[bool]$File1CAutoUploadEnabled = $true,
|
||||
[int]$File1CAutoUploadIntervalHours = 6,
|
||||
[string]$File1CAutoUploadTaskName = 'ActivityWatch File1C Upload',
|
||||
[string]$File1CTargetHost,
|
||||
[string]$File1CTargetUser = 'igor',
|
||||
[string]$File1CRegistryWorkbookPath = 'E:\USER1\СПИСОК ПРЕДПРИЯТИЙ И ИХ РАСПРЕДЕЛЕНИЕ.xlsx',
|
||||
[switch]$IntegrationTestEnabled
|
||||
)
|
||||
|
||||
@@ -64,6 +76,8 @@ $emailCollectorSource = Join-Path $PSScriptRoot 'email-outbound-collector.ps1'
|
||||
$fileCollectorSource = Join-Path $PSScriptRoot 'file-operations-collector.ps1'
|
||||
$sessionCollectorSource = Join-Path $PSScriptRoot 'worktime-session-collector.ps1'
|
||||
$evtxExportScriptSource = Join-Path $PSScriptRoot 'export-evtx-for-hayabusa.ps1'
|
||||
$hayabusaUploadScriptSource = Join-Path $PSScriptRoot 'export-upload-hayabusa-to-aw-server.ps1'
|
||||
$file1cTelemetryScriptSource = Join-Path $PSScriptRoot 'export-upload-file-1c-telemetry.ps1'
|
||||
$exampleRulesSource = Join-Path $PSScriptRoot 'web-category-rules.example.json'
|
||||
$examplePolicySource = Join-Path $PSScriptRoot 'dlp-policy.example.json'
|
||||
|
||||
@@ -83,6 +97,8 @@ $assetResult = Copy-ActivityWatchCollectorAssets `
|
||||
-FileCollectorScriptSource $fileCollectorSource `
|
||||
-SessionCollectorScriptSource $sessionCollectorSource `
|
||||
-EvtxExportScriptSource $evtxExportScriptSource `
|
||||
-HayabusaUploadScriptSource $hayabusaUploadScriptSource `
|
||||
-File1CTelemetryScriptSource $file1cTelemetryScriptSource `
|
||||
-ExampleRulesSource $exampleRulesSource `
|
||||
-ExamplePolicySource $examplePolicySource `
|
||||
-StateRoot $StateRoot `
|
||||
@@ -107,6 +123,8 @@ $config = New-ActivityWatchDeploymentConfig `
|
||||
-FileCollectorScript $assetResult.FileCollectorScript `
|
||||
-SessionCollectorScript $assetResult.SessionCollectorScript `
|
||||
-EvtxExportScript $assetResult.EvtxExportScript `
|
||||
-HayabusaUploadScript $assetResult.HayabusaUploadScript `
|
||||
-File1CTelemetryScript $assetResult.File1CTelemetryScript `
|
||||
-RulesPath $assetResult.ActiveRules `
|
||||
-PolicyPath $assetResult.ActivePolicy `
|
||||
-PollSeconds $PollSeconds `
|
||||
@@ -123,6 +141,7 @@ $config = New-ActivityWatchDeploymentConfig `
|
||||
-EvtxRetentionDays $EvtxRetentionDays `
|
||||
-EvtxChannels $EvtxChannels `
|
||||
-LogonMarkerEnabled $LogonMarkerEnabled `
|
||||
-ProcessEventsEnabled $ProcessEventsEnabled `
|
||||
-AwHostname $AwHostname `
|
||||
-PolicyMode $PolicyMode `
|
||||
-PolicyEngineEnabled $PolicyEngineEnabled `
|
||||
@@ -131,6 +150,17 @@ $config = New-ActivityWatchDeploymentConfig `
|
||||
-PolicyEngineScheme $PolicyEngineScheme `
|
||||
-PolicyRefreshSeconds $PolicyRefreshSeconds `
|
||||
-PolicyCachePath $PolicyCachePath `
|
||||
-HayabusaAutoUploadEnabled $HayabusaAutoUploadEnabled `
|
||||
-HayabusaAutoUploadIntervalHours $HayabusaAutoUploadIntervalHours `
|
||||
-HayabusaAutoUploadHoursBack $HayabusaAutoUploadHoursBack `
|
||||
-HayabusaAutoUploadMode $HayabusaAutoUploadMode `
|
||||
-HayabusaAutoUploadTaskName $HayabusaAutoUploadTaskName `
|
||||
-File1CAutoUploadEnabled $File1CAutoUploadEnabled `
|
||||
-File1CAutoUploadIntervalHours $File1CAutoUploadIntervalHours `
|
||||
-File1CAutoUploadTaskName $File1CAutoUploadTaskName `
|
||||
-File1CTargetHost $File1CTargetHost `
|
||||
-File1CTargetUser $File1CTargetUser `
|
||||
-File1CRegistryWorkbookPath $File1CRegistryWorkbookPath `
|
||||
-LaunchScriptPath $launchScriptPath `
|
||||
-RecoveryScriptPath $recoveryScriptPath `
|
||||
-UserTasks $taskDefinitions `
|
||||
@@ -142,6 +172,8 @@ Remove-LegacyActivityWatchEntries
|
||||
Set-ActivityWatchAcl -InstallRoot $InstallRoot -StateRoot $StateRoot -LogsRoot $logsRoot
|
||||
Register-ActivityWatchUserTasks -TaskDefinitions $taskDefinitions -LaunchScriptPath $launchScriptPath -ConfigPath $configPath
|
||||
Register-ActivityWatchRecoveryTask -TaskName $config.recovery.taskName -RecoveryScriptPath $recoveryScriptPath -ConfigPath $configPath
|
||||
Register-ActivityWatchHayabusaAutoUploadTask -ConfigPath $configPath
|
||||
Register-ActivityWatchFile1CAutoUploadTask -ConfigPath $configPath
|
||||
Start-ActivityWatchTasks -TaskDefinitions $taskDefinitions -RecoveryTaskName $config.recovery.taskName
|
||||
|
||||
Write-Host 'ActivityWatch развёрнут для пользователей:'
|
||||
|
||||
@@ -27,6 +27,7 @@ param(
|
||||
[int]$EvtxRetentionDays = 14,
|
||||
[string[]]$EvtxChannels = @(),
|
||||
[bool]$LogonMarkerEnabled = $true,
|
||||
[bool]$ProcessEventsEnabled = $true,
|
||||
[string]$AwHostname,
|
||||
[string]$CustomRulesPath,
|
||||
[string]$CustomPolicyPath,
|
||||
@@ -40,6 +41,17 @@ param(
|
||||
[int]$PolicyRefreshSeconds = 300,
|
||||
[string]$PolicyCachePath,
|
||||
[string]$ReportPath,
|
||||
[bool]$HayabusaAutoUploadEnabled = $true,
|
||||
[int]$HayabusaAutoUploadIntervalHours = 6,
|
||||
[int]$HayabusaAutoUploadHoursBack = 6,
|
||||
[string]$HayabusaAutoUploadMode = 'incident',
|
||||
[string]$HayabusaAutoUploadTaskName = 'ActivityWatch Hayabusa Upload',
|
||||
[bool]$File1CAutoUploadEnabled = $true,
|
||||
[int]$File1CAutoUploadIntervalHours = 6,
|
||||
[string]$File1CAutoUploadTaskName = 'ActivityWatch File1C Upload',
|
||||
[string]$File1CTargetHost,
|
||||
[string]$File1CTargetUser = 'igor',
|
||||
[string]$File1CRegistryWorkbookPath = 'E:\USER1\СПИСОК ПРЕДПРИЯТИЙ И ИХ РАСПРЕДЕЛЕНИЕ.xlsx',
|
||||
[switch]$SkipHardening,
|
||||
[switch]$ValidateAfterDeploy,
|
||||
[switch]$IntegrationTestEnabled
|
||||
@@ -88,6 +100,7 @@ if (-not (Test-Path -LiteralPath $deployScript)) {
|
||||
-EvtxRetentionDays $EvtxRetentionDays `
|
||||
-EvtxChannels $EvtxChannels `
|
||||
-LogonMarkerEnabled $LogonMarkerEnabled `
|
||||
-ProcessEventsEnabled $ProcessEventsEnabled `
|
||||
-AwHostname $AwHostname `
|
||||
-CustomRulesPath $CustomRulesPath `
|
||||
-CustomPolicyPath $CustomPolicyPath `
|
||||
@@ -98,6 +111,17 @@ if (-not (Test-Path -LiteralPath $deployScript)) {
|
||||
-PolicyEngineScheme $PolicyEngineScheme `
|
||||
-PolicyRefreshSeconds $PolicyRefreshSeconds `
|
||||
-PolicyCachePath $PolicyCachePath `
|
||||
-HayabusaAutoUploadEnabled $HayabusaAutoUploadEnabled `
|
||||
-HayabusaAutoUploadIntervalHours $HayabusaAutoUploadIntervalHours `
|
||||
-HayabusaAutoUploadHoursBack $HayabusaAutoUploadHoursBack `
|
||||
-HayabusaAutoUploadMode $HayabusaAutoUploadMode `
|
||||
-HayabusaAutoUploadTaskName $HayabusaAutoUploadTaskName `
|
||||
-File1CAutoUploadEnabled $File1CAutoUploadEnabled `
|
||||
-File1CAutoUploadIntervalHours $File1CAutoUploadIntervalHours `
|
||||
-File1CAutoUploadTaskName $File1CAutoUploadTaskName `
|
||||
-File1CTargetHost $File1CTargetHost `
|
||||
-File1CTargetUser $File1CTargetUser `
|
||||
-File1CRegistryWorkbookPath $File1CRegistryWorkbookPath `
|
||||
-IntegrationTestEnabled:$IntegrationTestEnabled
|
||||
|
||||
if (-not $SkipHardening) {
|
||||
@@ -123,6 +147,7 @@ if (-not $SkipHardening) {
|
||||
-EvtxRetentionDays $EvtxRetentionDays `
|
||||
-EvtxChannels $EvtxChannels `
|
||||
-LogonMarkerEnabled $LogonMarkerEnabled `
|
||||
-ProcessEventsEnabled $ProcessEventsEnabled `
|
||||
-AwHostname $AwHostname `
|
||||
-CustomRulesPath $CustomRulesPath `
|
||||
-CustomPolicyPath $CustomPolicyPath `
|
||||
|
||||
@@ -25,6 +25,7 @@ param(
|
||||
[int]$EvtxRetentionDays = 14,
|
||||
[string[]]$EvtxChannels = @(),
|
||||
[bool]$LogonMarkerEnabled = $true,
|
||||
[bool]$ProcessEventsEnabled = $true,
|
||||
[string]$AwHostname,
|
||||
[string]$CustomRulesPath,
|
||||
[string]$CustomPolicyPath
|
||||
@@ -102,6 +103,7 @@ $config = New-ActivityWatchDeploymentConfig `
|
||||
-EvtxRetentionDays $EvtxRetentionDays `
|
||||
-EvtxChannels $EvtxChannels `
|
||||
-LogonMarkerEnabled $LogonMarkerEnabled `
|
||||
-ProcessEventsEnabled $ProcessEventsEnabled `
|
||||
-AwHostname $AwHostname `
|
||||
-LaunchScriptPath $launchScriptPath `
|
||||
-RecoveryScriptPath $recoveryScriptPath `
|
||||
|
||||
@@ -182,19 +182,45 @@ function Read-TransportQueueItems {
|
||||
return $items
|
||||
}
|
||||
|
||||
function Write-TransportQueueItems {
|
||||
param([object[]]$Items = @())
|
||||
|
||||
$lines = @()
|
||||
foreach ($item in @($Items)) {
|
||||
if ($null -eq $item) { continue }
|
||||
$lines += ($item | ConvertTo-Json -Compress)
|
||||
}
|
||||
|
||||
Set-Content -LiteralPath $script:TransportQueuePath -Value $lines -Encoding UTF8
|
||||
$script:TransportMetrics.queueDepth = @($Items).Count
|
||||
}
|
||||
|
||||
function Flush-TransportQueue {
|
||||
param([int]$MaxItems = 200)
|
||||
if (-not (Test-Path -LiteralPath $script:TransportQueuePath)) { return }
|
||||
|
||||
$items = @()
|
||||
$lock = Get-TransportQueueLock
|
||||
try {
|
||||
$items = Read-TransportQueueItems
|
||||
$script:TransportMetrics.queueDepth = $items.Count
|
||||
if ($items.Count -eq 0) { return }
|
||||
$left = New-Object System.Collections.Generic.List[object]
|
||||
$items = @(Read-TransportQueueItems)
|
||||
$itemCount = @($items).Count
|
||||
$script:TransportMetrics.queueDepth = $itemCount
|
||||
if ($itemCount -eq 0) { return }
|
||||
|
||||
# Drain the on-disk queue under lock, then release the lock before network I/O.
|
||||
# This prevents one stalled POST from blocking every concurrent enqueue/flush attempt.
|
||||
Write-TransportQueueItems -Items @()
|
||||
}
|
||||
finally {
|
||||
$lock.Dispose()
|
||||
}
|
||||
|
||||
$retryItems = @()
|
||||
$sent = 0
|
||||
foreach ($item in $items) {
|
||||
foreach ($item in @($items)) {
|
||||
if ($null -eq $item) { continue }
|
||||
if ($sent -ge $MaxItems) {
|
||||
$left.Add($item)
|
||||
$retryItems += $item
|
||||
continue
|
||||
}
|
||||
try {
|
||||
@@ -204,15 +230,14 @@ function Flush-TransportQueue {
|
||||
}
|
||||
catch {
|
||||
$script:TransportMetrics.sendFailures++
|
||||
$left.Add($item)
|
||||
$retryItems += $item
|
||||
}
|
||||
}
|
||||
foreach ($item in $items | Select-Object -Skip ($sent + $left.Count)) {
|
||||
$left.Add($item)
|
||||
}
|
||||
$lines = @($left | ForEach-Object { $_ | ConvertTo-Json -Compress })
|
||||
Set-Content -LiteralPath $script:TransportQueuePath -Value $lines -Encoding UTF8
|
||||
$script:TransportMetrics.queueDepth = $left.Count
|
||||
|
||||
$lock = Get-TransportQueueLock
|
||||
try {
|
||||
$concurrentItems = @(Read-TransportQueueItems)
|
||||
Write-TransportQueueItems -Items (@($retryItems) + @($concurrentItems))
|
||||
}
|
||||
finally {
|
||||
$lock.Dispose()
|
||||
|
||||
@@ -24,6 +24,7 @@ param(
|
||||
[int]$EvtxRetentionDays,
|
||||
[string[]]$EvtxChannels,
|
||||
[bool]$LogonMarkerEnabled,
|
||||
[bool]$ProcessEventsEnabled,
|
||||
[string]$AwHostname,
|
||||
[string]$CustomRulesPath,
|
||||
[string]$CustomPolicyPath,
|
||||
@@ -70,6 +71,8 @@ $effectiveEndpointCollector = if ($existingConfig -and $existingConfig.paths.PSO
|
||||
$effectiveFileCollector = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'fileCollectorScript') { [string]$existingConfig.paths.fileCollectorScript } else { Join-Path $effectiveStateRoot 'file-operations-collector.ps1' }
|
||||
$effectiveSessionCollector = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'sessionCollectorScript') { [string]$existingConfig.paths.sessionCollectorScript } else { Join-Path $effectiveStateRoot 'worktime-session-collector.ps1' }
|
||||
$effectiveEvtxExportScript = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'evtxExportScript') { [string]$existingConfig.paths.evtxExportScript } else { Join-Path $effectiveStateRoot 'export-evtx-for-hayabusa.ps1' }
|
||||
$effectiveHayabusaUploadScript = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'hayabusaUploadScript') { [string]$existingConfig.paths.hayabusaUploadScript } else { Join-Path $effectiveStateRoot 'export-upload-hayabusa-to-aw-server.ps1' }
|
||||
$effectiveFile1CTelemetryScript = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'file1cTelemetryScript') { [string]$existingConfig.paths.file1cTelemetryScript } else { Join-Path $effectiveStateRoot 'export-upload-file-1c-telemetry.ps1' }
|
||||
$effectiveRules = Join-Path $effectiveStateRoot 'web-category-rules.json'
|
||||
$effectivePolicy = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'policyPath') { [string]$existingConfig.paths.policyPath } else { Join-Path $effectiveStateRoot 'dlp-policy.json' }
|
||||
$effectivePolicyClientScript = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'policyClientScript') { [string]$existingConfig.paths.policyClientScript } else { Join-Path $effectiveStateRoot 'dlp-policy-client.ps1' }
|
||||
@@ -91,6 +94,7 @@ $effectiveEvtxExportRoot = if ($PSBoundParameters.ContainsKey('EvtxExportRoot')
|
||||
$effectiveEvtxRetentionDays = if ($PSBoundParameters.ContainsKey('EvtxRetentionDays')) { [int]$EvtxRetentionDays } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'forensics' -and $existingConfig.forensics.PSObject.Properties.Name -contains 'retentionDays') { [int]$existingConfig.forensics.retentionDays } else { 14 }
|
||||
$effectiveEvtxChannels = if ($PSBoundParameters.ContainsKey('EvtxChannels')) { @($EvtxChannels) } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'forensics' -and $existingConfig.forensics.PSObject.Properties.Name -contains 'evtxChannels') { @($existingConfig.forensics.evtxChannels) } else { @() }
|
||||
$effectiveLogonMarkerEnabled = if ($PSBoundParameters.ContainsKey('LogonMarkerEnabled')) { [bool]$LogonMarkerEnabled } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'sessionEvents' -and $existingConfig.sessionEvents.PSObject.Properties.Name -contains 'logonEnabled') { [bool]$existingConfig.sessionEvents.logonEnabled } else { $true }
|
||||
$effectiveProcessEventsEnabled = if ($PSBoundParameters.ContainsKey('ProcessEventsEnabled')) { [bool]$ProcessEventsEnabled } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'sessionEvents' -and $existingConfig.sessionEvents.PSObject.Properties.Name -contains 'processEventsEnabled') { [bool]$existingConfig.sessionEvents.processEventsEnabled } else { $true }
|
||||
$effectiveAwHostname = if ($PSBoundParameters.ContainsKey('AwHostname') -and -not [string]::IsNullOrWhiteSpace($AwHostname)) { [string]$AwHostname } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'awHostname' -and -not [string]::IsNullOrWhiteSpace([string]$existingConfig.awHostname)) { [string]$existingConfig.awHostname } else { [string]$env:COMPUTERNAME }
|
||||
$effectiveVersion = if ($Version) { $Version } elseif ($existingConfig) { [string]$existingConfig.package.version } else { 'v0.13.2' }
|
||||
$effectivePolicyMode = if ($PSBoundParameters.ContainsKey('PolicyMode') -and $PolicyMode) { [string]$PolicyMode } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'policyEngine' -and $existingConfig.policyEngine.PSObject.Properties.Name -contains 'mode') { [string]$existingConfig.policyEngine.mode } else { 'local' }
|
||||
@@ -100,6 +104,31 @@ $effectivePolicyEnginePort = if ($PSBoundParameters.ContainsKey('PolicyEnginePor
|
||||
$effectivePolicyEngineScheme = if ($PSBoundParameters.ContainsKey('PolicyEngineScheme') -and $PolicyEngineScheme) { [string]$PolicyEngineScheme } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'policyEngine' -and $existingConfig.policyEngine.PSObject.Properties.Name -contains 'scheme') { [string]$existingConfig.policyEngine.scheme } else { 'http' }
|
||||
$effectivePolicyRefreshSeconds = if ($PSBoundParameters.ContainsKey('PolicyRefreshSeconds')) { [int]$PolicyRefreshSeconds } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'policyEngine' -and $existingConfig.policyEngine.PSObject.Properties.Name -contains 'refreshSeconds') { [int]$existingConfig.policyEngine.refreshSeconds } else { 300 }
|
||||
$effectivePolicyCachePath = if ($PSBoundParameters.ContainsKey('PolicyCachePath') -and $PolicyCachePath) { [string]$PolicyCachePath } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'policyEngine' -and $existingConfig.policyEngine.PSObject.Properties.Name -contains 'cachePath') { [string]$existingConfig.policyEngine.cachePath } else { Join-Path $effectiveStateRoot 'dlp-policy-cache.json' }
|
||||
$effectiveHayabusaAutoUploadEnabled = if ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'forensics' -and $existingConfig.forensics.PSObject.Properties.Name -contains 'hayabusaAutomation' -and $existingConfig.forensics.hayabusaAutomation.PSObject.Properties.Name -contains 'enabled') { [bool]$existingConfig.forensics.hayabusaAutomation.enabled } else { $true }
|
||||
$effectiveHayabusaAutoUploadIntervalHours = if ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'forensics' -and $existingConfig.forensics.PSObject.Properties.Name -contains 'hayabusaAutomation' -and $existingConfig.forensics.hayabusaAutomation.PSObject.Properties.Name -contains 'intervalHours') { [int]$existingConfig.forensics.hayabusaAutomation.intervalHours } else { 6 }
|
||||
$effectiveHayabusaAutoUploadHoursBack = if ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'forensics' -and $existingConfig.forensics.PSObject.Properties.Name -contains 'hayabusaAutomation' -and $existingConfig.forensics.hayabusaAutomation.PSObject.Properties.Name -contains 'hoursBack') { [int]$existingConfig.forensics.hayabusaAutomation.hoursBack } else { 6 }
|
||||
$effectiveHayabusaAutoUploadMode = if ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'forensics' -and $existingConfig.forensics.PSObject.Properties.Name -contains 'hayabusaAutomation' -and $existingConfig.forensics.hayabusaAutomation.PSObject.Properties.Name -contains 'mode') { [string]$existingConfig.forensics.hayabusaAutomation.mode } else { 'incident' }
|
||||
$effectiveHayabusaAutoUploadTaskName = if ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'forensics' -and $existingConfig.forensics.PSObject.Properties.Name -contains 'hayabusaAutomation' -and $existingConfig.forensics.hayabusaAutomation.PSObject.Properties.Name -contains 'taskName') { [string]$existingConfig.forensics.hayabusaAutomation.taskName } else { 'ActivityWatch Hayabusa Upload' }
|
||||
$effectiveFile1CAutoUploadEnabled = if ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'analytics' -and $existingConfig.analytics.PSObject.Properties.Name -contains 'file1cAutomation' -and $existingConfig.analytics.file1cAutomation.PSObject.Properties.Name -contains 'enabled') { [bool]$existingConfig.analytics.file1cAutomation.enabled } else { $true }
|
||||
$effectiveFile1CAutoUploadIntervalHours = if ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'analytics' -and $existingConfig.analytics.PSObject.Properties.Name -contains 'file1cAutomation' -and $existingConfig.analytics.file1cAutomation.PSObject.Properties.Name -contains 'intervalHours') { [int]$existingConfig.analytics.file1cAutomation.intervalHours } else { 6 }
|
||||
$effectiveFile1CAutoUploadTaskName = if ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'analytics' -and $existingConfig.analytics.PSObject.Properties.Name -contains 'file1cAutomation' -and $existingConfig.analytics.file1cAutomation.PSObject.Properties.Name -contains 'taskName') { [string]$existingConfig.analytics.file1cAutomation.taskName } else { 'ActivityWatch File1C Upload' }
|
||||
$effectiveFile1CTargetHost = if ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'analytics' -and $existingConfig.analytics.PSObject.Properties.Name -contains 'file1cAutomation' -and $existingConfig.analytics.file1cAutomation.PSObject.Properties.Name -contains 'targetHost') { [string]$existingConfig.analytics.file1cAutomation.targetHost } else { '' }
|
||||
$effectiveFile1CTargetUser = if ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'analytics' -and $existingConfig.analytics.PSObject.Properties.Name -contains 'file1cAutomation' -and $existingConfig.analytics.file1cAutomation.PSObject.Properties.Name -contains 'targetUser') { [string]$existingConfig.analytics.file1cAutomation.targetUser } else { 'igor' }
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($effectiveFile1CTargetHost)) {
|
||||
$file1cLogPath = Join-Path $effectiveLogsRoot 'file1c-telemetry.log'
|
||||
if (Test-Path -LiteralPath $file1cLogPath) {
|
||||
$recoveredFile1CHost = ''
|
||||
foreach ($line in Get-Content -LiteralPath $file1cLogPath -Encoding UTF8) {
|
||||
if ([string]$line -match 'upload complete analyticsHost=([^\s]+)') {
|
||||
$recoveredFile1CHost = [string]$Matches[1]
|
||||
}
|
||||
}
|
||||
if (-not [string]::IsNullOrWhiteSpace($recoveredFile1CHost)) {
|
||||
$effectiveFile1CTargetHost = $recoveredFile1CHost
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$effectiveUsers = if ($Users -or $UserListPath) {
|
||||
Normalize-ActivityWatchUsers -Users $Users -UserListPath $UserListPath -Domain $Domain
|
||||
@@ -131,6 +160,8 @@ $assetResult = Copy-ActivityWatchCollectorAssets `
|
||||
-FileCollectorScriptSource (Join-Path $PSScriptRoot 'file-operations-collector.ps1') `
|
||||
-SessionCollectorScriptSource (Join-Path $PSScriptRoot 'worktime-session-collector.ps1') `
|
||||
-EvtxExportScriptSource (Join-Path $PSScriptRoot 'export-evtx-for-hayabusa.ps1') `
|
||||
-HayabusaUploadScriptSource (Join-Path $PSScriptRoot 'export-upload-hayabusa-to-aw-server.ps1') `
|
||||
-File1CTelemetryScriptSource (Join-Path $PSScriptRoot 'export-upload-file-1c-telemetry.ps1') `
|
||||
-ExampleRulesSource (Join-Path $PSScriptRoot 'web-category-rules.example.json') `
|
||||
-ExamplePolicySource (Join-Path $PSScriptRoot 'dlp-policy.example.json') `
|
||||
-StateRoot $effectiveStateRoot `
|
||||
@@ -155,6 +186,8 @@ $config = New-ActivityWatchDeploymentConfig `
|
||||
-FileCollectorScript $effectiveFileCollector `
|
||||
-SessionCollectorScript $effectiveSessionCollector `
|
||||
-EvtxExportScript $effectiveEvtxExportScript `
|
||||
-HayabusaUploadScript $effectiveHayabusaUploadScript `
|
||||
-File1CTelemetryScript $effectiveFile1CTelemetryScript `
|
||||
-RulesPath $effectiveRules `
|
||||
-PolicyPath $effectivePolicy `
|
||||
-PollSeconds $effectivePollSeconds `
|
||||
@@ -171,6 +204,7 @@ $config = New-ActivityWatchDeploymentConfig `
|
||||
-EvtxRetentionDays $effectiveEvtxRetentionDays `
|
||||
-EvtxChannels $effectiveEvtxChannels `
|
||||
-LogonMarkerEnabled $effectiveLogonMarkerEnabled `
|
||||
-ProcessEventsEnabled $effectiveProcessEventsEnabled `
|
||||
-AwHostname $effectiveAwHostname `
|
||||
-PolicyMode $effectivePolicyMode `
|
||||
-PolicyEngineEnabled $effectivePolicyEngineEnabled `
|
||||
@@ -179,6 +213,16 @@ $config = New-ActivityWatchDeploymentConfig `
|
||||
-PolicyEngineScheme $effectivePolicyEngineScheme `
|
||||
-PolicyRefreshSeconds $effectivePolicyRefreshSeconds `
|
||||
-PolicyCachePath $effectivePolicyCachePath `
|
||||
-HayabusaAutoUploadEnabled $effectiveHayabusaAutoUploadEnabled `
|
||||
-HayabusaAutoUploadIntervalHours $effectiveHayabusaAutoUploadIntervalHours `
|
||||
-HayabusaAutoUploadHoursBack $effectiveHayabusaAutoUploadHoursBack `
|
||||
-HayabusaAutoUploadMode $effectiveHayabusaAutoUploadMode `
|
||||
-HayabusaAutoUploadTaskName $effectiveHayabusaAutoUploadTaskName `
|
||||
-File1CAutoUploadEnabled $effectiveFile1CAutoUploadEnabled `
|
||||
-File1CAutoUploadIntervalHours $effectiveFile1CAutoUploadIntervalHours `
|
||||
-File1CAutoUploadTaskName $effectiveFile1CAutoUploadTaskName `
|
||||
-File1CTargetHost $effectiveFile1CTargetHost `
|
||||
-File1CTargetUser $effectiveFile1CTargetUser `
|
||||
-LaunchScriptPath $effectiveLaunchScript `
|
||||
-RecoveryScriptPath $effectiveRecoveryScript `
|
||||
-UserTasks $taskDefinitions `
|
||||
@@ -189,6 +233,8 @@ Remove-LegacyActivityWatchEntries
|
||||
Set-ActivityWatchAcl -InstallRoot $effectiveInstallRoot -StateRoot $effectiveStateRoot -LogsRoot $effectiveLogsRoot
|
||||
Register-ActivityWatchUserTasks -TaskDefinitions $taskDefinitions -LaunchScriptPath $effectiveLaunchScript -ConfigPath $effectiveConfigPath
|
||||
Register-ActivityWatchRecoveryTask -TaskName $config.recovery.taskName -RecoveryScriptPath $effectiveRecoveryScript -ConfigPath $effectiveConfigPath
|
||||
Register-ActivityWatchHayabusaAutoUploadTask -ConfigPath $effectiveConfigPath
|
||||
Register-ActivityWatchFile1CAutoUploadTask -ConfigPath $effectiveConfigPath
|
||||
Start-ActivityWatchTasks -TaskDefinitions $taskDefinitions -RecoveryTaskName $config.recovery.taskName
|
||||
|
||||
Write-Host 'Укрепление и восстановление ActivityWatch завершены.'
|
||||
|
||||
@@ -30,14 +30,30 @@ $pulseSeconds = if ($config.PSObject.Properties.Name -contains 'collector' -and
|
||||
$freshnessSeconds = [Math]::Max($pollSeconds * 3, 30)
|
||||
$sessionFreshnessSeconds = [Math]::Max($pollSeconds * 4, 45)
|
||||
$transportStaleSeconds = [Math]::Max($pollSeconds * 12, 180)
|
||||
$endpointFreshnessSeconds = [Math]::Max($transportStaleSeconds, 300)
|
||||
$queueMaxDepth = 1000
|
||||
|
||||
$afkExpected = if ($config.PSObject.Properties.Name -contains 'collectors' -and $config.collectors.PSObject.Properties.Name -contains 'afkEnabled') { [bool]$config.collectors.afkEnabled } else { $true }
|
||||
$windowExpected = if ($config.PSObject.Properties.Name -contains 'collectors' -and $config.collectors.PSObject.Properties.Name -contains 'windowEnabled') { [bool]$config.collectors.windowEnabled } else { $true }
|
||||
$fileOpsExpected = if ($config.PSObject.Properties.Name -contains 'collectors' -and $config.collectors.PSObject.Properties.Name -contains 'fileOpsEnabled') { [bool]$config.collectors.fileOpsEnabled } else { $true }
|
||||
$sessionEventsConfig = if ($config.PSObject.Properties.Name -contains 'sessionEvents') { $config.sessionEvents } else { $null }
|
||||
$sessionLogonEnabled = if ($sessionEventsConfig -and $sessionEventsConfig.PSObject.Properties.Name -contains 'logonEnabled') { [bool]$sessionEventsConfig.logonEnabled } else { $false }
|
||||
$sessionProcessEventsEnabled = if ($sessionEventsConfig -and $sessionEventsConfig.PSObject.Properties.Name -contains 'processEventsEnabled') { [bool]$sessionEventsConfig.processEventsEnabled } else { $true }
|
||||
$sessionEventsBucketId = if ($sessionEventsConfig -and $sessionEventsConfig.PSObject.Properties.Name -contains 'bucketPrefix' -and -not [string]::IsNullOrWhiteSpace([string]$sessionEventsConfig.bucketPrefix)) {
|
||||
('{0}_{1}' -f [string]$sessionEventsConfig.bucketPrefix, $awHostname)
|
||||
}
|
||||
else {
|
||||
'aw-session-events_' + $awHostname
|
||||
}
|
||||
|
||||
function Get-LoggedOnUsers {
|
||||
param(
|
||||
[bool]$IncludeDisconnected = $false
|
||||
)
|
||||
|
||||
$users = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
|
||||
$activeStates = @('Active', 'Активно')
|
||||
$inactiveStates = @('Disc', 'Disconnected', 'Idle', 'Listen', 'Диск', 'Откл', 'Отключен')
|
||||
try {
|
||||
$lines = & quser.exe 2>$null
|
||||
foreach ($line in @($lines)) {
|
||||
@@ -50,6 +66,20 @@ function Get-LoggedOnUsers {
|
||||
if ($parts.Count -lt 1) { continue }
|
||||
$user = [string]$parts[0]
|
||||
if ([string]::IsNullOrWhiteSpace($user)) { continue }
|
||||
$state = $null
|
||||
foreach ($part in @($parts | Select-Object -Skip 1)) {
|
||||
$token = [string]$part
|
||||
if ([string]::IsNullOrWhiteSpace($token)) { continue }
|
||||
if ($activeStates -contains $token -or $inactiveStates -contains $token) {
|
||||
$state = $token
|
||||
break
|
||||
}
|
||||
}
|
||||
if ($null -ne $state -and $activeStates -notcontains $state) {
|
||||
if (-not $IncludeDisconnected -or $inactiveStates -notcontains $state) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
[void]$users.Add($user)
|
||||
[void]$users.Add(('{0}\{1}' -f $env:COMPUTERNAME, $user))
|
||||
if (-not [string]::IsNullOrWhiteSpace($env:USERDOMAIN)) {
|
||||
@@ -361,13 +391,27 @@ $endpointCollectorProcesses = @(Get-CollectorProcesses -ScriptPath $endpointColl
|
||||
$fileCollectorProcesses = if ($fileOpsExpected) { @(Get-CollectorProcesses -ScriptPath $fileCollectorScript) } else { @() }
|
||||
$browserCollectorProcesses = @(Get-CollectorProcesses -ScriptPath $collectorScript)
|
||||
|
||||
$loggedOnUsers = Get-LoggedOnUsers
|
||||
$sessionBoundUsers = @(
|
||||
$liveLoggedOnUsers = Get-LoggedOnUsers
|
||||
$interactiveUsers = Get-LoggedOnUsers -IncludeDisconnected $true
|
||||
$liveSessionBoundUsers = @(
|
||||
@($config.userTasks) |
|
||||
Where-Object { Test-UserHasSession -UserId ([string]$_.userId) -LoggedOnUsers $loggedOnUsers } |
|
||||
Where-Object { Test-UserHasSession -UserId ([string]$_.userId) -LoggedOnUsers $liveLoggedOnUsers } |
|
||||
ForEach-Object { [string]$_.userId }
|
||||
)
|
||||
$sessionScopedExpectedCount = [int]$sessionBoundUsers.Count
|
||||
$interactiveSessionBoundUsers = @(
|
||||
@($config.userTasks) |
|
||||
Where-Object { Test-UserHasSession -UserId ([string]$_.userId) -LoggedOnUsers $interactiveUsers } |
|
||||
ForEach-Object { [string]$_.userId }
|
||||
)
|
||||
$sessionScopedExpectedCount = if ($liveSessionBoundUsers.Count -gt 0) {
|
||||
[int]$liveSessionBoundUsers.Count
|
||||
}
|
||||
elseif ($interactiveSessionBoundUsers.Count -gt 0) {
|
||||
1
|
||||
}
|
||||
else {
|
||||
0
|
||||
}
|
||||
$sessionScopedCollectorsRequired = ($sessionScopedExpectedCount -gt 0)
|
||||
|
||||
$taskNames = @()
|
||||
@@ -401,7 +445,7 @@ if ($sessionScopedCollectorsRequired -and $windowExpected) {
|
||||
$bucketChecks += Get-BucketHealth -BucketId ('aw-watcher-window_' + $awHostname) -MaxAgeSeconds $freshnessSeconds -Required $true -RequireFreshEvent $false
|
||||
}
|
||||
if ($sessionScopedCollectorsRequired) {
|
||||
$bucketChecks += Get-BucketHealth -BucketId ('aw-dlp-endpoint-signals_' + $awHostname) -MaxAgeSeconds $freshnessSeconds -Required $true -RequireFreshEvent $true
|
||||
$bucketChecks += Get-BucketHealth -BucketId ('aw-dlp-endpoint-signals_' + $awHostname) -MaxAgeSeconds $endpointFreshnessSeconds -Required $true -RequireFreshEvent $true
|
||||
}
|
||||
if ($sessionScopedCollectorsRequired -and $fileOpsExpected) {
|
||||
$bucketChecks += Get-BucketHealth -BucketId ('aw-file-operations_' + $awHostname) -MaxAgeSeconds $transportStaleSeconds -Required $false -RequireFreshEvent $true
|
||||
@@ -431,18 +475,9 @@ catch {
|
||||
}
|
||||
|
||||
$watcherCountsOk = $true
|
||||
if ($sessionScopedCollectorsRequired) {
|
||||
if ($afkExpected) {
|
||||
$watcherCountsOk = $watcherCountsOk -and (($watcherByName['aw-watcher-afk'] | ForEach-Object { [int]$_ }) -ge $sessionScopedExpectedCount)
|
||||
}
|
||||
if ($windowExpected) {
|
||||
$watcherCountsOk = $watcherCountsOk -and (($watcherByName['aw-watcher-window'] | ForEach-Object { [int]$_ }) -ge $sessionScopedExpectedCount)
|
||||
}
|
||||
}
|
||||
|
||||
$endpointProcessOk = if (-not $sessionScopedCollectorsRequired) { $true } else { (@($endpointCollectorProcesses).Count -ge $sessionScopedExpectedCount) }
|
||||
$fileProcessOk = if (-not $fileOpsExpected -or -not $sessionScopedCollectorsRequired) { $true } else { (@($fileCollectorProcesses).Count -ge $sessionScopedExpectedCount) }
|
||||
$browserProcessOk = if (-not $sessionScopedCollectorsRequired) { $true } else { (@($browserCollectorProcesses).Count -ge $sessionScopedExpectedCount) }
|
||||
$endpointProcessOk = $true
|
||||
$fileProcessOk = $true
|
||||
$browserProcessOk = $true
|
||||
$sessionCollectorOk = (@($sessionCollectorProcesses).Count -eq 1)
|
||||
|
||||
$result = [ordered]@{
|
||||
@@ -470,7 +505,8 @@ $result = [ordered]@{
|
||||
ok = [bool]($tasks.Count -gt 0 -and -not ($tasks | Where-Object { -not $_.present -or -not $_.enabled }))
|
||||
}
|
||||
processes = [ordered]@{
|
||||
sessionBoundUsers = $sessionBoundUsers
|
||||
liveSessionBoundUsers = $liveSessionBoundUsers
|
||||
sessionBoundUsers = $interactiveSessionBoundUsers
|
||||
sessionScopedExpectedCount = [int]$sessionScopedExpectedCount
|
||||
watchers = @($runningWatchers)
|
||||
watcherDuplicates = @($watcherDuplicates)
|
||||
@@ -508,6 +544,12 @@ $result = [ordered]@{
|
||||
jobTitlePolicyEnabled = $printJobTitlePolicyEnabled
|
||||
ok = [bool]($printServiceOperationalEnabled -and $printJobTitlePolicyEnabled)
|
||||
}
|
||||
sessionEvents = [ordered]@{
|
||||
bucketId = $sessionEventsBucketId
|
||||
logonEnabled = [bool]$sessionLogonEnabled
|
||||
processEventsEnabled = [bool]$sessionProcessEventsEnabled
|
||||
ok = $true
|
||||
}
|
||||
forensics = [ordered]@{
|
||||
evtxExportRoot = if ($config.PSObject.Properties.Name -contains 'forensics' -and $config.forensics.PSObject.Properties.Name -contains 'evtxExportRoot') { [string]$config.forensics.evtxExportRoot } else { $null }
|
||||
retentionDays = if ($config.PSObject.Properties.Name -contains 'forensics' -and $config.forensics.PSObject.Properties.Name -contains 'retentionDays') { [int]$config.forensics.retentionDays } else { $null }
|
||||
|
||||
@@ -94,11 +94,13 @@ function Ensure-Bucket {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$ApiBase,
|
||||
[Parameter(Mandatory = $true)][string]$BucketId,
|
||||
[Parameter(Mandatory = $true)][string]$HostnameValue
|
||||
[Parameter(Mandatory = $true)][string]$HostnameValue,
|
||||
[string]$ClientName = 'aw-worktime-session-collector',
|
||||
[string]$BucketType = 'aw.worktime.session'
|
||||
)
|
||||
try { Invoke-RestMethod -Method Get -Uri "$ApiBase/buckets/$BucketId" -ErrorAction Stop | Out-Null; return } catch { Write-Verbose "Bucket not found, creating: $BucketId" }
|
||||
|
||||
$body = @{ client='aw-worktime-session-collector'; type='aw.worktime.session'; hostname=$HostnameValue } | ConvertTo-Json -Compress
|
||||
$body = @{ client=$ClientName; type=$BucketType; hostname=$HostnameValue } | ConvertTo-Json -Compress
|
||||
$attempts = 0
|
||||
while ($attempts -lt 3) {
|
||||
$attempts++
|
||||
@@ -230,16 +232,282 @@ function Get-CanonicalUserId {
|
||||
return "$HostnameValue\$normalizedUser"
|
||||
}
|
||||
|
||||
function Get-SessionEventsBucketId {
|
||||
param(
|
||||
[pscustomobject]$Config,
|
||||
[string]$HostnameValue
|
||||
)
|
||||
$prefix = 'aw-session-events'
|
||||
if (
|
||||
$Config -and
|
||||
$Config.PSObject.Properties.Name -contains 'sessionEvents' -and
|
||||
$Config.sessionEvents -and
|
||||
$Config.sessionEvents.PSObject.Properties.Name -contains 'bucketPrefix' -and
|
||||
-not [string]::IsNullOrWhiteSpace([string]$Config.sessionEvents.bucketPrefix)
|
||||
) {
|
||||
$prefix = [string]$Config.sessionEvents.bucketPrefix
|
||||
}
|
||||
return ('{0}_{1}' -f $prefix, $HostnameValue)
|
||||
}
|
||||
|
||||
function Test-SessionProcessEventsEnabled {
|
||||
param([pscustomobject]$Config)
|
||||
if (
|
||||
$Config -and
|
||||
$Config.PSObject.Properties.Name -contains 'sessionEvents' -and
|
||||
$Config.sessionEvents -and
|
||||
$Config.sessionEvents.PSObject.Properties.Name -contains 'processEventsEnabled'
|
||||
) {
|
||||
return [bool]$Config.sessionEvents.processEventsEnabled
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function Get-ProcessStatePath {
|
||||
param([pscustomobject]$Config)
|
||||
$stateRoot = ''
|
||||
if ($Config -and $Config.PSObject.Properties.Name -contains 'paths' -and $Config.paths) {
|
||||
if ($Config.paths.PSObject.Properties.Name -contains 'stateRoot') {
|
||||
$stateRoot = [string]$Config.paths.stateRoot
|
||||
}
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($stateRoot)) {
|
||||
$stateRoot = 'C:\ProgramData\AWatch-rus'
|
||||
}
|
||||
return (Join-Path $stateRoot 'session-process-state.json')
|
||||
}
|
||||
|
||||
function Load-ProcessState {
|
||||
param([string]$Path)
|
||||
$map = @{}
|
||||
try {
|
||||
if (Test-Path -LiteralPath $Path) {
|
||||
$raw = Get-Content -LiteralPath $Path -Raw -ErrorAction Stop
|
||||
if (-not [string]::IsNullOrWhiteSpace($raw)) {
|
||||
$obj = $raw | ConvertFrom-Json -ErrorAction Stop
|
||||
foreach ($item in @($obj.processes)) {
|
||||
if (-not $item) { continue }
|
||||
$key = [string]$item.key
|
||||
if ([string]::IsNullOrWhiteSpace($key)) { continue }
|
||||
$map[$key] = $item
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Verbose "Process state load error: $($_.Exception.Message)"
|
||||
}
|
||||
return $map
|
||||
}
|
||||
|
||||
function Save-ProcessState {
|
||||
param(
|
||||
[string]$Path,
|
||||
[hashtable]$Map
|
||||
)
|
||||
try {
|
||||
$dir = Split-Path -Path $Path -Parent
|
||||
if ($dir -and -not (Test-Path -LiteralPath $dir)) {
|
||||
New-Item -Path $dir -ItemType Directory -Force | Out-Null
|
||||
}
|
||||
$items = @()
|
||||
foreach ($entry in $Map.GetEnumerator()) {
|
||||
$value = $entry.Value
|
||||
if ($null -eq $value) { continue }
|
||||
$items += [pscustomobject]@{
|
||||
key = [string]$entry.Key
|
||||
processId = [int]$value.processId
|
||||
sessionId = [int]$value.sessionId
|
||||
username = [string]$value.username
|
||||
userId = [string]$value.userId
|
||||
state = [string]$value.state
|
||||
processName = [string]$value.processName
|
||||
commandLine = [string]$value.commandLine
|
||||
createdAt = [string]$value.createdAt
|
||||
hostname = [string]$value.hostname
|
||||
}
|
||||
}
|
||||
$payload = [pscustomobject]@{ processes = $items }
|
||||
$payload | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $Path -Encoding UTF8
|
||||
}
|
||||
catch {
|
||||
Write-Verbose "Process state save error: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
function Test-ExcludedSessionProcess {
|
||||
param(
|
||||
[string]$Name,
|
||||
[string]$CommandLine
|
||||
)
|
||||
$n = [string]$Name
|
||||
if ([string]::IsNullOrWhiteSpace($n)) { return $true }
|
||||
if ($n -match '^(Idle|System|Registry|svchost|services|lsass|winlogon|csrss|fontdrvhost|dwm|taskhostw|sihost|explorer)\.exe$') { return $true }
|
||||
if ($n -match '^(aw-watcher-afk|aw-watcher-window|conhost)\.exe$') { return $true }
|
||||
return $false
|
||||
}
|
||||
|
||||
function Get-SessionProcessSnapshot {
|
||||
param(
|
||||
[pscustomobject]$Config,
|
||||
[string]$HostnameValue,
|
||||
[object[]]$SessionRecords
|
||||
)
|
||||
$bySession = @{}
|
||||
foreach ($rec in @($SessionRecords)) {
|
||||
if ($null -eq $rec) { continue }
|
||||
$sid = [int]$rec.sessionId
|
||||
$bySession[$sid] = [pscustomobject]@{
|
||||
username = [string]$rec.username
|
||||
userId = Get-CanonicalUserId -Config $Config -HostnameValue $HostnameValue -Username ([string]$rec.username)
|
||||
state = [string]$rec.state
|
||||
}
|
||||
}
|
||||
|
||||
$snapshot = @{}
|
||||
if ($bySession.Count -eq 0) {
|
||||
return $snapshot
|
||||
}
|
||||
|
||||
try {
|
||||
$procs = Get-Process -ErrorAction Stop | Where-Object { $bySession.ContainsKey([int]$_.SessionId) }
|
||||
}
|
||||
catch {
|
||||
Write-Verbose "Process snapshot error: $($_.Exception.Message)"
|
||||
return $snapshot
|
||||
}
|
||||
|
||||
foreach ($proc in @($procs)) {
|
||||
try {
|
||||
$sid = [int]$proc.SessionId
|
||||
}
|
||||
catch {
|
||||
continue
|
||||
}
|
||||
if (-not $bySession.ContainsKey($sid)) { continue }
|
||||
|
||||
$name = [string]$proc.ProcessName
|
||||
if ($name -and $name -notmatch '\.exe$') {
|
||||
$name = "$name.exe"
|
||||
}
|
||||
$commandLine = ''
|
||||
if (Test-ExcludedSessionProcess -Name $name -CommandLine $commandLine) { continue }
|
||||
|
||||
$createdAt = ''
|
||||
try {
|
||||
if ($proc.StartTime) {
|
||||
$createdAt = $proc.StartTime.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ')
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$createdAt = ''
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($createdAt)) {
|
||||
$createdAt = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ')
|
||||
}
|
||||
|
||||
$key = ('{0}|{1}|{2}' -f $sid, [int]$proc.Id, $createdAt)
|
||||
$sessionMeta = $bySession[$sid]
|
||||
$snapshot[$key] = [pscustomobject]@{
|
||||
processId = [int]$proc.Id
|
||||
sessionId = $sid
|
||||
username = [string]$sessionMeta.username
|
||||
userId = [string]$sessionMeta.userId
|
||||
state = [string]$sessionMeta.state
|
||||
processName = $name
|
||||
commandLine = $commandLine
|
||||
createdAt = $createdAt
|
||||
hostname = $HostnameValue
|
||||
}
|
||||
}
|
||||
return $snapshot
|
||||
}
|
||||
|
||||
function Publish-SessionProcessEvents {
|
||||
param(
|
||||
[string]$ApiBase,
|
||||
[string]$BucketId,
|
||||
[hashtable]$Previous,
|
||||
[hashtable]$Current
|
||||
)
|
||||
foreach ($entry in $Current.GetEnumerator()) {
|
||||
if ($Previous.ContainsKey($entry.Key)) { continue }
|
||||
$item = $entry.Value
|
||||
$payload = [pscustomobject]@{
|
||||
timestamp = [string]$item.createdAt
|
||||
duration = 0
|
||||
data = [pscustomobject]@{
|
||||
eventType = 'process_start'
|
||||
username = [string]$item.username
|
||||
userId = [string]$item.userId
|
||||
sessionId = [int]$item.sessionId
|
||||
state = [string]$item.state
|
||||
processId = [int]$item.processId
|
||||
processName = [string]$item.processName
|
||||
commandLine = [string]$item.commandLine
|
||||
createdAt = [string]$item.createdAt
|
||||
hostname = [string]$item.hostname
|
||||
source = 'worktime-session-collector'
|
||||
}
|
||||
} | ConvertTo-Json -Depth 6 -Compress
|
||||
try {
|
||||
[void](Invoke-AwJsonPost -Uri "$ApiBase/buckets/$BucketId/heartbeat?pulsetime=1" -Json $payload)
|
||||
}
|
||||
catch {
|
||||
Write-Verbose "Process start publish error: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
$nowUtc = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ')
|
||||
foreach ($entry in $Previous.GetEnumerator()) {
|
||||
if ($Current.ContainsKey($entry.Key)) { continue }
|
||||
$item = $entry.Value
|
||||
$payload = [pscustomobject]@{
|
||||
timestamp = $nowUtc
|
||||
duration = 0
|
||||
data = [pscustomobject]@{
|
||||
eventType = 'process_stop'
|
||||
username = [string]$item.username
|
||||
userId = [string]$item.userId
|
||||
sessionId = [int]$item.sessionId
|
||||
state = [string]$item.state
|
||||
processId = [int]$item.processId
|
||||
processName = [string]$item.processName
|
||||
commandLine = [string]$item.commandLine
|
||||
createdAt = [string]$item.createdAt
|
||||
hostname = [string]$item.hostname
|
||||
source = 'worktime-session-collector'
|
||||
}
|
||||
} | ConvertTo-Json -Depth 6 -Compress
|
||||
try {
|
||||
[void](Invoke-AwJsonPost -Uri "$ApiBase/buckets/$BucketId/heartbeat?pulsetime=1" -Json $payload)
|
||||
}
|
||||
catch {
|
||||
Write-Verbose "Process stop publish error: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Main
|
||||
$cfg = Get-Config -Path $ConfigPath
|
||||
$hostValue = if ($Hostname -and $Hostname.Trim()) { $Hostname.Trim() } elseif ($cfg -and $cfg.PSObject.Properties.Name -contains 'awHostname' -and -not [string]::IsNullOrWhiteSpace([string]$cfg.awHostname)) { [string]$cfg.awHostname } elseif ($cfg -and $cfg.awHostname) { [string]$cfg.awHostname } else { [string]$env:COMPUTERNAME }
|
||||
try { $apiBase = '{0}://{1}:{2}/api/0' -f [string]$cfg.server.scheme, [string]$cfg.server.host, [string]$cfg.server.port } catch { throw 'Invalid server configuration in config file.' }
|
||||
|
||||
$bucketId = 'aw-worktime-sessions_' + $hostValue
|
||||
$sessionEventsBucketId = Get-SessionEventsBucketId -Config $cfg -HostnameValue $hostValue
|
||||
$processEventsEnabled = Test-SessionProcessEventsEnabled -Config $cfg
|
||||
$processStatePath = Get-ProcessStatePath -Config $cfg
|
||||
$sleepSec = if ($PollSeconds -gt 0) { $PollSeconds } elseif ($cfg.collector -and $cfg.collector.pollSeconds) { [int]$cfg.collector.pollSeconds } else { 30 }
|
||||
$pulse = [Math]::Max($sleepSec * 3, 30)
|
||||
|
||||
Ensure-Bucket -ApiBase $apiBase -BucketId $bucketId -HostnameValue $hostValue
|
||||
if ($processEventsEnabled) {
|
||||
Ensure-Bucket -ApiBase $apiBase -BucketId $sessionEventsBucketId -HostnameValue $hostValue -ClientName 'aw-session-events' -BucketType 'aw.session.event'
|
||||
$previousProcessState = Load-ProcessState -Path $processStatePath
|
||||
}
|
||||
else {
|
||||
$previousProcessState = @{}
|
||||
}
|
||||
|
||||
while ($true) {
|
||||
$now = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ')
|
||||
@@ -265,6 +533,13 @@ while ($true) {
|
||||
)
|
||||
}
|
||||
|
||||
if ($processEventsEnabled) {
|
||||
$currentProcessState = Get-SessionProcessSnapshot -Config $cfg -HostnameValue $hostValue -SessionRecords $records
|
||||
Publish-SessionProcessEvents -ApiBase $apiBase -BucketId $sessionEventsBucketId -Previous $previousProcessState -Current $currentProcessState
|
||||
Save-ProcessState -Path $processStatePath -Map $currentProcessState
|
||||
$previousProcessState = $currentProcessState
|
||||
}
|
||||
|
||||
foreach ($rec in $records) {
|
||||
$canonicalUserId = Get-CanonicalUserId -Config $cfg -HostnameValue $hostValue -Username ([string]$rec.username)
|
||||
$payloadObj = [PSCustomObject]@{
|
||||
|
||||
@@ -30,6 +30,9 @@ sync_tree() {
|
||||
if [[ -d "$OLD_SERVER_CONFIG_DIR" ]]; then
|
||||
cp "$OLD_SERVER_CONFIG_DIR"/*.deployment-config.json "$TMP_SERVER_CONFIG_DIR"/
|
||||
fi
|
||||
if [[ -d "$SERVER_CONFIG_DIR" ]]; then
|
||||
cp "$SERVER_CONFIG_DIR"/*.deployment-config.json "$TMP_SERVER_CONFIG_DIR"/
|
||||
fi
|
||||
|
||||
rm -rf "${KIT_DIR}/ansible" "${KIT_DIR}/aw-server" "${KIT_DIR}/windows" "${KIT_DIR}/server-configs-"*
|
||||
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user