diff --git a/ansible/README.md b/ansible/README.md index 6dab5f3..a8c77b6 100644 --- a/ansible/README.md +++ b/ansible/README.md @@ -1,37 +1,33 @@ # Ansible ensemble for AWatch-rus -Эта директория содержит Ansible-ensemble для двух сценариев: +Эта директория содержит Ansible-ensemble для полного развёртывания AWatch-rus: - деплой на уже существующий Debian host/CT; -- полный цикл с нуля в Proxmox: создание CT + bootstrap + установка ActivityWatch + RU patch. -- централизованный деплой Windows phase-2 collectors по WinRM. -- deployment внешнего pfSense poller'а на Debian/Ubuntu utility VM. +- полный цикл с нуля в Proxmox: создание CT + bootstrap + установка ActivityWatch + RU patch; +- централизованное развёртывание Windows phase-2 collector'ов по WinRM; +- развёртывание внешнего pfSense poller'а на Debian/Ubuntu utility VM. ## Файлы -- `/home/igor/tmp/AWatch-rus/ansible/deploy_aw_server.yml` — основной playbook. -- `/home/igor/tmp/AWatch-rus/ansible/provision_proxmox_ct_and_deploy_aw.yml` — full-stack playbook для Proxmox. -- `/home/igor/tmp/AWatch-rus/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml` — массовый full-stack playbook (несколько CT). -- `/home/igor/tmp/AWatch-rus/ansible/deploy_aw_windows_phase2.yml` — WinRM playbook для развёртывания phase-2 Windows collector'ов. -- `/home/igor/tmp/AWatch-rus/ansible/deploy_aw_pfsense_poller.yml` — deployment pfSense poller'а. -- `/home/igor/tmp/AWatch-rus/ansible/install_full_stack.yml` — полный установочный playbook (оркестратор всех этапов). -- `/home/igor/tmp/AWatch-rus/ansible/inventory.example.ini` — шаблон inventory. -- `/home/igor/tmp/AWatch-rus/ansible/group_vars/all.example.yml` — шаблон переменных. -- `/home/igor/tmp/AWatch-rus/ansible/group_vars/proxmox.example.yml` — шаблон переменных CT в Proxmox. -- `/home/igor/tmp/AWatch-rus/ansible/group_vars/proxmox-matrix.example.yml` — шаблон матрицы CT. -- `/home/igor/tmp/AWatch-rus/ansible/group_vars/windows.example.yml` — шаблон переменных Windows phase-2. -- `/home/igor/tmp/AWatch-rus/ansible/group_vars/pfsense-poller.example.yml` — шаблон переменных pfSense poller'а. +- `ansible/deploy_aw_server.yml` — основной playbook для уже существующего Debian/CT host. +- `ansible/provision_proxmox_ct_and_deploy_aw.yml` — полный playbook для Proxmox. +- `ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml` — массовый полный playbook (несколько CT). +- `ansible/deploy_aw_windows_phase2.yml` — WinRM playbook для развёртывания Windows/RDP collector'ов. +- `ansible/deploy_aw_pfsense_poller.yml` — развёртывание pfSense poller'а. +- `ansible/install_full_stack.yml` — полный установочный playbook (оркестратор всех этапов). +- `ansible/inventory.example.ini` — шаблон inventory. +- `ansible/group_vars/*.example.yml` — шаблоны переменных. ## Быстрый запуск 1. Скопируйте шаблоны: - - `cp /home/igor/tmp/AWatch-rus/ansible/inventory.example.ini /home/igor/tmp/AWatch-rus/ansible/inventory.ini` - - `cp /home/igor/tmp/AWatch-rus/ansible/group_vars/all.example.yml /home/igor/tmp/AWatch-rus/ansible/group_vars/all.yml` + - `cp ansible/inventory.example.ini ansible/inventory.ini` + - `cp ansible/group_vars/all.example.yml ansible/group_vars/all.yml` 2. Заполните значения в `inventory.ini` и `group_vars/all.yml`. 3. Запустите: ```bash -cd /home/igor/tmp/AWatch-rus/ansible +cd ansible ansible-playbook -i inventory.ini deploy_aw_server.yml ``` @@ -40,7 +36,7 @@ ansible-playbook -i inventory.ini deploy_aw_server.yml Если нужно прогнать полный цикл одной командой: ```bash -cd /home/igor/tmp/AWatch-rus/ansible +cd ansible ansible-playbook -i inventory.ini install_full_stack.yml ``` @@ -56,50 +52,50 @@ ansible-playbook -i inventory.ini install_full_stack.yml ## Полный запуск с нуля в Proxmox 1. Подготовьте inventory и vars: - - `cp /home/igor/tmp/AWatch-rus/ansible/inventory.example.ini /home/igor/tmp/AWatch-rus/ansible/inventory.ini` - - `cp /home/igor/tmp/AWatch-rus/ansible/group_vars/all.example.yml /home/igor/tmp/AWatch-rus/ansible/group_vars/all.yml` - - `cp /home/igor/tmp/AWatch-rus/ansible/group_vars/proxmox.example.yml /home/igor/tmp/AWatch-rus/ansible/group_vars/proxmox.yml` + - `cp ansible/inventory.example.ini ansible/inventory.ini` + - `cp ansible/group_vars/all.example.yml ansible/group_vars/all.yml` + - `cp ansible/group_vars/proxmox.example.yml ansible/group_vars/proxmox.yml` 2. Заполните `group_vars/proxmox.yml` и `group_vars/all.yml`. 3. Запустите playbook: ```bash -cd /home/igor/tmp/AWatch-rus/ansible +cd ansible ansible-playbook -i inventory.ini provision_proxmox_ct_and_deploy_aw.yml ``` ## Массовый запуск (матрица CT) 1. Подготовьте матрицу: - - `cp /home/igor/tmp/AWatch-rus/ansible/group_vars/proxmox-matrix.example.yml /home/igor/tmp/AWatch-rus/ansible/group_vars/proxmox-matrix.yml` + - `cp ansible/group_vars/proxmox-matrix.example.yml ansible/group_vars/proxmox-matrix.yml` 2. Заполните `proxmox-matrix.yml`. 3. Запустите: ```bash -cd /home/igor/tmp/AWatch-rus/ansible +cd ansible ansible-playbook -i inventory.ini provision_proxmox_ct_matrix_and_deploy_aw.yml ``` ## Windows phase-2 rollout (WinRM) 1. Подготовьте inventory и vars: - - `cp /home/igor/tmp/AWatch-rus/ansible/inventory.example.ini /home/igor/tmp/AWatch-rus/ansible/inventory.ini` - - `cp /home/igor/tmp/AWatch-rus/ansible/group_vars/windows.example.yml /home/igor/tmp/AWatch-rus/ansible/group_vars/windows.yml` + - `cp ansible/inventory.example.ini ansible/inventory.ini` + - `cp ansible/group_vars/windows.example.yml ansible/group_vars/windows.yml` 2. Заполните `inventory.ini` (секция `[aw_windows]`) и `group_vars/windows.yml`. - Для русской локализации Windows часто нужен `ansible_user=Администратор` (а не `Administrator`). - Если WinRM закрыт, playbook не сможет стартовать и нужно сначала открыть `5985/5986` и `wsman`. 3. Запустите: ```bash -cd /home/igor/tmp/AWatch-rus/ansible +cd ansible ansible-playbook -i inventory.ini deploy_aw_windows_phase2.yml ``` Playbook: -- выгружает `windows/*` toolkit на целевой хост в `C:\Deploy\AWatch-rus\windows`; +- выгружает полный `windows/*` toolkit на целевой хост в `C:\Deploy\AWatch-rus\windows`, включая DLP и `worktime-session-collector.ps1`; - выполняет `deploy-ensemble.ps1` (deploy + hardening/recovery) с phase-2 policy/rules; - после deploy принудительно запускает `ActivityWatch Recovery` и все `ActivityWatch Launch *` задачи; -- выполняет API smoke-check bucket `aw-watcher-afk_SHARKON2025` и ожидает свежие `not-afk` события; +- выполняет API smoke-check bucket `aw-watcher-afk_` и ожидает свежие `not-afk` события; - запускает `validate-deployment.ps1`; - забирает JSON-отчёт в локальную директорию (`/tmp/aw-rus-validation` по умолчанию). @@ -110,17 +106,20 @@ Playbook: - `aw_windows_incident_capture_enabled: false` — отключить блок incidentCapture; - `aw_windows_incident_screenshot_enabled: false` — не делать скриншот при DLP-инциденте; - `aw_windows_incident_artifacts_root: 'C:\...\incident-artifacts'` — переопределить путь артефактов; +- `aw_windows_package_version`, `aw_windows_package_url`, `aw_windows_package_zip_path` — версия и источник Windows-пакета ActivityWatch; +- `aw_windows_api_smoke_check_bucket: ""` — автоматически использовать `aw-watcher-afk_`; +- `aw_windows_fail_on_validation_error: true` — завершать playbook ошибкой, если `validate-deployment.ps1` возвращает `overallOk=false`; - `aw_windows_skip_hardening: true` — пропустить `hardening-recovery.ps1` внутри ensemble-скрипта. -## pfSense poller rollout +## Развёртывание pfSense poller 1. Подготовьте vars: - - `cp /home/igor/tmp/AWatch-rus/ansible/group_vars/pfsense-poller.example.yml /home/igor/tmp/AWatch-rus/ansible/group_vars/pfsense-poller.yml` + - `cp ansible/group_vars/pfsense-poller.example.yml ansible/group_vars/pfsense-poller.yml` 2. Добавьте inventory group `[aw_pfsense_pollers]`. 3. Запустите: ```bash -cd /home/igor/tmp/AWatch-rus/ansible +cd ansible ansible-playbook -i inventory.ini deploy_aw_pfsense_poller.yml ``` @@ -139,4 +138,6 @@ Playbook: - Для Web UI используется checksum-based cache-bust для `ru-patch-v5.js` и `sw-cleanup.js`, чтобы браузер не держал старую DLP/русскую статику после деплоя. - На `#/home` Web UI делит хосты на `Windows RDP` и `Virtual servers + Proxmox`. - Выполнена валидация API `http://127.0.0.1:5600/api/0/info`. -- Для full-stack сценария CT создаётся автоматически через `pct create`. +- Для полного сценария CT создаётся автоматически через `pct create`. +- На Windows/RDP host развёрнуты AFK/window watchers, browser domain collector, DLP endpoint collector и worktime session collector. +- Проверочный JSON-отчёт Windows playbook должен иметь `overallOk=true`. diff --git a/ansible/deploy_aw_pfsense_poller.yml b/ansible/deploy_aw_pfsense_poller.yml index 3eea27c..34aa29d 100644 --- a/ansible/deploy_aw_pfsense_poller.yml +++ b/ansible/deploy_aw_pfsense_poller.yml @@ -1,5 +1,5 @@ --- -- name: Deploy pfSense ActivityWatch poller +- name: Развернуть pfSense ActivityWatch poller hosts: aw_pfsense_pollers become: true gather_facts: true @@ -10,14 +10,14 @@ aw_pfsense_service_name: "aw-pfsense-poller.service" tasks: - - name: Install required packages + - name: Установить обязательные пакеты ansible.builtin.apt: name: - python3 state: present update_cache: true - - name: Ensure directories exist + - name: Создать каталоги ansible.builtin.file: path: "{{ item }}" state: directory @@ -26,29 +26,29 @@ - "{{ aw_pfsense_install_root }}" - "{{ aw_pfsense_config_dir }}" - - name: Install pfSense poller script + - name: Установить скрипт pfSense poller ansible.builtin.copy: src: "{{ aw_repo_root }}/pfsense/pfsense-aw-poller.py" dest: "{{ aw_pfsense_install_root }}/pfsense-aw-poller.py" mode: "0755" - - name: Install systemd service + - name: Установить systemd service ansible.builtin.copy: src: "{{ aw_repo_root }}/pfsense/pfsense-aw-poller.service" dest: "/etc/systemd/system/{{ aw_pfsense_service_name }}" mode: "0644" notify: - - Reload systemd + - Перезагрузить systemd - - name: Write pfSense poller config + - name: Записать конфигурацию pfSense poller ansible.builtin.copy: dest: "{{ aw_pfsense_config_dir }}/poller.json" mode: "0600" content: "{{ aw_pfsense_poller_config | to_nice_json }}" notify: - - Restart pfSense poller + - Перезапустить pfSense poller - - name: Enable and start pfSense poller + - name: Включить и запустить pfSense poller ansible.builtin.systemd: name: "{{ aw_pfsense_service_name }}" enabled: true @@ -56,11 +56,11 @@ daemon_reload: true handlers: - - name: Reload systemd + - name: Перезагрузить systemd ansible.builtin.systemd: daemon_reload: true - - name: Restart pfSense poller + - name: Перезапустить pfSense poller ansible.builtin.systemd: name: "{{ aw_pfsense_service_name }}" state: restarted diff --git a/ansible/deploy_aw_server.yml b/ansible/deploy_aw_server.yml index d5f7fda..37143de 100644 --- a/ansible/deploy_aw_server.yml +++ b/ansible/deploy_aw_server.yml @@ -1,5 +1,5 @@ --- -- name: Deploy AWatch-rus server +- name: Развернуть сервер AWatch-rus hosts: aw_server become: true gather_facts: true @@ -9,22 +9,29 @@ aw_release_dir: "{{ aw_release_root }}/{{ aw_server_version }}" aw_archive_path: "/tmp/activitywatch-{{ aw_server_version }}.zip" aw_bootstrap_dir: "/tmp/aw-rus-bootstrap" + aw_release_install_dir: "{{ aw_release_root }}/aw-server-rust-{{ aw_server_version }}" aw_ru_patch_cache_bust: "{{ lookup('file', aw_repo_root + '/aw-server/aw-ru-patch.js') | hash('sha1') | truncate(12, true, '') }}" aw_sw_cleanup_cache_bust: "{{ lookup('file', aw_repo_root + '/aw-server/aw-sw-cleanup.js') | hash('sha1') | truncate(12, true, '') }}" - aw_host_groups_cache_bust: "{{ lookup('file', aw_repo_root + '/aw-server/aw-host-groups.json') | hash('sha1') | truncate(12, true, '') }}" aw_worktime_classes: "{{ lookup('file', aw_repo_root + '/aw-server/settings/classes-worktime.json') | from_json }}" aw_default_views: "{{ lookup('file', aw_repo_root + '/aw-server/settings/views-default.json') | from_json }}" tasks: - - name: Install base packages + - name: Установить базовые пакеты ansible.builtin.apt: name: - curl + - rsync - unzip state: present update_cache: true - - name: Ensure service account exists + - name: Создать системную группу сервиса + ansible.builtin.group: + name: "{{ aw_server_group }}" + system: true + state: present + + - name: Создать системную учётную запись сервиса ansible.builtin.user: name: "{{ aw_server_user }}" group: "{{ aw_server_group }}" @@ -33,7 +40,25 @@ system: true create_home: false - - name: Ensure required directories + - name: Создать обязательные каталоги + ansible.builtin.file: + path: "{{ item }}" + state: directory + mode: "0755" + loop: + - "{{ aw_release_root }}" + - "{{ aw_release_dir }}" + - "{{ aw_release_install_dir }}" + - /opt/activitywatch + - /opt/activitywatch/bin + - "{{ aw_server_webui_dir }}" + - "{{ aw_server_webui_dir }}/js" + - "{{ aw_server_data_dir }}" + - "{{ aw_server_log_dir }}" + - /etc/activitywatch + - "{{ aw_bootstrap_dir }}" + + - name: Настроить каталоги ActivityWatch с владельцем сервиса ansible.builtin.file: path: "{{ item }}" state: directory @@ -41,103 +66,188 @@ group: "{{ aw_server_group }}" mode: "0755" loop: + - /opt/activitywatch + - /opt/activitywatch/bin - "{{ aw_release_root }}" - "{{ aw_release_dir }}" + - "{{ aw_release_install_dir }}" - "{{ aw_server_webui_dir }}" + - "{{ aw_server_webui_dir }}/js" - "{{ aw_server_data_dir }}" - "{{ aw_server_log_dir }}" - - /etc/activitywatch - - "{{ aw_bootstrap_dir }}" - - name: Download ActivityWatch release archive + - name: Скачать архив релиза ActivityWatch ansible.builtin.get_url: url: "{{ aw_server_download_url }}" dest: "{{ aw_archive_path }}" mode: "0644" - - name: Unpack ActivityWatch release + - name: Распаковать релиз ActivityWatch ansible.builtin.unarchive: src: "{{ aw_archive_path }}" dest: "{{ aw_release_dir }}" remote_src: true extra_opts: ["-o"] - - name: Discover extracted AW directory + - name: Найти распакованный каталог ActivityWatch ansible.builtin.find: paths: "{{ aw_release_dir }}" file_type: directory patterns: "activitywatch*" register: aw_release_find - - name: Set release extracted path - ansible.builtin.set_fact: - aw_release_extracted: "{{ (aw_release_find.files | sort(attribute='path') | map(attribute='path') | list | first) }}" + - name: Найти бинарный файл AW server + ansible.builtin.find: + paths: "{{ aw_release_dir }}" + file_type: file + patterns: + - aw-server-rust + - aw-server + register: aw_server_binary_find - - name: Verify extracted directory exists + - name: Найти каталог WebUI + ansible.builtin.find: + paths: "{{ aw_release_dir }}" + file_type: directory + patterns: + - aw-webui + - webui + register: aw_webui_dir_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: Проверить, что компоненты релиза найдены ansible.builtin.assert: that: - aw_release_extracted is defined - aw_release_extracted | length > 0 - fail_msg: "Cannot locate extracted ActivityWatch release directory." + - 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: Sync release content to /opt/activitywatch + - name: Создать каталог установленного релиза + ansible.builtin.file: + path: "{{ aw_release_install_dir }}" + state: directory + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + mode: "0755" + + - name: Установить бинарный файл AW server + ansible.builtin.copy: + remote_src: true + src: "{{ aw_server_binary_path }}" + dest: "{{ aw_release_install_dir }}/aw-server-rust" + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + mode: "0755" + + - name: Создать ссылку на активный бинарный файл AW server + ansible.builtin.file: + src: "{{ aw_release_install_dir }}/aw-server-rust" + dest: /opt/activitywatch/bin/aw-server-rust + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + state: link + force: true + + - name: Синхронизировать WebUI в RU каталог ansible.builtin.command: - cmd: "rsync -a --delete {{ aw_release_extracted }}/ /opt/activitywatch/" + cmd: "rsync -a {{ aw_webui_source_path }}/ {{ aw_server_webui_dir }}/" - - name: Copy bootstrap files from repository + - name: Настроить владельца файлов /opt/activitywatch + ansible.builtin.file: + path: /opt/activitywatch + state: directory + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + recurse: true + + - name: Установить systemd service из шаблона репозитория + ansible.builtin.copy: + dest: /etc/systemd/system/activitywatch-server.service + mode: "0644" + content: >- + {{ + lookup('file', aw_repo_root + '/aw-server/activitywatch-server.service') + | replace('__AW_SERVER_USER__', aw_server_user) + | replace('__AW_SERVER_GROUP__', aw_server_group) + | replace('__AW_SERVER_DATA_DIR__', aw_server_data_dir) + }} + notify: + - Перезагрузить 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/activitywatch-server.service", dest: "/etc/systemd/system/activitywatch-server.service", mode: "0644" } - { src: "{{ aw_repo_root }}/aw-server/aw-ru-patch.js", dest: "{{ aw_server_webui_dir }}/js/ru-patch-v5.js", mode: "0644" } - { src: "{{ aw_repo_root }}/aw-server/aw-sw-cleanup.js", dest: "{{ aw_server_webui_dir }}/js/sw-cleanup.js", mode: "0644" } - { src: "{{ aw_repo_root }}/aw-server/aw-host-groups.json", dest: "{{ aw_server_webui_dir }}/js/aw-host-groups.json", mode: "0644" } - notify: - - Reload systemd - - Restart activitywatch - - name: Copy WebUI index template from installed distribution - ansible.builtin.copy: - remote_src: true - src: "/opt/activitywatch/aw-webui/index.html" - dest: "{{ aw_server_webui_dir }}/index.html" - mode: "0644" + - name: Проверить наличие index.html после копирования + ansible.builtin.stat: + path: "{{ aw_server_webui_dir }}/index.html" + register: aw_webui_ru_index - - name: Insert RU patch scripts into index.html + - name: Проверить, что index.html доступен для RU patch + ansible.builtin.assert: + that: + - aw_webui_ru_index.stat.exists + fail_msg: "Не найден index.html WebUI для применения RU patch." + + - name: Удалить старые теги RU patch из index.html + ansible.builtin.replace: + path: "{{ aw_server_webui_dir }}/index.html" + regexp: ']+(?: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: Insert RU patch loader before body end + - name: Добавить загрузчик RU patch перед закрытием body ansible.builtin.replace: path: "{{ aw_server_webui_dir }}/index.html" regexp: '' replace: '' - - name: Write /etc/activitywatch/aw-server.env + - name: Записать /etc/activitywatch/aw-server.env ansible.builtin.copy: dest: /etc/activitywatch/aw-server.env mode: "0640" + owner: root + group: root content: | - AW_SERVER_HOST={{ aw_server_bind_host }} + AW_SERVER_BIND_HOST={{ aw_server_bind_host }} AW_SERVER_PORT={{ aw_server_port }} - AW_DATA_DIR={{ aw_server_data_dir }} - AW_LOG_DIR={{ aw_server_log_dir }} - AW_WEBUI_DIR={{ aw_server_webui_dir }} + AW_SERVER_DATA_DIR={{ aw_server_data_dir }} + AW_SERVER_LOG_DIR={{ aw_server_log_dir }} + AW_SERVER_WEBUI_DIR={{ aw_server_webui_dir }} AW_SERVER_USER={{ aw_server_user }} AW_SERVER_GROUP={{ aw_server_group }} - - name: Enable and start service + - name: Включить и запустить сервис ansible.builtin.systemd: name: activitywatch-server.service enabled: true state: restarted daemon_reload: true - - name: Wait for API + - name: Дождаться ответа API ansible.builtin.uri: url: "http://127.0.0.1:{{ aw_server_port }}/api/0/info" method: GET @@ -147,25 +257,25 @@ delay: 3 until: aw_api.status == 200 - - name: Apply baseline worktime settings (classes) + - name: Применить базовые worktime settings (classes) ansible.builtin.uri: url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/classes" method: POST body: "{{ aw_worktime_classes }}" body_format: json - status_code: 201 + status_code: [200, 201] when: aw_apply_worktime_settings | default(false) | bool - - name: Apply baseline views (include DLP and worktime) + - name: Применить базовые views для DLP и worktime ansible.builtin.uri: url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/views" method: POST body: "{{ aw_default_views }}" body_format: json - status_code: 201 + status_code: [200, 201] when: aw_apply_worktime_settings | default(false) | bool - - name: Derive worktime durationDefault from aw_worktime_from/to + - name: Вычислить worktime durationDefault из aw_worktime_from/to ansible.builtin.set_fact: aw_worktime_from_h: "{{ (aw_worktime_from | default('08:00')).split(':')[0] | int }}" aw_worktime_from_m: "{{ (aw_worktime_from | default('08:00')).split(':')[1] | int }}" @@ -182,7 +292,7 @@ }} when: aw_apply_worktime_settings | default(false) | bool - - name: Normalize derived durationDefault for overnight shifts + - name: Нормализовать durationDefault для ночных смен ansible.builtin.set_fact: aw_worktime_duration_default_effective: >- {{ @@ -192,15 +302,15 @@ }} when: aw_apply_worktime_settings | default(false) | bool - - name: Validate derived durationDefault is sane + - name: Проверить корректность durationDefault ansible.builtin.assert: that: - aw_worktime_duration_default_effective | int > 0 - aw_worktime_duration_default_effective | int <= 86400 - fail_msg: "Invalid worktime window: {{ aw_worktime_from }}..{{ aw_worktime_to }}" + fail_msg: "Некорректный интервал рабочего времени: {{ aw_worktime_from }}..{{ aw_worktime_to }}" when: aw_apply_worktime_settings | default(false) | bool - - name: Apply baseline worktime period (startOfDay) + - name: Применить базовый период worktime (startOfDay) ansible.builtin.uri: url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/startOfDay" method: POST @@ -209,7 +319,7 @@ status_code: 200 when: aw_apply_worktime_settings | default(false) | bool - - name: Apply baseline worktime period (durationDefault seconds) + - name: Применить базовый период worktime (durationDefault seconds) ansible.builtin.uri: url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/durationDefault" method: POST @@ -219,11 +329,11 @@ when: aw_apply_worktime_settings | default(false) | bool handlers: - - name: Reload systemd + - name: Перезагрузить systemd ansible.builtin.systemd: daemon_reload: true - - name: Restart activitywatch + - name: Перезапустить activitywatch ansible.builtin.systemd: name: activitywatch-server.service state: restarted diff --git a/ansible/deploy_aw_windows_phase2.yml b/ansible/deploy_aw_windows_phase2.yml index c772f44..48fa3d8 100644 --- a/ansible/deploy_aw_windows_phase2.yml +++ b/ansible/deploy_aw_windows_phase2.yml @@ -1,13 +1,17 @@ --- -- name: Deploy AWatch-rus Windows phase2 collectors +- name: Развернуть Windows/RDP phase-2 collector'ы AWatch-rus hosts: aw_windows gather_facts: false vars: - aw_windows_repo_root: "/home/igor/tmp/AWatch-rus" + aw_windows_repo_root: "{{ playbook_dir | dirname }}" aw_windows_deploy_root: "C:\\Deploy\\AWatch-rus" + aw_windows_server_scheme: "http" aw_windows_server_host: "10.10.10.13" aw_windows_server_port: 5600 + aw_windows_package_version: "v0.13.2" + aw_windows_package_url: "https://github.com/ActivityWatch/activitywatch/releases/download/v0.13.2/activitywatch-v0.13.2-windows-x86_64.zip" + aw_windows_package_zip_path: "" aw_windows_domain: "SHARKON2025" aw_windows_users: - user1 @@ -35,22 +39,24 @@ aw_windows_recovery_task_name: "ActivityWatch Recovery" aw_windows_force_task_restart: true aw_windows_api_smoke_check_enabled: true - aw_windows_api_smoke_check_bucket: "aw-watcher-afk_SHARKON2025" + aw_windows_api_smoke_check_bucket: "" aw_windows_api_smoke_check_limit: 10 + aw_windows_fail_on_validation_error: true tasks: - - name: Validate required variables + - name: Проверить обязательные переменные ansible.builtin.assert: that: - aw_windows_server_host is defined - aw_windows_server_port is defined + - aw_windows_server_scheme is defined - aw_windows_domain is defined - aw_windows_users_effective | length > 0 - aw_windows_install_root is defined - aw_windows_state_root is defined - fail_msg: "Missing required Windows deployment variables." + fail_msg: "Не заданы обязательные переменные Windows-развёртывания." - - name: Ensure deploy directories exist + - name: Создать каталоги развёртывания ansible.windows.win_file: path: "{{ item }}" state: directory @@ -58,7 +64,7 @@ - "{{ aw_windows_deploy_root }}" - "{{ aw_windows_deploy_root }}\\windows" - - name: Upload Windows deployment toolkit + - name: Загрузить Windows toolkit развёртывания ansible.windows.win_copy: src: "{{ aw_windows_repo_root }}/windows/{{ item }}" dest: "{{ aw_windows_deploy_root }}\\windows\\{{ item }}" @@ -67,6 +73,7 @@ - ActivityWatch.Windows.Common.psm1 - browser-domains-native-collector.ps1 - dlp-endpoint-signals-collector.ps1 + - worktime-session-collector.ps1 - deploy-domain-users.ps1 - deploy-ensemble.ps1 - hardening-recovery.ps1 @@ -74,24 +81,23 @@ - web-category-rules.example.json - dlp-policy.example.json - - name: Upload user list for domain deploy + - name: Загрузить список пользователей для доменного развёртывания ansible.windows.win_copy: dest: "{{ aw_windows_deploy_root }}\\windows\\users.txt" content: | - {% for user in aw_windows_users -%} - {{ user }} - {% endfor -%} - {% for user in aw_windows_extra_users -%} + {% for user in aw_windows_users_effective -%} {{ user }} {% endfor -%} - - name: Run phase2 ensemble deployment + - name: Запустить phase-2 ensemble развёртывание ansible.windows.win_powershell: script: | $ErrorActionPreference = 'Stop' $params = @{ + ServerScheme = "{{ aw_windows_server_scheme }}" ServerHost = "{{ aw_windows_server_host }}" ServerPort = {{ aw_windows_server_port }} + Version = "{{ aw_windows_package_version }}" Domain = "{{ aw_windows_domain }}" UserListPath = "{{ aw_windows_deploy_root }}\windows\users.txt" InstallRoot = "{{ aw_windows_install_root }}" @@ -106,12 +112,18 @@ CustomRulesPath = "{{ aw_windows_rules_path }}" CustomPolicyPath = "{{ aw_windows_policy_path }}" } + {% if (aw_windows_package_url | default('') | string | length) > 0 %} + $params.PackageUrl = "{{ aw_windows_package_url }}" + {% endif %} + {% if (aw_windows_package_zip_path | default('') | string | length) > 0 %} + $params.PackageZipPath = "{{ aw_windows_package_zip_path }}" + {% endif %} {% if aw_windows_skip_hardening | bool %} $params.SkipHardening = $true {% endif %} & "{{ aw_windows_deploy_root }}\windows\deploy-ensemble.ps1" @params - - name: Force start ActivityWatch recovery and launch tasks + - name: Принудительно запустить ActivityWatch recovery и launch tasks when: aw_windows_force_task_restart | bool ansible.windows.win_powershell: script: | @@ -121,11 +133,33 @@ Where-Object TaskName -like "{{ aw_windows_launch_task_pattern }}" | ForEach-Object { Start-ScheduledTask -TaskName $_.TaskName } - - name: Wait for fresh AFK events to appear on AW server - when: aw_windows_api_smoke_check_enabled | bool + - name: Получить Windows hostname для AW smoke-check bucket + when: + - aw_windows_api_smoke_check_enabled | bool + - aw_windows_afk_enabled | bool + ansible.windows.win_command: powershell.exe -NoProfile -Command "$env:COMPUTERNAME" + register: aw_windows_hostname_result + changed_when: false + + - name: Вычислить AW AFK smoke-check bucket + when: + - aw_windows_api_smoke_check_enabled | bool + - aw_windows_afk_enabled | bool + ansible.builtin.set_fact: + aw_windows_api_smoke_check_bucket_effective: >- + {{ + aw_windows_api_smoke_check_bucket + if (aw_windows_api_smoke_check_bucket | default('') | string | length) > 0 + else 'aw-watcher-afk_' ~ (aw_windows_hostname_result.stdout | trim) + }} + + - name: Дождаться свежих AFK событий на AW server + when: + - aw_windows_api_smoke_check_enabled | bool + - aw_windows_afk_enabled | bool delegate_to: localhost ansible.builtin.uri: - url: "http://{{ aw_windows_server_host }}:{{ aw_windows_server_port }}/api/0/buckets/{{ aw_windows_api_smoke_check_bucket }}/events?limit={{ aw_windows_api_smoke_check_limit }}" + url: "{{ aw_windows_server_scheme }}://{{ aw_windows_server_host }}:{{ aw_windows_server_port }}/api/0/buckets/{{ aw_windows_api_smoke_check_bucket_effective }}/events?limit={{ aw_windows_api_smoke_check_limit }}" method: GET return_content: true register: aw_windows_api_smoke @@ -141,29 +175,32 @@ retries: 10 delay: 6 - - name: Run validation and store report on target + - name: Выполнить валидацию и сохранить отчёт на целевом Windows host ansible.windows.win_powershell: script: | $ErrorActionPreference = 'Stop' $report = & "{{ aw_windows_deploy_root }}\windows\validate-deployment.ps1" ` -ConfigPath "{{ aw_windows_state_root }}\deployment-config.json" $report | ConvertTo-Json -Depth 12 | Out-File -FilePath "{{ aw_windows_validation_remote_path }}" -Encoding utf8 + if ({{ '$true' if (aw_windows_fail_on_validation_error | bool) else '$false' }} -and -not [bool]$report.overallOk) { + throw "Проверка развёртывания ActivityWatch завершилась ошибкой. Отчёт: {{ aw_windows_validation_remote_path }}" + } - - name: Ensure local validation directory exists + - name: Создать локальный каталог для validation reports ansible.builtin.file: path: "{{ aw_windows_validation_local_dir }}" state: directory mode: "0755" delegate_to: localhost - - name: Fetch validation report + - name: Забрать validation report ansible.builtin.fetch: src: "{{ aw_windows_validation_remote_path }}" - dest: "{{ aw_windows_validation_local_dir }}/" - flat: false + dest: "{{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_phase2_ansible.json" + flat: true - - name: Show report location + - name: Показать путь к отчёту ansible.builtin.debug: msg: - - "Windows phase2 deploy completed on {{ inventory_hostname }}." - - "Validation report: {{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}/C$/Windows/Temp/aw_validate_phase2_ansible.json" + - "Windows phase2 развёртывание завершено на {{ inventory_hostname }}." + - "Отчёт проверки: {{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_phase2_ansible.json" diff --git a/ansible/group_vars/all.example.yml b/ansible/group_vars/all.example.yml index 8a41775..27d5703 100644 --- a/ansible/group_vars/all.example.yml +++ b/ansible/group_vars/all.example.yml @@ -8,17 +8,17 @@ aw_server_log_dir: "/var/log/activitywatch" aw_server_user: "activitywatch" aw_server_group: "activitywatch" -aw_repo_root: "/home/igor/tmp/AWatch-rus" +aw_repo_root: "{{ playbook_dir | dirname }}" -# Optional: apply a baseline worktime-focused categorization and views via AW settings API. -# WARNING: this overwrites existing server-side settings/classes/views. +# Опционально: применить базовые категории и views для рабочего времени через AW settings API. +# Внимание: это перезаписывает существующие server-side settings/classes/views. aw_apply_worktime_settings: false -# Optional defaults for the worktime period in Web UI. -# startOfDay controls day-boundary and default report window start. -# durationDefault controls default time range (seconds) shown in UI. +# Опциональные значения периода рабочего времени в Web UI. +# startOfDay задаёт границу дня и стартовое время окна отчёта. +# durationDefault задаёт диапазон по умолчанию в секундах. # -# Recommended: set worktime window explicitly and let the playbook derive duration. +# Рекомендуется явно задать рабочий интервал и дать playbook вычислить duration. aw_worktime_from: "08:00" aw_worktime_to: "17:00" aw_worktime_start_of_day: "{{ aw_worktime_from }}" diff --git a/ansible/group_vars/windows.example.yml b/ansible/group_vars/windows.example.yml index 3b6a958..1255298 100644 --- a/ansible/group_vars/windows.example.yml +++ b/ansible/group_vars/windows.example.yml @@ -1,7 +1,11 @@ -aw_windows_repo_root: "/home/igor/tmp/AWatch-rus" +aw_windows_repo_root: "{{ playbook_dir | dirname }}" aw_windows_deploy_root: "C:\\Deploy\\AWatch-rus" +aw_windows_server_scheme: "http" aw_windows_server_host: "10.10.10.13" aw_windows_server_port: 5600 +aw_windows_package_version: "v0.13.2" +aw_windows_package_url: "https://github.com/ActivityWatch/activitywatch/releases/download/v0.13.2/activitywatch-v0.13.2-windows-x86_64.zip" +aw_windows_package_zip_path: "" aw_windows_domain: "SHARKON2025" aw_windows_users: - user1 @@ -31,3 +35,10 @@ aw_windows_policy_path: "{{ aw_windows_deploy_root }}\\windows\\dlp-policy.examp aw_windows_validation_remote_path: "C:\\Windows\\Temp\\aw_validate_phase2_ansible.json" aw_windows_validation_local_dir: "/tmp/aw-rus-validation" +aw_windows_fail_on_validation_error: true + +# По умолчанию AFK bucket вычисляется как aw-watcher-afk_. +# Задайте явное значение только если watcher пишет в нестандартный bucket. +aw_windows_api_smoke_check_enabled: true +aw_windows_api_smoke_check_bucket: "" +aw_windows_api_smoke_check_limit: 10 diff --git a/ansible/install_full_stack.yml b/ansible/install_full_stack.yml index 18b4052..0af14c9 100644 --- a/ansible/install_full_stack.yml +++ b/ansible/install_full_stack.yml @@ -1,14 +1,14 @@ --- -# Full-stack installer for AWatch-rus. -# Runs end-to-end rollout in one command: -# 1) Proxmox CT provision + AW bootstrap (if [proxmox] exists in inventory) -# 2) AW server deploy on [aw_server] hosts -# 3) Windows phase2 rollout on [aw_windows] hosts -# 4) pfSense poller deploy on [aw_pfsense_pollers] hosts +# Полный установщик AWatch-rus. +# Выполняет развёртывание одной командой: +# 1) создание Proxmox CT + bootstrap AW (если в inventory есть [proxmox]) +# 2) развёртывание AW server на хостах [aw_server] +# 3) развёртывание Windows/RDP collector'ов на [aw_windows] +# 4) развёртывание pfSense poller'а на [aw_pfsense_pollers] # -# Notes: -# - Keep only relevant inventory groups filled for your environment. -# - Plays with unmatched host groups are skipped automatically by Ansible. +# Примечания: +# - Заполняйте только нужные группы inventory для своего окружения. +# - Play без совпадающих host groups Ansible пропускает автоматически. - import_playbook: provision_proxmox_ct_and_deploy_aw.yml - import_playbook: deploy_aw_server.yml diff --git a/ansible/inventory.example.ini b/ansible/inventory.example.ini index da44e90..e1c77ff 100644 --- a/ansible/inventory.example.ini +++ b/ansible/inventory.example.ini @@ -5,5 +5,8 @@ pve-main ansible_host=192.168.10.2 ansible_user=root ansible_port=22 aw-ct ansible_host=10.20.30.13 ansible_user=root ansible_port=22 [aw_windows] -# NOTE: in RU-localized installs this account is often "Администратор" instead of "Administrator". +# Примечание: в русифицированных 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 + +[aw_pfsense_pollers] +# pfsense-poller1 ansible_host=192.168.100.30 ansible_user=root ansible_port=22 diff --git a/ansible/provision_proxmox_ct_and_deploy_aw.yml b/ansible/provision_proxmox_ct_and_deploy_aw.yml index 2a932a7..dd9dadf 100644 --- a/ansible/provision_proxmox_ct_and_deploy_aw.yml +++ b/ansible/provision_proxmox_ct_and_deploy_aw.yml @@ -1,5 +1,5 @@ --- -- name: Provision single Proxmox CT and deploy AWatch-rus +- name: Создать один Proxmox CT и развернуть AWatch-rus hosts: proxmox gather_facts: false @@ -17,7 +17,7 @@ - settings/views-default.json tasks: - - name: Execute single-CT provisioning workflow + - name: Выполнить workflow создания одного CT ansible.builtin.include_tasks: tasks/provision_ct_and_deploy_aw.yml vars: ct_id: "{{ proxmox_ct_id }}" diff --git a/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml b/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml index b228f66..44564da 100644 --- a/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml +++ b/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml @@ -1,5 +1,5 @@ --- -- name: Provision Proxmox CT matrix and deploy AWatch-rus with RU patch +- name: Создать матрицу Proxmox CT и развернуть AWatch-rus с RU patch hosts: proxmox gather_facts: false @@ -17,14 +17,14 @@ - settings/views-default.json tasks: - - name: Validate CT matrix is provided + - name: Проверить, что матрица CT задана ansible.builtin.assert: that: - proxmox_ct_matrix is defined - proxmox_ct_matrix | length > 0 - fail_msg: "Define proxmox_ct_matrix in group_vars/proxmox-matrix.yml" + fail_msg: "Задайте proxmox_ct_matrix в group_vars/proxmox-matrix.yml" - - name: Execute provisioning workflow for each CT + - name: Выполнить workflow создания для каждого CT ansible.builtin.include_tasks: tasks/provision_ct_and_deploy_aw.yml vars: ct_id: "{{ item.id }}" diff --git a/ansible/tasks/provision_ct_and_deploy_aw.yml b/ansible/tasks/provision_ct_and_deploy_aw.yml index 4cceb17..975e6ef 100644 --- a/ansible/tasks/provision_ct_and_deploy_aw.yml +++ b/ansible/tasks/provision_ct_and_deploy_aw.yml @@ -1,5 +1,5 @@ --- -- name: Validate required per-CT variables +- name: Проверить обязательные переменные CT ansible.builtin.assert: that: - ct_id is defined @@ -27,14 +27,14 @@ - aw_server_log_dir is defined - aw_server_user is defined - aw_server_group is defined - fail_msg: "Missing required variables for CT provisioning/deploy." + fail_msg: "Не заданы обязательные переменные для создания CT и развёртывания." -- name: Build CT network string +- name: Сформировать сетевую строку CT ansible.builtin.set_fact: ct_net0: >- name=eth0,bridge={{ ct_bridge }},ip={{ ct_ip }},gw={{ ct_gw }}{% if (ct_vlan | default('') | string | length) > 0 %},tag={{ ct_vlan }}{% endif %} -- name: Check whether CT already exists +- name: Проверить, существует ли CT ansible.builtin.command: argv: - pct @@ -44,7 +44,7 @@ failed_when: false changed_when: false -- name: Create CT when absent +- name: Создать CT, если он отсутствует ansible.builtin.command: argv: - pct @@ -78,8 +78,9 @@ - --ostype - debian when: ct_status_check.rc != 0 + no_log: true -- name: Check current CT runtime state +- name: Проверить текущее состояние CT ansible.builtin.command: argv: - pct @@ -88,7 +89,7 @@ register: ct_runtime_status changed_when: false -- name: Start CT when stopped +- name: Запустить CT, если он остановлен ansible.builtin.command: argv: - pct @@ -96,20 +97,23 @@ - "{{ ct_id }}" when: "'stopped' in ct_runtime_status.stdout" -- name: Ensure bootstrap directory on Proxmox host +- name: Создать bootstrap каталог на Proxmox host ansible.builtin.file: - path: "{{ proxmox_bootstrap_dir }}" + path: "{{ item }}" state: directory mode: "0700" + loop: + - "{{ proxmox_bootstrap_dir }}" + - "{{ proxmox_bootstrap_dir }}/settings" -- name: Copy AW bootstrap files to Proxmox host temp +- name: Скопировать AW bootstrap файлы во временный каталог Proxmox host ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/{{ item }}" dest: "{{ proxmox_bootstrap_dir }}/{{ item }}" mode: "0644" loop: "{{ aw_bootstrap_files }}" -- name: Bootstrap CT OS dependencies +- name: Установить базовые зависимости ОС внутри CT ansible.builtin.command: argv: - pct @@ -122,12 +126,16 @@ set -euo pipefail export DEBIAN_FRONTEND=noninteractive apt-get update - apt-get install -y curl ca-certificates bash unzip xz-utils jq rsync openssh-server - mkdir -p /root/bootstrap /etc/activitywatch + apt-get install -y curl ca-certificates bash unzip xz-utils jq rsync openssh-server python3 + mkdir -p /root/bootstrap/settings /etc/activitywatch systemctl enable ssh || true systemctl restart ssh || true + register: ct_bootstrap_result + retries: 10 + delay: 6 + until: ct_bootstrap_result.rc == 0 -- name: Push bootstrap files into CT +- name: Передать bootstrap файлы внутрь CT ansible.builtin.command: argv: - pct @@ -137,7 +145,7 @@ - "/root/bootstrap/{{ item }}" loop: "{{ aw_bootstrap_files }}" -- name: Write AW server env file on Proxmox host temp +- name: Записать AW server env во временный каталог Proxmox host ansible.builtin.copy: dest: "{{ proxmox_bootstrap_dir }}/aw-server.env" mode: "0600" @@ -151,8 +159,9 @@ AW_SERVER_LOG_DIR={{ aw_server_log_dir }} AW_SERVER_USER={{ aw_server_user }} AW_SERVER_GROUP={{ aw_server_group }} + no_log: true -- name: Push AW server env into CT +- name: Передать AW server env внутрь CT ansible.builtin.command: argv: - pct @@ -160,8 +169,9 @@ - "{{ ct_id }}" - "{{ proxmox_bootstrap_dir }}/aw-server.env" - /etc/activitywatch/aw-server.env + no_log: true -- name: Set mode for env inside CT +- name: Настроить права env файла внутри CT ansible.builtin.command: argv: - pct @@ -172,7 +182,7 @@ - "0600" - /etc/activitywatch/aw-server.env -- name: Install server and apply RU patch inside CT +- name: Установить сервер и применить RU patch внутри CT ansible.builtin.command: argv: - pct @@ -188,7 +198,7 @@ bash /root/bootstrap/apply_webui_ru_patch.sh systemctl restart activitywatch-server.service -- name: Validate AW API from inside CT +- name: Проверить AW API изнутри CT ansible.builtin.command: argv: - pct @@ -199,7 +209,7 @@ - -lc - "curl -fsS http://127.0.0.1:{{ aw_server_port }}/api/0/info >/dev/null" -- name: Validate RU patch hooks in index +- name: Проверить hooks RU patch в index.html ansible.builtin.command: argv: - pct @@ -210,8 +220,8 @@ - -lc - "grep -q 'ru-patch-v5.js' {{ aw_server_webui_dir }}/index.html && grep -q 'sw-cleanup.js' {{ aw_server_webui_dir }}/index.html" -- name: Show final endpoint +- name: Показать итоговый endpoint ansible.builtin.debug: msg: - - "CT {{ ct_id }} is provisioned and configured." - - "ActivityWatch endpoint: http://{{ ct_ip | regex_replace('/[0-9]+$', '') }}:{{ aw_server_port }}" + - "CT {{ ct_id }} создан и настроен." + - "Endpoint ActivityWatch: http://{{ ct_ip | regex_replace('/[0-9]+$', '') }}:{{ aw_server_port }}" diff --git a/install-kit-awindows-20260427-211240/ansible/README.md b/install-kit-awindows-20260427-211240/ansible/README.md index b8a82d7..a8c77b6 100644 --- a/install-kit-awindows-20260427-211240/ansible/README.md +++ b/install-kit-awindows-20260427-211240/ansible/README.md @@ -1,82 +1,101 @@ # Ansible ensemble for AWatch-rus -Эта директория содержит Ansible-ensemble для двух сценариев: +Эта директория содержит Ansible-ensemble для полного развёртывания AWatch-rus: - деплой на уже существующий Debian host/CT; -- полный цикл с нуля в Proxmox: создание CT + bootstrap + установка ActivityWatch + RU patch. -- централизованный деплой Windows phase-2 collectors по WinRM. -- deployment внешнего pfSense poller'а на Debian/Ubuntu utility VM. +- полный цикл с нуля в Proxmox: создание CT + bootstrap + установка ActivityWatch + RU patch; +- централизованное развёртывание Windows phase-2 collector'ов по WinRM; +- развёртывание внешнего pfSense poller'а на Debian/Ubuntu utility VM. ## Файлы -- `/home/igor/tmp/AWatch-rus/ansible/deploy_aw_server.yml` — основной playbook. -- `/home/igor/tmp/AWatch-rus/ansible/provision_proxmox_ct_and_deploy_aw.yml` — full-stack playbook для Proxmox. -- `/home/igor/tmp/AWatch-rus/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml` — массовый full-stack playbook (несколько CT). -- `/home/igor/tmp/AWatch-rus/ansible/deploy_aw_windows_phase2.yml` — WinRM playbook для развёртывания phase-2 Windows collector'ов. -- `/home/igor/tmp/AWatch-rus/ansible/deploy_aw_pfsense_poller.yml` — deployment pfSense poller'а. -- `/home/igor/tmp/AWatch-rus/ansible/inventory.example.ini` — шаблон inventory. -- `/home/igor/tmp/AWatch-rus/ansible/group_vars/all.example.yml` — шаблон переменных. -- `/home/igor/tmp/AWatch-rus/ansible/group_vars/proxmox.example.yml` — шаблон переменных CT в Proxmox. -- `/home/igor/tmp/AWatch-rus/ansible/group_vars/proxmox-matrix.example.yml` — шаблон матрицы CT. -- `/home/igor/tmp/AWatch-rus/ansible/group_vars/windows.example.yml` — шаблон переменных Windows phase-2. -- `/home/igor/tmp/AWatch-rus/ansible/group_vars/pfsense-poller.example.yml` — шаблон переменных pfSense poller'а. +- `ansible/deploy_aw_server.yml` — основной playbook для уже существующего Debian/CT host. +- `ansible/provision_proxmox_ct_and_deploy_aw.yml` — полный playbook для Proxmox. +- `ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml` — массовый полный playbook (несколько CT). +- `ansible/deploy_aw_windows_phase2.yml` — WinRM playbook для развёртывания Windows/RDP collector'ов. +- `ansible/deploy_aw_pfsense_poller.yml` — развёртывание pfSense poller'а. +- `ansible/install_full_stack.yml` — полный установочный playbook (оркестратор всех этапов). +- `ansible/inventory.example.ini` — шаблон inventory. +- `ansible/group_vars/*.example.yml` — шаблоны переменных. ## Быстрый запуск 1. Скопируйте шаблоны: - - `cp /home/igor/tmp/AWatch-rus/ansible/inventory.example.ini /home/igor/tmp/AWatch-rus/ansible/inventory.ini` - - `cp /home/igor/tmp/AWatch-rus/ansible/group_vars/all.example.yml /home/igor/tmp/AWatch-rus/ansible/group_vars/all.yml` + - `cp ansible/inventory.example.ini ansible/inventory.ini` + - `cp ansible/group_vars/all.example.yml ansible/group_vars/all.yml` 2. Заполните значения в `inventory.ini` и `group_vars/all.yml`. 3. Запустите: ```bash -cd /home/igor/tmp/AWatch-rus/ansible +cd ansible ansible-playbook -i inventory.ini deploy_aw_server.yml ``` +## Полный установочный playbook (всё за один запуск) + +Если нужно прогнать полный цикл одной командой: + +```bash +cd ansible +ansible-playbook -i inventory.ini install_full_stack.yml +``` + +Что делает: + +- `provision_proxmox_ct_and_deploy_aw.yml` (если есть хосты в группе `[proxmox]`); +- `deploy_aw_server.yml` (группа `[aw_server]`); +- `deploy_aw_windows_phase2.yml` (группа `[aw_windows]`); +- `deploy_aw_pfsense_poller.yml` (группа `[aw_pfsense_pollers]`). + +Пустые группы в `inventory.ini` безопасны: соответствующий play будет пропущен. + ## Полный запуск с нуля в Proxmox 1. Подготовьте inventory и vars: - - `cp /home/igor/tmp/AWatch-rus/ansible/inventory.example.ini /home/igor/tmp/AWatch-rus/ansible/inventory.ini` - - `cp /home/igor/tmp/AWatch-rus/ansible/group_vars/all.example.yml /home/igor/tmp/AWatch-rus/ansible/group_vars/all.yml` - - `cp /home/igor/tmp/AWatch-rus/ansible/group_vars/proxmox.example.yml /home/igor/tmp/AWatch-rus/ansible/group_vars/proxmox.yml` + - `cp ansible/inventory.example.ini ansible/inventory.ini` + - `cp ansible/group_vars/all.example.yml ansible/group_vars/all.yml` + - `cp ansible/group_vars/proxmox.example.yml ansible/group_vars/proxmox.yml` 2. Заполните `group_vars/proxmox.yml` и `group_vars/all.yml`. 3. Запустите playbook: ```bash -cd /home/igor/tmp/AWatch-rus/ansible +cd ansible ansible-playbook -i inventory.ini provision_proxmox_ct_and_deploy_aw.yml ``` ## Массовый запуск (матрица CT) 1. Подготовьте матрицу: - - `cp /home/igor/tmp/AWatch-rus/ansible/group_vars/proxmox-matrix.example.yml /home/igor/tmp/AWatch-rus/ansible/group_vars/proxmox-matrix.yml` + - `cp ansible/group_vars/proxmox-matrix.example.yml ansible/group_vars/proxmox-matrix.yml` 2. Заполните `proxmox-matrix.yml`. 3. Запустите: ```bash -cd /home/igor/tmp/AWatch-rus/ansible +cd ansible ansible-playbook -i inventory.ini provision_proxmox_ct_matrix_and_deploy_aw.yml ``` ## Windows phase-2 rollout (WinRM) 1. Подготовьте inventory и vars: - - `cp /home/igor/tmp/AWatch-rus/ansible/inventory.example.ini /home/igor/tmp/AWatch-rus/ansible/inventory.ini` - - `cp /home/igor/tmp/AWatch-rus/ansible/group_vars/windows.example.yml /home/igor/tmp/AWatch-rus/ansible/group_vars/windows.yml` + - `cp ansible/inventory.example.ini ansible/inventory.ini` + - `cp ansible/group_vars/windows.example.yml ansible/group_vars/windows.yml` 2. Заполните `inventory.ini` (секция `[aw_windows]`) и `group_vars/windows.yml`. + - Для русской локализации Windows часто нужен `ansible_user=Администратор` (а не `Administrator`). + - Если WinRM закрыт, playbook не сможет стартовать и нужно сначала открыть `5985/5986` и `wsman`. 3. Запустите: ```bash -cd /home/igor/tmp/AWatch-rus/ansible +cd ansible ansible-playbook -i inventory.ini deploy_aw_windows_phase2.yml ``` Playbook: -- выгружает `windows/*` toolkit на целевой хост в `C:\Deploy\AWatch-rus\windows`; +- выгружает полный `windows/*` toolkit на целевой хост в `C:\Deploy\AWatch-rus\windows`, включая DLP и `worktime-session-collector.ps1`; - выполняет `deploy-ensemble.ps1` (deploy + hardening/recovery) с phase-2 policy/rules; +- после deploy принудительно запускает `ActivityWatch Recovery` и все `ActivityWatch Launch *` задачи; +- выполняет API smoke-check bucket `aw-watcher-afk_` и ожидает свежие `not-afk` события; - запускает `validate-deployment.ps1`; - забирает JSON-отчёт в локальную директорию (`/tmp/aw-rus-validation` по умолчанию). @@ -87,17 +106,20 @@ Playbook: - `aw_windows_incident_capture_enabled: false` — отключить блок incidentCapture; - `aw_windows_incident_screenshot_enabled: false` — не делать скриншот при DLP-инциденте; - `aw_windows_incident_artifacts_root: 'C:\...\incident-artifacts'` — переопределить путь артефактов; +- `aw_windows_package_version`, `aw_windows_package_url`, `aw_windows_package_zip_path` — версия и источник Windows-пакета ActivityWatch; +- `aw_windows_api_smoke_check_bucket: ""` — автоматически использовать `aw-watcher-afk_`; +- `aw_windows_fail_on_validation_error: true` — завершать playbook ошибкой, если `validate-deployment.ps1` возвращает `overallOk=false`; - `aw_windows_skip_hardening: true` — пропустить `hardening-recovery.ps1` внутри ensemble-скрипта. -## pfSense poller rollout +## Развёртывание pfSense poller 1. Подготовьте vars: - - `cp /home/igor/tmp/AWatch-rus/ansible/group_vars/pfsense-poller.example.yml /home/igor/tmp/AWatch-rus/ansible/group_vars/pfsense-poller.yml` + - `cp ansible/group_vars/pfsense-poller.example.yml ansible/group_vars/pfsense-poller.yml` 2. Добавьте inventory group `[aw_pfsense_pollers]`. 3. Запустите: ```bash -cd /home/igor/tmp/AWatch-rus/ansible +cd ansible ansible-playbook -i inventory.ini deploy_aw_pfsense_poller.yml ``` @@ -116,4 +138,6 @@ Playbook: - Для Web UI используется checksum-based cache-bust для `ru-patch-v5.js` и `sw-cleanup.js`, чтобы браузер не держал старую DLP/русскую статику после деплоя. - На `#/home` Web UI делит хосты на `Windows RDP` и `Virtual servers + Proxmox`. - Выполнена валидация API `http://127.0.0.1:5600/api/0/info`. -- Для full-stack сценария CT создаётся автоматически через `pct create`. +- Для полного сценария CT создаётся автоматически через `pct create`. +- На Windows/RDP host развёрнуты AFK/window watchers, browser domain collector, DLP endpoint collector и worktime session collector. +- Проверочный JSON-отчёт Windows playbook должен иметь `overallOk=true`. diff --git a/install-kit-awindows-20260427-211240/ansible/deploy_aw_pfsense_poller.yml b/install-kit-awindows-20260427-211240/ansible/deploy_aw_pfsense_poller.yml index 3eea27c..34aa29d 100644 --- a/install-kit-awindows-20260427-211240/ansible/deploy_aw_pfsense_poller.yml +++ b/install-kit-awindows-20260427-211240/ansible/deploy_aw_pfsense_poller.yml @@ -1,5 +1,5 @@ --- -- name: Deploy pfSense ActivityWatch poller +- name: Развернуть pfSense ActivityWatch poller hosts: aw_pfsense_pollers become: true gather_facts: true @@ -10,14 +10,14 @@ aw_pfsense_service_name: "aw-pfsense-poller.service" tasks: - - name: Install required packages + - name: Установить обязательные пакеты ansible.builtin.apt: name: - python3 state: present update_cache: true - - name: Ensure directories exist + - name: Создать каталоги ansible.builtin.file: path: "{{ item }}" state: directory @@ -26,29 +26,29 @@ - "{{ aw_pfsense_install_root }}" - "{{ aw_pfsense_config_dir }}" - - name: Install pfSense poller script + - name: Установить скрипт pfSense poller ansible.builtin.copy: src: "{{ aw_repo_root }}/pfsense/pfsense-aw-poller.py" dest: "{{ aw_pfsense_install_root }}/pfsense-aw-poller.py" mode: "0755" - - name: Install systemd service + - name: Установить systemd service ansible.builtin.copy: src: "{{ aw_repo_root }}/pfsense/pfsense-aw-poller.service" dest: "/etc/systemd/system/{{ aw_pfsense_service_name }}" mode: "0644" notify: - - Reload systemd + - Перезагрузить systemd - - name: Write pfSense poller config + - name: Записать конфигурацию pfSense poller ansible.builtin.copy: dest: "{{ aw_pfsense_config_dir }}/poller.json" mode: "0600" content: "{{ aw_pfsense_poller_config | to_nice_json }}" notify: - - Restart pfSense poller + - Перезапустить pfSense poller - - name: Enable and start pfSense poller + - name: Включить и запустить pfSense poller ansible.builtin.systemd: name: "{{ aw_pfsense_service_name }}" enabled: true @@ -56,11 +56,11 @@ daemon_reload: true handlers: - - name: Reload systemd + - name: Перезагрузить systemd ansible.builtin.systemd: daemon_reload: true - - name: Restart pfSense poller + - name: Перезапустить pfSense poller ansible.builtin.systemd: name: "{{ aw_pfsense_service_name }}" state: restarted 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 d5f7fda..37143de 100644 --- a/install-kit-awindows-20260427-211240/ansible/deploy_aw_server.yml +++ b/install-kit-awindows-20260427-211240/ansible/deploy_aw_server.yml @@ -1,5 +1,5 @@ --- -- name: Deploy AWatch-rus server +- name: Развернуть сервер AWatch-rus hosts: aw_server become: true gather_facts: true @@ -9,22 +9,29 @@ aw_release_dir: "{{ aw_release_root }}/{{ aw_server_version }}" aw_archive_path: "/tmp/activitywatch-{{ aw_server_version }}.zip" aw_bootstrap_dir: "/tmp/aw-rus-bootstrap" + aw_release_install_dir: "{{ aw_release_root }}/aw-server-rust-{{ aw_server_version }}" aw_ru_patch_cache_bust: "{{ lookup('file', aw_repo_root + '/aw-server/aw-ru-patch.js') | hash('sha1') | truncate(12, true, '') }}" aw_sw_cleanup_cache_bust: "{{ lookup('file', aw_repo_root + '/aw-server/aw-sw-cleanup.js') | hash('sha1') | truncate(12, true, '') }}" - aw_host_groups_cache_bust: "{{ lookup('file', aw_repo_root + '/aw-server/aw-host-groups.json') | hash('sha1') | truncate(12, true, '') }}" aw_worktime_classes: "{{ lookup('file', aw_repo_root + '/aw-server/settings/classes-worktime.json') | from_json }}" aw_default_views: "{{ lookup('file', aw_repo_root + '/aw-server/settings/views-default.json') | from_json }}" tasks: - - name: Install base packages + - name: Установить базовые пакеты ansible.builtin.apt: name: - curl + - rsync - unzip state: present update_cache: true - - name: Ensure service account exists + - name: Создать системную группу сервиса + ansible.builtin.group: + name: "{{ aw_server_group }}" + system: true + state: present + + - name: Создать системную учётную запись сервиса ansible.builtin.user: name: "{{ aw_server_user }}" group: "{{ aw_server_group }}" @@ -33,7 +40,25 @@ system: true create_home: false - - name: Ensure required directories + - name: Создать обязательные каталоги + ansible.builtin.file: + path: "{{ item }}" + state: directory + mode: "0755" + loop: + - "{{ aw_release_root }}" + - "{{ aw_release_dir }}" + - "{{ aw_release_install_dir }}" + - /opt/activitywatch + - /opt/activitywatch/bin + - "{{ aw_server_webui_dir }}" + - "{{ aw_server_webui_dir }}/js" + - "{{ aw_server_data_dir }}" + - "{{ aw_server_log_dir }}" + - /etc/activitywatch + - "{{ aw_bootstrap_dir }}" + + - name: Настроить каталоги ActivityWatch с владельцем сервиса ansible.builtin.file: path: "{{ item }}" state: directory @@ -41,103 +66,188 @@ group: "{{ aw_server_group }}" mode: "0755" loop: + - /opt/activitywatch + - /opt/activitywatch/bin - "{{ aw_release_root }}" - "{{ aw_release_dir }}" + - "{{ aw_release_install_dir }}" - "{{ aw_server_webui_dir }}" + - "{{ aw_server_webui_dir }}/js" - "{{ aw_server_data_dir }}" - "{{ aw_server_log_dir }}" - - /etc/activitywatch - - "{{ aw_bootstrap_dir }}" - - name: Download ActivityWatch release archive + - name: Скачать архив релиза ActivityWatch ansible.builtin.get_url: url: "{{ aw_server_download_url }}" dest: "{{ aw_archive_path }}" mode: "0644" - - name: Unpack ActivityWatch release + - name: Распаковать релиз ActivityWatch ansible.builtin.unarchive: src: "{{ aw_archive_path }}" dest: "{{ aw_release_dir }}" remote_src: true extra_opts: ["-o"] - - name: Discover extracted AW directory + - name: Найти распакованный каталог ActivityWatch ansible.builtin.find: paths: "{{ aw_release_dir }}" file_type: directory patterns: "activitywatch*" register: aw_release_find - - name: Set release extracted path - ansible.builtin.set_fact: - aw_release_extracted: "{{ (aw_release_find.files | sort(attribute='path') | map(attribute='path') | list | first) }}" + - name: Найти бинарный файл AW server + ansible.builtin.find: + paths: "{{ aw_release_dir }}" + file_type: file + patterns: + - aw-server-rust + - aw-server + register: aw_server_binary_find - - name: Verify extracted directory exists + - name: Найти каталог WebUI + ansible.builtin.find: + paths: "{{ aw_release_dir }}" + file_type: directory + patterns: + - aw-webui + - webui + register: aw_webui_dir_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: Проверить, что компоненты релиза найдены ansible.builtin.assert: that: - aw_release_extracted is defined - aw_release_extracted | length > 0 - fail_msg: "Cannot locate extracted ActivityWatch release directory." + - 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: Sync release content to /opt/activitywatch + - name: Создать каталог установленного релиза + ansible.builtin.file: + path: "{{ aw_release_install_dir }}" + state: directory + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + mode: "0755" + + - name: Установить бинарный файл AW server + ansible.builtin.copy: + remote_src: true + src: "{{ aw_server_binary_path }}" + dest: "{{ aw_release_install_dir }}/aw-server-rust" + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + mode: "0755" + + - name: Создать ссылку на активный бинарный файл AW server + ansible.builtin.file: + src: "{{ aw_release_install_dir }}/aw-server-rust" + dest: /opt/activitywatch/bin/aw-server-rust + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + state: link + force: true + + - name: Синхронизировать WebUI в RU каталог ansible.builtin.command: - cmd: "rsync -a --delete {{ aw_release_extracted }}/ /opt/activitywatch/" + cmd: "rsync -a {{ aw_webui_source_path }}/ {{ aw_server_webui_dir }}/" - - name: Copy bootstrap files from repository + - name: Настроить владельца файлов /opt/activitywatch + ansible.builtin.file: + path: /opt/activitywatch + state: directory + owner: "{{ aw_server_user }}" + group: "{{ aw_server_group }}" + recurse: true + + - name: Установить systemd service из шаблона репозитория + ansible.builtin.copy: + dest: /etc/systemd/system/activitywatch-server.service + mode: "0644" + content: >- + {{ + lookup('file', aw_repo_root + '/aw-server/activitywatch-server.service') + | replace('__AW_SERVER_USER__', aw_server_user) + | replace('__AW_SERVER_GROUP__', aw_server_group) + | replace('__AW_SERVER_DATA_DIR__', aw_server_data_dir) + }} + notify: + - Перезагрузить 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/activitywatch-server.service", dest: "/etc/systemd/system/activitywatch-server.service", mode: "0644" } - { src: "{{ aw_repo_root }}/aw-server/aw-ru-patch.js", dest: "{{ aw_server_webui_dir }}/js/ru-patch-v5.js", mode: "0644" } - { src: "{{ aw_repo_root }}/aw-server/aw-sw-cleanup.js", dest: "{{ aw_server_webui_dir }}/js/sw-cleanup.js", mode: "0644" } - { src: "{{ aw_repo_root }}/aw-server/aw-host-groups.json", dest: "{{ aw_server_webui_dir }}/js/aw-host-groups.json", mode: "0644" } - notify: - - Reload systemd - - Restart activitywatch - - name: Copy WebUI index template from installed distribution - ansible.builtin.copy: - remote_src: true - src: "/opt/activitywatch/aw-webui/index.html" - dest: "{{ aw_server_webui_dir }}/index.html" - mode: "0644" + - name: Проверить наличие index.html после копирования + ansible.builtin.stat: + path: "{{ aw_server_webui_dir }}/index.html" + register: aw_webui_ru_index - - name: Insert RU patch scripts into index.html + - name: Проверить, что index.html доступен для RU patch + ansible.builtin.assert: + that: + - aw_webui_ru_index.stat.exists + fail_msg: "Не найден index.html WebUI для применения RU patch." + + - name: Удалить старые теги RU patch из index.html + ansible.builtin.replace: + path: "{{ aw_server_webui_dir }}/index.html" + regexp: ']+(?: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: Insert RU patch loader before body end + - name: Добавить загрузчик RU patch перед закрытием body ansible.builtin.replace: path: "{{ aw_server_webui_dir }}/index.html" regexp: '' replace: '' - - name: Write /etc/activitywatch/aw-server.env + - name: Записать /etc/activitywatch/aw-server.env ansible.builtin.copy: dest: /etc/activitywatch/aw-server.env mode: "0640" + owner: root + group: root content: | - AW_SERVER_HOST={{ aw_server_bind_host }} + AW_SERVER_BIND_HOST={{ aw_server_bind_host }} AW_SERVER_PORT={{ aw_server_port }} - AW_DATA_DIR={{ aw_server_data_dir }} - AW_LOG_DIR={{ aw_server_log_dir }} - AW_WEBUI_DIR={{ aw_server_webui_dir }} + AW_SERVER_DATA_DIR={{ aw_server_data_dir }} + AW_SERVER_LOG_DIR={{ aw_server_log_dir }} + AW_SERVER_WEBUI_DIR={{ aw_server_webui_dir }} AW_SERVER_USER={{ aw_server_user }} AW_SERVER_GROUP={{ aw_server_group }} - - name: Enable and start service + - name: Включить и запустить сервис ansible.builtin.systemd: name: activitywatch-server.service enabled: true state: restarted daemon_reload: true - - name: Wait for API + - name: Дождаться ответа API ansible.builtin.uri: url: "http://127.0.0.1:{{ aw_server_port }}/api/0/info" method: GET @@ -147,25 +257,25 @@ delay: 3 until: aw_api.status == 200 - - name: Apply baseline worktime settings (classes) + - name: Применить базовые worktime settings (classes) ansible.builtin.uri: url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/classes" method: POST body: "{{ aw_worktime_classes }}" body_format: json - status_code: 201 + status_code: [200, 201] when: aw_apply_worktime_settings | default(false) | bool - - name: Apply baseline views (include DLP and worktime) + - name: Применить базовые views для DLP и worktime ansible.builtin.uri: url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/views" method: POST body: "{{ aw_default_views }}" body_format: json - status_code: 201 + status_code: [200, 201] when: aw_apply_worktime_settings | default(false) | bool - - name: Derive worktime durationDefault from aw_worktime_from/to + - name: Вычислить worktime durationDefault из aw_worktime_from/to ansible.builtin.set_fact: aw_worktime_from_h: "{{ (aw_worktime_from | default('08:00')).split(':')[0] | int }}" aw_worktime_from_m: "{{ (aw_worktime_from | default('08:00')).split(':')[1] | int }}" @@ -182,7 +292,7 @@ }} when: aw_apply_worktime_settings | default(false) | bool - - name: Normalize derived durationDefault for overnight shifts + - name: Нормализовать durationDefault для ночных смен ansible.builtin.set_fact: aw_worktime_duration_default_effective: >- {{ @@ -192,15 +302,15 @@ }} when: aw_apply_worktime_settings | default(false) | bool - - name: Validate derived durationDefault is sane + - name: Проверить корректность durationDefault ansible.builtin.assert: that: - aw_worktime_duration_default_effective | int > 0 - aw_worktime_duration_default_effective | int <= 86400 - fail_msg: "Invalid worktime window: {{ aw_worktime_from }}..{{ aw_worktime_to }}" + fail_msg: "Некорректный интервал рабочего времени: {{ aw_worktime_from }}..{{ aw_worktime_to }}" when: aw_apply_worktime_settings | default(false) | bool - - name: Apply baseline worktime period (startOfDay) + - name: Применить базовый период worktime (startOfDay) ansible.builtin.uri: url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/startOfDay" method: POST @@ -209,7 +319,7 @@ status_code: 200 when: aw_apply_worktime_settings | default(false) | bool - - name: Apply baseline worktime period (durationDefault seconds) + - name: Применить базовый период worktime (durationDefault seconds) ansible.builtin.uri: url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/durationDefault" method: POST @@ -219,11 +329,11 @@ when: aw_apply_worktime_settings | default(false) | bool handlers: - - name: Reload systemd + - name: Перезагрузить systemd ansible.builtin.systemd: daemon_reload: true - - name: Restart activitywatch + - name: Перезапустить activitywatch ansible.builtin.systemd: name: activitywatch-server.service state: restarted diff --git a/install-kit-awindows-20260427-211240/ansible/deploy_aw_windows_phase2.yml b/install-kit-awindows-20260427-211240/ansible/deploy_aw_windows_phase2.yml index 911e3dc..48fa3d8 100644 --- a/install-kit-awindows-20260427-211240/ansible/deploy_aw_windows_phase2.yml +++ b/install-kit-awindows-20260427-211240/ansible/deploy_aw_windows_phase2.yml @@ -1,13 +1,17 @@ --- -- name: Deploy AWatch-rus Windows phase2 collectors +- name: Развернуть Windows/RDP phase-2 collector'ы AWatch-rus hosts: aw_windows gather_facts: false vars: - aw_windows_repo_root: "/home/igor/tmp/AWatch-rus" + aw_windows_repo_root: "{{ playbook_dir | dirname }}" aw_windows_deploy_root: "C:\\Deploy\\AWatch-rus" + aw_windows_server_scheme: "http" aw_windows_server_host: "10.10.10.13" aw_windows_server_port: 5600 + aw_windows_package_version: "v0.13.2" + aw_windows_package_url: "https://github.com/ActivityWatch/activitywatch/releases/download/v0.13.2/activitywatch-v0.13.2-windows-x86_64.zip" + aw_windows_package_zip_path: "" aw_windows_domain: "SHARKON2025" aw_windows_users: - user1 @@ -31,20 +35,28 @@ aw_windows_policy_path: "{{ aw_windows_deploy_root }}\\windows\\dlp-policy.example.json" aw_windows_validation_remote_path: "C:\\Windows\\Temp\\aw_validate_phase2_ansible.json" aw_windows_validation_local_dir: "/tmp/aw-rus-validation" + aw_windows_launch_task_pattern: "ActivityWatch Launch *" + aw_windows_recovery_task_name: "ActivityWatch Recovery" + aw_windows_force_task_restart: true + aw_windows_api_smoke_check_enabled: true + aw_windows_api_smoke_check_bucket: "" + aw_windows_api_smoke_check_limit: 10 + aw_windows_fail_on_validation_error: true tasks: - - name: Validate required variables + - name: Проверить обязательные переменные ansible.builtin.assert: that: - aw_windows_server_host is defined - aw_windows_server_port is defined + - aw_windows_server_scheme is defined - aw_windows_domain is defined - aw_windows_users_effective | length > 0 - aw_windows_install_root is defined - aw_windows_state_root is defined - fail_msg: "Missing required Windows deployment variables." + fail_msg: "Не заданы обязательные переменные Windows-развёртывания." - - name: Ensure deploy directories exist + - name: Создать каталоги развёртывания ansible.windows.win_file: path: "{{ item }}" state: directory @@ -52,7 +64,7 @@ - "{{ aw_windows_deploy_root }}" - "{{ aw_windows_deploy_root }}\\windows" - - name: Upload Windows deployment toolkit + - name: Загрузить Windows toolkit развёртывания ansible.windows.win_copy: src: "{{ aw_windows_repo_root }}/windows/{{ item }}" dest: "{{ aw_windows_deploy_root }}\\windows\\{{ item }}" @@ -61,6 +73,7 @@ - ActivityWatch.Windows.Common.psm1 - browser-domains-native-collector.ps1 - dlp-endpoint-signals-collector.ps1 + - worktime-session-collector.ps1 - deploy-domain-users.ps1 - deploy-ensemble.ps1 - hardening-recovery.ps1 @@ -68,24 +81,23 @@ - web-category-rules.example.json - dlp-policy.example.json - - name: Upload user list for domain deploy + - name: Загрузить список пользователей для доменного развёртывания ansible.windows.win_copy: dest: "{{ aw_windows_deploy_root }}\\windows\\users.txt" content: | - {% for user in aw_windows_users -%} - {{ user }} - {% endfor -%} - {% for user in aw_windows_extra_users -%} + {% for user in aw_windows_users_effective -%} {{ user }} {% endfor -%} - - name: Run phase2 ensemble deployment + - name: Запустить phase-2 ensemble развёртывание ansible.windows.win_powershell: script: | $ErrorActionPreference = 'Stop' $params = @{ + ServerScheme = "{{ aw_windows_server_scheme }}" ServerHost = "{{ aw_windows_server_host }}" ServerPort = {{ aw_windows_server_port }} + Version = "{{ aw_windows_package_version }}" Domain = "{{ aw_windows_domain }}" UserListPath = "{{ aw_windows_deploy_root }}\windows\users.txt" InstallRoot = "{{ aw_windows_install_root }}" @@ -100,34 +112,95 @@ CustomRulesPath = "{{ aw_windows_rules_path }}" CustomPolicyPath = "{{ aw_windows_policy_path }}" } + {% if (aw_windows_package_url | default('') | string | length) > 0 %} + $params.PackageUrl = "{{ aw_windows_package_url }}" + {% endif %} + {% if (aw_windows_package_zip_path | default('') | string | length) > 0 %} + $params.PackageZipPath = "{{ aw_windows_package_zip_path }}" + {% endif %} {% if aw_windows_skip_hardening | bool %} $params.SkipHardening = $true {% endif %} & "{{ aw_windows_deploy_root }}\windows\deploy-ensemble.ps1" @params - - name: Run validation and store report on target + - name: Принудительно запустить ActivityWatch recovery и launch tasks + when: aw_windows_force_task_restart | bool + ansible.windows.win_powershell: + script: | + $ErrorActionPreference = 'Stop' + Start-ScheduledTask -TaskName "{{ aw_windows_recovery_task_name }}" + Get-ScheduledTask | + Where-Object TaskName -like "{{ aw_windows_launch_task_pattern }}" | + ForEach-Object { Start-ScheduledTask -TaskName $_.TaskName } + + - name: Получить Windows hostname для AW smoke-check bucket + when: + - aw_windows_api_smoke_check_enabled | bool + - aw_windows_afk_enabled | bool + ansible.windows.win_command: powershell.exe -NoProfile -Command "$env:COMPUTERNAME" + register: aw_windows_hostname_result + changed_when: false + + - name: Вычислить AW AFK smoke-check bucket + when: + - aw_windows_api_smoke_check_enabled | bool + - aw_windows_afk_enabled | bool + ansible.builtin.set_fact: + aw_windows_api_smoke_check_bucket_effective: >- + {{ + aw_windows_api_smoke_check_bucket + if (aw_windows_api_smoke_check_bucket | default('') | string | length) > 0 + else 'aw-watcher-afk_' ~ (aw_windows_hostname_result.stdout | trim) + }} + + - name: Дождаться свежих AFK событий на AW server + when: + - aw_windows_api_smoke_check_enabled | bool + - aw_windows_afk_enabled | bool + delegate_to: localhost + ansible.builtin.uri: + url: "{{ aw_windows_server_scheme }}://{{ aw_windows_server_host }}:{{ aw_windows_server_port }}/api/0/buckets/{{ aw_windows_api_smoke_check_bucket_effective }}/events?limit={{ aw_windows_api_smoke_check_limit }}" + method: GET + return_content: true + register: aw_windows_api_smoke + until: > + aw_windows_api_smoke.status == 200 and + (aw_windows_api_smoke.json | length) > 0 and + ( + aw_windows_api_smoke.json + | selectattr('data.status', 'equalto', 'not-afk') + | list + | length + ) > 0 + retries: 10 + delay: 6 + + - name: Выполнить валидацию и сохранить отчёт на целевом Windows host ansible.windows.win_powershell: script: | $ErrorActionPreference = 'Stop' $report = & "{{ aw_windows_deploy_root }}\windows\validate-deployment.ps1" ` -ConfigPath "{{ aw_windows_state_root }}\deployment-config.json" $report | ConvertTo-Json -Depth 12 | Out-File -FilePath "{{ aw_windows_validation_remote_path }}" -Encoding utf8 + if ({{ '$true' if (aw_windows_fail_on_validation_error | bool) else '$false' }} -and -not [bool]$report.overallOk) { + throw "Проверка развёртывания ActivityWatch завершилась ошибкой. Отчёт: {{ aw_windows_validation_remote_path }}" + } - - name: Ensure local validation directory exists + - name: Создать локальный каталог для validation reports ansible.builtin.file: path: "{{ aw_windows_validation_local_dir }}" state: directory mode: "0755" delegate_to: localhost - - name: Fetch validation report + - name: Забрать validation report ansible.builtin.fetch: src: "{{ aw_windows_validation_remote_path }}" - dest: "{{ aw_windows_validation_local_dir }}/" - flat: false + dest: "{{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_phase2_ansible.json" + flat: true - - name: Show report location + - name: Показать путь к отчёту ansible.builtin.debug: msg: - - "Windows phase2 deploy completed on {{ inventory_hostname }}." - - "Validation report: {{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}/C$/Windows/Temp/aw_validate_phase2_ansible.json" + - "Windows phase2 развёртывание завершено на {{ inventory_hostname }}." + - "Отчёт проверки: {{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_phase2_ansible.json" 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 8a41775..27d5703 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 @@ -8,17 +8,17 @@ aw_server_log_dir: "/var/log/activitywatch" aw_server_user: "activitywatch" aw_server_group: "activitywatch" -aw_repo_root: "/home/igor/tmp/AWatch-rus" +aw_repo_root: "{{ playbook_dir | dirname }}" -# Optional: apply a baseline worktime-focused categorization and views via AW settings API. -# WARNING: this overwrites existing server-side settings/classes/views. +# Опционально: применить базовые категории и views для рабочего времени через AW settings API. +# Внимание: это перезаписывает существующие server-side settings/classes/views. aw_apply_worktime_settings: false -# Optional defaults for the worktime period in Web UI. -# startOfDay controls day-boundary and default report window start. -# durationDefault controls default time range (seconds) shown in UI. +# Опциональные значения периода рабочего времени в Web UI. +# startOfDay задаёт границу дня и стартовое время окна отчёта. +# durationDefault задаёт диапазон по умолчанию в секундах. # -# Recommended: set worktime window explicitly and let the playbook derive duration. +# Рекомендуется явно задать рабочий интервал и дать playbook вычислить duration. aw_worktime_from: "08:00" aw_worktime_to: "17:00" aw_worktime_start_of_day: "{{ aw_worktime_from }}" 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 3b6a958..1255298 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 @@ -1,7 +1,11 @@ -aw_windows_repo_root: "/home/igor/tmp/AWatch-rus" +aw_windows_repo_root: "{{ playbook_dir | dirname }}" aw_windows_deploy_root: "C:\\Deploy\\AWatch-rus" +aw_windows_server_scheme: "http" aw_windows_server_host: "10.10.10.13" aw_windows_server_port: 5600 +aw_windows_package_version: "v0.13.2" +aw_windows_package_url: "https://github.com/ActivityWatch/activitywatch/releases/download/v0.13.2/activitywatch-v0.13.2-windows-x86_64.zip" +aw_windows_package_zip_path: "" aw_windows_domain: "SHARKON2025" aw_windows_users: - user1 @@ -31,3 +35,10 @@ aw_windows_policy_path: "{{ aw_windows_deploy_root }}\\windows\\dlp-policy.examp aw_windows_validation_remote_path: "C:\\Windows\\Temp\\aw_validate_phase2_ansible.json" aw_windows_validation_local_dir: "/tmp/aw-rus-validation" +aw_windows_fail_on_validation_error: true + +# По умолчанию AFK bucket вычисляется как aw-watcher-afk_. +# Задайте явное значение только если watcher пишет в нестандартный bucket. +aw_windows_api_smoke_check_enabled: true +aw_windows_api_smoke_check_bucket: "" +aw_windows_api_smoke_check_limit: 10 diff --git a/install-kit-awindows-20260427-211240/ansible/install_full_stack.yml b/install-kit-awindows-20260427-211240/ansible/install_full_stack.yml new file mode 100644 index 0000000..0af14c9 --- /dev/null +++ b/install-kit-awindows-20260427-211240/ansible/install_full_stack.yml @@ -0,0 +1,16 @@ +--- +# Полный установщик AWatch-rus. +# Выполняет развёртывание одной командой: +# 1) создание Proxmox CT + bootstrap AW (если в inventory есть [proxmox]) +# 2) развёртывание AW server на хостах [aw_server] +# 3) развёртывание Windows/RDP collector'ов на [aw_windows] +# 4) развёртывание pfSense poller'а на [aw_pfsense_pollers] +# +# Примечания: +# - Заполняйте только нужные группы inventory для своего окружения. +# - Play без совпадающих host groups Ansible пропускает автоматически. + +- import_playbook: provision_proxmox_ct_and_deploy_aw.yml +- import_playbook: deploy_aw_server.yml +- import_playbook: deploy_aw_windows_phase2.yml +- import_playbook: deploy_aw_pfsense_poller.yml diff --git a/install-kit-awindows-20260427-211240/ansible/inventory.example.ini b/install-kit-awindows-20260427-211240/ansible/inventory.example.ini index f73f446..e1c77ff 100644 --- a/install-kit-awindows-20260427-211240/ansible/inventory.example.ini +++ b/install-kit-awindows-20260427-211240/ansible/inventory.example.ini @@ -5,4 +5,8 @@ pve-main ansible_host=192.168.10.2 ansible_user=root ansible_port=22 aw-ct ansible_host=10.20.30.13 ansible_user=root ansible_port=22 [aw_windows] -win-node1 ansible_host=192.168.100.21 ansible_user=Administrator ansible_password=CHANGE_ME ansible_connection=winrm ansible_winrm_transport=ntlm ansible_port=5985 ansible_winrm_server_cert_validation=ignore +# Примечание: в русифицированных Windows часто нужен "Администратор", а не "Administrator". +win-node1 ansible_host=192.168.100.21 ansible_user=Администратор ansible_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/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 2a932a7..dd9dadf 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 @@ -1,5 +1,5 @@ --- -- name: Provision single Proxmox CT and deploy AWatch-rus +- name: Создать один Proxmox CT и развернуть AWatch-rus hosts: proxmox gather_facts: false @@ -17,7 +17,7 @@ - settings/views-default.json tasks: - - name: Execute single-CT provisioning workflow + - name: Выполнить workflow создания одного CT ansible.builtin.include_tasks: tasks/provision_ct_and_deploy_aw.yml vars: ct_id: "{{ proxmox_ct_id }}" 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 b228f66..44564da 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 @@ -1,5 +1,5 @@ --- -- name: Provision Proxmox CT matrix and deploy AWatch-rus with RU patch +- name: Создать матрицу Proxmox CT и развернуть AWatch-rus с RU patch hosts: proxmox gather_facts: false @@ -17,14 +17,14 @@ - settings/views-default.json tasks: - - name: Validate CT matrix is provided + - name: Проверить, что матрица CT задана ansible.builtin.assert: that: - proxmox_ct_matrix is defined - proxmox_ct_matrix | length > 0 - fail_msg: "Define proxmox_ct_matrix in group_vars/proxmox-matrix.yml" + fail_msg: "Задайте proxmox_ct_matrix в group_vars/proxmox-matrix.yml" - - name: Execute provisioning workflow for each CT + - name: Выполнить workflow создания для каждого CT ansible.builtin.include_tasks: tasks/provision_ct_and_deploy_aw.yml vars: ct_id: "{{ item.id }}" 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 4cceb17..975e6ef 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 @@ -1,5 +1,5 @@ --- -- name: Validate required per-CT variables +- name: Проверить обязательные переменные CT ansible.builtin.assert: that: - ct_id is defined @@ -27,14 +27,14 @@ - aw_server_log_dir is defined - aw_server_user is defined - aw_server_group is defined - fail_msg: "Missing required variables for CT provisioning/deploy." + fail_msg: "Не заданы обязательные переменные для создания CT и развёртывания." -- name: Build CT network string +- name: Сформировать сетевую строку CT ansible.builtin.set_fact: ct_net0: >- name=eth0,bridge={{ ct_bridge }},ip={{ ct_ip }},gw={{ ct_gw }}{% if (ct_vlan | default('') | string | length) > 0 %},tag={{ ct_vlan }}{% endif %} -- name: Check whether CT already exists +- name: Проверить, существует ли CT ansible.builtin.command: argv: - pct @@ -44,7 +44,7 @@ failed_when: false changed_when: false -- name: Create CT when absent +- name: Создать CT, если он отсутствует ansible.builtin.command: argv: - pct @@ -78,8 +78,9 @@ - --ostype - debian when: ct_status_check.rc != 0 + no_log: true -- name: Check current CT runtime state +- name: Проверить текущее состояние CT ansible.builtin.command: argv: - pct @@ -88,7 +89,7 @@ register: ct_runtime_status changed_when: false -- name: Start CT when stopped +- name: Запустить CT, если он остановлен ansible.builtin.command: argv: - pct @@ -96,20 +97,23 @@ - "{{ ct_id }}" when: "'stopped' in ct_runtime_status.stdout" -- name: Ensure bootstrap directory on Proxmox host +- name: Создать bootstrap каталог на Proxmox host ansible.builtin.file: - path: "{{ proxmox_bootstrap_dir }}" + path: "{{ item }}" state: directory mode: "0700" + loop: + - "{{ proxmox_bootstrap_dir }}" + - "{{ proxmox_bootstrap_dir }}/settings" -- name: Copy AW bootstrap files to Proxmox host temp +- name: Скопировать AW bootstrap файлы во временный каталог Proxmox host ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/{{ item }}" dest: "{{ proxmox_bootstrap_dir }}/{{ item }}" mode: "0644" loop: "{{ aw_bootstrap_files }}" -- name: Bootstrap CT OS dependencies +- name: Установить базовые зависимости ОС внутри CT ansible.builtin.command: argv: - pct @@ -122,12 +126,16 @@ set -euo pipefail export DEBIAN_FRONTEND=noninteractive apt-get update - apt-get install -y curl ca-certificates bash unzip xz-utils jq rsync openssh-server - mkdir -p /root/bootstrap /etc/activitywatch + apt-get install -y curl ca-certificates bash unzip xz-utils jq rsync openssh-server python3 + mkdir -p /root/bootstrap/settings /etc/activitywatch systemctl enable ssh || true systemctl restart ssh || true + register: ct_bootstrap_result + retries: 10 + delay: 6 + until: ct_bootstrap_result.rc == 0 -- name: Push bootstrap files into CT +- name: Передать bootstrap файлы внутрь CT ansible.builtin.command: argv: - pct @@ -137,7 +145,7 @@ - "/root/bootstrap/{{ item }}" loop: "{{ aw_bootstrap_files }}" -- name: Write AW server env file on Proxmox host temp +- name: Записать AW server env во временный каталог Proxmox host ansible.builtin.copy: dest: "{{ proxmox_bootstrap_dir }}/aw-server.env" mode: "0600" @@ -151,8 +159,9 @@ AW_SERVER_LOG_DIR={{ aw_server_log_dir }} AW_SERVER_USER={{ aw_server_user }} AW_SERVER_GROUP={{ aw_server_group }} + no_log: true -- name: Push AW server env into CT +- name: Передать AW server env внутрь CT ansible.builtin.command: argv: - pct @@ -160,8 +169,9 @@ - "{{ ct_id }}" - "{{ proxmox_bootstrap_dir }}/aw-server.env" - /etc/activitywatch/aw-server.env + no_log: true -- name: Set mode for env inside CT +- name: Настроить права env файла внутри CT ansible.builtin.command: argv: - pct @@ -172,7 +182,7 @@ - "0600" - /etc/activitywatch/aw-server.env -- name: Install server and apply RU patch inside CT +- name: Установить сервер и применить RU patch внутри CT ansible.builtin.command: argv: - pct @@ -188,7 +198,7 @@ bash /root/bootstrap/apply_webui_ru_patch.sh systemctl restart activitywatch-server.service -- name: Validate AW API from inside CT +- name: Проверить AW API изнутри CT ansible.builtin.command: argv: - pct @@ -199,7 +209,7 @@ - -lc - "curl -fsS http://127.0.0.1:{{ aw_server_port }}/api/0/info >/dev/null" -- name: Validate RU patch hooks in index +- name: Проверить hooks RU patch в index.html ansible.builtin.command: argv: - pct @@ -210,8 +220,8 @@ - -lc - "grep -q 'ru-patch-v5.js' {{ aw_server_webui_dir }}/index.html && grep -q 'sw-cleanup.js' {{ aw_server_webui_dir }}/index.html" -- name: Show final endpoint +- name: Показать итоговый endpoint ansible.builtin.debug: msg: - - "CT {{ ct_id }} is provisioned and configured." - - "ActivityWatch endpoint: http://{{ ct_ip | regex_replace('/[0-9]+$', '') }}:{{ aw_server_port }}" + - "CT {{ ct_id }} создан и настроен." + - "Endpoint ActivityWatch: http://{{ ct_ip | regex_replace('/[0-9]+$', '') }}:{{ aw_server_port }}" diff --git a/install-kit-awindows-20260427-211240/windows/validate-deployment.ps1 b/install-kit-awindows-20260427-211240/windows/validate-deployment.ps1 index 85a0344..15d213c 100644 --- a/install-kit-awindows-20260427-211240/windows/validate-deployment.ps1 +++ b/install-kit-awindows-20260427-211240/windows/validate-deployment.ps1 @@ -20,9 +20,9 @@ $policyPath = if ($config.paths.PSObject.Properties.Name -contains 'policyPath') $launchScript = [string]$config.paths.launchScript $recoveryScript = [string]$config.paths.recoveryScript +$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 } $requiredFiles = @( - (Join-Path $installRoot 'aw-watcher-afk\aw-watcher-afk.exe'), - (Join-Path $installRoot 'aw-watcher-window\aw-watcher-window.exe'), $collectorScript, $endpointCollectorScript, $sessionCollectorScript, @@ -32,13 +32,24 @@ $requiredFiles = @( $recoveryScript, $ConfigPath ) +if ($afkExpected) { + $requiredFiles += (Join-Path $installRoot 'aw-watcher-afk\aw-watcher-afk.exe') +} +if ($windowExpected) { + $requiredFiles += (Join-Path $installRoot 'aw-watcher-window\aw-watcher-window.exe') +} $missingFiles = @( $requiredFiles | Where-Object { -not (Test-Path -LiteralPath $_) } ) -$processNames = @('aw-watcher-afk', 'aw-watcher-window') -$runningProcesses = Get-Process -Name $processNames -ErrorAction SilentlyContinue | Select-Object Name, Id, SessionId +$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 +} $sessionCollectorProcesses = Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object { ($_.Name -ieq 'powershell.exe' -or $_.Name -ieq 'pwsh.exe') -and @@ -88,10 +99,14 @@ $result = [ordered]@{ ok = [bool]($tasks.Count -gt 0 -and -not ($tasks | Where-Object { -not $_.present })) } processes = [ordered]@{ + expected = $processNames list = @($runningProcesses) sessionCollectors = @($sessionCollectorProcesses) ok = [bool]( - (($runningProcesses | Select-Object -ExpandProperty Name -Unique).Count -ge 2) -and + ( + ($processNames.Count -eq 0) -or + (($runningProcesses | Select-Object -ExpandProperty Name -Unique).Count -ge $processNames.Count) + ) -and ($sessionCollectorProcesses.Count -ge 1) ) } diff --git a/windows/validate-deployment.ps1 b/windows/validate-deployment.ps1 index 85a0344..15d213c 100644 --- a/windows/validate-deployment.ps1 +++ b/windows/validate-deployment.ps1 @@ -20,9 +20,9 @@ $policyPath = if ($config.paths.PSObject.Properties.Name -contains 'policyPath') $launchScript = [string]$config.paths.launchScript $recoveryScript = [string]$config.paths.recoveryScript +$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 } $requiredFiles = @( - (Join-Path $installRoot 'aw-watcher-afk\aw-watcher-afk.exe'), - (Join-Path $installRoot 'aw-watcher-window\aw-watcher-window.exe'), $collectorScript, $endpointCollectorScript, $sessionCollectorScript, @@ -32,13 +32,24 @@ $requiredFiles = @( $recoveryScript, $ConfigPath ) +if ($afkExpected) { + $requiredFiles += (Join-Path $installRoot 'aw-watcher-afk\aw-watcher-afk.exe') +} +if ($windowExpected) { + $requiredFiles += (Join-Path $installRoot 'aw-watcher-window\aw-watcher-window.exe') +} $missingFiles = @( $requiredFiles | Where-Object { -not (Test-Path -LiteralPath $_) } ) -$processNames = @('aw-watcher-afk', 'aw-watcher-window') -$runningProcesses = Get-Process -Name $processNames -ErrorAction SilentlyContinue | Select-Object Name, Id, SessionId +$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 +} $sessionCollectorProcesses = Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object { ($_.Name -ieq 'powershell.exe' -or $_.Name -ieq 'pwsh.exe') -and @@ -88,10 +99,14 @@ $result = [ordered]@{ ok = [bool]($tasks.Count -gt 0 -and -not ($tasks | Where-Object { -not $_.present })) } processes = [ordered]@{ + expected = $processNames list = @($runningProcesses) sessionCollectors = @($sessionCollectorProcesses) ok = [bool]( - (($runningProcesses | Select-Object -ExpandProperty Name -Unique).Count -ge 2) -and + ( + ($processNames.Count -eq 0) -or + (($runningProcesses | Select-Object -ExpandProperty Name -Unique).Count -ge $processNames.Count) + ) -and ($sessionCollectorProcesses.Count -ge 1) ) }