From 99143b108b4ce8314add009af9913e61ef2a904a Mon Sep 17 00:00:00 2001 From: igor04091968 Date: Thu, 21 May 2026 15:13:57 +0300 Subject: [PATCH] feat(aw-rus): sync deploy stack, install kit, and health checks --- ansible/audit_cryptopro_windows.yml | 71 + ansible/deploy_aw_server.yml | 56 +- ansible/deploy_aw_windows.yml | 1 + ansible/group_vars/all.example.yml | 4 + ansible/group_vars/all.yml | 4 + ansible/inventory.example.ini | 2 +- ansible/inventory.ini | 5 +- aw-server/apply_webui_ru_patch.sh | 10 +- aw-server/aw-ru-patch.js | 41 +- aw-server/aw-rus-healthd.py | 371 ++++ aw-server/aw-rus-healthd.service | 14 + aw-server/aw-rus-healthd.timer | 11 + aw-server/aw-server.env.example | 4 + aw-server/install_aw_server.sh | 21 + aw-server/test_aw_rus_healthd.py | 38 + check-aw-data.sh | 2 +- check-aw-full.sh | 4 +- docs/runbook.md | 33 +- install-kit-awindows-20260427-211240.tar.gz | Bin 60352 -> 119349 bytes install-kit-awindows-20260427-211240.zip | Bin 81144 -> 148222 bytes .../MANIFEST.txt | 74 +- .../README-INSTALL-KIT.txt | 4 +- .../ansible/README.md | 106 +- .../ansible/deploy_aw_server.yml | 1622 ++++++++++++++--- .../ansible/deploy_aw_windows.yml | 262 ++- .../ansible/group_vars/all.example.yml | 54 +- .../ansible/group_vars/windows.example.yml | 14 +- .../ansible/install_full_stack.yml | 2 + .../ansible/inventory.example.ini | 7 +- .../provision_proxmox_ct_and_deploy_aw.yml | 3 + ...vision_proxmox_ct_matrix_and_deploy_aw.yml | 3 + .../tasks/provision_ct_and_deploy_aw.yml | 1 + .../aw-server/activitywatch-server.service | 4 +- .../aw-server/apply_webui_ru_patch.sh | 67 +- .../aw-server/aw-host-groups.json | 15 + .../aw-server/aw-ru-patch.js | 385 +++- .../aw-server/aw-rus-healthd.py | 371 ++++ .../aw-server/aw-rus-healthd.service | 14 + .../aw-server/aw-rus-healthd.timer | 11 + .../aw-server/aw-server.env.example | 33 + .../aw-server/aw-worktime-api.py | 661 ++++++- .../aw-server/aw-worktime-api.service | 9 +- .../aw-server/aw-worktime-panel.js | 28 +- .../aw-server/install_aw_server.sh | 42 + .../aw-server/settings/classes-worktime.json | 8 +- .../aw-server/settings/views-default.json | 5 - .../awatch-rus-admin.deployment-config.json | 0 .../awatch-rus-u2u5.deployment-config.json | 0 .../awatch-rus-user1.deployment-config.json | 0 .../windows/ActivityWatch.Windows.Common.psm1 | 442 ++++- .../browser-domains-native-collector.ps1 | 24 +- .../windows/deploy-domain-users.ps1 | 36 +- .../windows/deploy-ensemble.ps1 | 43 +- .../windows/deploy-single-user.ps1 | 11 + .../dlp-endpoint-signals-collector.ps1 | 622 ++++++- .../windows/dlp-policy.example.json | 11 + .../windows/email-outbound-collector.ps1 | 46 +- .../windows/hardening-recovery.ps1 | 40 + .../windows/migrate-awatch-rus-paths.ps1 | 31 +- .../windows/validate-deployment.ps1 | 501 ++++- .../windows/worktime-session-collector.ps1 | 318 +++- scripts/check_install_kit_vs_repo.sh | 7 +- scripts/prod_rollout.sh | 2 +- scripts/rebuild_install_kit.sh | 125 ++ scripts/validate_install_kit.sh | 6 +- windows/ActivityWatch.Windows.Common.psm1 | 30 +- windows/audit-cryptopro.ps1 | 338 ++++ 67 files changed, 6449 insertions(+), 681 deletions(-) create mode 100644 ansible/audit_cryptopro_windows.yml create mode 100644 aw-server/aw-rus-healthd.py create mode 100644 aw-server/aw-rus-healthd.service create mode 100644 aw-server/aw-rus-healthd.timer create mode 100644 aw-server/test_aw_rus_healthd.py create mode 100644 install-kit-awindows-20260427-211240/aw-server/aw-rus-healthd.py create mode 100644 install-kit-awindows-20260427-211240/aw-server/aw-rus-healthd.service create mode 100644 install-kit-awindows-20260427-211240/aw-server/aw-rus-healthd.timer rename install-kit-awindows-20260427-211240/{server-configs-192.168.100.21 => server-configs-192.168.100.18}/awatch-rus-admin.deployment-config.json (100%) rename install-kit-awindows-20260427-211240/{server-configs-192.168.100.21 => server-configs-192.168.100.18}/awatch-rus-u2u5.deployment-config.json (100%) rename install-kit-awindows-20260427-211240/{server-configs-192.168.100.21 => server-configs-192.168.100.18}/awatch-rus-user1.deployment-config.json (100%) create mode 100644 scripts/rebuild_install_kit.sh create mode 100644 windows/audit-cryptopro.ps1 diff --git a/ansible/audit_cryptopro_windows.yml b/ansible/audit_cryptopro_windows.yml new file mode 100644 index 0000000..e286fb3 --- /dev/null +++ b/ansible/audit_cryptopro_windows.yml @@ -0,0 +1,71 @@ +--- +- name: Audit CryptoPro signer readiness on Windows/RDP host + hosts: aw_windows + gather_facts: false + + vars: + aw_windows_deploy_root: "C:\\Program Files\\AWatch-rus" + aw_windows_state_root: "C:\\ProgramData\\AWatch-rus" + aw_windows_cryptopro_audit_remote_path: "{{ aw_windows_state_root }}\\cryptopro_audit_ansible.json" + aw_windows_cryptopro_audit_local_dir: "/tmp/aw-rus-cryptopro-audit-{{ lookup('env','USER') | default('ansible', true) }}" + aw_windows_expected_signers: + - Администратор + - user1 + - user2 + - user3 + - user4 + - user5 + + tasks: + - name: Создать каталог toolkit на Windows host + ansible.windows.win_file: + path: "{{ aw_windows_deploy_root }}\\windows" + state: directory + + - name: Загрузить CryptoPro audit script + ansible.windows.win_copy: + src: "{{ playbook_dir | dirname }}/windows/audit-cryptopro.ps1" + dest: "{{ aw_windows_deploy_root }}\\windows\\audit-cryptopro.ps1" + + - name: Нормализовать кодировку PowerShell audit script + ansible.windows.win_powershell: + script: | + $ErrorActionPreference = 'Stop' + $path = "{{ aw_windows_deploy_root }}\windows\audit-cryptopro.ps1" + $encIn = New-Object System.Text.UTF8Encoding($false) + $encOut = New-Object System.Text.UTF8Encoding($true) + $text = [System.IO.File]::ReadAllText($path, $encIn) + [System.IO.File]::WriteAllText($path, $text, $encOut) + + - name: Выполнить CryptoPro audit и сохранить JSON report + ansible.windows.win_powershell: + script: | + $ErrorActionPreference = 'Stop' + $expected = @( + {% for user in aw_windows_expected_signers %} + "{{ user }}"{% if not loop.last %},{% endif %} + {% endfor %} + ) + $report = & "{{ aw_windows_deploy_root }}\windows\audit-cryptopro.ps1" ` + -ExpectedUsers $expected ` + -IncludeUnexpectedProfiles + $report | ConvertTo-Json -Depth 12 | Out-File -FilePath "{{ aw_windows_cryptopro_audit_remote_path }}" -Encoding utf8 + + - name: Создать локальный каталог для CryptoPro audit reports + ansible.builtin.file: + path: "{{ aw_windows_cryptopro_audit_local_dir }}" + state: directory + mode: "0755" + delegate_to: localhost + + - name: Забрать CryptoPro audit report + ansible.builtin.fetch: + src: "{{ aw_windows_cryptopro_audit_remote_path }}" + dest: "{{ aw_windows_cryptopro_audit_local_dir }}/{{ inventory_hostname }}-cryptopro-audit.json" + flat: true + + - name: Показать путь к CryptoPro audit report + ansible.builtin.debug: + msg: + - "CryptoPro audit completed on {{ inventory_hostname }}." + - "Report: {{ aw_windows_cryptopro_audit_local_dir }}/{{ inventory_hostname }}-cryptopro-audit.json" diff --git a/ansible/deploy_aw_server.yml b/ansible/deploy_aw_server.yml index e7e596f..46f3eb7 100644 --- a/ansible/deploy_aw_server.yml +++ b/ansible/deploy_aw_server.yml @@ -63,6 +63,8 @@ - "{{ aw_server_data_dir }}/.config/activitywatch" - "{{ aw_server_data_dir }}/.config/activitywatch/aw-server-rust" - "{{ aw_server_data_dir }}/backups" + - "{{ aw_rus_health_state_dir }}" + - "{{ aw_rus_health_validation_dir }}" - "{{ aw_server_log_dir }}" - /etc/activitywatch - "{{ aw_bootstrap_dir }}" @@ -88,6 +90,8 @@ - "{{ aw_server_data_dir }}/.config/activitywatch" - "{{ aw_server_data_dir }}/.config/activitywatch/aw-server-rust" - "{{ aw_server_data_dir }}/backups" + - "{{ aw_rus_health_state_dir }}" + - "{{ aw_rus_health_validation_dir }}" - "{{ aw_server_log_dir }}" - name: (Check mode) Пропустить установку релиза ActivityWatch @@ -334,6 +338,10 @@ AW_EXPECT_ALWAYS_ACTIVE_PATTERN={{ aw_server_always_active_pattern | default('') }} AW_EXPECT_LANDINGPAGE={{ aw_server_landingpage | default('') }} 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_STATE_DIR={{ aw_rus_health_state_dir }} + AW_RUS_HEALTH_VALIDATION_DIR={{ aw_rus_health_validation_dir }} - name: Создать каталог DLP policy engine ansible.builtin.file: @@ -707,6 +715,14 @@ group: root mode: "0755" + - name: Установить скрипт aw-rus-healthd + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/aw-rus-healthd.py" + dest: /usr/local/bin/aw-rus-healthd.py + owner: root + group: root + mode: "0755" + - name: Установить systemd unit AW worktime UI bridge ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/aw-worktime-ui-bridge.service" @@ -739,6 +755,22 @@ group: root mode: "0644" + - name: Установить systemd unit AW-RUS healthd + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/aw-rus-healthd.service" + dest: /etc/systemd/system/aw-rus-healthd.service + owner: root + group: root + mode: "0644" + + - name: Установить systemd timer AW-RUS healthd + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/aw-rus-healthd.timer" + dest: /etc/systemd/system/aw-rus-healthd.timer + owner: root + group: root + mode: "0644" + - name: Установить systemd unit AW worktime Influx exporter ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/aw-worktime-influx-exporter.service" @@ -779,6 +811,12 @@ ansible.builtin.systemd: daemon_reload: true + - name: Включить таймер AW-RUS healthd + ansible.builtin.systemd: + name: aw-rus-healthd.timer + enabled: true + state: started + - name: Включить и перезапустить DLP policy engine ansible.builtin.systemd: name: aw-dlp-policy-engine.service @@ -1524,31 +1562,21 @@ executable: /bin/bash changed_when: false - - name: Post-deploy health gate (aw-health-check) + - name: Post-deploy health gate (aw-rus-healthd) when: - not ansible_check_mode - aw_server_post_deploy_health_check_enabled | default(true) | bool block: - - name: Запустить /usr/local/bin/aw-health-check + - name: Запустить /usr/local/bin/aw-rus-healthd.py --json ansible.builtin.command: - cmd: /usr/local/bin/aw-health-check + cmd: /usr/local/bin/aw-rus-healthd.py --json register: aw_post_deploy_health changed_when: false - - name: Показать результат aw-health-check + - name: Показать результат aw-rus-healthd ansible.builtin.debug: msg: "{{ aw_post_deploy_health.stdout }}" - - name: Запустить /usr/local/bin/dlp-health-check --json - ansible.builtin.command: - cmd: /usr/local/bin/dlp-health-check --json - register: dlp_post_deploy_health - changed_when: false - - - name: Показать результат dlp-health-check - ansible.builtin.debug: - msg: "{{ dlp_post_deploy_health.stdout }}" - handlers: - name: Перезагрузить systemd ansible.builtin.systemd: diff --git a/ansible/deploy_aw_windows.yml b/ansible/deploy_aw_windows.yml index c50b6eb..52e924d 100644 --- a/ansible/deploy_aw_windows.yml +++ b/ansible/deploy_aw_windows.yml @@ -107,6 +107,7 @@ - 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 diff --git a/ansible/group_vars/all.example.yml b/ansible/group_vars/all.example.yml index bdac96e..fada2ca 100644 --- a/ansible/group_vars/all.example.yml +++ b/ansible/group_vars/all.example.yml @@ -25,6 +25,10 @@ aw_dlp_influx_hosts: "SHARKON2025" aw_dlp_influx_lookback_days: 30 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_state_dir: "{{ aw_server_data_dir }}/health" +aw_rus_health_validation_dir: "{{ aw_rus_health_state_dir }}/windows-validation" aw_repo_root: "{{ playbook_dir | dirname }}" diff --git a/ansible/group_vars/all.yml b/ansible/group_vars/all.yml index cf62c70..b0362ba 100644 --- a/ansible/group_vars/all.yml +++ b/ansible/group_vars/all.yml @@ -23,6 +23,10 @@ aw_dlp_influx_bucket: "aw_metrics" aw_dlp_influx_hosts: "SHARKON2025" aw_dlp_influx_lookback_days: 30 aw_dlp_influx_event_limit: 2000 +aw_monitored_windows_host: "192.168.100.18" +aw_monitored_windows_hostname: "SHARKON2025" +aw_rus_health_state_dir: "{{ aw_server_data_dir }}/health" +aw_rus_health_validation_dir: "{{ aw_rus_health_state_dir }}/windows-validation" aw_repo_root: "/mnt/usb_hdd2/Projects/ActivityWatch-Russian" diff --git a/ansible/inventory.example.ini b/ansible/inventory.example.ini index 62669da..8c8b1b0 100644 --- a/ansible/inventory.example.ini +++ b/ansible/inventory.example.ini @@ -6,7 +6,7 @@ aw-ct ansible_host=10.20.30.13 ansible_user=root ansible_port=22 [aw_windows] # Примечание: в русифицированных Windows часто нужен "Администратор", а не "Administrator". -win-node1 ansible_host=192.168.100.21 ansible_user=Администратор ansible_password=CHANGE_ME ansible_connection=winrm ansible_winrm_transport=ntlm ansible_port=5985 ansible_winrm_server_cert_validation=ignore +win-node1 ansible_host=192.168.100.18 ansible_user=Администратор ansible_password=CHANGE_ME ansible_connection=winrm ansible_winrm_transport=ntlm ansible_port=5985 ansible_winrm_server_cert_validation=ignore [aw_pfsense_pollers] # pfsense-poller1 ansible_host=192.168.100.30 ansible_user=root ansible_port=22 diff --git a/ansible/inventory.ini b/ansible/inventory.ini index e883a31..47d8860 100644 --- a/ansible/inventory.ini +++ b/ansible/inventory.ini @@ -1,5 +1,8 @@ +[proxmox] +pve-detmir ansible_host=10.10.10.2 ansible_user=igor ansible_password=04091968 ansible_connection=ssh ansible_become=true ansible_become_method=sudo ansible_become_password=04091968 ansible_port=22 + [aw_server] aw-prod ansible_host=10.10.10.13 ansible_user=igor ansible_password=04091968 ansible_connection=ssh ansible_become=true ansible_become_method=sudo ansible_become_password=04091968 [aw_windows] -rdp-prod ansible_host=192.168.100.21 ansible_user=Администратор ansible_password=Sergei2009@ ansible_connection=winrm ansible_winrm_transport=ntlm ansible_port=5985 ansible_winrm_server_cert_validation=ignore +rdp-prod ansible_host=192.168.100.18 ansible_user=Администратор ansible_password=Sergei2009@ ansible_connection=winrm ansible_winrm_transport=ntlm ansible_port=5985 ansible_winrm_server_cert_validation=ignore diff --git a/aw-server/apply_webui_ru_patch.sh b/aw-server/apply_webui_ru_patch.sh index 6160cce..343ee06 100755 --- a/aw-server/apply_webui_ru_patch.sh +++ b/aw-server/apply_webui_ru_patch.sh @@ -122,15 +122,15 @@ content = re.sub( ) content = content.replace( "", - f'', + ( + f'' + f'' + ), 1, ) content = content.replace( "", - ( - f'' - f'' - ), + f'', 1, ) if 'id="aw-report-links"' not in content: diff --git a/aw-server/aw-ru-patch.js b/aw-server/aw-ru-patch.js index 8538e81..0e7b585 100755 --- a/aw-server/aw-ru-patch.js +++ b/aw-server/aw-ru-patch.js @@ -1,6 +1,10 @@ (function () { - window.__awRuPatchVersion = "template-v12-activity-heading-ru"; - document.documentElement.setAttribute("data-aw-ru-patch", "template-v12-activity-heading-ru"); + if (window.__awRuPatchBootstrapped) { + 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"); const exact = new Map([ ["ActivityWatch", "АктивВотч"], @@ -1687,6 +1691,7 @@ .filter(Boolean) .filter(function (host) { return !/^unknown$/i.test(host); }); if (isLikelyClientHost(settingsHost) && hosts.indexOf(settingsHost) >= 0) return settingsHost; + if (isLikelyClientHost(settingsHost) && !hosts.length) return settingsHost; hosts.sort(); return hosts[0] || ""; } @@ -1749,20 +1754,25 @@ if (networkPatchesInstalled) return; networkPatchesInstalled = true; - const originalFetch = window.fetch ? window.fetch.bind(window) : null; - if (originalFetch) { - window.fetch = function (input, init) { + const originalFetch = window.fetch; + if (typeof originalFetch === "function" && !originalFetch.__awRuCategoryBuilderPatched) { + const patchedFetch = function (input, init) { + let nextInput = input; + let nextInit = init; try { - const url = typeof input === "string" ? input : String(input && input.url || ""); - if (/\/api\/0\/query\/?$/i.test(url) && init && typeof init.body === "string") { - init = Object.assign({}, init, { - body: rewriteUnknownCategoryBuilderQueryBody(init.body) + const url = typeof nextInput === "string" ? nextInput : String(nextInput && nextInput.url || ""); + if (/\/api\/0\/query\/?$/i.test(url) && nextInit && typeof nextInit.body === "string") { + nextInit = Object.assign({}, nextInit, { + body: rewriteUnknownCategoryBuilderQueryBody(nextInit.body) }); } } catch (error) { } - return originalFetch(input, init); + return originalFetch.call(this, nextInput, nextInit); }; + patchedFetch.__awRuCategoryBuilderPatched = true; + patchedFetch.__awRuOriginalFetch = originalFetch; + window.fetch = patchedFetch; } if (window.XMLHttpRequest && window.XMLHttpRequest.prototype) { @@ -1838,6 +1848,14 @@ } } + function primeCategoryBuilderEarlyFix() { + const hash = window.location.hash || ""; + if (!/^#\/settings\/category-builder(?:[/?#]|$)/i.test(hash)) return; + ensureSettingsHost(); + ensureHostGroupsData().catch(function () {}); + normalizeCategoryBuilderUnknownHostRefs(); + } + function patchActivityHeading(root) { const heading = root.querySelector("h3"); if (!heading) return; @@ -1920,6 +1938,9 @@ scheduleApplyPatch(); }); + installCategoryBuilderNetworkPatch(); + primeCategoryBuilderEarlyFix(); + window.addEventListener("load", function () { applyPatch(); attachObserver(); diff --git a/aw-server/aw-rus-healthd.py b/aw-server/aw-rus-healthd.py new file mode 100644 index 0000000..0423403 --- /dev/null +++ b/aw-server/aw-rus-healthd.py @@ -0,0 +1,371 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import json +import os +import socket +import subprocess +import sys +import tempfile +from dataclasses import dataclass +from datetime import UTC, datetime +from pathlib import Path +from typing import Any +from urllib import request + + +ENV_FILE = Path("/etc/activitywatch/aw-server.env") + + +def load_env_file(path: Path) -> None: + if not path.exists(): + return + for raw_line in path.read_text(encoding="utf-8").splitlines(): + line = raw_line.strip() + if not line or line.startswith("#") or "=" not in line: + continue + key, value = line.split("=", 1) + key = key.strip() + value = value.strip().strip("'").strip('"') + os.environ.setdefault(key, value) + + +def env(name: str, default: str) -> str: + value = os.environ.get(name) + return value if value not in (None, "") else default + + +def now_utc() -> datetime: + return datetime.now(UTC) + + +def parse_ts(value: str | None) -> datetime | None: + if not value: + return None + try: + return datetime.fromisoformat(value.replace("Z", "+00:00")).astimezone(UTC) + except ValueError: + return None + + +def age_seconds(ts: datetime | None, now: datetime) -> int | None: + if ts is None: + return None + return max(0, int((now - ts).total_seconds())) + + +def http_json(url: str, timeout: int = 10) -> Any: + with request.urlopen(url, timeout=timeout) as resp: + return json.loads(resp.read().decode("utf-8")) + + +def run_command(cmd: list[str]) -> tuple[int, str]: + proc = subprocess.run( + cmd, + check=False, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + return proc.returncode, proc.stdout.strip() + + +def tcp_connect(host: str, port: int, timeout: float) -> tuple[bool, str]: + try: + with socket.create_connection((host, port), timeout=timeout): + return True, "connected" + except OSError as exc: + return False, str(exc) + + +@dataclass +class CheckResult: + name: str + status: str + summary: str + details: dict[str, Any] + + +class Report: + def __init__(self) -> None: + self.results: list[CheckResult] = [] + + def add(self, name: str, status: str, summary: str, **details: Any) -> None: + self.results.append(CheckResult(name=name, status=status, summary=summary, details=details)) + + @property + def ok(self) -> bool: + return not any(item.status == "fail" for item in self.results) + + def as_dict(self) -> dict[str, Any]: + counts = {"ok": 0, "warn": 0, "fail": 0} + for item in self.results: + counts[item.status] = counts.get(item.status, 0) + 1 + return { + "generated_at_utc": now_utc().isoformat().replace("+00:00", "Z"), + "ok": self.ok, + "counts": counts, + "results": [ + { + "name": item.name, + "status": item.status, + "summary": item.summary, + "details": item.details, + } + for item in self.results + ], + } + + def render_text(self) -> str: + icon = {"ok": "OK", "warn": "WARN", "fail": "FAIL"} + lines = ["=== AW-RUS Health ===", f"Timestamp: {now_utc().isoformat().replace('+00:00', 'Z')}", ""] + for item in self.results: + lines.append(f"[{icon.get(item.status, item.status.upper())}] {item.name}: {item.summary}") + lines.append("") + payload = self.as_dict() + lines.append( + "Counts: ok={ok} warn={warn} fail={fail}".format( + ok=payload["counts"]["ok"], + warn=payload["counts"]["warn"], + fail=payload["counts"]["fail"], + ) + ) + lines.append(f"Overall: {'OK' if payload['ok'] else 'FAIL'}") + return "\n".join(lines) + + +def latest_bucket_event(api_base: str, bucket_id: str) -> dict[str, Any] | None: + events = http_json(f"{api_base}/buckets/{bucket_id}/events?limit=20") + if isinstance(events, list) and events: + events = [item for item in events if isinstance(item, dict)] + if not events: + return None + events.sort(key=lambda item: item.get("timestamp") or "", reverse=True) + return events[0] + return None + + +def host_activity_from_worktime(event: dict[str, Any] | None, max_age_seconds: int) -> dict[str, Any]: + now = now_utc() + if not event: + return {"fresh": False, "active": False, "age_seconds": None, "timestamp": None} + ts = parse_ts(event.get("timestamp")) + age = age_seconds(ts, now) + data = event.get("data") or {} + is_fresh = age is not None and age <= max_age_seconds + is_active = bool(is_fresh and data.get("active")) + return { + "fresh": bool(is_fresh), + "active": bool(is_active), + "age_seconds": age, + "timestamp": event.get("timestamp"), + "data": data, + } + + +def bucket_health( + api_base: str, + bucket_id: str, + max_age_seconds: int, + missing_status: str, + stale_status: str, +) -> tuple[str, str, dict[str, Any]]: + try: + event = latest_bucket_event(api_base, bucket_id) + except Exception as exc: + return "fail", f"bucket query failed: {exc}", {"bucket": bucket_id} + + if not event: + return missing_status, "no events", {"bucket": bucket_id} + + ts = parse_ts(event.get("timestamp")) + age = age_seconds(ts, now_utc()) + details = {"bucket": bucket_id, "timestamp": event.get("timestamp"), "age_seconds": age} + if age is None: + return "warn", "timestamp parse failed", details + if age > max_age_seconds: + return stale_status, f"stale ({age}s)", details + return "ok", f"fresh ({age}s)", details + + +def latest_validation_report(validation_dir: Path) -> Path | None: + candidates = sorted( + (path for path in validation_dir.glob("*-aw_validate_ansible.json") if path.is_file()), + key=lambda item: item.stat().st_mtime, + reverse=True, + ) + return candidates[0] if candidates else None + + +def write_atomic(path: Path, content: str) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + with tempfile.NamedTemporaryFile("w", encoding="utf-8", dir=path.parent, delete=False) as handle: + handle.write(content) + tmp_name = handle.name + os.replace(tmp_name, path) + + +def check_wrapper(report: Report, name: str, cmd: list[str], json_mode: bool = False) -> None: + if not Path(cmd[0]).exists(): + report.add(name, "warn", "binary missing", command=cmd) + return + rc, output = run_command(cmd) + details: dict[str, Any] = {"command": cmd, "returncode": rc} + if json_mode: + try: + details["payload"] = json.loads(output) if output else {} + except json.JSONDecodeError: + details["raw_output"] = output + report.add(name, "fail", "invalid JSON output", **details) + return + else: + details["output"] = output + report.add(name, "ok" if rc == 0 else "fail", "passed" if rc == 0 else "failed", **details) + + +def main() -> int: + load_env_file(ENV_FILE) + + 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("--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("--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") + args = parser.parse_args() + + report = Report() + aw_api_base = args.aw_server.rstrip("/") + if not aw_api_base.endswith("/api/0"): + aw_api_base = aw_api_base.rstrip("/") + "/api/0" + + check_wrapper(report, "wrapper:aw-health-check", ["/usr/local/bin/aw-health-check"]) + check_wrapper(report, "wrapper:dlp-health-check", ["/usr/local/bin/dlp-health-check", "--json"], json_mode=True) + + try: + info = http_json(f"{aw_api_base}/info") + report.add("http:aw-server", "ok", "activitywatch API responded", version=info.get("version")) + except Exception as exc: + 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) + except Exception as exc: + report.add("http:worktime-api", "fail", f"worktime API failed: {exc}", url=args.worktime_api) + + for port, label in ((5985, "winrm"), (3389, "rdp")): + ok, message = tcp_connect(args.rdp_host, port, args.tcp_timeout_seconds) + report.add(f"tcp:{label}", "ok" if ok else "fail", message if ok else f"unreachable: {message}", host=args.rdp_host, port=port) + + try: + buckets = http_json(f"{aw_api_base}/buckets") + if not isinstance(buckets, dict): + raise RuntimeError("bucket index is not a dict") + report.add("aw:buckets-index", "ok", "bucket index loaded", total=len(buckets)) + except Exception as exc: + report.add("aw:buckets-index", "fail", f"failed to load bucket index: {exc}") + buckets = {} + + host = args.rdp_hostname + worktime_bucket = f"aw-worktime-sessions_{host}" + worktime_event = None + if buckets: + try: + worktime_event = latest_bucket_event(aw_api_base, worktime_bucket) + except Exception: + worktime_event = None + activity = host_activity_from_worktime(worktime_event, args.session_max_age_seconds) + if worktime_event: + status, summary, details = bucket_health( + aw_api_base, + worktime_bucket, + args.session_max_age_seconds, + missing_status="fail", + stale_status="fail", + ) + details["host_activity"] = activity + report.add("bucket:worktime-sessions", status, summary, **details) + else: + report.add("bucket:worktime-sessions", "fail", "no events", bucket=worktime_bucket, host_activity=activity) + + interactive_required = bool(activity["active"]) + for bucket_name, label in ( + ("aw-watcher-afk", "bucket:afk"), + ("aw-watcher-window", "bucket:window"), + ("aw-dlp-endpoint-signals", "bucket:endpoint-signals"), + ): + status, summary, details = bucket_health( + aw_api_base, + f"{bucket_name}_{host}", + args.interactive_max_age_seconds, + missing_status="fail" if interactive_required else "warn", + stale_status="fail" if interactive_required else "warn", + ) + details["interactive_required"] = interactive_required + details["host_activity"] = activity + report.add(label, status, summary, **details) + + session_status, session_summary, session_details = bucket_health( + aw_api_base, + f"aw-session-events_{host}", + args.session_events_max_age_seconds, + missing_status="fail", + stale_status="warn", + ) + report.add("bucket:session-events", session_status, session_summary, **session_details) + + validation_dir = Path(args.validation_dir) + validation_report = latest_validation_report(validation_dir) + if validation_report is None: + report.add("validation:windows", "warn", "no validation report snapshot", directory=str(validation_dir)) + else: + try: + payload = json.loads(validation_report.read_text(encoding="utf-8-sig")) + age = age_seconds(datetime.fromtimestamp(validation_report.stat().st_mtime, tz=UTC), now_utc()) + if age is not None and age > args.validation_max_age_seconds: + report.add( + "validation:windows", + "warn", + f"validation snapshot is stale ({age}s)", + path=str(validation_report), + overall_ok=payload.get("overallOk"), + failed_sections=payload.get("summary", {}).get("failedSections", []), + ) + elif payload.get("overallOk") is True: + report.add("validation:windows", "ok", "validation snapshot OK", path=str(validation_report), age_seconds=age) + else: + report.add( + "validation:windows", + "fail", + "validation snapshot reports failure", + path=str(validation_report), + age_seconds=age, + failed_sections=payload.get("summary", {}).get("failedSections", []), + ) + except Exception as exc: + report.add("validation:windows", "fail", f"invalid validation snapshot: {exc}", path=str(validation_report)) + + payload = report.as_dict() + state_dir = Path(args.state_dir) + write_atomic(state_dir / "aw-rus-health.json", json.dumps(payload, ensure_ascii=False, indent=2) + "\n") + write_atomic(state_dir / "aw-rus-health.txt", report.render_text() + "\n") + + if args.json: + print(json.dumps(payload, ensure_ascii=False, indent=2)) + else: + print(report.render_text()) + return 0 if payload["ok"] else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/aw-server/aw-rus-healthd.service b/aw-server/aw-rus-healthd.service new file mode 100644 index 0000000..015a906 --- /dev/null +++ b/aw-server/aw-rus-healthd.service @@ -0,0 +1,14 @@ +[Unit] +Description=AW-RUS unified health orchestrator +After=network-online.target activitywatch-server.service aw-worktime-api.service +Wants=network-online.target activitywatch-server.service aw-worktime-api.service + +[Service] +Type=oneshot +EnvironmentFile=/etc/activitywatch/aw-server.env +ExecStart=/usr/bin/python3 /usr/local/bin/aw-rus-healthd.py +User=root +Group=root +StandardOutput=journal +StandardError=journal +SyslogIdentifier=aw-rus-healthd diff --git a/aw-server/aw-rus-healthd.timer b/aw-server/aw-rus-healthd.timer new file mode 100644 index 0000000..bc6f25b --- /dev/null +++ b/aw-server/aw-rus-healthd.timer @@ -0,0 +1,11 @@ +[Unit] +Description=Run AW-RUS unified health orchestrator every 2 minutes + +[Timer] +OnBootSec=2min +OnUnitActiveSec=2min +Unit=aw-rus-healthd.service +Persistent=true + +[Install] +WantedBy=timers.target diff --git a/aw-server/aw-server.env.example b/aw-server/aw-server.env.example index 14fc306..1d43206 100755 --- a/aw-server/aw-server.env.example +++ b/aw-server/aw-server.env.example @@ -37,6 +37,10 @@ AW_EXPECT_START_OF_DAY=00:00 AW_EXPECT_ALWAYS_ACTIVE_PATTERN=aw-watcher-window 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_STATE_DIR=/var/lib/activitywatch/health +AW_RUS_HEALTH_VALIDATION_DIR=/var/lib/activitywatch/health/windows-validation # Integration Test Configuration AW_INTEGRATION_TEST_ENABLED=false diff --git a/aw-server/install_aw_server.sh b/aw-server/install_aw_server.sh index 4ac7a30..6a28f35 100755 --- a/aw-server/install_aw_server.sh +++ b/aw-server/install_aw_server.sh @@ -29,6 +29,9 @@ WORKTIME_API_SERVICE_SRC="$BOOTSTRAP_DIR/aw-worktime-api.service" WORKTIME_UI_BRIDGE_SRC="$BOOTSTRAP_DIR/aw-worktime-ui-bridge.py" WORKTIME_UI_BRIDGE_SERVICE_SRC="$BOOTSTRAP_DIR/aw-worktime-ui-bridge.service" WORKTIME_UI_BRIDGE_TIMER_SRC="$BOOTSTRAP_DIR/aw-worktime-ui-bridge.timer" +HEALTHD_SRC="$BOOTSTRAP_DIR/aw-rus-healthd.py" +HEALTHD_SERVICE_SRC="$BOOTSTRAP_DIR/aw-rus-healthd.service" +HEALTHD_TIMER_SRC="$BOOTSTRAP_DIR/aw-rus-healthd.timer" for var_name in "${required_vars[@]}"; do if [[ -z "${!var_name:-}" ]]; then @@ -54,6 +57,7 @@ install -d -o "$AW_SERVER_USER" -g "$AW_SERVER_GROUP" /opt/activitywatch/release install -d -o "$AW_SERVER_USER" -g "$AW_SERVER_GROUP" "$AW_SERVER_WEBUI_DIR" install -d -o "$AW_SERVER_USER" -g "$AW_SERVER_GROUP" "$AW_SERVER_DATA_DIR" install -d -o "$AW_SERVER_USER" -g "$AW_SERVER_GROUP" "$AW_SERVER_LOG_DIR" +install -d -o "$AW_SERVER_USER" -g "$AW_SERVER_GROUP" "$AW_SERVER_DATA_DIR/health/windows-validation" tmp_dir=$(mktemp -d) trap 'rm -rf "$tmp_dir"' EXIT @@ -124,6 +128,23 @@ if [[ -f "$WORKTIME_UI_BRIDGE_TIMER_SRC" ]]; then systemctl --no-pager --full status aw-worktime-ui-bridge.timer || true fi +if [[ -f "$HEALTHD_SRC" ]]; then + install -m 0755 "$HEALTHD_SRC" /usr/local/bin/aw-rus-healthd.py +fi + +if [[ -f "$HEALTHD_SERVICE_SRC" ]]; then + install -m 0644 "$HEALTHD_SERVICE_SRC" /etc/systemd/system/aw-rus-healthd.service +fi + +if [[ -f "$HEALTHD_TIMER_SRC" ]]; then + install -m 0644 "$HEALTHD_TIMER_SRC" /etc/systemd/system/aw-rus-healthd.timer + systemctl daemon-reload + systemctl enable aw-rus-healthd.timer + systemctl restart aw-rus-healthd.timer + systemctl start aw-rus-healthd.service || true + systemctl --no-pager --full status aw-rus-healthd.timer || true +fi + for _ in $(seq 1 20); do if curl -fsS "http://127.0.0.1:${AW_SERVER_PORT}/api/0/info" >/dev/null 2>&1; then break diff --git a/aw-server/test_aw_rus_healthd.py b/aw-server/test_aw_rus_healthd.py new file mode 100644 index 0000000..c679602 --- /dev/null +++ b/aw-server/test_aw_rus_healthd.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +import importlib.util +import sys +from pathlib import Path + + +MODULE_PATH = Path(__file__).with_name("aw-rus-healthd.py") +SPEC = importlib.util.spec_from_file_location("aw_rus_healthd", MODULE_PATH) +MODULE = importlib.util.module_from_spec(SPEC) +sys.modules[SPEC.name] = MODULE +SPEC.loader.exec_module(MODULE) + + +def test_host_activity_marks_recent_active_session(): + MODULE.now_utc = lambda: MODULE.parse_ts("2026-05-18T10:05:00Z") + activity = MODULE.host_activity_from_worktime( + { + "timestamp": "2026-05-18T10:00:00Z", + "data": {"active": True}, + }, + max_age_seconds=900, + ) + assert activity["active"] is True + assert activity["fresh"] is True + + +def test_bucket_health_reports_missing_events_without_crash(monkeypatch): + monkeypatch.setattr(MODULE, "latest_bucket_event", lambda api_base, bucket_id: None) + status, summary, details = MODULE.bucket_health( + "http://127.0.0.1:5600/api/0", + "aw-watcher-window_SHARKON2025", + 900, + missing_status="warn", + stale_status="fail", + ) + assert status == "warn" + assert summary == "no events" + assert details["bucket"] == "aw-watcher-window_SHARKON2025" diff --git a/check-aw-data.sh b/check-aw-data.sh index 4d0f960..7099697 100644 --- a/check-aw-data.sh +++ b/check-aw-data.sh @@ -1,7 +1,7 @@ #!/bin/bash # check-aw-data.sh - Проверка сбора данных ActivityWatch с RDP-сервера SHARKON2025 # Сервер: 10.10.10.13:5600 -# Хост-источник: 192.168.100.21 (SHARKON2025) +# Хост-источник: 192.168.100.18 (SHARKON2025) SERVER="http://10.10.10.13:5600" HOSTNAME_FILTER="SHARKON2025" diff --git a/check-aw-full.sh b/check-aw-full.sh index ce31168..e24d83e 100644 --- a/check-aw-full.sh +++ b/check-aw-full.sh @@ -1,11 +1,11 @@ #!/bin/bash # check-aw-full.sh - Полная проверка ActivityWatch: сервер + RDP-хост # Сервер: 10.10.10.13:5600 -# RDP-хост: 192.168.100.21 (SHARKON2025) +# RDP-хост: 192.168.100.18 (SHARKON2025) SERVER="http://10.10.10.13:5600" HOSTNAME_FILTER="SHARKON2025" -RDP_HOST="192.168.100.21" +RDP_HOST="192.168.100.18" NOW=$(date -u +%s) # Цвета diff --git a/docs/runbook.md b/docs/runbook.md index 56aab50..ff51b21 100755 --- a/docs/runbook.md +++ b/docs/runbook.md @@ -106,7 +106,7 @@ curl -fsS 'http://127.0.0.1:5600/api/0/buckets/aw-dlp-incidents_SHARKON2025/even Цель: подтвердить, что при недоступности AW API события не теряются, а буферизуются в локальной очереди и автоматически отправляются после восстановления связи. -На RDP-хосте (`192.168.100.21`) в PowerShell под администратором: +На RDP-хосте (`192.168.100.18`) в PowerShell под администратором: 1) Проверить/обнулить очереди: @@ -207,14 +207,14 @@ chmod 600 /tmp/sharkon_ru.auth 3. Запустить recovery task: ```sh -wmiexec.py -nooutput -A /tmp/sharkon_ru.auth 192.168.100.21 \ +wmiexec.py -nooutput -A /tmp/sharkon_ru.auth 192.168.100.18 \ "powershell -NoProfile -Command \"Start-ScheduledTask -TaskName 'ActivityWatch Recovery'\"" ``` 4. Запустить все launch tasks: ```sh -wmiexec.py -nooutput -A /tmp/sharkon_ru.auth 192.168.100.21 \ +wmiexec.py -nooutput -A /tmp/sharkon_ru.auth 192.168.100.18 \ "powershell -NoProfile -Command \"Get-ScheduledTask | Where-Object TaskName -like 'ActivityWatch Launch *' | ForEach-Object { Start-ScheduledTask -TaskName \$_.TaskName }\"" ``` @@ -284,3 +284,30 @@ systemctl restart activitywatch-server.service - UI открывается; - русификация присутствует; - rollback-путь понятен оператору. + +## Аудит CryptoPro и готовности подписантов + +Для повторяемой проверки сертификатов подписантов и встроенных лицензий CryptoPro: + +```bash +cd /mnt/usb_hdd2/Projects/ActivityWatch-Russian/ansible +ansible-playbook -i inventory.ini audit_cryptopro_windows.yml +``` + +Итоговый JSON-отчёт сохраняется локально в: + +```text +/tmp/aw-rus-cryptopro-audit-/rdp-prod-cryptopro-audit.json +``` + +Отчёт содержит матрицу по профилям: + +- `requestedUser` +- `profileUser` +- `thumbprint` +- `subject` +- `hasPrivateKey` +- `embeddedLicenseOk` +- `embeddedLicenseStatus` +- `container` если виден +- `actionNeeded` diff --git a/install-kit-awindows-20260427-211240.tar.gz b/install-kit-awindows-20260427-211240.tar.gz index 1e4c1c9bf5b12123a64a166797d2b9faea5e4a8d..967b47c17f01f310ef93061bca1e5b45d0ad185e 100644 GIT binary patch literal 119349 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%0Aw7MNgAt5Z?~=UJ^S3XBol7!8HF`hIGo{ zj)b-UV3LtS35kgveJG!b^M*${G-%{FGB3BY8kU~{n);#n+-v4~aP;rhE;Gr<) z9@}MMd;z+?dz3yGUT-3+Kytt#YlEHiZll9b)b-LVY+b4elrh+u#OvTc$B1*uHo&*m zC=t~jLfizg0Pn6KB~p@Je`^pV^`V&jTIg3ISFY~L^&}UyBw_7y=3^7NIgDTUoax9` zGbD^1D7A=`?g-yI_$5h*U+@8n;Ekk(veM*{sPRNH9};mDos%Ksud;8z9W1=zE`~}P z^z0K#_RHCXzog0j3$iF}!qrPXGxtF9SkZp&7{sk?1qi+=63uo)z3u1Q1vORkL zp*JM&ySgDD%ksUpRAnc1!U27TZ@ci}y|=OF;Y}CZ7JQkv9Jphc4E&BbFC}i6z!K?i zQ&71MdR`$tGII?3OnE?ok;X&K{RHLy91VJsU((e4mEFVCT*0u-Njdm$koZ9`mO7HED+r7^seOzAlOgXQ3_ilwj0q_8@`Dw6Me9 z!>;5kGYWv$X|uu7uX{pk)l6U~rqpZrnthLl_Tsd5HrpZ_F2rg6tWJn9{X&$%=uy1& z0bPjQk68h^?yzwYS-7#=3+p9LN11Klb5GbqACaJ7uWaO+b*p>uKc8HzYhkwryU`X< zZ-P0pf;cpL2DJBIh4u4sdRE72hsa`2hxkV4Cp}@M!)1N&M?s>G*yhAXs`&B|Qr2In zhipy~rZFQmASPtBY$CIsQ0;|1WLBGRK0Om9{w+0p!UTC13i?sh0cHtPL$U&~V@lDf zS(!L|7>Sny-L^n7t%f5s^F7J>rKH;&@lrjBfZF6TrU}Y*!D4CFn?@Zz(A2v`Rt;TC z5$54r-wr?;SINVWWuZjVr*)+EC6HLQBd>}(69zTjoAA))0uTK@a~U>FuD126(&W(T^#hEVLgA$Ti{<%QsTM5Iea3I z$dFKZ`y$w%;spb?gnvmB{(!G;X(BK!wgh+h%>cF%+TwxqvQ$OW;)6Vi$Ex&%V_Y)Y zX*0pleF<&uJ9v>4UE^W1N68#yV`u>@P13$ZE+-Qn%ch$!n<0F`86!9WND82ZZ3JRx zYJ1gi6W0aad+^(hFh(Dn+dwN5BzAyYOjl3EGV#ebu$l}uCZtax6+`K|*9*F-!a$Gk zrfki&u2ZB#h zG1kSN(6OzsGr~UoPY1t}q8Ul<#P6BP^ zMfJH9)|@PiT7N_a;s@y=-9xFMI8F4AGbajl%;YICh-$kNe|R^`6Z4N+&SM}QktzV3wWE}lNuanSf~>g4Ry6mm9kPFu(HHL0KDB+Z-R%5u|U zw*G{e{Nq%xQD<1f^J>x~QG;De@z0kBe?U1$%&6`vCwu}P2w+6kijP3SO6W`kW5ic=anLQ)P|Xo@WUP>!wXb=d86urJ zB=_3VuamenZYJZ2baWb*f(so@H5pV z1;*`Dc|gbNkHkJDm^02)ZN1Pi#PRxMI!u=AK#+% zo)dCYl_%X&*Hm7ulJiS@hs?icxZ}Xe`hQC=Scl_-BAt&6%d0_2sY0tuh!1q@&_mqx zfgs7eq&v<8w{a{Y0d7LZlGL)?M}Zb)S=P#I6^IZZA&~c(a6#1t30=N4jkHk3kw{Aa#fuLI{>vLp7*be;Ld$?L+VJ||lF=TTl&wqz( zxe+meXwXmU-oCmdh*Zm;XI$(#~l&{F>(G3n7aF;o}u<>6=bopbwRDC>BlXcksj=KEsnn4D*QF zWznz}@8^aM=CKrZA5o>hGAH7x*REiCru!Qi7ftKPA8d!9&jp_$7VrfN>tvWj-vW}? zt-W!_zZ32Zc07NFE)+-&$(Q+W;{Z^OdLhg>9l{C04NCEWBa3c*-ZIG&0+qL6!Sfvu zst79|1PDAFK@p;Zkw0lBVNa=hXjov<(KjDZ79)8|6l{1`7WuHFFBC#)4fvlle$FZ{ zZAiBAr7b2UFzdI8Q`?8`5hrX)F!Q{iA1J%NfMD?iAP&AOA~da34>WuU@qxC`KV=Ud zAy1-z2`_5i)U>k}BwyQO51Rbn>4C|U%tp&@B{1>d@TSW913ABcJZbET-v~FF&uqtL z?x|F$1?NKhbSk#M=5WU^_YLz%Fkk;0GK z1z98_)F#kx4@6_l1jN$dC+4D++N2Tz#9@J^e|{l^;&dk2;v(4pzU3ePvFrX%-OR5_ ztJ+}My0|Tr_`m0)(?`09@__sHP$?)Hp06YUn`JPL83|sv#3&>x;g2K{<1{B>+19iI zLCJ2E8b}CGJ?rlV<77U1z*nmHkPZ0dW3pfI`4-*&c)*9xibO3b2FcWUR$s-$!KUV%K_f22gRzfVsTAE}dYLO$+Sh3`zxs@(-o2r_JxZAefx?QwNTd6tAt|D zX5m>E-_3nk_i?)8V}3@B)MhQi^#_?LH;#PJ36zh))Iz4ySrXeA@#LV5J1b@fWM2SG zaW9uSNNJl&nA7yP9aeMuq~Qm>fVivR=ePzy?vqD`x&x|1?^0KBEst4nh-_QZ_lWCEwWJ*If7}HtC!+sbAzyh$c*;s=t*I9QDaoG_+&j!OjNo zK_DF7k7m^kBjbcAbG3BVGCSGE9sATB?@`LDFJ&*SUWolI^5H`sg`BB{;tD=_KT?-% zi31a&*ohMPpYUK25-p#bd_hw1M=a|;u}-#9uNOXBd(3L~SJ0>5YO?WHWNiE`sfwQ( z{MNWSblGpS@l?#KI7}8}P0MZ2Q7aYtVqb+p!{wqmx!Bb+MYC<>Lo+SY>G{68QQ$l` zHx1hQ6+B{0zPB;hQa%;p^icWvdE{w`4pm?1*FWZyTjr|baEu?PcRr+J+289IV&1cy zfz|cbFC}ej?7*_W>emgs+6`>YZev~BT!%^Q2k|>$|KeZ+i;D-Ic%mtHDABPUY&K6D zV+B&#gKVcDw-v5V@k-E~8{iqd)WpdQhbP@mtKOuy7noi;!wp=(d;VcxMM0Tl30*qm zd@b(+iO_G8w)yL+uv>f}PB;FLTChho%2`*~V9?n+c$&M;b~K0qb6XT9g^$+Nf0%1QBjpNsB1ks2`%CwSm zTWYvol1;g@>gMnl0-uj{TI73K{s3>+QnUu#89=rWK5h?brX5$iJmKyAh-k&QuP7|k zq^^LRB}~*U+`r<`uX{4+lB8^LudJ?Jlr;BH{Do+41v<(E&KK1GahhrB<#@O4L%Jc&IKseGzS zRv%L5AKHNGh0UZ=hW$d-%I}ES0Uwbtt6gnSHrmhG$xYICRAkz`-$})2 zN_3iQk2ZA9Cv2=JxH`IUr_~vNKrqPMKl~@!%~cp1=>$;187YPSw{-UJ%&)bLg{FpV>wi&R8PbZLdK-UaT(X^y*gjsl;_mHvgi zR6=Ib&kjVmUa8S(kqn|lZ0I7U$*lRVd^oRRvAXyGacFQXQDD|CG3(q!+lSmm(>v?3 zq5MnhTD4I<_@1e+UfQjEKf#f``O>Qt)!b_k)$HcPM+tbNj*OnIXr~9#ZJf7FX_o${ zBz=7>WiUp`XS^q(0{0F2Uka(~skTVQ&+DGafC1}*z7QJ8UGRVBIL$t^90fq|E4bA^ zu8X9*sZTLRsi*VKZt5Uc_kPM$vK2*i;<6RJWJ;sd33VJVUGu(TNhsG5)r_Al{C^&L zB6`dxw3Qm}Zcz`9$BhtJ8K)}aRti+;mx-EXR=KizNv{Au&G+qS#}B&(Oc9)f7pYlV zME&tD45^iIh;W0+cCKW_lHtP&VOgQ81e}6^J?e~MJq~0nDLy86$hMi>no13-ZeZ9~ zP<|)WZFgNssp2JC;rBvfFuVitbVBb#r!~N!zUVuD%Tw`1|5d-%s9&!9RQOowo)$Wn zAVbgjwfESjpa(pnr*=sdI>;<)h7ZCZV*~b8R8CBk-AeeJY6gFD*06LydAW^{!sqS3 zGlWntg8ixkt0z5e)8R|g;fF%x7GYqdd$8_*G_x~%mA^_m)8;x*Mdx*cSPPRKekPF&jt1lvcDmZu9t!UP(Z2BEa(~hbig(ssZM;Lnk z|0z#~_-cT|e+Rm+@+T;Oey0=dlB%U3{j1Pq9*h0^siRsPpb4#sj!S0cf2MmHx}V6w z$Bz18w-vTI>Y=KNH&K4Q|3u8{i=1`qDkr-LEsjCRU*ZQGWt>vKpHc59 z7I%HyPpDSCABQ{DiHQl7NJ6Wm^5SVFCpnmReJkjj0JsIt5YP!QC3}J{@4s%%!sjlN`*YDh_Vgz~#pF4n$ zf#uJoq0sq27rF3NdPkKey``ufaP-jn+QsFo&Gm(!tWSEF$5Hfx;dP-Hk?9S7y1N~A zfHQM5^)BE50Rf`8WW6^?w&h#lGl>NzGMK{Rh$X6);z+y%xbKml-JC%XSh$Y=&`SZ7 z=Rd9T(%hV96;$9o>kaVuO`97I2mMcH&Kz~6UPJu9C03ThA->n+POx?z9^pW zbgGqWB$(Woyhh{Nj_?E?e))0e*J!ESsASP4z|skoXdxZdwF-em9;?B`pDEDFjXHo^ z@LStje1BkPv5p*W>$bp*Xb9It8a$t>oKZCp^|x&td#Y+;SDiHGlWS_=WG0UL+EOrQ zA?#upiZyf?OR73^Py`R%6=DC+?Jc&e6%Oj2}#k z)uKZjsJP?DTVZ$Bn?BvU<4v9MWCnA3aK_{OA@2RqO>5AKW@TTNtg7bS}uC115k&+&$j3Y@QyrhFh#L?^Vj9Z(i|??j3An05@P zpf8BWPIzj9PRbnfc}|M|++x=}A{nXSC)ij}T&hI54BFJxm-iWfKy$=tkPYeUw z-!?J958KdO8H$-ficte0LId%*57Kw1Ml(5!SE7dWpzWG0`HE*NJNi2~LTXiD0v_eH zvopYxp}Stx^IKsb$gqCuR9@L0y18i-8+K87s%>I-LSl@%4F0=fGj3b})yx0ar)GwrgS)hSa?~m2dOfQecPYaXwWZ=@1q2pj5a;Y(?J#CqB*qI9 zBQRdnOWf4TqB@J|(*~x8wNoPz7Fwf`?~0A*^wd;lGc6(!%4t`2{~iv=FTq$(9+k1; z9$sq)t%&U=EJ(RJhG}^zt?=_^4b#LIP9keK>0Aq{XUD`CX0nWdR)rch?q*ZD+#>DH zD9pk^!pFuetR7{ygVV#-d@)eg*zW2OW=<-QaxuM*i>V0AsB&9oM;;Dblio6~aHe$8 z%ve@RlrEUU={F{F#5r1H;sHt|rDV>ugIRg(^%*A!f^dWFIhE^OsVORTay#(bb&mXU zemm^6tE3uEWX(IMNiLxxxuhaPJB$s?rE)C|{-l!fufcJx<3lVA1lG36APS+W zs#NWDb@tvpuTq&1HnTHEHB@BvYKAePf^RDm?)s?ITV`El-=iu{(`+5RK&n-dIBLS1 zJ1dh&)loyPVvD+6Q1%nA0r|k+W6ki0F+}$y@U`3AT~XwW4u$L2S=$#J z5N!j<&nv^MOMn;Qn1<)oZ^zNjML*fDn)40A-v)>xUm|Qod`d@;35U8ZL;Aj7CV#Uz zs$3`Mvufkz>kaw(dZT*nXN|XBzVX^bV0Nc%aYKUT>zx`CCEW*M21mD|Ck@K%6xQAL5k%y8*7 zeg@3pTCI7b5i(h=(RDE@DyZ49sKewEMHo2W3B^1cuubb*7NW5}$Q~{%Z=zFlbdbJn z{H*%ctcVML54Njd6UeiDk4(I;$bJh8ToDK)vkrFS-t^4t*XvXCzv+8Z^6S*S+1crl zAYO0Zot`{#|9XAmF8s?sUTKu6PH*c#{=uE)&1z*979*N_Ytu*%jh|hcs{PonZO&f5 zcKybU*GhCro(hOWq~>%5=>69U*8I&tx)Zz?2D@)ZF;EB1-Vs*kj)VSxmRXjt01#h| zwx2$jcB8YXsh zv0c>Y30I?>@ENQ5dxCMk$JO_Dn*#$KqZBgGR@0nNWeoQR93^iD`OHaVLp^*|&>X?d#)+_#D4sSVdA3B6jQa*VN6 zM0+)}0RyiTtzU2CH6uwhpCvmxzql8lD5OP<;{l5(cw65LlpqWQFV|(*#Z_(qY!$ z4fd+!@?*|2o=u`kMR)sw!01bzp1!63nDy@7H>O)2kXkj-MH2{Mr^njjRNn6UuefE}+uKnA@p`&_E+OYUmDm|~izZw>ALo_o#neJH>etP4rK##E?XvQk4X zi3u6&Ai`H+1|l&{Cr(eO_n_2l*JB^p!;V!%tc00KZ+a^2il6LtTWN><-T7Ylw@K8k z4&u(F%oA(vr9lGgKS8C$-}S>jhv(%A%Nvvx!cP$QRVDAl0s8)gu!v#hC4it>6o*@3 zS0NA$AwdjpQxT;itO+%ozzTXUr6K$pKZh=!!PtE<>}Yd?{$}kBDYyxZT6G@h;_iKY zbCm|dtumQcve(pd|Ea6I~9D3JP+$V#G$aF%qoyB;t0CQAP(ktnUO0t zK8fKZ#j=UWA zy;&X=$7=~U_o^vHaW`l6?y7fv=0 z5!UnfI4V?c0J-3ppK`SXR+p?Q)sg~xjqWX0xFmHHO7LgTH{SHkYpl#AltSy*?lt^3*t)iRrpV}Gv> z0wn&eVz*H=5&Ijv&}9TBjEh5-skg(R(@u_c50zs{H^x~@7_j!u2{}-?TRO|jm#Q>v zpw6Q7MwQbyRA+I$fU2eKv(8G|dsQc0#EuGzP4OINrzOD)xmZU{PE{CH^l4F$+tNV7 zTaQzWNekzSDOwS?u%Z=kh%DOMe8EL4lo4MXWhAD`%Qr;NAlqO#nT1?ZR&SA<)elM1 z)`JXLJEk?FkG47Z|Dyq%YL}th-M0pLis0_Q7Oq9b*z=o14dsY|VCVfxXNq;K* z*pE^SNr?>$kogR8!@sh$X^x1CiP9D>nN384xRp+u{j69tA5vG0OjjoEdJ5odL1UBj z&QXFu-e=v0d9bP9LST24XuQfI}MZTj^7ivCNBFuDL#rmI$Vd1ZOX&i z=&iVL=f7ELymPO_!N0L%lf?D3KmJ(m32i-4@EbXUV4E*!}Bh$*@bmKHZr zA}9QY^1{KMrnnF{+frzjxo0dh-FdfDaN-3_?1hO7Yf7*L-VmBLs(m|rlA-enq7m&7(=By7rrvTmrz^*`Wu#uZrS?sdkXmT0%T$Xq5%kkyLK*l`sYjGenCfJ{he*3RSd zE-<_Wt)po%61f5jMv=dNX{Q0BjVpDe(fZj_YaDu;x9HO*)8bjL%9P2HUx+Md_fT%? zS_u@DyYjB1Ig2m`0nupStx~((tai>I?p885eYe&6p)*QZ@{B5|Pj%{EGU9%As2y$8 z{i(DFafm%?*e+GP=j5V0OmXeFDw`m0G~F0R@+y!jgnUlU1l#Cen{|EImQF-jj{wfP zhJ5KAx;+F4*bKL9&k9Lee8Xr~nVvl3VsVHTdG2>oi`%`*r1p{xzLl%|+FQM_V!Q^% z-ipH>U$T&)R;{Gyn07#kF%w@2#)Z?pIv$Jvu-+TO?a*9z@vfSyR;`k^~L2SeEHtF<)1w7`GN0wS;wiB7!{V6d7BDR)(obwu4TmTnhsEHB89sciV-nV3Hn30IPCj z9rSm?7=wG(q~*47Kz~AH8ELBQc9O#^irZn_ANZYG*xihM*Q$7r3y8c_iT9ef5=VD- zqB}*L3*)H#QU=45nHCN{#?&ixNB_OclM;-_aZ)KBn+OX>A)dZ0=42~xuPrRCE!ZCa@5;F=OY2v772JF8a_!#Q>U*_&i%V~>&W}jsz0I`%-Ac9t zze9mWOU!F;Gr>AFu2VFn5;_Rv!7gX89|69s^~{Jyd{g)ubZO%Y+D$&^LT8oCG1OV540~ORHgOgeQv``o+m8=thqIc`^Ce&}m9#KMu(0Hv zM0DSUqf|<#QgzW+WrRs&+j=1$J?dTjTa4RP$$*Lic`QTC^y59l-1U+UTbH%f_kmEy0yL2rF|xJoRRi>UnkedV zkyLmlvqtV`ais8_He$Re<1JEpXu@KMdLHG6m2KKHtKBOxh&pi_lSyNjc7`KJ=_B4J zv0Z;(da0_se@!F8@OvVzOHQES#L%ee1g!b@@VCnW`o^T}DSe9lNPnfi8usatqlk_V zX6iv2V$CD|E@Rd*Qk!ZE)Zh7=x6Bt=p+$dd3QhVO7Kup)L7neT-+b+qQH%mk+1zNG z+pY28hcz6KvGM#Yr*n9_6`Cf|*I6G6^!?s!k);j9B(abDT&Q&jjl%d<>_CVKXkmp-!$w;@|@ltU638p5`4(jdFT4b5CT zD~~8e<62|FX@+|!Ub~K%;Z5f3QsNi=*zoLi}*r&ddPwihcXCMdsLDPK3Br>RcfCCxo;Es9js&h=mOH|8ly>pLY!Iuh?7Sbh6ISody-R7Wh7!w1v_rI5jnQQJ2m`81?(>#s zJkYpED*VV@bIv7VnwoK@1LU!&NtyIsSO>B)9KA^AT0me|LBU!y(MZZXda+8gncB2K z&garpOmjrg34na!qz~bQqf`1dWn*lM$^*ZIWRl8nLV_Ym$n5PIYusCM+(2V2^{Ly- zJbIm8kdU;H$z^ezPBS?t+ARadCv|P0nv(&4W%xSkk~XBGvK;CzGYGY*yxD-d zCE?NJF({r#XMOodNkrLE64f$kYt>}@%51}F1jFpmw@=z*V@i`ZNm6>I-g0pRLeC9A zI|mW!3q$CY##Yj2WL0_D0FdHYmHC7yg|nIKkuvi2)keD6ScHyCCtdU%uqc zX%h`=k5hth`egLAkm=E2nPk=`t;{rilkTGNK?;RAB4?)89C9W-mmdhHq8@UptFh96lH_3zBWf@~lS)PF51O1KBt!Mr%ix-(b`S}86?yFq;3Pw*s? ztN}^T*=(Y*2}X)$L|D5jPl|T#7_{-0j#81ei#xW1I$rb94kHS$Zk@5buH`7KiIY)_ zxD$E(B#`e0ds1gHBGxFY1k6B2HtNSFUl5rRNQ%@)51NZ-h44phW1Pi7g9Bj!&~`H# zj_hQrg9G*G|C(F<=G;&=e?1wU*lbDW}Q82b`zyE7&FgfY3qnS|7*O-xOIS zESeEFl`~RkCb5_$h;n3{0#TF;aHuDyN`AsOJ5!xoJY_wbWKDijR?w{APKhHsn>%t! ztwCN?a}_+c$SfR{pIXaxG)Fl?MNUXedHTU5gko6lWtRm$iO@#)Y@ak&lSp)~1Mo$eD~x0a&afy$VRCg4RM?V%%*QdMM>5eP)*-GrN?7XZ$`MF-;%0MWeA?wn_?@-}eE2YpGu(YrytSBd zXnuEx;T@{F#y+%ctr`RQepL-!RT6P#EApvhWu?^WTw9q9Q<6GrEa@ycmtLl>;@Cz! zCWCsjbp%h&RAKQ2WQ3>}bSuNwX*Rh9$bIv@@EjW#Qj@Rddaf0r^bR0NMw?zv0X9z% zb!9ys+;!4;xz`)7z0#OWgU$;{QuDx(p?)xEslx|bywJR`MSS$x>+RQMDg!Q*2JhzI zX)m}ebYu3^>8Y92SZfqVfr#IWuI9MzNJoe5e!n-{XiU$%UY~;hrj5EeOsT~2;Uk!I!mra6_W*%0V{q24sptGr+FB z!4v*w`H!}ZV^AzgxaF4kFhu!wxV4R8$}2lTI~?p(Y$2ezF{0Gaq}graEw)?{0rpDh zKr1k}eLc)-VL)>Hq!Ct!T1ySl#0a4}BuqJejOeLuL+Ifp`K%aFCPuPQ%MYP4qwV65 z8Z%ba73KjYO#fb8ezAf*$8F8(pEx_($_%=As0iio_M{mh4x2oZ=9a!8bN_69T);A- z?hxzL;Z8xt$)L;5NO7C&$;V)RYL`I!XV&>pTW&He&aM?}O#`}t`7SF$^q8*LcWJP0 zu2%xrbp>Y8&g2SP7ejJZSBH+mN@|@qT9=>nO`Kn;IEe7*jCSdhL{Sk!GCSCU#i!Zi zaq;=>HVKG$lL2YGT4}+)ioszl##qfdYRcT#Wsko_`5U_LqJ6EvFpcDyF7?A4!w`#I zam7hxsF|qlIi}7@kiw@Xj8=wu59npZFzsME+#wDMBF{lU(5>slg~tqV|EyOwpRBoP4<`u3BMb0c>U5bnP^`J};-hY^pl={smA zbMT>fLM&Vm>@y703E`z`PEc2|>kyA1>*T~4pM4ogoPqW_3q$SE;qQ?<` z#ap~@=|5oZ-l`lkxyWuXsR+Zsh-8wl4h+GW8F6(aUocXEhq{7gZGKcQC51fY`Yr5V zUOWxyi(B~-=NXxvn#zWo&=!#c9Tzr3z(sZyZ2Nou#vnm!Qj16dWjoM*!d7DatjnV5 zys%fSc$~Rzmo_5pWhx1+s3Hw%dcsa^fdU#Fb?C9XMjZ5_xPKvx9o6_?op$BYV@uB=$ z`rxCi15JkSjKJbkzZzY2lgpF;=Iyf_E|7OuebMReCV6{k7CY|o>-#C}rMmn0lXO3& zj@aIy`sOP5$F~G=?AiYq4;08-A1~H?eek;>=S&G%U`g_4$y!KND&NnfH1vEMEP-~a zqRhF?x6z80O~2$YqKPkAJDQf%$hHip7BwR~Ql^+Ru5l^%VY0BZ+1Z#1%xi8!BE@@N zIFLwMO3f0J%DLp*;)|8d@RT7cDHs(eaQgO;p6QvPC1K)6@}B$%?i!hVQ8e!Ij!f=M zTLs=0hL1&|Z$){JihMtc)Zs~f%c@O92Ik?OjnCtz4KdM@7j2U5X!k;x2wO^t2Czb{ zEo!Ky%3ZZ_*DZ0~#;_P=&EleUBWuvdKh7sa__f;ppAbIaN{ZW4*f2kG8ft;*LRf`i-fg z%M3KV`+u??itajZ5oCuk&Z4vUZN)A*y0-Ir#k_>^SOqHAEv&gp5O<*Xa8JV;aUT!!HJj7iXmtnOl0& z$Ne#_`0R9+@^_QFK8J&p;aZWkyJT{vx=t#zcK#!uz#PND;r_qT%`LoZ{-JdkBEx(< zmACqi^EA0{f;8`xzyf6)IYo&>(Y@&l7kf` zIiL2~m@Bk)Vr z%oeMzJ82@ceG@A4D!Z4ugy8Rhqnsh{%H{UF<=jJf%@ui(Y=WS=4OkI@#f~K3=H`OY zMLt(Tmr*G^gEcF+P+6`ap2*B@Q=LO3q1p}OFx%aj+~h&Cw(zfOnzuaD6&S~F0!tC9%%DzW%-M+Un6V(u$b4SoEIPeX;Ap8hdHOOu z_+*lQKcslG4`~TM_A-XU{jc_)yxHJh*nJzO^9gAN5pj0^&x$G0i3jq9TpiAdd3NN< zM6=?@XYHs%Q`y);Q+kA;sr=y5`5{698Bh2B$|2FLe4e_7n&Ek94t-+ytRH+r1?7Y_ za1&+TiD7*|6y$mE3ATtAndh(8z5V}#flLt$rilG3l*LF-6p`((GPLN21pZSBF-(40 z7`BvRk3JpvT{&KXXzRrv?52dj@-mh)1T?q`+htqU`a(2Mz2!1x))gEukp|=|eUzJi^qv zI<)^`P2wl|D@Qyuy@TvO_7M@ZsE6u3enV_za71Qii+EYmJ?oC-?A7vgpPy8niwSDj#&SCzHNR)B@ACO2XA`I(`C`RW{>L{|2 z?jC*C^c8o=KhKCB7cUgk;hMNQk(Q!aqzEr49X!=32}q1q+Ls&~)DjzHG%`hho(cqZI%k(co9@6wCK$Ii|5ZIn0pz!fu;ZyUz&6uM-*E_v-dm>6~Elk zscpZz6%_QUoF!lwqJEC3NXCG zOESEJW#h`VDTtb;J)sQ!&|yzStUjG{8IKepTLZ2bSGWu*YNKnv1tSAbDQ3^ zP!vU`_x#w6mf)Q?_++ zcA*C{KUAXyZ2aGZ2@nQZfP`mq##1=i?&R0paZm!JqfOmW z&ZJyu%|Dqc-sM+r2lIn=*dKamKZ+9xCW`hEYQ~jBy2l`lb7h@ZQ*c&tp*+tATb$s^ zWlr(Af7f<{4HgNL$y!2iO64g)|E#Cx zS)@i)ZxMCLtTy>}0l>g)N)Ps1ppMy;MEI@T#mch_K_!5S0Y?!u>0FOj3&mub$A5ii^Ez8`M|DjG(az9TV|?D^W-#Y1#Dkk^`X`Y zCcpRFL8mi1u?X%sgc?e!v4Pa{#Me~qlEJTYv|Clt?}ab)a|hM?3^A!Pw8?;$?c z+?n~eiB;h{YpJurZK#j0(n-$hylv8=ohq~p$y6?mkEXF6SME3JJJQEU`PE1vq|n8t zqkL_&G;MWG3)&2O3`?woc3p+Q9t|K32I<*dNyNZOb-ZGq^;06rTdG94g*%6Mo+B)h z^jjlOlCRk&pLLdfFic0uA!2v`%jq52VI6!j!mx_92p7(VYY>*Znx#58yw$J-I#P!H zy`p@=%JuC0AV#ykbU)L*)Eq8n8iYf5oFOT{vNhr){+XK04* zaCXW_NPd{UNck{1WM5J};}|*vBA+m19kMq{B{`1Hpakbhb@h-PQaVh2hbsKYTyu`| z?X)T9DR<==xV+_hPy7QdNZjY1WKXLznMdujYTNFUJ+eM^h$HKYJg}}D?sPh1+^a5m>a$K`El2S`cTYvbFLDrYVHU zw6=9{T4WZ@lZxSG~L&xZ{X=&kO`VA-%e=3S(W64og$g;Qh1B< z-E)jxlUy{4%?e9+OLW}7whc&rb*|FDbMywxMw2Cx8{-o$GKNKQE1g{YRe~0Cu2&cb z+75x0-w&5nwd7{2gB3*DTF~#qdP*(|M2#%X9#Nb<>IDiumiSIwpx~~Keu2VqjDxD4 zafbI|Zd2rYCKTPT7~u|sz*+VxLlz~+a7!VjxUV(rA;EAN=pRqrF1}S`%#KVYXBrD0Q7`61~+H<&v;Bk6s)_v?w zW>~Nzge?NGnyLvXYL1z?YS#%%Qg)KkRaK(JklW-#oGy{ERy`uhj3^muC6jG|#jOtL z?NGMI-VGLjy75*^cnbowv&?Lc;I5UC^1DlVKW5=w3^+_sX7B8s?}S1k75OpV;x=T3 zOv+%M2AHqyDrz1E+WCPKrQImrp&*%Usj|St!-~Ed0-(yv6>69s(}~bDb{T&golb$& zVOfPBTfSA9xHfeo+tFk@8g$xnNpYWz{&KJA{B+JWETdg6hEv_(wOT>K$~AxfHk**(i+>@&mk zpkf$zI%DEQYR`wB;It3oC>_Y~WkrVt32@AgI86=6AsMO}R>m$Yb7qUODdhW^$#FR8 zq%TFFgsNL&8NNGOTdD%XLE`l(&P#Hhqq662xmN6%f}Q#M^>!TV?N|t@lmI30D9uAl z{_Sv!sJ*Jp8x$YYTt*o?k-lsaA%)E=S*hw=E6(k91w`N=2J*N{xHRK%Bvo@e)50iW z!FS?l&`VsIOMQ_zK}Fp`S%Tw}Y={?cS-)qkWC)2llWJ>T2j!;3(yEyLpE7y`c8G;| zKq>yMrhS#u{Fc^#xBG_@a^NS9j64n~Et_z@5D$c0l#P^OOj!$VF*n4Ars5&p_!BCy6yiRHJGisFX}vxR znud}m^;e@c@e;QoT>bLlaWQg{GUY#d0di()gtvvf-55yf3S;EFxD9d+)7l2{e?gTV z9YLxbSXYz#Sg{~e$V-|cp}L4~#O9#{TPV^K4KZ=9N4H?3Lt@pvPnd`MyNb`Ggps8S zF_n!iq>pBE{%UjeO7qInyGzSgmzq}>mo6+{Z8o**4^CMA=B;8t^KZYK0|f&hij45r ztJUk*-?~;AzcG>55mn&&1UGc;#u1=WugZcX^UU%0v?SP(x|-Lg#uo8_5;dqfQCv5& z6wo5ldKipCjGLV(YG1FK7%*n;`t?dK#lpMm6W0k9liv8X8{yT>(!4s143TN$G_27xv6l%R1;wU zRCD45pa5palE1|nTHp(u($PAaTUtnwTH*V;1S};{+lzt@4lIFpNjqFH3@$mR4yG|B z|7e0uDVhq#CKA6x%6#gBL57V~Ll9E7RT#x>IgSb`2}S{-x>_USPPH@Zi66^uXzD0N zV44Ipg~mGJ6PP>wMGDX~=TWgUj@Kc-4YilbE=!MrCQ`ec%UvIpnG(e~|k-`ZHrqQR6jVDIGOk0Vkk6h^DoC&yk_x_}M zIcZg5!k#Uwh#9?^Fd&(EZ0@V?`QdicGGeWmgw(>zpZ)DHnN0DU4kYCu@{3MPg;eO7 zsY4v~maX++bWGOM7Y&rr!DLQmS&D;rCk@90f>MEg2X;*^W- zUgmpzb0X9H8F#5Gw1xqW{@x-f97vtcTm?z_@fH~+^#g|ccBc~_m1aNE3=yIq7F#hH zi(nR3XR{2VJd4mxA4S|i?O3CjSj;Zn*Vl7F(ER)PXh*Wp7qZn#2H_(t+f;Hew^mw* z76g=7E@9-otfL}%eoBkh^@a>cQrqB~)wgD^HQsvp#=Tc2r{Lc{rtVQBDAyLN;)>09C0z0u9Y})4}CH<@q zxw0Imwa;)3Y48zc7kOydhcSB&hZufGP{7I=fJwdgLqYxtwVBPffey~%$71*bZFL5=X)GB=|r)kcyrU{#GekD`YS(@r?}|oAa|E&SND}1=H5`osxuOACJQwn>j7`acA%C@Xm|oN-qBh{Y`p? zj4`^Z&QL2-Un~_L#u!7EKp7QH-h#ly3tYD^f z%19!Hcu~!jFDB|B^zN9(HLgU~i72iN-zF<~liRBd-znPx`d=;!Z%o53^OEhOX`)TO zMQ2tRSrEfUW(>c=QYm_t>X^o*CAbZy33~OfIUZNGMiZDK3y)icsad6lh!#;5luOrodAIw zuEPgcRt~yAhpk}E-&7a(-wT7?x1)IFcA&G2B6e~ZhB6{c;Z-`^9GR9y+tM03Xjcz~{ch5MhLv-Bl3NyU1ujKl5|nT*D!xIOSIfy!3k&CJnt8R{Zria% zJ@*Z4TURLEpv${nvHOQR{}II~&GBBJBd?OX+T5A84|nRNJVX?k6moiia;0vg5J770 z0K?)9&S>ipiH=hID4>83`Q46f;eJk0Uv9%f0D@3pTz+eOaxx?vRf?5SuzTO83F8(> z5&fi`##y+S@#Ok!3*91<*{k>#1eNH!ID1xHbomN8cbxQU2Qd3=`+3`rej_B^cf=+F z^5i;5t1wol2j!4^C1w#dL<-WUwp3wm6?h;uf7iP_=+in^=*hP3-fk^7+#EkkoY=AQ zyF;5cg%*@G|(?9U0p7r4IGr;>op7#S({));Cd;cB%y!=vQ zki?CRu-gc_x4qt8e>>`)7(=_c*KBSMfDH!CrWfw?qPXw*-EPD@H5nU|&*ClE;FCc9 zM!$afJxb)aB*J}L{Tgia;;0oQ>H9tPwIA&CHp5Q9oj|FCuG9vJ?1g%yN(2}WPrkmg zetuFt7#v;c zsx*RrOWmy_i%+^(>VVM71k`E=n-qZnY(SI0wh0fKII=4CXO=)rc(t?MQq&D*<$eSh zrvBDRI07YNR>?TLE74E1I>!EP6YU4E1#yiSMDix;@m0E|10$QO4Eme3H!2f+u?b95j%sS168)iK(4r`V_KIC~!495R<|6j1u&9lSNqa%&rRv2dg`|!) zQMc|3^Rcv)R{jpJU%xgrH9IwhXKsFiRo(}<+;M~#+-U{9zV{whq#H%r@?? zn2=Mj-S79BbiE1qh#;dr3&69~5zy17s8_&nW`(5O-uW=O=WB zD${3dkg@zFU=iK=v^nbekfQSe0T(cYxVkgRb77j`at-$Y$pFk7{U6{GF7TnG=6;-C+xV12owS;{Ki* zM06`f2GUk$8j&;m-95frCeq4~9UtIUNDiRuXkpjr{)C36q>0m$He*g!+8~7m#KVib zmFQMw)&q`L+4bYD_(~1J*VLgUuW@_-N3hql{^LaQQ`#}LHQtN1~v>O-@lJIaO9rpu4ajM_E|>%oe_%d6GBRMnNqx!Tpo~ zIiVQ~riuua7=@n@7+gwsmF0IUDd|LVl6H_}TI38Bw7||^t*u^J z^DdHtiZ)p^X;ju_xa?W)ZZV;ZGog%6dgFgLK5-x0t=t%q;HgKFe{5E+-NoT$DP8+f zA3)(4M2P!0yt|4v?$3%JVovXC*Q<;+^m|u5e-DKK+^4aFa`ETXH0hbl$iN+=TXT1# zTlYPjfw{Z*?|l!kKX(`Zy>0H&`eiM_L=#@h*dqT<=ymemH?uTAq(* z;^LA@lLAgv{a)DI@DsT|iFaW;)hJAUY>25U)m$o5ZdUF}koOzn7IovU0(-x~b>8ZP zJ7IrrW=eD(1{fw}?di5e(lekz++snb?6yTqDIu$tiE$g0FJ74t@#Q29Wg=ZKLb0%6 zYlu{mcG@Q2lm*3XuH)})w0&w=A4Z^S(67z7k;8nRe{2al#zm(6quMWG=Ox^RhTniXd8cuN?C#* z7)1=^!Y}Qej$=12wZeoSQb>J%dS6hUShZ|UtqP02&MxSY$ujtvi}J*qFo9LM)zk%H zDb05R^RcGa2;n)Nv}o6*=5V%)xk7fQaXLf8U!Xta{oqzxn4#mY%>X<)r}Q|W2(W9Y zgWK|5@fv4P@xHO)i^@dLB*sq@45Aq5F)SZm_Y=D2L_$xt&B;;(=Ack@RZJ{9r*(t3 zSiKD%n80yGX)ijkvl)`J!Rqvd(?`F(>Rs$sGGRfLU5O~9ATL)}Sz}+l?RUa9nV*|6 zX_i&(Njr=+lj;g(Fw(}~!l-Cy5~A27P&d$|A2P_&9!Fn7GTLc?>sy^@qgr{j=I_c* z1Wmu2gc}_WWCPrfB#8cY9hnWw3MX#eI4J;)}P0~=M@J1%9=5FkhEY1WrD@+)MiNUm&O_JrUxf8Vm z-b|ov(S4U`pUw>c0!-b6)5MR>;UR66d#D!cM%V>pO8!-ul!*Z50GKWEFtxNMy=c(q zyMUIx&tO6s@dlZaC|sfJa+9d*O2ZmFid%}&q=QcJOM9qq3KANz= zD2!vT)9$FQxVw>U9(W<5Aa0?3XNqy4`qaZCuy#%XV%l2&n4ouj&`+fGoBL%RL&@VT zVJxCe0G%h~kzRdXTmxI7H`R8KwBnF<@43pAZnzl+ZK-jy3NFiB z6hW5)d=2CgM*~-vSKnPjTt_2t!t z3nEO)TJz%a+ByZJ{qanF`t%$1=_zRCjgl>p@u+MoOY@g$NNX48SKnP;nwgq8HKZ8| z%vA%H>}Ug~xOs75{?huz=GyxF`a<)<;wnOK-1g%}C)}_OL75tcw3qI&!yA2X{?g)w z`Sr!+rBW?7q$@+(#<0P2lF=GSIW>?%YErD49Q7F}z0_p&X$%8VNaiGIQ$3GsAn*Z}n>8z{c(Z9TcUVpr5X8te;nH|Q1SBxQW}&Q7QQ|slD)_7_xI6DRP=X~IVSs>LQfh3>_>($3 z5)KTpI8kp@3~Pl}r;hGm>NB@oH@iW6odG|ENmOR#A{R# zFmkOzE-Z9sh#Xn$A~z<=07jqJVUQhV>XaKh0?E)3_cdwALXCdZ_V;vz!k&PJi=bN-ElgN;Oz~KnMpJz%7ax|WA*Mw> z&CCdPVGjQ}EdK5G)D##^y0D)?pujvSzO))T=$udPDb-Uye&ZC5BkaaIXl$sSIPu1h zQ4wgPr6cWMbZb&e*{+|gQ*%(fnVx%3GK;}df{jXZf|KYsD^POwF7@huB^8mQTc&g@ z+t*%gRtDWT@LSuMrUB3(iXas1&YYujb96HAAVwLhv}omuG8(N3ZmH3hC?m||mOb1L zq1UT}F5*SR)D;FWx=|^Os6JKB;Sv7stZ1V~l~QsvK&hMv;`YAcBH&=LaVFC~fPoSh}+49u(ZdTGfBTPSsNzH!g zuwl!OjtBSPxU%`OsH0{{)0R6YsLm*&8znOZ(z#H&x`9zr+2s!>?Ta)^8Ir3^W>IF@ z4@_f0NubIcpj)lE{G5$clRD<)7s22(b)$)nXJJg&F6cBw7QH4mG&=Lu zgk4XC~v zXC#1BGtn&AnK9)1;v;L4%9o;^%#oqtSgp?K9PQYLNM8T_IrOT|oH)u)dN`OllRxX( z6Ph<$^asXG&^&f@v< zE!%}q^^>ICrpQd0n6;&&-Xz`$Z_FL7UBer4}}wzvxHbt za3Pxf@rtmhlkOaRw|krvcj%~;L%fP^He6B-fN}8;*%|^pM$c)KhWDte<74H5wwztGt$27nV|D&+WlZFG-C$ z9515jC#^6Pw-ZPi?e^zp$b@vgTN$mf{+&Mgp9!X)db+0IbYHzf)7E;hlN^_r%E)uYZ64_j!D-UFnAX8)Fx=lYMcZKM?2p zj)VQN`ArNBB0_4{B8p;F7YXBxg8+FIRuGD(o9EDgFP@KG_0hfP7y*u5TjOsx#@6w4 z4#ZsCvc?wprY0V$p}1}JAG-o@=8$m4C}s$M z1z_E_AGepu6=d$`5pZR{qaKs#H+{O7bfT?AO5z5y5zm>89GwFnZT%zK2HvE}YLz!gZWb2IP` zeqs|8H7ZaK@zI=nGZ|aK14@id(x1cQZ2&By_1_>i5wy?k(WS{a5gg)sIppx4(bs>< z8$lTKcY2*++yAije`0Fpq`m%UUO)N$`hOmum%Z~*Zx7GshV6u8CE5%-9eG*Z?{LI` zq&_x=VB!GShPTEMn9fJt&2VcFlP)-xy7a;S(IseZsy=<9KBK+3uzYpt((?QTbo!ga zW7cH0(b$5P1{?KOw9}Ab=dPMPLk`k!#6c(UlOSmT`F2sWY24-ptb#R=D+YIJciuSN zJbkkMe%RAGaBgww0y)mjO_A^4l=cpN1n1D}P(!eoyDaEH=SgQ zNs^g%zaBEj*w-HQtQJtu>d7xUVL!lLUy8Q2FlbGtuSf)!7T#O9MBYB~$@+5hZd1Y;mns@un&Y7;c`32Fzc1~+^EXPeYZ;n~+8OwE2 z99q@ki^z^iC|CyQj-v0A^J{%+<6B1I8g%J;u4wpG7B-Ki8g}tkOi1AxFrXycd z+=WM1y8WR36O+41`2YI9ZR*z4@qp`IpYjpyD{WK7~_C5}_ zLek?gv^V-m;?Fc%=uk{iRxdkP=>6}5d4Bgkqw{~m+*&FCXfgjkefrcXTmPRqJ#*@N z{{K8aIq^S54+yCB0dT}2Awbg*pB!JaE(2>i!+6Ty94YO6 z3T=Vkfcf=(u7WCJU4(oVwa)oTaB+QoMI`;ZNSA=)Ne`c~X<|r3P|-EM5HH^HWtv|k z>u6k5LNyTelMaHCIdZ$opw`mFT+&kP0U)yN2{CQ9UbyTSV3L1Brus-sQK zaDa^(wgYsi5~{zu!c}STjCrQYJR5`~s}Kr2=P0O@Kmfh3&r8R6nUWH ztY9zGdTApDtn^1Z$50oqNLGwl(P|;S2C#!yWDJguztdAru!Pe|Oa;-|@LRX$+?-3! zh3=@E5(;xQ5#gE8lzZ*Qt|Z%4`>QPQ=A1WGwwek%1V|Qs;9i!Gs<6DwrRbhhRj?xZ zF-H-cXZ1@_#(Tu1zF{dElzm5MUkY`r`Ypidwe%k2>y%eE)nzIbq=>@Vnmi!y{*^H- zs&>C(jhG$Rn#i!KZGUf$W0_H)9SZuZjUOGG$?XDD+UQ}hl_a4?t?K7Tt_D2y=)#mL z3l#gqpVof3Q~O~X)-v@-1Gq=7aeDzO%y_vp1G$>^ua-)zIH+W9dcpvx7KErE-=%05 zXxWGcT?*{1kR)(H!Pqm{&iu`0J>S72W{SovIUB^*`owLvg+(R{>H}_h0 zaK?b8E9~?{+EWum{%#}M#V~WS^`V8Eu(DxKy0hei93l=d|Z z;S~o7<8$E{j%f}yj`bW?32Cn{iOO`@12;Z`gA5B9$}_-Oyh{zKZ?s5{h~s_5^WR}NHK zTxK^dxHyu8Z6VZZm|zyU*w$}^6kiNwFts#9@_>W(m?*}F6fVZbvbJsU{T~r7%bFp6 zx#WUi%#%a}bn2`(Mbkn3!7o+ZbDYFG!4p^2`(Wgiz=${8u-VNyiiHk4hA}g)+0lcb zOL|p;2lWKEFbc`&@fC21GP$U@h!e9oUoO0&4$jrI6dS=NM$r#?z;HzhFHr`K4E>No z!&k0fH*bzZc|s9YagKDe;@VcPU%yLL#BFDN;d7&7Wk5(&s9l<`w25V|Zq*ChgmqBxA{KJY4Og?hua{>aPVtq7M1K_shO%#pc>s{`>#UbSbOv)J7$trrcRs|XFDP9W9MT`TupPUg zCb(E_u}&|&q5%-Y4fe;f2vAU zOlqT@pj&p5g43>R;d1io@!pjn`RFYoGce+OW>|@+n8!#2Lsj)D9(((aY>>#cJJ_MD z7*zq>l8^*-afNG)-Wx^<6?xk&*_-R0_&+TP$*`b5n7iYK=q$@ADO>5$kEl7$3Eg9Js4tEp@eVl5E8<`)-xEt>H33eNq;B-V*ZbEBHbHnM59 zbJpr*cED=T=``I?yRBJy(tvI5zhaH3lxkaXu!R<|GMjI@n~t(^kBOcqni+i1=`>?Z zJu6h>D&nBVb+Z}PRB9k(e`*^<6`q->=$Edv>-^xFtdh>P63j9}qK)Eo>Ljr#K7}1m z20PUGX1g!@4_`AwNaa0v%f!(pVS_s|QP2v~pZRc7!=JV_gs~Q71!JmTt38PZiYhAYUDvo0=47X!va>Y{ilM zncXf$GjaphTP;2yZ~)!(*+12~xESA#WHK>7Bf%J*Ay@}*~w2kZisG0r9ZtIyA6?LYg9WzMY zAcJ%*rhT$>pUh^ejYo%N3V}-MY`RZ=Rd;R089&y7&|7gY3tK*RZ=U!mR!|BzQB|%x&qF$7!JMBvL>abaDR=eH+I)NB+hU9oDlAalAcP$I>BRPMt6DNUz0}>>M zOAzGi-orSbUkBtDkpC!E)!j4Q^B}3+jdKTBfF!aX)z#J2)z#hARbGc~PL1og$rNVJ zZfjx1LalWr~z>bhy zG7X=ox6>g3Xt~4-AHFF>tYybdC=4wIA-WWxFt&x1!m<8p71gYwWd4F~nj(b9f(uVE zlC6ubTPhP|oKbPuC&K#XG)h3Tn^U=;)9|IR04ersS=~vRs1*w76ySj-GwI-uoHlN{ zv}Y3~Oy%CY$yymT5^fa$qCWKM>~YudFcgZNv$$Ox+T*^Hgv--8WNagke+nTYWU`FT z9iBq-ylkHT(=X~MsWgA6LyC~^Ieai81VExFZut5X(7q{c{RDx+$lh_=zAGCPBQzQj zse!BJZi>1WTKb6^&zd~oBzlv-f!JNBxLz9EIwMNq##21dL{T*iywpy_2o()7?0hdG z1UX<_ZzB}c`eJ&GfU$AK?FQC(oxJO}?CbGx>_# z=X-g<+$Y+ae_GId!cYED+VV}l10Y|r$#1700F-a1pBtgi@RO9){Zt3@uK?orlP{+q zF!=W!RR-N()?bzj=JM$!`=39sWQGbG3UM(JwJz3X;cU~Ge77?Bz3$w8@&#beWiBfksw4r6H zrt^Jvdum}pwwdJ<_{C>HHkv7wWAkHHT`I5MyUg#Fe!;RpdG!J%y|@8sDrdxlG4T19 z!Bc2g+QzecK3u$pi8Yp)Yd4lJ1nl zb%0g)z(aY=Om-C-UI3Y{=cXWob*atW^yOswnP_zf)|BC(%TdRp6iDYOnFvV0zXTci zyXgnhj{)@8ljjM5k%+jwzBtr>0~9|2$lpx9PZh%PX7m|w1@!*~7Zi~7zgValZLVCQ zm0v>(A5K36;0cg&f?NTbvaU}*g>@Z3|NZ1!SRcQcei{d_Uo2jNIKGgd=QNm}DCUon zQ4-q?tt8H(MktH*LnKUY#Kn+Y@nX?o2>WE=ENw-C2Nq6mfe!QI6(SaGD4%1&Y%~T- z0#E1>XzQ2IW$;Jip(G&vxMKz!$v6%5x;o?LS?7Yr0$0vj(ac-zg7%0GR&w=`M<{}q zaV4iUa@a@iY#3Vx2@cdqKb`yrw9@A==f0bMJpBjMF+n;_Kc)|q`swuV7%=zuC|(#> z%u2K@&^oX@Tl~+G_{=#Q!IHO&LVG?s*%~3oE~%`4Ir(FWmH}njXy*qV!!UNP*~2!5 zNhLoLySW|w2TX!dQM|E%39!)YLbaVUK%HK|`c5DN#YY)n70VLWl| zpx3Y+e4%907rY2}1J~-Cz<7P9QQ=?$Hvkm(9-%`>Hu-nx_9xSS!ahUy7*XXn(B+S4 zC&BLT-vjzn4Xo=2&-f?C>gW)JUC4t;81$hB4fPxh6W_-t7~3MtG+W&|Tk6z08=Y2^ zh+~7v^b;kc=W9r|Th6NEM9K7qku8-wm5r(uC7+E04Dz^C+gMv$--^<$b{pfjVTFzL zbphv+Un#u~ShLpfsp-4#xq~hStw*~|6Oy)Nnby#L7Ih9b5nXpsXJy$yXNbTnwb9d+ zN~yNSjAN%eblAf~+(F(Q219mi@gQjM4}HhDVGoH=b#`ufdETpn%)vLaT~e==+Xl+sLz@tj>w8rbUtbBE=xUpHRR@RH9@+Rzl0}omrSst2lS2wRKQe`$s zdORawR@RD@$|n4li=|3A4z#8M<=ts@3mT~8YdV1iN=XF>h2F@%JE8~~MzEUmdV*w}uDg7bF zUr8yxSxAfzkc(Bc;nBu)Yimp9$zBD+VZUX0@yRY|wH|@eDJBv*-INC7)Dy`TNW_6j zR`7L(qRH{n(LgmT!evIA&el+W16Qdlb@9c4^R?|dW+JM$( zP0w(Qw_;<=g|Rz;Ddh&~J&gB=+t<_?#Y&W9;gHNH4m~-9Xwv4OCRtaQ9WLFbfOL@JH9H{(-5VnkX~s?1Fpv#U+nS|MX*s#u02 zjfvVEgnq(~oR81e`b_+36P_e%t3PZ%j&WHM&y7g5D8g7}Izh?S5E$!>y?*zaTCLq|RUY5!==WJF_X#X~^FY2P9;EAhbNU}<)x+XK80R7FD z=U9&c5dK5I6}FCs&IswuqcaHC1F+r_Eh;~S2~un7NhY}d3~)@ zTc59ev!t|`wcf^v@zN^Oyy-Wy;VWnsNuFC&p~&I`j=@42!2&D%PtP3NwOC%+pKq06 z#0wGZa1XC(WuY|Ix3Y01L@5F(LX$Mr-QPc6@IL@gXc+ff)-us7>J6&57TzUnkKW5q#_G4dzIaV&gr0> ztfDl7hX%&(u0i_os;(x9uB z$?u>AGF8BP-joWOMGjW_=jkVC;DFaxsTG6`4Tt|VzncQGf5vVdznxJ<+N@w57^2D7 z(+{SfQk|b2AKg_dWp-KYD%CF282}7hZ)i7+t`iuH(*@d73{EbUDo}}br3{alfqh6( zHpF4Eu=C;WVjA4aXu+yvULz~&m&yqyD$GELENBEw{#n9Q4ZyVOV$06!`{UM`8zg8f zkL`C>cxi=(E7j@rqhVhw^~lWa2dayQ;Tp{I*D63 zG9RJpMd8-R9dL`G!ez1(P@Bt(bm@Wh6zNe7d#ftWK+OLj8S)KG)?eVY2+Ec8JC5n+ zlW#%1R702~a}{a_^7bN1@zk$WWXAP0WF|}hCl`1iL7q=PXHW@^gT4YGmQ_Ksgj%Fb zk%3tIa`JUX+4Y05l_4c$&4mn!)a zPKLmXn2~C#CE;tt6R(IrzredVqW@P{;!b6PtIK}c1XnK;=gu^d4$_7?4sGIHo?)Pq zFlvw8&ZqldmyW=k4zq{Nw+KsP#Nh7To|Hr-SZW(0nI}R;Uv92*oZ80NZT!LHF;wejtO3$5Mp_sh;S++cz$g zuLXJV6nyy)i^vF;dnaEj*OHGQsT>wV4s|@$eU|*;H>_GRc9FZ^rq0qR3&-b0>R7_D z26j{ycYU-#&lBB$Xw1nAH~HMg=7E}C%OHo_og^m0mrX}_+fExm#m^-lw3H6XfO{bsI1aiQv7I1&Q0)^&4j=OM9!T{ylosD10FjdR3x71M? zK1O-t7T>s=7%mxjnHpWR&K+ma4SK?A7oX&iyP!f!^=z)7>?g>6u;p@tG?znyX)*)? zyEelFxjv6V2}YiSZX6dt#5(h(sA<0I=LKqH%8!qroDs@~!DI1+r$MeM6}KH{_+Z>1 zR*m%#0E@?5T$L@d6JGALa8f?yUNM3F^garBm=UHE=Pm}2)Z|o%loMtMFVdPmthPWZ zG{9HgOj9e0MGN{=*3ja%=qJJ|or)`^g`4I?9g5s^f{#h`8p^{D1=G00Y4U&^`I`XW%;gB9j46oge6VA7IMI~ znD?z@shGuC+Sqb=CuC}N0ebWnz4MVjVkv~t$9X`qC0{6s7a~~#{3RBK%$4lVf3c90 zFh#ZrDqj8V8oi+5e&Odlq@h=Ca$tTvjfW%WN^{HW|R|+G1(+@Rfd02=y1$@a}cPP)7qm8NH zWQs{+kIzPa9texBjr_6aG%dgFx?J@@`$+Up*{Ei7qTJAWyl%Kn8Aqr zsp=W1($Xtu6d^31R{|6jnG%$!P-M3YJmMIx|IRrH>|726K$AR_H=872noWelia&rtSU}IHD zITU`cj-56J6VII-^Gc^Vlr0V4MU^Xez@vkmft+{~Lu9QuK|2=NEo5H|C6+sQ>a?km zMi}`}L_K7bK!@>^oD6oEyYJAcQ)4(V(P0ZnRGEZWuH_!$s}HU9+*{soJc^}3`)D#} zA5pBi#ydk;2(5mYeBgOQFG_vp_lMm>awQDvyw`}~1(Z4Dv-p%q?$|eGKlve`F1u{vt7;=gj}w#zrMR|0}EIALsuM@sp27FFprqaP(^MFyzd}vTQrC+BjPA*i!!JWKlCCu5Y)LhvLD)Wm5m-Q-M~lp zvJD9j_CGHG4r--BL&fMS;L>0?PhB5laU&Im?eUrLi+<2|@Z-&Ahc@~XF*!uCM)M6K z4~E3KCTkbOm~10)Wn#%4fEIrH!C&2B4H~!Q`}_A8-oEoIsybU{Z}5!pY8X;NqQAkG z4Mrvb4TD>TEOpxUYRxtn7fua>AB`Ei*a%)5v-dM#3c{y=+_3G{PCE%w++Jrwl*CT~ zxw#Jflmb#-Zl}UrGVqP9LhE4I?e?7n1-3d?r)A)+AX*8Cn?P%Kd0X-|30QSF+nxiH zM~rCRu-$V$f?N`lr#7WoN;RTh8@8GDR%J5;724hkvjCC&9vZnjPwN$0zhtVkLPGmZ zJRd#fmoLUP4pKBq@u@`Db%He{2(r0TjqQZ3UEIh{x3QBJN@%mjt&~(^il<|e`#PO% zdN{CNF0;xiJ>;q?&$W!*h_Uh3Zy^I5ET~mW?H8Juk>yvigKDdpf+RK>W7BGDwY}D{ zx8qi|G$=9q37T#5r*FGI^pmp}=$FyPFX`Glr48}gSeiV@hTV>b_l>^SPU|e*1viWZ z$RA=Trb>C6&rmY(7Bf)JyX!FRYB=q-CRi;xUL2~eezU=$Q&WXl&V^kEw`9IvVo8Zu3p;KF*b zMN|m(a24g>3jziPG&n$1?!YRF$5?rz-FKl+#fVO!enP#X8x6#h?u?Gv!6{Av4Zg-$y+aDp6GHI#WvT-l%fLGhmfm2llo;Yzo?r3Ne?%z_+gF*iD!@ zegkj9qLA|Ndwh6wS4-PJdUW^B(cV7Bo7XaK9^T!Tfp4Q!+~8;lj}C4=JVXl$9qQhL zy+q=jqqh=i58=O-@YemKhxc-YT<+%4(Szd$_xJ8$9iTBX*-8uk_yHR1(1Ws@xi>BWV7S=;f`KZ4!3%H_YRxK_irTt zB+o_Sm7tr4w+Um1-1{ji%<7S>&6nml}N zN<9)|)BtQ-)NGUsDpXe8y-^84^F?GB!?3bBMW9AqG_ZhBrqtAgIu-VTK|XqjMSIQ;?&wgETVA_|ajcVy zm6%S{MPtTvThYm9p1*dH(6%m(T*xj8+!tr#bRcJ}ys@@+HApJ$G`O*T8M*#|nSN(5 zyHtjhixmsU6rqKjWUw1IICCpW%6Nt(Ochj&ojv|v_O5Klk?TDBx}T!Z3ScQ1cAtHj zi^OBi*cLRB1~al1ByBX#+CvXFlk8a%8pwv+mE%Dg>k2fE_M4or21uJ?uGlQSFiEg+RRny;q-;% zzQtRuUcWVP7jPi0RykO#KGptmw6*@sd(9QSsO@Uvu%z6pM!c^0?)R?^lv>~86=2@j zbCJ-tYHOvpK4U@4n4&D zsqnV)+e)u$?!oH6((xt}Sjf+J=>1a3>&m{I$#3jtZ`EE~V}sVL84lP)z`okMru-hA zAEK*Q%C)_P1+Cg%aBZ=h>e02XmK3Orn4A}c{b%ov(yxK%8F}w&fTYQhMo-FQ)BPVHq`Wxvwz zl3IUNJ^JLyr!_JHrutd+!^KIPcdOMYn?IbDY?Zl_ttQR*(Q033&CA|>pLcZ}a`D;c z+0DH|3-IO4sH>)MRSq?&mMJ(Y)2!z;KU=LahksWY(w%DbtUVZ%Ei_k}TCtt0f!Obx zr;9z^n4|TW@K%176U55aCskdv;pRf$%e?$WU1$db7i)ukf9N+GZ?Ao>1K@UClA?Zo%wfZcy302IVt}H{GEcw z_s-7BL1*}Vg7)v$8%})w`hQ=4gMYvH_P6ym6`xP)$vfYC`%mBg!|N}rP36A*?PT>p zhzfuH@AcQ;{xe$nA5{PQ*T4VvU+}r?gz@!s*Pt)zpQqacO@I7i^7=ofn_i%qZ_vcL zLJKYYcYQR_=T}eNzUcLb>AY;NSq|?THYX6TN}-xgS}N>$hgxx;^W$o*WY{jr6nbBO z32MIrPre4-|9UGy!QYfExhCbn)Xrqkf02fR-gwf#9GBx>CoT-}D`(9IB_^FVKyW{2 z!1>8^P2{9ZXG{JPDW)PiplutWm(ba+hFtG_WQW~3{6d5 zJayv{FzMuZ+k+#qJqqCP0v|teWs}x!e>9#nlbyE~wFd1Bq$FC*`hWHEZ9EHKe^UP7VG!xt7Nt0)eCS`W!u-_N@p9rI(qT>8%TfY8cS|t{aX4;uU9aS^3oiqXO@{bJzj|K2f+S#*nfs< zqjp&vJc%#h(w~R=bD7T_htKQJW%fup{$xq2XJ^erl#fs9K`_LyTbUTq_su5B*;S_A zYu+e*R^BMxn2}4Z6kSDn@^1Z-3T*lH@)KYG?Ql)4pY7a61kiEW54V8g znPDv#g-0vnkmtU5#&Ur7&KhXEOy9fc!(s03peg$5d^}8PxpZClm+L&&@zfGlnef@t z-Q7F@z+NH{9|yz!U{rgRt^MDB_-M8BgEBALl!+KV)JjtPg-Ccd7 z*!Jl85YGRx`t^OUz5M@2_dfpMCl8){dNO`_6Cb#1{f8zi>%XaHQoi;7Z{o^4#zd2f zRB~pB;JPeJ)rngflgn0y%oFieYh|NgG8?CC7E-gAm}7bV;&{6TJ$`WS{zng5AAJ1e z(|Zpewm$rz)}79prT zdw0E2+jmk!#Ys0F3Cg_JN_n4&wlr{QdFCcMr!|c-66YjHqnSk>ETJTaD5Now9P!jSiw|C5QGSXv%m`5 zIJfaYBI>#O_3DOG)IrWr@8MQcVx_VwFu|SYoMxhu)=m%y0B}H$zfB}Tc%_tyxTUmA zU)MZ-PRDlq3^M>)t_6SYK*jLrz{wa6|=mSC*V4n-1$j>5I+kR1i<5?Y4|(a0lr zU=QQaZUy<>oSK;)FH)nau`pU9ePohT9w8jqE_&CVM4}Y6HYu05a8V1N7_rh(Whwk& zmWdxo&1`LEnZ|OCkKtKOMTU(^4M&>1H6bzKD0~ZpE#W)YK`}uo^coHyE*K6_7|R$4 zCUAq0?!i=)C8$7db5^u_?IYtP*vohz8T^k?WbZkXu$ZKs7t%W@H5w#RMTc_U83k_x zb0UUndZ1NkuHVsNnENT?TJ2L75XCE|gBUPF~^ zLgZl?UKvq<5)a-$NFw96?}DnSO|2mgf^fp3*JO-^5hpkxA)zal+sqjvlM7amXe^=t z1HdBELWqbHN=F3q1Bn~E(X-J4iEA0=@#Hz#RQ$3PLRzPYVIa$Kqh0U@?1WWWILd_J zDhNf0fMjiQ3Xx-PF<$LAv~3QvPcR!G6sEP+#Bm*I5K1TbMhm6ZKrhl#fCvOH;R2%^ z#o&OXf?`3#4yR@?=)5|gPOcwbo(~F9C!?oF(Hr;=m~Wz@1cOMS=?LZULRuuQoia(5 z7!e8a#w$3&(z0;cZ zx!ECHYVN(KDH0A%$%wdGM?}+`5HY-LO_}hk(Ggr33_570VBU@?sR0#&UCL~LhYg>8BiaoL}tA73;gd%G6+Z^0wn;_bIu7Y7C8u% zxTGXEBI89Xbdu{`4vJ>RBJKp(PAPpbQ98wDpSK(a>0mMN{aO2VgI#~mZ$IGPxARRCZ(5AhC z=iIP}z_<)eW5^vSIG&U8CMe;N^8kPdup%S3AYAGajEMr8%E?1^ngDIXTy90kY|FSK z&J^<;!9EB$N&)*&Z~{>Pp&<*@F+{i#6B*3IMhY8s@SFhoiJRi+54VtY8&@2;1QsNK zLdcv8tDqO?kqNCcAc27?Xe+poOiO2#wk}W>0MP&`@i`psvbIm3I^rfrYjOfaQrROF zljzq)3Y?7irX&F+z}JS^gbWxtD{>&fG%G9;0~QXCBU^^^k78up6i4a{OpU-}j9QAe ztSB){3D{7`P+5>bM3iu@ULxlJf|LdrZck#GRdb==%k7I%i&}O%d=} zn~U9c?_`Qc%9v$SWlAgB2#0^Jk&iY)0hL$QQ7z%aGo%22s*&?Ww{L#e z+&^7MG-Vo@fFX+?q zWLSYW$R>~rihU)$CqZ3$L^(m~A1K#ZM+ImD(+uz-@L4ANY>}c1d>6=fZ9t+0j3_8P zKC&=r7~2?%BJ^<3Ejp+32{-Yp13(Idlw?*g$pBdY@9DtUr0-W(YN zkcKnBL&$eClfY>PVng^v81jyICxWC0(lphbhNN1bMl~7b;o;_kh2BUlWdhU-*}$(z z50{0MD`{mM2|Rn96QQo0sTs9bNXJCoE?pU(v4C6_;5c(I#suRG z;w~44sRSZn{m>VljTBmdNQ;a@WRkKtm=T=;xDe~p-9gJm&viyC8sbEBK!HL6^hf}* z1ZH@`sEWV>K?-+8uGq)WRaplztav^!veK?0T$OxqhVpS z$74szBV!ssL+}hF9D-LwBX1y12B3H#S>hu?Go**KHylVt>Xzdy`#R*q!*sC1mD6ChP}0c!_jEuxBCLs87)uUFs2?9r8P{U zY&7_)rUH2)ES4dXCgctx0j6f83s8|V%7@I=N^k?QE>P_>Sc4n`z({x~S0sDq4l^pc z?TfO#erq8fl(TfE_FD`zB9rtiAeVBfq%8yv9^E0>BXgi0(cjZDgc1yl8ab#h18*F_ z*6V$T(`K|ZJ5!|ARV=E>(3}!$6kxdl#~nfpHyYV<^v^`p?cgWWLBQ1yq z=r==h#SgV%&I4^oHAP0Su11+fX+w~l+~#uDc-OWW?&cqM22U`4cP{gd;*1q zH35lmsCIV7@qP2EIXN@%+#CcjT<=;(u9=v>A$x_i{BX1e+3)w@$$t)|<@kM2}+?W{i+m{fK9 z5mr>b)=>1cv8K^`XlXY5h?Zu(x?ZgAeO5J_nv|UyeZT(<&CTkSgDaXr<~vnAZdtc9 z&uY+CQ@PRZG`v$y$409DFg#6V5gTgXsUH8ls>iRYvNW|WRZg>-j;h_6Z6;mKvZT_> zc2td?i@BB!ubQVhEjuMoOIM?w8;qXz$7ty2NxiLI;pM}!gt!_E`@aI)=4C23uKZ=Y z6RSEs{N5%hd)Ry zKqys&{h)gIJ<+pt{fW!}&5hCgp`Y~aweETo^53=oldR7F;&2IX&;NNNS2JrA;w`zw z@!e{^Jac~X+P(45>vPwt<07IK5iO=aCG;*8cNsmg)Ci``U(aTgPIaXeUp~pLxn@VG zRmT*{GyR{hKV0#+?EG1ZN=3-}5+i`QdSdHzlZ4~dLbIv-bxOO5rsKv29z())Q}5qB zJ9{+jmsNCCIa6qKc6RTxnU0^Gm96B~k#k(O4oT};x$nkqv5ocBJN=8%KDE!zO3C$Y z%+dFg8jtU8YM8#*k5HwX8&ZOCVRKXuCg2pY4y@ijvQ)APaC zwL2aE+OOe-@v_YgcHCtzJl&TO)6%VGG0bSo^Ebm$`!+l)-|F=HgSy$3R%cc?=lw?S z(6Ff-`Bk6aT8w}F!KdxpQ_;&zBUDu5S%-p5DB_^7zM(9>Q7T&d&J1p8bdM zfBpaed-m`D#{S3t9sec%``^aD9{;!h>z9Ae{`h44U;lrf@*n^8>_5VbK#pk9)&rEk zTj8#4oa`k*W>_3aH{b%9I{_{4Tx90Zp zO?UL_@jh>3{LAb=@7_488SN5MP`_GmA=R0Qq@k>Wl_1t^*g>RLM&O}#H z&)G(a{*Ql~@n3Jf_uk^w{jaz2TyAs@e;Xy^zi{KdTTcG38&~%~-pcb4myfVMuj4h{ zXgR8t20f`^C0^ktTx3JN1;|gyzy0vZ+8ucH!TjF!{nq}ixV`_uetcv9gT45JG@Xy% zPu^>%5PU677kQ19(eSW0fI1mdr{{0Xf3Wbut@rsi)^tDVo`9r<0vm(u1=G6uw>v4n zdn|C^SVkL=dk0f%MEgt*!`D25oUNvimUoV|$znEAff?{7vI@??Thnk!zi&1LMefO_ zwt9D8!uFMY{iv?QdhOlVX#U-rUSeu1Ia?ft@d{j_%ES$2`u$;VaFleiU!*1sSi*8z z?uY5nEV02?-6w+%E%rD)V*QFAqz6gs#3+BaJA`oWyhjaA|D$#%9h$x3Uw)DdGFbTO zx7xd6L_1lMr@mTZ06_zUw|b4<*mU2)vy~j=Sl{H$+H6!4LkNG~QKJaIq%Krz7@#)z zP3tFv3{VBLLB2FMcaRMaM|;gy?`V#)-<(!YYYGZqr%(SRoM$gL7?Qt@LixXN^VY2e zPyWZN{2y=S`MXCc!Q9y# z_WHG{DJfqgp9B;7_Y7_v80)Zl+iaIJ8nC~cOboCsML+3CURo2Sc9)iz7+W0;SX)CO zGiLg-bTJI|f&uBK$92^-2b1kb^%(LOYk1AIe69Pq*GcOIU~iAI&X5;lfQ>#j8f2F- zJRJ0nqnb`6i5Vk58Gjy~{lD2iU!Q#vL2^mRUdO(F0x2JVOING2UqzT}@>9%f_lNNp z5&OqZeD^6rV*iR&31pEThllC0xdPnD259>@5qS0!w*A)2()I@YPT3Z;zRs4s1WPVQ zGq8_q?CN5yyKGh5odFY;SR71}*0&hL#PB(aA3%1-q=P@OhCwAjac!6$MX@dr;TuSo z9chH)d%XeVZjT;~hVet5$=dEO-_Ki%9z%7wh3QR(s^2r(1PA*=i<&0l2wX@fPL9(=@%swKopbJDS`z_!4o{7z&4fGI<>?Wp{9Wl;l|}cUR6t`Bt30r`(Zk4 zis`WdXCCW3XC_-k#bHfB)zx7vgFrAMNjFFIk29_Q5D?Ghl20f5`r-H=6KIb91!EN4!2i8@=BU zOK!rsRS3)HLE94&&pji)*6=h{*)j#*pzre+awW>y&B0W>KF>_*eFE;loJ%ih=)SMg9KTHDgqj z`Xe7;gOJ){VcGE5&xPfH^KDUO&7MNNFoiW^;O?+(OmS*Mfb{HQ3Z z-<+S1-c@7YaH_V7>3d42>O0ZBq|@1B0ulKRHQu9)LGBK1GG=+sBr_|bY-oBD(^S&t z9BlR`*-IM2y}hSYH>W=KI1sL{KweTzikns&6g6ovHsKyEYIjQST-YOB>CEr}_% z$}>|$K#Md=`N+d4wnbgG2aWazy`zXR%8RTwV!sl`CT(J+&iNFxln7(CP!%mEXcbKo zqDOqp7Wh=E5wD_|r%k>Zce3YfmnpE0->pTbq!OElcN3-p%4&wp!G)+`dOsy={vD3n~m8c53Uo2dmvVu8Hgi$X8PZ1xVE?u^YJQy4!8nYdbaaV+mNWkS+8GXmlu{--%#<#)t#aYQc0ejQ*#^=FOLp zphg7gccWo1I%G20ly%pbUeZ5_a;BqqPMXvRfB?Y=0DAGQ8@u)Gb>6?#H0!_y)u8pe z5o@rawH`oLRMa>(?+wzFD_Ii0tz_}NPaQWuZ0GYe+HJX+^{?6H=W8(9em4Gk{2TVa ze}Wg|e`GJme;9wxOX3bGVq4!OCc4vfnQefhy^|pXE0#@uf0^xw`Afbh*7(7Y&PEo( zvjkhex>mONi7CI8XuOoAaU}X=TO2lj@Tp|%Tiw}FG>3`gHH*QQ7g0H!NU+R0PPYFud)zX=T@DPrHB9`iUT5R09`;uWkg!7U0q<=YE0h`p)x^Q)Ofk>K&64toKZ} zgYGmLIq!ByM=2ij)eViU`~(O=HSyG?;a_gJsg2v#*sAjR_^Sw(n6tkd{}$Jt&&OY$ z{q6Xxv%eYtE`qba@gLdeFWHN~W1s$juh0GhD}QPFwJb$5b))@hXOjQj-oO#Z9lViXaF}r7M(qGZg4kvVO-Nh~0QVadw{iR15-F-BbiU0Pbp;b800;NwF!_?v8wryPhg=4Ka~EA2Kfh#FbF zVG93%fkt6333rYY6k5H&q6M-j?TUk>Z9~nxE5Fpu(-l(XMu$a zD6ECGBk&Q5SMwJZx<(5O-)^gi`?n`s_ue-k=`n=41w#m+eWRdDvf&sVb)j$#$O5}F z__xdCA^g3W9Idoky-|1gP`r82JIK0DCyZ{hR+}4}oHv`|td(u)#r_%0ad6Wn5^aY- zA#GmZk?pdc6aZUP4A!d1qc6OI$w9$lSghscSo_&yG=3f|;q!I&y*(|Q74TT90z9am zFgk5FP24r-A7{hEdhMNBkn%Rg zp|0CZF@8 zJ8TzK!>tmB!FEJX1O0}_!wE=*P#xNhO%Oiv(CkS!`}ruf&&&uhbw88IAw@ z`1cIlKMUWYm@mcSF8~(*B}IJG&~(I#|52Tw-zZig>T7Uj2koE&v-z2OxL zJ-(iZt1)-d{j8gD@8?SxIi=1SL&t#ZO!(-mtVMPMhb~v89#ftMsbD$; zvnXYs_gGUc3 zu4!oW<`tcpVy-r7u0vsGQ7B$wh-mUH4S^HQ0%WMHCLSmeXmsDV{KMZ0dblvs1w|}~ zKdV34y!-g!%KF{c<9`_cm+^lZ|DWUkmHqo4#{bLse;@xx_}$pauXz>FN@VGS4q{Ry zigWP8h-v`X#S&}C6^>2wg>$!!LV<7wLf)R=osj&&%~cg7gdpZQYSk93QCy~ODkEw4 z#nS4d^^GT6e5ht4fzx@miU178{LZ8Gm9>Z7epSIogYaYHYKTZNa>(OIHf+xnY-s9m z78)j*Hu;J6WoieGNdOifLy*!%sGMy$RD-~Bnbnu8rQ+OTcw#Qnwf0*R(PXG;^PEcs z_5!_5n(RN}vx(|beD5K@DVehUvz?vYYdbsj=C#I*Avz2|>u4j(w-?BegwT=o8kB0G z(&f`ffwO${&pmY}K*g9j79x&$PyrMxA#K3XHcf6dw*Io3J>4Zo8ds>jsv+lDIyuqS zZA?xTECW1NhL@rN?#@^dICvt<^Lt|4^@kC0z$(vY^mt$GWNdSPIT(No&LwwBh{LLV zy8B}IlHZXlE9eP3|0Pd4nQUN9}9=EVO0kco1MhP4!8~5b( z4?kMDH7`m)7N@?JG*pwP1X_xPVkjRoi5?#&C&}I@Pbe&7ewD#1h%6-Gk-1gY*W#?> z0^s0$kO+j7PSMJ&;lrgrAqZX->yl$x!#wL1ZPvN0Jq^hzatLhU8e@SEO-a%WzsM3; zhlHIocoQyA%}^df{Rp>dbip(E9UrGq6T!vMYQrZPDvU(Y!f3S+kQMeg_}9*oQSeu) zGzOveOeV5rl0$!vdvG}~!#`NDS|1A9HT&U{hn0EB)hulAu6V36n_VTt57&@!)N=!g z@2C*oqg4D}e4gq6DmYXP^~q?i>9}Z_g5Mbc%itVp#7FLGjo4J9wD*#Y7pOuva~RJ} zI1a`~xxR-B&PzIzqbfE-w`~RXYimBr*W}<%&OlWqu&$vak$)LeRo$gTwdO7#s+MPU zkhMGuf~w}1`dG~`<#i7&X-kM=%UE5jk37@$$n=QFv+SEWsuu1c+)7Yu;3;kh zFK;81$E;SIT6(xh`LbXD>Jq^!#`C=#tH36Dx8LvO=eU%C_un{x4K|z?*>(erO$-YA z4c}7nZC)n?@j%3bJwF6%5?s2GrjmK)QVlbgZIY>AjHzgbIX`BPWxUz*gV<1EAkI1a zHgwR{^FkoeSkDPU25ee0qhS(gd%a#~cVL_$5UZXVT0-$UD>S@nAuC%2klgU~aSBdSY+l?+PC(6FxZyo^fugI2Nw>?? zB_77hL!^UVcRd+APY2-+Oez6Mfa8lzkjF+$cZWv=jC#867hk*r};ix;}vJ;=J0L(r%nl&ZYAwZ_WntG;jWP-{1D%`b$y0bx;~ z7Z@I;pc5P*Ur%MQw3@%|$LT&(#Si6Wu;u7FjoEH54+#T#z5zA$XX%fEQDDPiQi=nj zZfcor+yZz=Y;TMXrg> zi^j*C=@Jq1En;95ENL1JzPfqV%6HIe$}OmY=81ezjjMNP&M;m{kAj1t=`;6{SyP{kmA}X1SNrWszt7kZN80}632&sXwHv{WO6zUG)%h;0Vi@`*k8v3h zYKVvdb3ur3t7gblBt|Jz_I*$lP}pxMeP8bClvgbCVL zb+GyJ!wo3TjnEE$y7|i6brGo=wqOhDrI|BB-rR(1V~yrk?@2d{;)KdkA0IBIs9%vmIpez;a~djRF|-lN5lJ$taM^ zmBrY(sUjMwJ{ua`UY8<8a(VFb&UGc#xZ;vXl+*`)w46-K5vg)=V`da^mPY+w>Vmmi^{nqvF2D?6d4WD=;bSH~TDyjsZ+$ZOhNHYkM~; zsI(nwoYcn3LpoJZk_8q0u!`g~(%6Xe)#*lc+-)1?o6~l7m zcM%48ISM*G2B(PLiW$X@?d_MbA9Hxi+=H3BGM9sGec96`k!Yj5Ki+&Be2#lQcv`YD zZ4FvdXltV(Mb(->bT2CByRk!c zT2TsSgxgZTM(Xp?H`0bw;S-%VL@WW;Zr3PIvxdN~!$}%q2b1Rg=i;KuB4-##sI0MU zCq^}yH$?unBfYEaAwKMwCRGJ7H-m`izmY(sRx%|JIiC|8Jbj{4PCVb1Bj4WJsBr{a zcLNcGPsH-LKUa0^@WjPlajKdiBu4|1!3FR-Nzo zWa8MZb&D^QC`=xj#(FSFKa;yl^Ma|g=-}E=ccZIuSf!!Wtozc>W}Y@xb9e!mG!GYy z1Mzr0*J{*QJsx#2`%FpKy+;`Z1b8-+=B=ckwpZzn^Kp8RzJvgj_^nRCHbk{P;TRm~ z>XE73Fb{8QOM{@Cg)}tlU)o>Q7`mJOr+hG&|KR?PyPHcPKuh zqtM`|4US_BU)b1Q|9L>tX*UjMZmDSnfh3(cU(e70yLEScBdSkZsaMrr@YVcj6KZ2P zI0H05E{K|@8!ZkZ$;*k9N(Dk%bs;3EO`tMj+D0bh^zG`Fn*GciKIn&82M>LOi>o1t z*_^aW7KSJEU6vHX|IEC`%=yFpD-`8YPPOC&(m+H;J}TBf@z*k}Z)*^>)V_zdWHWA>z^y-D(L&~s5eT<< z1j3(s!b#Xne(E2M7EiS#N5L8soR*By3;c6=*G(`LIvlF-hPo}6X)WK?95usfWB!Si zW0+?fVC9)))ALO1TXZW=23hH5$E~FVuUcuq|AL?HM7{uMatna};pO~;t@cR;FyxJ8 zEL!K-%&N@_#%PhkgQ<@oJ|MEOK3Jc_lhy`lmWir2T;5O^g&>9*>eQX0cX z^D#!(Ft`l2-WpnSE#DlqfO}xA9x+|19cH)Cd4VdROpY!K1px7a`*1GbG09B2cqe1X z=4oA?!JFBuAVPJ_O{Qf^KQHCE91ojYY|GjaSv6t*XxlQ>hVjSU-f@?g1NRJmS^3B5 zNnN+=xjr8jD`Fb4F&wU^!^0kv!Q$T^rXr=w^s^b={z^Z)4L9NExcNMv)9p^%X|Xto zJ&14XO1o-W`BAnth!I}X80zX8mx(QqPhH$l6&lw<4AJ7nvXMr z=OMj1m<>{3W>}316^Mkeoc2P;pn6U!mz-tCXxl^|pk{9P=scS_!g&ZUa<4v98Wh$T z&q8YPE&YQ>t1AyyHa708Y^@lyF>v9*Mr?BctxIhlJG>^FqSQ&RDprallc1wXU2_^Y zNP8z6pe$6|X%!6RnHdemXs|p1RpAK=0qx!7TUj(ICe@RO>V?5FZ}bPfJvZkaZKQUL zha_+;&>SB1&ExEXow$Z9w&Jzk^BUQOuWYWatr@_AxhxD|lcv|Q2UqpXC}l=)t!OkM zvQf2BCKz+qqOcq?HN<)cZ!es(MNExn4xR39eHul!k)@D-6;?PI6NrZAqg&b>L*(d&G zC7iG9yHrb>_`OzWhJ(N$GC4y>JAR%%$#Q-z*-8{2`*{wyeP8v#(%qL8k4;M*(2WzrV)<5K*`ElO z|JXFvFbsf!m7~0wdWFL|CSniLsOr&s>3TR!&`78@3A7iOfNlC+I}Fsj)3B$RjyJbf zri1&dWN>Q%@V09f<( zpMwqI;JG?$jSszcKG@0Ogie;!~(ee<D~D?hxVjs-9%>sv{@ zJ?tFZ0C{O|ud)e~IX#;ZH$*B!Do*7DGzSU8M0+mQwHQQEtxQs}av+on%E9LbYd|}` zxJZLc;PxhtH~;(k;*PkvsTZJ~7Yoh#9m|PEP}MSfP9P2S4W62`Ad1@hOEs~o&@e2d zE-->Gk&R$4M<%iF|9o>jGB7FWl!>*kU(RKvZ?0n|DkVL$@b-1hx?1(kb&MiX(k%mL zkFEaMOyJ7nd6!QQw8FL1*Ls-{E(|M*o$4sBHDRQ*T-joM0h&TK_CHEFouY>s24J~M z5jzy72J0<5Cm@jmS_unp?)cK-b&PlN;JK^Dl7cX8@6c?4Qw15d08NaKUU-%$91m7S z^sybCV`U?w`J@AbX5bv&_=vUI-tmyN(zoB4l(^+R5qYpL))}oaoWDP&%_(@AE4G?S zgoUOKhYbHU-3W9?LPj3bNMUag$X3_bR7SD6m9@2!0yHNci@Z1pci9@eioM81{fjdb>O%JgmG9k-UClCE3pN~K)wZW~oiuYmLP zMN)AlzTXfL~V924<9nC{146>ty7HsHKDX4WJ zrvnf8vbp5qW1jkR%@fc2!?AaLU`Pg6axj|=7@uTHGn@ z%dG0TT7;ATwJ&d|d$UVQo1%=2I@%(Y#<`L<5WHdC&af_=k3AkaNS2J_O2L)g7piv7 zVKievAh-a=5(P1^>QcYgtA|$+X&JQyhL_Ebk+#uORUDu@6-PQfmd#96oU0^84YKc1 zgQ1+HX~8f9idx&c@NRj`1o{y2Xc)oL%B5ggk;hy1JV}=eU4Y0mk%U4$D|eHY%9&Ac z1uwh+(^VzB3{AA)_^mV6x@TTjw4C({M7-Be&v9iVt5{`MHnm^}?jjQNl8iDFcY3`( z-af3AuTEFD4ZnrA4Zr)h3Hgo}ggV>i_J$QNzRN|x=iw4g9r?_gJ5q8?$ttkr6YhR* zkrGJ3ER;4JB226tn=&)91Qx|+ogQ+Y$o>56GO{aM_&mGR^v{=qAz!7wfdtUcR0im0l~X`JySyCG&+H`7 z&(4_z`q^cqfqq7EDnAP*f_@gvyzF`d%|_48p9|XY`3)z7F2ji?we?~&c|Pc8!GzG2 zazLMZiN5g48a;CpJU{z}Gdw?YQxiWkb38wzxtX8YS)QLwl;+uj6-@N(#i}6Hv!{HO z339G%&xMm?B6Q5TQIhfbnVs_aS#i$iXThY;1vV=8`Z=ALrXua;efGLR|FF~BOFE&b zHdc(Mi#mDxW|$EXiG=IFoQOs66htk8=aM5A@pW;r_Y1xbIr*1)*1t(FKHkC`aj}Q5m+cjAodKWAD}F%Ccf=RTu>n)J517^R%gxrhOg#cHw<}kt=NS-VmpGq0_;#ZunqMa9zNx-Z9pYWp_Ji zm7nMyfK~NR*IAi%w2FK+g-QUekduYcnso|tC*23-q39?mro3kGq;C@tFq~0ihG@5` z&J48KNPZdx%txIdr+Ig=(nrd0`c%Qp{jZx=2y_2&kTsFFnc{lLmZ|mRrHnFG0(LgK zK3}}=fyXt=+9o5w1KeadCMIIa`s4GHaRg+^sk0*iEBeZ~?R>M|GQjG_@iAXDxRYu+ zKya7hFJtA_s1^{LUZ*3T0yBGTmnM%N*B+rm1YHH8W(R{I& z0wY2c+0Ruxj^YYJefndLLlb*6-I(INW(!p`Jw5I1^E8_dN>h$(WjNxRFxOxs4MKMn z>XYv1BKjq3dN!J$jqc%Q^}Ai64%VK|B2Uek$|8H7x6fcDP{biH#JUJWslV^nh&z(n0Y2M8o34 zFgRKg7cr_VLYypvQTN9w%=Fs*A3s=M+S%;w50Ba8-ciZ$(F^Ve&(Wy14m*rwpkosLQl!4& zPa~M4%202ef2h9~wT{|&<}}xert{X}5R-h&-AOwP?`Ur8Vf1WTHSlaYn!6W$zdPzg zi{Jm=f)NEy4mTU>WXOgAcP;ZDM(P)yRFD_fuF(Oka?*M3gU)K_oPITPJ?nI`T;9^0gHDY$@#-46ePSlfD(DmcE|0g zKj^_cfH?wty%=wlMZALMy=oK`{UM!pf&u#iY)R^s1Xe|=3)3owBLY=*{{d{H2!q_ z<@oEfU!DDJH2!-0oAFodzn_i2IQxXXNH}@Mzh#A3!Lz>}e-X{Rl31NCMKkr8r_H-B zTWKE(HrLZUPYzNN+sBDgOKDlS=Q2k1_KEEHo01D!k;iuMl<7@R+2Cx2_qIW~IFTwv z+jqvH{Uhu%BKwHQ?Vyu4gpIlGsmI$o;yYz`??sry6M6#!`tO0r#D!=Lk%6v zmCPa3`sfEf(@t}?MQxkg2D?X$*=)yrjr7yz1H+M;vDY`p%szK-!pe@bH{aXaG|;wZ z;;e1*t>Y0wR4?#q$i-$tjz{FA7Jbw6j`E9t-i7cqj*R7FMsC#8(ds)A)C1RUzkE01 z*##akm1$)fa$rp+tJ+I!yep4hutqF-LvUwA}|Np!2!;D zr>QORDPDn$#gBxWa76miBtAq4tv`T-to@|pgSVkR%7Y^pRK#!3;}wN9vQvp4kGc=i z7ilNDpB)^gB>m}3o({p=KKC`bDZYVKF<$9(;KZ5ji`Vuab#KEK9t~(QzGEv;cjZNr zb>QkX+cJ+j!>r#)rB_KFakneh1hFK7JN$#}C>us~Xd8Yq-H%b~es46$BXUxtZUt;< zm9>g$O~fD}mbK)@RLL^<-4x6e2U{Y~@Xa9Xl`7c+2x1g0S+@&WG>@FM%UR_lN)xzb z>c4EWTD7GnNMi!7%#>9yPIAkeNE(VMrlw)6CkdHMvE*aI?aqCj^2Lyz6n|T6Ic`S# z%U%E{8ho^BI`>*w29ohq(OD7!~IYt*F6Pi^zxY;N6MuhB(EBw1Z) zwYY1#cinkIYYrd4%UgS!Q2EuIR^%qy+V<%ps+9p>)>r_L%l&Q7uNeJ{#dNvo3^Ek$!#mrb(_xoT+;%^eJq?vVXadv`Nl zh;POV?=RIKtu^Ya?0@$fHTC}bVtn()je>7C^|#0WntilZrwV3JVFDFcqjmGM@#km1 zJo~rfFUDV<{R(ng|0)`PjlX|0{{8st@$aJXKe11LXY_H}PX!&o>`#>js)qpzNWOY2 z1XzFj{g?fd-FJPVjT!Wotpc{bqlE6VZQ7D0jPrG`|E8Hy-Egrx_tsOnNjp1Z&wbIr z{*54N;%zJP#n&f`6zNK5r-s3}q%CJ$iko0S@4kdUuJL*g;H{@ed$2yvWg z-}i1@XMZo;ghp@Ny1BsKv(^_D{w$h*8w6l7AsIx`pADEWDk@j@{X6<>uO78K>F_oi zVTcgG1g0lGm=zvub!mr0!`LBttOB8YW=&|D7_Sk6tBVid%PZb)uvseJ{)EjmuqZGs zv|fW`n&6-V|K>Fz*DZ*fJUG4i=`ow{x!$R2hevcP^L$_;(erkVY0qsiksDtTaRy2G z8`Ia2heY}amj_V`$54Z#_1e_bO1mB7rY8p3bhOtwLCN=IZDqt(HH=0sa=un537>NU z^P}mhQ4T(Bd~f`r@eADcz|+2oHojh8?7DKR6y${caB8YQ+GCWBz*C?7)9QfN-a`}= zE5|(hqg=?h?{qqAMWvSl&I@(K1`J?wNLz<&A#TgCrtP|D^YG(tdp7EghP-&E-#%7lO4^={Ru7W_Y%!PR zU(F}otOcoR3(zvLVlPcp_>I%sqpZ_ri&>}7f6rpIE@mKs#3bjZiqrgqw0kf-q)0I6 z+{wAL^Rr-tjY`+lRqWGsQ6hiMnB*PtUaNE)?G6p*9ytObk)?+FHtq%QQsp z!EG4wQ`~hC=bL5|^x};3kp{EZ7iPF(UK{^+rgMKf{%7z+8Gjy;=0HCme-VxU$W-!w z8vi3x(LVu~m9xLaf_@?1gc~(h=RjD?&TVYohRLXuaWjK(O;@NjNbeCsS?^JthPvL@ z5Lnrpx8~c0dLiMbAUbS@AevO6^`z0vKQ=ZIu4$G)QI;g)V{h*zFupon-{1jS6iU)~QSJw=! z)mjyCSI^MG>Qu31cd>AHs{2G!x%|?kR$tJr@}QTrISB4V>a;fG0kJ7{?{xa8_@hg2 zgTB6#?kA%T4hM<+j7*$rWVO3$i-{w)Vee}FQQjd^o&c~=^8<@KeF29{C;a7cZAxP# zZol!E)1twnJZ&Ff_TU=)!C|!6c9W+RuJJ$l>%Bqp0ul(b*Z3!Y`yzXhblMrb6@T*g zm{E_H#y`bnJ~s9CdoSUw_>*F|Dz+<{a9HW1SCKrcffx}__rOZUIQ?mkcB;JgVf4d6 zZ`79s*zY@B%j5A{_A(}xUM}8X8z`_iF0{lS`CgWbKc6#cIvR){?JQ~cWT9@tXxlvK zy%4_-dif~tj1DsR-4KTyHioQv{uFfWWUmal-u`~pN||DQgeV;*gA{YM$Tt9rHQzhv zs6y>Q_9C_3F`)?uHF*zy9A$$ZehA{1z9_@%AR8Wz_9S@ri}gkRVg%)Rnw!1B0lYZr zjfR7y^}N~Y^hRxXlMKO5m?hn2HysA?VDOGc-K>Qs%rcCIX>yb|kL1Nsi41GH)9XEN znuKJ#-{vswq_E47Smqr>kr((yuXofO!eU1Hr@;`sZSh@kDxyA0@R12{KB9H+Q49f#ieyC-phsV?cz?lkD9D* zrn%L70REzD-4_WH$K7FF1oIeV2ZvZzHxeo<+e7FOMq5;d4&ck?U8XcboSS;BS&OcT z?`}>LKnTuKo(%j^CTG}(%A|y{Rjyo`>^L(TfFim5WRPKV#s_im0)IA(h$j)0b(-jU zA}DZteB7)#ce*5Ys>W!K_pW|J!z4cKR`~#f2m0;gl41K7KE!^q6Rtc5BUc#&6+xP^ zCRCk+k|5BsWy`j0+wQ6^+qP|X*|u%lwr$(Cjj4O*O~ky2^AUM+?OZ$f7%-Vo)ZJw9 zlttr_e}y5E?H+MqMrU8yj=`ZkVwXpw5t2(|GYi&JebpMo6jl@LqxRj(dEH>j;wio0 zq?V;lcFJh*$Ku;fw|gBsR8?KI8w>7Gw~Fq}V=GYRn&SSqa*~Ox*~-*;OE8}br4gfFa9d+>eU)6F;M7}hKo=_28{x<7YWK}6lV_%3^nHLq4`VSTbL8F3$Opd3mg8HVXm~CB)jO&xOyxl+4b;Z?9Q*Z@l(N)zUaWnCC$S~n67(MY4QzNK(Uqoj_g<5_zEmDFrqMX%Z* zDot&bR|T(AS6pj*Hfhp6g5p|;|JiZ2=3SI~?_2{lX zx2|v44?4u2G4`1nki`~Ib591D1U%F_RJ{YB;1`0R)~e+g-+%Qx$W5-v!=?hQJK=Gg z60;P--?S3Z`^d02Bf;<98DygKIxJw%MPe^e<7i6b)NBP8;-Bdk8O3BAs_xtARxlA# zm=S5=jjHDSZ{D@V!1&GRlk=1J165O{+xPI$hv$QoYcqg)reyQu8~;D>{Nj#Ju=~ED zM?IXuA1<5gw0l)?q9*uV{(T(zT7>;W6Y{yVYqZNcutKG;_ZDw2r7LBY*wveULMR*F@mY4-ANpG0vJUUuNV;uq*erp z6B`?@8XT1Gx1xqwg=q|AdGO{dVcu;VF4(NDDp}W}ZCc$li7?L#P1nT{2^oSi7d`tY z)|bn+GTHQ`j)*r>f;;WY%7e3Vh}M$zdE+_VTqfR6xlSsqbBHY0c8naV7HZk;ie@R9 z^R4Ei5r-p}V&W|1Q}3~*=u29?GZAs?$V#0V9QL*a&HkwZq50cJopANgFl$EF0Cjs-R|y^LMqNv^DFP+jN^a zhRUU4DxETA?RE2)P|GiohW)IKa+*r4EW97~P4!POVICU=qHeZW*pU?sd&NjQ!}Wkb zYI7E*>RTj((U+uH;0dKz3pzIb;}@#abs1j{!6OMAs&m2+Z<6yaPUrx#Nztd^k{J#t z1Jz)z??YUs3hb|X0-u7diFwC=QC|1X&;;Jh+DLd03ID`YSlSD6y_mBwVYyNlt@o!# z!{_fXsK--+5tzGUSpnDyy(@#)Y7?^I>9v=7rFvZKiK~?RujOnA$cCgl*6lzY^H&BGOUX5WGxSUi)82c7hh?)4lTAL)m^KzBv67pQRG|%0 zke9Dbaka|kZvBe|gg~wQd;EH4l}=r5E>{vZfXl-2Mxza@a!;xE?!fk%5pps@xm;g7 z0}NQFkK8rLJm9+zYT@ z?QGQo!cy3PAj(@rL@Pul9~%d2pIx=H0jSk1r!pxZ$)IGHS-<=AMAA$XO5 ztCg&w?nZQX)1C`QKag;QR~yrCIDf^5m^(p|{@tc6GEPi;7Fx-4h-ODazMU?KVj&sgv*MWbKgWz}Z^Fi@hW1_yvs`_pvw~C;1$fIF&l!{csTd;LfKu!RDt3L};igMQ$S`k_{*!l8QVH#0 zj)bMAd6JNSGzA4_KSvjfcjW1C!P9RNywl!??b~$7!FCQ5 zD6+5H%3Rtz#50RvQ-ssYst!soTR$WHtiBaoMcKpKi3-jBJ|x|`i&9s!g~cB`l7|55R;oaM&mRXo^R2^1W+3#}FFTmatalPTX9E#ARr zX)(P;gy#KxtZZNe;|ti8dpvEi4IBPH@`I(rEJW{)oJ^hloL~A)`J{Hi*v5g?qT{D) zHuHr$>=_xwium~wve-vMb$B#U?-tau*j^StubohXR3@91i2q@BMn^ zwD%&1_~%oRMCJtDK-E#7!jRq$&*|!wHFo~8g%zcYOGUvUK=o9##@adJ@SDW0&q5gm6y{>|VK8;JxmZNp{ul3~)34 z%+Eb@FDK4h7N$u%gk6vs-Wq+HMP2F}tsG^JOv@0-RvSwEPsk=wp%+zFmo}4ks^Pgp`1!>7xd*#s zig);N?Vr(SW${)e^}MO`j|ppBCoI(IY>xVO+%@ny36iw_CV1uAtu(n$V6bZR{249! z<6+1Tn7=iYs%u8aGG2BXkuklJRGs?0s0-IhHvu`^#1-=${FRAG1Z~R_1_6FU;WrLL!b$K8G?d!5c3=)AUJa>t~W0QNLBS>_U ztZG5sqaMm0rpeOUxj(nsD3F!=e?xnb;HEy3?L3B>pXJ)t9zs2C!aCt>2J1HBzz)KOmT)JAKL;AwUJS*Kr6D7xKYx*h4-%2%>LX<+8>X2d$WEUe1_bu z9P9|<%-2kRhkv2}Vd*n38NbV6B#laRk}J_1t1lii(8gVB zN5}MX`ddd~7h!K;Bu8fUN-wcCGolVk~*L5VYG-xrTT1L@Icu-bnQ+ zNexr({5D70!jq!)s$1BKU;<0Dh$5J73xawyfgfB2IflF^6OQ?1NbEKb<2F|7S)(E# zE`jgHMFE0=2#pi2qj}tJ(+Y9b>Iq>zi^aA`ivI%cK|O*a zbMV~`l6QX(iOnoiq|>)!6+4v(dJ7z7DJeIQt8x!{n7beGFABZDBRL2S&HX1m<-S?L z^jv|xDvwi=w$QJbh85Jk(;m3k&H2sXt(NslNnOu@F=7Hi#6m)7t8o!4D|AEELX_vcEjvS(U0oBX9md3!5pM`PTjd%fWt7C38=r}HY&@i8SO@Cn* zet{~Pf9C71CbYuL8h--hA7f8t7o|bR%Y#|-r;Z&e&ptNGhXB_O`NHovgt%l=)b>x z|BQd@0^th>3ak(oSP)ar1$O=a;X?jJmecJKj1TmoF=dr?PCyEDRfFIF&S?Mvq@Uu)Gz)N8Q3|1 z^`#%liSdDV3!U~JtKs;(yIe8-fxGl)GA*r#(J0JJ=i&`KDCFX^K#1Sus!w|x3P?JTH% z(UlTmhT~8~YJvc}c1eE1Gy@T1Ym{g8422v=))qn1^FxBr5@<5!E z4K^B6OnV3Hx9P)HG7cU;Ug9Tr(^e7sPOz)uQj7G4nE31E$eeNG7sP8hviPn zz+HQML~s8>C`;ulF}4$?W6COaoW2il-kIgUZL^oe4+kSeT@9ym?l)Y+AAU(jr}tNI z(r=pU>Ec24t!HOgG4rQmwkQG}lI&?Y@2;1RA%`_$fV-9KLcB@3O3THydiRO6t*qwI0JemBf=#ts_-dOoZ1++k?)h`!oxA-00+6OOpJE|BGb+xea?a2 zv7u-{o3f=k3A#b#%-Dpd3v*lokKcmg{f^ONrqs#bty`A_WVrvMo*+iIvHmC1XdK9! zf%gH{S|0NBt-xDHi6qqyhCUrrcx6({Ls$-4(K~M5W&hv6+ANjh%h2u7R!VGGs;5wR zf654Dss)DOmbgr8GJ7T_(9kbauwlOnl^aGQg>&C>7F%JW5OEPUUTfgB^N40zbM3JZ zjIwptC$N)>2xJ~h)9Yj_Zq|)9GGB+(*|mA`rS$geW6{6M7s?mgR@rG3QcKk$lKbo3 z_IKSQ^P2hVV0N_ZSlFFaKbInm5fY(x4k2C=5Zjl$dy-%!HQk_eSTEQHHqNy!Q2<_z z{eEu|#=dS{uB`}QX-dCFHzPfr^So)TH9XR7P}$wk5m_3Tg4+UM4^;6&-hg5zVrhF0 zaiRbHZBD>C{3Il11yM(6&q4L4jPh2|gj8i+R6Paq2CxzW2DsTDuW*2z=so6MH#Cav z?IiVwS`M0 zmPvK01XAjpU{DXfwh4HpWa7Kzv&DSrf5-fgkm(xVoD2mWfY?uZZ-+U~v0e?|;$GmL zShndcKvs0B#3V(Z@CfsUM=%VdW(B)DY&euxE{ z0>W67`tV>)QU(mSwRC3B%;SP-gssDOaxytqeLZ`u<2hz6*CwxiJulJYu=#(uea4GW zW32KX0pa8Y`J#4XXwv#dY$wP{`P+X4&oP!ffy5Wr8-pU`8427;ioGe3;_2#>si{2o z2WYGzfNGq6DK0g(`r+ZIrM9X-{i1F|;aX`Gbbw0&Ur)j{a}+peOY6A7C8LQkzj>fSwmc9LJ-ArmhR~7I^;O&@A!_NP&J^* zn$oI;uFgB<(%oYb?L-KuaH!2(BzMc8X?j8;D)aQ`5d%*BWmr@w3FwCQ6A!=1+Vtln z2uZkx`kKFap9~e?;~<5hK`md(i}Q-h6wy4COg|V(u&AijknNTA))>P`Z&_I4QVC&$ z)OwVV>A%G>2JCrkD0fRN8EPshOyPre2G|NHD<4+mfC=I0#yZ3jqpb!@4p)?*#b_1P z&Jim=>wCmHtMAehX)fq7l);J0!{`er1;(M)K6h(IhZxkvQbI6HWI6jPIqOo9HN|m* zmYjEqC-WWhtw*AF&DvrgwYMSR`- zlVn}sISES;z{-r?H#?>j8Y-b5`LX`ww7g?}y-{F!2roA)i4B#v^qff!^=G#hc=H&9 zwgrp%VeFUe*X1v+Z}>Ik!k8()^DzWWq-3l09ndo*`j}POO6g|7s=7`)eorhrgsQ}; zaDD`p9DcqGN4=m`KI{x-K(p6O`|J$=?xj7PzbklkvVQ*OUBA_Ib|(ROxWWaq=a_zd zjQQ4K{MJ+?bg8eIKE&B#ev{%-OUCrDd(}$aM2uY+gkpnS%Ihx};IYBVR6V~a+W0C2 zV1+djzr&KC5b`-wA>O=QHZGPC++)j05(kND<;Kt0JxOAWhDVQm+PK3!2#%Q@{Z*P% zyOQUKM9-we+(`sRG+t24@pYt7Jo1Ayw@vD~xwV_+Hreq1u`>P_*ecLt>XAr4{JW$z z8mr`iZAtGo?XECsL8CFg-y>aX6dVrfrObU}bqaQce7>jNI8CyC%&3oAA8}HizB1## zKOSG_%4>2~C@AihH#K8Wkn^E;K*Qcf!2fG)P$7HBw3I6c74)wywwkPT0?nTas72~m zPS&{;cq0sA*M?9{6c)du8vM+Ncz<-@chk9H%3<8w32nFpt>uUNc15{1( zoJxRKXpN~CD@}PTAhkg~Kt7=IM1c7R@wX!gfX8>wFIT>{W(7#y3+WU=yps3G0oW!~ z?{ktq?`ev-&A5pDU?pyq&|8V!*hz2^7w;I*iFtrc)VO6h3S&1EJMYsI@JDy+&rvd3 zKQkkYjRsQ%&`9&x6vAZrYPaD(<|C#)@v0qa1YQ4<@rSC{UaQu(r&Q`fF1hZlhL)sZ z)S2=PUhL7vQ@C%z?)qxa8CoyIuE3Vw60HTZP!_@BqGwL+Xf{kYQt3d^j*=5IVPL93cKRDEnvqfI_9V^?D!h;XmgfF)$DnzkCWG~1@)o}{B=56zR%uU&Ep)AP zxpo@&2YO(y29vH=IUDbL42LfJT5 zh>rHXXX!1wk5UO1SFq}S!G==Sy#C+C^7%$Lk! zdY75A%bR4Pe1EB)w+Ts@65YL-8U%Q6^sw7a<84o@AH=h}wbzKd6hCa)R+cq#3|01$ z>g;>d`iC3^?x>ci{)e2P7c_Jmt(IvYo9)&%JLmrx>V7^Z_|WhHDFc{T?So}WT!36; zK3oTQY3pS?>0A~a{VqsS>|+KuK8J3W61ODMPI;jgyhk-ZK2Mosptljz!1F&Mt$5PK z6OZKY{jWa$&P1K>n-Q%0?rvvt(RUgr{=%YmyonI&47QmyQ@BW)HUP@I?F}J03_Y+N z%uyNQ8iCv$nQ>Cu$Hb^v8fXMPmIW0@N(Spum#-99AbbyqPgx#876IIK3f~z@OV2IN zf?Zi#!$`Axf~=D&jk4j+#EF$96Z5Xz6Sw>r$OVQ2zvtjw#^Z^t1@&@;81l#b)3!_j z%Y`t#wSh{k(DD3@w)mg{;(x}bE#q%~b(3x3OV3M$Z_!0okD*r{((Y(YZ96H#tYYVu zVLOR3LKL?!&NvI}$o{6-kNVCU8)(0FS?Ijz%j&ttZ2C+fZd|y4oAEJ97T$&P1jvosmQg1%Hwj-90LUId!=LyfZd zRu;Kd(H&8#V)yQya29twGGab2(oIK0%AlGy%o&#P$v(C+0Y)JmoeQw|PI#FfYm)Jr zUAR1qm~fO4`Zo(WE9%%(`Zem5#X!@v*mHraB1p-kQV{W8Bt5fZezWGtOjbJp9~P?CsvZkj0}I*)%qzFrhEYWSzv*`S+LZ24@xck zzTHXf)(-KnDNqI|74dIYyR^3JjY74e$DN+(YS{vm*w-nqjE)4=t4$K+AoRIaEA422 zr(AHU$c$w)SveS4&|gDp)PqNPeVkX3f4&(A$KEJ~&|a1cgGTTkkKn0(d>2?U+9*%m zHaSV1mrKaTSJ&B31yzU+^hoz07=jkV6(pf^^(x)cj&1T4)m9OaT{+%jqKv7M73R-O z2PDMn4{-^eoRn0NCQ9jQKhJY6}vx1MTtmzC~G`YU;rojH5u>AI39R?hDNL!GhA6T=q1kVCsF>HY%A63 zc0tRNwgQ`idkC-|^7HR+6ows_c_aWkJR-eGx!j(9?_oP$BKV}UK0%sEo-#; zgs1=LIch~Gv)eN4t{EW(4mhy`$A1B7bZ{fx+gnqm!fRqD<#v%Fr$vQAvt2~QrqYS5 zGS>?w!&U10G*PAhxvSa=$}NLk2~j|FzKV>v4HUt!-2>bZGYQ|rE5Ud}&N3V_u`uWZISLaq@#o?I$y-EB8Ak$t`ph&MJqHjFvX zXQuJsy?4)bRJ_2?>FsRc3zk~zRI@q8O|f>D8qQ}=fxpZ;)#)$f)~e6}8a3d{bm~2H zB6=QbkndQjf+b$1WBb#u5gI&Vm4&c8r1tqSpcEQ*h8ff_`NTW2F@AzlyQwoXNg4331&bu8EI)A|HL-o8e6!jlRQrN##-m7=9X4ncOEcQjV*PX=bkCjyuoW;gmFs&MC^okV1S4STkY%EyEc69n-)y3B zH1kmlJ7<^41Hor{WmqtxO8J-st8p0_y)yFaxiwyzD$B6mP!=f&UgXNkfs4n*_g7It zK@pGb_07#sq7}ymF72g5L3#_mD-4v*UJF=(fP% zSXVlfF4-S^He(&vmjo92p0u&-uxjmh8dcw~1x@edhxVbbWkBOhwUTHPR3|0+W;>18 zAfL_=;_MwcU+*v!;c2f)|oh)Td4DsQAOwr+AyqT8EH4_d~dH?$r}GBybu z`{yGB5PDr93l-$_C1VrgIL^4iKp)71B#7CU-=9dL^g@YyI*Ty~F!{<=t-$r1Tq^>M_rn z?4CPlC0MX$1c8Zj$W?0o`tyv+eh%3k{qX^O_Ai>Uv-F}eH01Z{g}9h_s&J4d^t=G+ zC~qRy?I+lLQN+9cpw#RpAqmX_V@kGe%c;L&TUcNJ-CE|Xto_(zvVCj;JBc&|J^dF- zJ^XJdWgjkuid8!~FIwqgFW0b_A(Q?Bko5_e&}hY8DXWKa-|Y}I?LDN^!6X9ldVTb) zL|N@_3ebWEcPSB+>i(}0RAZ&Z1TMXPJI)UkY^VCN2;TmW2R|-cl)lA%ishDnO!N?IG)Vk7Yreo|G`pm9BS@y4aw_L+tcyvFAJBuc4Et;{~wm3<>(3l zuKmSQ<4gY+OR*AS04eRYUIgP8ehjRMeQlmU%_YHiy+yxIOk?B3Kq^}Mm`Pn6F4aFg)YI3KpHqG zmYe8^l>CiUF5MB1c4`MC+)-Mt8ok(tf=q2kt~b09BL`NM9ToB^W8I+*_E!lYfvC~! z36(;-KjCqEKFnK6ySt4ij+iFuT(_bTUHr0pXw+^$28-M=tbfBnrp9y0NJ-WiL{Adi zQm;>*rG~eY%x096%{R8Ma*CH*q`54>)Iqe6*e8~h(1f;jZ?JTXRbx8=LD;Y+8XJpG z9#Cx$4qUNq;-9y2VPot?f{FnA1A+b<7BtfmCe|f{CTzgg?9vfUgv@_RpqaU>Ab6*) zjj*YNtKL3jU^;bc68qDS80&?m=B)CM(nlp{W}6zKBCE1zMH0-K3)<~rLux{QgKM{$ zD~5#pI^u3@_dtdxJPBcmkosMR8{807J5z?0NzzV=1fZGA3OlE{Br8jEEC?4LF(REK zC$_Qri=)sx{)UjKg+EhT>RP;LGz)OUnMW+7QcEtib}5L`N?@`dsWH!cq>xu}c&z4l znSdLJ?_ymc%35*YfNNpe;Y`sH{M(4?VGa+tBqNCOR;v>hxc>?Oz;YY!NbA*kP2_Uy z_(}tn&%&_K(a!Mnxt*~Bm(bs?v~46S>rKs25~pl!)NJL%)OrVakdmz#nMiv?AOy7Memi3`At+qH-gOLN>%Tg!sBn#Z!sk0Kfai?#>OO6xWbY z>){B~99skY4U}|mvR)KJLY7dzgkvb{(hWns@jS5hlw z_Bm#xYugKWKLdsHZRG0@S}HzagHV*^$EveW;`;h|E<}RO)odj?nyOOj#Gy3g51c)Z zxelI51W^1&XL&Fb$t#L(8`bDR+sfKsLIneNK-b7;em*m9|LD4j$zxrrce@&Gb%Mgy zN!>|FX|9A1qb^8pmFExolQb{#)W_K4@PMy2A+2~1wLlruSE)qa59CbYP?bWhcs4_P zC(IpiFWWyOpuJ1e)+XMYa{|$q$dtVRFnEv|0?04RwK90K-kO931+psjte8WCF4X*} zk@M+E7*w)svxnvsHs22(K}pR_Kl-<<&Wn)|*iyJDs+hz3!6E(+!1sep;YZvN2C#mt zOMRdzdG;qJGo&n$O%;1frB%z4I-iQ~n!djV!u=c%!^3hjh!?;5xrhm^*Q<1;aC zq4ZQ$GpL3e19t>7rzaxCw&4IAphSu*x$_EU2#Gwm^#EGv)Y?=PY8c4ojEASGA>6d6 z@#!XoHGW}a>t)Fd`|Uj2A}>2-EB*LEj>XAGhp3kW84cy*qq)N;O8cn}z>(KL6lqsn zl&yY2S5eQ6hPRrIsJU*!cUQduQZSpLcdB@DnGMswV>@zbwGx+;u4BjyAZLlPVA%Yz z#!J*ySKr%_3VY|2L=(Ve%VAACvDv>{UhpAAPZxS{w|79(;PMW%qBC3`gy=6cLU~DD zcq|7UZXO!F`fzp1>UqSUB$!4RwnQQ_D*;Hh&F)r2qkl`1ocKAtII&KWO&CJRQH&d( zddFyS7<~ugL*vvDSPjGL%P6j8qkn&L$jb|I*CehA%%k0$!mHPQQ?=D`TWb_~m)T8) z32?#5`WjrRwKJkpK+(~jC&%-Q1puPb?q0Owyak>!56Kmh#~%9+1Gu-Us~TSMroxN* z2H0GwFfs(K1*%9e^8t^>x0W@mwGEscP0gr$^WK$>N9ogIQ?HLSa3 z)@IE)9Nkv(k$>t1h_&3R*8M0t&T(;x&C{W-6c{|QK7ng(TMEStM@QQ$vQzERv$`%akh3L~lt5~i66SE}lN4~#-du-D%%B(6}984Ci#PI)HK|iY z(P~7A*9^F86I6;;%QHwpKXlmr4I@MlUK%P_Mpsjgkza|LDJ5SsVZS)EbxC(t9Q;lS zi)nmwc`FuLT*JA#JHCjt&@-I~K<9uZLW7u*a1DGa!a-}X5;X0Cr~$n^)^zGsUyF&i^;CKTCHE_X;% zAE&;dWx>0N!Kc09CH<#u^qZumkcW{V+Eb#fOh)VkFEWcTCSQv+@?YiI`~%i3HxhcQ zrFx6z2ZOsGK?>z8JZ!>>zKfk2wCH0-Ai;{P;r&k5Cph=6o)3TjE$Oa4g1Ck z(xC5&4J*#J`^KTdAJEbyoD)XA%BY+Y@>5-Mh_6j%oP#K;GdMg^39R4eT*@&f`2ShjA2Q_ome&&5hRK z{@&GC@u;ksSmlqsKw12i;X)X~NnEqXx%P=Y6ePx+hPuRxxPDiuyMyDXY@FJv8te_F z42w>3&@n@I&XG!mom5!S+{e*bC#z2J4>M@ z+py`g63w>WTSp43?ea@1ejm?|-L(r?W6Ny0O{A3sQf;EyyGch^0u*&}#Q@UMMCiv> z*MJ(9jLG;SJEX%pk0XFMTMeYS79F~%+Ev{*2cn8!)KQ`00g8SJ;%lg~opUzKH<6v< zo-B~z?)FH8wPnsq?jTG`ox7>~DF%LABjD!aLDOKxO*@6NE#Ba+&VQaAqwGr~)D!;* zCf}3_q131y3m6PSC^ugTZjipX#=6gii|+RwHsL!n;63h>`wQYC@CbO6H?0RJD(4dO zB~TjQk%uqtgpMN52+L_ZC<3?vc+uEx(hJK!bYQ0CdqRHs zOmR|<^cNy9=ENJXAY4=E>}v$w1%wWl1wg3WMY+4!L;*vQVn&xz>&^tp7N6q)Pc<_n)L8Tv0_5s3rM+zhG)&?xskNEpiQZw zAwn=%O2Qbw3HZ9eAG}qK;z!O4iw#_DHnP%zgo97;_DEP16=voR}2}#QkeF6Bp&g&Sb!cnlB8I*`+fPlEsZctSTAC zQ+F-v!hGNnsm;w(peJ$FyT)*5VAi)iBP;2nvLlWfcO1!Or-{Fq! zFH`1zPsweEe{KD|-xi;gANJ2H|Itz6eg*>cBS`4ZPqlbJidv}b^#+KDO!C17-Gmkeon9SYBJM$M z*kLaf)9IjnwZ!t@C>=z*8<|G|O?CT>e~P@n$}&FGaJYl!0EhzcJiXiQ#&MK-qbMs| z;B_cYOWI_rNHbDF-pN=Ova!lO^iXbQVQCNfcp~qbKBZ75B$=+u@RLOHS^r42eX^Jw zty$4$T2`&Fd&JcaB6OHxZ~O*3u)t)j{EYzKmvwUq;*b1VHpkJ9>DTT*J(`IEP9{t* zr&Q-&bB2G!sv>>V1-pYHePdF3WEtOK z>b;>~6G>uf`yGWg2lyI$WcOFBx)#-s*Qea&{|?l=71LYj4NRm|WgRJ5NrFlFTz!S| zUq?-W4%YBTa&o?&UKjpDXW2?iV)(w6&5t2nibAYt_%A*7{`vXGqCNNUZc z1T-Q{*F+zwGt!M9-&FVLifYL8_jrXtQ!+hE3`BBi*2{LcD!{w8A3ph?d9-i7+?rE( zWPebd61;R|!Dgzs$CdMC&~{7?gU*~q>Yc___n)bBwJo{%dZD?&j^PRMf!|XI1-CC? zm%{>PGX|Q;YzrryNWn(o)es7_4d=7LyAGUsLGXoSFW|}&pv%ZUTDlsTTP(Nd0%sjK zUx4O@9=yr1$M=hchVP5B4IzzywH=hWd?y}7p3L-D_Rkgs9{I|n-$hfxpF8noQBS0m zo!#)#f*HND+pP|+Yj47#*S{+h3kr!{c0%hqr(ki$?cf;8I}a%IAI2g5E7X!2wWr5W zXsbccQ&v)4<83-@X3v}1&;0D$v|A*@DbVThy@#n4;`PuFAO!%t(S!W2O|6?vxY{vm zFS^fIH3M8>taL)zsHL{EI#;&FERoqrEI>6Ym_UaJx{k!n8is`y7?j>Dh`92wVwHCD zvzq2R=n2HmPfsb*DDd}TmJ^~2N%pb3$wgv=>H2gscXMaC*9Ip-QTfq{15M~OO|C4R zn)GvCm!$YjKTSJwT14Jz|1Am@z(i@irCJ+;TW4dlGFf!mhIYp zBW?)>?$WvM(xD6#VGk2*V&k>0Ozdi88rg@4CN#V0%qt8R@vaDKa@h+6`^hk@{Zo;T{3{PNa@1SLygAMev}y-lEzgwcv9dCW!H@F6Cm8r*|B;!KK^pL^wPZ&;$-0djeAlgdHurc8fVbH|+E{jdDtQ;i8{!jzbPP*VdY1 zLP_woW4`x5K}l@m&R>y_vcai@uwo@H4j8E{mdx*>IT4Dkndw2-_jY5cgAMEs5@DM> zh6DS}Rp+}&r>)y$Qgxi(S7gY=A)CA{(Nd5?)^&cJcP(f;k34uy`c#tpvqF$z7G9&j z%dm9FAzH4rj9uQ5K+xY8MoxhqCR}s5iMvWLL{`CXiI_sJ<7{dH9b|b zo*s31Rl;L3!9X&+gw7$!k{A`LzKy~H1L)&ed2Wzw&3et}(}%Alr1)n>Al%Q6u@;Qa zOQO3?3+uv$u44&A_VTZb>-68%(KxGP{CUgsu^1OeO^tiDW^>uyHXhD~hrd6SG(~77T zrbx!Zn_v2zL-2RHmkZ>6up?n08oM9dnBOc2^n0xygZ{ar2+3Osrp)e`f(!hoUvBWI zZNwB%FN0nq++@(6+&~g0flhZS(l{_j2oCHwwJgPb_0OFgz^-X!YX&aR+it|0wz=RF zeOO2i<4%nT0aUcQ`W-f1TO4p7*Q~`#4eKgK8gH}Et<kfx#3mWT2a$8y7-wPNs|BQ_K&@O6ijT8oo9{X+mgD^=dJ^W0?Qhx-s`( zg(P=n5HQ>6E?{HB{jWw%#P*1eKv>!{Z z)kOdANx*AZVQdbHmY9keZ0I=v$@mdk8i%N86OT8m57el=D(N}RIs>8^DK5-5Xe~|1 zN%^FD5G^JZcw}@F0?b_(5e@wO;u06cmgd5iCFQ-?zkbxs%jp!wbj;!`X;0$h=Ch1*E%4jhq-?ZtYQl` z-mBQ%{AivDEYnwTDD?$f{)!#Gq{^wi?0@qP)dbk9AyKdR`YQ|lSi{|9&>uNWfIX^s zHFjL3C?K?+|h~qRz>X zZ_jWj!^$-0dY6dt)wV0g)qr=?HMFltdjz4@EE$D;V;Ki@&I zz!sFwATp?|fI(9HJpUbc_x7muJjDa{k6xWl*@`NWYw%*FygrQITNZI@dA^BJB_)=&_R^%Ts z)+R~2=`B>Bt?6CJuz7vHLE7)^1IMnxiO!3pGZ8Gv*m;Wp1AHaG6*I;ygOhc+OR^F0 z*JDm8o@%7JGzZxhB2^@0ktjAM%XWs2wQ`si)q$wC#~nb z{e4Z2g{gNUm=6cs>m?d8c8>Nt$w5?y1FvHUFV^ZE_1RbOtcfFe1v(n`A{^8P;UNVF zX2lz|*!Ws1F|Bx!bGLvNL6|<5wBauAaKPvRDS~{2(L}&_)ZEuch+Up))EH=f6et{q zP{>V@p!{mI{?#ajo0fXvcGvPLUoihE-yK(J2#%B`JVI9`hF)?2WF6|*>*m_pn3_61 z%-HDHjU)`FFP9q~|6XU5A3B|3^A47Hbc8bfax3tvdmibFJYNn;34yFzFDv~nq-?%5 zZ#oA12t1Sk*!&r()GD*l9)}0sj(L<022O}^Z+fPCSTf=NBLnY#33*orD< z4Z?W#vW`hltwh+t>D3zyiUC4N!}2sqqDGi(`5u|k+f<4bQ(^*pM&t@ zIq888IjcQ(3UOO%?0Ot1&@rAl|Ljn2SMomcKnvMCcn;Iiy?AbytW`&!)RxpUL8lB2w zO~HEFI!w9;X@PW8gE%=pMl~JaxK$S5SU!HQ(;^M>+O<-;Qk}ABO(J(vPuLT$ugC57 z*8TfSM@LI}-rV2c|7oq^Z7r13#AITd&tr4IOUPTE^xD?=is#%Y-^19D^3uR+cC^M+ zU5F?XeVlc#FFs|;TgEnOce4Dsyh#vEV;x|r<;Cd=*(OzUt z=qn8UZu;#MdZ8GN6%ArVgnBaZTk@UA~mYU8Ro|gH?7fSl$Z8`l1-=@466C@x#sFzKY*tJ!plr zuq8%d$~_1BrWtNkvMpm*|K!t*t|y4eA;)Gq?jREJh@Mw@7K=x&nN;Il~m%@{H(6>#`dH<}yt;8L(#}i#i zlsCnCo||HX^x;b>4hBV+OYFgMYoGrbNA;DbEViJ0>==V(^sqbEF6&nnMw0Kn0PLWJ zJukzCr)b5c?d>gVzL4TUrnG}+8uT|9yJ%fwT6CIpNB!F=$m6nJt+dKkKbwQp)GMppO{-Na`vp z_19Bm*GIVq4F5j4k~-|GSWSC@2j}f$E%KDe2$$B0rtOF+?pp9KL_w-w0tYnCt5``# zr8lKr@pb9^`juFVCepBY;Kr(ImJEt`sfw2Vul>!NEM6T$rl6oyNf9pwHQ>nsXP+@T zy7*l=6#a_*T6mHd%^6(N!b5i>RI*2#gvQq;I?F!M3FDm)lcW9{nULo=*?pl-N^1+v z`R2S^;*j_z+P8C@!XqZzXkodGIR)V&>IkMm?J!L`!^0D|;TE2{`soNy>UiT6NlGt> zj_tJLlbwEhL$8GbPr1SwoFzVjIQz(bD*|PD^N`brpGt?D;fJ6?jqvB10u1WmOjJ5& zyuj&NNlL~F#dvlrUa(Qz$&iqNr@G2QT)+(12V*)Kuw8L9?e`4>VUja8n64oIDgfqQ zc<4X06wTDVg3fMA$UHjkA(tkI=I%S(rc%p)B3=xb2zJZ$X0r2jh~D~VSI@Wi0dGOJ zQ;VNx!#Ft>W{k!8#ar_?7T=2(7Zw(8%+HDUb0qFT`jV|^9e&-Qtc`j0y!YOF_%D0x ze{bHnweV*PH=)rRw{9-5_lwtW%-{I4X#Q;wU<9s{QS@hnUT;`buI&4F^nr;zEi{7Z zrDy>H08UFE8}@=v{AuBA!(PBSGLI5CZ6&*6AFt2Pi*Gz(3VSsT5emdhnLLHRJ_fmW ziVZ@Z6{x?B<)pBg&Y;2RKEn=-Xz_8n7q>^CBE`qO!Sh2<)u&~|LNAJ5ByUM14K$NZ zIv8qb6xE}&osEw4H<2+2K8^p=_=~f@9)ETAH{;KvvtN!sKl|0$ug`wT{`)u4_%nC| z8IK@S_~)#^_%kRn{=?bdX<)~PX;;yiW6bo!gWllehlAdzk5o}4nBLU^8mFQEaIC^g zd|cLokFlDSNvp(V;W(SAyAj_Y}txj*$j&r6;Lfj#sP6`J-F^&iGxkN=7N z@3)LxUz~lyNcQE~-y)HJ2RU88X5{_k?APNzM&sY}oa|qYKZQ|XfB)g^*U{NuBHh0l zf5AR{arW=deqBWFcJByGiHiH=>p_p{NiUmhJ25?LegHp&gI@0dv&30nkJ5vr+5Lt2 zve!*{tq%Ohg~-U?JYgzb`m#9~X>DM)!uTcKUiaiE`-KQ{_M+c~Y4Xhu-~V>@tMMP% zSiqqDRweoR0w(|)%iqNK`)hpn2ln^xSgFr>IC1m(kQ6vuZS>;Djqf5Rl+U*LFs&S&FS zY&QOq&Ch>kgUu#wboSTm$7e8UaYX5SW;K4#e*DiUAV1?$^jDZ6jxmkVX4d6yc&yb$ z+6R%l49=TfCaC`)YcN*)6q)PG`q|&U9)Hm=2NH@v%r&5@`z23X1Dh-0ee1;w_a(N3 ztk1gtNr)|k2#0*@MT5^i$FT4ua9O5{LFC@%m|JU}S3v8L`-+X)+5ZccHYugQ0x7Kr zd)i9^+&2~x0SgfBZ}(c?8qWU*n%NLmtKTy&{{yo8C*w~UL89>=Vf|vljZFwftWVk3 zFA65Z*Uq*h1}Co}ts-makGKkc@*0>KHgvB|c6^OE{v*yT z_Tn|1B@7rD8rIM#ovdZ1r`58{%qj@jFy%eo|3%zyccrGMG77?HK8fVOP3xR~V%$`d zS+7;1a^`4O$DtVx`%80k3-e9!|JRpp-ddQ)Jqm*}$Nu+pufs6tWcd)cDYE^2NHjD= z7|BVpH_DTzd3JD=JbkgC(Z^dV;=~oW=N4RX`V-vEPPG3^*-G1qd!ykV#7KxqAhvv= zppD}F-_7^lyMD{s|J}HL^J@S1R-XUyovG&Lzj^p*V{>g2dH>FXjR?m%lH*Kl%xvmJ zI`&so=f|px&6|xTR$lLQGgdB#x(7YDV#z_h$P&2n-|6+9N2`z4!O}O3_~k)};l~PM zb*912u@*Mhw>BcCjKL-^600BB(%R{e|J9tDYTmiKx%zl*V{7fvL#(@X+>?l+L9f?N z8G24TvF&ZrO?#ufbJAE65-r6Jz=KFh>^j@M}6MERObj@k||iHF_rOj z1m{o;t;1CC2t)ABAlV;s!(m;L{s<#|u?|CI#ppO29_l=;{Gx9isf*>BQ}29#YJ2sl z-ARYHGbBpAu{%Xl9w>i@RTZBWP9zFqeX8rnF=r+oVPn>WMgmXp4|r`#N1hOGf0A@C zgz{!Otn=upvpkAwUTf_7Ac{~E<|lpU-1;CQ2~ZS>LDq+ZV0b_S*cSfX>p{p83mb8R zj~AM1OigWyi%G2^zn?0v-#q}f8BWa*52OuDfs}UnaX2JNsKM3j`0^eGZSydJyjNLw ze~>VwM=glD7uAn4z=O#n#%BX9$nwS|BVcCaaAG1G+4$5jLbx0Jzysl6@D)6X3(&XGn)QRm4Zo1L`Ik95Kp`0vc#3D2OmjaSm{c3ZtR_cnU6b?^Nq>+WqR zP{(?_is-{*ek69-YeUj={1-bEGpr&&Es}KRXKP8n-x0ao=Rnkb7`52k%G2TUXt*E0 zuTr&hgv?Mrihg&yIh%hmk3v(Dc!n2R^JN@e?PRH)LC2~GNE15ILo*r($8;H#O*R}R zoY{lU83)iUL?S{TA&rpwJOrvm{Ii7;JoGHx3jM>mP|wV*9?nY`kE1@@f8=x+2`l2r zP0y%)%HR%je09&~RR5+U4iS6B`{$v}wkof0irGnq$R*T?)BK`X;py=B7m}46Kb=m- z;spFmeh6+C+iZy&!KS^rIymVMVIl7yo-{Y_uPolY#Q^YBPvX9rDQD^cq6rjdTRrGs zo#WA9lN^f`C&T+xT%WI`NDf92hoJM!FHN)0ivu<|-OALbkI@uWF>W62!^T&yR83%Dk$ z2!V3kWSerXDsm#t^plfLFKL_e?9DjJv`6A39z(aNw{X@kX07FGl50N$HW@U*jY7)l zB`S1#$yNL=GW~VLE?QK{i6|r~6j1J3$dRvw_T<{R<`jlmF@{gx@NfpVEkk2o3 z#uXlI~2_LN`q}X|e&WiBvgRYmpg&Rhw(P{h(IJOc1S~WI0B!vjZCN zufQ(4vHH#Vd2&KI`DvF_%sFle88ARZG79-hffTW@^*|!j!0uzV-FA4jkBa#`dsC)n= z*jW5l)S%0n+_G8(ue5#6%|*=$ekV8dJOE1Dk@KObB{gS4ZK(pjJ0Yq~HJi9|I(^0W zeooxNfH%3Gb3P!>G=%^9FQ545R3D64$|bwX4HQcw8FfI8X2aVjDx7lhX5=3GZ#qC< z2BLBx6RnzjLpKuL-f@-aIn>}Kp_#@m(!7;QKFQ60)5h z!B9X1XLv;4j)Djmm{y&MXYK=pS%?Q2_6>D0jv}Vq6on=2S(m+L<64R#mW6M@`JXSD;j z$hn+j-zQqhXpSYZTmc9jyU^FvjMxoRc?`9(b^vAPD!m4Uc}Dq7DVztM{^XoT(6`8u z(WJiolb=yZ){0_$Z=q_y46Yhbav=F|()Miz_9J3}FGM9rexH~Qbe06DK>!zGkf4ryV1o6=k`;A>;1Y!(RbII6!&a7RPumhCEt^xxO|3`1iY*Q?h;&cD_x z<7>m}IPG?}?$=dgqA!_^#g=jw)r}Mj5@k%q(XgYfX#CaAg~&Cv-LXWE*u^+G0gE?9 zgji(rLIt_r_1KbFZe^-A>Dm0uNJBDhQ@nGVl!aA`(_C>xO-1nIl3d594N6K!CzsW+ zT|o_u4UbM$GI7PeN7uRHF#eRiJo^OQy+38>{T^QEPJLQ{skTlQ9CcY_j$&arR^5`P9b`Jh zLQZIA^~QD)dYbwzHk_W2(?Z=dBM=brUsBy(eM~r9L5c_(rdnlM0xeBqIkJ_DKyj`E zEjtR8QG8D;2@(~p8d!EsF?Tf5@|A}dy(S5HLMQBuFussnLb^)6S~yh}&E;2%r`q%J z)%8<3c2D6ja{1K_VaMP+GAo&5R`bNH#&5QCn0>a7kc8kYwauhMe}pHHb*>T#C&GzN zXlA5v@oibPl<^(F%(T&c^d4KKc$i)x(S_8)p@v{g;7M|ai+5hOzHqC-^qF3V|0G_h z2v%a$T*t^Mw3P<+e<~bl!xNT|HaNhxwcD#6eRE)CiV<<79UM7@ra6T07?u#tmX)k# z0mKR3+?1x!Uy2-*i?g4&HlH_g#NzSgmcHk2%3eb<`iRz7dq;xc)NUs}-pO(&rGbw) z=D`C$X%HYb2EBbY8mq$XJ#CjAHB3vg^m$D@a%idQ=xQ!PCgYj+Y(yQiqqaafQ5Vi3XQTXi z*6+g^40!)%a%53Km@e;?C8|s!t@OeDV*4>$y0W9RiK$_FeG%O*Z>}eU{4nV-J)BVl zI{hN$J*(FQfaZ#J+9YlmAqnUTM^+!iqAF3Zt*>mXc|gJJIfsP3Nd+vx0BKceEDUPH z%0=eyK1Zb2PrH-_FCy;jW+yl(D3E1JA{5TmvW0Y}x6f2kOcsVQlJuHdyZ__WCrdk< zz5U@a+lTL{$fP@uuu5$lseeL7JbdVapoEw`6oEvYsDWvwZo%XezCi~A!4P0f+O+s%l z)Bv^xqPrHPQ}CzBTl`Lrs;|k%sv-4cKizy@oH257IfW#EWmQ;H__v(;JA#d@Kwm1{ zw}<)^zSqJc3zUq2foJu&^AbJX4BxfNpG|2X9GcPB=;KUr`V`b)5#ig1Pa$m?o6lOd>Ok1%awD^|%S@LRpM4aFXHcWbjkNhTJ8 zeV*lHIhvNGqVMx`wlyxT5JBR+x4u5tZqI=WUxdzkRKzp<@kS3{tvL#h#k;y_iZre{ z8j2!IYY6thxuNqE8$j*mYA1nJzMDojuA71Z7mjs)Bz)t#CkQXugqn^T1W{;)eO>sF z^(%^*n!{VwyHd_{@cG)cU`R!5Nz#T^hS6g!1oD{>1h3IE9eCaJ?Qr4ogmj&8x3ZIU zmsr><_1A8XV($QyG-%#qlQ@vy!#8+*&H(6s-Wuh@-cb)FtnG>;pSxYgOKW%JPa7ri z*;{qfw0784#bWn%s-JRJ^Vi-Q z%I;~@sJ#5Zqn!xN@8{4^i5?8$t0(ekf&a<{P|QGM34~jtp*2vgV%~K!#s$aT8HjA7 z9b1!B3kSk9oKsSq$7&^V=CvIio_}v!7rg|Opswiwuq`W9j|V>^EH=eS`wSW5G5iK% zU2-pU2uT@;&+Xc9DLy53R-XX!NwD0MTvbJv5;k0b_|{HWc2u`2Td;hlW!GBlm0?xv z#-X*ZiV>MK8Zam4raUsl92ZJj(DC2ryrofadaMH|;H_?OpOrVntD3YmyQ*jkrag21 zG#d8Mw5=(egLPYEQr4_H8(@}MN1fr3XZ57=IZ8bE> z%dTKAwBG)OD{%((vZY;#tfe+L+C|k#vlhly3Hv;dLo^(;PEB8OYdwhU94F%BxgIaf zH)Q1RRUY%4?E=~nA`-3L$)oynaO6ja!^IoIXXCuNbQW(ktcA%F=3Ol?PLOw)r~c6H zdQEx0TOmb`w>eM5cBwPG0V{bZ_KLXL`6jL9sB~-|4g0GkP|7Q>^^_IbzyKq#16sT} z8{K^Wz1ir-E%w*q&BdE!PfLTYEc>IW#F!~Ox|_p|z6?_+U&O4Wk<3ujxF`P#luIu(1)=axKYG5pN)wSlK4_-4M7`R4#ci!)a!e}09AguWCaL_xBY6O0kGg)A|1DaiDE#Fi({$-G|c8bk>o4d<0 zt<(w-X9W(4_*wPR+??1e&L#b9ZobAnSv}%&)_#p4H|>|Gm)jv!WSkZwnVA!kMe0_@ zrfg&FPNS)#w)kSGfhFSA>6Cpt)p|`0JDyLr@xv@Zqo%-Sgx<~Y$+UN)>v&Z|0SHZ= zV$LDe<^ju{kh^+JIe{=NuGTBxRM^5z zf+9lzY4Di&_*W+hgTb!2amTC*$e9kq^4hpBpeXa#ce`x!pZam8CF~hQznyO$ zQ3%FtBJdyc+(;&mngCW4R+m!lIAan5_`IKF14s*{!s+Um;rup)4Ud<7GU)g}{50$1 zM(q+ZQp~XZ8yj)=(#6K+CTc%u>8RoTt893JLQh4oWV-7STd({1(L!x%?FcwIUPpKl zh0ARsQ&SKjl$BU%A7x!8g$8g5mSK>-WItHnNhiwfHGuuJsEXdhg`mxQpbxomX41xXviQ3<-10JNj0J> zIH|6(HLH(&ryolgF^?&pTjVgf;2vHBeWS$Kr%hBQgr``Wq%e zZnaXG3jT_f2?4H@;4%>lI7O7uim;l*Z(?RxtyGy3HeywKpJJsL>c{)NK`e7_x_#&F zMy1jQvcSs}=`kjy<8qZiQW2})0PRBO)n0(U)9MPc!#=ULI>s|Wyh#S>JMC2NtbyF* zosN07CCMFzkT{o1N_Azz>y9E56xn!OzSoHdQQUR;vGjYKSV*4JGH5kqA>L7sA*D#424k$K19eRm3}Bd=gyQ z--eo&^L+4dWkT9J^}XbGTlr3nK<;+3@>n=^tBi_qlr`aq76nPB|PN^0QD*EnJp_%|?AaGR!^XRXlkt-eoa4JsWU z4LJ36vVu`i0@y)=5H}8ahQ~PaonVBFdP3Iu&ym0V<1#S&653a3paW0;U|V&=A+J0y zpMbKm3McAEb>rN<@$IK(YV?73p^&@nzG0PyeQ;%Y7*tkE?TVI8>cD*i^cCjlyDw6n zLDmPyC{tCMqDzavC0-|QJ6-JPDh$F@DX&Ov0THi3Mzt2R8GPw@GU49y`Y zLPh>JPkD3|&uLR=uqHSHva3h>O4Zt&{+lR+mU zzVq4{-rd5*ACs*tvA`h1nLq4IUU5^o6qLO(byqkM8p^1Z$xC0evEO_?PPIJMi5m!zP5#qxCQbwqRUIuTOuqnNvq~PfkIJejG5(K9}sesDE22& zMbB>AZQCH4RYJI9>mtha&+i{93sR@YfNQ42E8wMA;2bGx65l|tKI=qG4>c}7x z*QTJSnBsV!TTYq(lbU>951LXJ9;>?R0aC>m#R6iwD3rNPI9zDevObXF<~eTB!7-_% zjywY=p<4XdM{kAaeUgpzQHuAp%9tK_s^C4vFp8N5@?yihr=qnIIG;RKtX0AJ~>$%0NGw50ci2xi{*v4S$;B9cgo8+3HlLAOR(Df^G zOog*`I)|Q>N3&F~^5Y^07!cv;pfO5NTsy80<(G;0r4%V1(8a*0GG!?0#YjtT!?|CB z%hs4?{f}tM`D*+Hm~y^|#-EM9KKm73%>Gm)Ti4$W6eBbUeFj*?SCpc;*G;3PZ#Z|Knx5Gs^COAr~rcPl}j-(;}Ez2 z3UScU7OaX}yfn zh!2@bsqPElX@JAi&H1Ud(&_Y$;S{0n>m1g!yH)I-gH=3i6%k-o5@Y<(e9ZO^2*H%d zvymkj8%9YTz$`N4U&EjV$dq_RDW^;}3H)?k3D7ioY6;BN$97~OCfcY~zxHI`Ryr%j zhc%T;sn?2VwzP03d!E|G`7R)Ow-%imghQ-NOo)CD2ou1i`F2j7G|_j(q3en`Bf=e; zI8yAU$3*it1hBEpgyO_-D7{WQ1jSe|gHTH6lF@>P>q^q{A`r}^V*oPBtV&~c^+;5H zU`7V-qD~pazeuMHKo{+lADGc8e(*kx2k#KNYW%zLKf(Sc3PscU3O7ZOHIvnA%4*GN zgMpv)Au3!F>5^$!Y<2?l@1%_zi#g%Wc9d2sb7HppKIZ@|~COl;c zUJ6G~v6f|NFX%shNCqbd=y~vE3WAnHt!Z z>urwOxmrmTjhbqK?AgWzOSzSW=(gQh3$Z}gHVpU{j;^`+cUKEzaL7>M$E2llcIZX3 z##X|GIvRogDeVAQnuCPy%rRslvDQT}MQ|O9+G6v_CIYS3Vw)e#Cnsvm@zO21 z&mQ(7ZGeby?$hdn^cYO9Ar-?@{vhpwhfW%CL6Z*Vq=1Ncq|cxNe)!?H#WK1t7+J|>dvL7*{C-D|Dyd`S+A|p z6!wz2$9FcO@gE_oDGxyU8KHR1NTgG24RDyQyb57Ntl@6ermUSBq-o|mHAAEQ=v5@P z%g!|WFp}9#8Jop|&Dd^hwh(~oiCG=Fv$(Q3J#}?9EvmIn>1bA$t*g3DF6Ypug6Hve zOb_sgK>)*oDG`ax;PstEqg4kOU|Wf2)0eHzsGYJ3T<)0J2}t^QVFY3OY;%k6$&v6* zwDO{tfhfUarj*SoFeWkCwnPPBmV>Zit6^lw{JoP%&7s;B@M+OYybL-&$U}bab?!R8! zT77iqE<-(y-uI?>;TvS`BdnX*Pcm3k>P&+eE*qd4Aozr)5!GglDbc9G+E^nTQk%B= zW&S1l8L(%^(!tR>5;3ih9@V1)JaXD@Lz{HLAnKdFGJyN7XJx3iku~|Y%8t~kQL+WJ zOP4svdeHGXzWUbz(@q|H+-m8O57Nyt$=5@AF)!?}Q(*p@=IT&FLE?57v)y+h6m^*Zb*excD7EUz7$gJwwO`J{G*#Flsg z*D2QuThU#-6*R>dK0cM;`yIwx1=g8@HaJCH7y6It5~8{e#s{H-ADDHPPCdYkVJ5y` zbX|c~-iYVdxd-7*`hJ~r+;Aw~j`x>t%E}ye(>m_991#qIn%tfH?1gkWtQBtKD@i%4 zEhia$AugtaLn$&^$(tFZeSWL|drk`_<7o6sgofbInlKb>9zAlhC(6Sf+r(g`DFN@h zPw_>{n3nYy9z>@$XsX-!&tyB`?vf*0j}aA&(0V#Dv#ZTb}l) z4~6zHcv{rV7@}FJT@Ie&W1Q1UsqU^G&ohz#UtlJcwNdQdjeyYl~g z8_!$G|My+wDIKd4f zCXnQ2at18SV`Fk&mHaiN8>MM$sM~?RNmXKM#&82TUtdFcXAD3k`DXI)<{Y}@N#JrZ zY{Lm;JXn_|j`3n|<%^lIJS6-L(n4$`Z7+zxa2my?W)deBWvjX$#6^NVie^GV9-Yq> zgFEtEE)Xq*ExNDfPnYKAUM-w1y;?l2L0)PbzLEN6HuM?7tj&tBN#+-rUW|zS>~@j^ z1*bIvbL{Qh{4_G8oit_w8Y{sgy-Y-1Es!d5a>oq;<$wz3l<9j?$-Mnu7|Cf0y5UVU zv%St{0OQK_%NxOQ;Xcai7MZijc(jzm4}0*o1;u; zs#IF3>cZkIl_o9|69`-%&$EbEF)8}iL}DxCDhg;$pB#PlkE&^tIt+mE{Z4x?}{GZ1E=lFkR|Ne*Z|1$pH$Nv$2H+J%CwW7|kp&aIBzoIzrcQVE# zP}fBSYe+YN&M;ai0uKXDM+qx$&+kr1Cje7Kp>1$H+2x$xTxqvOjp8zO&{cMrrh;0E z$Ae2W>)=`;P%Z$2!MXEjeP!*T!^2ha(IEWTxK0M}-xjjREAmEow|&IvlA9NF(2fog zgUx&q@g?BMFGukShW?wiKWy(}0D3Kfgzud?CMc#Ehl;~2onU3mW8|e46S-(aTx-8o zaV7?eHqUuXAk4Arq{;ph&h6rZ>bCx-X72XSc6N5J?d;T>*BUdX?=c0mj&9ib_5!&k z5yD2+qesG_!sV>5pjjvD=Vto}P%*gPLgdtPJ5lh&nnB?^?s+D6k|kMJqueK!SZ5k> zwsfv^DRYN#3QaHTmZUl^r1MElX;lGn$d2$31T3H*XWi?IPvuSQ!b@t=YeTGc@*FD23kMua8Twc?X$Vr? z!s`gTB}Y3Jy7>vxbT(47CR3e6w4DSPJs5RosKmQywsA(6K9~y*Wj3^P}JPWhywL(fWMU;rH}Ua^K{6B4-@D) zF|&zl*iC^76b)!+1gzcu;r%q}3=c17P2ms?9j+n-Zri zar*O7HqgiZp#o^S!8|@bQ8ANZh9Bad4u+S($)o2`nlHh;94u_>jFlj=@G>E!wGU;j z^_gY}W!Abcde77NG1Gk4)8S!{?Rmt%jNDQDWROME(!w~mhqTJ)6w&55WB&K%Hp}M}5Yw2Ki=^17aD|@43r3PrnrK7Wad;T!UrLP5B0o z6L#F4-mG&&3Eh#A@Yqt7&-gCX^p@X*LmMg6@1%Jv#dB>{uHneB_+B>1nTR%3Ox=!4 z1ILtMP#C9{G(g_#^m>DOU0uv;G+E!f8KgH#IslU`hOxH|R&c1|SZYQxe0F9IliZkn z6mNnQw{p=JaPDo){)Pg$(mm1OJp926_MVGno2vm*Ry4FtOJ^Byu;PU(;GKq5 z*wpu$F&5u4*K55R$!$khOn3~Ywi?sl^icvSAB1VDAe6aOkshH#Pm=(JBHj=s(lM~k zZpl-De+3r)%Uo|!=Q($6LYrGn{$;GtYjJ-aZ+ky?VZI6=V}6AEJA< zy>24Q6J(5q5id~kd)%IwLkhVHF=bR!0Mc1mXh(p=w{Gn6>q`&P?tvAL#OVqzC|`mM zU>`1h<KLR=qr|q$?aXd6*2K{==TVuOoOhnY!HXu^m)pQd7DZj>RU$!A~y5;YXc>4-hIN zLM^DM^c;Y5yDb5U_l3eo>Ntb`VbV?8MNNAo9w=9rftkbtb7B6%kA{-Olc{CwCwIQ5 zv_*rT*=PRBK%h}w@e;O)8N(t`LE-wjn(527pjV&O4Ad%vvkaw5GIFH0f(y*q(W^wDG=3bM@$^5Rjpp^dXA=W$2_}#g(^z zjDtGYY-tASLpwA-Y*F=LZ%p^kY`y=v6^RfY9_Wv2KUQqLl9jn62B1dlw0xTmk7Wde z>fSTjYPM9>`m)BvTM%sZ!rY~obNfkE%G`oNJWk7UR2%=7vro=`1>r+5wDo6azaD>n z_KABVA07}hDpQr|fI*+$^Kjdz&YPC4l=5Qkz^`@pd){_krNyq}FbuvA2V{i5xIU#m zcoaZthI9MoQ#|Z$k)blwkZ`Y%@erxabO={dtGNsjUm@?15ln<2G20OJONMh|0#ukE zWNxUP|9ql^t=_rT@{vpH?uw${xFe8rzO3^Tb8Q1AvW)Pnu6l1;ydv0_7YYs z<$A%_C@%_Sicjg$DQ63NZtLS}iHzq{OfRDQr9;DW_iJ+40lCz$E?!Z<0ZCA?L62Y{ zJ1Oo+Fe4(QOs3c z`kFV&MM=_QzN#rj6O$jCFzxFsZa@VrubIr)*eR7ZC#XU%g^W_}I=X(qgD2KJ>jrq~fu3lR%@1)j?%Dd;>z^c+O=>;zKlHUs`=%u)%=aUTHLw>HJtGEQUDAuQj^$9`?V5@j9x>G}i zOW4yVhXF=&0O6iHg~+3BBw7~~W6x33NA-e5Q#s&jf|oE%+18y7hG3YYVE{0_eqLo1 z>|h8J4uHS<8>hWreK-f*fz$hJeH=o1?X1O!FS?R+?%HN#ZICpouuXy3r+l1s`7C0) z`5aOPN452=HR$EN{h`QXoHLz7SDhox)dI%Va`c21&%&v>CNDj*PD)dx>h6zvdt2GC zqjJWUbXDY$)_xbtY1=Sd)b9WI!TQq9ChPBUGDvr@T9)QJiVQn~4AH}_9i&8xmq9t( zZXWiIC1Puwln~Xfm@=WB)cN*_px+F@$0Lk|hD9c^Mj}V$#D8>>Y%z%<{c{mF*(Iugc2_A$f7LwqRc@|$jSoRTp zy_$G0K~P?r_G4vVr-l>%i{M)#5-Z>;wn&#r#Ne)L61?m^F=7VLgPUb~17-p3sPbgO zPJKWHa{a4ZUH1hc;r zx2-@p3ast@N~GStI1KPnhj`7HhXmS10k95F@q(WyKmmHVV0tIijc>*UErN(NZ21uB zWcg5@k6Xayxfp(;7IxxdCBC|@0DO8sZOT(aEr*^qkyBBZNf(+LV$ zR?(EDxZ5p9gY5qcv7mN>>PX-oX)EodN9oX;EDT%ITTixWB0I+7{i&+|=b;^Eco*ki zr<0gu%C34<-lOnc5LnFeU#9Q3Riq~Pk$ zNOvbWIZt%7%juedg&Q7C12fSL_6mBxdYE*(?E5!5UJ{8KKXmtxlPu(%t@{|?5`E|D zl~;&f4o_YdV4EW@6JataF4)pRaxY`TL+ucJo7oI!UF5lv*bc;(u@JW*5C==-=Zk7e z(}o{n$$~%ms=-T8(G7113m1k-Fr(!o-ePSq+~@Fv3Zrjvqx_DTLtRBx`Gs#ud|y=G zykA9Uz2A8613)P`yhZfj_MT5^+rZn8Vn0#*_mV_7p*D)+zt1l$-nwDMf4@0@()!w7fAojgeO5C;eJvAybg>C99~o zvDMQ+*e6&Zl8*Y?8yv_i82mzoNF?b;C>`|AhY+h6LTE2DzEnb{n~sJ|{yk;?$-u1O zK}r-U!Rt7CuAA?sLyFk*J>yFy#MoMwgYNq@@3k`8y24AAC_#+ZFF;@l{<=!_ahcC# z^1m>)d$5gS`M-Gc#`Wu#{J-(u^{f0JZ{>Ld**<1XX!7(xyvB{yhGFfW2My?*2$OF_ zPqyyG?_ZuE9txEw-0O{NrO{8OZja&}A!>G|tW4wPFuZ)I0<96Wo9(xaW{qVaL%u&45 z!LMpHon=o(M3jED`1+k_3o>;h?h$rtA8*&y+icszUS(a(^b_SN({dToxi=#4Xt@7A z?|)>(@ucJf;F5%Oeyl$$+#3~~;Y&PH(BPUsOo!r#i+0%V5V-!nV(2Z`pla>IXvkVz z25_M;s=V*Aci3^&#SfzlBZi8qgifm)@87zGN!V9678Y+VXj(9I`tU8xGpr}7`>bT+ zL+mcE!z;U;{V>o=EKa-0ex6c>H1GmUD?siK+e>zfK0`f{i- zH)fT=9Gfl1FT59UJr9y!^9VP+{b==Mkaap)E9#^}a4-1~F2pez5FKU*haeu1oA-FW zaI_zN$bWL6%Tc_bjDsRZDmwaV3K%XC2&L5o>Z_MpKnw#xmuGU(*t|_Y`Jui&5TQ9m z5Zn69aJIJ7f3=p&9-h`3(kP*6rXiE&xYG)Lz~BX{yLfIdY0A!-=3M@U!!FUkY? zXz)Myg0#?Mbq`_!D<)eK0#C=CRg{x3G@-ZWcR69*P_dW*$`I6Y)zh&{%Tpah8mdwOO#Hye%BSzkyZ4Ou z3-bN#+s6Av_5PlFB|V*J!ta@@3JF%`;JoO^`Cb6$dv_m%o1i3OM66`%fp-33)H;MXr^98YG{x`NqWE^NeIjvl zhL4Rg)3oD<^s+w`>y3^8v$sCj3=Q~QLQ1$eFF4kTMYN9|C4` zJQAj19rcGg+S~CV6Di>IDnCGmi|dBMBtKodrr+T?mn%dnP#v*CdYCISLRw8-@) zVy-Vn?`pgZhpacw8ex_&XWBk$Q7t&y7pF)TW6(+Zc+>^Y|G^37L6A2$2U%(zIeqjB zbQHFSIBMe+`43kH2l;YsuEir`MsTaMb~;FP4VMRHbmzGDaio)gD;BP`!fx9o$)(8W6cw*9E$yJaw>BAm( z&Q}nvf{avT3H4BsEu=i14*<2I)iEKQCP&=gqt|MheQv_c*~AiNt)tvKf=szyPmh?C z_tn`O?eAmu4^odCaiSaZAKWqu3vmU3qmwrGn+1SP(Anz(((L@K_)93KjfRR}02nrL z-qO+ltYM9~-V$Q}KTfhCIKRKlhV}R7=jXi{Zp4ldEBu)4_`o&5v^mfx5J$+QjnIPt z?XBL*V33?h%akf3lv39$#{U*TJd*&GSk%nnPt5LAr`c#^5uP1sdS~#1o$hqQ7n}K*AcY(0gEkGU zt_PQwBO78_n1oaWNz+bWsx|YS=;5QSyGs#7J7=mn+Y0hCz-VQ2b!{!~bvq}ZqqCY| z#dVpvQ-4mw`*s9}+g0+`bFddQ8l)J!Lamfuu6T zV#4>vIfz)aJ}^Rn4W%pgmplgybkX%P=I?QvCX)2*?CJbkun>V1eGP9KJA3FBV-jOQ z(g-Zj^_fh#v5Nv9M(hnJ6SRGJdF`4RI33b;EK4-EXR=*mW0k-AecVzT)D=UcVjAWOPKF(u!L(CW#y zn+KgNKa8e%$jfOw40t?Z*d^U)di?45_h+AszZie^?z9qYkV2)6HZm#KngS##Kg|%? z8O`6gK9B!hf&boE#J}Ii|1MHO)fwWF2RS0(L55;Ve<0uz7hKIv9kiNsd)=&+bVSG* z|7O}^1aU;@8Mu*)@W0bmpB9Ulao#_ zX|q)cXtS}oiX-&MJ3>0o7^cZSHa@ncFvab@5G-biWSB?_mb3&#ymfG#v5rmD zDx87v^DrM6%w`}(K9#VKSz1K`dk^6>$6UFYUp-gP)pPY+Jy*}wbM;(3e}d2d0|p>mcK~1s0G=fsEC2ui literal 60352 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 F1OUL`$+rLi diff --git a/install-kit-awindows-20260427-211240.zip b/install-kit-awindows-20260427-211240.zip index 02d46371c24456e9417a4e97ae4e1d48b217fa08..5d503e46017612800d13a7c4352bd865b6517c59 100644 GIT binary patch literal 148222 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 KxCBUbp#K4K;)Hep literal 81144 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 JCSd4-{sVg1p?&}W diff --git a/install-kit-awindows-20260427-211240/MANIFEST.txt b/install-kit-awindows-20260427-211240/MANIFEST.txt index 253acb5..6f2a380 100644 --- a/install-kit-awindows-20260427-211240/MANIFEST.txt +++ b/install-kit-awindows-20260427-211240/MANIFEST.txt @@ -1,41 +1,47 @@ -0754dcba7c651d67a40e09446868d2fcae623a100d4fb01794dd96272d353b49 install-kit-awindows-20260427-211240/README-INSTALL-KIT.txt -089595753398c8b82980919d230dafac548c3ba36817f5c96a68051582f9faa3 install-kit-awindows-20260427-211240/ansible/README.md +f3dde1e6d1532804379faf7e395deaf95cf3e0b97769d425f8a69f4572de2a2f install-kit-awindows-20260427-211240/README-INSTALL-KIT.txt +a08ccceada7a21e4396a640e54a354e4d4d760980ec3f18f1bc7f543cd8f4cc6 install-kit-awindows-20260427-211240/ansible/README.md 412bb766bbf0791c3593f38daa771d5d0aa58cc1f2d3c9010fcd4588d0fe87df install-kit-awindows-20260427-211240/ansible/deploy_aw_pfsense_poller.yml -90ac38a33918fcd3620f078f51fbf7c6a9d7f8fd1a34d16b38cb3ac45678b0d7 install-kit-awindows-20260427-211240/ansible/deploy_aw_server.yml -a649eeb57472fb259d248983c486f0474bfb7317600feb98f76a80e7af7e4549 install-kit-awindows-20260427-211240/ansible/deploy_aw_windows.yml -531bfec24f86d28a06e5c0d73005489a818e2c7b1cce1d98f524a3f76802b8ee install-kit-awindows-20260427-211240/ansible/group_vars/all.example.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 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 -7c468f252e328fd3bb7ee776a45feea88efc4b438dfef55b832da4eea867aaf2 install-kit-awindows-20260427-211240/ansible/group_vars/windows.example.yml -195e7dbdb91f4e77db3263bd0812301ddc912a37ba1519688768bb64a2887567 install-kit-awindows-20260427-211240/ansible/install_full_stack.yml -2e4e94d90143923fefd3ec1257d0ec57daa3e96450d85471bc2c418aae37e105 install-kit-awindows-20260427-211240/ansible/inventory.example.ini -d9e43352fd6bdb647db9754ab2c557b6bb27f88f51b2bf23d8c19227e535e7b9 install-kit-awindows-20260427-211240/ansible/provision_proxmox_ct_and_deploy_aw.yml -f3d34547f345ad1c635ee44613a60e7f08479f9ae4d1111810bc7e5968bfb084 install-kit-awindows-20260427-211240/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml -ef4ed198745777bd1227170241b2db21dc853685f962d6116241c55216b466d6 install-kit-awindows-20260427-211240/ansible/tasks/provision_ct_and_deploy_aw.yml -a50dbadbf619342c2178e255b68f69a36503756daf80eedd9170311c63964f2e install-kit-awindows-20260427-211240/aw-server/activitywatch-server.service -2dbf55d4a8f204ebdc97af926d90435932c0aad7a2431e2b9987e47c5abf71a9 install-kit-awindows-20260427-211240/aw-server/apply_webui_ru_patch.sh -07d4e583f6e9757a11f01558e1f15cfd73c4d82695f1768204f2f50621712168 install-kit-awindows-20260427-211240/aw-server/aw-host-groups.json -09605da1754abb0dc0446825580b57ebad6e646dc670f9f072fce1489e88dd43 install-kit-awindows-20260427-211240/aw-server/aw-ru-patch.js -7c5952f8f0a8590e849ea8381bfcd7059b138250bca8551bd5625f395eb66cd8 install-kit-awindows-20260427-211240/aw-server/aw-server.env.example +5da847b74fac52e0fec2f60e134f4377cfa1581e026b291d3d3ac362371f6e49 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 +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 98c0bed353bbda0fa7a69df23f3b008cb0e8e70cdff6cc63330d4caf79fd3280 install-kit-awindows-20260427-211240/aw-server/aw-sw-cleanup.js -dce731fdfdcfd773c154d12dbd6b9e621a0bff17ced5a05a65f0fdcb1adcb70f install-kit-awindows-20260427-211240/aw-server/install_aw_server.sh -1856e9f44636030b0cb9ece37ba2a0618eb5187fa82c7969976c1bb5f10fc622 install-kit-awindows-20260427-211240/aw-server/settings/classes-worktime.json -ff07b90cb6a7f09b27d522307cf55b0359e136a2e695190b8564e859f14f9204 install-kit-awindows-20260427-211240/aw-server/settings/views-default.json -1654cf688560465fcce629468a0be869b0c81b056179e1b5e9bcc7a2d5ed6ce0 install-kit-awindows-20260427-211240/server-configs-192.168.100.21/awatch-rus-admin.deployment-config.json -ac022b9a074c542ade66d18af8db385f6c14376ac4eadd54ef033dfa7f60fb50 install-kit-awindows-20260427-211240/server-configs-192.168.100.21/awatch-rus-u2u5.deployment-config.json -98cf4c54d494318b74cfbd3c8892830a34928bd76a2296d5333e5e176c1f3f49 install-kit-awindows-20260427-211240/server-configs-192.168.100.21/awatch-rus-user1.deployment-config.json +d8d6be450a726f51f87415eaebd67396468c73ddda9b942481d56b4d33d68bb7 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 +aedffecfa24834968742cb2477faef80bf794345275a9679ac12c5a1f609acc2 install-kit-awindows-20260427-211240/aw-server/settings/classes-worktime.json +38fd98fe5816fb87055a9ad1fc570a65052785c829ecd8359fe8305949ab32ca 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 -0cb9cd8d8b612429f79899c126f4141ab4fbbb6d919425c0b8fd8d0a74b1bd44 install-kit-awindows-20260427-211240/windows/ActivityWatch.Windows.Common.psm1 -7db2d3767ae81c877e8f04ecbc77a2fc26b2d9bbbf77d0e774fba0a7956bd0a6 install-kit-awindows-20260427-211240/windows/browser-domains-native-collector.ps1 -b497400a1ba57cddf28dc8e217115dc85eccb67150cbdbb6a81abd804ed20109 install-kit-awindows-20260427-211240/windows/deploy-domain-users.ps1 -973db51854fc744539a7b75e13c6749e822a08a79f47447485611f07e8f902a8 install-kit-awindows-20260427-211240/windows/deploy-ensemble.ps1 -0d66dcb551889e6b7bc21b29d53b77e46f41d61dd2e4e0d9913dbf0f8bd5eb18 install-kit-awindows-20260427-211240/windows/deploy-single-user.ps1 -8857f3e17f3f3ed6f211ce7f0a0c46c586a2548541078401ee3befaa20924b3d install-kit-awindows-20260427-211240/windows/dlp-endpoint-signals-collector.ps1 -7362e1aecb56d8863b8b2542f262f28827febf9b25a2a14b382750dc6368664f install-kit-awindows-20260427-211240/windows/email-outbound-collector.ps1 -aef0032edd9b1e0c54f7b575664ed511dfc6cb53364e7496cbc95e137678e11a install-kit-awindows-20260427-211240/windows/dlp-policy.example.json -f03886caf56c6838e8a163d6b48d1f229e83a5682aeeb447c3a65f13d62dbca4 install-kit-awindows-20260427-211240/windows/hardening-recovery.ps1 -71911cd53ad0abd8bf83994f8a79bbbfe2c0eaf4f4c5f6c2d636dd7786191c2f install-kit-awindows-20260427-211240/windows/migrate-awatch-rus-paths.ps1 -5dcf249742bd82fa0c803c878bfa0a1344b7b14df85c05f12e8c205663aea158 install-kit-awindows-20260427-211240/windows/validate-deployment.ps1 +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 +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 +5ef21a25d5e2da4eeaef17126e60f96f195f90f9dc17a776f8629334b904d096 install-kit-awindows-20260427-211240/windows/migrate-awatch-rus-paths.ps1 +d01edd14b2c839ae171006fd3345dbddf1b683b1adcac885b6eabc52e3baeb79 install-kit-awindows-20260427-211240/windows/validate-deployment.ps1 731098681d89b9af6f3872abd586ac3b1faba2d7f9340211e503f52ad0243b3f install-kit-awindows-20260427-211240/windows/web-category-rules.example.json -41171f0d7ed1e8b00dd0faf1a4b75c9cb063fd09aba8d333851a7a31fb297de1 install-kit-awindows-20260427-211240/windows/worktime-session-collector.ps1 +945ccfffd56697ed328b82e82d1cffbc83fa4e50a6120b27e3948f3d13fa8a33 install-kit-awindows-20260427-211240/windows/worktime-session-collector.ps1 diff --git a/install-kit-awindows-20260427-211240/README-INSTALL-KIT.txt b/install-kit-awindows-20260427-211240/README-INSTALL-KIT.txt index c59015e..8435107 100644 --- a/install-kit-awindows-20260427-211240/README-INSTALL-KIT.txt +++ b/install-kit-awindows-20260427-211240/README-INSTALL-KIT.txt @@ -3,8 +3,8 @@ ActivityWatch DetMir Windows Install Kit Includes: - windows/* (deploy scripts, collectors, common module, configs/examples) - ansible/* (Windows and AW server playbooks, examples, inventory, tasks) -- aw-server/* (server installer, RU patch loader, host groups, default settings) -- server-configs-192.168.100.21/* (working Windows/RDP config snapshots) +- aw-server/* (server installer, health orchestrator, RU patch loader, host groups, default settings) +- server-configs-192.168.100.18/* (working Windows/RDP config snapshots) Source: - Local project snapshot at build time. diff --git a/install-kit-awindows-20260427-211240/ansible/README.md b/install-kit-awindows-20260427-211240/ansible/README.md index b22fc6c..b8fbe87 100644 --- a/install-kit-awindows-20260427-211240/ansible/README.md +++ b/install-kit-awindows-20260427-211240/ansible/README.md @@ -14,6 +14,8 @@ - `ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml` — массовый полный playbook (несколько CT). - `ansible/deploy_aw_windows.yml` — WinRM playbook для развёртывания Windows/RDP collector'ов. - `ansible/deploy_aw_pfsense_poller.yml` — развёртывание pfSense poller'а. +- `ansible/deploy_grafana_dashboards.yml` — импорт version-controlled Grafana dashboard'ов через HTTP API. +- `ansible/deploy_tsj_guardian_bot_proxmox.yml` — развёртывание TSJ Guardian Telegram Bot на Proxmox host. - `ansible/install_full_stack.yml` — полный установочный playbook (оркестратор всех этапов). - `ansible/inventory.example.ini` — шаблон inventory. - `ansible/group_vars/*.example.yml` — шаблоны переменных. @@ -31,6 +33,15 @@ cd ansible ansible-playbook -i inventory.ini deploy_aw_server.yml ``` +## Секреты (пароли) безопасно + +Рекомендуемый способ не хранить пароли в репозитории — перед запуском экспортировать их в переменные окружения: + +- Linux `aw_server` (SSH пароль root): `AW_SSH_PASSWORD` +- Windows `aw_windows` (WinRM пароль): `AW_WINRM_PASSWORD` + +В `group_vars/aw_server.yml` и `group_vars/windows.yml` они читаются через `lookup('env', ...)`. + ## Полный установочный playbook (всё за один запуск) Если нужно прогнать полный цикл одной командой: @@ -45,7 +56,8 @@ ansible-playbook -i inventory.ini install_full_stack.yml - `provision_proxmox_ct_and_deploy_aw.yml` (если есть хосты в группе `[proxmox]`); - `deploy_aw_server.yml` (группа `[aw_server]`); - `deploy_aw_windows.yml` (группа `[aw_windows]`); -- `deploy_aw_pfsense_poller.yml` (группа `[aw_pfsense_pollers]`). +- `deploy_aw_pfsense_poller.yml` (группа `[aw_pfsense_pollers]`); +- `deploy_grafana_dashboards.yml` (группа `[grafana]`). Пустые группы в `inventory.ini` безопасны: соответствующий play будет пропущен. @@ -87,18 +99,29 @@ ansible-playbook -i inventory.ini provision_proxmox_ct_matrix_and_deploy_aw.yml ```bash cd ansible -ansible-playbook -i inventory.ini deploy_aw_windows.yml +AW_WINRM_PASSWORD='...' bash ./run_deploy_aw_windows.sh ``` +`run_deploy_aw_windows.sh` автоматически: +- очищает proxy env (`http_proxy/https_proxy/...`), чтобы WinRM не уходил в локальный прокси; +- включает OpenSSL legacy provider, если на хосте отключён `MD4` (нужно для NTLM в pywinrm). +- перезапускает `ansible-playbook` при временных WinRM/NTLM сбоях (по умолчанию 5 попыток, пауза 30 сек). + +Параметры retry: +- `AW_DEPLOY_RETRIES` (по умолчанию `5`); +- `AW_DEPLOY_RETRY_DELAY_SEC` (по умолчанию `30`). + Playbook: - выгружает полный `windows/*` toolkit на целевой хост в InnoSetup-compatible каталог `C:\Program Files\AWatch-rus\windows`, включая DLP и `worktime-session-collector.ps1`; - если найден legacy config `C:\ProgramData\ActivityWatch-Phase2\deployment-config.json`, выполняет безопасную миграцию через `migrate-awatch-rus-paths.ps1`: backup, остановка задач, перенос данных, переписывание путей, пересоздание scheduled tasks и validation; - выполняет `deploy-ensemble.ps1` (deploy + hardening/recovery) с policy/rules из AWatch-rus toolkit; - после deploy принудительно запускает `ActivityWatch Recovery` и все `ActivityWatch Launch *` задачи; -- выполняет API smoke-check bucket `aw-watcher-afk_` и ожидает свежие `not-afk` события; +- включает (`Enable-ScheduledTask`) `ActivityWatch Recovery` и все `ActivityWatch Launch *` задачи перед запуском (иначе WebUI может показывать `Active time: 0s`); +- выполняет API smoke-check bucket `aw-watcher-afk_` и ожидает свежие события; +- выполняет API smoke-check bucket `aw-watcher-window_` и ожидает свежие события (по умолчанию включено); - запускает `validate-deployment.ps1`; -- забирает JSON-отчёт в локальную директорию (`/tmp/aw-rus-validation` по умолчанию). +- забирает JSON-отчёт в локальную директорию (`/tmp/aw-rus-validation-` по умолчанию). Дополнительные флаги: @@ -116,6 +139,9 @@ Playbook: - `aw_windows_migration_report_remote_path` — JSON-отчёт о миграции на Windows-хосте; - `aw_windows_package_version`, `aw_windows_package_url`, `aw_windows_package_zip_path` — версия и источник Windows-пакета ActivityWatch; - `aw_windows_api_smoke_check_bucket: ""` — автоматически использовать `aw-watcher-afk_`; +- `aw_windows_api_smoke_check_window_enabled: true` — включить дополнительный smoke-check `aw-watcher-window_`; +- `aw_windows_api_smoke_check_window_bucket: ""` — переопределить bucket для window smoke-check; +- `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-скрипта. @@ -138,6 +164,68 @@ Playbook: - пишет `/etc/aw-pfsense/poller.json`; - поднимает `aw-pfsense-poller.service`. +## Импорт Grafana dashboard'ов + +1. Подготовьте inventory и vars: + - `cp ansible/inventory.example.ini ansible/inventory.ini` + - `cp ansible/group_vars/grafana.example.yml ansible/group_vars/grafana.yml` +2. Укажите в inventory группу `[grafana]` и переменную `grafana_url`. +3. Экспортируйте пароль Grafana API: + +```bash +export GRAFANA_ADMIN_PASSWORD='...' +``` + +4. Запустите: + +```bash +cd ansible +ansible-playbook -i inventory.ini deploy_grafana_dashboards.yml +``` + +Playbook: + +- проверяет `GET /api/health`; +- создает или актуализирует folder `AWatch-rus` в Grafana; +- импортирует dashboard JSON из каталога `grafana/`; +- верифицирует доступность dashboard'ов по `uid` через Grafana API. + +По умолчанию импортируются: + +- `DetMir: Работа пользователей в RDP` +- `DetMir: DLP и ИБ обзор` +- `DetMir: ИБ сводка для руководства` +- `AW-rus: DLP обзор` + +Подробная документация: `docs/GRAFANA_DASHBOARDS_RU.md` + +## Развёртывание TSJ Guardian Bot на Proxmox + +1. Подготовьте vars: + - `cp ansible/group_vars/proxmox-bot.example.yml ansible/group_vars/proxmox-bot.yml` +2. Заполните минимум: + - `telegram_bot_token` + - `telegram_allowed_chat_ids` + - `tsj_bot_source_local_path` +3. Убедитесь, что в inventory есть группа `[proxmox]`. + Для текущего контура AW-Rus bot ожидает Proxmox host `10.10.10.2`. + Рабочая модель для этого контура: `igor` + `sudo`, а не обязательный `root` login. +4. При необходимости задайте recovery-команды для AW-Rus: + - `tsj_bot_aw_rus_worktime_heal_cmd` + - `tsj_bot_aw_rus_dlp_heal_cmd` +5. Запустите: + +```bash +cd ansible +ansible-playbook -i inventory.ini deploy_tsj_guardian_bot_proxmox.yml +``` + +После актуального production hardening: + +- bot различает `worktime idle` и реальную деградацию; +- bot поддерживает отдельный `AW_RUS_DLP_HEAL_CMD`; +- redeploy не должен терять runtime env-ключи, связанные с proxy, FS checks и AI escalation. + ## Результат - Установлен ActivityWatch Server. @@ -149,3 +237,13 @@ Playbook: - Для полного сценария CT создаётся автоматически через `pct create`. - На Windows/RDP host развёрнуты AFK/window watchers, browser domain collector, DLP endpoint collector и worktime session collector. - Проверочный JSON-отчёт Windows playbook должен иметь `overallOk=true`. + +## Prod rollout одной командой + +Для ручного запуска с dry-run и логированием используйте: + +```bash +bash scripts/prod_rollout.sh +``` + +Скрипт попросит `AW_SSH_PASSWORD` и `AW_WINRM_PASSWORD` интерактивно (ввод скрыт) и сложит логи в `.rollout-logs/`. diff --git a/install-kit-awindows-20260427-211240/ansible/deploy_aw_server.yml b/install-kit-awindows-20260427-211240/ansible/deploy_aw_server.yml index b136df7..46f3eb7 100644 --- a/install-kit-awindows-20260427-211240/ansible/deploy_aw_server.yml +++ b/install-kit-awindows-20260427-211240/ansible/deploy_aw_server.yml @@ -20,7 +20,11 @@ ansible.builtin.apt: name: - curl + - python3-venv + - python3-pip - rsync + - tesseract-ocr + - tesseract-ocr-rus - unzip state: present update_cache: true @@ -54,6 +58,13 @@ - "{{ aw_server_webui_dir }}" - "{{ aw_server_webui_dir }}/js" - "{{ aw_server_data_dir }}" + - "{{ aw_server_db_path | dirname }}" + - "{{ aw_server_data_dir }}/.config" + - "{{ aw_server_data_dir }}/.config/activitywatch" + - "{{ aw_server_data_dir }}/.config/activitywatch/aw-server-rust" + - "{{ aw_server_data_dir }}/backups" + - "{{ aw_rus_health_state_dir }}" + - "{{ aw_rus_health_validation_dir }}" - "{{ aw_server_log_dir }}" - /etc/activitywatch - "{{ aw_bootstrap_dir }}" @@ -74,100 +85,149 @@ - "{{ aw_server_webui_dir }}" - "{{ aw_server_webui_dir }}/js" - "{{ aw_server_data_dir }}" + - "{{ aw_server_db_path | dirname }}" + - "{{ aw_server_data_dir }}/.config" + - "{{ aw_server_data_dir }}/.config/activitywatch" + - "{{ aw_server_data_dir }}/.config/activitywatch/aw-server-rust" + - "{{ aw_server_data_dir }}/backups" + - "{{ aw_rus_health_state_dir }}" + - "{{ aw_rus_health_validation_dir }}" - "{{ aw_server_log_dir }}" - - name: Скачать архив релиза ActivityWatch - ansible.builtin.get_url: - url: "{{ aw_server_download_url }}" - dest: "{{ aw_archive_path }}" - mode: "0644" + - name: (Check mode) Пропустить установку релиза ActivityWatch + ansible.builtin.debug: + msg: "ansible_check_mode=true: download/unarchive/install of ActivityWatch release is skipped." + when: ansible_check_mode - - name: Распаковать релиз ActivityWatch - ansible.builtin.unarchive: - src: "{{ aw_archive_path }}" - dest: "{{ aw_release_dir }}" - remote_src: true - extra_opts: ["-o"] + - name: Установить релиз ActivityWatch (download/unarchive/install) + when: not ansible_check_mode + block: + - name: Скачать архив релиза ActivityWatch + ansible.builtin.get_url: + url: "{{ aw_server_download_url }}" + dest: "{{ aw_archive_path }}" + mode: "0644" - - name: Найти распакованный каталог ActivityWatch - ansible.builtin.find: - paths: "{{ aw_release_dir }}" - file_type: directory - patterns: "activitywatch*" - register: aw_release_find + - name: Распаковать релиз ActivityWatch + ansible.builtin.unarchive: + src: "{{ aw_archive_path }}" + dest: "{{ aw_release_dir }}" + remote_src: true + extra_opts: ["-o"] - - name: Найти бинарный файл AW server - ansible.builtin.find: - paths: "{{ aw_release_dir }}" - file_type: file - patterns: - - aw-server-rust - - aw-server - register: aw_server_binary_find + - name: Найти распакованный каталог ActivityWatch + ansible.builtin.find: + paths: "{{ aw_release_dir }}" + recurse: true + file_type: directory + patterns: "activitywatch*" + register: aw_release_find - - name: Найти каталог WebUI - ansible.builtin.find: - paths: "{{ aw_release_dir }}" - file_type: directory - patterns: - - aw-webui - - webui - register: aw_webui_dir_find + - name: Найти бинарный файл AW server + ansible.builtin.find: + paths: "{{ aw_release_dir }}" + recurse: true + file_type: file + patterns: + - aw-server-rust + - aw-server + register: aw_server_binary_find - - name: Сохранить пути распакованного релиза - ansible.builtin.set_fact: - aw_release_extracted: "{{ (aw_release_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list | first) | default('') }}" - aw_server_binary_path: "{{ (aw_server_binary_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list | first) | default('') }}" - aw_webui_source_path: "{{ (aw_webui_dir_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list | first) | default('') }}" + - name: Найти index.html WebUI + ansible.builtin.find: + paths: "{{ aw_release_dir }}" + recurse: true + file_type: file + patterns: + - index.html + register: aw_webui_index_find - - name: Проверить, что компоненты релиза найдены - ansible.builtin.assert: - that: - - aw_release_extracted is defined - - aw_release_extracted | length > 0 - - aw_server_binary_path is defined - - aw_server_binary_path | length > 0 - - aw_webui_source_path is defined - - aw_webui_source_path | length > 0 - fail_msg: "Не удалось найти бинарный файл или WebUI в распакованном релизе ActivityWatch." + - name: Сохранить пути распакованного релиза (binary + webui index) + ansible.builtin.set_fact: + aw_release_extracted: "{{ (aw_release_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list | first) | default('') }}" + aw_server_binary_path: >- + {{ + ( + ( + (aw_server_binary_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list) + | select('match', '.*/aw-server-rust$') | list | first + ) + | default( + ( + (aw_server_binary_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list | first) + ), + true + ) + ) | default('') + }} + aw_webui_index_path: >- + {{ + ( + ( + (aw_webui_index_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list) + | select('search', '/static/index\\.html$') | list | first + ) + | default( + ( + (aw_webui_index_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list | first) + ), + true + ) + ) | default('') + }} - - name: Создать каталог установленного релиза - ansible.builtin.file: - path: "{{ aw_release_install_dir }}" - state: directory - owner: "{{ aw_server_user }}" - group: "{{ aw_server_group }}" - mode: "0755" + - name: Сохранить каталог WebUI (dirname index.html) + ansible.builtin.set_fact: + aw_webui_source_path: "{{ aw_webui_index_path | dirname }}" - - name: Установить бинарный файл AW server - ansible.builtin.copy: - remote_src: true - src: "{{ aw_server_binary_path }}" - dest: "{{ aw_release_install_dir }}/aw-server-rust" - owner: "{{ aw_server_user }}" - group: "{{ aw_server_group }}" - mode: "0755" + - name: Проверить, что компоненты релиза найдены + ansible.builtin.assert: + that: + - aw_release_extracted is defined + - aw_release_extracted | length > 0 + - aw_server_binary_path is defined + - aw_server_binary_path | length > 0 + - aw_webui_source_path is defined + - aw_webui_source_path | length > 0 + fail_msg: "Не удалось найти бинарный файл или WebUI в распакованном релизе ActivityWatch." - - name: Создать ссылку на активный бинарный файл AW server - ansible.builtin.file: - src: "{{ aw_release_install_dir }}/aw-server-rust" - dest: /opt/activitywatch/bin/aw-server-rust - owner: "{{ aw_server_user }}" - group: "{{ aw_server_group }}" - state: link - force: true + - name: Создать каталог установленного релиза + ansible.builtin.file: + path: "{{ aw_release_install_dir }}" + state: directory + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + mode: "0755" - - name: Синхронизировать WebUI в RU каталог - ansible.builtin.command: - cmd: "rsync -a {{ aw_webui_source_path }}/ {{ aw_server_webui_dir }}/" + - name: Установить бинарный файл AW server + ansible.builtin.copy: + remote_src: true + src: "{{ aw_server_binary_path }}" + dest: "{{ aw_release_install_dir }}/aw-server-rust" + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + mode: "0755" - - name: Настроить владельца файлов /opt/activitywatch - ansible.builtin.file: - path: /opt/activitywatch - state: directory - owner: "{{ aw_server_user }}" - group: "{{ aw_server_group }}" - recurse: true + - name: Создать ссылку на активный бинарный файл AW server + ansible.builtin.file: + src: "{{ aw_release_install_dir }}/aw-server-rust" + dest: /opt/activitywatch/bin/aw-server-rust + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + state: link + force: true + + - name: Синхронизировать WebUI в RU каталог + ansible.builtin.command: + cmd: "rsync -a {{ aw_webui_source_path }}/ {{ aw_server_webui_dir }}/" + + - name: Настроить владельца файлов /opt/activitywatch + ansible.builtin.file: + path: /opt/activitywatch + state: directory + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + recurse: true - name: Установить systemd service из шаблона репозитория ansible.builtin.copy: @@ -184,143 +244,1106 @@ - Перезагрузить systemd - Перезапустить activitywatch - - name: Скопировать RU patch файлы WebUI из репозитория - ansible.builtin.copy: - src: "{{ item.src }}" - dest: "{{ item.dest }}" - mode: "{{ item.mode }}" - owner: "{{ aw_server_user }}" - group: "{{ aw_server_group }}" - loop: - - { src: "{{ aw_repo_root }}/aw-server/aw-ru-patch.js", dest: "{{ aw_server_webui_dir }}/js/ru-patch-v5.js", mode: "0644" } - - { src: "{{ aw_repo_root }}/aw-server/aw-sw-cleanup.js", dest: "{{ aw_server_webui_dir }}/js/sw-cleanup.js", mode: "0644" } - - { src: "{{ aw_repo_root }}/aw-server/aw-worktime-panel.js", dest: "{{ aw_server_webui_dir }}/js/aw-worktime-panel.js", mode: "0644" } - - { src: "{{ aw_repo_root }}/aw-server/aw-host-groups.json", dest: "{{ aw_server_webui_dir }}/js/aw-host-groups.json", mode: "0644" } - - - name: Создать каталог /root/bootstrap для apply_webui_ru_patch.sh - ansible.builtin.file: - path: /root/bootstrap - state: directory - mode: "0755" - - - name: Скопировать RU patch файлы для apply_webui_ru_patch.sh (хотфиксы compiled JS чанков) - ansible.builtin.copy: - src: "{{ item.src }}" - dest: "{{ item.dest }}" - mode: "{{ item.mode }}" - loop: - - { src: "{{ aw_repo_root }}/aw-server/aw-ru-patch.js", dest: "/root/bootstrap/aw-ru-patch.js", mode: "0644" } - - { src: "{{ aw_repo_root }}/aw-server/aw-sw-cleanup.js", dest: "/root/bootstrap/aw-sw-cleanup.js", mode: "0644" } - - { src: "{{ aw_repo_root }}/aw-server/aw-worktime-panel.js", dest: "/root/bootstrap/aw-worktime-panel.js", mode: "0644" } - - { src: "{{ aw_repo_root }}/aw-server/aw-host-groups.json", dest: "/root/bootstrap/aw-host-groups.json", mode: "0644" } - - - name: Скопировать apply_webui_ru_patch.sh скрипт - ansible.builtin.copy: - src: "{{ aw_repo_root }}/aw-server/apply_webui_ru_patch.sh" - dest: /opt/activitywatch/aw-server/apply_webui_ru_patch.sh - mode: "0755" - - - name: Записать /etc/activitywatch/aw-server.env перед хотфиксами - ansible.builtin.copy: - dest: /etc/activitywatch/aw-server.env - mode: "0640" - owner: root - group: root - content: | - AW_SERVER_BIND_HOST={{ aw_server_bind_host }} - AW_SERVER_PORT={{ aw_server_port }} - AW_SERVER_DATA_DIR={{ aw_server_data_dir }} - AW_SERVER_DB_PATH={{ aw_server_db_path }} - AW_SERVER_LOG_DIR={{ aw_server_log_dir }} - AW_SERVER_WEBUI_DIR={{ aw_server_webui_dir }} - AW_SERVER_USER={{ aw_server_user }} - AW_SERVER_GROUP={{ aw_server_group }} - AW_WORKTIME_REPORT_BASE={{ aw_worktime_report_base }} - AW_WORKTIME_TZ={{ aw_worktime_timezone }} - XDG_DATA_HOME={{ aw_server_data_dir }}/.local/share - XDG_CONFIG_HOME={{ aw_server_data_dir }}/.config - - - name: Установить скрипт AW worktime API - ansible.builtin.copy: - src: "{{ aw_repo_root }}/aw-server/aw-worktime-api.py" - dest: /usr/local/bin/aw-worktime-api.py - owner: root - group: root - mode: "0755" - - - name: Установить systemd unit AW worktime API - ansible.builtin.copy: - src: "{{ aw_repo_root }}/aw-server/aw-worktime-api.service" - dest: /etc/systemd/system/aw-worktime-api.service - owner: root - group: root - mode: "0644" - - - name: Перезагрузить systemd после установки AW worktime API - ansible.builtin.systemd: - daemon_reload: true - - - name: Включить и перезапустить AW worktime API - ansible.builtin.systemd: - name: aw-worktime-api.service - enabled: true - state: restarted - - - name: Применить хотфиксы compiled JS чанков (Trends, Timespiral, Category helper) - ansible.builtin.command: - cmd: "/opt/activitywatch/aw-server/apply_webui_ru_patch.sh" - register: apply_ru_patch_result - failed_when: false - - - name: Вывести результат применения хотфиксов + - name: (Check mode) Пропустить WebUI patch и запуск сервиса ansible.builtin.debug: - msg: "apply_webui_ru_patch.sh: {{ apply_ru_patch_result.stdout }}" + msg: "ansible_check_mode=true: WebUI patch + service start + API checks are skipped." + when: ansible_check_mode - - name: Проверить наличие index.html после копирования - ansible.builtin.stat: - path: "{{ aw_server_webui_dir }}/index.html" - register: aw_webui_ru_index + - name: Применить WebUI RU patch и запустить сервис + when: not ansible_check_mode + block: + - name: Скопировать RU patch файлы WebUI из репозитория + ansible.builtin.copy: + src: "{{ item.src }}" + dest: "{{ item.dest }}" + mode: "{{ item.mode }}" + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + loop: + - { src: "{{ aw_repo_root }}/aw-server/aw-ru-patch.js", dest: "{{ aw_server_webui_dir }}/js/ru-patch-v5.js", mode: "0644" } + - { src: "{{ aw_repo_root }}/aw-server/aw-sw-cleanup.js", dest: "{{ aw_server_webui_dir }}/js/sw-cleanup.js", mode: "0644" } + - { src: "{{ aw_repo_root }}/aw-server/aw-worktime-panel.js", dest: "{{ aw_server_webui_dir }}/js/aw-worktime-panel.js", mode: "0644" } + - { src: "{{ aw_repo_root }}/aw-server/aw-host-groups.json", dest: "{{ aw_server_webui_dir }}/js/aw-host-groups.json", mode: "0644" } - - name: Проверить, что index.html доступен для RU patch - ansible.builtin.assert: - that: - - aw_webui_ru_index.stat.exists - fail_msg: "Не найден index.html WebUI для применения RU patch." + - name: Создать каталог /root/bootstrap для apply_webui_ru_patch.sh + ansible.builtin.file: + path: /root/bootstrap + state: directory + mode: "0755" - - name: Удалить старые теги RU patch из index.html - ansible.builtin.replace: - path: "{{ aw_server_webui_dir }}/index.html" - regexp: ']+(?:ru-patch-v5\.js|sw-cleanup\.js|aw-ru-patch\.js|aw-sw-cleanup\.js)[^>]*>' - replace: '' + - name: Скопировать RU patch файлы для apply_webui_ru_patch.sh (хотфиксы compiled JS чанков) + ansible.builtin.copy: + src: "{{ item.src }}" + dest: "{{ item.dest }}" + mode: "{{ item.mode }}" + loop: + - { src: "{{ aw_repo_root }}/aw-server/aw-ru-patch.js", dest: "/root/bootstrap/aw-ru-patch.js", mode: "0644" } + - { src: "{{ aw_repo_root }}/aw-server/aw-sw-cleanup.js", dest: "/root/bootstrap/aw-sw-cleanup.js", mode: "0644" } + - { src: "{{ aw_repo_root }}/aw-server/aw-worktime-panel.js", dest: "/root/bootstrap/aw-worktime-panel.js", mode: "0644" } + - { src: "{{ aw_repo_root }}/aw-server/aw-host-groups.json", dest: "/root/bootstrap/aw-host-groups.json", mode: "0644" } - - name: Добавить cleanup script RU patch в index.html - ansible.builtin.replace: - path: "{{ aw_server_webui_dir }}/index.html" - regexp: '' - replace: '' + - name: Скопировать apply_webui_ru_patch.sh скрипт + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/apply_webui_ru_patch.sh" + dest: /opt/activitywatch/aw-server/apply_webui_ru_patch.sh + mode: "0755" - - name: Добавить загрузчик RU patch перед закрытием body - ansible.builtin.replace: - path: "{{ aw_server_webui_dir }}/index.html" - regexp: '' - replace: '' + - name: Записать /etc/activitywatch/aw-server.env перед хотфиксами + ansible.builtin.copy: + dest: /etc/activitywatch/aw-server.env + mode: "0640" + owner: root + group: root + content: | + AW_SERVER_BIND_HOST={{ aw_server_bind_host }} + AW_SERVER_PORT={{ aw_server_port }} + AW_SERVER_DATA_DIR={{ aw_server_data_dir }} + AW_SERVER_DB_PATH={{ aw_server_db_path }} + AW_SERVER_LOG_DIR={{ aw_server_log_dir }} + AW_SERVER_WEBUI_DIR={{ aw_server_webui_dir }} + AW_SERVER_USER={{ aw_server_user }} + AW_SERVER_GROUP={{ aw_server_group }} + AW_WORKTIME_REPORT_BASE={{ aw_worktime_report_base }} + AW_WORKTIME_TZ={{ aw_worktime_timezone }} + AW_DLP_IOC_DIR={{ aw_dlp_ioc_workdir }}/output + AW_DLP_POLICY_ENGINE_BIND_HOST={{ aw_dlp_policy_engine_bind_host }} + AW_DLP_POLICY_ENGINE_PORT={{ aw_dlp_policy_engine_port }} + AW_DLP_POLICY_ENGINE_DB_PATH={{ aw_dlp_policy_engine_db_path }} + AW_DLP_CASE_BIND_HOST={{ aw_dlp_case_bind_host }} + AW_DLP_CASE_PORT={{ aw_dlp_case_port }} + AW_DLP_CASE_DB_PATH={{ aw_dlp_case_db_path }} + AW_DLP_COMPLIANCE_REPORT_DIR={{ aw_dlp_compliance_report_dir }} + AW_DLP_COMPLIANCE_TEMPLATE={{ aw_dlp_compliance_template_path }} + AW_SERVER_URL=http://127.0.0.1:5600 + XDG_DATA_HOME={{ aw_server_data_dir }}/.local/share + XDG_CONFIG_HOME={{ aw_server_data_dir }}/.config + AW_WORKTIME_INFLUX_ENABLED={{ 'true' if (aw_worktime_influx_enabled | default(false) | bool) else 'false' }} + AW_WORKTIME_INFLUX_URL={{ aw_worktime_influx_url | default('') }} + AW_WORKTIME_INFLUX_ORG={{ aw_worktime_influx_org | default('proxmox') }} + AW_WORKTIME_INFLUX_BUCKET={{ aw_worktime_influx_bucket | default('aw_metrics') }} + AW_WORKTIME_INFLUX_HOSTS={{ aw_worktime_influx_hosts | default('SHARKON2025') }} + AW_WORKTIME_INFLUX_DAYS={{ aw_worktime_influx_days | default('today,yesterday') }} + AW_WORKTIME_INFLUX_TOKEN={{ aw_worktime_influx_token | default('') }} + AW_DLP_INFLUX_ENABLED={{ 'true' if (aw_dlp_influx_enabled | default(false) | bool) else 'false' }} + AW_DLP_INFLUX_URL={{ aw_dlp_influx_url | default('') }} + AW_DLP_INFLUX_ORG={{ aw_dlp_influx_org | default('proxmox') }} + AW_DLP_INFLUX_BUCKET={{ aw_dlp_influx_bucket | default('aw_metrics') }} + AW_DLP_INFLUX_HOSTS={{ aw_dlp_influx_hosts | default('SHARKON2025') }} + AW_DLP_INFLUX_LOOKBACK_DAYS={{ aw_dlp_influx_lookback_days | default(30) }} + AW_DLP_INFLUX_EVENT_LIMIT={{ aw_dlp_influx_event_limit | default(2000) }} + AW_DLP_INFLUX_TOKEN={{ aw_dlp_influx_token | default('') }} + AW_DLP_AW_API_BASE=http://127.0.0.1:5600/api/0 + AW_DLP_CASE_API_BASE=http://127.0.0.1:5602/api/0/dlp/cases + AW_EXPECT_START_OF_DAY={{ aw_worktime_start_of_day | default('') }} + AW_EXPECT_ALWAYS_ACTIVE_PATTERN={{ aw_server_always_active_pattern | default('') }} + AW_EXPECT_LANDINGPAGE={{ aw_server_landingpage | default('') }} + 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_STATE_DIR={{ aw_rus_health_state_dir }} + AW_RUS_HEALTH_VALIDATION_DIR={{ aw_rus_health_validation_dir }} - - name: Включить и запустить сервис - ansible.builtin.systemd: - name: activitywatch-server.service - enabled: true - state: restarted - daemon_reload: true + - name: Создать каталог DLP policy engine + ansible.builtin.file: + path: /opt/activitywatch/dlp-policy-engine + state: directory + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + mode: "0755" + when: aw_dlp_policy_engine_enabled | default(false) | bool - - name: Дождаться ответа API + - name: Скопировать файлы DLP policy engine + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/dlp-policy-engine/{{ item }}" + dest: "/opt/activitywatch/dlp-policy-engine/{{ item }}" + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + mode: "0644" + loop: + - policy_service.py + - policy_schema.py + - policy_storage.py + - policy_distributor.py + - requirements.txt + when: aw_dlp_policy_engine_enabled | default(false) | bool + + - name: Создать virtualenv DLP policy engine + ansible.builtin.command: + cmd: python3 -m venv /opt/activitywatch/dlp-policy-engine/.venv + args: + creates: /opt/activitywatch/dlp-policy-engine/.venv/bin/python + when: aw_dlp_policy_engine_enabled | default(false) | bool + + - name: Установить зависимости DLP policy engine + ansible.builtin.pip: + requirements: /opt/activitywatch/dlp-policy-engine/requirements.txt + virtualenv: /opt/activitywatch/dlp-policy-engine/.venv + when: aw_dlp_policy_engine_enabled | default(false) | bool + + - name: Установить systemd unit DLP policy engine + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/dlp-policy-engine/dlp-policy-engine.service" + dest: /etc/systemd/system/aw-dlp-policy-engine.service + owner: root + group: root + mode: "0644" + when: aw_dlp_policy_engine_enabled | default(false) | bool + + - name: Создать каталог DLP content analysis + ansible.builtin.file: + path: /opt/activitywatch/dlp-content-analysis + state: directory + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + mode: "0755" + when: aw_dlp_content_analysis_enabled | default(true) | bool + + - name: Скопировать файлы DLP content analysis + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/dlp-content-analysis/" + dest: /opt/activitywatch/dlp-content-analysis/ + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + mode: "0644" + when: aw_dlp_content_analysis_enabled | default(true) | bool + + - name: Установить wrapper запуска DLP content analysis через virtualenv + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/dlp-content-analysis/aw-dlp-content-analyzer.sh" + dest: /usr/local/bin/aw-dlp-content-analyzer + owner: root + group: root + mode: "0755" + when: aw_dlp_content_analysis_enabled | default(true) | bool + + - name: Создать virtualenv DLP content analysis + ansible.builtin.command: + cmd: python3 -m venv /opt/activitywatch/dlp-content-analysis/.venv + args: + creates: /opt/activitywatch/dlp-content-analysis/.venv/bin/python + when: aw_dlp_content_analysis_enabled | default(true) | bool + + - name: Установить зависимости DLP content analysis + ansible.builtin.pip: + requirements: /opt/activitywatch/dlp-content-analysis/requirements.txt + virtualenv: /opt/activitywatch/dlp-content-analysis/.venv + when: aw_dlp_content_analysis_enabled | default(true) | bool + + - name: Создать каталог DLP integrations + ansible.builtin.file: + path: /opt/activitywatch/dlp-integrations + state: directory + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + mode: "0755" + when: aw_dlp_integrations_enabled | default(true) | bool + + - name: Скопировать файлы DLP integrations + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/dlp-integrations/{{ item }}" + dest: "/opt/activitywatch/dlp-integrations/{{ item }}" + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + mode: "0644" + loop: + - cef_exporter.py + - syslog_forwarder.py + - webhook_sender.py + - cef-config.yaml + - syslog-forwarder-config.yaml + - webhook-config.yaml + - requirements.txt + when: aw_dlp_integrations_enabled | default(true) | bool + + - name: Выставить executable для python scripts DLP integrations + ansible.builtin.file: + path: "/opt/activitywatch/dlp-integrations/{{ item }}" + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + mode: "0755" + loop: + - cef_exporter.py + - syslog_forwarder.py + - webhook_sender.py + when: aw_dlp_integrations_enabled | default(true) | bool + + - name: Создать virtualenv DLP integrations + ansible.builtin.command: + cmd: python3 -m venv /opt/activitywatch/dlp-integrations/.venv + args: + creates: /opt/activitywatch/dlp-integrations/.venv/bin/python + when: aw_dlp_integrations_enabled | default(true) | bool + + - name: Установить зависимости DLP integrations + ansible.builtin.pip: + requirements: /opt/activitywatch/dlp-integrations/requirements.txt + virtualenv: /opt/activitywatch/dlp-integrations/.venv + when: aw_dlp_integrations_enabled | default(true) | bool + + - name: Создать state каталог DLP integrations + ansible.builtin.file: + path: /var/lib/activitywatch/dlp-integrations + state: directory + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + mode: "0755" + when: aw_dlp_integrations_enabled | default(true) | bool + + - name: Установить systemd unit CEF exporter + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/dlp-integrations/cef-exporter.service" + dest: /etc/systemd/system/aw-dlp-cef-exporter.service + owner: root + group: root + mode: "0644" + when: aw_dlp_integrations_enabled | default(true) | bool + + - name: Установить systemd timer CEF exporter + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/dlp-integrations/cef-exporter.timer" + dest: /etc/systemd/system/aw-dlp-cef-exporter.timer + owner: root + group: root + mode: "0644" + when: aw_dlp_integrations_enabled | default(true) | bool + + - name: Установить systemd unit syslog forwarder + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/dlp-integrations/syslog-forwarder.service" + dest: /etc/systemd/system/aw-dlp-syslog-forwarder.service + owner: root + group: root + mode: "0644" + when: aw_dlp_integrations_enabled | default(true) | bool + + - name: Установить systemd timer syslog forwarder + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/dlp-integrations/syslog-forwarder.timer" + dest: /etc/systemd/system/aw-dlp-syslog-forwarder.timer + owner: root + group: root + mode: "0644" + when: aw_dlp_integrations_enabled | default(true) | bool + + - name: Установить systemd unit webhook sender + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/dlp-integrations/webhook-sender.service" + dest: /etc/systemd/system/aw-dlp-webhook-sender.service + owner: root + group: root + mode: "0644" + when: aw_dlp_integrations_enabled | default(true) | bool + + - name: Установить systemd timer webhook sender + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/dlp-integrations/webhook-sender.timer" + dest: /etc/systemd/system/aw-dlp-webhook-sender.timer + owner: root + group: root + mode: "0644" + when: aw_dlp_integrations_enabled | default(true) | bool + + - name: Создать каталог DLP case management + ansible.builtin.file: + path: /opt/activitywatch/dlp-case-management + state: directory + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + mode: "0755" + when: aw_dlp_case_management_enabled | default(true) | bool + + - name: Скопировать файлы DLP case management + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/dlp-case-management/{{ item }}" + dest: "/opt/activitywatch/dlp-case-management/{{ item }}" + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + mode: "0644" + loop: + - case_service.py + - case_rules.py + - case_schema.py + - case_storage.py + - evidence_chain.py + - requirements.txt + when: aw_dlp_case_management_enabled | default(true) | bool + + - name: Выставить executable для case_service + ansible.builtin.file: + path: /opt/activitywatch/dlp-case-management/case_service.py + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + mode: "0755" + when: aw_dlp_case_management_enabled | default(true) | bool + + - name: Создать virtualenv DLP case management + ansible.builtin.command: + cmd: python3 -m venv /opt/activitywatch/dlp-case-management/.venv + args: + creates: /opt/activitywatch/dlp-case-management/.venv/bin/python + when: aw_dlp_case_management_enabled | default(true) | bool + + - name: Установить зависимости DLP case management + ansible.builtin.pip: + requirements: /opt/activitywatch/dlp-case-management/requirements.txt + virtualenv: /opt/activitywatch/dlp-case-management/.venv + when: aw_dlp_case_management_enabled | default(true) | bool + + - name: Установить systemd unit DLP case management + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/dlp-case-management/case-service.service" + dest: /etc/systemd/system/aw-dlp-case-management.service + owner: root + group: root + mode: "0644" + when: aw_dlp_case_management_enabled | default(true) | bool + + - name: Создать каталоги DLP compliance + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + mode: "0755" + loop: + - /opt/activitywatch/dlp-compliance + - /opt/activitywatch/dlp-compliance/templates + - "{{ aw_dlp_compliance_report_dir }}" + when: aw_dlp_compliance_enabled | default(true) | bool + + - name: Скопировать файлы DLP compliance + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/dlp-compliance/{{ item.src }}" + dest: "{{ item.dest }}" + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + mode: "{{ item.mode }}" + loop: + - { src: "report_generator.py", dest: "/opt/activitywatch/dlp-compliance/report_generator.py", mode: "0755" } + - { src: "compliance_scheduler.py", dest: "/opt/activitywatch/dlp-compliance/compliance_scheduler.py", mode: "0755" } + - { src: "requirements.txt", dest: "/opt/activitywatch/dlp-compliance/requirements.txt", mode: "0644" } + - { src: "templates/152-fz-report.html", dest: "/opt/activitywatch/dlp-compliance/templates/152-fz-report.html", mode: "0644" } + - { src: "templates/pci-dss-report.html", dest: "/opt/activitywatch/dlp-compliance/templates/pci-dss-report.html", mode: "0644" } + - { src: "report-scheduler.service", dest: "/etc/systemd/system/aw-dlp-report-scheduler.service", mode: "0644" } + - { src: "report-scheduler.timer", dest: "/etc/systemd/system/aw-dlp-report-scheduler.timer", mode: "0644" } + when: aw_dlp_compliance_enabled | default(true) | bool + + - name: Создать virtualenv DLP compliance + ansible.builtin.command: + cmd: python3 -m venv /opt/activitywatch/dlp-compliance/.venv + args: + creates: /opt/activitywatch/dlp-compliance/.venv/bin/python + when: aw_dlp_compliance_enabled | default(true) | bool + + - name: Установить зависимости DLP compliance + ansible.builtin.pip: + requirements: /opt/activitywatch/dlp-compliance/requirements.txt + virtualenv: /opt/activitywatch/dlp-compliance/.venv + when: aw_dlp_compliance_enabled | default(true) | bool + + - name: Установить dlp-admin-cli + ansible.builtin.copy: + src: "{{ aw_repo_root }}/scripts/dlp-admin-cli.py" + dest: /usr/local/bin/dlp-admin-cli.py + owner: root + group: root + mode: "0755" + + - name: Установить скрипт AW worktime API + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/aw-worktime-api.py" + dest: /usr/local/bin/aw-worktime-api.py + owner: root + group: root + mode: "0755" + + - name: Установить systemd unit AW worktime API + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/aw-worktime-api.service" + dest: /etc/systemd/system/aw-worktime-api.service + owner: root + group: root + mode: "0644" + + - name: Установить скрипт AW worktime UI bridge + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/aw-worktime-ui-bridge.py" + dest: /usr/local/bin/aw-worktime-ui-bridge.py + owner: root + group: root + mode: "0755" + + - name: Установить скрипт AW worktime autoheal + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/aw-worktime-autoheal.sh" + dest: /usr/local/bin/aw-worktime-autoheal.sh + owner: root + group: root + mode: "0755" + + - name: Установить скрипт AW worktime Influx exporter + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/aw-worktime-influx-exporter.py" + dest: /usr/local/bin/aw-worktime-influx-exporter.py + owner: root + group: root + mode: "0755" + when: aw_worktime_influx_enabled | default(false) | bool + + - name: Установить скрипт AW DLP Influx exporter + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/aw-dlp-influx-exporter.py" + dest: /usr/local/bin/aw-dlp-influx-exporter.py + owner: root + group: root + mode: "0755" + when: aw_dlp_influx_enabled | default(false) | bool + + - name: Установить скрипт aw-health-check + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/health-check.sh" + dest: /usr/local/bin/aw-health-check + owner: root + group: root + mode: "0755" + + - name: Установить скрипт dlp-health-check + ansible.builtin.copy: + src: "{{ aw_repo_root }}/scripts/dlp-health-check.py" + dest: /usr/local/bin/dlp-health-check + owner: root + group: root + mode: "0755" + + - name: Установить скрипт aw-rus-healthd + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/aw-rus-healthd.py" + dest: /usr/local/bin/aw-rus-healthd.py + owner: root + group: root + mode: "0755" + + - name: Установить systemd unit AW worktime UI bridge + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/aw-worktime-ui-bridge.service" + dest: /etc/systemd/system/aw-worktime-ui-bridge.service + owner: root + group: root + mode: "0644" + + - name: Установить systemd timer AW worktime UI bridge + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/aw-worktime-ui-bridge.timer" + dest: /etc/systemd/system/aw-worktime-ui-bridge.timer + owner: root + group: root + mode: "0644" + + - name: Установить systemd unit AW worktime autoheal + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/aw-worktime-autoheal.service" + dest: /etc/systemd/system/aw-worktime-autoheal.service + owner: root + group: root + mode: "0644" + + - name: Установить systemd timer AW worktime autoheal + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/aw-worktime-autoheal.timer" + dest: /etc/systemd/system/aw-worktime-autoheal.timer + owner: root + group: root + mode: "0644" + + - name: Установить systemd unit AW-RUS healthd + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/aw-rus-healthd.service" + dest: /etc/systemd/system/aw-rus-healthd.service + owner: root + group: root + mode: "0644" + + - name: Установить systemd timer AW-RUS healthd + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/aw-rus-healthd.timer" + dest: /etc/systemd/system/aw-rus-healthd.timer + owner: root + group: root + mode: "0644" + + - name: Установить systemd unit AW worktime Influx exporter + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/aw-worktime-influx-exporter.service" + dest: /etc/systemd/system/aw-worktime-influx-exporter.service + owner: root + group: root + mode: "0644" + when: aw_worktime_influx_enabled | default(false) | bool + + - name: Установить systemd timer AW worktime Influx exporter + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/aw-worktime-influx-exporter.timer" + dest: /etc/systemd/system/aw-worktime-influx-exporter.timer + owner: root + group: root + mode: "0644" + when: aw_worktime_influx_enabled | default(false) | bool + + - name: Установить systemd unit AW DLP Influx exporter + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/aw-dlp-influx-exporter.service" + dest: /etc/systemd/system/aw-dlp-influx-exporter.service + owner: root + group: root + mode: "0644" + when: aw_dlp_influx_enabled | default(false) | bool + + - name: Установить systemd timer AW DLP Influx exporter + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/aw-dlp-influx-exporter.timer" + dest: /etc/systemd/system/aw-dlp-influx-exporter.timer + owner: root + group: root + mode: "0644" + when: aw_dlp_influx_enabled | default(false) | bool + + - name: Перезагрузить systemd после установки AW worktime API + ansible.builtin.systemd: + daemon_reload: true + + - name: Включить таймер AW-RUS healthd + ansible.builtin.systemd: + name: aw-rus-healthd.timer + enabled: true + state: started + + - name: Включить и перезапустить DLP policy engine + ansible.builtin.systemd: + name: aw-dlp-policy-engine.service + enabled: true + state: restarted + when: aw_dlp_policy_engine_enabled | default(false) | bool + + - name: Включить и перезапустить timer CEF exporter + ansible.builtin.systemd: + name: aw-dlp-cef-exporter.timer + enabled: true + state: restarted + when: aw_dlp_integrations_enabled | default(true) | bool + + - name: Включить и перезапустить timer syslog forwarder + ansible.builtin.systemd: + name: aw-dlp-syslog-forwarder.timer + enabled: true + state: restarted + when: aw_dlp_integrations_enabled | default(true) | bool + + - name: Включить и перезапустить timer webhook sender + ansible.builtin.systemd: + name: aw-dlp-webhook-sender.timer + enabled: true + state: restarted + when: aw_dlp_integrations_enabled | default(true) | bool + + - name: Включить и перезапустить DLP case management + ansible.builtin.systemd: + name: aw-dlp-case-management.service + enabled: true + state: restarted + when: aw_dlp_case_management_enabled | default(true) | bool + + - name: Включить и перезапустить timer DLP compliance report + ansible.builtin.systemd: + name: aw-dlp-report-scheduler.timer + enabled: true + state: restarted + when: aw_dlp_compliance_enabled | default(true) | bool + + - name: Выполнить разовый прогон DLP compliance report + ansible.builtin.systemd: + name: aw-dlp-report-scheduler.service + state: started + failed_when: false + when: aw_dlp_compliance_enabled | default(true) | bool + + - name: Включить и перезапустить AW worktime API + ansible.builtin.systemd: + name: aw-worktime-api.service + enabled: true + state: restarted + + - name: Отключить legacy timer aw-worktime-afk-bridge (если есть) + ansible.builtin.systemd: + name: aw-worktime-afk-bridge.timer + enabled: false + state: stopped + failed_when: false + + - name: Включить и перезапустить AW worktime UI bridge timer + ansible.builtin.systemd: + name: aw-worktime-ui-bridge.timer + enabled: true + state: restarted + + - name: Выполнить разовый прогон AW worktime UI bridge + ansible.builtin.systemd: + name: aw-worktime-ui-bridge.service + state: started + failed_when: false + + - name: Включить и перезапустить AW worktime autoheal timer + ansible.builtin.systemd: + name: aw-worktime-autoheal.timer + enabled: true + state: restarted + + - name: Выполнить разовый прогон AW worktime autoheal + ansible.builtin.systemd: + name: aw-worktime-autoheal.service + state: started + failed_when: false + + - name: Включить и перезапустить AW worktime Influx exporter timer + ansible.builtin.systemd: + name: aw-worktime-influx-exporter.timer + enabled: true + state: restarted + when: aw_worktime_influx_enabled | default(false) | bool + + - name: Выполнить разовый прогон AW worktime Influx exporter + 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 + ansible.builtin.systemd: + name: aw-dlp-influx-exporter.timer + enabled: true + state: restarted + when: aw_dlp_influx_enabled | default(false) | bool + + - name: Выполнить разовый прогон AW DLP Influx exporter + 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) + ansible.builtin.command: + cmd: "/opt/activitywatch/aw-server/apply_webui_ru_patch.sh" + register: apply_ru_patch_result + failed_when: false + + - name: Вывести результат применения хотфиксов + ansible.builtin.debug: + msg: "apply_webui_ru_patch.sh: {{ apply_ru_patch_result.stdout }}" + + - name: Проверить наличие index.html после копирования + ansible.builtin.stat: + path: "{{ aw_server_webui_dir }}/index.html" + register: aw_webui_ru_index + + - name: Проверить, что index.html доступен для RU patch + ansible.builtin.assert: + that: + - aw_webui_ru_index.stat.exists + - (aw_webui_ru_index.stat.size | default(0) | int) > 0 + fail_msg: "Не найден index.html WebUI для применения RU patch." + + - name: Проверить, что index.html не опустел после применения RU patch + ansible.builtin.stat: + path: "{{ aw_server_webui_dir }}/index.html" + register: aw_webui_ru_index_after_patch + + - name: Подтвердить, что index.html остался непустым + ansible.builtin.assert: + that: + - aw_webui_ru_index_after_patch.stat.exists + - (aw_webui_ru_index_after_patch.stat.size | default(0) | int) > 0 + fail_msg: "index.html WebUI пуст после RU patch." + + - name: Удалить старые теги RU patch из index.html + ansible.builtin.replace: + path: "{{ aw_server_webui_dir }}/index.html" + regexp: ']+(?:ru-patch-v5\.js|sw-cleanup\.js|aw-ru-patch\.js|aw-sw-cleanup\.js)[^>]*>' + replace: '' + + - name: Добавить cleanup script RU patch в index.html + ansible.builtin.replace: + path: "{{ aw_server_webui_dir }}/index.html" + regexp: '' + replace: '' + + - name: Добавить загрузчик RU patch перед закрытием body + ansible.builtin.replace: + path: "{{ aw_server_webui_dir }}/index.html" + regexp: '' + replace: '' + + - name: Скопировать merge script AW DB на сервер + ansible.builtin.copy: + src: "{{ aw_repo_root }}/scripts/merge_aw_server_dbs.py" + dest: /usr/local/bin/merge_aw_server_dbs.py + owner: root + group: root + mode: "0755" + + - name: Проверить наличие legacy root DB + ansible.builtin.stat: + path: /root/.local/share/activitywatch/aw-server-rust/sqlite.db + register: aw_legacy_root_db + + - name: Проверить наличие target DB + ansible.builtin.stat: + path: "{{ aw_server_db_path }}" + register: aw_target_db + + - name: Остановить сервис перед merge server DB + ansible.builtin.systemd: + name: activitywatch-server.service + state: stopped + when: aw_legacy_root_db.stat.exists | default(false) + + - name: Создать backup каталоги server DB + ansible.builtin.file: + path: "{{ aw_server_data_dir }}/backups/db" + state: directory + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + mode: "0755" + when: aw_legacy_root_db.stat.exists | default(false) + + - name: Backup target DB перед merge + ansible.builtin.copy: + remote_src: true + src: "{{ aw_server_db_path }}" + dest: "{{ aw_server_data_dir }}/backups/db/target-before-merge-{{ ansible_date_time.iso8601_basic_short }}.sqlite.db" + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + mode: "0644" + when: + - aw_legacy_root_db.stat.exists | default(false) + - aw_target_db.stat.exists | default(false) + + - name: Backup legacy root DB перед merge + ansible.builtin.copy: + remote_src: true + src: /root/.local/share/activitywatch/aw-server-rust/sqlite.db + dest: "{{ aw_server_data_dir }}/backups/db/legacy-root-{{ ansible_date_time.iso8601_basic_short }}.sqlite.db" + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + mode: "0644" + when: aw_legacy_root_db.stat.exists | default(false) + + - name: Merge legacy root DB в target DB + ansible.builtin.command: + argv: + - python3 + - /usr/local/bin/merge_aw_server_dbs.py + - --base + - /root/.local/share/activitywatch/aw-server-rust/sqlite.db + - --overlay + - "{{ aw_server_db_path }}" + - --output + - "{{ aw_server_db_path }}.merged" + register: aw_merge_result + failed_when: false + when: + - aw_legacy_root_db.stat.exists | default(false) + - aw_target_db.stat.exists | default(false) + + - name: Показать результат merge legacy root DB + ansible.builtin.debug: + msg: "{{ aw_merge_result.stdout | default(aw_merge_result.stderr | default('merge not executed')) }}" + when: + - aw_legacy_root_db.stat.exists | default(false) + - aw_target_db.stat.exists | default(false) + + - name: Install merged DB as active target DB + ansible.builtin.copy: + remote_src: true + src: "{{ aw_server_db_path }}.merged" + dest: "{{ aw_server_db_path }}" + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + mode: "0644" + when: + - aw_legacy_root_db.stat.exists | default(false) + - aw_target_db.stat.exists | default(false) + - aw_merge_result is defined + - (aw_merge_result.rc | default(1) | int) == 0 + + - name: Скопировать legacy root DB в target DB если target ещё не существует + ansible.builtin.copy: + remote_src: true + src: /root/.local/share/activitywatch/aw-server-rust/sqlite.db + dest: "{{ aw_server_db_path }}" + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + mode: "0644" + when: + - aw_legacy_root_db.stat.exists | default(false) + - not (aw_target_db.stat.exists | default(false)) + + - name: Записать aw-server-rust config.toml с разрешёнными CORS origin + ansible.builtin.copy: + dest: "{{ aw_server_data_dir }}/.config/activitywatch/aw-server-rust/config.toml" + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + mode: "0644" + content: | + cors = [ + {% for origin in aw_server_cors_origins | default([]) %} + "{{ origin }}"{% if not loop.last %},{% endif %} + {% endfor %} + ] + + - name: Включить и запустить сервис + ansible.builtin.systemd: + name: activitywatch-server.service + enabled: true + state: restarted + daemon_reload: true + + - name: Дождаться ответа API + ansible.builtin.uri: + url: "http://127.0.0.1:{{ aw_server_port }}/api/0/info" + method: GET + status_code: 200 + register: aw_api + retries: 10 + delay: 3 + until: aw_api.status == 200 + + - name: Считать текущие server-side settings ansible.builtin.uri: - url: "http://127.0.0.1:{{ aw_server_port }}/api/0/info" + url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/" method: GET status_code: 200 - register: aw_api - retries: 10 - delay: 3 - until: aw_api.status == 200 + register: aw_settings_current + when: aw_apply_worktime_settings | default(false) | bool + + - name: Считать текущие server-side views + ansible.builtin.uri: + url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/views" + method: GET + status_code: 200 + register: aw_views_current + when: aw_apply_worktime_settings | default(false) | bool + + - name: Считать текущие server-side classes + ansible.builtin.uri: + url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/classes" + method: GET + status_code: 200 + register: aw_classes_current + when: aw_apply_worktime_settings | default(false) | bool + + - name: Создать backup текущих server-side settings/views/classes + ansible.builtin.copy: + dest: "{{ aw_server_data_dir }}/backups/{{ item.name }}-{{ ansible_date_time.iso8601_basic_short }}.json" + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + mode: "0644" + content: "{{ item.payload | to_nice_json }}" + loop: + - name: settings + payload: "{{ aw_settings_current.json | default({}) }}" + - name: views + payload: "{{ aw_views_current.json | default(none) }}" + - name: classes + payload: "{{ aw_classes_current.json | default(none) }}" + when: aw_apply_worktime_settings | default(false) | bool + + - name: Настроить DLP Aggregator (Phase 2) + block: + - name: Создать каталог для скриптов + ansible.builtin.file: + path: "/opt/activitywatch/scripts" + state: directory + owner: root + group: root + mode: "0755" + + - name: Скопировать агрегатор событий DLP + ansible.builtin.copy: + src: "{{ aw_repo_root }}/scripts/aggregate_dlp_events.py" + dest: "/opt/activitywatch/scripts/aggregate_dlp_events.py" + owner: root + group: root + mode: "0755" + + - name: Установить systemd unit для агрегатора + ansible.builtin.copy: + dest: /etc/systemd/system/activitywatch-dlp-aggregator.service + content: | + [Unit] + Description=ActivityWatch DLP Event Aggregator + After=activitywatch-server.service + + [Service] + Type=oneshot + User={{ aw_server_user }} + WorkingDirectory={{ aw_server_data_dir }} + ExecStart=/usr/bin/python3 /opt/activitywatch/scripts/aggregate_dlp_events.py \ + --aw-url http://127.0.0.1:{{ aw_server_port }}/api/0 \ + --sqlite-path {{ aw_server_data_dir }}/dlp_warehouse.sqlite \ + --state-path {{ aw_server_data_dir }}/dlp-aggregator-state.json + + [Install] + WantedBy=multi-user.target + + - name: Установить systemd timer для агрегатора + ansible.builtin.copy: + dest: /etc/systemd/system/activitywatch-dlp-aggregator.timer + content: | + [Unit] + Description=Run ActivityWatch DLP Aggregator every 5 minutes + + [Timer] + OnBootSec=1min + OnUnitActiveSec=5min + AccuracySec=1s + + [Install] + WantedBy=timers.target + + - name: Включить и запустить таймер агрегатора + ansible.builtin.systemd: + name: activitywatch-dlp-aggregator.timer + enabled: true + state: started + daemon_reload: true + + - name: Настроить IOC enrichment из Hayabusa Sigma + when: aw_dlp_ioc_enabled | default(false) | bool + block: + - name: Установить зависимости IOC refresh + ansible.builtin.apt: + name: + - python3-yaml + state: present + update_cache: true + + - name: Создать каталог IOC enrichment + ansible.builtin.file: + path: "{{ aw_dlp_ioc_workdir }}/{{ item }}" + state: directory + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + mode: "0755" + loop: + - "" + - output + - tmp + + - name: Установить экстрактор IOC из Sigma + ansible.builtin.copy: + src: "{{ aw_repo_root }}/scripts/extract_ioc_from_sigma.py" + dest: /usr/local/bin/aw-extract-ioc-from-sigma.py + owner: root + group: root + mode: "0755" + + - name: Установить wrapper refresh IOC + ansible.builtin.copy: + dest: /usr/local/bin/aw-dlp-ioc-refresh.sh + owner: root + group: root + mode: "0755" + content: | + #!/usr/bin/env bash + set -euo pipefail + WORKDIR="{{ aw_dlp_ioc_workdir }}" + OUTDIR="${WORKDIR}/output" + TMPDIR="${WORKDIR}/tmp" + RULES_DIR="${TMPDIR}/hayabusa-rules" + ZIP_PATH="${TMPDIR}/hayabusa-rules.zip" + + mkdir -p "${OUTDIR}" "${TMPDIR}" + rm -rf "${RULES_DIR}" + curl -fsSL -o "${ZIP_PATH}" "{{ aw_dlp_ioc_rules_zip_url }}" + unzip -q -o "${ZIP_PATH}" -d "${TMPDIR}" + + EXTRACTED_DIR=$(find "${TMPDIR}" -maxdepth 1 -type d -name 'hayabusa-rules-*' | head -n1) + if [ -z "${EXTRACTED_DIR}" ]; then + echo "ERROR: hayabusa-rules archive extraction failed" >&2 + exit 1 + fi + mv "${EXTRACTED_DIR}" "${RULES_DIR}" + + /usr/bin/python3 /usr/local/bin/aw-extract-ioc-from-sigma.py \ + --rules-root "${RULES_DIR}" \ + --out-dir "${OUTDIR}" + + ln -sfn "${OUTDIR}" "${WORKDIR}/latest" + + - name: Установить systemd unit IOC refresh + ansible.builtin.copy: + dest: /etc/systemd/system/aw-dlp-ioc-refresh.service + owner: root + group: root + mode: "0644" + content: | + [Unit] + Description=Refresh DLP IOC blacklist from Hayabusa Sigma rules + After=network-online.target + Wants=network-online.target + + [Service] + Type=oneshot + ExecStart=/usr/local/bin/aw-dlp-ioc-refresh.sh + + - name: Установить systemd timer IOC refresh + ansible.builtin.copy: + dest: /etc/systemd/system/aw-dlp-ioc-refresh.timer + owner: root + group: root + mode: "0644" + content: | + [Unit] + Description=Run DLP IOC refresh from Hayabusa rules + + [Timer] + OnBootSec={{ aw_dlp_ioc_refresh_on_boot_sec }} + OnUnitActiveSec={{ aw_dlp_ioc_refresh_interval }} + Persistent=true + Unit=aw-dlp-ioc-refresh.service + + [Install] + WantedBy=timers.target + + - name: Включить и запустить IOC refresh timer + ansible.builtin.systemd: + name: aw-dlp-ioc-refresh.timer + enabled: true + state: started + daemon_reload: true + + - name: Выполнить принудительный refresh IOC + ansible.builtin.systemd: + name: aw-dlp-ioc-refresh.service + state: started + + - name: Проверить наличие IOC артефактов после refresh + ansible.builtin.stat: + path: "{{ aw_dlp_ioc_workdir }}/output/{{ item }}" + register: aw_dlp_ioc_artifacts + loop: + - ioc_blacklist.json + - ioc_blacklist.csv + - ioc_blacklist.sql + + - name: Assert по IOC артефактам + ansible.builtin.assert: + that: + - item.stat.exists + - (item.stat.size | int) > 100 + fail_msg: "Не сгенерирован IOC артефакт: {{ item.stat.path | default('unknown') }}" + loop: "{{ aw_dlp_ioc_artifacts.results }}" - name: Применить базовые worktime settings (classes) ansible.builtin.uri: @@ -342,16 +1365,12 @@ - name: Вычислить worktime durationDefault из aw_worktime_from/to ansible.builtin.set_fact: - aw_worktime_from_h: "{{ (aw_worktime_from | default('08:00')).split(':')[0] | int }}" - aw_worktime_from_m: "{{ (aw_worktime_from | default('08:00')).split(':')[1] | int }}" - aw_worktime_to_h: "{{ (aw_worktime_to | default('17:00')).split(':')[0] | int }}" - aw_worktime_to_m: "{{ (aw_worktime_to | default('17:00')).split(':')[1] | int }}" aw_worktime_duration_default_derived: >- {{ ( ( - ((aw_worktime_to_h | int) * 60 + (aw_worktime_to_m | int)) - - ((aw_worktime_from_h | int) * 60 + (aw_worktime_from_m | int)) + (((aw_worktime_to | default('17:00')).split(':')[0] | int) * 60 + ((aw_worktime_to | default('17:00')).split(':')[1] | int)) - + (((aw_worktime_from | default('00:00')).split(':')[0] | int) * 60 + ((aw_worktime_from | default('00:00')).split(':')[1] | int)) ) * 60 ) }} @@ -379,20 +1398,185 @@ ansible.builtin.uri: url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/startOfDay" method: POST - body: "{{ aw_worktime_start_of_day }}" - body_format: json - status_code: 200 + body: "\"{{ aw_worktime_start_of_day }}\"" + headers: + Content-Type: application/json + status_code: [200, 201] when: aw_apply_worktime_settings | default(false) | bool - name: Применить базовый период worktime (durationDefault seconds) ansible.builtin.uri: url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/durationDefault" method: POST - body: "{{ aw_worktime_duration_default_effective }}" - body_format: json - status_code: 200 + body: "{{ aw_worktime_duration_default_effective | string }}" + headers: + Content-Type: application/json + status_code: [200, 201] when: aw_apply_worktime_settings | default(false) | bool + - name: Применить always_active_pattern для fallback без AFK + ansible.builtin.uri: + url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/always_active_pattern" + method: POST + body: "\"{{ aw_server_always_active_pattern }}\"" + headers: + Content-Type: application/json + status_code: [200, 201] + when: + - aw_apply_worktime_settings | default(false) | bool + - (aw_server_always_active_pattern | default('') | string | length) > 0 + + - name: Применить landingpage профиля + ansible.builtin.uri: + url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/landingpage" + method: POST + body: "\"{{ aw_server_landingpage }}\"" + headers: + Content-Type: application/json + status_code: [200, 201] + when: + - aw_apply_worktime_settings | default(false) | bool + - (aw_server_landingpage | default('') | string | length) > 0 + + - name: Обновить IOC blacklist из hayabusa (если включено) + when: + - aw_hayabusa_ioc_refresh_enabled | default(false) | bool + - aw_hayabusa_rules_root is defined + - aw_hayabusa_ioc_output_dir is defined + block: + - name: Создать выходную директорию для IOC + ansible.builtin.file: + path: "{{ aw_hayabusa_ioc_output_dir }}" + state: directory + mode: "0755" + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + + - name: Обновить IOC blacklist из hayabusa правил + ansible.builtin.shell: + cmd: | + set -euo pipefail + {{ aw_repo_root }}/scripts/build_dlp_ioc_from_hayabusa.sh \ + "{{ aw_hayabusa_rules_root }}" \ + "{{ aw_hayabusa_ioc_output_dir }}" + args: + chdir: "{{ aw_repo_root }}" + register: aw_hayabusa_ioc_refresh_result + changed_when: "'IOC artifacts generated' in aw_hayabusa_ioc_refresh_result.stdout" + + - name: Показать результат обновления IOC + ansible.builtin.debug: + msg: "{{ aw_hayabusa_ioc_refresh_result.stdout }}" + when: aw_hayabusa_ioc_refresh_result.stdout is defined + + - name: Установить server-side Hayabusa runner + when: aw_hayabusa_runner_enabled | default(false) | bool + block: + - name: Создать каталоги Hayabusa + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: root + group: root + mode: "0755" + loop: + - "{{ aw_hayabusa_root }}" + - "{{ aw_hayabusa_root }}/releases" + - "{{ aw_hayabusa_release_dir }}" + - "{{ aw_hayabusa_reports_dir }}" + - "{{ aw_hayabusa_state_dir }}" + - "{{ aw_hayabusa_inbox_dir }}" + - "{{ aw_hayabusa_archive_dir }}" + - "{{ aw_hayabusa_incoming_dir }}" + - "{{ aw_hayabusa_staging_dir }}" + - "{{ aw_hayabusa_archive_packages_dir }}" + - "{{ aw_hayabusa_archive_extracted_dir }}" + - "{{ aw_hayabusa_logs_dir }}" + + - name: Скачать архив Hayabusa + ansible.builtin.get_url: + url: "{{ aw_hayabusa_download_url }}" + dest: "{{ aw_hayabusa_archive_path }}" + mode: "0644" + + - name: Распаковать pinned Hayabusa release + ansible.builtin.unarchive: + src: "{{ aw_hayabusa_archive_path }}" + dest: "{{ aw_hayabusa_release_dir }}" + remote_src: true + creates: "{{ aw_hayabusa_release_dir }}/{{ aw_hayabusa_binary_name }}" + + - name: Нормализовать права release Hayabusa + ansible.builtin.file: + path: "{{ aw_hayabusa_release_dir }}" + state: directory + owner: root + group: root + mode: "0755" + recurse: true + + - name: Сделать бинарь Hayabusa исполняемым + ansible.builtin.file: + path: "{{ aw_hayabusa_release_dir }}/{{ aw_hayabusa_binary_name }}" + owner: root + group: root + mode: "0755" + state: file + + - name: Создать canonical symlink на текущий релиз Hayabusa + ansible.builtin.file: + src: "{{ aw_hayabusa_release_dir }}" + dest: "{{ aw_hayabusa_current_link }}" + state: link + force: true + + - name: Создать canonical symlink на бинарь Hayabusa + ansible.builtin.file: + src: "{{ aw_hayabusa_binary_name }}" + dest: "{{ aw_hayabusa_release_dir }}/hayabusa" + state: link + force: true + + - name: Установить wrapper aw-hayabusa + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/hayabusa/aw-hayabusa.sh" + dest: /usr/local/bin/aw-hayabusa + owner: root + group: root + mode: "0755" + + - name: Проверить server-side runner через doctor + ansible.builtin.command: + cmd: /usr/local/bin/aw-hayabusa doctor + changed_when: false + + - name: Проверить загрузку profiles Hayabusa + ansible.builtin.shell: /usr/local/bin/aw-hayabusa profiles > /tmp/aw-hayabusa-profiles.txt + args: + executable: /bin/bash + changed_when: false + + - name: Проверить наличие standard profile у Hayabusa + ansible.builtin.shell: "grep -q 'standard:' /tmp/aw-hayabusa-profiles.txt" + args: + executable: /bin/bash + changed_when: false + + - name: Post-deploy health gate (aw-rus-healthd) + when: + - not ansible_check_mode + - aw_server_post_deploy_health_check_enabled | default(true) | bool + block: + - name: Запустить /usr/local/bin/aw-rus-healthd.py --json + ansible.builtin.command: + cmd: /usr/local/bin/aw-rus-healthd.py --json + register: aw_post_deploy_health + changed_when: false + + - name: Показать результат aw-rus-healthd + ansible.builtin.debug: + msg: "{{ aw_post_deploy_health.stdout }}" + handlers: - name: Перезагрузить systemd ansible.builtin.systemd: diff --git a/install-kit-awindows-20260427-211240/ansible/deploy_aw_windows.yml b/install-kit-awindows-20260427-211240/ansible/deploy_aw_windows.yml index c78830c..c50b6eb 100644 --- a/install-kit-awindows-20260427-211240/ansible/deploy_aw_windows.yml +++ b/install-kit-awindows-20260427-211240/ansible/deploy_aw_windows.yml @@ -4,6 +4,8 @@ gather_facts: false vars: + ansible_winrm_operation_timeout_sec: 120 + ansible_winrm_read_timeout_sec: 180 aw_windows_repo_root: "{{ playbook_dir | dirname }}" aw_windows_deploy_root: "C:\\Program Files\\AWatch-rus" aw_windows_server_scheme: "http" @@ -14,6 +16,7 @@ aw_windows_package_zip_path: "" aw_windows_domain: "SHARKON2025" aw_windows_users: + - Администратор - user1 - user2 - user3 @@ -23,24 +26,35 @@ aw_windows_users_effective: "{{ (aw_windows_users + aw_windows_extra_users) | unique }}" aw_windows_install_root: "C:\\Program Files\\AWatch-rus\\bin" aw_windows_state_root: "C:\\ProgramData\\AWatch-rus" - aw_windows_afk_enabled: true - aw_windows_window_enabled: true + aw_windows_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_afk_enabled_default: true + aw_windows_window_enabled_default: true + aw_windows_file_ops_enabled: true aw_windows_local_agent_logs_enabled: false aw_windows_incident_capture_enabled: true aw_windows_incident_screenshot_enabled: true aw_windows_incident_artifacts_root: "{{ aw_windows_state_root }}\\incident-artifacts" + aw_windows_forensics_root: "{{ aw_windows_state_root }}\\forensics\\evtx-exports" aw_windows_logon_marker_enabled: true aw_windows_skip_hardening: false aw_windows_rules_path: "{{ aw_windows_deploy_root }}\\windows\\web-category-rules.example.json" aw_windows_policy_path: "{{ aw_windows_deploy_root }}\\windows\\dlp-policy.example.json" aw_windows_validation_remote_path: "{{ aw_windows_state_root }}\\aw_validate_ansible.json" - aw_windows_validation_local_dir: "/tmp/aw-rus-validation" + aw_windows_validation_local_dir: "/tmp/aw-rus-validation-{{ lookup('env','USER') | default('ansible', true) }}" aw_windows_launch_task_pattern: "ActivityWatch Launch *" aw_windows_recovery_task_name: "ActivityWatch Recovery" aw_windows_force_task_restart: true aw_windows_api_smoke_check_enabled: true aw_windows_api_smoke_check_bucket: "" aw_windows_api_smoke_check_limit: 10 + aw_windows_api_smoke_check_window_enabled_default: true + aw_windows_api_smoke_check_window_bucket: "" + aw_windows_api_smoke_check_min_events: 1 aw_windows_fail_on_validation_error: true aw_windows_migration_enabled: true aw_windows_legacy_install_root: "C:\\Program Files\\ActivityWatch-Phase2" @@ -60,6 +74,12 @@ - aw_windows_state_root is defined fail_msg: "Не заданы обязательные переменные Windows-развёртывания." + - name: Нормализовать effective флаги collector'ов и smoke-check + ansible.builtin.set_fact: + aw_windows_afk_enabled_effective: "{{ (aw_windows_afk_enabled | default(aw_windows_afk_enabled_default)) | bool }}" + aw_windows_window_enabled_effective: "{{ (aw_windows_window_enabled | default(aw_windows_window_enabled_default)) | bool }}" + aw_windows_api_smoke_check_window_enabled_effective: "{{ (aw_windows_api_smoke_check_window_enabled | default(aw_windows_api_smoke_check_window_enabled_default)) | bool }}" + - name: Создать каталоги развёртывания ansible.windows.win_file: path: "{{ item }}" @@ -77,16 +97,32 @@ - ActivityWatch.Windows.Common.psm1 - browser-domains-native-collector.ps1 - dlp-endpoint-signals-collector.ps1 + - dlp-policy-client.ps1 - email-outbound-collector.ps1 + - file-operations-collector.ps1 - worktime-session-collector.ps1 + - export-evtx-for-hayabusa.ps1 - migrate-awatch-rus-paths.ps1 - deploy-domain-users.ps1 - deploy-ensemble.ps1 - hardening-recovery.ps1 + - rebuild-worktime-tasks.ps1 - validate-deployment.ps1 - web-category-rules.example.json - dlp-policy.example.json + - name: Нормализовать кодировку PowerShell файлов (UTF-8 BOM для Windows PowerShell) + ansible.windows.win_powershell: + script: | + $ErrorActionPreference = 'Stop' + $toolkitDir = "{{ aw_windows_deploy_root }}\windows" + $encIn = New-Object System.Text.UTF8Encoding($false) + $encOut = New-Object System.Text.UTF8Encoding($true) + Get-ChildItem -LiteralPath $toolkitDir -File -Include *.ps1,*.psm1,*.psd1 | ForEach-Object { + $text = [System.IO.File]::ReadAllText($_.FullName, $encIn) + [System.IO.File]::WriteAllText($_.FullName, $text, $encOut) + } + - name: Загрузить список пользователей для доменного развёртывания ansible.windows.win_copy: dest: "{{ aw_windows_deploy_root }}\\windows\\users.txt" @@ -129,13 +165,22 @@ UserListPath = "{{ aw_windows_deploy_root }}\windows\users.txt" InstallRoot = "{{ aw_windows_install_root }}" StateRoot = "{{ aw_windows_state_root }}" - AfkEnabled = {{ '$true' if (aw_windows_afk_enabled | bool) else '$false' }} - WindowEnabled = {{ '$true' if (aw_windows_window_enabled | bool) else '$false' }} + AfkEnabled = {{ '$true' if (aw_windows_afk_enabled_effective | bool) else '$false' }} + WindowEnabled = {{ '$true' if (aw_windows_window_enabled_effective | bool) else '$false' }} + FileOpsEnabled = {{ '$true' if (aw_windows_file_ops_enabled | bool) else '$false' }} LocalAgentLogsEnabled = {{ '$true' if (aw_windows_local_agent_logs_enabled | bool) else '$false' }} IncidentCaptureEnabled = {{ '$true' if (aw_windows_incident_capture_enabled | bool) else '$false' }} IncidentScreenshotEnabled = {{ '$true' if (aw_windows_incident_screenshot_enabled | bool) else '$false' }} IncidentArtifactsRoot = "{{ aw_windows_incident_artifacts_root }}" + EvtxExportRoot = "{{ aw_windows_forensics_root }}" + EvtxRetentionDays = {{ aw_windows_evtx_retention_days | int }} LogonMarkerEnabled = {{ '$true' if (aw_windows_logon_marker_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 }}" + PolicyEnginePort = {{ aw_windows_policy_engine_port }} + PolicyEngineScheme = "{{ aw_windows_policy_engine_scheme }}" + PolicyRefreshSeconds = {{ aw_windows_policy_refresh_seconds }} CustomRulesPath = "{{ aw_windows_rules_path }}" CustomPolicyPath = "{{ aw_windows_policy_path }}" } @@ -145,89 +190,230 @@ {% if (aw_windows_package_zip_path | default('') | string | length) > 0 %} $params.PackageZipPath = "{{ aw_windows_package_zip_path }}" {% endif %} + {% if (aw_windows_evtx_channels | default([]) | length) > 0 %} + $params.EvtxChannels = @( + {% for channel in aw_windows_evtx_channels %} + "{{ channel }}"{% if not loop.last %},{% endif %} + {% endfor %} + ) + {% endif %} + {% if (aw_windows_hostname_override | default('') | string | length) > 0 %} + $params.AwHostname = "{{ aw_windows_hostname_override }}" + {% endif %} {% if aw_windows_skip_hardening | bool %} $params.SkipHardening = $true {% endif %} + {% if aw_windows_integration_test_enabled | bool %} + $params.IntegrationTestEnabled = $true + {% endif %} & "{{ aw_windows_deploy_root }}\windows\deploy-ensemble.ps1" @params + - name: Удалить лишние ActivityWatch Launch tasks вне текущего deployment-config + ansible.windows.win_powershell: + script: | + $ErrorActionPreference = 'Stop' + $config = Get-Content -Raw -LiteralPath "{{ aw_windows_state_root }}\deployment-config.json" | ConvertFrom-Json + $desired = @($config.userTasks | ForEach-Object { [string]$_.LaunchTaskName }) + foreach ($task in @(Get-ScheduledTask | Where-Object { $_.TaskName -like 'ActivityWatch Launch *' })) { + if ($desired -notcontains [string]$task.TaskName) { + Unregister-ScheduledTask -TaskName $task.TaskName -Confirm:$false -ErrorAction SilentlyContinue + & cmd.exe /c "schtasks /Delete /TN `"$($task.TaskName)`" /F >nul 2>&1" | Out-Null + } + } + - name: Принудительно запустить ActivityWatch recovery и launch tasks when: aw_windows_force_task_restart | bool ansible.windows.win_powershell: script: | $ErrorActionPreference = 'Stop' - Start-ScheduledTask -TaskName "{{ aw_windows_recovery_task_name }}" - Get-ScheduledTask | - Where-Object TaskName -like "{{ aw_windows_launch_task_pattern }}" | - ForEach-Object { Start-ScheduledTask -TaskName $_.TaskName } + + function Get-CollectorKey { + param([string]$CommandLine) + if (-not $CommandLine) { return $null } + $cl = $CommandLine.ToLowerInvariant() + if ($cl -like '*browser-domains-native-collector.ps1*') { return 'browser' } + if ($cl -like '*file-operations-collector.ps1*') { return 'fileops' } + if ($cl -like '*dlp-endpoint-signals-collector.ps1*') { return 'endpoint' } + if ($cl -like '*email-outbound-collector.ps1*') { return 'email' } + if ($cl -like '*worktime-session-collector.ps1*') { return 'worktime' } + return $null + } + + $collectorProcs = Get-CimInstance Win32_Process | + Where-Object { $_.Name -eq 'powershell.exe' -and $_.CommandLine } | + ForEach-Object { + $key = Get-CollectorKey -CommandLine $_.CommandLine + if ($key) { + $groupKey = if ($key -eq 'worktime') { 'worktime::global' } else { '{0}::{1}' -f $key, ([int]$_.SessionId) } + [pscustomobject]@{ + ProcessId = [int]$_.ProcessId + SessionId = [int]$_.SessionId + CreationDate = $_.CreationDate + CollectorKey = $key + GroupKey = $groupKey + } + } + } | + Where-Object { $_ -ne $null } + + # Keep only one process per collector scope: worktime collector is global, others stay per-session. + foreach ($group in ($collectorProcs | Group-Object GroupKey)) { + $ordered = @($group.Group | Sort-Object CreationDate -Descending) + if ($ordered.Count -le 1) { continue } + foreach ($dup in $ordered | Select-Object -Skip 1) { + Stop-Process -Id $dup.ProcessId -Force -ErrorAction SilentlyContinue + } + } + + # Force managed collectors/watchers to reload the freshly deployed scripts. + Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | + Where-Object { + ( + ($_.Name -ieq 'powershell.exe' -or $_.Name -ieq 'pwsh.exe') -and + $_.CommandLine -match 'C:\\ProgramData\\AWatch-rus\\' -and + $_.CommandLine -match '(collector|launch-watchers|recovery-loop)\.ps1' + ) -or + ($_.Name -ieq 'aw-watcher-afk.exe') -or + ($_.Name -ieq 'aw-watcher-window.exe') + } | + ForEach-Object { + Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue + } + + Start-Sleep -Seconds 2 + + # Ensure tasks are enabled (some environments keep them disabled, causing "0s" in WebUI). + try { + Enable-ScheduledTask -TaskName "{{ aw_windows_recovery_task_name }}" -ErrorAction SilentlyContinue | Out-Null + } catch {} + + $config = Get-Content -Raw -LiteralPath "{{ aw_windows_state_root }}\deployment-config.json" | ConvertFrom-Json + foreach ($taskDef in @($config.userTasks)) { + try { Enable-ScheduledTask -TaskName ([string]$taskDef.launchTaskName) -ErrorAction SilentlyContinue | Out-Null } catch {} + } + + $recoveryTask = Get-ScheduledTask -TaskName "{{ aw_windows_recovery_task_name }}" -ErrorAction SilentlyContinue + if ($recoveryTask -and $recoveryTask.State -notin @('Running', 'Queued')) { + Start-ScheduledTask -TaskName "{{ aw_windows_recovery_task_name }}" + } + foreach ($taskDef in @($config.userTasks)) { + $launchTask = Get-ScheduledTask -TaskName ([string]$taskDef.launchTaskName) -ErrorAction SilentlyContinue + if ($launchTask -and $launchTask.State -notin @('Running', 'Queued')) { + Start-ScheduledTask -TaskName ([string]$taskDef.launchTaskName) -ErrorAction SilentlyContinue + } + } - name: Получить Windows hostname для AW smoke-check bucket when: - aw_windows_api_smoke_check_enabled | bool - - aw_windows_afk_enabled | bool ansible.windows.win_command: powershell.exe -NoProfile -Command "$env:COMPUTERNAME" register: aw_windows_hostname_result changed_when: false - - name: Вычислить AW AFK smoke-check bucket + - name: Вычислить AW worktime smoke-check bucket when: - aw_windows_api_smoke_check_enabled | bool - - aw_windows_afk_enabled | bool + - aw_windows_hostname_result.stdout is defined ansible.builtin.set_fact: aw_windows_api_smoke_check_bucket_effective: >- {{ aw_windows_api_smoke_check_bucket if (aw_windows_api_smoke_check_bucket | default('') | string | length) > 0 - else 'aw-watcher-afk_' ~ (aw_windows_hostname_result.stdout | trim) + else 'aw-worktime-sessions_' ~ (aw_windows_hostname_result.stdout | trim) }} - - name: Дождаться свежих AFK событий на AW server + - name: Вычислить AW Window smoke-check bucket + when: + - aw_windows_api_smoke_check_enabled | bool + - aw_windows_api_smoke_check_window_enabled_effective | bool + - aw_windows_window_enabled_effective | bool + - aw_windows_hostname_result.stdout is defined + ansible.builtin.set_fact: + aw_windows_api_smoke_check_window_bucket_effective: >- + {{ + aw_windows_api_smoke_check_window_bucket + if (aw_windows_api_smoke_check_window_bucket | default('') | string | length) > 0 + else 'aw-watcher-window_' ~ (aw_windows_hostname_result.stdout | trim) + }} + + - name: Выполнить AW API smoke-check (worktime bucket должен получать события) when: - aw_windows_api_smoke_check_enabled | bool - - aw_windows_afk_enabled | bool - delegate_to: localhost ansible.builtin.uri: url: "{{ aw_windows_server_scheme }}://{{ aw_windows_server_host }}:{{ aw_windows_server_port }}/api/0/buckets/{{ aw_windows_api_smoke_check_bucket_effective }}/events?limit={{ aw_windows_api_smoke_check_limit }}" method: GET + status_code: 200 return_content: true - register: aw_windows_api_smoke - until: > - aw_windows_api_smoke.status == 200 and - (aw_windows_api_smoke.json | length) > 0 and - ( - aw_windows_api_smoke.json - | selectattr('data.status', 'equalto', 'not-afk') + register: aw_windows_api_smoke_result + failed_when: false + until: >- + (aw_windows_api_smoke_result.status | default(0)) == 200 + and ((aw_windows_api_smoke_result.json | default([])) | length) >= aw_windows_api_smoke_check_min_events + and ( + (aw_windows_api_smoke_result.json | default([])) + | selectattr('data.source', 'equalto', 'worktime-session-collector') | list | length ) > 0 - retries: 10 - delay: 6 + retries: 12 + delay: 5 + ignore_errors: "{{ (not aw_windows_fail_on_validation_error | bool) }}" + delegate_to: localhost + changed_when: false - - name: Выполнить валидацию и сохранить отчёт на целевом Windows host + - name: Выполнить AW API smoke-check (Window bucket должен получать события) + when: + - aw_windows_api_smoke_check_enabled | bool + - 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 }}" + method: GET + status_code: 200 + return_content: true + register: aw_windows_api_smoke_window_result + failed_when: false + until: >- + (aw_windows_api_smoke_window_result.status | default(0)) == 200 + and ((aw_windows_api_smoke_window_result.json | default([])) | length) >= aw_windows_api_smoke_check_min_events + retries: 12 + delay: 5 + ignore_errors: "{{ (not aw_windows_fail_on_validation_error | bool) }}" + delegate_to: localhost + changed_when: false + + - name: Валидировать развёртывание на эндпоинте ansible.windows.win_powershell: script: | $ErrorActionPreference = 'Stop' - $report = & "{{ aw_windows_deploy_root }}\windows\validate-deployment.ps1" ` + $result = & "{{ aw_windows_deploy_root }}\windows\validate-deployment.ps1" ` -ConfigPath "{{ aw_windows_state_root }}\deployment-config.json" - $report | ConvertTo-Json -Depth 12 | Out-File -FilePath "{{ aw_windows_validation_remote_path }}" -Encoding utf8 - if ({{ '$true' if (aw_windows_fail_on_validation_error | bool) else '$false' }} -and -not [bool]$report.overallOk) { - throw "Проверка развёртывания ActivityWatch завершилась ошибкой. Отчёт: {{ aw_windows_validation_remote_path }}" - } + $result | ConvertTo-Json -Depth 12 | Out-File -FilePath "{{ aw_windows_validation_remote_path }}" -Encoding utf8 + return $result - - name: Создать локальный каталог для validation reports + - name: Создать локальную директорию для отчётов валидации ansible.builtin.file: path: "{{ aw_windows_validation_local_dir }}" state: directory mode: "0755" delegate_to: localhost - - name: Забрать validation report + - name: Стянуть отчёт валидации с эндпоинта ansible.builtin.fetch: src: "{{ aw_windows_validation_remote_path }}" dest: "{{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_ansible.json" flat: true - - name: Показать путь к отчёту - ansible.builtin.debug: - msg: - - "Windows/RDP развёртывание завершено на {{ inventory_hostname }}." - - "Отчёт проверки: {{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_ansible.json" + - name: Проверить статус валидации + ansible.builtin.shell: | + python3 - <<'PY' + import json, sys + with open('{{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_ansible.json', 'r', encoding='utf-8-sig') as f: + data = json.load(f) + if not data.get('overallOk', False): + failed = ", ".join(data.get("summary", {}).get("failedSections", [])) or "unknown" + print(f"Validation failed for {{ inventory_hostname }}: {failed}") + sys.exit(1) + PY + delegate_to: localhost + when: aw_windows_fail_on_validation_error | bool diff --git a/install-kit-awindows-20260427-211240/ansible/group_vars/all.example.yml b/install-kit-awindows-20260427-211240/ansible/group_vars/all.example.yml index be28ad3..fada2ca 100644 --- a/install-kit-awindows-20260427-211240/ansible/group_vars/all.example.yml +++ b/install-kit-awindows-20260427-211240/ansible/group_vars/all.example.yml @@ -4,23 +4,69 @@ aw_server_bind_host: "0.0.0.0" aw_server_port: 5600 aw_server_webui_dir: "/opt/activitywatch/webui-ru" aw_server_data_dir: "/var/lib/activitywatch" +aw_server_db_path: "/var/lib/activitywatch/.local/share/activitywatch/aw-server-rust/sqlite.db" aw_server_log_dir: "/var/log/activitywatch" aw_server_user: "activitywatch" aw_server_group: "activitywatch" aw_worktime_report_base: "http://10.10.10.13:5610" aw_worktime_timezone: "Europe/Moscow" +aw_worktime_influx_enabled: false +aw_worktime_influx_url: "http://10.10.10.10:8086" +aw_worktime_influx_org: "proxmox" +aw_worktime_influx_bucket: "aw_metrics" +aw_worktime_influx_hosts: "SHARKON2025" +aw_worktime_influx_days: "today,yesterday" +aw_worktime_influx_token: "" +aw_dlp_influx_enabled: false +aw_dlp_influx_url: "http://10.10.10.10:8086" +aw_dlp_influx_org: "proxmox" +aw_dlp_influx_bucket: "aw_metrics" +aw_dlp_influx_hosts: "SHARKON2025" +aw_dlp_influx_lookback_days: 30 +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_state_dir: "{{ aw_server_data_dir }}/health" +aw_rus_health_validation_dir: "{{ aw_rus_health_state_dir }}/windows-validation" aw_repo_root: "{{ playbook_dir | dirname }}" -# Опционально: применить базовые категории и views для рабочего времени через AW settings API. -# Внимание: это перезаписывает существующие server-side settings/classes/views. -aw_apply_worktime_settings: false +# Применить базовые категории и views для рабочего времени через AW settings API. +# При прод-обновлениях это нужно оставлять включённым, иначе UI остаётся без views/classes. +aw_apply_worktime_settings: true + +# Дополнительные origin для aw-server-rust CORS. +# Обязательно включите тот origin, с которого реально открывается Web UI. +aw_server_cors_origins: + - "http://127.0.0.1:5600" + - "http://localhost:5600" + - "http://10.10.10.13:5600" + - "http://aw-server:5600" # Опциональные значения периода рабочего времени в Web UI. # startOfDay задаёт границу дня и стартовое время окна отчёта. # durationDefault задаёт диапазон по умолчанию в секундах. # # Рекомендуется явно задать рабочий интервал и дать playbook вычислить duration. -aw_worktime_from: "08:00" +aw_worktime_from: "00:00" aw_worktime_to: "17:00" aw_worktime_start_of_day: "{{ aw_worktime_from }}" +aw_server_always_active_pattern: "aw-watcher-window" +aw_server_landingpage: "/activity/SHARKON2025/view/" +aw_health_strict_fileops: 0 + +aw_dlp_policy_engine_enabled: true +aw_dlp_policy_engine_bind_host: "0.0.0.0" +aw_dlp_policy_engine_port: 5601 +aw_dlp_policy_engine_db_path: "{{ aw_server_data_dir }}/dlp-policy-engine.sqlite" +aw_dlp_content_analysis_enabled: true +aw_dlp_integrations_enabled: true +aw_dlp_case_management_enabled: true +aw_dlp_case_bind_host: "0.0.0.0" +aw_dlp_case_port: 5602 +aw_dlp_case_db_path: "/opt/activitywatch/dlp-case-management/cases.db" +aw_dlp_compliance_enabled: true +aw_dlp_compliance_report_dir: "/opt/activitywatch/dlp-compliance/reports" +aw_dlp_compliance_template_path: "/opt/activitywatch/dlp-compliance/templates/152-fz-report.html" +aw_server_post_deploy_health_check_enabled: true diff --git a/install-kit-awindows-20260427-211240/ansible/group_vars/windows.example.yml b/install-kit-awindows-20260427-211240/ansible/group_vars/windows.example.yml index 30c20c3..4dfe821 100644 --- a/install-kit-awindows-20260427-211240/ansible/group_vars/windows.example.yml +++ b/install-kit-awindows-20260427-211240/ansible/group_vars/windows.example.yml @@ -8,6 +8,7 @@ aw_windows_package_url: "https://github.com/ActivityWatch/activitywatch/releases aw_windows_package_zip_path: "" aw_windows_domain: "SHARKON2025" aw_windows_users: + - Администратор - user1 - user2 - user3 @@ -21,12 +22,23 @@ aw_windows_extra_users: [] # Единые Windows/RDP пути: те же, что использует InnoSetup. aw_windows_install_root: "C:\\Program Files\\AWatch-rus\\bin" aw_windows_state_root: "C:\\ProgramData\\AWatch-rus" +aw_windows_hostname_override: "" # Например: SHARKON2025 aw_windows_afk_enabled: true aw_windows_window_enabled: true +aw_windows_file_ops_enabled: true aw_windows_local_agent_logs_enabled: false aw_windows_incident_capture_enabled: true aw_windows_incident_screenshot_enabled: true aw_windows_incident_artifacts_root: "{{ aw_windows_state_root }}\\incident-artifacts" +aw_windows_forensics_root: "{{ aw_windows_state_root }}\\forensics\\evtx-exports" +aw_windows_evtx_retention_days: 14 +aw_windows_evtx_channels: + - Security + - System + - Application + - Microsoft-Windows-PowerShell/Operational + - Microsoft-Windows-TerminalServices-LocalSessionManager/Operational + - Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational aw_windows_logon_marker_enabled: true aw_windows_skip_hardening: false @@ -34,7 +46,7 @@ aw_windows_rules_path: "{{ aw_windows_deploy_root }}\\windows\\web-category-rule aw_windows_policy_path: "{{ aw_windows_deploy_root }}\\windows\\dlp-policy.example.json" aw_windows_validation_remote_path: "{{ aw_windows_state_root }}\\aw_validate_ansible.json" -aw_windows_validation_local_dir: "/tmp/aw-rus-validation" +aw_windows_validation_local_dir: "/tmp/aw-rus-validation-{{ lookup('env','USER') | default('ansible', true) }}" aw_windows_fail_on_validation_error: true # Безопасная миграция текущего прода со старых путей в единый профиль AWatch-rus. diff --git a/install-kit-awindows-20260427-211240/ansible/install_full_stack.yml b/install-kit-awindows-20260427-211240/ansible/install_full_stack.yml index cd3a7f6..f2694c9 100644 --- a/install-kit-awindows-20260427-211240/ansible/install_full_stack.yml +++ b/install-kit-awindows-20260427-211240/ansible/install_full_stack.yml @@ -5,6 +5,7 @@ # 2) развёртывание AW server на хостах [aw_server] # 3) развёртывание Windows/RDP collector'ов на [aw_windows] # 4) развёртывание pfSense poller'а на [aw_pfsense_pollers] +# 5) импорт Grafana dashboard'ов на [grafana] # # Примечания: # - Заполняйте только нужные группы inventory для своего окружения. @@ -14,3 +15,4 @@ - import_playbook: deploy_aw_server.yml - import_playbook: deploy_aw_windows.yml - import_playbook: deploy_aw_pfsense_poller.yml +- import_playbook: deploy_grafana_dashboards.yml diff --git a/install-kit-awindows-20260427-211240/ansible/inventory.example.ini b/install-kit-awindows-20260427-211240/ansible/inventory.example.ini index e1c77ff..8c8b1b0 100644 --- a/install-kit-awindows-20260427-211240/ansible/inventory.example.ini +++ b/install-kit-awindows-20260427-211240/ansible/inventory.example.ini @@ -6,7 +6,12 @@ aw-ct ansible_host=10.20.30.13 ansible_user=root ansible_port=22 [aw_windows] # Примечание: в русифицированных Windows часто нужен "Администратор", а не "Administrator". -win-node1 ansible_host=192.168.100.21 ansible_user=Администратор ansible_password=CHANGE_ME ansible_connection=winrm ansible_winrm_transport=ntlm ansible_port=5985 ansible_winrm_server_cert_validation=ignore +win-node1 ansible_host=192.168.100.18 ansible_user=Администратор ansible_password=CHANGE_ME ansible_connection=winrm ansible_winrm_transport=ntlm ansible_port=5985 ansible_winrm_server_cert_validation=ignore [aw_pfsense_pollers] # pfsense-poller1 ansible_host=192.168.100.30 ansible_user=root ansible_port=22 + +[grafana] +# Для API-import playbook достаточно указать grafana_url. +# SSH-подключение не требуется: playbook работает через HTTP API с control host. +grafana-main grafana_url=http://10.20.30.11:3000 diff --git a/install-kit-awindows-20260427-211240/ansible/provision_proxmox_ct_and_deploy_aw.yml b/install-kit-awindows-20260427-211240/ansible/provision_proxmox_ct_and_deploy_aw.yml index 40c6382..1ac786e 100644 --- a/install-kit-awindows-20260427-211240/ansible/provision_proxmox_ct_and_deploy_aw.yml +++ b/install-kit-awindows-20260427-211240/ansible/provision_proxmox_ct_and_deploy_aw.yml @@ -11,6 +11,9 @@ - activitywatch-server.service - aw-worktime-api.py - aw-worktime-api.service + - aw-worktime-ui-bridge.py + - aw-worktime-ui-bridge.service + - aw-worktime-ui-bridge.timer - aw-worktime-panel.js - aw-server.env.example - aw-ru-patch.js diff --git a/install-kit-awindows-20260427-211240/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml b/install-kit-awindows-20260427-211240/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml index 5452935..e0ff9bc 100644 --- a/install-kit-awindows-20260427-211240/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml +++ b/install-kit-awindows-20260427-211240/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml @@ -11,6 +11,9 @@ - activitywatch-server.service - aw-worktime-api.py - aw-worktime-api.service + - aw-worktime-ui-bridge.py + - aw-worktime-ui-bridge.service + - aw-worktime-ui-bridge.timer - aw-worktime-panel.js - aw-server.env.example - aw-ru-patch.js diff --git a/install-kit-awindows-20260427-211240/ansible/tasks/provision_ct_and_deploy_aw.yml b/install-kit-awindows-20260427-211240/ansible/tasks/provision_ct_and_deploy_aw.yml index 0551e2f..a2610fd 100644 --- a/install-kit-awindows-20260427-211240/ansible/tasks/provision_ct_and_deploy_aw.yml +++ b/install-kit-awindows-20260427-211240/ansible/tasks/provision_ct_and_deploy_aw.yml @@ -159,6 +159,7 @@ AW_SERVER_LOG_DIR={{ aw_server_log_dir }} AW_SERVER_USER={{ aw_server_user }} AW_SERVER_GROUP={{ aw_server_group }} + AW_SERVER_URL=http://127.0.0.1:{{ aw_server_port }} AW_WORKTIME_REPORT_BASE={{ aw_worktime_report_base }} AW_WORKTIME_TZ={{ aw_worktime_timezone }} no_log: true diff --git a/install-kit-awindows-20260427-211240/aw-server/activitywatch-server.service b/install-kit-awindows-20260427-211240/aw-server/activitywatch-server.service index e8f26e3..89d31e3 100755 --- a/install-kit-awindows-20260427-211240/aw-server/activitywatch-server.service +++ b/install-kit-awindows-20260427-211240/aw-server/activitywatch-server.service @@ -9,7 +9,7 @@ EnvironmentFile=/etc/activitywatch/aw-server.env User=__AW_SERVER_USER__ Group=__AW_SERVER_GROUP__ WorkingDirectory=__AW_SERVER_DATA_DIR__ -ExecStart=/bin/sh -lc 'exec /opt/activitywatch/bin/aw-server-rust --host "$AW_SERVER_BIND_HOST" --port "$AW_SERVER_PORT"' +ExecStart=/bin/sh -lc 'exec /opt/activitywatch/bin/aw-server-rust --host "$AW_SERVER_BIND_HOST" --port "$AW_SERVER_PORT" --dbpath "$AW_SERVER_DB_PATH" --webpath "$AW_SERVER_WEBUI_DIR"' Restart=on-failure RestartSec=5s StateDirectory=activitywatch @@ -17,7 +17,7 @@ LogsDirectory=activitywatch NoNewPrivileges=true PrivateTmp=true ProtectSystem=full -ProtectHome=true +ProtectHome=read-only LimitNOFILE=65535 [Install] diff --git a/install-kit-awindows-20260427-211240/aw-server/apply_webui_ru_patch.sh b/install-kit-awindows-20260427-211240/aw-server/apply_webui_ru_patch.sh index 32e53fa..343ee06 100755 --- a/install-kit-awindows-20260427-211240/aw-server/apply_webui_ru_patch.sh +++ b/install-kit-awindows-20260427-211240/aw-server/apply_webui_ru_patch.sh @@ -11,6 +11,8 @@ 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}" +CASE_PORT="${AW_DLP_CASE_PORT:-5602}" +CASE_BASE="${AW_DLP_CASE_PUBLIC_BASE:-}" PATCH_JS_SRC="/root/bootstrap/aw-ru-patch.js" SW_CLEANUP_SRC="/root/bootstrap/aw-sw-cleanup.js" WORKTIME_PANEL_SRC="/root/bootstrap/aw-worktime-panel.js" @@ -35,6 +37,17 @@ CATEGORY_HELPER_REPLACEMENT='hostname:t.hostnameChoices.filter((function(t){retu [[ -f "$HOST_GROUPS_SRC" ]] || { echo "missing $HOST_GROUPS_SRC" >&2; exit 1; } [[ -f "$INDEX_HTML" ]] || { echo "missing $INDEX_HTML" >&2; exit 1; } +if [[ ! -s "$INDEX_HTML" ]]; then + latest_nonempty_backup="$(find "$WEBUI_DIR" -maxdepth 1 -type f -name 'index.html.bak.*' -size +0c | sort | tail -n 1 || true)" + if [[ -n "$latest_nonempty_backup" ]]; then + cp "$latest_nonempty_backup" "$INDEX_HTML" + echo "restored empty index.html from backup: $latest_nonempty_backup" + else + echo "index.html is empty and no non-empty backup exists: $INDEX_HTML" >&2 + exit 1 + fi +fi + install -d "$WEBUI_DIR/js" install -m 0644 "$PATCH_JS_SRC" "$PATCH_TARGET" install -m 0644 "$SW_CLEANUP_SRC" "$SW_TARGET" @@ -46,6 +59,21 @@ patch_hash="$(sha1sum "$PATCH_TARGET" | awk '{print substr($1,1,12)}')" sw_hash="$(sha1sum "$SW_TARGET" | awk '{print substr($1,1,12)}')" worktime_panel_hash="$(sha1sum "$WORKTIME_PANEL_TARGET" | awk '{print substr($1,1,12)}')" +if [[ -z "$CASE_BASE" ]]; then + CASE_BASE="$(python3 - "$REPORT_BASE" "$CASE_PORT" <<'PY' +from urllib.parse import urlsplit, urlunsplit +import sys + +report_base = sys.argv[1] +case_port = sys.argv[2] +parts = urlsplit(report_base) +hostname = parts.hostname or "10.10.10.13" +scheme = parts.scheme or "http" +print(urlunsplit((scheme, f"{hostname}:{case_port}", "", "", ""))) +PY +)" +fi + python3 - "$WORKTIME_PANEL_TARGET" "$REPORT_BASE" <<'PY' from pathlib import Path import sys @@ -57,16 +85,18 @@ text = text.replace("__AW_WORKTIME_REPORT_BASE__", report_base) path.write_text(text) PY -python3 - "$INDEX_HTML" "$sw_hash" "$patch_hash" "$worktime_panel_hash" "$REPORT_BASE" <<'PY' +python3 - "$INDEX_HTML" "$sw_hash" "$patch_hash" "$worktime_panel_hash" "$REPORT_BASE" "$CASE_BASE" <<'PY' from pathlib import Path import re import sys +from urllib.parse import urlsplit path = Path(sys.argv[1]) sw_hash = sys.argv[2] patch_hash = sys.argv[3] panel_hash = sys.argv[4] report_base = sys.argv[5] +case_base = sys.argv[6] content = path.read_text() content = re.sub( @@ -74,23 +104,33 @@ content = re.sub( '', content, ) -content = re.sub(r"; frame-src 'self' [^\";>]*", "", content) -content = content.replace( - "script-src 'self' 'unsafe-eval'", - f"script-src 'self' 'unsafe-eval'; frame-src 'self' {report_base}", - 1, +content = re.sub(r";\s*frame-src 'self' [^\";>]*", "", content) +content = re.sub(r";\s*connect-src 'self' [^\";>]*", "", content) +report_origin = urlsplit(report_base) +case_origin = urlsplit(case_base) +connect_targets = " ".join( + [ + f"{report_origin.scheme}://{report_origin.netloc}", + f"{case_origin.scheme}://{case_origin.netloc}", + ] +) +content = re.sub( + r"script-src 'self' 'unsafe-eval'(?:;\s*connect-src 'self' [^\";>]*)?(?:;\s*frame-src 'self' [^\";>]*)?", + f"script-src 'self' 'unsafe-eval'; connect-src 'self' {connect_targets}; frame-src 'self' {report_base}", + content, + count=1, ) content = content.replace( "", - f'', + ( + f'' + f'' + ), 1, ) content = content.replace( "", - ( - f'' - f'' - ), + f'', 1, ) if 'id="aw-report-links"' not in content: @@ -103,6 +143,11 @@ path.write_text(content) PY cp "$SW_CLEANUP_SRC" "$SERVICE_WORKER" +if [[ ! -s "$INDEX_HTML" ]]; then + echo "index.html became empty after RU patch: $INDEX_HTML" >&2 + exit 1 +fi + trends_chunk="$(grep -Rsl "$TRENDS_NEEDLE" "$WEBUI_DIR/js"/*.js 2>/dev/null | head -n 1 || true)" if [[ -n "$trends_chunk" ]]; then cp "$trends_chunk" "$trends_chunk.bak.$TS" diff --git a/install-kit-awindows-20260427-211240/aw-server/aw-host-groups.json b/install-kit-awindows-20260427-211240/aw-server/aw-host-groups.json index f3d0118..7432f60 100644 --- a/install-kit-awindows-20260427-211240/aw-server/aw-host-groups.json +++ b/install-kit-awindows-20260427-211240/aw-server/aw-host-groups.json @@ -29,6 +29,21 @@ { "label": "DLP", "type": "bucket", "bucket_prefix": "aw-dlp-endpoint-signals_" } ] }, + { + "id": "linux-remote", + "name": "Linux remote workers", + "description": "Linux-хосты удалённых сотрудников: GUI активность, SSH/console и browser admin UI.", + "patterns": [ + "^(LINUX-WS|LINUX-DESKTOP|LX-|DESKTOP-|ADMIN-|WORKSTATION-|DEVBOX-)" + ], + "links": [ + { "label": "Активность", "type": "activity" }, + { "label": "SSH сессии", "type": "bucket", "bucket_prefix": "aw-ssh-sessions_" }, + { "label": "Команды shell", "type": "bucket", "bucket_prefix": "aw-console-commands_" }, + { "label": "Web категории", "type": "bucket", "bucket_prefix": "aw-detmir-web-category_" }, + { "label": "Все бакеты", "type": "buckets" } + ] + }, { "id": "virtual-infra", "name": "Virtual servers + Proxmox", diff --git a/install-kit-awindows-20260427-211240/aw-server/aw-ru-patch.js b/install-kit-awindows-20260427-211240/aw-server/aw-ru-patch.js index 91cfa39..0e7b585 100755 --- a/install-kit-awindows-20260427-211240/aw-server/aw-ru-patch.js +++ b/install-kit-awindows-20260427-211240/aw-server/aw-ru-patch.js @@ -1,6 +1,10 @@ (function () { - window.__awRuPatchVersion = "template-v12-activity-heading-ru"; - document.documentElement.setAttribute("data-aw-ru-patch", "template-v12-activity-heading-ru"); + if (window.__awRuPatchBootstrapped) { + 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"); const exact = new Map([ ["ActivityWatch", "АктивВотч"], @@ -370,25 +374,43 @@ return /^pve[-_]/i.test(String(host || "")); } + function isLikelyClientHost(host) { + const value = String(host || "").trim(); + if (!value) return false; + if (/^(?:unknown|undefined|null)$/i.test(value)) return false; + if (/^(?:localhost|127\.0\.0\.1|0\.0\.0\.0|::1)$/i.test(value)) return false; + if (/^(?:\d{1,3}\.){3}\d{1,3}$/.test(value)) return false; + if (value.indexOf(":") !== -1 && /^[0-9a-f:\[\]]+$/i.test(value)) return false; + return true; + } + function enforceSafeActivityViewForPveHost() { const hash = window.location.hash || ""; - const match = hash.match(/^#\/activity\/([^/]+)\/day\/([^/]+)\/view\/([^/?#]+)/i); + const match = hash.match(/^#\/activity\/([^/]+)(?:\/day\/([^/]+))?\/view\/([^/?#]+)/i); if (!match) return; const host = decodeURIComponent(match[1] || ""); - const day = decodeURIComponent(match[2] || ""); + const day = match[2] ? decodeURIComponent(match[2]) : ""; const viewId = decodeURIComponent(match[3] || ""); - if (!isPveLikeHost(host)) return; - const safeHash = "#/activity/" + encodeURIComponent(host) + "/day/" + encodeURIComponent(day) + "/view/" + encodeURIComponent("pve_audit"); - if (safeHash !== hash && !/^pve_audit$/i.test(viewId)) { - window.location.replace(safeHash); + const prefix = day + ? "#/activity/" + encodeURIComponent(host) + "/day/" + encodeURIComponent(day) + "/view/" + : "#/activity/" + encodeURIComponent(host) + "/view/"; + if (isPveLikeHost(host)) { + const safeHash = prefix + encodeURIComponent("pve_audit"); + if (safeHash !== hash && !/^pve_audit$/i.test(viewId)) { + window.location.replace(safeHash); + } + return; + } + if (/^pve_audit$/i.test(viewId)) { + window.location.replace(prefix + encodeURIComponent("summary")); } } function getDlpHostFromSettings(settings) { const routeHost = getCurrentHostFromHash(); - if (routeHost) return routeHost; + if (isLikelyClientHost(routeHost)) return routeHost; const bucketHost = getDlpHostFromBucketId(getDlpBucketIdFromHash()); - if (bucketHost) return bucketHost; + if (isLikelyClientHost(bucketHost)) return bucketHost; return getTrendsHostFromSettings(settings); } @@ -680,6 +702,19 @@ { label: "DLP", type: "bucket", bucket_prefix: "aw-dlp-endpoint-signals_" } ] }, + { + id: "linux-remote", + name: "Linux remote workers", + description: "Linux-хосты удалённых сотрудников: GUI активность, SSH/console и browser admin UI.", + patterns: ["^(LINUX-WS|LINUX-DESKTOP|LX-|DESKTOP-|ADMIN-|WORKSTATION-|DEVBOX-)"], + links: [ + { label: "Активность", type: "activity" }, + { label: "SSH сессии", type: "bucket", bucket_prefix: "aw-ssh-sessions_" }, + { label: "Команды shell", type: "bucket", bucket_prefix: "aw-console-commands_" }, + { label: "Web категории", type: "bucket", bucket_prefix: "aw-detmir-web-category_" }, + { label: "Все бакеты", type: "buckets" } + ] + }, { id: "virtual-infra", name: "Virtual servers + Proxmox", @@ -740,7 +775,15 @@ const prefixes = [ "aw-watcher-window_", "aw-watcher-afk_", + "aw-console-commands_", + "aw-ssh-sessions_", + "aw-linux-web-context_", + "aw-detmir-web-category_", "aw-dlp-endpoint-signals_", + "aw-session-events_", + "aw-worktime-sessions_", + "aw-pve-webadmin-events_", + "aw-pve-task-events_", "aw-dlp-incidents_", "aw-pfsense-health_", "aw-pfsense-gateways_", @@ -770,7 +813,27 @@ return result; } - function matchHostGroup(host, groups) { + function hostHasBucketPrefix(hostBuckets, prefix) { + return (hostBuckets || []).some(function (bucketId) { + return String(bucketId || "").indexOf(prefix) === 0; + }); + } + + function matchHostGroup(host, groups, hostBuckets) { + const bucketList = hostBuckets || []; + if (hostHasBucketPrefix(bucketList, "aw-dlp-endpoint-signals_") || hostHasBucketPrefix(bucketList, "aw-session-events_")) { + return "windows-rdp"; + } + if ( + hostHasBucketPrefix(bucketList, "aw-console-commands_") || + hostHasBucketPrefix(bucketList, "aw-ssh-sessions_") || + hostHasBucketPrefix(bucketList, "aw-linux-web-context_") || + hostHasBucketPrefix(bucketList, "aw-detmir-web-category_") + ) { + if (!hostHasBucketPrefix(bucketList, "aw-pve-webadmin-events_") && !hostHasBucketPrefix(bucketList, "aw-pve-task-events_")) { + return "linux-remote"; + } + } for (const group of groups) { const patterns = Array.isArray(group.patterns) ? group.patterns : []; for (const pattern of patterns) { @@ -813,7 +876,7 @@ grouped.set("__ungrouped__", []); Array.from(hostBuckets.keys()).sort().forEach(function (host) { - const groupId = matchHostGroup(host, groups) || "__ungrouped__"; + const groupId = matchHostGroup(host, groups, hostBuckets.get(host) || []) || "__ungrouped__"; grouped.get(groupId).push(host); }); @@ -869,7 +932,7 @@ center.setAttribute("data-aw-ru-host-groups", "1"); center.innerHTML = '

Разделы хостов

' + - '

Здесь хосты разделены на пользовательские Windows RDP и инфраструктурные виртуальные серверы/Proxmox.

' + + '

Здесь хосты разделены на Windows RDP, Linux remote workers и инфраструктурные узлы.

' + '

Загрузка...

'; heading.parentElement.insertBefore(center, heading.nextSibling); } @@ -885,9 +948,10 @@ const hideSuppressed = center.querySelector("[data-aw-ru-hide-suppressed]") && center.querySelector("[data-aw-ru-hide-suppressed]").checked; const rows = []; for (const event of state.events) { + const data = event.data || {}; + if (String(data.signalType || "").toLowerCase() === "self_test") continue; const matchedRule = state.activeRules.find(function (rule) { return ruleMatchesEvent(rule, event); }) || null; if (hideSuppressed && matchedRule) continue; - const data = event.data || {}; const eventKey = buildDlpKey(event); rows.push( '' + @@ -907,6 +971,7 @@ '' + '' + '' + + '' + "" + "" ); @@ -977,6 +1042,58 @@ }, 1); } + function getCaseApiBase() { + if (window.__awCaseApiBase && typeof window.__awCaseApiBase === "string") { + return window.__awCaseApiBase.replace(/\/+$/, ""); + } + try { + const origin = window.location.origin || ""; + if (/:\d+$/.test(origin)) return origin.replace(/:\d+$/, ":5602"); + return origin + ":5602"; + } catch (error) { + return "http://127.0.0.1:5602"; + } + } + + async function caseApi(path, init) { + const response = await fetch(getCaseApiBase() + path, Object.assign({ credentials: "omit" }, init || {})); + if (!response.ok) throw new Error("Case API HTTP " + response.status); + if (response.status === 204) return null; + return response.json(); + } + + async function createCaseFromEvent(host, event, row) { + const data = event.data || {}; + if (String(data.signalType || "").toLowerCase() === "self_test") { + throw new Error("self_test не должен превращаться в кейс"); + } + const verdict = row.querySelector("[data-aw-ru-dlp-verdict]").value; + const category = row.querySelector("[data-aw-ru-dlp-category]").value.trim(); + const comment = row.querySelector("[data-aw-ru-dlp-comment]").value.trim(); + const incidentId = buildDlpKey(event); + const title = "DLP " + (data.signalType || "incident") + " · " + (data.username || data.owner || host || "unknown"); + return caseApi("/api/0/dlp/cases", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + incident_id: incidentId, + host: host, + title: title, + severity: verdict === "incident" ? "high" : "medium", + source_bucket: getDlpBucketIdFromHash(), + source_event_ts: event.timestamp, + evidence: { + signalType: data.signalType || "", + username: data.username || data.owner || "", + documentName: data.documentName || "", + printerName: data.printerName || "", + category: category, + comment: comment + } + }) + }); + } + async function saveDlpRule(host, event, row) { const bucketId = "aw-dlp-rules_" + host; await ensureAwBucket(bucketId, "aw-dlp-rules", "aw.dlp.rule", host); @@ -1030,9 +1147,57 @@ message.textContent = "Ошибка сохранения правила: " + error.message; } }); + row.querySelector("[data-aw-ru-create-case]").addEventListener("click", async function () { + const message = center.querySelector("[data-aw-ru-dlp-message]"); + try { + const created = await createCaseFromEvent(host, event, row); + await renderCaseManager(center, host); + message.textContent = "Кейс создан: #" + (created && created.id ? created.id : "?"); + } catch (error) { + message.textContent = "Ошибка создания кейса: " + error.message; + } + }); }); } + async function renderCaseManager(center, host) { + const tbody = center.querySelector("[data-aw-ru-dlp-cases]"); + if (!tbody) return; + try { + const cases = await caseApi("/api/0/dlp/cases?host=" + encodeURIComponent(host) + "&limit=100", { method: "GET" }); + function renderCaseDfir(c) { + const hayabusa = c && c.forensics && c.forensics.hayabusa; + if (!hayabusa) return ""; + const status = String(hayabusa.status || ""); + const mode = String(hayabusa.mode || ""); + const reportDir = String(hayabusa.report_dir || ""); + const title = reportDir ? ' title="' + escapeHtml(reportDir) + '"' : ""; + return 'Hayabusa ' + escapeHtml(status) + (mode ? " · " + escapeHtml(mode) : "") + ''; + } + const rows = (cases || []).map(function (c) { + return ( + "" + + "" + escapeHtml(String(c.id || "")) + "" + + "" + escapeHtml(String(c.status || "")) + "" + + "" + escapeHtml(String(c.severity || "")) + "" + + "" + escapeHtml(String(c.title || "")) + "" + + "" + escapeHtml(String(c.assignee || "")) + "" + + "" + escapeHtml(String(c.incident_id || "")) + "" + + "" + renderCaseDfir(c) + "" + + "" + escapeHtml(String(c.updated_at || c.created_at || "")) + "" + + "" + ); + }); + tbody.innerHTML = rows.length ? rows.join("") : 'Кейсов нет.'; + const status = center.querySelector("[data-aw-ru-dlp-cases-status]"); + if (status) status.textContent = "Кейсов: " + (cases || []).length; + } catch (error) { + tbody.innerHTML = 'Ошибка загрузки кейсов: ' + escapeHtml(error.message) + ''; + const status = center.querySelector("[data-aw-ru-dlp-cases-status]"); + if (status) status.textContent = "Кейсы недоступны"; + } + } + async function setDlpRuleEnabled(host, ruleEvent, enabled) { const bucketId = "aw-dlp-rules_" + host; await ensureAwBucket(bucketId, "aw-dlp-rules", "aw.dlp.rule", host); @@ -1187,6 +1352,7 @@ state.reviews = []; } renderDlpTableRows(center, host); + await renderCaseManager(center, host); center.querySelector("[data-aw-ru-dlp-message]").textContent = "DLP review центр обновлен."; } catch (error) { center.querySelector("[data-aw-ru-dlp-message]").textContent = "Ошибка загрузки DLP-событий: " + error.message; @@ -1242,6 +1408,16 @@ 'Загрузка...' + '' + '' + + '
' + + '
' + + '
Case Management
' + + '
Кейсов: 0
' + + '
' + + '' + + '' + + '' + + '
IDСтатусSeverityЗаголовокИсполнительIncident IDDFIRОбновлено
Загрузка...
' + + '
' + '
'; heading.parentElement.insertBefore(center, heading.nextSibling); center.querySelector("[data-aw-ru-refresh-dlp]").addEventListener("click", function () { @@ -1378,6 +1554,16 @@ } } + function hidePveAuditTabForRegularHost(root) { + const hash = window.location.hash || ""; + const match = hash.match(/^#\/activity\/([^/]+)/i); + const host = match && match[1] ? decodeURIComponent(match[1]) : ""; + if (!host || isPveLikeHost(host)) return; + Array.from(root.querySelectorAll('a[href*="/view/pve_audit"]')).forEach(function (link) { + link.style.display = "none"; + }); + } + function injectDlpAlertsCenter(root) { if (!isAlertsRoute()) return; const host = window.__awRuPatchSettingsHost || getCurrentHostFromHash(); @@ -1414,18 +1600,27 @@ center.setAttribute("data-aw-ru-loaded", "1"); refreshDlpAlertsCenter(center, host); } + Array.from(heading.parentElement.children).forEach(function (child) { + if (child === heading || child === center) return; + child.style.display = "none"; + }); } let trendsRedirectInFlight = false; let settingsHostFetchInFlight = false; let applyPatchScheduled = false; let networkPatchesInstalled = false; + let dlpOverlayFailureCount = 0; + let applyPatchInFlight = false; + let observerAttached = false; + let staticPatchRouteKey = ""; function getTrendsHostFromSettings(settings) { if (!settings || typeof settings !== "object") return ""; const landingpage = typeof settings.landingpage === "string" ? settings.landingpage : ""; const match = landingpage.match(/\/activity\/([^/]+)/); - return match && match[1] ? match[1] : ""; + const host = match && match[1] ? decodeURIComponent(match[1]) : ""; + return isLikelyClientHost(host) ? host : ""; } function getTrendsPath(hash) { @@ -1477,6 +1672,9 @@ .finally(function () { settingsHostFetchInFlight = false; injectDlpNavigation(document.body); + if (isAlertsRoute()) { + scheduleApplyPatch(); + } }); } @@ -1492,14 +1690,25 @@ .map(function (bucketId) { return bucketId.replace(/^aw-watcher-window_/i, ""); }) .filter(Boolean) .filter(function (host) { return !/^unknown$/i.test(host); }); - if (settingsHost && hosts.indexOf(settingsHost) >= 0) return settingsHost; - if (settingsHost) return settingsHost; + if (isLikelyClientHost(settingsHost) && hosts.indexOf(settingsHost) >= 0) return settingsHost; + if (isLikelyClientHost(settingsHost) && !hosts.length) return settingsHost; hosts.sort(); return hosts[0] || ""; } function rewriteUnknownCategoryBuilderQueryBody(body) { if (typeof body !== "string") return body; + function stripUnknownBucketTokens(raw) { + return raw + .replace(/aw-watcher-window_unknown/gi, "__AW_RU_UNKNOWN_WINDOW__") + .replace(/aw-watcher-afk_unknown/gi, "__AW_RU_UNKNOWN_AFK__") + .replace(/find_bucket\((\\?["'])__AW_RU_UNKNOWN_WINDOW__(\\?["'])\)/gi, "[]") + .replace(/find_bucket\((\\?["'])__AW_RU_UNKNOWN_AFK__(\\?["'])\)/gi, "[]") + .replace(/query_bucket\((\\?["'])__AW_RU_UNKNOWN_WINDOW__(\\?["'])\)/gi, "[]") + .replace(/query_bucket\((\\?["'])__AW_RU_UNKNOWN_AFK__(\\?["'])\)/gi, "[]") + .replace(/__AW_RU_UNKNOWN_WINDOW__/g, "") + .replace(/__AW_RU_UNKNOWN_AFK__/g, ""); + } function stripUnknownBucketQueries(raw) { return raw .replace(/flood\(query_bucket\(find_bucket\(\\"aw-watcher-window_unknown\\"\)\)\)/g, '[]') @@ -1534,6 +1743,10 @@ body = stripUnknownBucketQueries(body); } } + if (body.indexOf("aw-watcher-window_unknown") !== -1 || body.indexOf("aw-watcher-afk_unknown") !== -1) { + body = stripUnknownBucketQueries(body); + body = stripUnknownBucketTokens(body); + } return body; } @@ -1541,20 +1754,25 @@ if (networkPatchesInstalled) return; networkPatchesInstalled = true; - const originalFetch = window.fetch ? window.fetch.bind(window) : null; - if (originalFetch) { - window.fetch = function (input, init) { + const originalFetch = window.fetch; + if (typeof originalFetch === "function" && !originalFetch.__awRuCategoryBuilderPatched) { + const patchedFetch = function (input, init) { + let nextInput = input; + let nextInit = init; try { - const url = typeof input === "string" ? input : String(input && input.url || ""); - if (/\/api\/0\/query\/?$/i.test(url) && init && typeof init.body === "string") { - init = Object.assign({}, init, { - body: rewriteUnknownCategoryBuilderQueryBody(init.body) + const url = typeof nextInput === "string" ? nextInput : String(nextInput && nextInput.url || ""); + if (/\/api\/0\/query\/?$/i.test(url) && nextInit && typeof nextInit.body === "string") { + nextInit = Object.assign({}, nextInit, { + body: rewriteUnknownCategoryBuilderQueryBody(nextInit.body) }); } } catch (error) { } - return originalFetch(input, init); + return originalFetch.call(this, nextInput, nextInit); }; + patchedFetch.__awRuCategoryBuilderPatched = true; + patchedFetch.__awRuOriginalFetch = originalFetch; + window.fetch = patchedFetch; } if (window.XMLHttpRequest && window.XMLHttpRequest.prototype) { @@ -1599,6 +1817,45 @@ }); } + function normalizeCategoryBuilderUnknownHostRefs() { + const hash = window.location.hash || ""; + if (!/^#\/settings\/category-builder(?:[/?#]|$)/i.test(hash)) return; + const preferredHost = getPreferredWindowHostFromBuckets(); + if (!preferredHost) return; + + const nextHash = hash + .replace(/aw-watcher-window_unknown/gi, "aw-watcher-window_" + preferredHost) + .replace(/aw-watcher-afk_unknown/gi, "aw-watcher-afk_" + preferredHost); + if (nextHash !== hash) { + window.location.replace(nextHash); + return; + } + + try { + for (let i = 0; i < window.localStorage.length; i += 1) { + const key = window.localStorage.key(i); + if (!key) continue; + const value = window.localStorage.getItem(key); + if (!value || (value.indexOf("aw-watcher-window_unknown") === -1 && value.indexOf("aw-watcher-afk_unknown") === -1)) continue; + window.localStorage.setItem( + key, + value + .replace(/aw-watcher-window_unknown/gi, "aw-watcher-window_" + preferredHost) + .replace(/aw-watcher-afk_unknown/gi, "aw-watcher-afk_" + preferredHost) + ); + } + } catch (error) { + } + } + + function primeCategoryBuilderEarlyFix() { + const hash = window.location.hash || ""; + if (!/^#\/settings\/category-builder(?:[/?#]|$)/i.test(hash)) return; + ensureSettingsHost(); + ensureHostGroupsData().catch(function () {}); + normalizeCategoryBuilderUnknownHostRefs(); + } + function patchActivityHeading(root) { const heading = root.querySelector("h3"); if (!heading) return; @@ -1611,27 +1868,64 @@ }); } + function detachObserver() { + if (!observerAttached) return; + observer.disconnect(); + observerAttached = false; + } + + function attachObserver() { + if (observerAttached || !document.body) return; + observer.observe(document.body, { childList: true, subtree: true }); + observerAttached = true; + } + function applyPatch() { - enforceSafeActivityViewForPveHost(); - ensureSettingsHost(); - ensureHostGroupsData().catch(function () {}); - installCategoryBuilderNetworkPatch(); - injectStyles(); - walk(document.body); - translateAttributes(document.body); - hideNoiseNavigation(document.body); - patchActivityHeading(document.body); - patchCategoryBuilderHostLabel(document.body); - injectPveAuditCenter(document.body); - injectDlpNavigation(document.body); - injectDlpReviewCenter(document.body); - injectDlpAlertsCenter(document.body); - injectHostGroupsCenter(document.body).catch(function () {}); - redirectBareTrendsRoute(); + if (applyPatchInFlight || !document.body) return; + applyPatchInFlight = true; + detachObserver(); + try { + const routeKey = window.location.hash || "#"; + const routeChanged = routeKey !== staticPatchRouteKey; + enforceSafeActivityViewForPveHost(); + ensureSettingsHost(); + ensureHostGroupsData().catch(function () {}); + normalizeCategoryBuilderUnknownHostRefs(); + 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); + injectDlpNavigation(document.body); + if (isDlpSignalBucketRoute() && dlpOverlayFailureCount === 0) { + try { + injectDlpReviewCenter(document.body); + } catch (error) { + dlpOverlayFailureCount += 1; + 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); + } + injectDlpAlertsCenter(document.body); + injectHostGroupsCenter(document.body).catch(function () {}); + redirectBareTrendsRoute(); + } finally { + applyPatchInFlight = false; + attachObserver(); + } } function scheduleApplyPatch() { - if (applyPatchScheduled) return; + if (applyPatchScheduled || applyPatchInFlight) return; applyPatchScheduled = true; window.setTimeout(function () { applyPatchScheduled = false; @@ -1640,15 +1934,20 @@ } const observer = new MutationObserver(function () { + if (applyPatchInFlight) return; scheduleApplyPatch(); }); + installCategoryBuilderNetworkPatch(); + primeCategoryBuilderEarlyFix(); + window.addEventListener("load", function () { applyPatch(); - observer.observe(document.body, { childList: true, subtree: true }); + attachObserver(); }); window.addEventListener("hashchange", function () { redirectBareTrendsRoute(); + staticPatchRouteKey = ""; scheduleApplyPatch(); }); })(); diff --git a/install-kit-awindows-20260427-211240/aw-server/aw-rus-healthd.py b/install-kit-awindows-20260427-211240/aw-server/aw-rus-healthd.py new file mode 100644 index 0000000..0423403 --- /dev/null +++ b/install-kit-awindows-20260427-211240/aw-server/aw-rus-healthd.py @@ -0,0 +1,371 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import json +import os +import socket +import subprocess +import sys +import tempfile +from dataclasses import dataclass +from datetime import UTC, datetime +from pathlib import Path +from typing import Any +from urllib import request + + +ENV_FILE = Path("/etc/activitywatch/aw-server.env") + + +def load_env_file(path: Path) -> None: + if not path.exists(): + return + for raw_line in path.read_text(encoding="utf-8").splitlines(): + line = raw_line.strip() + if not line or line.startswith("#") or "=" not in line: + continue + key, value = line.split("=", 1) + key = key.strip() + value = value.strip().strip("'").strip('"') + os.environ.setdefault(key, value) + + +def env(name: str, default: str) -> str: + value = os.environ.get(name) + return value if value not in (None, "") else default + + +def now_utc() -> datetime: + return datetime.now(UTC) + + +def parse_ts(value: str | None) -> datetime | None: + if not value: + return None + try: + return datetime.fromisoformat(value.replace("Z", "+00:00")).astimezone(UTC) + except ValueError: + return None + + +def age_seconds(ts: datetime | None, now: datetime) -> int | None: + if ts is None: + return None + return max(0, int((now - ts).total_seconds())) + + +def http_json(url: str, timeout: int = 10) -> Any: + with request.urlopen(url, timeout=timeout) as resp: + return json.loads(resp.read().decode("utf-8")) + + +def run_command(cmd: list[str]) -> tuple[int, str]: + proc = subprocess.run( + cmd, + check=False, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + return proc.returncode, proc.stdout.strip() + + +def tcp_connect(host: str, port: int, timeout: float) -> tuple[bool, str]: + try: + with socket.create_connection((host, port), timeout=timeout): + return True, "connected" + except OSError as exc: + return False, str(exc) + + +@dataclass +class CheckResult: + name: str + status: str + summary: str + details: dict[str, Any] + + +class Report: + def __init__(self) -> None: + self.results: list[CheckResult] = [] + + def add(self, name: str, status: str, summary: str, **details: Any) -> None: + self.results.append(CheckResult(name=name, status=status, summary=summary, details=details)) + + @property + def ok(self) -> bool: + return not any(item.status == "fail" for item in self.results) + + def as_dict(self) -> dict[str, Any]: + counts = {"ok": 0, "warn": 0, "fail": 0} + for item in self.results: + counts[item.status] = counts.get(item.status, 0) + 1 + return { + "generated_at_utc": now_utc().isoformat().replace("+00:00", "Z"), + "ok": self.ok, + "counts": counts, + "results": [ + { + "name": item.name, + "status": item.status, + "summary": item.summary, + "details": item.details, + } + for item in self.results + ], + } + + def render_text(self) -> str: + icon = {"ok": "OK", "warn": "WARN", "fail": "FAIL"} + lines = ["=== AW-RUS Health ===", f"Timestamp: {now_utc().isoformat().replace('+00:00', 'Z')}", ""] + for item in self.results: + lines.append(f"[{icon.get(item.status, item.status.upper())}] {item.name}: {item.summary}") + lines.append("") + payload = self.as_dict() + lines.append( + "Counts: ok={ok} warn={warn} fail={fail}".format( + ok=payload["counts"]["ok"], + warn=payload["counts"]["warn"], + fail=payload["counts"]["fail"], + ) + ) + lines.append(f"Overall: {'OK' if payload['ok'] else 'FAIL'}") + return "\n".join(lines) + + +def latest_bucket_event(api_base: str, bucket_id: str) -> dict[str, Any] | None: + events = http_json(f"{api_base}/buckets/{bucket_id}/events?limit=20") + if isinstance(events, list) and events: + events = [item for item in events if isinstance(item, dict)] + if not events: + return None + events.sort(key=lambda item: item.get("timestamp") or "", reverse=True) + return events[0] + return None + + +def host_activity_from_worktime(event: dict[str, Any] | None, max_age_seconds: int) -> dict[str, Any]: + now = now_utc() + if not event: + return {"fresh": False, "active": False, "age_seconds": None, "timestamp": None} + ts = parse_ts(event.get("timestamp")) + age = age_seconds(ts, now) + data = event.get("data") or {} + is_fresh = age is not None and age <= max_age_seconds + is_active = bool(is_fresh and data.get("active")) + return { + "fresh": bool(is_fresh), + "active": bool(is_active), + "age_seconds": age, + "timestamp": event.get("timestamp"), + "data": data, + } + + +def bucket_health( + api_base: str, + bucket_id: str, + max_age_seconds: int, + missing_status: str, + stale_status: str, +) -> tuple[str, str, dict[str, Any]]: + try: + event = latest_bucket_event(api_base, bucket_id) + except Exception as exc: + return "fail", f"bucket query failed: {exc}", {"bucket": bucket_id} + + if not event: + return missing_status, "no events", {"bucket": bucket_id} + + ts = parse_ts(event.get("timestamp")) + age = age_seconds(ts, now_utc()) + details = {"bucket": bucket_id, "timestamp": event.get("timestamp"), "age_seconds": age} + if age is None: + return "warn", "timestamp parse failed", details + if age > max_age_seconds: + return stale_status, f"stale ({age}s)", details + return "ok", f"fresh ({age}s)", details + + +def latest_validation_report(validation_dir: Path) -> Path | None: + candidates = sorted( + (path for path in validation_dir.glob("*-aw_validate_ansible.json") if path.is_file()), + key=lambda item: item.stat().st_mtime, + reverse=True, + ) + return candidates[0] if candidates else None + + +def write_atomic(path: Path, content: str) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + with tempfile.NamedTemporaryFile("w", encoding="utf-8", dir=path.parent, delete=False) as handle: + handle.write(content) + tmp_name = handle.name + os.replace(tmp_name, path) + + +def check_wrapper(report: Report, name: str, cmd: list[str], json_mode: bool = False) -> None: + if not Path(cmd[0]).exists(): + report.add(name, "warn", "binary missing", command=cmd) + return + rc, output = run_command(cmd) + details: dict[str, Any] = {"command": cmd, "returncode": rc} + if json_mode: + try: + details["payload"] = json.loads(output) if output else {} + except json.JSONDecodeError: + details["raw_output"] = output + report.add(name, "fail", "invalid JSON output", **details) + return + else: + details["output"] = output + report.add(name, "ok" if rc == 0 else "fail", "passed" if rc == 0 else "failed", **details) + + +def main() -> int: + load_env_file(ENV_FILE) + + 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("--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("--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") + args = parser.parse_args() + + report = Report() + aw_api_base = args.aw_server.rstrip("/") + if not aw_api_base.endswith("/api/0"): + aw_api_base = aw_api_base.rstrip("/") + "/api/0" + + check_wrapper(report, "wrapper:aw-health-check", ["/usr/local/bin/aw-health-check"]) + check_wrapper(report, "wrapper:dlp-health-check", ["/usr/local/bin/dlp-health-check", "--json"], json_mode=True) + + try: + info = http_json(f"{aw_api_base}/info") + report.add("http:aw-server", "ok", "activitywatch API responded", version=info.get("version")) + except Exception as exc: + 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) + except Exception as exc: + report.add("http:worktime-api", "fail", f"worktime API failed: {exc}", url=args.worktime_api) + + for port, label in ((5985, "winrm"), (3389, "rdp")): + ok, message = tcp_connect(args.rdp_host, port, args.tcp_timeout_seconds) + report.add(f"tcp:{label}", "ok" if ok else "fail", message if ok else f"unreachable: {message}", host=args.rdp_host, port=port) + + try: + buckets = http_json(f"{aw_api_base}/buckets") + if not isinstance(buckets, dict): + raise RuntimeError("bucket index is not a dict") + report.add("aw:buckets-index", "ok", "bucket index loaded", total=len(buckets)) + except Exception as exc: + report.add("aw:buckets-index", "fail", f"failed to load bucket index: {exc}") + buckets = {} + + host = args.rdp_hostname + worktime_bucket = f"aw-worktime-sessions_{host}" + worktime_event = None + if buckets: + try: + worktime_event = latest_bucket_event(aw_api_base, worktime_bucket) + except Exception: + worktime_event = None + activity = host_activity_from_worktime(worktime_event, args.session_max_age_seconds) + if worktime_event: + status, summary, details = bucket_health( + aw_api_base, + worktime_bucket, + args.session_max_age_seconds, + missing_status="fail", + stale_status="fail", + ) + details["host_activity"] = activity + report.add("bucket:worktime-sessions", status, summary, **details) + else: + report.add("bucket:worktime-sessions", "fail", "no events", bucket=worktime_bucket, host_activity=activity) + + interactive_required = bool(activity["active"]) + for bucket_name, label in ( + ("aw-watcher-afk", "bucket:afk"), + ("aw-watcher-window", "bucket:window"), + ("aw-dlp-endpoint-signals", "bucket:endpoint-signals"), + ): + status, summary, details = bucket_health( + aw_api_base, + f"{bucket_name}_{host}", + args.interactive_max_age_seconds, + missing_status="fail" if interactive_required else "warn", + stale_status="fail" if interactive_required else "warn", + ) + details["interactive_required"] = interactive_required + details["host_activity"] = activity + report.add(label, status, summary, **details) + + session_status, session_summary, session_details = bucket_health( + aw_api_base, + f"aw-session-events_{host}", + args.session_events_max_age_seconds, + missing_status="fail", + stale_status="warn", + ) + report.add("bucket:session-events", session_status, session_summary, **session_details) + + validation_dir = Path(args.validation_dir) + validation_report = latest_validation_report(validation_dir) + if validation_report is None: + report.add("validation:windows", "warn", "no validation report snapshot", directory=str(validation_dir)) + else: + try: + payload = json.loads(validation_report.read_text(encoding="utf-8-sig")) + age = age_seconds(datetime.fromtimestamp(validation_report.stat().st_mtime, tz=UTC), now_utc()) + if age is not None and age > args.validation_max_age_seconds: + report.add( + "validation:windows", + "warn", + f"validation snapshot is stale ({age}s)", + path=str(validation_report), + overall_ok=payload.get("overallOk"), + failed_sections=payload.get("summary", {}).get("failedSections", []), + ) + elif payload.get("overallOk") is True: + report.add("validation:windows", "ok", "validation snapshot OK", path=str(validation_report), age_seconds=age) + else: + report.add( + "validation:windows", + "fail", + "validation snapshot reports failure", + path=str(validation_report), + age_seconds=age, + failed_sections=payload.get("summary", {}).get("failedSections", []), + ) + except Exception as exc: + report.add("validation:windows", "fail", f"invalid validation snapshot: {exc}", path=str(validation_report)) + + payload = report.as_dict() + state_dir = Path(args.state_dir) + write_atomic(state_dir / "aw-rus-health.json", json.dumps(payload, ensure_ascii=False, indent=2) + "\n") + write_atomic(state_dir / "aw-rus-health.txt", report.render_text() + "\n") + + if args.json: + print(json.dumps(payload, ensure_ascii=False, indent=2)) + else: + print(report.render_text()) + return 0 if payload["ok"] else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/install-kit-awindows-20260427-211240/aw-server/aw-rus-healthd.service b/install-kit-awindows-20260427-211240/aw-server/aw-rus-healthd.service new file mode 100644 index 0000000..015a906 --- /dev/null +++ b/install-kit-awindows-20260427-211240/aw-server/aw-rus-healthd.service @@ -0,0 +1,14 @@ +[Unit] +Description=AW-RUS unified health orchestrator +After=network-online.target activitywatch-server.service aw-worktime-api.service +Wants=network-online.target activitywatch-server.service aw-worktime-api.service + +[Service] +Type=oneshot +EnvironmentFile=/etc/activitywatch/aw-server.env +ExecStart=/usr/bin/python3 /usr/local/bin/aw-rus-healthd.py +User=root +Group=root +StandardOutput=journal +StandardError=journal +SyslogIdentifier=aw-rus-healthd diff --git a/install-kit-awindows-20260427-211240/aw-server/aw-rus-healthd.timer b/install-kit-awindows-20260427-211240/aw-server/aw-rus-healthd.timer new file mode 100644 index 0000000..bc6f25b --- /dev/null +++ b/install-kit-awindows-20260427-211240/aw-server/aw-rus-healthd.timer @@ -0,0 +1,11 @@ +[Unit] +Description=Run AW-RUS unified health orchestrator every 2 minutes + +[Timer] +OnBootSec=2min +OnUnitActiveSec=2min +Unit=aw-rus-healthd.service +Persistent=true + +[Install] +WantedBy=timers.target diff --git a/install-kit-awindows-20260427-211240/aw-server/aw-server.env.example b/install-kit-awindows-20260427-211240/aw-server/aw-server.env.example index 03693fb..1d43206 100755 --- a/install-kit-awindows-20260427-211240/aw-server/aw-server.env.example +++ b/install-kit-awindows-20260427-211240/aw-server/aw-server.env.example @@ -1,13 +1,46 @@ # Copy to /etc/activitywatch/aw-server.env and fill with real values. +# Core AW Server Configuration AW_SERVER_VERSION=0.13.2 AW_SERVER_DOWNLOAD_URL=https://github.com/ActivityWatch/aw-server-rust/releases/download/v0.13.2/aw-server-rust-linux-x86_64.zip AW_SERVER_BIND_HOST=0.0.0.0 AW_SERVER_PORT=5600 AW_SERVER_WEBUI_DIR=/opt/activitywatch/webui-ru AW_SERVER_DATA_DIR=/var/lib/activitywatch +AW_SERVER_DB_PATH=/var/lib/activitywatch/pebble.db AW_SERVER_LOG_DIR=/var/log/activitywatch AW_SERVER_USER=activitywatch AW_SERVER_GROUP=activitywatch + +# Worktime API Configuration AW_WORKTIME_REPORT_BASE=http://10.10.10.13:5610 AW_WORKTIME_TZ=Europe/Moscow +AW_SERVER_URL=http://127.0.0.1:5600 + +# DLP IOC Configuration +AW_DLP_IOC_DIR=/opt/activitywatch/dlp-ioc/output + +# DLP Policy Engine Configuration +AW_DLP_POLICY_ENGINE_BIND_HOST=0.0.0.0 +AW_DLP_POLICY_ENGINE_PORT=5601 +AW_DLP_POLICY_ENGINE_DB_PATH=/var/lib/activitywatch/dlp-policy-engine.sqlite + +# Logging Configuration +AW_LOG_LEVEL=info +AW_LOG_TO_JOURNAL=true +AW_LOG_TO_FILE=true + +# Health Check Configuration +AW_HEALTH_CHECK_ENABLED=true +AW_HEALTH_CHECK_INTERVAL=60 +AW_EXPECT_START_OF_DAY=00:00 +AW_EXPECT_ALWAYS_ACTIVE_PATTERN=aw-watcher-window +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_STATE_DIR=/var/lib/activitywatch/health +AW_RUS_HEALTH_VALIDATION_DIR=/var/lib/activitywatch/health/windows-validation + +# Integration Test Configuration +AW_INTEGRATION_TEST_ENABLED=false diff --git a/install-kit-awindows-20260427-211240/aw-server/aw-worktime-api.py b/install-kit-awindows-20260427-211240/aw-server/aw-worktime-api.py index 6f6611d..a1ad20b 100644 --- a/install-kit-awindows-20260427-211240/aw-server/aw-worktime-api.py +++ b/install-kit-awindows-20260427-211240/aw-server/aw-worktime-api.py @@ -1,15 +1,36 @@ #!/usr/bin/env python3 -from http.server import BaseHTTPRequestHandler, HTTPServer import csv +import html import io +import importlib.util import json import os +import sys import urllib.request from datetime import datetime, timezone, timedelta +from http.server import BaseHTTPRequestHandler, HTTPServer +from pathlib import Path +from urllib.parse import parse_qs, urlencode, urlparse from zoneinfo import ZoneInfo -AW = "http://127.0.0.1:5600/api/0" + +def build_aw_api_base(raw_url): + url = (raw_url or "http://127.0.0.1:5600").strip().rstrip("/") + if url.endswith("/api/0"): + return url + return url + "/api/0" + + +AW_SERVER_URL = os.environ.get("AW_SERVER_URL", "http://127.0.0.1:5600") +AW = build_aw_api_base(AW_SERVER_URL) REPORT_TZ = ZoneInfo(os.environ.get("AW_WORKTIME_TZ", "Europe/Moscow")) +IOC_DIR = os.environ.get("AW_DLP_IOC_DIR", "/opt/activitywatch/dlp-ioc/output") +DEFAULT_HOST = os.environ.get("AW_WORKTIME_HOST", "SHARKON2025").strip() or "SHARKON2025" +DEFAULT_SAMPLE_SECONDS = max(1.0, float(os.environ.get("AW_WORKTIME_DEFAULT_SAMPLE_SECONDS", "30"))) +MAX_SAMPLE_SECONDS = max(DEFAULT_SAMPLE_SECONDS, float(os.environ.get("AW_WORKTIME_MAX_SAMPLE_SECONDS", "300"))) +LISTEN_HOST = os.environ.get("AW_WORKTIME_LISTEN_HOST", "0.0.0.0") +LISTEN_PORT = int(os.environ.get("AW_WORKTIME_PORT", "5610")) +MODULE_PATH = Path(__file__).resolve() def get(u): @@ -17,80 +38,404 @@ def get(u): return json.loads(r.read().decode()) +def log_warning(message): + print(f"[aw-worktime-api] {message}", file=sys.stderr, flush=True) + + def pts(s): return datetime.fromisoformat(s.replace("Z", "+00:00")).astimezone(timezone.utc) -def report_today(): +def to_iso_utc(dt): + return dt.astimezone(timezone.utc).isoformat().replace("+00:00", "Z") + + +def hhmm(total_seconds): + total_seconds = max(0, int(total_seconds)) + return "%02d:%02d" % (total_seconds // 3600, (total_seconds % 3600) // 60) + + +def safe_slug(value): + text = str(value or "").strip().lower() + slug = [] + for char in text: + if char.isalnum(): + slug.append(char) + else: + slug.append("-") + normalized = "".join(slug).strip("-") + while "--" in normalized: + normalized = normalized.replace("--", "-") + return normalized or "user" + + +def clamp_seconds(value, fallback=DEFAULT_SAMPLE_SECONDS): + try: + seconds = float(value) + except Exception: + seconds = float(fallback) + if seconds <= 0: + seconds = float(fallback) + return min(seconds, MAX_SAMPLE_SECONDS) + + +def resolve_host(request_host=None): + host = (request_host or DEFAULT_HOST).strip() + if not host: + host = DEFAULT_HOST + return host + + +def get_sessions_bucket_id(host): + return f"aw-worktime-sessions_{resolve_host(host)}" + + +def resolve_report_date(day=None, date_text=None): now_local = datetime.now(REPORT_TZ) - start_local = datetime(now_local.year, now_local.month, now_local.day, tzinfo=REPORT_TZ) + if date_text: + return datetime.strptime(date_text, "%Y-%m-%d").date() + if day == "yesterday": + return (now_local - timedelta(days=1)).date() + return now_local.date() + + +def get_report_bounds(report_date): + start_local = datetime(report_date.year, report_date.month, report_date.day, tzinfo=REPORT_TZ) end_local = start_local + timedelta(days=1) - timedelta(seconds=1) start = start_local.astimezone(timezone.utc) end = end_local.astimezone(timezone.utc) - b = get(AW + "/buckets") - sb = next((k for k in b if k.startswith("aw-worktime-sessions_")), None) - if not sb: + end_exclusive = end + timedelta(seconds=1) + return { + "start_local": start_local, + "end_local": end_local, + "start": start, + "end": end, + "end_exclusive": end_exclusive, + } + + +def _is_machine_user(user: str): + u = (user or "").strip().lower() + return u.endswith("$") or u in {"system", "localservice", "networkservice"} + + +def _is_active_sample(data: dict): + state = str(data.get("state") or "").strip().lower() + if isinstance(data.get("active"), bool) and data.get("active"): + return True + if ("актив" in state) or (state == "active"): + return True + if state == "unknown": + try: + sid = int(data.get("sessionId")) + except Exception: + sid = -1 + user = str(data.get("username") or "").strip() + session_name = str(data.get("sessionName") or "").strip().lower() + if sid > 0 and user and (not _is_machine_user(user)) and (session_name.startswith("rdp-") or session_name == "console"): + return True + return False + + +def _normalize_user_id(data, host, username): + user_id = str(data.get("userId") or "").strip() + if user_id: + left, sep, right = user_id.partition("\\") + if sep and right: + return f"{resolve_host(host)}\\{right}" + return user_id + return f"{resolve_host(host)}\\{username}" + + +def _event_sample_seconds(event, next_same_session_ts=None): + data = event.get("data") or {} + for key in ("sampleSeconds", "pollSeconds"): + value = data.get(key) + try: + if float(value) > 0: + return clamp_seconds(value) + except Exception: + pass + try: + duration = float(event.get("duration") or 0.0) + except Exception: + duration = 0.0 + if duration > 0: + return clamp_seconds(duration) + if next_same_session_ts is not None: + delta = (next_same_session_ts - event["_ts"]).total_seconds() + if delta > 0: + return clamp_seconds(delta) + return clamp_seconds(DEFAULT_SAMPLE_SECONDS) + + +def _merge_intervals(intervals): + if not intervals: return [] - ev = get(f"{AW}/buckets/{sb}/events?limit=50000") - by = {} - for e in ev: - ts = pts(e.get("timestamp")) + ordered = sorted(intervals, key=lambda item: item[0]) + merged = [ordered[0]] + for start, end in ordered[1:]: + last_start, last_end = merged[-1] + if start <= last_end: + if end > last_end: + merged[-1] = (last_start, end) + continue + merged.append((start, end)) + return merged + + +def _collect_user_rows(events, start, end, host): + end_exclusive = end + timedelta(seconds=1) + by_user = {} + by_identity = {} + + for event in events: + ts = pts(event.get("timestamp")) if ts < start or ts > end: continue - d = e.get("data") or {} - user = (d.get("username") or "").strip() - if not user: + data = event.get("data") or {} + username = str(data.get("username") or "").strip() + if not username: continue - state = (d.get("state") or "").lower() - active = ("актив" in state) or (state == "active") - row = by.setdefault(user, {"active": set(), "first": None, "last": None, "rows": 0}) - row["rows"] += 1 - if active: - second = ts.replace(microsecond=0) - row["active"].add(second) - row["first"] = second if row["first"] is None or second < row["first"] else row["first"] - row["last"] = second if row["last"] is None or second > row["last"] else row["last"] + session_id = str(data.get("sessionId") or "").strip() or "unknown" + event_copy = { + "_ts": ts, + "data": data, + "duration": event.get("duration"), + } + by_identity.setdefault((username, session_id), []).append(event_copy) + + for (username, session_id), samples in by_identity.items(): + ordered = sorted(samples, key=lambda item: item["_ts"]) + for idx, sample in enumerate(ordered): + data = sample["data"] + active = _is_active_sample(data) + next_ts = ordered[idx + 1]["_ts"] if idx + 1 < len(ordered) else None + sample_seconds = _event_sample_seconds(sample, next_ts) + row = by_user.setdefault( + username, + { + "user": username, + "user_id": _normalize_user_id(data, host, username), + "samples_count": 0, + "active_samples": 0, + "session_ids": set(), + "intervals": [], + }, + ) + row["samples_count"] += 1 + row["session_ids"].add(session_id) + if active: + row["active_samples"] += 1 + interval_start = sample["_ts"] + interval_end = min(sample["_ts"] + timedelta(seconds=sample_seconds), end_exclusive) + if interval_end > interval_start: + row["intervals"].append((interval_start, interval_end)) + return by_user + + +def aggregate_rows(events, start, end, host): + by_user = _collect_user_rows(events, start, end, host) rows = [] - full = int((end_local - start_local).total_seconds()) - for user in sorted(by): - row = by[user] - active_seconds = len(row["active"]) - rows.append({ - "user": user, - "active_seconds": active_seconds, - "active_hhmm": "%02d:%02d" % (active_seconds // 3600, (active_seconds % 3600) // 60), - "first_activity": row["first"].isoformat().replace("+00:00", "Z") if row["first"] else "", - "last_activity": row["last"].isoformat().replace("+00:00", "Z") if row["last"] else "", - "idle_seconds": max(0, full - active_seconds), - "sessions_count": row["rows"], - }) + full_range = int((end - start).total_seconds()) + 1 + for username in sorted(by_user): + row = by_user[username] + merged = _merge_intervals(row["intervals"]) + active_seconds = int(sum((end_dt - start_dt).total_seconds() for start_dt, end_dt in merged)) + active_seconds = min(active_seconds, full_range) + first_activity = to_iso_utc(merged[0][0]) if merged else "" + last_activity = to_iso_utc(merged[-1][1]) if merged else "" + rows.append( + { + "user": row["user"], + "user_id": row["user_id"], + "active_seconds": active_seconds, + "active_hhmm": hhmm(active_seconds), + "first_activity": first_activity, + "last_activity": last_activity, + "idle_seconds": max(0, full_range - active_seconds), + "sessions_count": len(row["session_ids"]), + "samples_count": row["samples_count"], + "active_samples": row["active_samples"], + } + ) return rows -def render_html(rows): +def aggregate_hourly_rows(events, start, end, host): + by_user = _collect_user_rows(events, start, end, host) + rows = [] + for username in sorted(by_user): + row = by_user[username] + merged = _merge_intervals(row["intervals"]) + per_bucket = {} + for interval_start, interval_end in merged: + cursor = interval_start + while cursor < interval_end: + bucket_local = cursor.astimezone(REPORT_TZ).replace(minute=0, second=0, microsecond=0) + bucket_start = bucket_local.astimezone(timezone.utc) + bucket_end = (bucket_local + timedelta(hours=1)).astimezone(timezone.utc) + overlap_start = max(interval_start, bucket_start) + overlap_end = min(interval_end, bucket_end) + if overlap_end > overlap_start: + key = bucket_start + per_bucket[key] = per_bucket.get(key, 0) + int((overlap_end - overlap_start).total_seconds()) + cursor = bucket_end + + for bucket_start in sorted(per_bucket): + active_seconds = per_bucket[bucket_start] + if active_seconds <= 0: + continue + bucket_local = bucket_start.astimezone(REPORT_TZ) + rows.append( + { + "user": row["user"], + "user_id": row["user_id"], + "bucket_start_utc": to_iso_utc(bucket_start), + "bucket_start_local": bucket_local.isoformat(), + "report_date": bucket_local.date().isoformat(), + "hour_local": bucket_local.strftime("%H:00"), + "active_seconds": active_seconds, + "active_hhmm": hhmm(active_seconds), + } + ) + return rows + + +def fetch_events_for_date(host, report_date): + bounds = get_report_bounds(report_date) + bucket_id = get_sessions_bucket_id(host) + try: + get(f"{AW}/buckets/{bucket_id}") + except Exception: + log_warning(f"bucket lookup failed for host={host} bucket={bucket_id} aw_base={AW}") + return bounds, [] + try: + events = get(f"{AW}/buckets/{bucket_id}/events?limit=50000") + except Exception: + log_warning(f"events fetch failed for host={host} bucket={bucket_id} aw_base={AW}") + return bounds, [] + return bounds, events + + +def build_report_summary(rows): + if not rows: + return { + "users_count": 0, + "total_active_seconds": 0, + "total_active_hhmm": "00:00", + "first_activity": "", + "last_activity": "", + "top_user": "", + "top_user_active_hhmm": "00:00", + } + + total_active_seconds = sum(int(row.get("active_seconds", 0) or 0) for row in rows) + first_values = [row.get("first_activity") for row in rows if row.get("first_activity")] + last_values = [row.get("last_activity") for row in rows if row.get("last_activity")] + top_row = max(rows, key=lambda row: int(row.get("active_seconds", 0) or 0)) + return { + "users_count": len(rows), + "total_active_seconds": total_active_seconds, + "total_active_hhmm": hhmm(total_active_seconds), + "first_activity": min(first_values) if first_values else "", + "last_activity": max(last_values) if last_values else "", + "top_user": top_row.get("user", ""), + "top_user_active_hhmm": top_row.get("active_hhmm", "00:00"), + } + + +def report_for_date(host, report_date): + bounds, events = fetch_events_for_date(host, report_date) + return aggregate_rows(events, bounds["start"], bounds["end"], host) + + +def report_today(host): + return report_for_date(host, resolve_report_date()) + + +def report_for_date_fresh(host, report_date): + spec = importlib.util.spec_from_file_location("aw_worktime_runtime", MODULE_PATH) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module.report_for_date(host, report_date) + + +def render_html(rows, host, report_date, selected_day=None): generated = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") - date_local = datetime.now(REPORT_TZ).strftime("%Y-%m-%d") + date_local = report_date.strftime("%Y-%m-%d") + day_query = f"&day={selected_day}" if selected_day in {"today", "yesterday"} else "" + date_query = f"&date={date_local}" if not day_query else "" + summary = build_report_summary(rows) + today_url = "/reports/worktime/today?" + urlencode({"format": "html", "host": resolve_host(host), "day": "today"}) + yesterday_url = "/reports/worktime/today?" + urlencode({"format": "html", "host": resolve_host(host), "day": "yesterday"}) + csv_url = "/reports/worktime/today?" + urlencode({"format": "csv", "host": resolve_host(host), **({"day": selected_day} if selected_day in {"today", "yesterday"} else {"date": date_local})}) + json_url = "/reports/worktime/today?" + urlencode({"host": resolve_host(host), **({"day": selected_day} if selected_day in {"today", "yesterday"} else {"date": date_local})}) + form_action = "/reports/worktime/today" + cards = [ + ("Пользователи", str(summary["users_count"])), + ("Активное время", summary["total_active_hhmm"]), + ("Лидер дня", f"{summary['top_user']} · {summary['top_user_active_hhmm']}" if summary["top_user"] else "н/д"), + ("Диапазон", f"{summary['first_activity']} -> {summary['last_activity']}" if summary["first_activity"] else "нет активности"), + ] trs = [] + detail_cards = [] for row in rows: + user_slug = safe_slug(row["user"]) + active_seconds = int(row.get("active_seconds", 0) or 0) + utilization = 0.0 + day_total = 24 * 3600 + if day_total > 0: + utilization = round((active_seconds / day_total) * 100.0, 2) trs.append( "" - f"{row['user']}" - f"{row['active_hhmm']}" + f"
{html.escape(row['user'])}" + f"{html.escape(row['user_id'])}" + f"{row['active_hhmm']}" f"{row['active_seconds']}" - f"{row['first_activity']}" - f"{row['last_activity']}" + f"{html.escape(row['first_activity'])}" + f"{html.escape(row['last_activity'])}" f"{row['idle_seconds']}" f"{row['sessions_count']}" + f"{row['samples_count']}" "" ) + detail_cards.append( + "
" + "
" + "

{user}

" + "{active}" + "
" + "
" + "
Пользователь{user_id}
" + "
Загрузка{utilization}%
" + "
Начало активности{first_activity}
" + "
Конец активности{last_activity}
" + "
Сессии{sessions}
" + "
Активные сэмплы{active_samples} / {samples}
" + "
" + "
" + .format( + slug=user_slug, + user=html.escape(row["user"]), + active=html.escape(row["active_hhmm"]), + user_id=html.escape(row["user_id"]), + utilization=utilization, + first_activity=html.escape(row["first_activity"] or "н/д"), + last_activity=html.escape(row["last_activity"] or "н/д"), + sessions=row["sessions_count"], + active_samples=row["active_samples"], + samples=row["samples_count"], + )) if not trs: - trs.append('No data for today yet.') + trs.append('За выбранную дату данных пока нет.') + detail_cards.append("

За выбранную дату нет активности пользователей.

") return f""" - + - AW-rus Worktime + AW-rus Отчёт по работе в RDP
-

RDP Worktime Report

-
Date: {date_local} · Timezone: {REPORT_TZ} · Generated UTC: {generated}
+

Отчёт по работе в RDP

+
Хост: {resolve_host(host)} · Дата: {date_local} · Часовой пояс: {REPORT_TZ} · Сформировано UTC: {generated}
+
+
+ + + + +
+
+
+ {''.join(f"
{html.escape(label)}{html.escape(value)}
" for label, value in cards)}
+

Таблица по пользователям

- - - - - - - + + + + + + + + + @@ -182,6 +656,12 @@ def render_html(rows):
UserActiveActive secFirst activityLast activityIdle secSamplesПользовательУчётная записьАктивноАктивно, секНачало активностиКонец активностиПростой, секСессииСэмплы
+
+

Детали по пользователям

+
+ {''.join(detail_cards)} +
+
""" @@ -189,28 +669,65 @@ def render_html(rows): class H(BaseHTTPRequestHandler): def do_GET(self): - if not self.path.startswith("/reports/worktime/today"): + parsed = urlparse(self.path) + if parsed.path.startswith("/dlp-ioc/"): + name = parsed.path.rsplit("/", 1)[-1] + if name not in {"ioc_blacklist.json", "ioc_blacklist.csv", "ioc_blacklist.sql"}: + self.send_response(404) + self.end_headers() + return + path = os.path.join(IOC_DIR, name) + if not os.path.isfile(path): + self.send_response(404) + self.end_headers() + return + with open(path, "rb") as f: + data = f.read() + if name.endswith(".json"): + ctype = "application/json; charset=utf-8" + elif name.endswith(".csv"): + ctype = "text/csv; charset=utf-8" + else: + ctype = "text/plain; charset=utf-8" + self.send_response(200) + self.send_header("Content-Type", ctype) + self.send_header("Content-Length", str(len(data))) + self.end_headers() + self.wfile.write(data) + return + + if parsed.path != "/reports/worktime/today": self.send_response(404) self.end_headers() return + + params = parse_qs(parsed.query, keep_blank_values=False) fmt = "json" - if "format=csv" in self.path: + if params.get("format", ["json"])[0] == "csv": fmt = "csv" - elif "format=html" in self.path: + elif params.get("format", ["json"])[0] == "html": fmt = "html" - rows = report_today() + host = resolve_host(params.get("host", [DEFAULT_HOST])[0]) + day = params.get("day", ["today"])[0] + date_text = params.get("date", [None])[0] + report_date = resolve_report_date(day=day, date_text=date_text) + rows = report_for_date_fresh(host, report_date) + if fmt == "csv": out = io.StringIO() writer = csv.DictWriter( out, fieldnames=[ "user", + "user_id", "active_seconds", "active_hhmm", "first_activity", "last_activity", "idle_seconds", "sessions_count", + "samples_count", + "active_samples", ], ) writer.writeheader() @@ -222,17 +739,22 @@ class H(BaseHTTPRequestHandler): self.end_headers() self.wfile.write(data) return + if fmt == "html": - data = render_html(rows).encode("utf-8") + data = render_html(rows, host, report_date, selected_day=day if day in {"today", "yesterday"} else None).encode("utf-8") self.send_response(200) self.send_header("Content-Type", "text/html; charset=utf-8") self.send_header("Content-Length", str(len(data))) self.end_headers() self.wfile.write(data) return + obj = { "generated_at_utc": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"), "report_timezone": str(REPORT_TZ), + "host": host, + "report_date": report_date.isoformat(), + "bucket_id": get_sessions_bucket_id(host), "rows": rows, } data = json.dumps(obj, ensure_ascii=False, indent=2).encode("utf-8") @@ -243,4 +765,9 @@ class H(BaseHTTPRequestHandler): self.wfile.write(data) -HTTPServer(("0.0.0.0", 5610), H).serve_forever() +def main(): + HTTPServer((LISTEN_HOST, LISTEN_PORT), H).serve_forever() + + +if __name__ == "__main__": + main() diff --git a/install-kit-awindows-20260427-211240/aw-server/aw-worktime-api.service b/install-kit-awindows-20260427-211240/aw-server/aw-worktime-api.service index 4225fb5..e85d3bf 100644 --- a/install-kit-awindows-20260427-211240/aw-server/aw-worktime-api.service +++ b/install-kit-awindows-20260427-211240/aw-server/aw-worktime-api.service @@ -2,15 +2,20 @@ Description=AW Worktime Report API After=network.target activitywatch-server.service Wants=activitywatch-server.service +StartLimitBurst=3 +StartLimitIntervalSec=60 [Service] Type=simple EnvironmentFile=/etc/activitywatch/aw-server.env ExecStart=/usr/bin/python3 /usr/local/bin/aw-worktime-api.py -Restart=always -RestartSec=2 +Restart=on-failure +RestartSec=5 User=activitywatch Group=activitywatch +StandardOutput=journal +StandardError=journal +SyslogIdentifier=aw-worktime-api [Install] WantedBy=multi-user.target diff --git a/install-kit-awindows-20260427-211240/aw-server/aw-worktime-panel.js b/install-kit-awindows-20260427-211240/aw-server/aw-worktime-panel.js index c38b29e..a91741d 100644 --- a/install-kit-awindows-20260427-211240/aw-server/aw-worktime-panel.js +++ b/install-kit-awindows-20260427-211240/aw-server/aw-worktime-panel.js @@ -1,15 +1,23 @@ (function () { var reportBase = "__AW_WORKTIME_REPORT_BASE__"; - var reportUrl = reportBase + "/reports/worktime/today?format=html"; + function defaultDayQuery() { + var now = new Date(); + return now.getHours() < 6 ? "day=yesterday" : "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 existing = document.getElementById("aw-report-links"); if (!existing) return; existing.innerHTML = - 'RDP report: ' + - 'HTML | ' + - 'CSV | ' + - 'JSON | ' + - 'Panel'; + 'RDP отчёт: ' + + 'HTML | ' + + 'CSV | ' + + 'JSON | ' + + 'Панель'; var panel = document.createElement("div"); panel.id = "aw-report-panel"; @@ -30,12 +38,12 @@ panel.innerHTML = '
' + - '
RDP Worktime Report
' + + '
Отчёт по работе в RDP
' + '
' + - 'Open' + - 'Close' + + 'Открыть' + + 'Закрыть' + "
" + - ''; + ''; document.body.appendChild(panel); diff --git a/install-kit-awindows-20260427-211240/aw-server/install_aw_server.sh b/install-kit-awindows-20260427-211240/aw-server/install_aw_server.sh index 50ba3e7..6a28f35 100755 --- a/install-kit-awindows-20260427-211240/aw-server/install_aw_server.sh +++ b/install-kit-awindows-20260427-211240/aw-server/install_aw_server.sh @@ -26,6 +26,12 @@ VIEWS_JSON="$BOOTSTRAP_DIR/settings/views-default.json" CLASSES_JSON="$BOOTSTRAP_DIR/settings/classes-worktime.json" WORKTIME_API_SRC="$BOOTSTRAP_DIR/aw-worktime-api.py" WORKTIME_API_SERVICE_SRC="$BOOTSTRAP_DIR/aw-worktime-api.service" +WORKTIME_UI_BRIDGE_SRC="$BOOTSTRAP_DIR/aw-worktime-ui-bridge.py" +WORKTIME_UI_BRIDGE_SERVICE_SRC="$BOOTSTRAP_DIR/aw-worktime-ui-bridge.service" +WORKTIME_UI_BRIDGE_TIMER_SRC="$BOOTSTRAP_DIR/aw-worktime-ui-bridge.timer" +HEALTHD_SRC="$BOOTSTRAP_DIR/aw-rus-healthd.py" +HEALTHD_SERVICE_SRC="$BOOTSTRAP_DIR/aw-rus-healthd.service" +HEALTHD_TIMER_SRC="$BOOTSTRAP_DIR/aw-rus-healthd.timer" for var_name in "${required_vars[@]}"; do if [[ -z "${!var_name:-}" ]]; then @@ -51,6 +57,7 @@ install -d -o "$AW_SERVER_USER" -g "$AW_SERVER_GROUP" /opt/activitywatch/release install -d -o "$AW_SERVER_USER" -g "$AW_SERVER_GROUP" "$AW_SERVER_WEBUI_DIR" install -d -o "$AW_SERVER_USER" -g "$AW_SERVER_GROUP" "$AW_SERVER_DATA_DIR" install -d -o "$AW_SERVER_USER" -g "$AW_SERVER_GROUP" "$AW_SERVER_LOG_DIR" +install -d -o "$AW_SERVER_USER" -g "$AW_SERVER_GROUP" "$AW_SERVER_DATA_DIR/health/windows-validation" tmp_dir=$(mktemp -d) trap 'rm -rf "$tmp_dir"' EXIT @@ -103,6 +110,41 @@ if [[ -f "$WORKTIME_API_SERVICE_SRC" ]]; then systemctl --no-pager --full status aw-worktime-api.service || true fi +if [[ -f "$WORKTIME_UI_BRIDGE_SRC" ]]; then + install -m 0755 "$WORKTIME_UI_BRIDGE_SRC" /usr/local/bin/aw-worktime-ui-bridge.py +fi + +if [[ -f "$WORKTIME_UI_BRIDGE_SERVICE_SRC" ]]; then + install -m 0644 "$WORKTIME_UI_BRIDGE_SERVICE_SRC" /etc/systemd/system/aw-worktime-ui-bridge.service +fi + +if [[ -f "$WORKTIME_UI_BRIDGE_TIMER_SRC" ]]; then + install -m 0644 "$WORKTIME_UI_BRIDGE_TIMER_SRC" /etc/systemd/system/aw-worktime-ui-bridge.timer + systemctl daemon-reload + systemctl disable --now aw-worktime-afk-bridge.timer >/dev/null 2>&1 || true + systemctl enable aw-worktime-ui-bridge.timer + systemctl restart aw-worktime-ui-bridge.timer + systemctl start aw-worktime-ui-bridge.service || true + systemctl --no-pager --full status aw-worktime-ui-bridge.timer || true +fi + +if [[ -f "$HEALTHD_SRC" ]]; then + install -m 0755 "$HEALTHD_SRC" /usr/local/bin/aw-rus-healthd.py +fi + +if [[ -f "$HEALTHD_SERVICE_SRC" ]]; then + install -m 0644 "$HEALTHD_SERVICE_SRC" /etc/systemd/system/aw-rus-healthd.service +fi + +if [[ -f "$HEALTHD_TIMER_SRC" ]]; then + install -m 0644 "$HEALTHD_TIMER_SRC" /etc/systemd/system/aw-rus-healthd.timer + systemctl daemon-reload + systemctl enable aw-rus-healthd.timer + systemctl restart aw-rus-healthd.timer + systemctl start aw-rus-healthd.service || true + systemctl --no-pager --full status aw-rus-healthd.timer || true +fi + for _ in $(seq 1 20); do if curl -fsS "http://127.0.0.1:${AW_SERVER_PORT}/api/0/info" >/dev/null 2>&1; then break diff --git a/install-kit-awindows-20260427-211240/aw-server/settings/classes-worktime.json b/install-kit-awindows-20260427-211240/aw-server/settings/classes-worktime.json index b6361f4..11ee810 100644 --- a/install-kit-awindows-20260427-211240/aw-server/settings/classes-worktime.json +++ b/install-kit-awindows-20260427-211240/aw-server/settings/classes-worktime.json @@ -20,7 +20,7 @@ "name": ["Работа", "Документы"], "rule": { "type": "regex", - "regex": "\\b(winword|excel|powerpnt|outlook|acrord32|acrord64)\\.exe\\b|Adobe Reader|Acrobat", + "regex": "\\b(winword|excel|powerpnt|outlook|acrord32|acrord64|libreoffice|writer|calc)\\.exe\\b|LibreOffice|OnlyOffice|Adobe Reader|Acrobat", "ignore_case": true }, "data": { "color": "#2E7D32" } @@ -40,7 +40,7 @@ "name": ["Работа", "Администрирование"], "rule": { "type": "regex", - "regex": "\\b(mstsc|putty|kitty|winscp|anydesk|teamviewer|vncviewer|mmc|regedit|services|control|powershell|cmd)\\.exe\\b", + "regex": "\\b(mstsc|putty|kitty|winscp|anydesk|teamviewer|vncviewer|mmc|regedit|services|control|powershell|cmd|gnome-terminal|gnome-terminal-server|xfce4-terminal|konsole|tilix|alacritty|xterm|remmina|virt-manager)\\.exe\\b|\\b(gnome-terminal|gnome-terminal-server|xfce4-terminal|konsole|tilix|alacritty|xterm|remmina|virt-manager)\\b|Proxmox Virtual Environment|\\bpfSense\\b|\\bGrafana\\b|\\bKibana\\b|\\bPortainer\\b", "ignore_case": true }, "data": { "color": "#6D4C41" } @@ -56,7 +56,7 @@ "name": ["Интернет", "Браузер"], "rule": { "type": "regex", - "regex": "\\b(chrome|msedge|firefox|opera|brave|vivaldi|browser)\\.exe\\b", + "regex": "\\b(chrome|msedge|firefox|opera|brave|vivaldi|browser|chromium)\\.exe\\b|\\b(chrome|chromium|firefox|opera|brave|vivaldi)\\b", "ignore_case": true }, "data": { "color": "#00897B" } @@ -82,7 +82,7 @@ "name": ["ActivityWatch"], "rule": { "type": "regex", - "regex": "ActivityWatch|\\baw-(watcher|qt)\\.exe\\b", + "regex": "ActivityWatch|\\baw-(watcher|qt)\\.exe\\b|\\baw-(watcher|qt)\\b", "ignore_case": true }, "data": {} diff --git a/install-kit-awindows-20260427-211240/aw-server/settings/views-default.json b/install-kit-awindows-20260427-211240/aw-server/settings/views-default.json index 439fce6..5421e98 100644 --- a/install-kit-awindows-20260427-211240/aw-server/settings/views-default.json +++ b/install-kit-awindows-20260427-211240/aw-server/settings/views-default.json @@ -30,10 +30,5 @@ { "type": "category_tree", "size": 3, "props": {} }, { "type": "top_apps", "size": 3, "props": {} } ] - }, - { - "id": "pve_audit", - "name": "PVE Audit", - "elements": [] } ] diff --git a/install-kit-awindows-20260427-211240/server-configs-192.168.100.21/awatch-rus-admin.deployment-config.json b/install-kit-awindows-20260427-211240/server-configs-192.168.100.18/awatch-rus-admin.deployment-config.json similarity index 100% rename from install-kit-awindows-20260427-211240/server-configs-192.168.100.21/awatch-rus-admin.deployment-config.json rename to install-kit-awindows-20260427-211240/server-configs-192.168.100.18/awatch-rus-admin.deployment-config.json diff --git a/install-kit-awindows-20260427-211240/server-configs-192.168.100.21/awatch-rus-u2u5.deployment-config.json b/install-kit-awindows-20260427-211240/server-configs-192.168.100.18/awatch-rus-u2u5.deployment-config.json similarity index 100% rename from install-kit-awindows-20260427-211240/server-configs-192.168.100.21/awatch-rus-u2u5.deployment-config.json rename to install-kit-awindows-20260427-211240/server-configs-192.168.100.18/awatch-rus-u2u5.deployment-config.json diff --git a/install-kit-awindows-20260427-211240/server-configs-192.168.100.21/awatch-rus-user1.deployment-config.json b/install-kit-awindows-20260427-211240/server-configs-192.168.100.18/awatch-rus-user1.deployment-config.json similarity index 100% rename from install-kit-awindows-20260427-211240/server-configs-192.168.100.21/awatch-rus-user1.deployment-config.json rename to install-kit-awindows-20260427-211240/server-configs-192.168.100.18/awatch-rus-user1.deployment-config.json diff --git a/install-kit-awindows-20260427-211240/windows/ActivityWatch.Windows.Common.psm1 b/install-kit-awindows-20260427-211240/windows/ActivityWatch.Windows.Common.psm1 index e3a30b5..2f7f461 100755 --- a/install-kit-awindows-20260427-211240/windows/ActivityWatch.Windows.Common.psm1 +++ b/install-kit-awindows-20260427-211240/windows/ActivityWatch.Windows.Common.psm1 @@ -1,4 +1,4 @@ -Set-StrictMode -Version Latest +Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' function Assert-Administrator { @@ -83,6 +83,41 @@ function Get-ActivityWatchPackageRoot { return (Split-Path -Path (Split-Path -Path $afkBinary.FullName -Parent) -Parent) } +function Expand-ActivityWatchArchiveSafe { + param( + [Parameter(Mandatory = $true)] + [string]$ArchivePath, + [Parameter(Mandatory = $true)] + [string]$DestinationPath, + [int]$Attempts = 3 + ) + + for ($attempt = 1; $attempt -le $Attempts; $attempt++) { + try { + if (Test-Path -LiteralPath $DestinationPath) { + Remove-Item -LiteralPath $DestinationPath -Recurse -Force -ErrorAction SilentlyContinue + } + New-ActivityWatchDirectory -Path $DestinationPath + Expand-Archive -Path $ArchivePath -DestinationPath $DestinationPath -Force -ErrorAction Stop + return + } + catch { + if ($attempt -lt $Attempts) { + Start-Sleep -Milliseconds (500 * $attempt) + continue + } + } + } + + # Fallback for intermittent Expand-Archive issues in Windows PowerShell. + if (Test-Path -LiteralPath $DestinationPath) { + Remove-Item -LiteralPath $DestinationPath -Recurse -Force -ErrorAction SilentlyContinue + } + New-ActivityWatchDirectory -Path $DestinationPath + Add-Type -AssemblyName System.IO.Compression.FileSystem + [System.IO.Compression.ZipFile]::ExtractToDirectory($ArchivePath, $DestinationPath) +} + function Install-ActivityWatchPackage { param( [Parameter(Mandatory = $true)] @@ -98,6 +133,13 @@ function Install-ActivityWatchPackage { New-ActivityWatchDirectory -Path $WorkingRoot New-ActivityWatchDirectory -Path $BackupRoot + # Cleanup stale extraction directories from previous failed deployments. + Get-ChildItem -LiteralPath $WorkingRoot -Directory -ErrorAction SilentlyContinue | + Where-Object { $_.Name -like 'extract-*' } | + ForEach-Object { + try { Remove-Item -LiteralPath $_.FullName -Recurse -Force -ErrorAction SilentlyContinue } catch {} + } + # Ensure nothing is holding locks inside InstallRoot during upgrade. foreach ($procName in @('aw-watcher-afk', 'aw-watcher-window', 'aw-server', 'aw-qt')) { try { @@ -114,7 +156,17 @@ function Install-ActivityWatchPackage { } New-ActivityWatchDirectory -Path $extractRoot - Expand-Archive -Path $ArchivePath -DestinationPath $extractRoot -Force + $archiveSize = (Get-Item -LiteralPath $ArchivePath -ErrorAction Stop).Length + $workDrive = (Get-PSDrive -Name ([System.IO.Path]::GetPathRoot($WorkingRoot).TrimEnd('\').TrimEnd(':')) -ErrorAction SilentlyContinue) + if ($workDrive) { + # Require at least ~2.5x archive size to handle extraction + copy safely. + $required = [int64]([Math]::Ceiling($archiveSize * 2.5)) + if ([int64]$workDrive.Free -lt $required) { + throw ("Недостаточно свободного места на {0}: free={1} bytes, required>={2} bytes" -f $workDrive.Name, $workDrive.Free, $required) + } + } + + Expand-ActivityWatchArchiveSafe -ArchivePath $ArchivePath -DestinationPath $extractRoot $packageRoot = Get-ActivityWatchPackageRoot -ExpandedRoot $extractRoot if (Test-Path -LiteralPath $InstallRoot) { @@ -181,7 +233,7 @@ function Normalize-ActivityWatchUsers { $resolved = Resolve-Path -LiteralPath $UserListPath -ErrorAction Stop $extension = [IO.Path]::GetExtension($resolved.Path) if ($extension -ieq '.csv') { - $rows = Import-Csv -LiteralPath $resolved.Path + $rows = Import-Csv -LiteralPath $resolved.Path -Encoding UTF8 foreach ($row in $rows) { foreach ($column in 'User', 'Username', 'SamAccountName', 'Login') { if ($row.PSObject.Properties.Name -contains $column) { @@ -195,7 +247,7 @@ function Normalize-ActivityWatchUsers { } } else { - Get-Content -LiteralPath $resolved.Path | ForEach-Object { + Get-Content -LiteralPath $resolved.Path -Encoding UTF8 | ForEach-Object { $line = $_.Trim() if ($line -and -not $line.StartsWith('#')) { $collected.Add($line) @@ -259,16 +311,95 @@ function New-ActivityWatchUserTaskDefinitions { return @($result) } +function Get-ActivityWatchLoggedOnUsers { + $users = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase) + + try { + $lines = & quser.exe 2>$null + foreach ($line in @($lines)) { + $normalized = [string]$line + if ([string]::IsNullOrWhiteSpace($normalized)) { + continue + } + + $normalized = $normalized.TrimStart(' ', '>') + if ([string]::IsNullOrWhiteSpace($normalized)) { + continue + } + + if ($normalized -match '^(USERNAME|ПОЛЬЗОВАТЕЛЬ)\s+') { + continue + } + + $parts = $normalized -split '\s+' + if ($parts.Count -lt 1) { + continue + } + + $user = [string]$parts[0] + if ([string]::IsNullOrWhiteSpace($user)) { + continue + } + + [void]$users.Add($user) + [void]$users.Add(('{0}\{1}' -f $env:COMPUTERNAME, $user)) + if (-not [string]::IsNullOrWhiteSpace($env:USERDOMAIN)) { + [void]$users.Add(('{0}\{1}' -f $env:USERDOMAIN, $user)) + } + } + } + catch { + } + + return @($users) +} + +function Test-ActivityWatchUserHasSession { + param( + [Parameter(Mandatory = $true)] + [string]$UserId, + [string[]]$LoggedOnUsers + ) + + if ([string]::IsNullOrWhiteSpace($UserId)) { + return $false + } + + $candidateIds = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase) + [void]$candidateIds.Add($UserId) + + $leafUser = $UserId + if ($leafUser -match '^[^\\]+\\(.+)$') { + $leafUser = $Matches[1] + [void]$candidateIds.Add($leafUser) + } + + [void]$candidateIds.Add(('{0}\{1}' -f $env:COMPUTERNAME, $leafUser)) + if (-not [string]::IsNullOrWhiteSpace($env:USERDOMAIN)) { + [void]$candidateIds.Add(('{0}\{1}' -f $env:USERDOMAIN, $leafUser)) + } + + foreach ($candidate in @($candidateIds)) { + if ($LoggedOnUsers -contains $candidate) { + return $true + } + } + + return $false +} + function Copy-ActivityWatchCollectorAssets { param( [Parameter(Mandatory = $true)] [string]$CollectorScriptSource, [Parameter(Mandatory = $true)] [string]$EndpointCollectorScriptSource, + [string]$PolicyClientScriptSource, [Parameter(Mandatory = $true)] [string]$FileCollectorScriptSource, [Parameter(Mandatory = $true)] [string]$SessionCollectorScriptSource, + [string]$EvtxExportScriptSource, [string]$EmailCollectorScriptSource, [Parameter(Mandatory = $true)] [string]$ExampleRulesSource, @@ -284,8 +415,10 @@ function Copy-ActivityWatchCollectorAssets { $collectorTarget = Join-Path $StateRoot 'browser-domains-native-collector.ps1' $endpointCollectorTarget = Join-Path $StateRoot 'dlp-endpoint-signals-collector.ps1' + $policyClientTarget = Join-Path $StateRoot 'dlp-policy-client.ps1' $fileCollectorTarget = Join-Path $StateRoot 'file-operations-collector.ps1' $sessionCollectorTarget = Join-Path $StateRoot 'worktime-session-collector.ps1' + $evtxExportTarget = Join-Path $StateRoot 'export-evtx-for-hayabusa.ps1' $emailCollectorTarget = Join-Path $StateRoot 'email-outbound-collector.ps1' $exampleRulesTarget = Join-Path $StateRoot 'web-category-rules.example.json' $rulesTarget = Join-Path $StateRoot 'web-category-rules.json' @@ -294,8 +427,14 @@ function Copy-ActivityWatchCollectorAssets { Copy-Item -LiteralPath $CollectorScriptSource -Destination $collectorTarget -Force Copy-Item -LiteralPath $EndpointCollectorScriptSource -Destination $endpointCollectorTarget -Force + if ($PolicyClientScriptSource -and (Test-Path -LiteralPath $PolicyClientScriptSource)) { + Copy-Item -LiteralPath $PolicyClientScriptSource -Destination $policyClientTarget -Force + } Copy-Item -LiteralPath $FileCollectorScriptSource -Destination $fileCollectorTarget -Force Copy-Item -LiteralPath $SessionCollectorScriptSource -Destination $sessionCollectorTarget -Force + if ($EvtxExportScriptSource -and (Test-Path -LiteralPath $EvtxExportScriptSource)) { + Copy-Item -LiteralPath $EvtxExportScriptSource -Destination $evtxExportTarget -Force + } if ($EmailCollectorScriptSource -and (Test-Path -LiteralPath $EmailCollectorScriptSource)) { Copy-Item -LiteralPath $EmailCollectorScriptSource -Destination $emailCollectorTarget -Force } @@ -306,6 +445,9 @@ function Copy-ActivityWatchCollectorAssets { $resolvedRules = Resolve-Path -LiteralPath $CustomRulesSource -ErrorAction Stop Copy-Item -LiteralPath $resolvedRules.Path -Destination $rulesTarget -Force } + else { + Copy-Item -LiteralPath $exampleRulesTarget -Destination $rulesTarget -Force + } if ($CustomPolicySource) { $resolvedPolicy = Resolve-Path -LiteralPath $CustomPolicySource -ErrorAction Stop @@ -318,8 +460,10 @@ function Copy-ActivityWatchCollectorAssets { return [pscustomobject]@{ CollectorScript = $collectorTarget EndpointCollectorScript = $endpointCollectorTarget + PolicyClientScript = $policyClientTarget FileCollectorScript = $fileCollectorTarget SessionCollectorScript = $sessionCollectorTarget + EvtxExportScript = $evtxExportTarget EmailCollectorScript = $emailCollectorTarget ExampleRules = $exampleRulesTarget ActiveRules = $rulesTarget @@ -346,10 +490,12 @@ function New-ActivityWatchDeploymentConfig { [string]$CollectorScript, [Parameter(Mandatory = $true)] [string]$EndpointCollectorScript, + [string]$PolicyClientScript, [Parameter(Mandatory = $true)] [string]$FileCollectorScript, [Parameter(Mandatory = $true)] [string]$SessionCollectorScript, + [string]$EvtxExportScript, [string]$EmailCollectorScript, [Parameter(Mandatory = $true)] [string]$RulesPath, @@ -368,21 +514,51 @@ function New-ActivityWatchDeploymentConfig { [bool]$IncidentCaptureEnabled = $true, [bool]$IncidentScreenshotEnabled = $true, [string]$IncidentArtifactsRoot, + [string]$EvtxExportRoot, + [int]$EvtxRetentionDays = 14, + [string[]]$EvtxChannels = @(), [bool]$LogonMarkerEnabled = $true, [Parameter(Mandatory = $true)] [string]$LaunchScriptPath, [Parameter(Mandatory = $true)] [string]$RecoveryScriptPath, + [string]$AwHostname, + [ValidateSet('local', 'server')] + [string]$PolicyMode = 'local', + [bool]$PolicyEngineEnabled = $false, + [string]$PolicyEngineHost, + [int]$PolicyEnginePort = 5601, + [ValidateSet('http', 'https')] + [string]$PolicyEngineScheme = 'http', + [int]$PolicyRefreshSeconds = 300, + [string]$PolicyCachePath, [Parameter(Mandatory = $true)] [pscustomobject[]]$UserTasks, - [string]$PackageVersion = 'v0.13.2' + [string]$PackageVersion = 'v0.13.2', + [switch]$IntegrationTestEnabled ) $effectiveIncidentArtifactsRoot = if ($IncidentArtifactsRoot) { $IncidentArtifactsRoot } else { Join-Path $StateRoot 'incident-artifacts' } + $effectiveEvtxExportRoot = if ($EvtxExportRoot) { $EvtxExportRoot } else { Join-Path $StateRoot 'forensics\evtx-exports' } + $effectiveEvtxChannels = if ($EvtxChannels -and $EvtxChannels.Count -gt 0) { + @($EvtxChannels) + } else { + @( + 'Security', + 'System', + 'Application', + 'Microsoft-Windows-PowerShell/Operational', + 'Microsoft-Windows-TerminalServices-LocalSessionManager/Operational', + 'Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational' + ) + } + $effectivePolicyEngineHost = if ([string]::IsNullOrWhiteSpace($PolicyEngineHost)) { $ServerHost } else { $PolicyEngineHost } + $effectivePolicyCachePath = if ([string]::IsNullOrWhiteSpace($PolicyCachePath)) { Join-Path $StateRoot 'dlp-policy-cache.json' } else { $PolicyCachePath } return [pscustomobject]@{ version = 1 generatedAtUtc = (Get-Date).ToUniversalTime().ToString('o') + awHostname = if ([string]::IsNullOrWhiteSpace($AwHostname)) { [string]$env:COMPUTERNAME } else { [string]$AwHostname } server = [pscustomobject]@{ host = $ServerHost port = $ServerPort @@ -394,9 +570,11 @@ function New-ActivityWatchDeploymentConfig { logsRoot = $LogsRoot collectorScript = $CollectorScript endpointCollectorScript = $EndpointCollectorScript + policyClientScript = $PolicyClientScript emailCollectorScript = $EmailCollectorScript fileCollectorScript = $FileCollectorScript sessionCollectorScript = $SessionCollectorScript + evtxExportScript = $EvtxExportScript rulesPath = $RulesPath policyPath = $PolicyPath launchScript = $LaunchScriptPath @@ -410,7 +588,7 @@ function New-ActivityWatchDeploymentConfig { afkEnabled = $AfkEnabled windowEnabled = $WindowEnabled fileOpsEnabled = $FileOpsEnabled - emailEnabled = ($null -ne $EmailCollectorScript -and $EmailCollectorScript -ne '') + emailEnabled = $false } logging = [pscustomobject]@{ localAgentLogsEnabled = $LocalAgentLogsEnabled @@ -420,6 +598,11 @@ function New-ActivityWatchDeploymentConfig { screenshotEnabled = $IncidentScreenshotEnabled artifactsRoot = $effectiveIncidentArtifactsRoot } + forensics = [pscustomobject]@{ + evtxExportRoot = $effectiveEvtxExportRoot + retentionDays = $EvtxRetentionDays + evtxChannels = @($effectiveEvtxChannels) + } sessionEvents = [pscustomobject]@{ logonEnabled = $LogonMarkerEnabled bucketPrefix = 'aw-session-events' @@ -432,10 +615,20 @@ function New-ActivityWatchDeploymentConfig { incidentBucketPrefix = 'aw-dlp-incidents' enabled = $true } + policyEngine = [pscustomobject]@{ + enabled = $PolicyEngineEnabled + mode = $PolicyMode + host = $effectivePolicyEngineHost + port = $PolicyEnginePort + scheme = $PolicyEngineScheme + refreshSeconds = $PolicyRefreshSeconds + cachePath = $effectivePolicyCachePath + } package = [pscustomobject]@{ version = $PackageVersion } userTasks = @($UserTasks) + integrationTestEnabled = [bool]$IntegrationTestEnabled } } @@ -534,11 +727,7 @@ function Get-CollectorPowerShellProcessCount { function New-LaunchLock { param([string]`$StateRoot, [int]`$SessionId) - if (-not (Test-Path -LiteralPath `$StateRoot)) { - New-Item -Path `$StateRoot -ItemType Directory -Force | Out-Null - } - - `$lockPath = Join-Path `$StateRoot ("launch-watchers-session-{0}.lock" -f `$SessionId) + `$lockPath = Join-Path `$env:TEMP ("launch-watchers-session-{0}.lock" -f `$SessionId) if (Test-Path -LiteralPath `$lockPath) { try { `$lockData = Get-Content -LiteralPath `$lockPath -Raw | ConvertFrom-Json @@ -560,6 +749,33 @@ function New-LaunchLock { return `$lockPath } +function Get-SessionMarkerToken { + param([int]`$SessionId) + + try { + `$explorer = Get-Process -Name 'explorer' -ErrorAction SilentlyContinue | + Where-Object { `$_.SessionId -eq `$SessionId } | + Sort-Object StartTime | + Select-Object -First 1 + if (`$explorer -and `$explorer.StartTime) { + return `$explorer.StartTime.ToUniversalTime().ToString('yyyyMMddTHHmmssZ') + } + } + catch { + } + + try { + `$currentProcess = Get-Process -Id `$PID -ErrorAction Stop + if (`$currentProcess.StartTime) { + return `$currentProcess.StartTime.ToUniversalTime().ToString('yyyyMMddTHHmmssZ') + } + } + catch { + } + + return [string]`$SessionId +} + function Invoke-AwJsonPost { param( [Parameter(Mandatory = `$true)][string]`$Uri, @@ -675,7 +891,8 @@ function Send-LogonMarkerIfNeeded { return } - `$markerFile = Join-Path `$markerDir ("logon-{0}-{1}.marker" -f `$env:USERNAME, `$SessionId) + `$sessionMarkerToken = Get-SessionMarkerToken -SessionId `$SessionId + `$markerFile = Join-Path `$markerDir ("logon-{0}-{1}-{2}.marker" -f `$env:USERNAME, `$SessionId, `$sessionMarkerToken) if (Test-Path -LiteralPath `$markerFile) { return } @@ -731,13 +948,11 @@ function Start-CollectorScriptIfNeeded { return } - Start-Process -FilePath `$PowerShellExe -ArgumentList @( - '-NoProfile', - '-WindowStyle', 'Hidden', - '-ExecutionPolicy', 'Bypass', - '-File', `$ScriptPath, - '-ConfigPath', `$ConfigPath - ) -WindowStyle Hidden + `$staParam = if (`$ScriptPath -like "*endpoint-signals*") { "-STA" } else { `$null } + `$argumentList = @('-NoProfile', '-WindowStyle', 'Hidden', '-ExecutionPolicy', 'Bypass') + if (`$staParam) { `$argumentList += `$staParam } + `$argumentList += @('-File', `$ScriptPath, '-ConfigPath', `$ConfigPath) + Start-Process -FilePath `$PowerShellExe -ArgumentList `$argumentList -WindowStyle Hidden } `$config = Get-DeploymentConfig -Path `$ConfigPath @@ -745,7 +960,7 @@ function Start-CollectorScriptIfNeeded { `$installRoot = [string]`$config.paths.installRoot `$stateRoot = [string]`$config.paths.stateRoot `$script:ApiBase = '{0}://{1}:{2}/api/0' -f [string]`$config.server.scheme, [string]`$config.server.host, [string]`$config.server.port -`$script:Hostname = `$env:COMPUTERNAME +`$script:Hostname = if (`$config.PSObject.Properties.Name -contains 'awHostname' -and -not [string]::IsNullOrWhiteSpace([string]`$config.awHostname)) { [string]`$config.awHostname } else { `$env:COMPUTERNAME } `$script:KnownBuckets = @{} `$collectorScript = [string]`$config.paths.collectorScript `$endpointCollectorScript = if (`$config.paths.PSObject.Properties.Name -contains 'endpointCollectorScript') { [string]`$config.paths.endpointCollectorScript } else { Join-Path `$stateRoot 'dlp-endpoint-signals-collector.ps1' } @@ -792,7 +1007,6 @@ try { if (`$fileOpsEnabled) { Start-CollectorScriptIfNeeded -ScriptPath `$fileCollectorScript -ConfigPath `$ConfigPath -PowerShellExe `$powershellExe -SessionId `$sessionId } - Start-CollectorScriptIfNeeded -ScriptPath `$sessionCollectorScript -ConfigPath `$ConfigPath -PowerShellExe `$powershellExe -SessionId `$sessionId if (`$emailEnabled -and (Test-Path -LiteralPath `$emailCollectorScript)) { Start-CollectorScriptIfNeeded -ScriptPath `$emailCollectorScript -ConfigPath `$ConfigPath -PowerShellExe `$powershellExe -SessionId `$sessionId } @@ -859,17 +1073,21 @@ function Get-RecoveryConfigPaths { return @(`$paths | Sort-Object -Unique) } -function Get-RecoveryTaskNames { +function Get-RecoveryTaskDefinitions { param([string[]]`$ConfigPaths) - `$taskNames = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase) + `$taskMap = [ordered]@{} foreach (`$candidatePath in @(`$ConfigPaths)) { try { `$config = Get-DeploymentConfig -Path `$candidatePath foreach (`$task in @(`$config.userTasks)) { `$taskName = [string]`$task.launchTaskName - if (-not [string]::IsNullOrWhiteSpace(`$taskName)) { - [void]`$taskNames.Add(`$taskName) + `$userId = [string]`$task.userId + if (-not [string]::IsNullOrWhiteSpace(`$taskName) -and -not `$taskMap.Contains(`$taskName)) { + `$taskMap[`$taskName] = [pscustomobject]@{ + taskName = `$taskName + userId = `$userId + } } } } @@ -877,7 +1095,7 @@ function Get-RecoveryTaskNames { } } - return @(`$taskNames) + return @(`$taskMap.Values) } function New-RecoveryLock { @@ -910,11 +1128,23 @@ function New-RecoveryLock { } function Start-TaskIfNotRunning { - param([string]`$TaskName) + param( + [string]`$TaskName, + [string]`$UserId, + [string[]]`$LoggedOnUsers + ) if ([string]::IsNullOrWhiteSpace(`$TaskName)) { return } + if ([string]::IsNullOrWhiteSpace(`$UserId)) { + return + } + + if (-not (Test-UserHasSession -UserId `$UserId -LoggedOnUsers `$LoggedOnUsers)) { + return + } + try { `$task = Get-ScheduledTask -TaskName `$TaskName -ErrorAction SilentlyContinue if (-not `$task) { @@ -929,6 +1159,120 @@ function Start-TaskIfNotRunning { } } +function Get-LoggedOnUsers { + `$users = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase) + + try { + `$lines = & quser.exe 2>`$null + foreach (`$line in @(`$lines)) { + `$normalized = [string]`$line + if ([string]::IsNullOrWhiteSpace(`$normalized)) { + continue + } + + `$normalized = `$normalized.TrimStart(' ', '>') + if ([string]::IsNullOrWhiteSpace(`$normalized)) { + continue + } + + if (`$normalized -match '^(USERNAME|ПОЛЬЗОВАТЕЛЬ)\s+') { + continue + } + + `$parts = `$normalized -split '\s+' + if (`$parts.Count -lt 1) { + continue + } + + `$user = [string]`$parts[0] + if ([string]::IsNullOrWhiteSpace(`$user)) { + continue + } + + [void]`$users.Add(`$user) + [void]`$users.Add(('{0}\{1}' -f `$env:COMPUTERNAME, `$user)) + if (-not [string]::IsNullOrWhiteSpace(`$env:USERDOMAIN)) { + [void]`$users.Add(('{0}\{1}' -f `$env:USERDOMAIN, `$user)) + } + } + } + catch { + } + + return @(`$users) +} + +function Test-UserHasSession { + param( + [string]`$UserId, + [string[]]`$LoggedOnUsers + ) + + if ([string]::IsNullOrWhiteSpace(`$UserId)) { + return `$false + } + + `$candidateIds = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase) + [void]`$candidateIds.Add(`$UserId) + + `$leafUser = `$UserId + if (`$leafUser -match '^[^\\]+\\(.+)$') { + `$leafUser = `$Matches[1] + [void]`$candidateIds.Add(`$leafUser) + } + + [void]`$candidateIds.Add(('{0}\{1}' -f `$env:COMPUTERNAME, `$leafUser)) + if (-not [string]::IsNullOrWhiteSpace(`$env:USERDOMAIN)) { + [void]`$candidateIds.Add(('{0}\{1}' -f `$env:USERDOMAIN, `$leafUser)) + } + + foreach (`$candidate in @(`$candidateIds)) { + if (`$LoggedOnUsers -contains `$candidate) { + return `$true + } + } + + return `$false +} + +function Test-CollectorRunningGlobal { + param([string]`$ScriptPath) + if ([string]::IsNullOrWhiteSpace(`$ScriptPath)) { + return `$false + } + + return [bool]@( + Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | + Where-Object { + (`$_.Name -ieq 'powershell.exe' -or `$_.Name -ieq 'pwsh.exe') -and + `$_.CommandLine -match [Regex]::Escape(`$ScriptPath) + } + ).Count +} + +function Start-CollectorScriptGlobalIfNeeded { + param( + [string]`$ScriptPath, + [string]`$ConfigPath + ) + + if ([string]::IsNullOrWhiteSpace(`$ScriptPath)) { + return + } + + if (-not (Test-Path -LiteralPath `$ScriptPath)) { + return + } + + if (Test-CollectorRunningGlobal -ScriptPath `$ScriptPath) { + return + } + + `$powershellExe = Join-Path `$env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe' + `$argumentList = @('-NoProfile', '-WindowStyle', 'Hidden', '-ExecutionPolicy', 'Bypass', '-File', `$ScriptPath, '-ConfigPath', `$ConfigPath) + Start-Process -FilePath `$powershellExe -ArgumentList `$argumentList -WindowStyle Hidden +} + `$recoveryLockPath = New-RecoveryLock -PrimaryConfigPath `$ConfigPath if (-not `$recoveryLockPath) { return @@ -939,11 +1283,15 @@ try { `$sleepSeconds = 180 try { `$configPaths = Get-RecoveryConfigPaths -PrimaryConfigPath `$ConfigPath - foreach (`$taskName in Get-RecoveryTaskNames -ConfigPaths `$configPaths) { - Start-TaskIfNotRunning -TaskName `$taskName + `$config = Get-DeploymentConfig -Path `$ConfigPath + `$loggedOnUsers = Get-LoggedOnUsers + `$stateRoot = [string]`$config.paths.stateRoot + `$sessionCollectorScript = if (`$config.paths.PSObject.Properties.Name -contains 'sessionCollectorScript') { [string]`$config.paths.sessionCollectorScript } else { Join-Path `$stateRoot 'worktime-session-collector.ps1' } + Start-CollectorScriptGlobalIfNeeded -ScriptPath `$sessionCollectorScript -ConfigPath `$ConfigPath + foreach (`$taskDef in Get-RecoveryTaskDefinitions -ConfigPaths `$configPaths) { + Start-TaskIfNotRunning -TaskName `$taskDef.taskName -UserId `$taskDef.userId -LoggedOnUsers `$loggedOnUsers } - `$config = Get-DeploymentConfig -Path `$ConfigPath if (`$config -and `$config.recovery -and `$config.recovery.intervalSeconds) { `$sleepSeconds = [Math]::Max([int]`$config.recovery.intervalSeconds, 30) } @@ -1049,10 +1397,24 @@ function Set-ActivityWatchScheduledTaskAction { [string]$Arguments ) - $taskCommand = ('"{0}" {1}' -f $Execute, $Arguments) - & schtasks.exe /Change /TN $TaskName /TR $taskCommand | Out-Null - if ($LASTEXITCODE -ne 0) { - throw "schtasks.exe /Change завершился с ошибкой для $TaskName" + $task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue + if (-not $task) { + return $false + } + + $newAction = New-ScheduledTaskAction -Execute $Execute -Argument $Arguments + try { + # Non-interactive update path. Avoids schtasks.exe /Change password prompt for user-bound tasks. + Set-ScheduledTask -TaskName $TaskName -Action $newAction -ErrorAction Stop | Out-Null + return $true + } + catch { + $taskCommand = ('"{0}" {1}' -f $Execute, $Arguments) + & schtasks.exe /Change /TN $TaskName /TR $taskCommand | Out-Null + if ($LASTEXITCODE -ne 0) { + throw "Не удалось обновить action задачи ${TaskName}: $($_.Exception.Message)" + } + return $true } } @@ -1137,8 +1499,10 @@ function Register-ActivityWatchUserTasks { $existingTask = Get-ActivityWatchScheduledTaskByCommand -TaskName $definition.LaunchTaskName -CommandMatch $ConfigPath if ($existingTask) { - Set-ActivityWatchScheduledTaskAction -TaskName $existingTask.TaskName -Execute $wscriptExe -Arguments $action.Arguments - continue + $updated = Set-ActivityWatchScheduledTaskAction -TaskName $existingTask.TaskName -Execute $wscriptExe -Arguments $action.Arguments + if ($updated) { + continue + } } Remove-ActivityWatchScheduledTask -TaskName $definition.LaunchTaskName @@ -1206,8 +1570,12 @@ function Start-ActivityWatchTasks { [string]$RecoveryTaskName = 'ActivityWatch Recovery' ) + $loggedOnUsers = Get-ActivityWatchLoggedOnUsers + foreach ($definition in $TaskDefinitions) { - Start-ScheduledTask -TaskName $definition.LaunchTaskName -ErrorAction SilentlyContinue + if (Test-ActivityWatchUserHasSession -UserId $definition.UserId -LoggedOnUsers $loggedOnUsers) { + Start-ScheduledTask -TaskName $definition.LaunchTaskName -ErrorAction SilentlyContinue + } } Start-ScheduledTask -TaskName $RecoveryTaskName -ErrorAction SilentlyContinue diff --git a/install-kit-awindows-20260427-211240/windows/browser-domains-native-collector.ps1 b/install-kit-awindows-20260427-211240/windows/browser-domains-native-collector.ps1 index d0c809f..b40b48c 100755 --- a/install-kit-awindows-20260427-211240/windows/browser-domains-native-collector.ps1 +++ b/install-kit-awindows-20260427-211240/windows/browser-domains-native-collector.ps1 @@ -62,13 +62,14 @@ $resolvedIncidentLogPath = if ($IncidentLogPath) { $IncidentLogPath } else { Joi $resolvedLocalAgentLogsEnabled = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'logging' -and $deploymentConfig.logging.PSObject.Properties.Name -contains 'localAgentLogsEnabled') { [bool]$deploymentConfig.logging.localAgentLogsEnabled } else { $true } $resolvedIncidentArtifactsRoot = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $deploymentConfig.incidentCapture.PSObject.Properties.Name -contains 'artifactsRoot') { [string]$deploymentConfig.incidentCapture.artifactsRoot } else { Join-Path $env:LOCALAPPDATA 'AWatch-rus\\incident-artifacts' } $resolvedIncidentScreenshotEnabled = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $deploymentConfig.incidentCapture.PSObject.Properties.Name -contains 'screenshotEnabled') { [bool]$deploymentConfig.incidentCapture.screenshotEnabled } else { $true } +$resolvedHostname = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'awHostname' -and -not [string]::IsNullOrWhiteSpace([string]$deploymentConfig.awHostname)) { [string]$deploymentConfig.awHostname } else { [string]$env:COMPUTERNAME } if ($resolvedLocalAgentLogsEnabled -and -not (Test-Path -LiteralPath $resolvedLogsRoot)) { New-Item -Path $resolvedLogsRoot -ItemType Directory -Force | Out-Null } $script:ApiBase = '{0}://{1}:{2}/api/0' -f $resolvedServerScheme, $resolvedServerHost, $resolvedServerPort -$script:Hostname = $env:COMPUTERNAME +$script:Hostname = $resolvedHostname $script:SessionId = (Get-Process -Id $PID).SessionId $script:KnownBuckets = @{} $script:LocalAgentLogsEnabled = $resolvedLocalAgentLogsEnabled @@ -541,7 +542,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 | Out-Null + Invoke-RestMethod -Method Post -Uri "$($script:ApiBase)/buckets/$bucketId/heartbeat?pulsetime=$resolvedPulseSeconds" -ContentType 'application/json' -Body $event -TimeoutSec 15 -DisableKeepAlive | Out-Null } function Get-FileSha256Hex { @@ -701,13 +702,26 @@ function Ensure-Bucket { return } + try { + Invoke-RestMethod -Method Get -Uri "$($script:ApiBase)/buckets/$BucketId" | Out-Null + $script:KnownBuckets[$BucketId] = $true + return + } + catch { + } + $body = @{ client = $ClientName type = $BucketType hostname = $script:Hostname } | ConvertTo-Json -Compress - Invoke-RestMethod -Method Post -Uri "$($script:ApiBase)/buckets/$BucketId" -ContentType 'application/json' -Body $body | Out-Null + try { + Invoke-RestMethod -Method Post -Uri "$($script:ApiBase)/buckets/$BucketId" -ContentType 'application/json; charset=utf-8' -Body ([Text.Encoding]::UTF8.GetBytes($body)) | Out-Null + } + catch { + Invoke-RestMethod -Method Get -Uri "$($script:ApiBase)/buckets/$BucketId" | Out-Null + } $script:KnownBuckets[$BucketId] = $true } @@ -733,7 +747,7 @@ 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 | Out-Null + Invoke-RestMethod -Method Post -Uri "$($script:ApiBase)/buckets/$BucketId/heartbeat?pulsetime=$resolvedPulseSeconds" -ContentType 'application/json' -Body $event -TimeoutSec 15 -DisableKeepAlive | Out-Null } function Send-CategoryHeartbeat { @@ -770,7 +784,7 @@ 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 | Out-Null + Invoke-RestMethod -Method Post -Uri "$($script:ApiBase)/buckets/$bucketId/heartbeat?pulsetime=$resolvedPulseSeconds" -ContentType 'application/json' -Body $event -TimeoutSec 15 -DisableKeepAlive | Out-Null } Load-CustomCategoryRules -Path $resolvedRulesPath diff --git a/install-kit-awindows-20260427-211240/windows/deploy-domain-users.ps1 b/install-kit-awindows-20260427-211240/windows/deploy-domain-users.ps1 index 92e5494..0f9cb1f 100755 --- a/install-kit-awindows-20260427-211240/windows/deploy-domain-users.ps1 +++ b/install-kit-awindows-20260427-211240/windows/deploy-domain-users.ps1 @@ -23,9 +23,23 @@ param( [bool]$IncidentCaptureEnabled = $true, [bool]$IncidentScreenshotEnabled = $true, [string]$IncidentArtifactsRoot, + [string]$EvtxExportRoot, + [int]$EvtxRetentionDays = 14, + [string[]]$EvtxChannels = @(), [bool]$LogonMarkerEnabled = $true, + [string]$AwHostname, [string]$CustomRulesPath, - [string]$CustomPolicyPath + [string]$CustomPolicyPath, + [ValidateSet('local', 'server')] + [string]$PolicyMode = 'local', + [bool]$PolicyEngineEnabled = $false, + [string]$PolicyEngineHost, + [int]$PolicyEnginePort = 5601, + [ValidateSet('http', 'https')] + [string]$PolicyEngineScheme = 'http', + [int]$PolicyRefreshSeconds = 300, + [string]$PolicyCachePath, + [switch]$IntegrationTestEnabled ) Set-StrictMode -Version Latest @@ -45,9 +59,11 @@ $launchScriptPath = Join-Path $StateRoot 'launch-watchers.ps1' $recoveryScriptPath = Join-Path $StateRoot 'recovery-loop.ps1' $collectorSource = Join-Path $PSScriptRoot 'browser-domains-native-collector.ps1' $endpointCollectorSource = Join-Path $PSScriptRoot 'dlp-endpoint-signals-collector.ps1' +$policyClientSource = Join-Path $PSScriptRoot 'dlp-policy-client.ps1' $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' $exampleRulesSource = Join-Path $PSScriptRoot 'web-category-rules.example.json' $examplePolicySource = Join-Path $PSScriptRoot 'dlp-policy.example.json' @@ -62,9 +78,11 @@ Get-ActivityWatchExecutableMap -InstallRoot $InstallRoot | Out-Null $assetResult = Copy-ActivityWatchCollectorAssets ` -CollectorScriptSource $collectorSource ` -EndpointCollectorScriptSource $endpointCollectorSource ` + -PolicyClientScriptSource $policyClientSource ` -EmailCollectorScriptSource $emailCollectorSource ` -FileCollectorScriptSource $fileCollectorSource ` -SessionCollectorScriptSource $sessionCollectorSource ` + -EvtxExportScriptSource $evtxExportScriptSource ` -ExampleRulesSource $exampleRulesSource ` -ExamplePolicySource $examplePolicySource ` -StateRoot $StateRoot ` @@ -84,9 +102,11 @@ $config = New-ActivityWatchDeploymentConfig ` -LogsRoot $logsRoot ` -CollectorScript $assetResult.CollectorScript ` -EndpointCollectorScript $assetResult.EndpointCollectorScript ` + -PolicyClientScript $assetResult.PolicyClientScript ` -EmailCollectorScript $assetResult.EmailCollectorScript ` -FileCollectorScript $assetResult.FileCollectorScript ` -SessionCollectorScript $assetResult.SessionCollectorScript ` + -EvtxExportScript $assetResult.EvtxExportScript ` -RulesPath $assetResult.ActiveRules ` -PolicyPath $assetResult.ActivePolicy ` -PollSeconds $PollSeconds ` @@ -99,11 +119,23 @@ $config = New-ActivityWatchDeploymentConfig ` -IncidentCaptureEnabled $IncidentCaptureEnabled ` -IncidentScreenshotEnabled $IncidentScreenshotEnabled ` -IncidentArtifactsRoot $IncidentArtifactsRoot ` + -EvtxExportRoot $EvtxExportRoot ` + -EvtxRetentionDays $EvtxRetentionDays ` + -EvtxChannels $EvtxChannels ` -LogonMarkerEnabled $LogonMarkerEnabled ` + -AwHostname $AwHostname ` + -PolicyMode $PolicyMode ` + -PolicyEngineEnabled $PolicyEngineEnabled ` + -PolicyEngineHost $PolicyEngineHost ` + -PolicyEnginePort $PolicyEnginePort ` + -PolicyEngineScheme $PolicyEngineScheme ` + -PolicyRefreshSeconds $PolicyRefreshSeconds ` + -PolicyCachePath $PolicyCachePath ` -LaunchScriptPath $launchScriptPath ` -RecoveryScriptPath $recoveryScriptPath ` -UserTasks $taskDefinitions ` - -PackageVersion $Version + -PackageVersion $Version ` + -IntegrationTestEnabled:$IntegrationTestEnabled Write-ActivityWatchDeploymentConfig -Config $config -Path $configPath Remove-LegacyActivityWatchEntries diff --git a/install-kit-awindows-20260427-211240/windows/deploy-ensemble.ps1 b/install-kit-awindows-20260427-211240/windows/deploy-ensemble.ps1 index fa6fd75..11d4366 100644 --- a/install-kit-awindows-20260427-211240/windows/deploy-ensemble.ps1 +++ b/install-kit-awindows-20260427-211240/windows/deploy-ensemble.ps1 @@ -23,12 +23,26 @@ param( [bool]$IncidentCaptureEnabled = $true, [bool]$IncidentScreenshotEnabled = $true, [string]$IncidentArtifactsRoot, + [string]$EvtxExportRoot, + [int]$EvtxRetentionDays = 14, + [string[]]$EvtxChannels = @(), [bool]$LogonMarkerEnabled = $true, + [string]$AwHostname, [string]$CustomRulesPath, [string]$CustomPolicyPath, + [ValidateSet('local', 'server')] + [string]$PolicyMode = 'local', + [bool]$PolicyEngineEnabled = $false, + [string]$PolicyEngineHost, + [int]$PolicyEnginePort = 5601, + [ValidateSet('http', 'https')] + [string]$PolicyEngineScheme = 'http', + [int]$PolicyRefreshSeconds = 300, + [string]$PolicyCachePath, [string]$ReportPath, [switch]$SkipHardening, - [switch]$ValidateAfterDeploy + [switch]$ValidateAfterDeploy, + [switch]$IntegrationTestEnabled ) Set-StrictMode -Version Latest @@ -70,9 +84,21 @@ if (-not (Test-Path -LiteralPath $deployScript)) { -IncidentCaptureEnabled $IncidentCaptureEnabled ` -IncidentScreenshotEnabled $IncidentScreenshotEnabled ` -IncidentArtifactsRoot $IncidentArtifactsRoot ` + -EvtxExportRoot $EvtxExportRoot ` + -EvtxRetentionDays $EvtxRetentionDays ` + -EvtxChannels $EvtxChannels ` -LogonMarkerEnabled $LogonMarkerEnabled ` + -AwHostname $AwHostname ` -CustomRulesPath $CustomRulesPath ` - -CustomPolicyPath $CustomPolicyPath + -CustomPolicyPath $CustomPolicyPath ` + -PolicyMode $PolicyMode ` + -PolicyEngineEnabled $PolicyEngineEnabled ` + -PolicyEngineHost $PolicyEngineHost ` + -PolicyEnginePort $PolicyEnginePort ` + -PolicyEngineScheme $PolicyEngineScheme ` + -PolicyRefreshSeconds $PolicyRefreshSeconds ` + -PolicyCachePath $PolicyCachePath ` + -IntegrationTestEnabled:$IntegrationTestEnabled if (-not $SkipHardening) { & $hardeningScript ` @@ -93,9 +119,20 @@ if (-not $SkipHardening) { -IncidentCaptureEnabled $IncidentCaptureEnabled ` -IncidentScreenshotEnabled $IncidentScreenshotEnabled ` -IncidentArtifactsRoot $IncidentArtifactsRoot ` + -EvtxExportRoot $EvtxExportRoot ` + -EvtxRetentionDays $EvtxRetentionDays ` + -EvtxChannels $EvtxChannels ` -LogonMarkerEnabled $LogonMarkerEnabled ` + -AwHostname $AwHostname ` -CustomRulesPath $CustomRulesPath ` - -CustomPolicyPath $CustomPolicyPath + -CustomPolicyPath $CustomPolicyPath ` + -PolicyMode $PolicyMode ` + -PolicyEngineEnabled $PolicyEngineEnabled ` + -PolicyEngineHost $PolicyEngineHost ` + -PolicyEnginePort $PolicyEnginePort ` + -PolicyEngineScheme $PolicyEngineScheme ` + -PolicyRefreshSeconds $PolicyRefreshSeconds ` + -PolicyCachePath $PolicyCachePath } $report = [ordered]@{ diff --git a/install-kit-awindows-20260427-211240/windows/deploy-single-user.ps1 b/install-kit-awindows-20260427-211240/windows/deploy-single-user.ps1 index 160265d..c970c13 100755 --- a/install-kit-awindows-20260427-211240/windows/deploy-single-user.ps1 +++ b/install-kit-awindows-20260427-211240/windows/deploy-single-user.ps1 @@ -21,7 +21,11 @@ param( [bool]$IncidentCaptureEnabled = $true, [bool]$IncidentScreenshotEnabled = $true, [string]$IncidentArtifactsRoot, + [string]$EvtxExportRoot, + [int]$EvtxRetentionDays = 14, + [string[]]$EvtxChannels = @(), [bool]$LogonMarkerEnabled = $true, + [string]$AwHostname, [string]$CustomRulesPath, [string]$CustomPolicyPath ) @@ -44,6 +48,7 @@ $collectorSource = Join-Path $PSScriptRoot 'browser-domains-native-collector.ps1 $endpointCollectorSource = Join-Path $PSScriptRoot 'dlp-endpoint-signals-collector.ps1' $emailCollectorSource = Join-Path $PSScriptRoot 'email-outbound-collector.ps1' $sessionCollectorSource = Join-Path $PSScriptRoot 'worktime-session-collector.ps1' +$evtxExportScriptSource = Join-Path $PSScriptRoot 'export-evtx-for-hayabusa.ps1' $exampleRulesSource = Join-Path $PSScriptRoot 'web-category-rules.example.json' $examplePolicySource = Join-Path $PSScriptRoot 'dlp-policy.example.json' @@ -59,6 +64,7 @@ $assetResult = Copy-ActivityWatchCollectorAssets ` -EndpointCollectorScriptSource $endpointCollectorSource ` -EmailCollectorScriptSource $emailCollectorSource ` -SessionCollectorScriptSource $sessionCollectorSource ` + -EvtxExportScriptSource $evtxExportScriptSource ` -ExampleRulesSource $exampleRulesSource ` -ExamplePolicySource $examplePolicySource ` -StateRoot $StateRoot ` @@ -80,6 +86,7 @@ $config = New-ActivityWatchDeploymentConfig ` -EndpointCollectorScript $assetResult.EndpointCollectorScript ` -EmailCollectorScript $assetResult.EmailCollectorScript ` -SessionCollectorScript $assetResult.SessionCollectorScript ` + -EvtxExportScript $assetResult.EvtxExportScript ` -RulesPath $assetResult.ActiveRules ` -PolicyPath $assetResult.ActivePolicy ` -PollSeconds $PollSeconds ` @@ -91,7 +98,11 @@ $config = New-ActivityWatchDeploymentConfig ` -IncidentCaptureEnabled $IncidentCaptureEnabled ` -IncidentScreenshotEnabled $IncidentScreenshotEnabled ` -IncidentArtifactsRoot $IncidentArtifactsRoot ` + -EvtxExportRoot $EvtxExportRoot ` + -EvtxRetentionDays $EvtxRetentionDays ` + -EvtxChannels $EvtxChannels ` -LogonMarkerEnabled $LogonMarkerEnabled ` + -AwHostname $AwHostname ` -LaunchScriptPath $launchScriptPath ` -RecoveryScriptPath $recoveryScriptPath ` -UserTasks $taskDefinitions ` diff --git a/install-kit-awindows-20260427-211240/windows/dlp-endpoint-signals-collector.ps1 b/install-kit-awindows-20260427-211240/windows/dlp-endpoint-signals-collector.ps1 index b286e48..9a754f0 100644 --- a/install-kit-awindows-20260427-211240/windows/dlp-endpoint-signals-collector.ps1 +++ b/install-kit-awindows-20260427-211240/windows/dlp-endpoint-signals-collector.ps1 @@ -1,11 +1,19 @@ -[CmdletBinding()] +[CmdletBinding()] param( [string]$ConfigPath = 'C:\ProgramData\AWatch-rus\deployment-config.json', [string]$ServerHost, [int]$ServerPort, [ValidateSet('http', 'https')] [string]$ServerScheme, + [string]$PolicyEngineHost, + [int]$PolicyEnginePort, + [ValidateSet('http', 'https')] + [string]$PolicyEngineScheme, [string]$PolicyPath, + [ValidateSet('local', 'server')] + [string]$PolicyMode, + [int]$PolicyRefreshSeconds, + [string]$PolicyCachePath, [string]$LogPath, [int]$PollSeconds ) @@ -13,6 +21,36 @@ param( Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' +# Ensure HttpClient is available (Windows PowerShell 5 may not auto-load it) +try { + Add-Type -AssemblyName System.Net.Http +} +catch { +} + +$script:TransportQueuePath = $null +$script:TransportQueueLockPath = $null +$script:TransportMetrics = @{ + eventsEnqueued = 0 + eventsFlushed = 0 + sendFailures = 0 + queueDepth = 0 +} + +$policyClientModulePath = Join-Path $PSScriptRoot 'dlp-policy-client.ps1' +if (Test-Path -LiteralPath $policyClientModulePath) { + try { + Import-Module $policyClientModulePath -Force -DisableNameChecking + $script:PolicyClientAvailable = $true + } + catch { + $script:PolicyClientAvailable = $false + } +} +else { + $script:PolicyClientAvailable = $false +} + function Get-DeploymentConfig { param([string]$Path) if ($Path -and (Test-Path -LiteralPath $Path)) { @@ -39,8 +77,146 @@ function Invoke-AwJsonPost { [Parameter(Mandatory = $true)][string]$Json ) - $bytes = [Text.Encoding]::UTF8.GetBytes($Json) - Invoke-RestMethod -Method Post -Uri $Uri -ContentType 'application/json; charset=utf-8' -Body $bytes | Out-Null + try { + $bytes = [System.Text.Encoding]::UTF8.GetBytes($Json) + $req = [System.Net.HttpWebRequest]::Create($Uri) + $req.Method = 'POST' + $req.ContentType = 'application/json' + $req.Accept = 'application/json' + $req.KeepAlive = $false + $req.Timeout = 15000 + $req.ReadWriteTimeout = 15000 + $req.ContentLength = $bytes.Length + + $stream = $req.GetRequestStream() + try { $stream.Write($bytes, 0, $bytes.Length) } finally { $stream.Close() } + + $resp = $req.GetResponse() + try { + # read body for debugging, but discard on success + $rs = $resp.GetResponseStream() + if ($rs) { $sr = New-Object System.IO.StreamReader($rs); $null = $sr.ReadToEnd(); $sr.Close() } + } finally { + $resp.Close() + } + return + } + catch [System.Net.WebException] { + $status = $null + $body = '' + try { + if ($_.Exception.Response) { + try { $status = [int]$_.Exception.Response.StatusCode } catch {} + $rs = $_.Exception.Response.GetResponseStream() + if ($rs) { $sr = New-Object System.IO.StreamReader($rs); $body = $sr.ReadToEnd(); $sr.Close() } + } + } catch {} + + # aw-server-rust may return 304 for idempotent bucket create. Treat it as OK. + if ($status -eq 304) { + Write-EndpointLog ("POST bucket exists (304): uri={0}" -f $Uri) + return + } + + Write-EndpointLog ("POST failed: uri={0} status={1} err={2} body={3}" -f $Uri, $status, $_.Exception.Message, $body) + throw + } + catch { + Write-EndpointLog ("POST error: uri={0} err={1}" -f $Uri, $_.Exception.Message) + throw + } +} + +function Initialize-TransportQueue { + param([Parameter(Mandatory = $true)][string]$StateRoot) + $script:TransportQueuePath = Join-Path $StateRoot 'dlp-endpoint-signals-queue.jsonl' + $script:TransportQueueLockPath = Join-Path $StateRoot 'dlp-endpoint-signals-queue.lock' + if (-not (Test-Path -LiteralPath $script:TransportQueuePath)) { + New-Item -Path $script:TransportQueuePath -ItemType File -Force | Out-Null + } +} + +function Get-TransportQueueLock { + $tries = 0 + while ($tries -lt 50) { + try { + return [System.IO.File]::Open($script:TransportQueueLockPath, [System.IO.FileMode]::OpenOrCreate, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None) + } + catch { + Start-Sleep -Milliseconds 50 + $tries++ + } + } + throw "Failed to acquire transport queue lock: $script:TransportQueueLockPath" +} + +function Add-TransportQueueItem { + param( + [Parameter(Mandatory = $true)][string]$Uri, + [Parameter(Mandatory = $true)][string]$Payload, + [string]$Kind = 'endpoint' + ) + $lock = Get-TransportQueueLock + try { + $line = @{ + ts = (Get-Date).ToUniversalTime().ToString('o') + uri = $Uri + payload = $Payload + kind = $Kind + } | ConvertTo-Json -Compress + Add-Content -LiteralPath $script:TransportQueuePath -Value $line -Encoding UTF8 + $script:TransportMetrics.eventsEnqueued++ + } + finally { + $lock.Dispose() + } +} + +function Read-TransportQueueItems { + if (-not (Test-Path -LiteralPath $script:TransportQueuePath)) { return @() } + $items = @() + foreach ($line in @(Get-Content -LiteralPath $script:TransportQueuePath -ErrorAction SilentlyContinue)) { + if ([string]::IsNullOrWhiteSpace($line)) { continue } + try { $items += ($line | ConvertFrom-Json) } catch {} + } + return $items +} + +function Flush-TransportQueue { + param([int]$MaxItems = 200) + if (-not (Test-Path -LiteralPath $script:TransportQueuePath)) { return } + $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] + $sent = 0 + foreach ($item in $items) { + if ($sent -ge $MaxItems) { + $left.Add($item) + continue + } + try { + Invoke-AwJsonPost -Uri ([string]$item.uri) -Json ([string]$item.payload) + $sent++ + $script:TransportMetrics.eventsFlushed++ + } + catch { + $script:TransportMetrics.sendFailures++ + $left.Add($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 + } + finally { + $lock.Dispose() + } } function Ensure-Bucket { @@ -54,13 +230,39 @@ function Ensure-Bucket { return } + if ($script:KnownBuckets.ContainsKey($BucketId)) { + return + } + + # Fast-path: if bucket already exists, don't POST. + try { + Invoke-RestMethod -Method Get -Uri "$($script:ApiBase)/buckets/$BucketId" -TimeoutSec 10 -DisableKeepAlive -ErrorAction Stop | Out-Null + Write-EndpointLog ("bucket ok (GET): {0}" -f $BucketId) + $script:KnownBuckets[$BucketId] = $true + return + } + catch { + Write-EndpointLog ("bucket GET failed: {0} err={1}" -f $BucketId, $_.Exception.Message) + } + $body = @{ client = $ClientName type = $BucketType hostname = $script:Hostname } | ConvertTo-Json -Compress - Invoke-AwJsonPost -Uri "$($script:ApiBase)/buckets/$BucketId" -Json $body + try { + Invoke-AwJsonPost -Uri "$($script:ApiBase)/buckets/$BucketId" -Json $body + } + catch { + # If create failed (race), verify it exists now. + try { + Invoke-RestMethod -Method Get -Uri "$($script:ApiBase)/buckets/$BucketId" -TimeoutSec 10 -DisableKeepAlive | Out-Null + } + catch { + throw + } + } $script:KnownBuckets[$BucketId] = $true } @@ -85,7 +287,8 @@ function Send-EndpointSignalHeartbeat { } + $Data } | ConvertTo-Json -Depth 6 -Compress - Invoke-AwJsonPost -Uri "$($script:ApiBase)/buckets/$bucketId/heartbeat?pulsetime=$script:PulseSeconds" -Json $payload + Add-TransportQueueItem -Uri "$($script:ApiBase)/buckets/$bucketId/heartbeat?pulsetime=$script:PulseSeconds" -Payload $payload -Kind 'endpoint_signal' + Flush-TransportQueue -MaxItems 50 } function Send-DlpIncidentHeartbeat { @@ -126,7 +329,8 @@ function Send-DlpIncidentHeartbeat { } + $Data + $captureData } | ConvertTo-Json -Depth 7 -Compress - Invoke-AwJsonPost -Uri "$($script:ApiBase)/buckets/$bucketId/heartbeat?pulsetime=$script:PulseSeconds" -Json $payload + Add-TransportQueueItem -Uri "$($script:ApiBase)/buckets/$bucketId/heartbeat?pulsetime=$script:PulseSeconds" -Payload $payload -Kind 'dlp_incident' + Flush-TransportQueue -MaxItems 100 } function Get-FileSha256Hex { @@ -321,6 +525,53 @@ function Get-StringHash { } } +function Get-ClipboardTextSafe { + [OutputType([string])] + param() + + try { + $v = Get-Clipboard -Raw -ErrorAction Stop + if ($null -ne $v) { return [string]$v } + } + catch { + Write-EndpointLog ("clipboard direct read failed: {0}" -f $_.Exception.Message) + } + + # Clipboard is not reliably accessible from Session 0 (SYSTEM). Avoid noisy thread hacks there. + if ($script:SessionId -eq 0) { + return $null + } + + # Fallback: read clipboard in a dedicated STA thread for RDP/user-session edge cases. + try { + Add-Type -AssemblyName System.Windows.Forms -ErrorAction SilentlyContinue | Out-Null + $result = [string]::Empty + $script:__aw_clip = $null + $threadStart = [System.Threading.ThreadStart]{ + try { + $script:__aw_clip = [System.Windows.Forms.Clipboard]::GetText() + } + catch { + $script:__aw_clip = $null + } + } + $thread = New-Object System.Threading.Thread($threadStart) + $thread.SetApartmentState([System.Threading.ApartmentState]::STA) + $thread.Start() + $thread.Join(3000) | Out-Null + if ($thread.IsAlive) { + try { $thread.Abort() } catch {} + } + $result = [string]$script:__aw_clip + Remove-Variable -Name __aw_clip -Scope Script -ErrorAction SilentlyContinue + return $result + } + catch { + Write-EndpointLog ("clipboard STA read failed: {0}" -f $_.Exception.Message) + return $null + } +} + function Load-DlpPolicy { param([string]$Path) @@ -336,8 +587,17 @@ function Load-DlpPolicy { usb = @() print = @() } + contentAnalysis = [ordered]@{ + dictionaryPack = $null + regexPack = $null + ocrEnabled = $false + } } + $script:PolicySource = 'defaults' + $script:PolicyVersion = $null + $script:PolicyChecksum = $null + if (-not $Path -or -not (Test-Path -LiteralPath $Path)) { Write-EndpointLog ("policy not found, using defaults: {0}" -f $Path) return @@ -353,16 +613,240 @@ function Load-DlpPolicy { } if ($raw.endpoint) { - if ($raw.endpoint.clipboard) { $script:Policy.endpoint.clipboard = @($raw.endpoint.clipboard) } - if ($raw.endpoint.usb) { $script:Policy.endpoint.usb = @($raw.endpoint.usb) } - if ($raw.endpoint.print) { $script:Policy.endpoint.print = @($raw.endpoint.print) } + $props = @() + try { $props = @($raw.endpoint.PSObject.Properties.Name) } catch { $props = @() } + if ($props -contains 'clipboard' -and $raw.endpoint.clipboard) { $script:Policy.endpoint.clipboard = @($raw.endpoint.clipboard) } + if ($props -contains 'usb' -and $raw.endpoint.usb) { $script:Policy.endpoint.usb = @($raw.endpoint.usb) } + if ($props -contains 'print' -and $raw.endpoint.print) { $script:Policy.endpoint.print = @($raw.endpoint.print) } } + + if ($raw.contentAnalysis) { + if ($raw.contentAnalysis.PSObject.Properties.Name -contains 'dictionaryPack' -and $raw.contentAnalysis.dictionaryPack) { + $script:Policy.contentAnalysis.dictionaryPack = [string]$raw.contentAnalysis.dictionaryPack + } + if ($raw.contentAnalysis.PSObject.Properties.Name -contains 'regexPack' -and $raw.contentAnalysis.regexPack) { + $script:Policy.contentAnalysis.regexPack = [string]$raw.contentAnalysis.regexPack + } + if ($raw.contentAnalysis.PSObject.Properties.Name -contains 'ocrEnabled') { + $script:Policy.contentAnalysis.ocrEnabled = [bool]$raw.contentAnalysis.ocrEnabled + } + } + $script:PolicySource = 'local' } catch { Write-EndpointLog ("policy parse failed: {0}" -f $_.Exception.Message) } } +function Test-ValidInn { + param([string]$Value) + $digits = ($Value -replace '\D', '') + if ($digits.Length -eq 10) { + $coef = @(2, 4, 10, 3, 5, 9, 4, 6, 8) + $sum = 0 + for ($i = 0; $i -lt 9; $i++) { $sum += ([int][string]$digits[$i]) * $coef[$i] } + $chk = ($sum % 11) % 10 + return $chk -eq ([int][string]$digits[9]) + } + if ($digits.Length -eq 12) { + $c11 = @(7, 2, 4, 10, 3, 5, 9, 4, 6, 8) + $c12 = @(3, 7, 2, 4, 10, 3, 5, 9, 4, 6, 8) + $sum11 = 0 + for ($i = 0; $i -lt 10; $i++) { $sum11 += ([int][string]$digits[$i]) * $c11[$i] } + $sum12 = 0 + for ($i = 0; $i -lt 11; $i++) { $sum12 += ([int][string]$digits[$i]) * $c12[$i] } + return ((($sum11 % 11) % 10) -eq ([int][string]$digits[10])) -and ((($sum12 % 11) % 10) -eq ([int][string]$digits[11])) + } + return $false +} + +function Test-ValidSnils { + param([string]$Value) + $digits = ($Value -replace '\D', '') + if ($digits.Length -ne 11) { return $false } + $num = $digits.Substring(0, 9) + $checksum = [int]$digits.Substring(9, 2) + $sum = 0 + for ($i = 0; $i -lt 9; $i++) { $sum += ([int][string]$num[$i]) * (9 - $i) } + if ($sum -lt 100) { $expected = $sum } + elseif ($sum -eq 100 -or $sum -eq 101) { $expected = 0 } + else { + $expected = $sum % 101 + if ($expected -eq 100) { $expected = 0 } + } + return $checksum -eq $expected +} + +function Test-ValidPassport { + param([string]$Value) + $digits = ($Value -replace '\D', '') + if ($digits.Length -ne 10) { return $false } + if ($digits -eq '0000000000') { return $false } + return ($digits.ToCharArray() | Select-Object -Unique).Count -gt 1 +} + +function Get-AdvancedContentMatches { + param( + [string]$Text, + [string]$DictionaryPack, + [string]$RegexPack + ) + + $result = @{ + dictionaryMatches = @() + regexMatches = @() + } + if ([string]::IsNullOrWhiteSpace($Text)) { return $result } + + if ($DictionaryPack -eq '152-fz-pdn') { + $m = [regex]::Matches($Text, '\b\d{10}\b|\b\d{12}\b') + foreach ($item in $m) { + if (Test-ValidInn -Value $item.Value) { + $result.dictionaryMatches += @{ name = 'inn'; value = $item.Value; severity = 'high' } + } + } + $m = [regex]::Matches($Text, '\b\d{3}-\d{3}-\d{3}\s?\d{2}\b') + foreach ($item in $m) { + if (Test-ValidSnils -Value $item.Value) { + $result.dictionaryMatches += @{ name = 'snils'; value = $item.Value; severity = 'high' } + } + } + $m = [regex]::Matches($Text, '\b\d{4}\s?\d{6}\b') + foreach ($item in $m) { + if (Test-ValidPassport -Value $item.Value) { + $result.dictionaryMatches += @{ name = 'passport'; value = $item.Value; severity = 'high' } + } + } + } + + $regexRules = @() + switch ($RegexPack) { + 'financial' { + $regexRules = @( + @{ id = 'card-pan'; regex = '\b(?:\d[ -]*?){13,19}\b'; severity = 'high' }, + @{ id = 'iban'; regex = '\b[A-Z]{2}\d{2}[A-Z0-9]{11,30}\b'; severity = 'medium' } + ) + } + 'contacts' { + $regexRules = @( + @{ id = 'email'; regex = '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}'; severity = 'low' }, + @{ id = 'phone-ru'; regex = '(?:\+7|8)\s*\(?\d{3}\)?\s*\d{3}[- ]?\d{2}[- ]?\d{2}'; severity = 'low' } + ) + } + 'secrets' { + $regexRules = @( + @{ id = 'aws-access-key'; regex = 'AKIA[0-9A-Z]{16}'; severity = 'high' }, + @{ id = 'generic-password'; regex = '(?i)(password|пароль)\s*[:=]\s*\S{6,}'; severity = 'medium' } + ) + } + } + foreach ($rule in $regexRules) { + $m = [regex]::Matches($Text, [string]$rule.regex) + foreach ($item in $m) { + $result.regexMatches += @{ name = [string]$rule.id; value = $item.Value; severity = [string]$rule.severity } + } + } + + return $result +} + +function Apply-PolicyFromBundle { + param( + [Parameter(Mandatory = $true)]$Bundle, + [Parameter(Mandatory = $true)][string]$Source + ) + + if (-not $Bundle.policy) { + throw 'Policy bundle has no policy payload.' + } + + $tempPath = [System.IO.Path]::GetTempFileName() + try { + $Bundle.policy | ConvertTo-Json -Depth 20 | Set-Content -LiteralPath $tempPath -Encoding UTF8 + Load-DlpPolicy -Path $tempPath + $script:PolicySource = $Source + $script:PolicyVersion = if ($Bundle.PSObject.Properties.Name -contains 'version') { [string]$Bundle.version } else { $null } + $script:PolicyChecksum = if ($Bundle.PSObject.Properties.Name -contains 'checksum') { [string]$Bundle.checksum } else { $null } + } + finally { + Remove-Item -LiteralPath $tempPath -Force -ErrorAction SilentlyContinue + } +} + +function Refresh-DlpPolicyFromServer { + if (-not $script:PolicyEngineEnabled) { + return $false + } + if (-not $script:PolicyClientAvailable) { + Write-EndpointLog 'policy client module unavailable, cannot use server mode' + return $false + } + + try { + $bundle = Get-RemoteDlpPolicyBundle -ApiBase $script:PolicyApiBase -TimeoutSec 10 + Save-CachedDlpPolicyBundle -Bundle $bundle -CachePath $script:PolicyCachePath + Apply-PolicyFromBundle -Bundle $bundle -Source 'server' + $script:LastPolicyRefreshAt = (Get-Date).ToUniversalTime() + Write-EndpointLog ("policy refreshed from server version={0} checksum={1}" -f $script:PolicyVersion, $script:PolicyChecksum) + return $true + } + catch { + Write-EndpointLog ("policy refresh failed: {0}" -f $_.Exception.Message) + return $false + } +} + +function Sync-DlpPolicyDesiredState { + if (-not $script:PolicyEngineEnabled -or -not $script:PolicyClientAvailable) { + return $false + } + if (-not $script:PolicyAgentId) { + return $false + } + + try { + [void](Send-DlpPolicyAgentHeartbeat -ApiBase $script:PolicyApiBase -AgentId $script:PolicyAgentId -Hostname $script:Hostname -Version $script:PolicyVersion -Checksum $script:PolicyChecksum -TimeoutSec 10) + $desired = Get-RemoteDlpPolicyDesired -ApiBase $script:PolicyApiBase -AgentId $script:PolicyAgentId -TimeoutSec 10 + if ($desired -and $desired.refreshNow -eq $true) { + Write-EndpointLog ("policy desired refresh requested: reason={0}" -f $desired.reason) + return (Refresh-DlpPolicyFromServer) + } + return $true + } + catch { + Write-EndpointLog ("policy desired sync failed: {0}" -f $_.Exception.Message) + return $false + } +} + +function Initialize-DlpPolicy { + if ($script:PolicyMode -eq 'server') { + if (Refresh-DlpPolicyFromServer) { + return + } + + if ($script:PolicyClientAvailable) { + $cached = Read-CachedDlpPolicyBundle -CachePath $script:PolicyCachePath + if ($cached) { + try { + Apply-PolicyFromBundle -Bundle $cached -Source 'cache' + Write-EndpointLog ("policy loaded from cache version={0} checksum={1}" -f $script:PolicyVersion, $script:PolicyChecksum) + return + } + catch { + Write-EndpointLog ("cached policy load failed: {0}" -f $_.Exception.Message) + } + } + } + + Load-DlpPolicy -Path $script:LocalPolicyPath + $script:PolicySource = 'local-fallback' + return + } + + Load-DlpPolicy -Path $script:LocalPolicyPath +} + function Should-EmitByCooldown { param( [string]$Fingerprint, @@ -394,6 +878,9 @@ function Evaluate-ClipboardRules { if (-not $ruleId) { continue } $minLength = if ($rule.minLength) { [int]$rule.minLength } else { 0 } $regexPatterns = if ($rule.regexPatterns) { @($rule.regexPatterns) } else { @() } + $dictionaryPack = if ($rule.dictionaryPack) { [string]$rule.dictionaryPack } elseif ($script:Policy.contentAnalysis.dictionaryPack) { [string]$script:Policy.contentAnalysis.dictionaryPack } else { $null } + $regexPack = if ($rule.regexPack) { [string]$rule.regexPack } elseif ($script:Policy.contentAnalysis.regexPack) { [string]$script:Policy.contentAnalysis.regexPack } else { $null } + $ocrEnabled = if ($rule.PSObject.Properties.Name -contains 'ocrEnabled') { [bool]$rule.ocrEnabled } else { [bool]$script:Policy.contentAnalysis.ocrEnabled } if ($ClipboardText.Length -lt $minLength) { continue } $matched = $false @@ -403,6 +890,9 @@ function Evaluate-ClipboardRules { break } } + $advanced = Get-AdvancedContentMatches -Text $ClipboardText -DictionaryPack $dictionaryPack -RegexPack $regexPack + $advancedMatched = (@($advanced.dictionaryMatches).Count -gt 0) -or (@($advanced.regexMatches).Count -gt 0) + if ($advancedMatched) { $matched = $true } if (-not $matched) { continue } @@ -424,6 +914,11 @@ function Evaluate-ClipboardRules { clipboardHash = $ClipboardHash clipboardLength = $ClipboardText.Length enforced = $enforced + dictionaryPack = $dictionaryPack + regexPack = $regexPack + dictionaryMatches = @($advanced.dictionaryMatches) + regexMatches = @($advanced.regexMatches) + ocrRequested = $ocrEnabled } Write-EndpointLog ("incident clipboard rule={0} action={1} severity={2} enforced={3}" -f $ruleId, $action, $severity, $enforced) } @@ -484,6 +979,12 @@ function Evaluate-PrintRules { if ($rule.documentRegex) { $match = $match -and ($DocumentName -match [string]$rule.documentRegex) } + $dictionaryPack = if ($rule.dictionaryPack) { [string]$rule.dictionaryPack } elseif ($script:Policy.contentAnalysis.dictionaryPack) { [string]$script:Policy.contentAnalysis.dictionaryPack } else { $null } + $regexPack = if ($rule.regexPack) { [string]$rule.regexPack } elseif ($script:Policy.contentAnalysis.regexPack) { [string]$script:Policy.contentAnalysis.regexPack } else { $null } + $ocrEnabled = if ($rule.PSObject.Properties.Name -contains 'ocrEnabled') { [bool]$rule.ocrEnabled } else { [bool]$script:Policy.contentAnalysis.ocrEnabled } + $advanced = Get-AdvancedContentMatches -Text $DocumentName -DictionaryPack $dictionaryPack -RegexPack $regexPack + $advancedMatched = (@($advanced.dictionaryMatches).Count -gt 0) -or (@($advanced.regexMatches).Count -gt 0) + if ($advancedMatched) { $match = $true } if (-not $match) { continue } $cooldown = if ($rule.cooldownSeconds) { [int]$rule.cooldownSeconds } else { [int]$script:Policy.defaults.cooldownSeconds } @@ -505,6 +1006,11 @@ function Evaluate-PrintRules { documentName = $DocumentName owner = $Owner enforced = $enforced + dictionaryPack = $dictionaryPack + regexPack = $regexPack + dictionaryMatches = @($advanced.dictionaryMatches) + regexMatches = @($advanced.regexMatches) + ocrRequested = $ocrEnabled } Write-EndpointLog ("incident print rule={0} action={1} severity={2} printer={3} enforced={4}" -f $ruleId, $action, $severity, $PrinterName, $enforced) } @@ -734,19 +1240,29 @@ $resolvedServerHost = if ($ServerHost) { $ServerHost } elseif ($deploymentConfig $resolvedServerPort = if ($PSBoundParameters.ContainsKey('ServerPort')) { $ServerPort } elseif ($deploymentConfig) { [int]$deploymentConfig.server.port } else { 5600 } $resolvedServerScheme = if ($ServerScheme) { $ServerScheme } elseif ($deploymentConfig) { [string]$deploymentConfig.server.scheme } else { 'http' } $resolvedPolicyPath = if ($PolicyPath) { $PolicyPath } elseif ($deploymentConfig -and $deploymentConfig.paths.PSObject.Properties.Name -contains 'policyPath') { [string]$deploymentConfig.paths.policyPath } else { 'C:\ProgramData\AWatch-rus\dlp-policy.json' } +$resolvedStateRoot = if ($deploymentConfig -and $deploymentConfig.paths.PSObject.Properties.Name -contains 'stateRoot') { [string]$deploymentConfig.paths.stateRoot } else { Split-Path -Path $resolvedPolicyPath -Parent } $resolvedPollSeconds = if ($PSBoundParameters.ContainsKey('PollSeconds')) { $PollSeconds } elseif ($deploymentConfig) { [int]$deploymentConfig.collector.pollSeconds } else { 5 } $resolvedLogsRoot = if ($deploymentConfig) { [string]$deploymentConfig.paths.logsRoot } else { 'C:\ProgramData\AWatch-rus\logs' } $resolvedLogPath = if ($LogPath) { $LogPath } else { Join-Path $resolvedLogsRoot ("endpoint-signals-{0}.log" -f $env:USERNAME) } $resolvedLocalAgentLogsEnabled = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'logging' -and $deploymentConfig.logging.PSObject.Properties.Name -contains 'localAgentLogsEnabled') { [bool]$deploymentConfig.logging.localAgentLogsEnabled } else { $true } $resolvedIncidentArtifactsRoot = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $deploymentConfig.incidentCapture.PSObject.Properties.Name -contains 'artifactsRoot') { [string]$deploymentConfig.incidentCapture.artifactsRoot } else { Join-Path $env:LOCALAPPDATA 'AWatch-rus\\incident-artifacts' } $resolvedIncidentScreenshotEnabled = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $deploymentConfig.incidentCapture.PSObject.Properties.Name -contains 'screenshotEnabled') { [bool]$deploymentConfig.incidentCapture.screenshotEnabled } else { $true } +$resolvedHostname = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'awHostname' -and -not [string]::IsNullOrWhiteSpace([string]$deploymentConfig.awHostname)) { [string]$deploymentConfig.awHostname } else { [string]$env:COMPUTERNAME } +$resolvedPolicyMode = if ($PolicyMode) { [string]$PolicyMode } elseif ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'policyEngine' -and $deploymentConfig.policyEngine.PSObject.Properties.Name -contains 'mode') { [string]$deploymentConfig.policyEngine.mode } else { 'local' } +$resolvedPolicyEngineEnabled = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'policyEngine' -and $deploymentConfig.policyEngine.PSObject.Properties.Name -contains 'enabled') { [bool]$deploymentConfig.policyEngine.enabled } else { $false } +$resolvedPolicyEngineHost = if ($PolicyEngineHost) { [string]$PolicyEngineHost } elseif ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'policyEngine' -and $deploymentConfig.policyEngine.PSObject.Properties.Name -contains 'host') { [string]$deploymentConfig.policyEngine.host } else { $resolvedServerHost } +$resolvedPolicyEnginePort = if ($PSBoundParameters.ContainsKey('PolicyEnginePort')) { $PolicyEnginePort } elseif ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'policyEngine' -and $deploymentConfig.policyEngine.PSObject.Properties.Name -contains 'port') { [int]$deploymentConfig.policyEngine.port } else { $resolvedServerPort } +$resolvedPolicyEngineScheme = if ($PolicyEngineScheme) { [string]$PolicyEngineScheme } elseif ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'policyEngine' -and $deploymentConfig.policyEngine.PSObject.Properties.Name -contains 'scheme') { [string]$deploymentConfig.policyEngine.scheme } else { $resolvedServerScheme } +$resolvedPolicyRefreshSeconds = if ($PSBoundParameters.ContainsKey('PolicyRefreshSeconds')) { $PolicyRefreshSeconds } elseif ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'policyEngine' -and $deploymentConfig.policyEngine.PSObject.Properties.Name -contains 'refreshSeconds') { [int]$deploymentConfig.policyEngine.refreshSeconds } else { 300 } +$resolvedPolicyCachePath = if ($PolicyCachePath) { [string]$PolicyCachePath } elseif ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'policyEngine' -and $deploymentConfig.policyEngine.PSObject.Properties.Name -contains 'cachePath') { [string]$deploymentConfig.policyEngine.cachePath } else { Join-Path $resolvedStateRoot 'dlp-policy-cache.json' } if ($resolvedLocalAgentLogsEnabled -and -not (Test-Path -LiteralPath $resolvedLogsRoot)) { New-Item -Path $resolvedLogsRoot -ItemType Directory -Force | Out-Null } $script:ApiBase = '{0}://{1}:{2}/api/0' -f $resolvedServerScheme, $resolvedServerHost, $resolvedServerPort -$script:Hostname = $env:COMPUTERNAME +$script:PolicyApiBase = '{0}://{1}:{2}/api/0' -f $resolvedPolicyEngineScheme, $resolvedPolicyEngineHost, $resolvedPolicyEnginePort +$script:Hostname = $resolvedHostname $script:SessionId = (Get-Process -Id $PID).SessionId $script:KnownBuckets = @{} $script:Cooldown = @{} @@ -762,17 +1278,59 @@ $script:LogPath = $resolvedLogPath $script:IncidentArtifactsRoot = $resolvedIncidentArtifactsRoot $script:IncidentScreenshotEnabled = $resolvedIncidentScreenshotEnabled $script:ScreenshotTypesLoaded = $false +$script:PolicyMode = $resolvedPolicyMode +$script:PolicyEngineEnabled = $resolvedPolicyEngineEnabled +$script:PolicyRefreshSeconds = [Math]::Max($resolvedPolicyRefreshSeconds, 60) +$script:PolicyCachePath = $resolvedPolicyCachePath +$script:LocalPolicyPath = $resolvedPolicyPath +$script:LastPolicyRefreshAt = [datetime]::MinValue +$script:PolicyAgentId = $resolvedHostname +$script:TransportBackoffSeconds = 1 +# Integration test flag (backward compatible - defaults to false) +$script:IntegrationTestEnabled = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'integrationTestEnabled') { [bool]$deploymentConfig.integrationTestEnabled } else { $false } -Load-DlpPolicy -Path $resolvedPolicyPath +# Integration metadata tracking (backward compatible) +$script:TotalEventsProcessed = 0 +$script:LastEventTime = $null + +Initialize-TransportQueue -StateRoot $resolvedStateRoot +Initialize-DlpPolicy Write-EndpointLog ("endpoint collector started against {0}" -f $script:ApiBase) while ($true) { try { + try { + Flush-TransportQueue -MaxItems 200 + $script:TransportBackoffSeconds = 1 + } + catch { + $script:TransportBackoffSeconds = [Math]::Min($script:TransportBackoffSeconds * 2, 60) + Write-EndpointLog ("transport flush failed, backoff={0}s err={1}" -f $script:TransportBackoffSeconds, $_.Exception.Message) + } + + if ($script:PolicyMode -eq 'server') { + $policyAge = ((Get-Date).ToUniversalTime() - $script:LastPolicyRefreshAt).TotalSeconds + if ($policyAge -ge $script:PolicyRefreshSeconds) { + [void](Refresh-DlpPolicyFromServer) + } + else { + [void](Sync-DlpPolicyDesiredState) + } + } + $nowUtc = (Get-Date).ToUniversalTime() if (($nowUtc - $script:LastSelfTestAt).TotalSeconds -ge $script:SelfTestIntervalSeconds) { Send-EndpointSignalHeartbeat -SignalType 'self_test' -Data @{ collector = 'dlp-endpoint-signals' policyEnabled = [bool]$script:Policy.defaults.enabled + policyMode = $script:PolicyMode + policySource = $script:PolicySource + policyVersion = $script:PolicyVersion + policyChecksum = $script:PolicyChecksum + queueDepth = [int]$script:TransportMetrics.queueDepth + eventsEnqueued = [int]$script:TransportMetrics.eventsEnqueued + eventsFlushed = [int]$script:TransportMetrics.eventsFlushed + sendFailures = [int]$script:TransportMetrics.sendFailures } $script:LastSelfTestAt = $nowUtc } @@ -783,7 +1341,7 @@ while ($true) { } try { - $clipboardText = Get-Clipboard -Raw -ErrorAction SilentlyContinue + $clipboardText = Get-ClipboardTextSafe if ($clipboardText) { $clipboardHash = Get-StringHash -Value $clipboardText if ($clipboardHash -and $clipboardHash -ne $script:LastClipboardHash) { @@ -792,6 +1350,8 @@ while ($true) { clipboardHash = $clipboardHash clipboardLength = $clipboardText.Length } + $script:TotalEventsProcessed++ + $script:LastEventTime = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ') Evaluate-ClipboardRules -ClipboardText $clipboardText -ClipboardHash $clipboardHash } } @@ -813,6 +1373,8 @@ while ($true) { driveLetter = $deviceId volumeName = $volumeName } + $script:TotalEventsProcessed++ + $script:LastEventTime = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ') Evaluate-UsbRules -DriveLetter $deviceId -VolumeName $volumeName } } @@ -852,6 +1414,8 @@ while ($true) { documentNameOriginal = $documentNameOriginal owner = $owner } + $script:TotalEventsProcessed++ + $script:LastEventTime = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ') Evaluate-PrintRules -PrinterName $printerName -DocumentName $documentName -Owner $owner } @@ -899,6 +1463,8 @@ while ($true) { eventRecordId = $recordId eventSource = 'printservice-307' } + $script:TotalEventsProcessed++ + $script:LastEventTime = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ') Evaluate-PrintRules -PrinterName $printerName -DocumentName (if ($resolvedDocument) { $resolvedDocument } else { $documentName }) -Owner $owner } @@ -917,5 +1483,35 @@ while ($true) { Write-EndpointLog ("collector error: {0}" -f $_.Exception.Message) } - Start-Sleep -Seconds $resolvedPollSeconds + # Integration metadata self-test (backward compatible) + if ($script:IntegrationTestEnabled -and (Get-Date).Minute -eq 0) { + try { + $testMetadata = @{ + timestamp = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ') + collector = 'dlp-endpoint-signals' + version = '1.0.0' + hostname = $env:COMPUTERNAME + username = $env:USERNAME + status = 'healthy' + checks = @{ + eventsProcessed = $script:TotalEventsProcessed + lastEventTime = $script:LastEventTime + iocRulesLoaded = if ($script:IocRules) { @($script:IocRules).Count } else { 0 } + policyRulesLoaded = if ($script:Policy -and $script:Policy.endpoint) { (@($script:Policy.endpoint.clipboard).Count + @($script:Policy.endpoint.usb).Count + @($script:Policy.endpoint.print).Count) } else { 0 } + } + } + Send-EndpointSignalHeartbeat -SignalType 'integration_test' -Data $testMetadata + Write-EndpointLog "Integration metadata test sent" + } + catch { + Write-EndpointLog "Integration test failed: $($_.Exception.Message)" + } + } + + if ($script:TransportBackoffSeconds -gt $resolvedPollSeconds) { + Start-Sleep -Seconds $script:TransportBackoffSeconds + } + else { + Start-Sleep -Seconds $resolvedPollSeconds + } } diff --git a/install-kit-awindows-20260427-211240/windows/dlp-policy.example.json b/install-kit-awindows-20260427-211240/windows/dlp-policy.example.json index 5ed8ef6..7c8c40f 100644 --- a/install-kit-awindows-20260427-211240/windows/dlp-policy.example.json +++ b/install-kit-awindows-20260427-211240/windows/dlp-policy.example.json @@ -95,5 +95,16 @@ "documentRegex": "(?i)(salary|зарплат|passport|паспорт|договор|contract)" } ] + }, + "contentAnalysis": { + "dictionaryPack": "152-fz-pdn", + "regexPack": "secrets", + "ocrEnabled": true + }, + "ioc": { + "enabled": true, + "source": "http://10.10.10.13:5610/dlp-ioc/ioc_blacklist.json", + "format": "hayabusa_sigma_v1", + "refreshMinutes": 360 } } diff --git a/install-kit-awindows-20260427-211240/windows/email-outbound-collector.ps1 b/install-kit-awindows-20260427-211240/windows/email-outbound-collector.ps1 index 7a1600f..96c6461 100644 --- a/install-kit-awindows-20260427-211240/windows/email-outbound-collector.ps1 +++ b/install-kit-awindows-20260427-211240/windows/email-outbound-collector.ps1 @@ -26,7 +26,7 @@ param( [string]$LogPath, [int]$PollSeconds, [ValidateSet('outlook', 'smtp', 'both')] - [string]$Mode = 'both' + [string]$Mode = 'smtp' ) Set-StrictMode -Version Latest @@ -322,8 +322,23 @@ function Invoke-EmailEnforcement { # --------------------------------------------------------------------------- function Initialize-OutlookCom { + if ($script:OutlookDisabled) { + return $false + } + + if (-not (Test-OutlookProfileConfigured)) { + Write-CollectorLog "Outlook profile not configured for current user, Outlook mode disabled" + $script:OutlookDisabled = $true + return $false + } + + if (-not (Get-Process -Name OUTLOOK -ErrorAction SilentlyContinue | Select-Object -First 1)) { + Write-CollectorLog "Outlook process not running, skipping COM initialization" + return $false + } + try { - $script:OutlookApp = New-Object -ComObject Outlook.Application + $script:OutlookApp = [Runtime.InteropServices.Marshal]::GetActiveObject('Outlook.Application') $script:OutlookNamespace = $script:OutlookApp.GetNamespace('MAPI') $script:SentFolder = $script:OutlookNamespace.GetDefaultFolder(5) # olFolderSentMail Write-CollectorLog "Outlook COM initialized, Sent Items folder opened" @@ -335,6 +350,32 @@ function Initialize-OutlookCom { } } +function Test-OutlookProfileConfigured { + [OutputType([bool])] + $officeRoots = @( + 'HKCU:\Software\Microsoft\Office', + 'HKCU:\Software\WOW6432Node\Microsoft\Office' + ) + + foreach ($root in $officeRoots) { + if (-not (Test-Path -LiteralPath $root)) { continue } + $versions = Get-ChildItem -LiteralPath $root -ErrorAction SilentlyContinue | + Where-Object { $_.PSChildName -match '^\d+\.\d+$' } | + Sort-Object { [version]$_.PSChildName } -Descending + foreach ($ver in $versions) { + $profilesPath = Join-Path $ver.PSPath 'Outlook\Profiles' + if (Test-Path -LiteralPath $profilesPath) { + $profiles = Get-ChildItem -LiteralPath $profilesPath -ErrorAction SilentlyContinue + if ($profiles -and $profiles.Count -gt 0) { + return $true + } + } + } + } + + return $false +} + function Get-OutlookSentItems { param([datetime]$Since) @@ -521,6 +562,7 @@ $script:OutlookApp = $null $script:OutlookNamespace = $null $script:SentFolder = $null $script:OutlookLastPoll = (Get-Date).AddMinutes(-5) +$script:OutlookDisabled = $false Load-EmailPolicy -Path $resolvedPolicyPath Write-CollectorLog ("email collector started mode={0} against {1}" -f $Mode, $script:ApiBase) diff --git a/install-kit-awindows-20260427-211240/windows/hardening-recovery.ps1 b/install-kit-awindows-20260427-211240/windows/hardening-recovery.ps1 index d382f41..dd9852e 100755 --- a/install-kit-awindows-20260427-211240/windows/hardening-recovery.ps1 +++ b/install-kit-awindows-20260427-211240/windows/hardening-recovery.ps1 @@ -20,9 +20,22 @@ param( [bool]$IncidentCaptureEnabled, [bool]$IncidentScreenshotEnabled, [string]$IncidentArtifactsRoot, + [string]$EvtxExportRoot, + [int]$EvtxRetentionDays, + [string[]]$EvtxChannels, [bool]$LogonMarkerEnabled, + [string]$AwHostname, [string]$CustomRulesPath, [string]$CustomPolicyPath, + [ValidateSet('local', 'server')] + [string]$PolicyMode, + [bool]$PolicyEngineEnabled, + [string]$PolicyEngineHost, + [int]$PolicyEnginePort, + [ValidateSet('http', 'https')] + [string]$PolicyEngineScheme, + [int]$PolicyRefreshSeconds, + [string]$PolicyCachePath, [switch]$RepairPackage, [string]$Version, [string]$PackageUrl, @@ -56,8 +69,10 @@ $effectiveCollector = Join-Path $effectiveStateRoot 'browser-domains-native-coll $effectiveEndpointCollector = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'endpointCollectorScript') { [string]$existingConfig.paths.endpointCollectorScript } else { Join-Path $effectiveStateRoot 'dlp-endpoint-signals-collector.ps1' } $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' } $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' } $effectiveServerHost = if ($ServerHost) { $ServerHost } elseif ($existingConfig) { [string]$existingConfig.server.host } else { $null } $effectiveServerPort = if ($PSBoundParameters.ContainsKey('ServerPort')) { $ServerPort } elseif ($existingConfig) { [int]$existingConfig.server.port } else { 5600 } @@ -72,8 +87,19 @@ $effectiveLocalAgentLogsEnabled = if ($PSBoundParameters.ContainsKey('LocalAgent $effectiveIncidentCaptureEnabled = if ($PSBoundParameters.ContainsKey('IncidentCaptureEnabled')) { [bool]$IncidentCaptureEnabled } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $existingConfig.incidentCapture.PSObject.Properties.Name -contains 'enabled') { [bool]$existingConfig.incidentCapture.enabled } else { $true } $effectiveIncidentScreenshotEnabled = if ($PSBoundParameters.ContainsKey('IncidentScreenshotEnabled')) { [bool]$IncidentScreenshotEnabled } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $existingConfig.incidentCapture.PSObject.Properties.Name -contains 'screenshotEnabled') { [bool]$existingConfig.incidentCapture.screenshotEnabled } else { $true } $effectiveIncidentArtifactsRoot = if ($PSBoundParameters.ContainsKey('IncidentArtifactsRoot') -and $IncidentArtifactsRoot) { $IncidentArtifactsRoot } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $existingConfig.incidentCapture.PSObject.Properties.Name -contains 'artifactsRoot') { [string]$existingConfig.incidentCapture.artifactsRoot } else { Join-Path $effectiveStateRoot 'incident-artifacts' } +$effectiveEvtxExportRoot = if ($PSBoundParameters.ContainsKey('EvtxExportRoot') -and $EvtxExportRoot) { $EvtxExportRoot } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'forensics' -and $existingConfig.forensics.PSObject.Properties.Name -contains 'evtxExportRoot') { [string]$existingConfig.forensics.evtxExportRoot } else { Join-Path $effectiveStateRoot 'forensics\evtx-exports' } +$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 } +$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' } +$effectivePolicyEngineEnabled = if ($PSBoundParameters.ContainsKey('PolicyEngineEnabled')) { [bool]$PolicyEngineEnabled } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'policyEngine' -and $existingConfig.policyEngine.PSObject.Properties.Name -contains 'enabled') { [bool]$existingConfig.policyEngine.enabled } else { $false } +$effectivePolicyEngineHost = if ($PSBoundParameters.ContainsKey('PolicyEngineHost') -and -not [string]::IsNullOrWhiteSpace($PolicyEngineHost)) { [string]$PolicyEngineHost } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'policyEngine' -and $existingConfig.policyEngine.PSObject.Properties.Name -contains 'host') { [string]$existingConfig.policyEngine.host } else { [string]$effectiveServerHost } +$effectivePolicyEnginePort = if ($PSBoundParameters.ContainsKey('PolicyEnginePort')) { [int]$PolicyEnginePort } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'policyEngine' -and $existingConfig.policyEngine.PSObject.Properties.Name -contains 'port') { [int]$existingConfig.policyEngine.port } else { 5601 } +$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' } $effectiveUsers = if ($Users -or $UserListPath) { Normalize-ActivityWatchUsers -Users $Users -UserListPath $UserListPath -Domain $Domain @@ -104,6 +130,7 @@ $assetResult = Copy-ActivityWatchCollectorAssets ` -EmailCollectorScriptSource (Join-Path $PSScriptRoot 'email-outbound-collector.ps1') ` -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') ` -ExampleRulesSource (Join-Path $PSScriptRoot 'web-category-rules.example.json') ` -ExamplePolicySource (Join-Path $PSScriptRoot 'dlp-policy.example.json') ` -StateRoot $effectiveStateRoot ` @@ -123,9 +150,11 @@ $config = New-ActivityWatchDeploymentConfig ` -LogsRoot $effectiveLogsRoot ` -CollectorScript $effectiveCollector ` -EndpointCollectorScript $effectiveEndpointCollector ` + -PolicyClientScript $effectivePolicyClientScript ` -EmailCollectorScript $assetResult.EmailCollectorScript ` -FileCollectorScript $effectiveFileCollector ` -SessionCollectorScript $effectiveSessionCollector ` + -EvtxExportScript $effectiveEvtxExportScript ` -RulesPath $effectiveRules ` -PolicyPath $effectivePolicy ` -PollSeconds $effectivePollSeconds ` @@ -138,7 +167,18 @@ $config = New-ActivityWatchDeploymentConfig ` -IncidentCaptureEnabled $effectiveIncidentCaptureEnabled ` -IncidentScreenshotEnabled $effectiveIncidentScreenshotEnabled ` -IncidentArtifactsRoot $effectiveIncidentArtifactsRoot ` + -EvtxExportRoot $effectiveEvtxExportRoot ` + -EvtxRetentionDays $effectiveEvtxRetentionDays ` + -EvtxChannels $effectiveEvtxChannels ` -LogonMarkerEnabled $effectiveLogonMarkerEnabled ` + -AwHostname $effectiveAwHostname ` + -PolicyMode $effectivePolicyMode ` + -PolicyEngineEnabled $effectivePolicyEngineEnabled ` + -PolicyEngineHost $effectivePolicyEngineHost ` + -PolicyEnginePort $effectivePolicyEnginePort ` + -PolicyEngineScheme $effectivePolicyEngineScheme ` + -PolicyRefreshSeconds $effectivePolicyRefreshSeconds ` + -PolicyCachePath $effectivePolicyCachePath ` -LaunchScriptPath $effectiveLaunchScript ` -RecoveryScriptPath $effectiveRecoveryScript ` -UserTasks $taskDefinitions ` diff --git a/install-kit-awindows-20260427-211240/windows/migrate-awatch-rus-paths.ps1 b/install-kit-awindows-20260427-211240/windows/migrate-awatch-rus-paths.ps1 index eb7cbe4..c588050 100644 --- a/install-kit-awindows-20260427-211240/windows/migrate-awatch-rus-paths.ps1 +++ b/install-kit-awindows-20260427-211240/windows/migrate-awatch-rus-paths.ps1 @@ -154,7 +154,36 @@ if ($PSCmdlet.ShouldProcess($env:COMPUTERNAME, 'Миграция ActivityWatch W @{ Source = $NewStateRoot; Name = 'new-state' } )) { if (Test-Path -LiteralPath $item.Source) { - Copy-Item -LiteralPath $item.Source -Destination (Join-Path $backupRoot $item.Name) -Recurse -Force + $backupDest = Join-Path $backupRoot $item.Name + New-ActivityWatchDirectory -Path $backupDest + + $excludeDirs = @() + if ($item.Source -eq $NewStateRoot) { + # Avoid infinite recursion: backupRoot is inside NewStateRoot by default. + $excludeDirs += $backupRoot + } + + $robocopyArgs = @( + $item.Source, + $backupDest, + '/E', + '/R:1', + '/W:1', + '/NFL', + '/NDL', + '/NJH', + '/NJS', + '/NP' + ) + if ($excludeDirs.Count -gt 0) { + $robocopyArgs += '/XD' + $robocopyArgs += $excludeDirs + } + + & robocopy @robocopyArgs | Out-Null + if ($LASTEXITCODE -ge 8) { + throw "Backup robocopy failed (exit=$LASTEXITCODE) for source '$($item.Source)' to '$backupDest'" + } } } diff --git a/install-kit-awindows-20260427-211240/windows/validate-deployment.ps1 b/install-kit-awindows-20260427-211240/windows/validate-deployment.ps1 index 52b35d2..1273a86 100644 --- a/install-kit-awindows-20260427-211240/windows/validate-deployment.ps1 +++ b/install-kit-awindows-20260427-211240/windows/validate-deployment.ps1 @@ -14,24 +14,327 @@ $installRoot = [string]$config.paths.installRoot $stateRoot = [string]$config.paths.stateRoot $collectorScript = [string]$config.paths.collectorScript $endpointCollectorScript = if ($config.paths.PSObject.Properties.Name -contains 'endpointCollectorScript') { [string]$config.paths.endpointCollectorScript } else { Join-Path $stateRoot 'dlp-endpoint-signals-collector.ps1' } +$fileCollectorScript = if ($config.paths.PSObject.Properties.Name -contains 'fileCollectorScript') { [string]$config.paths.fileCollectorScript } else { Join-Path $stateRoot 'file-operations-collector.ps1' } $sessionCollectorScript = if ($config.paths.PSObject.Properties.Name -contains 'sessionCollectorScript') { [string]$config.paths.sessionCollectorScript } else { Join-Path $stateRoot 'worktime-session-collector.ps1' } +$evtxExportScript = if ($config.paths.PSObject.Properties.Name -contains 'evtxExportScript') { [string]$config.paths.evtxExportScript } else { Join-Path $stateRoot 'export-evtx-for-hayabusa.ps1' } $rulesPath = [string]$config.paths.rulesPath $policyPath = if ($config.paths.PSObject.Properties.Name -contains 'policyPath') { [string]$config.paths.policyPath } else { Join-Path $stateRoot 'dlp-policy.json' } +$policyClientScript = if ($config.paths.PSObject.Properties.Name -contains 'policyClientScript') { [string]$config.paths.policyClientScript } else { Join-Path $stateRoot 'dlp-policy-client.ps1' } $launchScript = [string]$config.paths.launchScript $recoveryScript = [string]$config.paths.recoveryScript +$awHostname = if ($config.PSObject.Properties.Name -contains 'awHostname' -and -not [string]::IsNullOrWhiteSpace([string]$config.awHostname)) { [string]$config.awHostname } else { [string]$env:COMPUTERNAME } +$serverUrl = '{0}://{1}:{2}' -f [string]$config.server.scheme, [string]$config.server.host, [int]$config.server.port +$apiBase = "$serverUrl/api/0" +$pollSeconds = if ($config.PSObject.Properties.Name -contains 'collector' -and $config.collector.PSObject.Properties.Name -contains 'pollSeconds') { [int]$config.collector.pollSeconds } else { 5 } +$pulseSeconds = if ($config.PSObject.Properties.Name -contains 'collector' -and $config.collector.PSObject.Properties.Name -contains 'pulseSeconds') { [int]$config.collector.pulseSeconds } else { [Math]::Max($pollSeconds * 3, 30) } +$freshnessSeconds = [Math]::Max($pollSeconds * 3, 30) +$sessionFreshnessSeconds = [Math]::Max($pollSeconds * 4, 45) +$transportStaleSeconds = [Math]::Max($pollSeconds * 12, 180) +$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 } + +function Get-LoggedOnUsers { + $users = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase) + try { + $lines = & quser.exe 2>$null + foreach ($line in @($lines)) { + $normalized = [string]$line + if ([string]::IsNullOrWhiteSpace($normalized)) { continue } + $normalized = $normalized.TrimStart(' ', '>') + if ([string]::IsNullOrWhiteSpace($normalized)) { continue } + if ($normalized -match '^(USERNAME|ПОЛЬЗОВАТЕЛЬ)\s+') { continue } + $parts = $normalized -split '\s+' + if ($parts.Count -lt 1) { continue } + $user = [string]$parts[0] + if ([string]::IsNullOrWhiteSpace($user)) { continue } + [void]$users.Add($user) + [void]$users.Add(('{0}\{1}' -f $env:COMPUTERNAME, $user)) + if (-not [string]::IsNullOrWhiteSpace($env:USERDOMAIN)) { + [void]$users.Add(('{0}\{1}' -f $env:USERDOMAIN, $user)) + } + } + } + catch { + } + return @($users) +} + +function Test-UserHasSession { + param( + [string]$UserId, + [string[]]$LoggedOnUsers + ) + + if ([string]::IsNullOrWhiteSpace($UserId)) { return $false } + $candidateIds = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase) + [void]$candidateIds.Add($UserId) + $leafUser = $UserId + if ($leafUser -match '^[^\\]+\\(.+)$') { + $leafUser = $Matches[1] + [void]$candidateIds.Add($leafUser) + } + [void]$candidateIds.Add(('{0}\{1}' -f $env:COMPUTERNAME, $leafUser)) + if (-not [string]::IsNullOrWhiteSpace($env:USERDOMAIN)) { + [void]$candidateIds.Add(('{0}\{1}' -f $env:USERDOMAIN, $leafUser)) + } + foreach ($candidate in @($candidateIds)) { + if ($LoggedOnUsers -contains $candidate) { return $true } + } + return $false +} + +function Get-CollectorProcesses { + param( + [Parameter(Mandatory = $true)] + [string]$ScriptPath + ) + + return @( + Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | + Where-Object { + ($_.Name -ieq 'powershell.exe' -or $_.Name -ieq 'pwsh.exe') -and + $_.CommandLine -and + $_.CommandLine -match [Regex]::Escape($ScriptPath) + } | + Select-Object @{ Name = 'Name'; Expression = { $_.Name } }, @{ Name = 'Id'; Expression = { [int]$_.ProcessId } }, @{ Name = 'SessionId'; Expression = { [int]$_.SessionId } }, @{ Name = 'CommandLine'; Expression = { [string]$_.CommandLine } } + ) +} + +function Get-DuplicateProcessGroups { + param( + [object[]]$Processes, + [bool]$PerSession = $true + ) + + if (-not $Processes -or @($Processes).Count -eq 0) { return @() } + $groups = if ($PerSession) { + $Processes | Group-Object -Property Name, SessionId + } + else { + $Processes | Group-Object -Property Name + } + + return @( + $groups | + Where-Object { $_.Count -gt 1 } | + ForEach-Object { + [pscustomobject]@{ + name = [string]$_.Name + count = [int]$_.Count + members = @($_.Group | Select-Object Name, Id, SessionId, CommandLine) + } + } + ) +} + +function Convert-ToUtcDate { + param($Value) + + if ($null -eq $Value) { return $null } + try { + return ([DateTimeOffset]::Parse([string]$Value)).UtcDateTime + } + catch { + return $null + } +} + +function Get-BucketHealth { + param( + [Parameter(Mandatory = $true)] + [string]$BucketId, + [Parameter(Mandatory = $true)] + [int]$MaxAgeSeconds, + [bool]$Required = $true, + [bool]$RequireFreshEvent = $true + ) + + $events = @() + $queryOk = $false + $errorMessage = $null + try { + $response = Invoke-RestMethod -Method Get -Uri "$apiBase/buckets/$BucketId/events?limit=25" -TimeoutSec 15 -DisableKeepAlive -ErrorAction Stop + $events = @($response) + $queryOk = $true + } + catch { + $errorMessage = $_.Exception.Message + } + + $latestTimestampUtc = $null + $ageSeconds = $null + if ($events.Count -gt 0) { + $latestTimestampUtc = @( + $events | + ForEach-Object { Convert-ToUtcDate $_.timestamp } | + Where-Object { $null -ne $_ } | + Sort-Object -Descending + ) | Select-Object -First 1 + if ($null -ne $latestTimestampUtc) { + $ageSeconds = [int][Math]::Floor(((Get-Date).ToUniversalTime() - $latestTimestampUtc).TotalSeconds) + } + } + + $hasFreshEvent = ($null -ne $ageSeconds -and $ageSeconds -le $MaxAgeSeconds) + $hasAnyEvent = ($events.Count -gt 0) + $ok = if (-not $Required) { $true } elseif ($RequireFreshEvent) { $queryOk -and $hasFreshEvent } else { $queryOk -and $hasAnyEvent } + + return [pscustomobject]@{ + bucketId = $BucketId + required = [bool]$Required + requireFreshEvent = [bool]$RequireFreshEvent + maxAgeSeconds = [int]$MaxAgeSeconds + queryOk = [bool]$queryOk + latestTimestampUtc = if ($null -ne $latestTimestampUtc) { $latestTimestampUtc.ToString('o') } else { $null } + ageSeconds = if ($null -ne $ageSeconds) { [int]$ageSeconds } else { $null } + count = [int]$events.Count + ok = [bool]$ok + error = $errorMessage + } +} + +function Get-TransportQueueHealth { + param( + [Parameter(Mandatory = $true)] + [string]$Name, + [Parameter(Mandatory = $true)] + [string]$QueuePath, + [Parameter(Mandatory = $true)] + [string]$LockPath, + [Parameter(Mandatory = $true)] + [int]$StaleAfterSeconds, + [Parameter(Mandatory = $true)] + [int]$MaxDepth, + [int]$ActiveProcessCount = 0, + [bool]$Required = $true + ) + + $queueExists = Test-Path -LiteralPath $QueuePath + $depth = 0 + $sizeBytes = 0 + $ageSeconds = $null + $lastWriteUtc = $null + if ($queueExists) { + $item = Get-Item -LiteralPath $QueuePath -ErrorAction SilentlyContinue + if ($item) { + $sizeBytes = [int64]$item.Length + $lastWriteUtc = $item.LastWriteTimeUtc + $ageSeconds = [int][Math]::Floor(((Get-Date).ToUniversalTime() - $lastWriteUtc).TotalSeconds) + } + try { + $depth = [int]((Get-Content -LiteralPath $QueuePath -ErrorAction SilentlyContinue | Measure-Object).Count) + } + catch { + $depth = 0 + } + } + + $lockExists = Test-Path -LiteralPath $LockPath + $lockHeld = $false + if ($lockExists) { + try { + $lockHandle = [System.IO.File]::Open($LockPath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None) + $lockHandle.Dispose() + } + catch { + $lockHeld = $true + } + } + + $staleQueue = ($depth -gt 0 -and $null -ne $ageSeconds -and $ageSeconds -gt $StaleAfterSeconds -and -not $lockHeld) + $orphanedQueue = ($depth -gt 0 -and $ActiveProcessCount -le 0 -and $null -ne $ageSeconds -and $ageSeconds -gt $StaleAfterSeconds) + $oversizedQueue = ($depth -gt $MaxDepth) + $ok = if (-not $Required) { $true } else { -not ($staleQueue -or $orphanedQueue -or $oversizedQueue) } + + return [pscustomobject]@{ + name = $Name + required = [bool]$Required + queuePath = $QueuePath + queueExists = [bool]$queueExists + depth = [int]$depth + sizeBytes = [int64]$sizeBytes + lastWriteUtc = if ($null -ne $lastWriteUtc) { $lastWriteUtc.ToString('o') } else { $null } + ageSeconds = if ($null -ne $ageSeconds) { [int]$ageSeconds } else { $null } + lockPath = $LockPath + lockExists = [bool]$lockExists + lockHeld = [bool]$lockHeld + activeProcessCount = [int]$ActiveProcessCount + staleAfterSeconds = [int]$StaleAfterSeconds + maxDepth = [int]$MaxDepth + staleQueue = [bool]$staleQueue + orphanedQueue = [bool]$orphanedQueue + oversizedQueue = [bool]$oversizedQueue + ok = [bool]$ok + } +} + +function Get-TaskSnapshot { + param( + [Parameter(Mandatory = $true)] + [string[]]$TaskNames + ) + + return @( + foreach ($taskName in @($TaskNames | Sort-Object -Unique)) { + $task = Get-ScheduledTask -ErrorAction SilentlyContinue | Where-Object { $_.TaskName -eq $taskName } | Select-Object -First 1 + if ($null -eq $task) { + [pscustomobject]@{ + taskName = $taskName + present = $false + enabled = $false + state = 'Отсутствует' + lastResult = $null + ok = $false + } + continue + } + + $taskInfo = $null + try { + $taskInfo = Get-ScheduledTaskInfo -TaskName $task.TaskName -TaskPath $task.TaskPath -ErrorAction Stop + } + catch { + } + + $enabled = $true + try { + if ($task.Settings.PSObject.Properties.Name -contains 'Enabled') { + $enabled = [bool]$task.Settings.Enabled + } + } + catch { + } + + [pscustomobject]@{ + taskName = [string]$task.TaskName + present = $true + enabled = [bool]$enabled + state = [string]$task.State + lastResult = if ($taskInfo) { [int64]$taskInfo.LastTaskResult } else { $null } + ok = [bool]($enabled) + } + } + ) +} + $requiredFiles = @( $collectorScript, $endpointCollectorScript, $sessionCollectorScript, + $evtxExportScript, $rulesPath, $policyPath, + $policyClientScript, $launchScript, $recoveryScript, $ConfigPath ) +if ($fileOpsExpected) { + $requiredFiles += $fileCollectorScript +} if ($afkExpected) { $requiredFiles += (Join-Path $installRoot 'aw-watcher-afk\aw-watcher-afk.exe') } @@ -40,55 +343,123 @@ if ($windowExpected) { } $missingFiles = @( - $requiredFiles | Where-Object { -not (Test-Path -LiteralPath $_) } + $requiredFiles | + Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) } | + Where-Object { -not (Test-Path -LiteralPath $_) } ) -$processNames = @() -if ($afkExpected) { $processNames += 'aw-watcher-afk' } -if ($windowExpected) { $processNames += 'aw-watcher-window' } -$runningProcesses = @() -if ($processNames.Count -gt 0) { - $runningProcesses = Get-Process -Name $processNames -ErrorAction SilentlyContinue | Select-Object Name, Id, SessionId +$runningWatchers = @() +$expectedWatcherNames = @() +if ($afkExpected) { $expectedWatcherNames += 'aw-watcher-afk' } +if ($windowExpected) { $expectedWatcherNames += 'aw-watcher-window' } +if ($expectedWatcherNames.Count -gt 0) { + $runningWatchers = @(Get-Process -Name $expectedWatcherNames -ErrorAction SilentlyContinue | Select-Object Name, Id, SessionId) } -$sessionCollectorProcesses = Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | - Where-Object { - ($_.Name -ieq 'powershell.exe' -or $_.Name -ieq 'pwsh.exe') -and - $_.CommandLine -match [Regex]::Escape($sessionCollectorScript) - } | - Select-Object Name, ProcessId, SessionId, CommandLine + +$sessionCollectorProcesses = @(Get-CollectorProcesses -ScriptPath $sessionCollectorScript) +$endpointCollectorProcesses = @(Get-CollectorProcesses -ScriptPath $endpointCollectorScript) +$fileCollectorProcesses = if ($fileOpsExpected) { @(Get-CollectorProcesses -ScriptPath $fileCollectorScript) } else { @() } +$browserCollectorProcesses = @(Get-CollectorProcesses -ScriptPath $collectorScript) + +$loggedOnUsers = Get-LoggedOnUsers +$sessionBoundUsers = @( + @($config.userTasks) | + Where-Object { Test-UserHasSession -UserId ([string]$_.userId) -LoggedOnUsers $loggedOnUsers } | + ForEach-Object { [string]$_.userId } +) +$sessionScopedExpectedCount = [int]$sessionBoundUsers.Count +$sessionScopedCollectorsRequired = ($sessionScopedExpectedCount -gt 0) $taskNames = @() if ($config.userTasks) { $taskNames += @($config.userTasks | ForEach-Object { [string]$_.launchTaskName }) } $taskNames += [string]$config.recovery.taskName -$taskNames = $taskNames | Sort-Object -Unique +$tasks = @(Get-TaskSnapshot -TaskNames $taskNames) -$tasks = foreach ($taskName in $taskNames) { - $task = Get-ScheduledTask -ErrorAction SilentlyContinue | Where-Object { $_.TaskName -eq $taskName } | Select-Object -First 1 - if ($task) { - [pscustomobject]@{ - taskName = $task.TaskName - state = [string]$task.State - present = $true - } +$watcherDuplicates = @(Get-DuplicateProcessGroups -Processes $runningWatchers -PerSession $true) +$sessionCollectorDuplicates = @(Get-DuplicateProcessGroups -Processes $sessionCollectorProcesses -PerSession $false) +$endpointCollectorDuplicates = @(Get-DuplicateProcessGroups -Processes $endpointCollectorProcesses -PerSession $true) +$fileCollectorDuplicates = @(Get-DuplicateProcessGroups -Processes $fileCollectorProcesses -PerSession $true) +$browserCollectorDuplicates = @(Get-DuplicateProcessGroups -Processes $browserCollectorProcesses -PerSession $true) + +$watcherByName = @{} +foreach ($watcher in $runningWatchers) { + if (-not $watcherByName.ContainsKey([string]$watcher.Name)) { + $watcherByName[[string]$watcher.Name] = 0 } - else { - [pscustomobject]@{ - taskName = $taskName - state = 'Отсутствует' - present = $false - } + $watcherByName[[string]$watcher.Name]++ +} + +$bucketChecks = @( + Get-BucketHealth -BucketId ('aw-worktime-sessions_' + $awHostname) -MaxAgeSeconds $sessionFreshnessSeconds -Required $true -RequireFreshEvent $true +) +if ($sessionScopedCollectorsRequired -and $afkExpected) { + $bucketChecks += Get-BucketHealth -BucketId ('aw-watcher-afk_' + $awHostname) -MaxAgeSeconds $freshnessSeconds -Required $true -RequireFreshEvent $false +} +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 +} +if ($sessionScopedCollectorsRequired -and $fileOpsExpected) { + $bucketChecks += Get-BucketHealth -BucketId ('aw-file-operations_' + $awHostname) -MaxAgeSeconds $transportStaleSeconds -Required $false -RequireFreshEvent $true +} + +$queueChecks = @( + Get-TransportQueueHealth -Name 'endpoint' -QueuePath (Join-Path $stateRoot 'dlp-endpoint-signals-queue.jsonl') -LockPath (Join-Path $stateRoot 'dlp-endpoint-signals-queue.lock') -StaleAfterSeconds $transportStaleSeconds -MaxDepth $queueMaxDepth -ActiveProcessCount @($endpointCollectorProcesses).Count -Required $sessionScopedCollectorsRequired +) +if ($fileOpsExpected) { + $queueChecks += Get-TransportQueueHealth -Name 'fileops' -QueuePath (Join-Path $stateRoot 'file-operations-queue.jsonl') -LockPath (Join-Path $stateRoot 'file-operations-queue.lock') -StaleAfterSeconds $transportStaleSeconds -MaxDepth $queueMaxDepth -ActiveProcessCount @($fileCollectorProcesses).Count -Required $sessionScopedCollectorsRequired +} + +$printServiceOperationalEnabled = $false +try { + $printServiceLog = Get-WinEvent -ListLog 'Microsoft-Windows-PrintService/Operational' -ErrorAction Stop + $printServiceOperationalEnabled = [bool]$printServiceLog.IsEnabled +} +catch { +} + +$printJobTitlePolicyEnabled = $false +try { + $printPolicy = Get-ItemProperty -LiteralPath 'HKLM:\Software\Policies\Microsoft\Windows NT\Printers' -Name 'ShowJobTitleInEventLogs' -ErrorAction Stop + $printJobTitlePolicyEnabled = ([int]$printPolicy.ShowJobTitleInEventLogs -eq 1) +} +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) } } -$serverUrl = '{0}://{1}:{2}' -f [string]$config.server.scheme, [string]$config.server.host, [int]$config.server.port +$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) } +$sessionCollectorOk = (@($sessionCollectorProcesses).Count -eq 1) + $result = [ordered]@{ generatedAtUtc = (Get-Date).ToUniversalTime().ToString('o') configPath = $ConfigPath serverUrl = $serverUrl + apiBase = $apiBase + awHostname = $awHostname installRoot = $installRoot stateRoot = $stateRoot + timing = [ordered]@{ + pollSeconds = [int]$pollSeconds + pulseSeconds = [int]$pulseSeconds + freshnessSeconds = [int]$freshnessSeconds + sessionFreshnessSeconds = [int]$sessionFreshnessSeconds + transportStaleSeconds = [int]$transportStaleSeconds + } files = [ordered]@{ required = $requiredFiles missing = $missingFiles @@ -96,22 +467,76 @@ $result = [ordered]@{ } tasks = [ordered]@{ list = $tasks - ok = [bool]($tasks.Count -gt 0 -and -not ($tasks | Where-Object { -not $_.present })) + ok = [bool]($tasks.Count -gt 0 -and -not ($tasks | Where-Object { -not $_.present -or -not $_.enabled })) } processes = [ordered]@{ - expected = $processNames - list = @($runningProcesses) + sessionBoundUsers = $sessionBoundUsers + sessionScopedExpectedCount = [int]$sessionScopedExpectedCount + watchers = @($runningWatchers) + watcherDuplicates = @($watcherDuplicates) sessionCollectors = @($sessionCollectorProcesses) + sessionCollectorDuplicates = @($sessionCollectorDuplicates) + browserCollectors = @($browserCollectorProcesses) + browserCollectorDuplicates = @($browserCollectorDuplicates) + endpointCollectors = @($endpointCollectorProcesses) + endpointCollectorDuplicates = @($endpointCollectorDuplicates) + fileCollectors = @($fileCollectorProcesses) + fileCollectorDuplicates = @($fileCollectorDuplicates) ok = [bool]( - ( - ($processNames.Count -eq 0) -or - (($runningProcesses | Select-Object -ExpandProperty Name -Unique).Count -ge $processNames.Count) - ) -and - ($sessionCollectorProcesses.Count -ge 1) + $watcherCountsOk -and + $sessionCollectorOk -and + $browserProcessOk -and + $endpointProcessOk -and + $fileProcessOk -and + ($watcherDuplicates.Count -eq 0) -and + ($sessionCollectorDuplicates.Count -eq 0) -and + ($browserCollectorDuplicates.Count -eq 0) -and + ($endpointCollectorDuplicates.Count -eq 0) -and + ($fileCollectorDuplicates.Count -eq 0) + ) + } + buckets = [ordered]@{ + list = @($bucketChecks) + ok = [bool](-not ($bucketChecks | Where-Object { -not $_.ok })) + } + queues = [ordered]@{ + list = @($queueChecks) + ok = [bool](-not ($queueChecks | Where-Object { -not $_.ok })) + } + printTelemetry = [ordered]@{ + operationalLogEnabled = $printServiceOperationalEnabled + jobTitlePolicyEnabled = $printJobTitlePolicyEnabled + ok = [bool]($printServiceOperationalEnabled -and $printJobTitlePolicyEnabled) + } + 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 } + evtxChannels = if ($config.PSObject.Properties.Name -contains 'forensics' -and $config.forensics.PSObject.Properties.Name -contains 'evtxChannels') { @($config.forensics.evtxChannels) } else { @() } + ok = [bool]( + ($config.PSObject.Properties.Name -contains 'forensics') -and + ($config.forensics.PSObject.Properties.Name -contains 'evtxExportRoot') -and + ($config.forensics.PSObject.Properties.Name -contains 'retentionDays') -and + ($config.forensics.PSObject.Properties.Name -contains 'evtxChannels') -and + (@($config.forensics.evtxChannels).Count -gt 0) ) } } -$result.overallOk = [bool]($result.files.ok -and $result.tasks.ok -and $result.processes.ok) +$result.summary = [ordered]@{ + failedSections = @( + 'files', 'tasks', 'processes', 'buckets', 'queues', 'printTelemetry', 'forensics' | + Where-Object { -not [bool]$result.$_.ok } + ) +} + +$result.overallOk = [bool]( + $result.files.ok -and + $result.tasks.ok -and + $result.processes.ok -and + $result.buckets.ok -and + $result.queues.ok -and + $result.printTelemetry.ok -and + $result.forensics.ok +) $result diff --git a/install-kit-awindows-20260427-211240/windows/worktime-session-collector.ps1 b/install-kit-awindows-20260427-211240/windows/worktime-session-collector.ps1 index 226a500..84fbd04 100644 --- a/install-kit-awindows-20260427-211240/windows/worktime-session-collector.ps1 +++ b/install-kit-awindows-20260427-211240/windows/worktime-session-collector.ps1 @@ -1,20 +1,77 @@ param( [string]$ConfigPath = 'C:\ProgramData\AWatch-rus\deployment-config.json', [string]$Hostname, - [int]$PollSeconds = 30 + [int]$PollSeconds = 0 ) +# Force UTF-8 for console I/O +try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {} +try { [Console]::InputEncoding = [System.Text.Encoding]::UTF8 } catch {} Set-StrictMode -Version Latest -$ErrorActionPreference = 'Stop' +$ErrorActionPreference = 'Continue' + +function Decode-Bytes-Auto { + param([byte[]]$Bytes) + if (-not $Bytes) { return '' } + + $candidates = @() + + # Try strict UTF8 first (detect invalid sequences) + try { + $utf8Strict = New-Object System.Text.UTF8Encoding($false,$true) + $txt = $utf8Strict.GetString($Bytes) + $candidates += @{enc='utf8'; text=$txt} + } + catch { + # invalid UTF8 sequences; ignore + } + + # Try CP866 and CP1251 + try { $cp866 = [System.Text.Encoding]::GetEncoding(866); $txt866 = $cp866.GetString($Bytes); $candidates += @{enc='cp866'; text=$txt866} } catch {} + try { $cp1251 = [System.Text.Encoding]::GetEncoding(1251); $txt1251 = $cp1251.GetString($Bytes); $candidates += @{enc='cp1251'; text=$txt1251} } catch {} + + # If nothing decoded yet, fallback to UTF8 permissive + if ($candidates.Count -eq 0) { + try { $txt = [System.Text.Encoding]::UTF8.GetString($Bytes); return $txt } catch { return '' } + } + + # Score decodings by count of Cyrillic letters; prefer highest + $best = $null; $bestScore = -1 + foreach ($c in $candidates) { + $t = $c.text + if (-not $t) { continue } + $score = 0 + try { $score = ([regex]::Matches($t,'\p{IsCyrillic}')).Count } catch { $score = 0 } + if ($score -gt $bestScore) { $best = $c; $bestScore = $score } + } + + if ($best -ne $null) { return $best.text } + + # Final fallback: first candidate text + return $candidates[0].text +} function Get-Config { param([string]$Path) - if (-not (Test-Path -LiteralPath $Path)) { - throw "Конфигурация не найдена: $Path" + throw "Config not found: $Path" + } + try { + $bytes = [System.IO.File]::ReadAllBytes($Path) + # Config is JSON. Prefer deterministic BOM-based decoding over heuristics. + if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) { + $text = [System.Text.Encoding]::UTF8.GetString($bytes) + } elseif ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFF -and $bytes[1] -eq 0xFE) { + $text = [System.Text.Encoding]::Unicode.GetString($bytes) + } else { + $text = [System.Text.Encoding]::UTF8.GetString($bytes) + } + $text = $text -replace '^\uFEFF', '' + return $text | ConvertFrom-Json -ErrorAction Stop + } + catch { + throw "Failed to read config: $Path - $($_.Exception.Message)" } - - Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json } function Invoke-AwJsonPost { @@ -22,9 +79,15 @@ function Invoke-AwJsonPost { [Parameter(Mandatory = $true)][string]$Uri, [Parameter(Mandatory = $true)][string]$Json ) - - $bytes = [Text.Encoding]::UTF8.GetBytes($Json) - Invoke-RestMethod -Method Post -Uri $Uri -ContentType 'application/json; charset=utf-8' -Body $bytes | Out-Null + try { + $bytes = [System.Text.Encoding]::UTF8.GetBytes($Json) + Invoke-RestMethod -Method Post -Uri $Uri -ContentType 'application/json; charset=utf-8' -Body $bytes -ErrorAction Stop | Out-Null + return $true + } + catch { + Write-Verbose "POST error: $($_.Exception.Message)" + return $false + } } function Ensure-Bucket { @@ -33,127 +96,202 @@ function Ensure-Bucket { [Parameter(Mandatory = $true)][string]$BucketId, [Parameter(Mandatory = $true)][string]$HostnameValue ) + try { Invoke-RestMethod -Method Get -Uri "$ApiBase/buckets/$BucketId" -ErrorAction Stop | Out-Null; return } catch { Write-Verbose "Bucket not found, creating: $BucketId" } - try { - Invoke-RestMethod -Method Get -Uri "$ApiBase/buckets/$BucketId" | Out-Null - return + $body = @{ client='aw-worktime-session-collector'; type='aw.worktime.session'; hostname=$HostnameValue } | ConvertTo-Json -Compress + $attempts = 0 + while ($attempts -lt 3) { + $attempts++ + $ok = Invoke-AwJsonPost -Uri "$ApiBase/buckets/$BucketId" -Json $body + if ($ok) { return } + Start-Sleep -Seconds (2 * $attempts) } - catch { - } - - $body = @{ - client = 'aw-worktime-session-collector' - type = 'aw.worktime.session' - hostname = $HostnameValue - } | ConvertTo-Json -Compress - - Invoke-AwJsonPost -Uri "$ApiBase/buckets/$BucketId" -Json $body + try { Invoke-RestMethod -Method Get -Uri "$ApiBase/buckets/$BucketId" -ErrorAction Stop | Out-Null } catch { Write-Verbose "Ensure-Bucket final check failed: $BucketId" } } -function Get-SessionRecords { +function Run-QueryUser { + $tries = @( + @{File='cmd.exe';Args='/c query user'}, + @{File='cmd.exe';Args='/c quser'}, + @{File='query.exe';Args='user'}, + @{File='quser.exe';Args=''} + ) + foreach ($t in $tries) { + try { + $psi = New-Object System.Diagnostics.ProcessStartInfo + $psi.FileName = $t.File + if ($t.Args) { $psi.Arguments = $t.Args } + $psi.RedirectStandardOutput = $true + $psi.RedirectStandardError = $true + $psi.UseShellExecute = $false + $psi.CreateNoWindow = $true + + $proc = [System.Diagnostics.Process]::Start($psi) + $stream = $proc.StandardOutput.BaseStream + $ms = New-Object System.IO.MemoryStream + $buffer = New-Object byte[] 4096 + while (($read = $stream.Read($buffer,0,$buffer.Length)) -gt 0) { $ms.Write($buffer,0,$read) } + if (-not $proc.WaitForExit(8000)) { + try { $proc.Kill() } catch {} + continue + } + $bytes = $ms.ToArray() + + $text = Decode-Bytes-Auto -Bytes $bytes + if ($text -and $text.Trim()) { return ($text -split "\r?\n") | Where-Object { $_ -ne '' } } + } + catch { + # try next + } + } + return @() +} + +function Parse-SessionLines { + param([string[]]$Lines) $records = @() + if (-not $Lines) { return $records } - try { - $lines = quser 2>$null - if (-not $lines) { - return @() + $startIndex = 0 + # NOTE: Keep this script ASCII-only to stay compatible with Windows PowerShell 5 + # when the file is UTF-8 without BOM. Avoid Cyrillic literals in regex patterns. + if ($Lines.Count -gt 0 -and $Lines[0] -match '\b(USERNAME|UserName|USER)\b') { $startIndex = 1 } + + for ($i = $startIndex; $i -lt $Lines.Count; $i++) { + $line = ($Lines[$i] -replace '^\s*>', '').Trim() + if (-not $line) { continue } + + $parts = $line -split '\s+' + if ($parts.Count -lt 3) { continue } + $user = $parts[0] + $sess = '' + $id = -1 + $state = '' + + if ($parts.Count -ge 4 -and $parts[1] -match '^\d+$') { + $sess = '' + $id = [int]$parts[1] + $state = [string]$parts[2] + } + elseif ($parts.Count -ge 4 -and $parts[2] -match '^\d+$') { + $sess = [string]$parts[1] + $id = [int]$parts[2] + $state = [string]$parts[3] + } + else { + continue } - foreach ($line in ($lines | Select-Object -Skip 1)) { - $clean = ($line -replace '^\s*>?', '').Trim() - if (-not $clean) { - continue - } + if ($id -lt 0) { continue } - $parts = $clean -split '\s+' - if ($parts.Count -lt 4) { - continue - } - - $sessionName = '' - $sessionIdIndex = 2 - if ($parts[1] -match '^\d+$') { - $sessionIdIndex = 1 - } - else { - $sessionName = $parts[1] - } - - $sessionId = 0 - if ($parts[$sessionIdIndex] -match '^\d+$') { - $sessionId = [int]$parts[$sessionIdIndex] - } - - $records += [pscustomobject]@{ - username = $parts[0] - sessionName = $sessionName - sessionId = $sessionId - state = $parts[$sessionIdIndex + 1] - } - } + $records += [pscustomobject]@{ username=$user; sessionName=$sess; sessionId=$id; state=$state } } - catch { - } - return $records } function Test-SessionIsActive { - param([AllowNull()][string]$State) - if ([string]::IsNullOrWhiteSpace($State)) { return $false } + param([string]$State) + if (-not $State) { return $false } $s = $State.Trim().ToLowerInvariant() - return ($s -eq 'active') -or ($s -like 'актив*') + # Match English "active" and Russian "актив*" without embedding Cyrillic. + # "актив" = \u0430\u043A\u0442\u0438\u0432 + return ($s -match 'active') -or ($s -match '\u0430\u043a\u0442\u0438\u0432') } +function Get-CanonicalUserId { + param( + [pscustomobject]$Config, + [string]$HostnameValue, + [string]$Username + ) + + $normalizedUser = [string]$Username + if ([string]::IsNullOrWhiteSpace($normalizedUser)) { + return '' + } + + if ($Config -and $Config.PSObject.Properties.Name -contains 'userTasks' -and $Config.userTasks) { + foreach ($task in @($Config.userTasks)) { + try { + $taskUserId = [string]$task.userId + if ([string]::IsNullOrWhiteSpace($taskUserId)) { + continue + } + $parts = $taskUserId -split '\\', 2 + if ($parts.Count -eq 2 -and $parts[1].Equals($normalizedUser, [System.StringComparison]::OrdinalIgnoreCase)) { + return $taskUserId + } + } + catch { + } + } + } + + return "$HostnameValue\$normalizedUser" +} + +# Main $cfg = Get-Config -Path $ConfigPath -$hostValue = if ($Hostname) { $Hostname } else { [string]$env:COMPUTERNAME } -$apiBase = '{0}://{1}:{2}/api/0' -f [string]$cfg.server.scheme, [string]$cfg.server.host, [string]$cfg.server.port +$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 -$pulse = 120 -$sleepSec = if ($PollSeconds -gt 0) { - $PollSeconds -} -elseif ($cfg.collector -and $cfg.collector.pollSeconds) { - [int]$cfg.collector.pollSeconds -} -else { - 30 -} +$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 while ($true) { $now = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ') - $records = Get-SessionRecords + try { + $lines = Run-QueryUser + $records = Parse-SessionLines -Lines $lines + } + catch { + Write-Verbose "Session parse error: $($_.Exception.Message)" + $records = @() + } + if (-not $records -or $records.Count -eq 0) { - $records = @([pscustomobject]@{ - username = $env:USERNAME - sessionName = '' - sessionId = (Get-Process -Id $PID).SessionId - state = 'Unknown' - }) + # Fallback sample: keep bucket alive even when query user output is unavailable + # in non-interactive/session-0 contexts. + $records = @( + [pscustomobject]@{ + username = [string]$env:USERNAME + sessionName = '' + sessionId = [int](Get-Process -Id $PID).SessionId + state = 'Unknown' + } + ) } foreach ($rec in $records) { - $payload = @{ + $canonicalUserId = Get-CanonicalUserId -Config $cfg -HostnameValue $hostValue -Username ([string]$rec.username) + $payloadObj = [PSCustomObject]@{ timestamp = $now - duration = 0 - data = @{ + duration = $sleepSec + data = [PSCustomObject]@{ username = [string]$rec.username - userId = "$($env:USERDOMAIN)\$($rec.username)" + userId = $canonicalUserId sessionId = [int]$rec.sessionId sessionName = [string]$rec.sessionName state = [string]$rec.state - active = (Test-SessionIsActive -State ([string]$rec.state)) + active = Test-SessionIsActive -State ([string]$rec.state) + sampleSeconds = $sleepSec + pollSeconds = $sleepSec hostname = $hostValue source = 'worktime-session-collector' } - } | ConvertTo-Json -Depth 6 -Compress + } + + $payload = $payloadObj | ConvertTo-Json -Depth 6 -Compress try { - Invoke-AwJsonPost -Uri "$apiBase/buckets/$bucketId/heartbeat?pulsetime=$pulse" -Json $payload + $ok = Invoke-AwJsonPost -Uri "$apiBase/buckets/$bucketId/heartbeat?pulsetime=$pulse" -Json $payload + if (-not $ok) { Write-Verbose "Heartbeat not confirmed for user $($rec.username)" } } catch { + Write-Verbose "Heartbeat error: $($_.Exception.Message)" } } diff --git a/scripts/check_install_kit_vs_repo.sh b/scripts/check_install_kit_vs_repo.sh index 97d165d..9e39a2b 100755 --- a/scripts/check_install_kit_vs_repo.sh +++ b/scripts/check_install_kit_vs_repo.sh @@ -6,7 +6,7 @@ cd "$ROOT_DIR" KIT_DIR="install-kit-awindows-20260427-211240" -python - <<'PY' +python3 - <<'PY' from pathlib import Path import hashlib @@ -21,11 +21,16 @@ def sha(path: Path) -> str: all_compared=[] mismatches=[] missing_in_repo=[] +allowed_kit_only_prefixes=('server-configs-' ,) +allowed_kit_only_files={'README-INSTALL-KIT.txt'} for kp in sorted(p for p in kit.rglob('*') if p.is_file() and p.name!='MANIFEST.txt'): rel=kp.relative_to(kit) rp=root/rel if not rp.exists(): + rel_str=str(rel) + if rel_str in allowed_kit_only_files or rel_str.startswith(allowed_kit_only_prefixes): + continue missing_in_repo.append(str(rel)) continue all_compared.append(str(rel)) diff --git a/scripts/prod_rollout.sh b/scripts/prod_rollout.sh index 1f9ebfd..61583f7 100644 --- a/scripts/prod_rollout.sh +++ b/scripts/prod_rollout.sh @@ -52,7 +52,7 @@ fi if [[ -t 0 ]]; then prompt_secret AW_SSH_PASSWORD "Enter SSH password for aw_server (root@10.10.10.13)" - prompt_secret AW_WINRM_PASSWORD "Enter WinRM password for aw_windows (192.168.100.21)" + prompt_secret AW_WINRM_PASSWORD "Enter WinRM password for aw_windows (192.168.100.18)" fi if [[ -z "${AW_SSH_PASSWORD:-}" || -z "${AW_WINRM_PASSWORD:-}" ]]; then diff --git a/scripts/rebuild_install_kit.sh b/scripts/rebuild_install_kit.sh new file mode 100644 index 0000000..d89ffb2 --- /dev/null +++ b/scripts/rebuild_install_kit.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT_DIR" + +KIT_DIR="install-kit-awindows-20260427-211240" +ZIP_ARCHIVE="${KIT_DIR}.zip" +TAR_ARCHIVE="${KIT_DIR}.tar.gz" +SERVER_CONFIG_DIR="${KIT_DIR}/server-configs-192.168.100.18" +OLD_SERVER_CONFIG_DIR="${KIT_DIR}/server-configs-192.168.100.21" +TMP_SERVER_CONFIG_DIR="$(mktemp -d)" +trap 'rm -rf "$TMP_SERVER_CONFIG_DIR"' EXIT + +copy_file() { + local src="$1" + local dest="$2" + mkdir -p "$(dirname "$dest")" + cp "$src" "$dest" +} + +sync_tree() { + local base="$1" + shift + for rel in "$@"; do + copy_file "$rel" "${KIT_DIR}/${rel}" + done +} + +if [[ -d "$OLD_SERVER_CONFIG_DIR" ]]; then + cp "$OLD_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-"* + +ansible_files=( + ansible/README.md + ansible/deploy_aw_pfsense_poller.yml + ansible/deploy_aw_server.yml + ansible/deploy_aw_windows.yml + ansible/group_vars/all.example.yml + ansible/group_vars/pfsense-poller.example.yml + ansible/group_vars/proxmox-matrix.example.yml + ansible/group_vars/proxmox.example.yml + ansible/group_vars/windows.example.yml + ansible/install_full_stack.yml + ansible/inventory.example.ini + ansible/provision_proxmox_ct_and_deploy_aw.yml + ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml + ansible/tasks/provision_ct_and_deploy_aw.yml +) + +aw_server_files=( + aw-server/activitywatch-server.service + aw-server/apply_webui_ru_patch.sh + aw-server/aw-host-groups.json + aw-server/aw-ru-patch.js + aw-server/aw-rus-healthd.py + aw-server/aw-rus-healthd.service + aw-server/aw-rus-healthd.timer + aw-server/aw-server.env.example + aw-server/aw-sw-cleanup.js + aw-server/aw-worktime-api.py + aw-server/aw-worktime-api.service + aw-server/aw-worktime-panel.js + aw-server/install_aw_server.sh + aw-server/settings/classes-worktime.json + aw-server/settings/views-default.json +) + +windows_files=( + windows/ActivityWatch.Windows.Common.psd1 + windows/ActivityWatch.Windows.Common.psm1 + windows/browser-domains-native-collector.ps1 + windows/deploy-domain-users.ps1 + windows/deploy-ensemble.ps1 + windows/deploy-single-user.ps1 + windows/dlp-endpoint-signals-collector.ps1 + windows/dlp-policy.example.json + windows/email-outbound-collector.ps1 + windows/hardening-recovery.ps1 + windows/migrate-awatch-rus-paths.ps1 + windows/validate-deployment.ps1 + windows/web-category-rules.example.json + windows/worktime-session-collector.ps1 +) + +sync_tree ansible "${ansible_files[@]}" +sync_tree aw-server "${aw_server_files[@]}" +sync_tree windows "${windows_files[@]}" + +mkdir -p "$SERVER_CONFIG_DIR" +if compgen -G "$TMP_SERVER_CONFIG_DIR/*.deployment-config.json" >/dev/null; then + cp "$TMP_SERVER_CONFIG_DIR"/*.deployment-config.json "$SERVER_CONFIG_DIR"/ +fi + +cat > "${KIT_DIR}/README-INSTALL-KIT.txt" <<'EOF' +ActivityWatch DetMir Windows Install Kit + +Includes: +- windows/* (deploy scripts, collectors, common module, configs/examples) +- ansible/* (Windows and AW server playbooks, examples, inventory, tasks) +- aw-server/* (server installer, health orchestrator, RU patch loader, host groups, default settings) +- server-configs-192.168.100.18/* (working Windows/RDP config snapshots) + +Source: +- Local project snapshot at build time. +EOF + +python3 - <<'PY' +from pathlib import Path +import hashlib + +root = Path('install-kit-awindows-20260427-211240') +manifest = root / 'MANIFEST.txt' +files = sorted(p for p in root.rglob('*') if p.is_file() and p.name != 'MANIFEST.txt') +with manifest.open('w', encoding='utf-8') as handle: + for path in files: + digest = hashlib.sha256(path.read_bytes()).hexdigest() + handle.write(f"{digest} {path.as_posix()}\n") +PY + +rm -f "$ZIP_ARCHIVE" "$TAR_ARCHIVE" +zip -rq "$ZIP_ARCHIVE" "$KIT_DIR" +tar -czf "$TAR_ARCHIVE" "$KIT_DIR" diff --git a/scripts/validate_install_kit.sh b/scripts/validate_install_kit.sh index f0ddc43..0c0e037 100755 --- a/scripts/validate_install_kit.sh +++ b/scripts/validate_install_kit.sh @@ -14,7 +14,7 @@ required_files=( "$KIT_DIR/README-INSTALL-KIT.txt" "$KIT_DIR/windows/deploy-ensemble.ps1" "$KIT_DIR/windows/validate-deployment.ps1" - "$KIT_DIR/ansible/deploy_aw_windows_phase2.yml" + "$KIT_DIR/ansible/deploy_aw_windows.yml" "$KIT_DIR/aw-server/install_aw_server.sh" ) @@ -27,7 +27,7 @@ echo "[2/4] Manifest checksum verification" sha256sum -c "$MANIFEST" >/dev/null echo "[3/4] Manifest completeness" -python - <<'PY' +python3 - <<'PY' from pathlib import Path import sys kit=Path('install-kit-awindows-20260427-211240') @@ -54,7 +54,7 @@ print(f'MANIFEST complete: {len(actual_set)} files tracked') PY echo "[4/4] Archive composition check" -python - <<'PY' +python3 - <<'PY' from pathlib import Path import tarfile, zipfile, sys kit_prefix='install-kit-awindows-20260427-211240/' diff --git a/windows/ActivityWatch.Windows.Common.psm1 b/windows/ActivityWatch.Windows.Common.psm1 index 0d69fed..2f7f461 100755 --- a/windows/ActivityWatch.Windows.Common.psm1 +++ b/windows/ActivityWatch.Windows.Common.psm1 @@ -749,6 +749,33 @@ function New-LaunchLock { return `$lockPath } +function Get-SessionMarkerToken { + param([int]`$SessionId) + + try { + `$explorer = Get-Process -Name 'explorer' -ErrorAction SilentlyContinue | + Where-Object { `$_.SessionId -eq `$SessionId } | + Sort-Object StartTime | + Select-Object -First 1 + if (`$explorer -and `$explorer.StartTime) { + return `$explorer.StartTime.ToUniversalTime().ToString('yyyyMMddTHHmmssZ') + } + } + catch { + } + + try { + `$currentProcess = Get-Process -Id `$PID -ErrorAction Stop + if (`$currentProcess.StartTime) { + return `$currentProcess.StartTime.ToUniversalTime().ToString('yyyyMMddTHHmmssZ') + } + } + catch { + } + + return [string]`$SessionId +} + function Invoke-AwJsonPost { param( [Parameter(Mandatory = `$true)][string]`$Uri, @@ -864,7 +891,8 @@ function Send-LogonMarkerIfNeeded { return } - `$markerFile = Join-Path `$markerDir ("logon-{0}-{1}.marker" -f `$env:USERNAME, `$SessionId) + `$sessionMarkerToken = Get-SessionMarkerToken -SessionId `$SessionId + `$markerFile = Join-Path `$markerDir ("logon-{0}-{1}-{2}.marker" -f `$env:USERNAME, `$SessionId, `$sessionMarkerToken) if (Test-Path -LiteralPath `$markerFile) { return } diff --git a/windows/audit-cryptopro.ps1 b/windows/audit-cryptopro.ps1 new file mode 100644 index 0000000..7dab628 --- /dev/null +++ b/windows/audit-cryptopro.ps1 @@ -0,0 +1,338 @@ +[CmdletBinding()] +param( + [string[]]$ExpectedUsers = @(), + [switch]$IncludeUnexpectedProfiles +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +function Get-UninstallEntries { + param( + [Parameter(Mandatory = $true)] + [string]$NamePattern + ) + + $paths = @( + 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*', + 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' + ) + + return @( + Get-ItemProperty $paths -ErrorAction SilentlyContinue | + Where-Object { + $_.PSObject.Properties.Name -contains 'DisplayName' -and + -not [string]::IsNullOrWhiteSpace([string]$_.DisplayName) -and + ([string]$_.DisplayName -match $NamePattern) + } | + Select-Object DisplayName, DisplayVersion, Publisher + ) +} + +function Get-CryptoProToolPath { + param( + [Parameter(Mandatory = $true)] + [string]$LeafName + ) + + $candidate = Join-Path 'C:\Program Files\Crypto Pro\CSP' $LeafName + if (Test-Path -LiteralPath $candidate) { + return $candidate + } + return $null +} + +function Get-CryptoProStoreMapForCurrentUser { + param( + [string]$CertmgrPath + ) + + $map = @{} + if ([string]::IsNullOrWhiteSpace($CertmgrPath) -or -not (Test-Path -LiteralPath $CertmgrPath)) { + return $map + } + + $raw = & $CertmgrPath -list -store uMy 2>&1 + if ($LASTEXITCODE -ne 0) { + return $map + } + + $current = @{} + foreach ($line in @($raw | ForEach-Object { [string]$_ })) { + $trimmed = $line.Trim() + if ($trimmed -match '^SHA1 Thumbprint\s*:\s*(.+)$') { + $current.thumbprint = $Matches[1].Trim().ToUpperInvariant() + continue + } + if ($trimmed -match '^Embedded License\s*:\s*(.+)$') { + $current.embeddedLicense = $Matches[1].Trim() + continue + } + if ($trimmed -match '^Container\s*:\s*(.+)$') { + $current.container = $Matches[1].Trim() + continue + } + if ($trimmed -match '^\[ErrorCode:') { + if ($current.ContainsKey('thumbprint')) { + $map[$current.thumbprint] = [pscustomobject]@{ + embeddedLicense = if ($current.ContainsKey('embeddedLicense')) { [string]$current.embeddedLicense } else { $null } + container = if ($current.ContainsKey('container')) { [string]$current.container } else { $null } + } + } + $current = @{} + } + } + + return $map +} + +function Test-CertificateEmbeddedLicense { + param( + [Parameter(Mandatory = $true)] + [System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate, + [string]$CspTestPath + ) + + $result = [ordered]@{ + ok = $null + status = $null + error = $null + } + + if ([string]::IsNullOrWhiteSpace($CspTestPath) -or -not (Test-Path -LiteralPath $CspTestPath)) { + $result.error = 'csptest_missing' + return [pscustomobject]$result + } + + $tempPath = Join-Path $env:TEMP ("cryptopro-audit-{0}.cer" -f ([Guid]::NewGuid().ToString('N'))) + try { + Export-Certificate -Cert $Certificate -FilePath $tempPath -Force | Out-Null + $raw = & $CspTestPath -certlic -check -certfile $tempPath 2>&1 + if ($LASTEXITCODE -eq 0) { + $licenseLine = @($raw | Where-Object { [string]$_ -match '^License:\s*' } | Select-Object -First 1) + if ($licenseLine.Count -gt 0) { + $statusText = (($licenseLine[0] -replace '^License:\s*', '').Trim()) + $result.status = $statusText + $result.ok = ($statusText -match '^Good license\b') + } + else { + $result.ok = $false + $result.error = 'license_line_missing' + } + } + else { + $result.ok = $false + $result.error = ('csptest_exit_{0}' -f $LASTEXITCODE) + } + } + catch { + $result.ok = $false + $result.error = $_.Exception.Message + } + finally { + Remove-Item -LiteralPath $tempPath -Force -ErrorAction SilentlyContinue + } + + return [pscustomobject]$result +} + +function Get-ProfileMap { + $profileMap = @{} + foreach ($profile in @(Get-CimInstance Win32_UserProfile | Where-Object { -not $_.Special })) { + $leaf = Split-Path -Leaf ([string]$profile.LocalPath) + if ([string]::IsNullOrWhiteSpace($leaf)) { continue } + $profileMap[$leaf.ToUpperInvariant()] = [pscustomobject]@{ + user = $leaf + sid = [string]$profile.SID + localPath = [string]$profile.LocalPath + loaded = [bool]$profile.Loaded + } + } + return $profileMap +} + +function Get-UserAuditRows { + param( + [Parameter(Mandatory = $true)] + [string]$RequestedUser, + [pscustomobject]$Profile, + [string]$CspTestPath, + [hashtable]$CurrentUserStoreMap, + [bool]$ExpectedSigner + ) + + if ($null -eq $Profile) { + return @( + [pscustomobject]@{ + requestedUser = $RequestedUser + profileUser = $null + profilePath = $null + profileLoaded = $false + sid = $null + storeFilePath = $null + thumbprint = $null + subject = $null + issuer = $null + notAfterUtc = $null + hasPrivateKey = $false + embeddedLicenseOk = $null + embeddedLicenseStatus = $null + container = $null + expectedSigner = $ExpectedSigner + actionNeeded = 'profile_missing_or_no_login' + } + ) + } + + $certDir = Join-Path $Profile.localPath 'AppData\Roaming\Microsoft\SystemCertificates\My\Certificates' + $files = @() + if (Test-Path -LiteralPath $certDir) { + $files = @(Get-ChildItem -LiteralPath $certDir -File -ErrorAction SilentlyContinue) + } + if ($files.Count -eq 0) { + return @( + [pscustomobject]@{ + requestedUser = $RequestedUser + profileUser = $Profile.user + profilePath = $Profile.localPath + profileLoaded = [bool]$Profile.loaded + sid = $Profile.sid + storeFilePath = $certDir + thumbprint = $null + subject = $null + issuer = $null + notAfterUtc = $null + hasPrivateKey = $false + embeddedLicenseOk = $null + embeddedLicenseStatus = $null + container = $null + expectedSigner = $ExpectedSigner + actionNeeded = 'certificate_missing' + } + ) + } + + $rows = @() + foreach ($file in $files) { + try { + $cert = Get-PfxCertificate -FilePath $file.FullName + $thumbprint = [string]$cert.Thumbprint + $license = Test-CertificateEmbeddedLicense -Certificate $cert -CspTestPath $CspTestPath + $storeMeta = $null + if ($CurrentUserStoreMap.ContainsKey($thumbprint.ToUpperInvariant())) { + $storeMeta = $CurrentUserStoreMap[$thumbprint.ToUpperInvariant()] + } + + $actionNeeded = 'manual_review' + if ($cert.NotAfter -lt (Get-Date)) { + $actionNeeded = 'renew_certificate' + } + elseif (-not [bool]$cert.HasPrivateKey) { + $actionNeeded = 'attach_private_key_or_token' + } + elseif ($license.ok -eq $true) { + $actionNeeded = 'ready' + } + elseif ($license.ok -eq $false) { + $actionNeeded = 'embedded_license_missing_or_invalid' + } + + $rows += [pscustomobject]@{ + requestedUser = $RequestedUser + profileUser = $Profile.user + profilePath = $Profile.localPath + profileLoaded = [bool]$Profile.loaded + sid = $Profile.sid + storeFilePath = $file.FullName + thumbprint = $thumbprint + subject = [string]$cert.Subject + issuer = [string]$cert.Issuer + notAfterUtc = $cert.NotAfter.ToUniversalTime().ToString('o') + hasPrivateKey = [bool]$cert.HasPrivateKey + embeddedLicenseOk = $license.ok + embeddedLicenseStatus = $license.status + container = if ($null -ne $storeMeta) { [string]$storeMeta.container } else { $null } + expectedSigner = $ExpectedSigner + actionNeeded = $actionNeeded + } + } + catch { + $rows += [pscustomobject]@{ + requestedUser = $RequestedUser + profileUser = $Profile.user + profilePath = $Profile.localPath + profileLoaded = [bool]$Profile.loaded + sid = $Profile.sid + storeFilePath = $file.FullName + thumbprint = $null + subject = $null + issuer = $null + notAfterUtc = $null + hasPrivateKey = $false + embeddedLicenseOk = $null + embeddedLicenseStatus = $null + container = $null + expectedSigner = $ExpectedSigner + actionNeeded = ('certificate_parse_error: {0}' -f $_.Exception.Message) + } + } + } + + return $rows +} + +$cspEntries = @(Get-UninstallEntries -NamePattern 'CryptoPro|КриптоПро|Крипто') +$cspMain = @($cspEntries | Where-Object { $_.DisplayName -match 'CSP' } | Select-Object -First 1) +$browserPlugin = @($cspEntries | Where-Object { $_.DisplayName -match 'Browser' } | Select-Object -First 1) +$cspTestPath = Get-CryptoProToolPath -LeafName 'csptest.exe' +$certmgrPath = Get-CryptoProToolPath -LeafName 'certmgr.exe' +$currentUserStoreMap = Get-CryptoProStoreMapForCurrentUser -CertmgrPath $certmgrPath +$profileMap = Get-ProfileMap + +$targets = New-Object 'System.Collections.Generic.List[string]' +foreach ($expected in @($ExpectedUsers)) { + if (-not [string]::IsNullOrWhiteSpace([string]$expected)) { + $targets.Add([string]$expected) + } +} +if ($IncludeUnexpectedProfiles.IsPresent -or $targets.Count -eq 0) { + foreach ($profileKey in @($profileMap.Keys | Sort-Object)) { + $targets.Add([string]$profileMap[$profileKey].user) + } +} + +$seen = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase) +$rows = @() +foreach ($target in $targets) { + if (-not $seen.Add($target)) { continue } + $key = $target.ToUpperInvariant() + $profile = if ($profileMap.ContainsKey($key)) { $profileMap[$key] } else { $null } + $rows += Get-UserAuditRows -RequestedUser $target -Profile $profile -CspTestPath $cspTestPath -CurrentUserStoreMap $currentUserStoreMap -ExpectedSigner:([bool]($ExpectedUsers -contains $target)) +} + +$summary = [ordered]@{ + expectedSignerCount = [int](@($ExpectedUsers | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) }).Count) + readyCount = [int](@($rows | Where-Object { $_.actionNeeded -eq 'ready' }).Count) + missingProfileCount = [int](@($rows | Where-Object { $_.actionNeeded -eq 'profile_missing_or_no_login' }).Count) + missingCertificateCount = [int](@($rows | Where-Object { $_.actionNeeded -eq 'certificate_missing' }).Count) + noPrivateKeyCount = [int](@($rows | Where-Object { $_.actionNeeded -eq 'attach_private_key_or_token' }).Count) + licenseProblemCount = [int](@($rows | Where-Object { $_.actionNeeded -eq 'embedded_license_missing_or_invalid' }).Count) + renewalNeededCount = [int](@($rows | Where-Object { $_.actionNeeded -eq 'renew_certificate' }).Count) +} + +[pscustomobject]@{ + generatedAtUtc = (Get-Date).ToUniversalTime().ToString('o') + computerName = [string]$env:COMPUTERNAME + currentUser = [string]$env:USERNAME + cryptopro = [ordered]@{ + cspInstalled = ($null -ne $cspMain) + cspVersion = if ($null -ne $cspMain) { [string]$cspMain.DisplayVersion } else { $null } + browserPluginInstalled = ($null -ne $browserPlugin) + browserPluginVersion = if ($null -ne $browserPlugin) { [string]$browserPlugin.DisplayVersion } else { $null } + cspTestPath = $cspTestPath + certmgrPath = $certmgrPath + } + expectedUsers = @($ExpectedUsers) + signers = @($rows) + summary = $summary +}