Русифицировать и доработать Ansible развёртывание

This commit is contained in:
Devin AI
2026-05-02 09:07:08 +00:00
parent 8c9dfffc7d
commit 1e92b8b679
24 changed files with 777 additions and 327 deletions
+36 -35
View File
@@ -1,37 +1,33 @@
# Ansible ensemble for AWatch-rus # Ansible ensemble for AWatch-rus
Эта директория содержит Ansible-ensemble для двух сценариев: Эта директория содержит Ansible-ensemble для полного развёртывания AWatch-rus:
- деплой на уже существующий Debian host/CT; - деплой на уже существующий Debian host/CT;
- полный цикл с нуля в Proxmox: создание CT + bootstrap + установка ActivityWatch + RU patch. - полный цикл с нуля в Proxmox: создание CT + bootstrap + установка ActivityWatch + RU patch;
- централизованный деплой Windows phase-2 collectors по WinRM. - централизованное развёртывание Windows phase-2 collector'ов по WinRM;
- deployment внешнего pfSense poller'а на Debian/Ubuntu utility VM. - развёртывание внешнего pfSense poller'а на Debian/Ubuntu utility VM.
## Файлы ## Файлы
- `/home/igor/tmp/AWatch-rus/ansible/deploy_aw_server.yml` — основной playbook. - `ansible/deploy_aw_server.yml` — основной playbook для уже существующего Debian/CT host.
- `/home/igor/tmp/AWatch-rus/ansible/provision_proxmox_ct_and_deploy_aw.yml`full-stack playbook для Proxmox. - `ansible/provision_proxmox_ct_and_deploy_aw.yml`полный playbook для Proxmox.
- `/home/igor/tmp/AWatch-rus/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml` — массовый full-stack playbook (несколько CT). - `ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml` — массовый полный playbook (несколько CT).
- `/home/igor/tmp/AWatch-rus/ansible/deploy_aw_windows_phase2.yml` — WinRM playbook для развёртывания phase-2 Windows collector'ов. - `ansible/deploy_aw_windows_phase2.yml` — WinRM playbook для развёртывания Windows/RDP collector'ов.
- `/home/igor/tmp/AWatch-rus/ansible/deploy_aw_pfsense_poller.yml`deployment pfSense poller'а. - `ansible/deploy_aw_pfsense_poller.yml`развёртывание pfSense poller'а.
- `/home/igor/tmp/AWatch-rus/ansible/install_full_stack.yml` — полный установочный playbook (оркестратор всех этапов). - `ansible/install_full_stack.yml` — полный установочный playbook (оркестратор всех этапов).
- `/home/igor/tmp/AWatch-rus/ansible/inventory.example.ini` — шаблон inventory. - `ansible/inventory.example.ini` — шаблон inventory.
- `/home/igor/tmp/AWatch-rus/ansible/group_vars/all.example.yml` — шаблон переменных. - `ansible/group_vars/*.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'а.
## Быстрый запуск ## Быстрый запуск
1. Скопируйте шаблоны: 1. Скопируйте шаблоны:
- `cp /home/igor/tmp/AWatch-rus/ansible/inventory.example.ini /home/igor/tmp/AWatch-rus/ansible/inventory.ini` - `cp ansible/inventory.example.ini 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/group_vars/all.example.yml ansible/group_vars/all.yml`
2. Заполните значения в `inventory.ini` и `group_vars/all.yml`. 2. Заполните значения в `inventory.ini` и `group_vars/all.yml`.
3. Запустите: 3. Запустите:
```bash ```bash
cd /home/igor/tmp/AWatch-rus/ansible cd ansible
ansible-playbook -i inventory.ini deploy_aw_server.yml ansible-playbook -i inventory.ini deploy_aw_server.yml
``` ```
@@ -40,7 +36,7 @@ ansible-playbook -i inventory.ini deploy_aw_server.yml
Если нужно прогнать полный цикл одной командой: Если нужно прогнать полный цикл одной командой:
```bash ```bash
cd /home/igor/tmp/AWatch-rus/ansible cd ansible
ansible-playbook -i inventory.ini install_full_stack.yml ansible-playbook -i inventory.ini install_full_stack.yml
``` ```
@@ -56,50 +52,50 @@ ansible-playbook -i inventory.ini install_full_stack.yml
## Полный запуск с нуля в Proxmox ## Полный запуск с нуля в Proxmox
1. Подготовьте inventory и vars: 1. Подготовьте inventory и vars:
- `cp /home/igor/tmp/AWatch-rus/ansible/inventory.example.ini /home/igor/tmp/AWatch-rus/ansible/inventory.ini` - `cp ansible/inventory.example.ini 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/group_vars/all.example.yml 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/group_vars/proxmox.example.yml ansible/group_vars/proxmox.yml`
2. Заполните `group_vars/proxmox.yml` и `group_vars/all.yml`. 2. Заполните `group_vars/proxmox.yml` и `group_vars/all.yml`.
3. Запустите playbook: 3. Запустите playbook:
```bash ```bash
cd /home/igor/tmp/AWatch-rus/ansible cd ansible
ansible-playbook -i inventory.ini provision_proxmox_ct_and_deploy_aw.yml ansible-playbook -i inventory.ini provision_proxmox_ct_and_deploy_aw.yml
``` ```
## Массовый запуск (матрица CT) ## Массовый запуск (матрица CT)
1. Подготовьте матрицу: 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`. 2. Заполните `proxmox-matrix.yml`.
3. Запустите: 3. Запустите:
```bash ```bash
cd /home/igor/tmp/AWatch-rus/ansible cd ansible
ansible-playbook -i inventory.ini provision_proxmox_ct_matrix_and_deploy_aw.yml ansible-playbook -i inventory.ini provision_proxmox_ct_matrix_and_deploy_aw.yml
``` ```
## Windows phase-2 rollout (WinRM) ## Windows phase-2 rollout (WinRM)
1. Подготовьте inventory и vars: 1. Подготовьте inventory и vars:
- `cp /home/igor/tmp/AWatch-rus/ansible/inventory.example.ini /home/igor/tmp/AWatch-rus/ansible/inventory.ini` - `cp ansible/inventory.example.ini 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/group_vars/windows.example.yml ansible/group_vars/windows.yml`
2. Заполните `inventory.ini` (секция `[aw_windows]`) и `group_vars/windows.yml`. 2. Заполните `inventory.ini` (секция `[aw_windows]`) и `group_vars/windows.yml`.
- Для русской локализации Windows часто нужен `ansible_user=Администратор` (а не `Administrator`). - Для русской локализации Windows часто нужен `ansible_user=Администратор` (а не `Administrator`).
- Если WinRM закрыт, playbook не сможет стартовать и нужно сначала открыть `5985/5986` и `wsman`. - Если WinRM закрыт, playbook не сможет стартовать и нужно сначала открыть `5985/5986` и `wsman`.
3. Запустите: 3. Запустите:
```bash ```bash
cd /home/igor/tmp/AWatch-rus/ansible cd ansible
ansible-playbook -i inventory.ini deploy_aw_windows_phase2.yml ansible-playbook -i inventory.ini deploy_aw_windows_phase2.yml
``` ```
Playbook: 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-ensemble.ps1` (deploy + hardening/recovery) с phase-2 policy/rules;
- после deploy принудительно запускает `ActivityWatch Recovery` и все `ActivityWatch Launch *` задачи; - после deploy принудительно запускает `ActivityWatch Recovery` и все `ActivityWatch Launch *` задачи;
- выполняет API smoke-check bucket `aw-watcher-afk_SHARKON2025` и ожидает свежие `not-afk` события; - выполняет API smoke-check bucket `aw-watcher-afk_<COMPUTERNAME>` и ожидает свежие `not-afk` события;
- запускает `validate-deployment.ps1`; - запускает `validate-deployment.ps1`;
- забирает JSON-отчёт в локальную директорию (`/tmp/aw-rus-validation` по умолчанию). - забирает JSON-отчёт в локальную директорию (`/tmp/aw-rus-validation` по умолчанию).
@@ -110,17 +106,20 @@ Playbook:
- `aw_windows_incident_capture_enabled: false` — отключить блок incidentCapture; - `aw_windows_incident_capture_enabled: false` — отключить блок incidentCapture;
- `aw_windows_incident_screenshot_enabled: false` — не делать скриншот при DLP-инциденте; - `aw_windows_incident_screenshot_enabled: false` — не делать скриншот при DLP-инциденте;
- `aw_windows_incident_artifacts_root: 'C:\...\incident-artifacts'` — переопределить путь артефактов; - `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_<COMPUTERNAME>`;
- `aw_windows_fail_on_validation_error: true` — завершать playbook ошибкой, если `validate-deployment.ps1` возвращает `overallOk=false`;
- `aw_windows_skip_hardening: true` — пропустить `hardening-recovery.ps1` внутри ensemble-скрипта. - `aw_windows_skip_hardening: true` — пропустить `hardening-recovery.ps1` внутри ensemble-скрипта.
## pfSense poller rollout ## Развёртывание pfSense poller
1. Подготовьте vars: 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]`. 2. Добавьте inventory group `[aw_pfsense_pollers]`.
3. Запустите: 3. Запустите:
```bash ```bash
cd /home/igor/tmp/AWatch-rus/ansible cd ansible
ansible-playbook -i inventory.ini deploy_aw_pfsense_poller.yml 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/русскую статику после деплоя. - Для Web UI используется checksum-based cache-bust для `ru-patch-v5.js` и `sw-cleanup.js`, чтобы браузер не держал старую DLP/русскую статику после деплоя.
- На `#/home` Web UI делит хосты на `Windows RDP` и `Virtual servers + Proxmox`. - На `#/home` Web UI делит хосты на `Windows RDP` и `Virtual servers + Proxmox`.
- Выполнена валидация API `http://127.0.0.1:5600/api/0/info`. - Выполнена валидация 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`.
+11 -11
View File
@@ -1,5 +1,5 @@
--- ---
- name: Deploy pfSense ActivityWatch poller - name: Развернуть pfSense ActivityWatch poller
hosts: aw_pfsense_pollers hosts: aw_pfsense_pollers
become: true become: true
gather_facts: true gather_facts: true
@@ -10,14 +10,14 @@
aw_pfsense_service_name: "aw-pfsense-poller.service" aw_pfsense_service_name: "aw-pfsense-poller.service"
tasks: tasks:
- name: Install required packages - name: Установить обязательные пакеты
ansible.builtin.apt: ansible.builtin.apt:
name: name:
- python3 - python3
state: present state: present
update_cache: true update_cache: true
- name: Ensure directories exist - name: Создать каталоги
ansible.builtin.file: ansible.builtin.file:
path: "{{ item }}" path: "{{ item }}"
state: directory state: directory
@@ -26,29 +26,29 @@
- "{{ aw_pfsense_install_root }}" - "{{ aw_pfsense_install_root }}"
- "{{ aw_pfsense_config_dir }}" - "{{ aw_pfsense_config_dir }}"
- name: Install pfSense poller script - name: Установить скрипт pfSense poller
ansible.builtin.copy: ansible.builtin.copy:
src: "{{ aw_repo_root }}/pfsense/pfsense-aw-poller.py" src: "{{ aw_repo_root }}/pfsense/pfsense-aw-poller.py"
dest: "{{ aw_pfsense_install_root }}/pfsense-aw-poller.py" dest: "{{ aw_pfsense_install_root }}/pfsense-aw-poller.py"
mode: "0755" mode: "0755"
- name: Install systemd service - name: Установить systemd service
ansible.builtin.copy: ansible.builtin.copy:
src: "{{ aw_repo_root }}/pfsense/pfsense-aw-poller.service" src: "{{ aw_repo_root }}/pfsense/pfsense-aw-poller.service"
dest: "/etc/systemd/system/{{ aw_pfsense_service_name }}" dest: "/etc/systemd/system/{{ aw_pfsense_service_name }}"
mode: "0644" mode: "0644"
notify: notify:
- Reload systemd - Перезагрузить systemd
- name: Write pfSense poller config - name: Записать конфигурацию pfSense poller
ansible.builtin.copy: ansible.builtin.copy:
dest: "{{ aw_pfsense_config_dir }}/poller.json" dest: "{{ aw_pfsense_config_dir }}/poller.json"
mode: "0600" mode: "0600"
content: "{{ aw_pfsense_poller_config | to_nice_json }}" content: "{{ aw_pfsense_poller_config | to_nice_json }}"
notify: notify:
- Restart pfSense poller - Перезапустить pfSense poller
- name: Enable and start pfSense poller - name: Включить и запустить pfSense poller
ansible.builtin.systemd: ansible.builtin.systemd:
name: "{{ aw_pfsense_service_name }}" name: "{{ aw_pfsense_service_name }}"
enabled: true enabled: true
@@ -56,11 +56,11 @@
daemon_reload: true daemon_reload: true
handlers: handlers:
- name: Reload systemd - name: Перезагрузить systemd
ansible.builtin.systemd: ansible.builtin.systemd:
daemon_reload: true daemon_reload: true
- name: Restart pfSense poller - name: Перезапустить pfSense poller
ansible.builtin.systemd: ansible.builtin.systemd:
name: "{{ aw_pfsense_service_name }}" name: "{{ aw_pfsense_service_name }}"
state: restarted state: restarted
+159 -49
View File
@@ -1,5 +1,5 @@
--- ---
- name: Deploy AWatch-rus server - name: Развернуть сервер AWatch-rus
hosts: aw_server hosts: aw_server
become: true become: true
gather_facts: true gather_facts: true
@@ -9,22 +9,29 @@
aw_release_dir: "{{ aw_release_root }}/{{ aw_server_version }}" aw_release_dir: "{{ aw_release_root }}/{{ aw_server_version }}"
aw_archive_path: "/tmp/activitywatch-{{ aw_server_version }}.zip" aw_archive_path: "/tmp/activitywatch-{{ aw_server_version }}.zip"
aw_bootstrap_dir: "/tmp/aw-rus-bootstrap" 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_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_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_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 }}" aw_default_views: "{{ lookup('file', aw_repo_root + '/aw-server/settings/views-default.json') | from_json }}"
tasks: tasks:
- name: Install base packages - name: Установить базовые пакеты
ansible.builtin.apt: ansible.builtin.apt:
name: name:
- curl - curl
- rsync
- unzip - unzip
state: present state: present
update_cache: true update_cache: true
- name: Ensure service account exists - name: Создать системную группу сервиса
ansible.builtin.group:
name: "{{ aw_server_group }}"
system: true
state: present
- name: Создать системную учётную запись сервиса
ansible.builtin.user: ansible.builtin.user:
name: "{{ aw_server_user }}" name: "{{ aw_server_user }}"
group: "{{ aw_server_group }}" group: "{{ aw_server_group }}"
@@ -33,7 +40,25 @@
system: true system: true
create_home: false 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: ansible.builtin.file:
path: "{{ item }}" path: "{{ item }}"
state: directory state: directory
@@ -41,103 +66,188 @@
group: "{{ aw_server_group }}" group: "{{ aw_server_group }}"
mode: "0755" mode: "0755"
loop: loop:
- /opt/activitywatch
- /opt/activitywatch/bin
- "{{ aw_release_root }}" - "{{ aw_release_root }}"
- "{{ aw_release_dir }}" - "{{ aw_release_dir }}"
- "{{ aw_release_install_dir }}"
- "{{ aw_server_webui_dir }}" - "{{ aw_server_webui_dir }}"
- "{{ aw_server_webui_dir }}/js"
- "{{ aw_server_data_dir }}" - "{{ aw_server_data_dir }}"
- "{{ aw_server_log_dir }}" - "{{ aw_server_log_dir }}"
- /etc/activitywatch
- "{{ aw_bootstrap_dir }}"
- name: Download ActivityWatch release archive - name: Скачать архив релиза ActivityWatch
ansible.builtin.get_url: ansible.builtin.get_url:
url: "{{ aw_server_download_url }}" url: "{{ aw_server_download_url }}"
dest: "{{ aw_archive_path }}" dest: "{{ aw_archive_path }}"
mode: "0644" mode: "0644"
- name: Unpack ActivityWatch release - name: Распаковать релиз ActivityWatch
ansible.builtin.unarchive: ansible.builtin.unarchive:
src: "{{ aw_archive_path }}" src: "{{ aw_archive_path }}"
dest: "{{ aw_release_dir }}" dest: "{{ aw_release_dir }}"
remote_src: true remote_src: true
extra_opts: ["-o"] extra_opts: ["-o"]
- name: Discover extracted AW directory - name: Найти распакованный каталог ActivityWatch
ansible.builtin.find: ansible.builtin.find:
paths: "{{ aw_release_dir }}" paths: "{{ aw_release_dir }}"
file_type: directory file_type: directory
patterns: "activitywatch*" patterns: "activitywatch*"
register: aw_release_find register: aw_release_find
- name: Set release extracted path - name: Найти бинарный файл AW server
ansible.builtin.set_fact: ansible.builtin.find:
aw_release_extracted: "{{ (aw_release_find.files | sort(attribute='path') | map(attribute='path') | list | first) }}" 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: ansible.builtin.assert:
that: that:
- aw_release_extracted is defined - aw_release_extracted is defined
- aw_release_extracted | length > 0 - 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: 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: ansible.builtin.copy:
src: "{{ item.src }}" src: "{{ item.src }}"
dest: "{{ item.dest }}" dest: "{{ item.dest }}"
mode: "{{ item.mode }}" mode: "{{ item.mode }}"
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
loop: 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-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-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" } - { 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 - name: Проверить наличие index.html после копирования
ansible.builtin.copy: ansible.builtin.stat:
remote_src: true path: "{{ aw_server_webui_dir }}/index.html"
src: "/opt/activitywatch/aw-webui/index.html" register: aw_webui_ru_index
dest: "{{ aw_server_webui_dir }}/index.html"
mode: "0644"
- 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: '<script[^>]+(?:ru-patch-v5\.js|sw-cleanup\.js|aw-ru-patch\.js|aw-sw-cleanup\.js)[^>]*></script>'
replace: ''
- name: Добавить cleanup script RU patch в index.html
ansible.builtin.replace: ansible.builtin.replace:
path: "{{ aw_server_webui_dir }}/index.html" path: "{{ aw_server_webui_dir }}/index.html"
regexp: '</head>' regexp: '</head>'
replace: '<script src="/js/sw-cleanup.js?v={{ aw_sw_cleanup_cache_bust }}"></script></head>' replace: '<script src="/js/sw-cleanup.js?v={{ aw_sw_cleanup_cache_bust }}"></script></head>'
- name: Insert RU patch loader before body end - name: Добавить загрузчик RU patch перед закрытием body
ansible.builtin.replace: ansible.builtin.replace:
path: "{{ aw_server_webui_dir }}/index.html" path: "{{ aw_server_webui_dir }}/index.html"
regexp: '</body>' regexp: '</body>'
replace: '<script defer="defer" src="/js/ru-patch-v5.js?v={{ aw_ru_patch_cache_bust }}"></script></body>' replace: '<script defer="defer" src="/js/ru-patch-v5.js?v={{ aw_ru_patch_cache_bust }}"></script></body>'
- name: Write /etc/activitywatch/aw-server.env - name: Записать /etc/activitywatch/aw-server.env
ansible.builtin.copy: ansible.builtin.copy:
dest: /etc/activitywatch/aw-server.env dest: /etc/activitywatch/aw-server.env
mode: "0640" mode: "0640"
owner: root
group: root
content: | content: |
AW_SERVER_HOST={{ aw_server_bind_host }} AW_SERVER_BIND_HOST={{ aw_server_bind_host }}
AW_SERVER_PORT={{ aw_server_port }} AW_SERVER_PORT={{ aw_server_port }}
AW_DATA_DIR={{ aw_server_data_dir }} AW_SERVER_DATA_DIR={{ aw_server_data_dir }}
AW_LOG_DIR={{ aw_server_log_dir }} AW_SERVER_LOG_DIR={{ aw_server_log_dir }}
AW_WEBUI_DIR={{ aw_server_webui_dir }} AW_SERVER_WEBUI_DIR={{ aw_server_webui_dir }}
AW_SERVER_USER={{ aw_server_user }} AW_SERVER_USER={{ aw_server_user }}
AW_SERVER_GROUP={{ aw_server_group }} AW_SERVER_GROUP={{ aw_server_group }}
- name: Enable and start service - name: Включить и запустить сервис
ansible.builtin.systemd: ansible.builtin.systemd:
name: activitywatch-server.service name: activitywatch-server.service
enabled: true enabled: true
state: restarted state: restarted
daemon_reload: true daemon_reload: true
- name: Wait for API - name: Дождаться ответа API
ansible.builtin.uri: 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/info"
method: GET method: GET
@@ -147,25 +257,25 @@
delay: 3 delay: 3
until: aw_api.status == 200 until: aw_api.status == 200
- name: Apply baseline worktime settings (classes) - name: Применить базовые worktime settings (classes)
ansible.builtin.uri: ansible.builtin.uri:
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/classes" url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/classes"
method: POST method: POST
body: "{{ aw_worktime_classes }}" body: "{{ aw_worktime_classes }}"
body_format: json body_format: json
status_code: 201 status_code: [200, 201]
when: aw_apply_worktime_settings | default(false) | bool when: aw_apply_worktime_settings | default(false) | bool
- name: Apply baseline views (include DLP and worktime) - name: Применить базовые views для DLP и worktime
ansible.builtin.uri: ansible.builtin.uri:
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/views" url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/views"
method: POST method: POST
body: "{{ aw_default_views }}" body: "{{ aw_default_views }}"
body_format: json body_format: json
status_code: 201 status_code: [200, 201]
when: aw_apply_worktime_settings | default(false) | bool 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: ansible.builtin.set_fact:
aw_worktime_from_h: "{{ (aw_worktime_from | default('08:00')).split(':')[0] | int }}" 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_from_m: "{{ (aw_worktime_from | default('08:00')).split(':')[1] | int }}"
@@ -182,7 +292,7 @@
}} }}
when: aw_apply_worktime_settings | default(false) | bool when: aw_apply_worktime_settings | default(false) | bool
- name: Normalize derived durationDefault for overnight shifts - name: Нормализовать durationDefault для ночных смен
ansible.builtin.set_fact: ansible.builtin.set_fact:
aw_worktime_duration_default_effective: >- aw_worktime_duration_default_effective: >-
{{ {{
@@ -192,15 +302,15 @@
}} }}
when: aw_apply_worktime_settings | default(false) | bool when: aw_apply_worktime_settings | default(false) | bool
- name: Validate derived durationDefault is sane - name: Проверить корректность durationDefault
ansible.builtin.assert: ansible.builtin.assert:
that: that:
- aw_worktime_duration_default_effective | int > 0 - aw_worktime_duration_default_effective | int > 0
- aw_worktime_duration_default_effective | int <= 86400 - 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 when: aw_apply_worktime_settings | default(false) | bool
- name: Apply baseline worktime period (startOfDay) - name: Применить базовый период worktime (startOfDay)
ansible.builtin.uri: ansible.builtin.uri:
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/startOfDay" url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/startOfDay"
method: POST method: POST
@@ -209,7 +319,7 @@
status_code: 200 status_code: 200
when: aw_apply_worktime_settings | default(false) | bool when: aw_apply_worktime_settings | default(false) | bool
- name: Apply baseline worktime period (durationDefault seconds) - name: Применить базовый период worktime (durationDefault seconds)
ansible.builtin.uri: ansible.builtin.uri:
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/durationDefault" url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/durationDefault"
method: POST method: POST
@@ -219,11 +329,11 @@
when: aw_apply_worktime_settings | default(false) | bool when: aw_apply_worktime_settings | default(false) | bool
handlers: handlers:
- name: Reload systemd - name: Перезагрузить systemd
ansible.builtin.systemd: ansible.builtin.systemd:
daemon_reload: true daemon_reload: true
- name: Restart activitywatch - name: Перезапустить activitywatch
ansible.builtin.systemd: ansible.builtin.systemd:
name: activitywatch-server.service name: activitywatch-server.service
state: restarted state: restarted
+62 -25
View File
@@ -1,13 +1,17 @@
--- ---
- name: Deploy AWatch-rus Windows phase2 collectors - name: Развернуть Windows/RDP phase-2 collector'ы AWatch-rus
hosts: aw_windows hosts: aw_windows
gather_facts: false gather_facts: false
vars: 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_deploy_root: "C:\\Deploy\\AWatch-rus"
aw_windows_server_scheme: "http"
aw_windows_server_host: "10.10.10.13" aw_windows_server_host: "10.10.10.13"
aw_windows_server_port: 5600 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_domain: "SHARKON2025"
aw_windows_users: aw_windows_users:
- user1 - user1
@@ -35,22 +39,24 @@
aw_windows_recovery_task_name: "ActivityWatch Recovery" aw_windows_recovery_task_name: "ActivityWatch Recovery"
aw_windows_force_task_restart: true aw_windows_force_task_restart: true
aw_windows_api_smoke_check_enabled: 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_api_smoke_check_limit: 10
aw_windows_fail_on_validation_error: true
tasks: tasks:
- name: Validate required variables - name: Проверить обязательные переменные
ansible.builtin.assert: ansible.builtin.assert:
that: that:
- aw_windows_server_host is defined - aw_windows_server_host is defined
- aw_windows_server_port is defined - aw_windows_server_port is defined
- aw_windows_server_scheme is defined
- aw_windows_domain is defined - aw_windows_domain is defined
- aw_windows_users_effective | length > 0 - aw_windows_users_effective | length > 0
- aw_windows_install_root is defined - aw_windows_install_root is defined
- aw_windows_state_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: ansible.windows.win_file:
path: "{{ item }}" path: "{{ item }}"
state: directory state: directory
@@ -58,7 +64,7 @@
- "{{ aw_windows_deploy_root }}" - "{{ aw_windows_deploy_root }}"
- "{{ aw_windows_deploy_root }}\\windows" - "{{ aw_windows_deploy_root }}\\windows"
- name: Upload Windows deployment toolkit - name: Загрузить Windows toolkit развёртывания
ansible.windows.win_copy: ansible.windows.win_copy:
src: "{{ aw_windows_repo_root }}/windows/{{ item }}" src: "{{ aw_windows_repo_root }}/windows/{{ item }}"
dest: "{{ aw_windows_deploy_root }}\\windows\\{{ item }}" dest: "{{ aw_windows_deploy_root }}\\windows\\{{ item }}"
@@ -67,6 +73,7 @@
- ActivityWatch.Windows.Common.psm1 - ActivityWatch.Windows.Common.psm1
- browser-domains-native-collector.ps1 - browser-domains-native-collector.ps1
- dlp-endpoint-signals-collector.ps1 - dlp-endpoint-signals-collector.ps1
- worktime-session-collector.ps1
- deploy-domain-users.ps1 - deploy-domain-users.ps1
- deploy-ensemble.ps1 - deploy-ensemble.ps1
- hardening-recovery.ps1 - hardening-recovery.ps1
@@ -74,24 +81,23 @@
- web-category-rules.example.json - web-category-rules.example.json
- dlp-policy.example.json - dlp-policy.example.json
- name: Upload user list for domain deploy - name: Загрузить список пользователей для доменного развёртывания
ansible.windows.win_copy: ansible.windows.win_copy:
dest: "{{ aw_windows_deploy_root }}\\windows\\users.txt" dest: "{{ aw_windows_deploy_root }}\\windows\\users.txt"
content: | content: |
{% for user in aw_windows_users -%} {% for user in aw_windows_users_effective -%}
{{ user }}
{% endfor -%}
{% for user in aw_windows_extra_users -%}
{{ user }} {{ user }}
{% endfor -%} {% endfor -%}
- name: Run phase2 ensemble deployment - name: Запустить phase-2 ensemble развёртывание
ansible.windows.win_powershell: ansible.windows.win_powershell:
script: | script: |
$ErrorActionPreference = 'Stop' $ErrorActionPreference = 'Stop'
$params = @{ $params = @{
ServerScheme = "{{ aw_windows_server_scheme }}"
ServerHost = "{{ aw_windows_server_host }}" ServerHost = "{{ aw_windows_server_host }}"
ServerPort = {{ aw_windows_server_port }} ServerPort = {{ aw_windows_server_port }}
Version = "{{ aw_windows_package_version }}"
Domain = "{{ aw_windows_domain }}" Domain = "{{ aw_windows_domain }}"
UserListPath = "{{ aw_windows_deploy_root }}\windows\users.txt" UserListPath = "{{ aw_windows_deploy_root }}\windows\users.txt"
InstallRoot = "{{ aw_windows_install_root }}" InstallRoot = "{{ aw_windows_install_root }}"
@@ -106,12 +112,18 @@
CustomRulesPath = "{{ aw_windows_rules_path }}" CustomRulesPath = "{{ aw_windows_rules_path }}"
CustomPolicyPath = "{{ aw_windows_policy_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 %} {% if aw_windows_skip_hardening | bool %}
$params.SkipHardening = $true $params.SkipHardening = $true
{% endif %} {% endif %}
& "{{ aw_windows_deploy_root }}\windows\deploy-ensemble.ps1" @params & "{{ 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 when: aw_windows_force_task_restart | bool
ansible.windows.win_powershell: ansible.windows.win_powershell:
script: | script: |
@@ -121,11 +133,33 @@
Where-Object TaskName -like "{{ aw_windows_launch_task_pattern }}" | Where-Object TaskName -like "{{ aw_windows_launch_task_pattern }}" |
ForEach-Object { Start-ScheduledTask -TaskName $_.TaskName } ForEach-Object { Start-ScheduledTask -TaskName $_.TaskName }
- name: Wait for fresh AFK events to appear on AW server - name: Получить Windows hostname для AW smoke-check bucket
when: aw_windows_api_smoke_check_enabled | bool 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 delegate_to: localhost
ansible.builtin.uri: 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 method: GET
return_content: true return_content: true
register: aw_windows_api_smoke register: aw_windows_api_smoke
@@ -141,29 +175,32 @@
retries: 10 retries: 10
delay: 6 delay: 6
- name: Run validation and store report on target - name: Выполнить валидацию и сохранить отчёт на целевом Windows host
ansible.windows.win_powershell: ansible.windows.win_powershell:
script: | script: |
$ErrorActionPreference = 'Stop' $ErrorActionPreference = 'Stop'
$report = & "{{ aw_windows_deploy_root }}\windows\validate-deployment.ps1" ` $report = & "{{ aw_windows_deploy_root }}\windows\validate-deployment.ps1" `
-ConfigPath "{{ aw_windows_state_root }}\deployment-config.json" -ConfigPath "{{ aw_windows_state_root }}\deployment-config.json"
$report | ConvertTo-Json -Depth 12 | Out-File -FilePath "{{ aw_windows_validation_remote_path }}" -Encoding utf8 $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: ansible.builtin.file:
path: "{{ aw_windows_validation_local_dir }}" path: "{{ aw_windows_validation_local_dir }}"
state: directory state: directory
mode: "0755" mode: "0755"
delegate_to: localhost delegate_to: localhost
- name: Fetch validation report - name: Забрать validation report
ansible.builtin.fetch: ansible.builtin.fetch:
src: "{{ aw_windows_validation_remote_path }}" src: "{{ aw_windows_validation_remote_path }}"
dest: "{{ aw_windows_validation_local_dir }}/" dest: "{{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_phase2_ansible.json"
flat: false flat: true
- name: Show report location - name: Показать путь к отчёту
ansible.builtin.debug: ansible.builtin.debug:
msg: msg:
- "Windows phase2 deploy completed on {{ inventory_hostname }}." - "Windows phase2 развёртывание завершено на {{ inventory_hostname }}."
- "Validation report: {{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}/C$/Windows/Temp/aw_validate_phase2_ansible.json" - "Отчёт проверки: {{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_phase2_ansible.json"
+7 -7
View File
@@ -8,17 +8,17 @@ aw_server_log_dir: "/var/log/activitywatch"
aw_server_user: "activitywatch" aw_server_user: "activitywatch"
aw_server_group: "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. # Опционально: применить базовые категории и views для рабочего времени через AW settings API.
# WARNING: this overwrites existing server-side settings/classes/views. # Внимание: это перезаписывает существующие server-side settings/classes/views.
aw_apply_worktime_settings: false aw_apply_worktime_settings: false
# Optional defaults for the worktime period in Web UI. # Опциональные значения периода рабочего времени в Web UI.
# startOfDay controls day-boundary and default report window start. # startOfDay задаёт границу дня и стартовое время окна отчёта.
# durationDefault controls default time range (seconds) shown in UI. # durationDefault задаёт диапазон по умолчанию в секундах.
# #
# Recommended: set worktime window explicitly and let the playbook derive duration. # Рекомендуется явно задать рабочий интервал и дать playbook вычислить duration.
aw_worktime_from: "08:00" aw_worktime_from: "08:00"
aw_worktime_to: "17:00" aw_worktime_to: "17:00"
aw_worktime_start_of_day: "{{ aw_worktime_from }}" aw_worktime_start_of_day: "{{ aw_worktime_from }}"
+12 -1
View File
@@ -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_deploy_root: "C:\\Deploy\\AWatch-rus"
aw_windows_server_scheme: "http"
aw_windows_server_host: "10.10.10.13" aw_windows_server_host: "10.10.10.13"
aw_windows_server_port: 5600 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_domain: "SHARKON2025"
aw_windows_users: aw_windows_users:
- user1 - 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_remote_path: "C:\\Windows\\Temp\\aw_validate_phase2_ansible.json"
aw_windows_validation_local_dir: "/tmp/aw-rus-validation" aw_windows_validation_local_dir: "/tmp/aw-rus-validation"
aw_windows_fail_on_validation_error: true
# По умолчанию AFK bucket вычисляется как aw-watcher-afk_<COMPUTERNAME>.
# Задайте явное значение только если watcher пишет в нестандартный bucket.
aw_windows_api_smoke_check_enabled: true
aw_windows_api_smoke_check_bucket: ""
aw_windows_api_smoke_check_limit: 10
+9 -9
View File
@@ -1,14 +1,14 @@
--- ---
# Full-stack installer for AWatch-rus. # Полный установщик AWatch-rus.
# Runs end-to-end rollout in one command: # Выполняет развёртывание одной командой:
# 1) Proxmox CT provision + AW bootstrap (if [proxmox] exists in inventory) # 1) создание Proxmox CT + bootstrap AW (если в inventory есть [proxmox])
# 2) AW server deploy on [aw_server] hosts # 2) развёртывание AW server на хостах [aw_server]
# 3) Windows phase2 rollout on [aw_windows] hosts # 3) развёртывание Windows/RDP collector'ов на [aw_windows]
# 4) pfSense poller deploy on [aw_pfsense_pollers] hosts # 4) развёртывание pfSense poller'а на [aw_pfsense_pollers]
# #
# Notes: # Примечания:
# - Keep only relevant inventory groups filled for your environment. # - Заполняйте только нужные группы inventory для своего окружения.
# - Plays with unmatched host groups are skipped automatically by Ansible. # - Play без совпадающих host groups Ansible пропускает автоматически.
- import_playbook: provision_proxmox_ct_and_deploy_aw.yml - import_playbook: provision_proxmox_ct_and_deploy_aw.yml
- import_playbook: deploy_aw_server.yml - import_playbook: deploy_aw_server.yml
+4 -1
View File
@@ -5,5 +5,8 @@ pve-main ansible_host=192.168.10.2 ansible_user=root ansible_port=22
aw-ct ansible_host=10.20.30.13 ansible_user=root ansible_port=22 aw-ct ansible_host=10.20.30.13 ansible_user=root ansible_port=22
[aw_windows] [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 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
@@ -1,5 +1,5 @@
--- ---
- name: Provision single Proxmox CT and deploy AWatch-rus - name: Создать один Proxmox CT и развернуть AWatch-rus
hosts: proxmox hosts: proxmox
gather_facts: false gather_facts: false
@@ -17,7 +17,7 @@
- settings/views-default.json - settings/views-default.json
tasks: tasks:
- name: Execute single-CT provisioning workflow - name: Выполнить workflow создания одного CT
ansible.builtin.include_tasks: tasks/provision_ct_and_deploy_aw.yml ansible.builtin.include_tasks: tasks/provision_ct_and_deploy_aw.yml
vars: vars:
ct_id: "{{ proxmox_ct_id }}" ct_id: "{{ proxmox_ct_id }}"
@@ -1,5 +1,5 @@
--- ---
- name: Provision Proxmox CT matrix and deploy AWatch-rus with RU patch - name: Создать матрицу Proxmox CT и развернуть AWatch-rus с RU patch
hosts: proxmox hosts: proxmox
gather_facts: false gather_facts: false
@@ -17,14 +17,14 @@
- settings/views-default.json - settings/views-default.json
tasks: tasks:
- name: Validate CT matrix is provided - name: Проверить, что матрица CT задана
ansible.builtin.assert: ansible.builtin.assert:
that: that:
- proxmox_ct_matrix is defined - proxmox_ct_matrix is defined
- proxmox_ct_matrix | length > 0 - 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 ansible.builtin.include_tasks: tasks/provision_ct_and_deploy_aw.yml
vars: vars:
ct_id: "{{ item.id }}" ct_id: "{{ item.id }}"
+33 -23
View File
@@ -1,5 +1,5 @@
--- ---
- name: Validate required per-CT variables - name: Проверить обязательные переменные CT
ansible.builtin.assert: ansible.builtin.assert:
that: that:
- ct_id is defined - ct_id is defined
@@ -27,14 +27,14 @@
- aw_server_log_dir is defined - aw_server_log_dir is defined
- aw_server_user is defined - aw_server_user is defined
- aw_server_group 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: ansible.builtin.set_fact:
ct_net0: >- 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=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: ansible.builtin.command:
argv: argv:
- pct - pct
@@ -44,7 +44,7 @@
failed_when: false failed_when: false
changed_when: false changed_when: false
- name: Create CT when absent - name: Создать CT, если он отсутствует
ansible.builtin.command: ansible.builtin.command:
argv: argv:
- pct - pct
@@ -78,8 +78,9 @@
- --ostype - --ostype
- debian - debian
when: ct_status_check.rc != 0 when: ct_status_check.rc != 0
no_log: true
- name: Check current CT runtime state - name: Проверить текущее состояние CT
ansible.builtin.command: ansible.builtin.command:
argv: argv:
- pct - pct
@@ -88,7 +89,7 @@
register: ct_runtime_status register: ct_runtime_status
changed_when: false changed_when: false
- name: Start CT when stopped - name: Запустить CT, если он остановлен
ansible.builtin.command: ansible.builtin.command:
argv: argv:
- pct - pct
@@ -96,20 +97,23 @@
- "{{ ct_id }}" - "{{ ct_id }}"
when: "'stopped' in ct_runtime_status.stdout" when: "'stopped' in ct_runtime_status.stdout"
- name: Ensure bootstrap directory on Proxmox host - name: Создать bootstrap каталог на Proxmox host
ansible.builtin.file: ansible.builtin.file:
path: "{{ proxmox_bootstrap_dir }}" path: "{{ item }}"
state: directory state: directory
mode: "0700" 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: ansible.builtin.copy:
src: "{{ aw_repo_root }}/aw-server/{{ item }}" src: "{{ aw_repo_root }}/aw-server/{{ item }}"
dest: "{{ proxmox_bootstrap_dir }}/{{ item }}" dest: "{{ proxmox_bootstrap_dir }}/{{ item }}"
mode: "0644" mode: "0644"
loop: "{{ aw_bootstrap_files }}" loop: "{{ aw_bootstrap_files }}"
- name: Bootstrap CT OS dependencies - name: Установить базовые зависимости ОС внутри CT
ansible.builtin.command: ansible.builtin.command:
argv: argv:
- pct - pct
@@ -122,12 +126,16 @@
set -euo pipefail set -euo pipefail
export DEBIAN_FRONTEND=noninteractive export DEBIAN_FRONTEND=noninteractive
apt-get update apt-get update
apt-get install -y curl ca-certificates bash unzip xz-utils jq rsync openssh-server apt-get install -y curl ca-certificates bash unzip xz-utils jq rsync openssh-server python3
mkdir -p /root/bootstrap /etc/activitywatch mkdir -p /root/bootstrap/settings /etc/activitywatch
systemctl enable ssh || true systemctl enable ssh || true
systemctl restart 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: ansible.builtin.command:
argv: argv:
- pct - pct
@@ -137,7 +145,7 @@
- "/root/bootstrap/{{ item }}" - "/root/bootstrap/{{ item }}"
loop: "{{ aw_bootstrap_files }}" loop: "{{ aw_bootstrap_files }}"
- name: Write AW server env file on Proxmox host temp - name: Записать AW server env во временный каталог Proxmox host
ansible.builtin.copy: ansible.builtin.copy:
dest: "{{ proxmox_bootstrap_dir }}/aw-server.env" dest: "{{ proxmox_bootstrap_dir }}/aw-server.env"
mode: "0600" mode: "0600"
@@ -151,8 +159,9 @@
AW_SERVER_LOG_DIR={{ aw_server_log_dir }} AW_SERVER_LOG_DIR={{ aw_server_log_dir }}
AW_SERVER_USER={{ aw_server_user }} AW_SERVER_USER={{ aw_server_user }}
AW_SERVER_GROUP={{ aw_server_group }} AW_SERVER_GROUP={{ aw_server_group }}
no_log: true
- name: Push AW server env into CT - name: Передать AW server env внутрь CT
ansible.builtin.command: ansible.builtin.command:
argv: argv:
- pct - pct
@@ -160,8 +169,9 @@
- "{{ ct_id }}" - "{{ ct_id }}"
- "{{ proxmox_bootstrap_dir }}/aw-server.env" - "{{ proxmox_bootstrap_dir }}/aw-server.env"
- /etc/activitywatch/aw-server.env - /etc/activitywatch/aw-server.env
no_log: true
- name: Set mode for env inside CT - name: Настроить права env файла внутри CT
ansible.builtin.command: ansible.builtin.command:
argv: argv:
- pct - pct
@@ -172,7 +182,7 @@
- "0600" - "0600"
- /etc/activitywatch/aw-server.env - /etc/activitywatch/aw-server.env
- name: Install server and apply RU patch inside CT - name: Установить сервер и применить RU patch внутри CT
ansible.builtin.command: ansible.builtin.command:
argv: argv:
- pct - pct
@@ -188,7 +198,7 @@
bash /root/bootstrap/apply_webui_ru_patch.sh bash /root/bootstrap/apply_webui_ru_patch.sh
systemctl restart activitywatch-server.service systemctl restart activitywatch-server.service
- name: Validate AW API from inside CT - name: Проверить AW API изнутри CT
ansible.builtin.command: ansible.builtin.command:
argv: argv:
- pct - pct
@@ -199,7 +209,7 @@
- -lc - -lc
- "curl -fsS http://127.0.0.1:{{ aw_server_port }}/api/0/info >/dev/null" - "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: ansible.builtin.command:
argv: argv:
- pct - pct
@@ -210,8 +220,8 @@
- -lc - -lc
- "grep -q 'ru-patch-v5.js' {{ aw_server_webui_dir }}/index.html && grep -q 'sw-cleanup.js' {{ aw_server_webui_dir }}/index.html" - "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: ansible.builtin.debug:
msg: msg:
- "CT {{ ct_id }} is provisioned and configured." - "CT {{ ct_id }} создан и настроен."
- "ActivityWatch endpoint: http://{{ ct_ip | regex_replace('/[0-9]+$', '') }}:{{ aw_server_port }}" - "Endpoint ActivityWatch: http://{{ ct_ip | regex_replace('/[0-9]+$', '') }}:{{ aw_server_port }}"
@@ -1,82 +1,101 @@
# Ansible ensemble for AWatch-rus # Ansible ensemble for AWatch-rus
Эта директория содержит Ansible-ensemble для двух сценариев: Эта директория содержит Ansible-ensemble для полного развёртывания AWatch-rus:
- деплой на уже существующий Debian host/CT; - деплой на уже существующий Debian host/CT;
- полный цикл с нуля в Proxmox: создание CT + bootstrap + установка ActivityWatch + RU patch. - полный цикл с нуля в Proxmox: создание CT + bootstrap + установка ActivityWatch + RU patch;
- централизованный деплой Windows phase-2 collectors по WinRM. - централизованное развёртывание Windows phase-2 collector'ов по WinRM;
- deployment внешнего pfSense poller'а на Debian/Ubuntu utility VM. - развёртывание внешнего pfSense poller'а на Debian/Ubuntu utility VM.
## Файлы ## Файлы
- `/home/igor/tmp/AWatch-rus/ansible/deploy_aw_server.yml` — основной playbook. - `ansible/deploy_aw_server.yml` — основной playbook для уже существующего Debian/CT host.
- `/home/igor/tmp/AWatch-rus/ansible/provision_proxmox_ct_and_deploy_aw.yml`full-stack playbook для Proxmox. - `ansible/provision_proxmox_ct_and_deploy_aw.yml`полный playbook для Proxmox.
- `/home/igor/tmp/AWatch-rus/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml` — массовый full-stack playbook (несколько CT). - `ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml` — массовый полный playbook (несколько CT).
- `/home/igor/tmp/AWatch-rus/ansible/deploy_aw_windows_phase2.yml` — WinRM playbook для развёртывания phase-2 Windows collector'ов. - `ansible/deploy_aw_windows_phase2.yml` — WinRM playbook для развёртывания Windows/RDP collector'ов.
- `/home/igor/tmp/AWatch-rus/ansible/deploy_aw_pfsense_poller.yml`deployment pfSense poller'а. - `ansible/deploy_aw_pfsense_poller.yml`развёртывание pfSense poller'а.
- `/home/igor/tmp/AWatch-rus/ansible/inventory.example.ini` — шаблон inventory. - `ansible/install_full_stack.yml` — полный установочный playbook (оркестратор всех этапов).
- `/home/igor/tmp/AWatch-rus/ansible/group_vars/all.example.yml` — шаблон переменных. - `ansible/inventory.example.ini` — шаблон inventory.
- `/home/igor/tmp/AWatch-rus/ansible/group_vars/proxmox.example.yml` — шаблон переменных CT в Proxmox. - `ansible/group_vars/*.example.yml` — шаблоны переменных.
- `/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'а.
## Быстрый запуск ## Быстрый запуск
1. Скопируйте шаблоны: 1. Скопируйте шаблоны:
- `cp /home/igor/tmp/AWatch-rus/ansible/inventory.example.ini /home/igor/tmp/AWatch-rus/ansible/inventory.ini` - `cp ansible/inventory.example.ini 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/group_vars/all.example.yml ansible/group_vars/all.yml`
2. Заполните значения в `inventory.ini` и `group_vars/all.yml`. 2. Заполните значения в `inventory.ini` и `group_vars/all.yml`.
3. Запустите: 3. Запустите:
```bash ```bash
cd /home/igor/tmp/AWatch-rus/ansible cd ansible
ansible-playbook -i inventory.ini deploy_aw_server.yml 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 ## Полный запуск с нуля в Proxmox
1. Подготовьте inventory и vars: 1. Подготовьте inventory и vars:
- `cp /home/igor/tmp/AWatch-rus/ansible/inventory.example.ini /home/igor/tmp/AWatch-rus/ansible/inventory.ini` - `cp ansible/inventory.example.ini 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/group_vars/all.example.yml 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/group_vars/proxmox.example.yml ansible/group_vars/proxmox.yml`
2. Заполните `group_vars/proxmox.yml` и `group_vars/all.yml`. 2. Заполните `group_vars/proxmox.yml` и `group_vars/all.yml`.
3. Запустите playbook: 3. Запустите playbook:
```bash ```bash
cd /home/igor/tmp/AWatch-rus/ansible cd ansible
ansible-playbook -i inventory.ini provision_proxmox_ct_and_deploy_aw.yml ansible-playbook -i inventory.ini provision_proxmox_ct_and_deploy_aw.yml
``` ```
## Массовый запуск (матрица CT) ## Массовый запуск (матрица CT)
1. Подготовьте матрицу: 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`. 2. Заполните `proxmox-matrix.yml`.
3. Запустите: 3. Запустите:
```bash ```bash
cd /home/igor/tmp/AWatch-rus/ansible cd ansible
ansible-playbook -i inventory.ini provision_proxmox_ct_matrix_and_deploy_aw.yml ansible-playbook -i inventory.ini provision_proxmox_ct_matrix_and_deploy_aw.yml
``` ```
## Windows phase-2 rollout (WinRM) ## Windows phase-2 rollout (WinRM)
1. Подготовьте inventory и vars: 1. Подготовьте inventory и vars:
- `cp /home/igor/tmp/AWatch-rus/ansible/inventory.example.ini /home/igor/tmp/AWatch-rus/ansible/inventory.ini` - `cp ansible/inventory.example.ini 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/group_vars/windows.example.yml ansible/group_vars/windows.yml`
2. Заполните `inventory.ini` (секция `[aw_windows]`) и `group_vars/windows.yml`. 2. Заполните `inventory.ini` (секция `[aw_windows]`) и `group_vars/windows.yml`.
- Для русской локализации Windows часто нужен `ansible_user=Администратор` (а не `Administrator`).
- Если WinRM закрыт, playbook не сможет стартовать и нужно сначала открыть `5985/5986` и `wsman`.
3. Запустите: 3. Запустите:
```bash ```bash
cd /home/igor/tmp/AWatch-rus/ansible cd ansible
ansible-playbook -i inventory.ini deploy_aw_windows_phase2.yml ansible-playbook -i inventory.ini deploy_aw_windows_phase2.yml
``` ```
Playbook: 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-ensemble.ps1` (deploy + hardening/recovery) с phase-2 policy/rules;
- после deploy принудительно запускает `ActivityWatch Recovery` и все `ActivityWatch Launch *` задачи;
- выполняет API smoke-check bucket `aw-watcher-afk_<COMPUTERNAME>` и ожидает свежие `not-afk` события;
- запускает `validate-deployment.ps1`; - запускает `validate-deployment.ps1`;
- забирает JSON-отчёт в локальную директорию (`/tmp/aw-rus-validation` по умолчанию). - забирает JSON-отчёт в локальную директорию (`/tmp/aw-rus-validation` по умолчанию).
@@ -87,17 +106,20 @@ Playbook:
- `aw_windows_incident_capture_enabled: false` — отключить блок incidentCapture; - `aw_windows_incident_capture_enabled: false` — отключить блок incidentCapture;
- `aw_windows_incident_screenshot_enabled: false` — не делать скриншот при DLP-инциденте; - `aw_windows_incident_screenshot_enabled: false` — не делать скриншот при DLP-инциденте;
- `aw_windows_incident_artifacts_root: 'C:\...\incident-artifacts'` — переопределить путь артефактов; - `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_<COMPUTERNAME>`;
- `aw_windows_fail_on_validation_error: true` — завершать playbook ошибкой, если `validate-deployment.ps1` возвращает `overallOk=false`;
- `aw_windows_skip_hardening: true` — пропустить `hardening-recovery.ps1` внутри ensemble-скрипта. - `aw_windows_skip_hardening: true` — пропустить `hardening-recovery.ps1` внутри ensemble-скрипта.
## pfSense poller rollout ## Развёртывание pfSense poller
1. Подготовьте vars: 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]`. 2. Добавьте inventory group `[aw_pfsense_pollers]`.
3. Запустите: 3. Запустите:
```bash ```bash
cd /home/igor/tmp/AWatch-rus/ansible cd ansible
ansible-playbook -i inventory.ini deploy_aw_pfsense_poller.yml 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/русскую статику после деплоя. - Для Web UI используется checksum-based cache-bust для `ru-patch-v5.js` и `sw-cleanup.js`, чтобы браузер не держал старую DLP/русскую статику после деплоя.
- На `#/home` Web UI делит хосты на `Windows RDP` и `Virtual servers + Proxmox`. - На `#/home` Web UI делит хосты на `Windows RDP` и `Virtual servers + Proxmox`.
- Выполнена валидация API `http://127.0.0.1:5600/api/0/info`. - Выполнена валидация 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`.
@@ -1,5 +1,5 @@
--- ---
- name: Deploy pfSense ActivityWatch poller - name: Развернуть pfSense ActivityWatch poller
hosts: aw_pfsense_pollers hosts: aw_pfsense_pollers
become: true become: true
gather_facts: true gather_facts: true
@@ -10,14 +10,14 @@
aw_pfsense_service_name: "aw-pfsense-poller.service" aw_pfsense_service_name: "aw-pfsense-poller.service"
tasks: tasks:
- name: Install required packages - name: Установить обязательные пакеты
ansible.builtin.apt: ansible.builtin.apt:
name: name:
- python3 - python3
state: present state: present
update_cache: true update_cache: true
- name: Ensure directories exist - name: Создать каталоги
ansible.builtin.file: ansible.builtin.file:
path: "{{ item }}" path: "{{ item }}"
state: directory state: directory
@@ -26,29 +26,29 @@
- "{{ aw_pfsense_install_root }}" - "{{ aw_pfsense_install_root }}"
- "{{ aw_pfsense_config_dir }}" - "{{ aw_pfsense_config_dir }}"
- name: Install pfSense poller script - name: Установить скрипт pfSense poller
ansible.builtin.copy: ansible.builtin.copy:
src: "{{ aw_repo_root }}/pfsense/pfsense-aw-poller.py" src: "{{ aw_repo_root }}/pfsense/pfsense-aw-poller.py"
dest: "{{ aw_pfsense_install_root }}/pfsense-aw-poller.py" dest: "{{ aw_pfsense_install_root }}/pfsense-aw-poller.py"
mode: "0755" mode: "0755"
- name: Install systemd service - name: Установить systemd service
ansible.builtin.copy: ansible.builtin.copy:
src: "{{ aw_repo_root }}/pfsense/pfsense-aw-poller.service" src: "{{ aw_repo_root }}/pfsense/pfsense-aw-poller.service"
dest: "/etc/systemd/system/{{ aw_pfsense_service_name }}" dest: "/etc/systemd/system/{{ aw_pfsense_service_name }}"
mode: "0644" mode: "0644"
notify: notify:
- Reload systemd - Перезагрузить systemd
- name: Write pfSense poller config - name: Записать конфигурацию pfSense poller
ansible.builtin.copy: ansible.builtin.copy:
dest: "{{ aw_pfsense_config_dir }}/poller.json" dest: "{{ aw_pfsense_config_dir }}/poller.json"
mode: "0600" mode: "0600"
content: "{{ aw_pfsense_poller_config | to_nice_json }}" content: "{{ aw_pfsense_poller_config | to_nice_json }}"
notify: notify:
- Restart pfSense poller - Перезапустить pfSense poller
- name: Enable and start pfSense poller - name: Включить и запустить pfSense poller
ansible.builtin.systemd: ansible.builtin.systemd:
name: "{{ aw_pfsense_service_name }}" name: "{{ aw_pfsense_service_name }}"
enabled: true enabled: true
@@ -56,11 +56,11 @@
daemon_reload: true daemon_reload: true
handlers: handlers:
- name: Reload systemd - name: Перезагрузить systemd
ansible.builtin.systemd: ansible.builtin.systemd:
daemon_reload: true daemon_reload: true
- name: Restart pfSense poller - name: Перезапустить pfSense poller
ansible.builtin.systemd: ansible.builtin.systemd:
name: "{{ aw_pfsense_service_name }}" name: "{{ aw_pfsense_service_name }}"
state: restarted state: restarted
@@ -1,5 +1,5 @@
--- ---
- name: Deploy AWatch-rus server - name: Развернуть сервер AWatch-rus
hosts: aw_server hosts: aw_server
become: true become: true
gather_facts: true gather_facts: true
@@ -9,22 +9,29 @@
aw_release_dir: "{{ aw_release_root }}/{{ aw_server_version }}" aw_release_dir: "{{ aw_release_root }}/{{ aw_server_version }}"
aw_archive_path: "/tmp/activitywatch-{{ aw_server_version }}.zip" aw_archive_path: "/tmp/activitywatch-{{ aw_server_version }}.zip"
aw_bootstrap_dir: "/tmp/aw-rus-bootstrap" 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_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_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_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 }}" aw_default_views: "{{ lookup('file', aw_repo_root + '/aw-server/settings/views-default.json') | from_json }}"
tasks: tasks:
- name: Install base packages - name: Установить базовые пакеты
ansible.builtin.apt: ansible.builtin.apt:
name: name:
- curl - curl
- rsync
- unzip - unzip
state: present state: present
update_cache: true update_cache: true
- name: Ensure service account exists - name: Создать системную группу сервиса
ansible.builtin.group:
name: "{{ aw_server_group }}"
system: true
state: present
- name: Создать системную учётную запись сервиса
ansible.builtin.user: ansible.builtin.user:
name: "{{ aw_server_user }}" name: "{{ aw_server_user }}"
group: "{{ aw_server_group }}" group: "{{ aw_server_group }}"
@@ -33,7 +40,25 @@
system: true system: true
create_home: false 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: ansible.builtin.file:
path: "{{ item }}" path: "{{ item }}"
state: directory state: directory
@@ -41,103 +66,188 @@
group: "{{ aw_server_group }}" group: "{{ aw_server_group }}"
mode: "0755" mode: "0755"
loop: loop:
- /opt/activitywatch
- /opt/activitywatch/bin
- "{{ aw_release_root }}" - "{{ aw_release_root }}"
- "{{ aw_release_dir }}" - "{{ aw_release_dir }}"
- "{{ aw_release_install_dir }}"
- "{{ aw_server_webui_dir }}" - "{{ aw_server_webui_dir }}"
- "{{ aw_server_webui_dir }}/js"
- "{{ aw_server_data_dir }}" - "{{ aw_server_data_dir }}"
- "{{ aw_server_log_dir }}" - "{{ aw_server_log_dir }}"
- /etc/activitywatch
- "{{ aw_bootstrap_dir }}"
- name: Download ActivityWatch release archive - name: Скачать архив релиза ActivityWatch
ansible.builtin.get_url: ansible.builtin.get_url:
url: "{{ aw_server_download_url }}" url: "{{ aw_server_download_url }}"
dest: "{{ aw_archive_path }}" dest: "{{ aw_archive_path }}"
mode: "0644" mode: "0644"
- name: Unpack ActivityWatch release - name: Распаковать релиз ActivityWatch
ansible.builtin.unarchive: ansible.builtin.unarchive:
src: "{{ aw_archive_path }}" src: "{{ aw_archive_path }}"
dest: "{{ aw_release_dir }}" dest: "{{ aw_release_dir }}"
remote_src: true remote_src: true
extra_opts: ["-o"] extra_opts: ["-o"]
- name: Discover extracted AW directory - name: Найти распакованный каталог ActivityWatch
ansible.builtin.find: ansible.builtin.find:
paths: "{{ aw_release_dir }}" paths: "{{ aw_release_dir }}"
file_type: directory file_type: directory
patterns: "activitywatch*" patterns: "activitywatch*"
register: aw_release_find register: aw_release_find
- name: Set release extracted path - name: Найти бинарный файл AW server
ansible.builtin.set_fact: ansible.builtin.find:
aw_release_extracted: "{{ (aw_release_find.files | sort(attribute='path') | map(attribute='path') | list | first) }}" 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: ansible.builtin.assert:
that: that:
- aw_release_extracted is defined - aw_release_extracted is defined
- aw_release_extracted | length > 0 - 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: 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: ansible.builtin.copy:
src: "{{ item.src }}" src: "{{ item.src }}"
dest: "{{ item.dest }}" dest: "{{ item.dest }}"
mode: "{{ item.mode }}" mode: "{{ item.mode }}"
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
loop: 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-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-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" } - { 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 - name: Проверить наличие index.html после копирования
ansible.builtin.copy: ansible.builtin.stat:
remote_src: true path: "{{ aw_server_webui_dir }}/index.html"
src: "/opt/activitywatch/aw-webui/index.html" register: aw_webui_ru_index
dest: "{{ aw_server_webui_dir }}/index.html"
mode: "0644"
- 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: '<script[^>]+(?:ru-patch-v5\.js|sw-cleanup\.js|aw-ru-patch\.js|aw-sw-cleanup\.js)[^>]*></script>'
replace: ''
- name: Добавить cleanup script RU patch в index.html
ansible.builtin.replace: ansible.builtin.replace:
path: "{{ aw_server_webui_dir }}/index.html" path: "{{ aw_server_webui_dir }}/index.html"
regexp: '</head>' regexp: '</head>'
replace: '<script src="/js/sw-cleanup.js?v={{ aw_sw_cleanup_cache_bust }}"></script></head>' replace: '<script src="/js/sw-cleanup.js?v={{ aw_sw_cleanup_cache_bust }}"></script></head>'
- name: Insert RU patch loader before body end - name: Добавить загрузчик RU patch перед закрытием body
ansible.builtin.replace: ansible.builtin.replace:
path: "{{ aw_server_webui_dir }}/index.html" path: "{{ aw_server_webui_dir }}/index.html"
regexp: '</body>' regexp: '</body>'
replace: '<script defer="defer" src="/js/ru-patch-v5.js?v={{ aw_ru_patch_cache_bust }}"></script></body>' replace: '<script defer="defer" src="/js/ru-patch-v5.js?v={{ aw_ru_patch_cache_bust }}"></script></body>'
- name: Write /etc/activitywatch/aw-server.env - name: Записать /etc/activitywatch/aw-server.env
ansible.builtin.copy: ansible.builtin.copy:
dest: /etc/activitywatch/aw-server.env dest: /etc/activitywatch/aw-server.env
mode: "0640" mode: "0640"
owner: root
group: root
content: | content: |
AW_SERVER_HOST={{ aw_server_bind_host }} AW_SERVER_BIND_HOST={{ aw_server_bind_host }}
AW_SERVER_PORT={{ aw_server_port }} AW_SERVER_PORT={{ aw_server_port }}
AW_DATA_DIR={{ aw_server_data_dir }} AW_SERVER_DATA_DIR={{ aw_server_data_dir }}
AW_LOG_DIR={{ aw_server_log_dir }} AW_SERVER_LOG_DIR={{ aw_server_log_dir }}
AW_WEBUI_DIR={{ aw_server_webui_dir }} AW_SERVER_WEBUI_DIR={{ aw_server_webui_dir }}
AW_SERVER_USER={{ aw_server_user }} AW_SERVER_USER={{ aw_server_user }}
AW_SERVER_GROUP={{ aw_server_group }} AW_SERVER_GROUP={{ aw_server_group }}
- name: Enable and start service - name: Включить и запустить сервис
ansible.builtin.systemd: ansible.builtin.systemd:
name: activitywatch-server.service name: activitywatch-server.service
enabled: true enabled: true
state: restarted state: restarted
daemon_reload: true daemon_reload: true
- name: Wait for API - name: Дождаться ответа API
ansible.builtin.uri: 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/info"
method: GET method: GET
@@ -147,25 +257,25 @@
delay: 3 delay: 3
until: aw_api.status == 200 until: aw_api.status == 200
- name: Apply baseline worktime settings (classes) - name: Применить базовые worktime settings (classes)
ansible.builtin.uri: ansible.builtin.uri:
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/classes" url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/classes"
method: POST method: POST
body: "{{ aw_worktime_classes }}" body: "{{ aw_worktime_classes }}"
body_format: json body_format: json
status_code: 201 status_code: [200, 201]
when: aw_apply_worktime_settings | default(false) | bool when: aw_apply_worktime_settings | default(false) | bool
- name: Apply baseline views (include DLP and worktime) - name: Применить базовые views для DLP и worktime
ansible.builtin.uri: ansible.builtin.uri:
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/views" url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/views"
method: POST method: POST
body: "{{ aw_default_views }}" body: "{{ aw_default_views }}"
body_format: json body_format: json
status_code: 201 status_code: [200, 201]
when: aw_apply_worktime_settings | default(false) | bool 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: ansible.builtin.set_fact:
aw_worktime_from_h: "{{ (aw_worktime_from | default('08:00')).split(':')[0] | int }}" 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_from_m: "{{ (aw_worktime_from | default('08:00')).split(':')[1] | int }}"
@@ -182,7 +292,7 @@
}} }}
when: aw_apply_worktime_settings | default(false) | bool when: aw_apply_worktime_settings | default(false) | bool
- name: Normalize derived durationDefault for overnight shifts - name: Нормализовать durationDefault для ночных смен
ansible.builtin.set_fact: ansible.builtin.set_fact:
aw_worktime_duration_default_effective: >- aw_worktime_duration_default_effective: >-
{{ {{
@@ -192,15 +302,15 @@
}} }}
when: aw_apply_worktime_settings | default(false) | bool when: aw_apply_worktime_settings | default(false) | bool
- name: Validate derived durationDefault is sane - name: Проверить корректность durationDefault
ansible.builtin.assert: ansible.builtin.assert:
that: that:
- aw_worktime_duration_default_effective | int > 0 - aw_worktime_duration_default_effective | int > 0
- aw_worktime_duration_default_effective | int <= 86400 - 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 when: aw_apply_worktime_settings | default(false) | bool
- name: Apply baseline worktime period (startOfDay) - name: Применить базовый период worktime (startOfDay)
ansible.builtin.uri: ansible.builtin.uri:
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/startOfDay" url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/startOfDay"
method: POST method: POST
@@ -209,7 +319,7 @@
status_code: 200 status_code: 200
when: aw_apply_worktime_settings | default(false) | bool when: aw_apply_worktime_settings | default(false) | bool
- name: Apply baseline worktime period (durationDefault seconds) - name: Применить базовый период worktime (durationDefault seconds)
ansible.builtin.uri: ansible.builtin.uri:
url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/durationDefault" url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/durationDefault"
method: POST method: POST
@@ -219,11 +329,11 @@
when: aw_apply_worktime_settings | default(false) | bool when: aw_apply_worktime_settings | default(false) | bool
handlers: handlers:
- name: Reload systemd - name: Перезагрузить systemd
ansible.builtin.systemd: ansible.builtin.systemd:
daemon_reload: true daemon_reload: true
- name: Restart activitywatch - name: Перезапустить activitywatch
ansible.builtin.systemd: ansible.builtin.systemd:
name: activitywatch-server.service name: activitywatch-server.service
state: restarted state: restarted
@@ -1,13 +1,17 @@
--- ---
- name: Deploy AWatch-rus Windows phase2 collectors - name: Развернуть Windows/RDP phase-2 collector'ы AWatch-rus
hosts: aw_windows hosts: aw_windows
gather_facts: false gather_facts: false
vars: 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_deploy_root: "C:\\Deploy\\AWatch-rus"
aw_windows_server_scheme: "http"
aw_windows_server_host: "10.10.10.13" aw_windows_server_host: "10.10.10.13"
aw_windows_server_port: 5600 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_domain: "SHARKON2025"
aw_windows_users: aw_windows_users:
- user1 - user1
@@ -31,20 +35,28 @@
aw_windows_policy_path: "{{ aw_windows_deploy_root }}\\windows\\dlp-policy.example.json" aw_windows_policy_path: "{{ aw_windows_deploy_root }}\\windows\\dlp-policy.example.json"
aw_windows_validation_remote_path: "C:\\Windows\\Temp\\aw_validate_phase2_ansible.json" aw_windows_validation_remote_path: "C:\\Windows\\Temp\\aw_validate_phase2_ansible.json"
aw_windows_validation_local_dir: "/tmp/aw-rus-validation" 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: tasks:
- name: Validate required variables - name: Проверить обязательные переменные
ansible.builtin.assert: ansible.builtin.assert:
that: that:
- aw_windows_server_host is defined - aw_windows_server_host is defined
- aw_windows_server_port is defined - aw_windows_server_port is defined
- aw_windows_server_scheme is defined
- aw_windows_domain is defined - aw_windows_domain is defined
- aw_windows_users_effective | length > 0 - aw_windows_users_effective | length > 0
- aw_windows_install_root is defined - aw_windows_install_root is defined
- aw_windows_state_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: ansible.windows.win_file:
path: "{{ item }}" path: "{{ item }}"
state: directory state: directory
@@ -52,7 +64,7 @@
- "{{ aw_windows_deploy_root }}" - "{{ aw_windows_deploy_root }}"
- "{{ aw_windows_deploy_root }}\\windows" - "{{ aw_windows_deploy_root }}\\windows"
- name: Upload Windows deployment toolkit - name: Загрузить Windows toolkit развёртывания
ansible.windows.win_copy: ansible.windows.win_copy:
src: "{{ aw_windows_repo_root }}/windows/{{ item }}" src: "{{ aw_windows_repo_root }}/windows/{{ item }}"
dest: "{{ aw_windows_deploy_root }}\\windows\\{{ item }}" dest: "{{ aw_windows_deploy_root }}\\windows\\{{ item }}"
@@ -61,6 +73,7 @@
- ActivityWatch.Windows.Common.psm1 - ActivityWatch.Windows.Common.psm1
- browser-domains-native-collector.ps1 - browser-domains-native-collector.ps1
- dlp-endpoint-signals-collector.ps1 - dlp-endpoint-signals-collector.ps1
- worktime-session-collector.ps1
- deploy-domain-users.ps1 - deploy-domain-users.ps1
- deploy-ensemble.ps1 - deploy-ensemble.ps1
- hardening-recovery.ps1 - hardening-recovery.ps1
@@ -68,24 +81,23 @@
- web-category-rules.example.json - web-category-rules.example.json
- dlp-policy.example.json - dlp-policy.example.json
- name: Upload user list for domain deploy - name: Загрузить список пользователей для доменного развёртывания
ansible.windows.win_copy: ansible.windows.win_copy:
dest: "{{ aw_windows_deploy_root }}\\windows\\users.txt" dest: "{{ aw_windows_deploy_root }}\\windows\\users.txt"
content: | content: |
{% for user in aw_windows_users -%} {% for user in aw_windows_users_effective -%}
{{ user }}
{% endfor -%}
{% for user in aw_windows_extra_users -%}
{{ user }} {{ user }}
{% endfor -%} {% endfor -%}
- name: Run phase2 ensemble deployment - name: Запустить phase-2 ensemble развёртывание
ansible.windows.win_powershell: ansible.windows.win_powershell:
script: | script: |
$ErrorActionPreference = 'Stop' $ErrorActionPreference = 'Stop'
$params = @{ $params = @{
ServerScheme = "{{ aw_windows_server_scheme }}"
ServerHost = "{{ aw_windows_server_host }}" ServerHost = "{{ aw_windows_server_host }}"
ServerPort = {{ aw_windows_server_port }} ServerPort = {{ aw_windows_server_port }}
Version = "{{ aw_windows_package_version }}"
Domain = "{{ aw_windows_domain }}" Domain = "{{ aw_windows_domain }}"
UserListPath = "{{ aw_windows_deploy_root }}\windows\users.txt" UserListPath = "{{ aw_windows_deploy_root }}\windows\users.txt"
InstallRoot = "{{ aw_windows_install_root }}" InstallRoot = "{{ aw_windows_install_root }}"
@@ -100,34 +112,95 @@
CustomRulesPath = "{{ aw_windows_rules_path }}" CustomRulesPath = "{{ aw_windows_rules_path }}"
CustomPolicyPath = "{{ aw_windows_policy_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 %} {% if aw_windows_skip_hardening | bool %}
$params.SkipHardening = $true $params.SkipHardening = $true
{% endif %} {% endif %}
& "{{ aw_windows_deploy_root }}\windows\deploy-ensemble.ps1" @params & "{{ 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: ansible.windows.win_powershell:
script: | script: |
$ErrorActionPreference = 'Stop' $ErrorActionPreference = 'Stop'
$report = & "{{ aw_windows_deploy_root }}\windows\validate-deployment.ps1" ` $report = & "{{ aw_windows_deploy_root }}\windows\validate-deployment.ps1" `
-ConfigPath "{{ aw_windows_state_root }}\deployment-config.json" -ConfigPath "{{ aw_windows_state_root }}\deployment-config.json"
$report | ConvertTo-Json -Depth 12 | Out-File -FilePath "{{ aw_windows_validation_remote_path }}" -Encoding utf8 $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: ansible.builtin.file:
path: "{{ aw_windows_validation_local_dir }}" path: "{{ aw_windows_validation_local_dir }}"
state: directory state: directory
mode: "0755" mode: "0755"
delegate_to: localhost delegate_to: localhost
- name: Fetch validation report - name: Забрать validation report
ansible.builtin.fetch: ansible.builtin.fetch:
src: "{{ aw_windows_validation_remote_path }}" src: "{{ aw_windows_validation_remote_path }}"
dest: "{{ aw_windows_validation_local_dir }}/" dest: "{{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_phase2_ansible.json"
flat: false flat: true
- name: Show report location - name: Показать путь к отчёту
ansible.builtin.debug: ansible.builtin.debug:
msg: msg:
- "Windows phase2 deploy completed on {{ inventory_hostname }}." - "Windows phase2 развёртывание завершено на {{ inventory_hostname }}."
- "Validation report: {{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}/C$/Windows/Temp/aw_validate_phase2_ansible.json" - "Отчёт проверки: {{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_phase2_ansible.json"
@@ -8,17 +8,17 @@ aw_server_log_dir: "/var/log/activitywatch"
aw_server_user: "activitywatch" aw_server_user: "activitywatch"
aw_server_group: "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. # Опционально: применить базовые категории и views для рабочего времени через AW settings API.
# WARNING: this overwrites existing server-side settings/classes/views. # Внимание: это перезаписывает существующие server-side settings/classes/views.
aw_apply_worktime_settings: false aw_apply_worktime_settings: false
# Optional defaults for the worktime period in Web UI. # Опциональные значения периода рабочего времени в Web UI.
# startOfDay controls day-boundary and default report window start. # startOfDay задаёт границу дня и стартовое время окна отчёта.
# durationDefault controls default time range (seconds) shown in UI. # durationDefault задаёт диапазон по умолчанию в секундах.
# #
# Recommended: set worktime window explicitly and let the playbook derive duration. # Рекомендуется явно задать рабочий интервал и дать playbook вычислить duration.
aw_worktime_from: "08:00" aw_worktime_from: "08:00"
aw_worktime_to: "17:00" aw_worktime_to: "17:00"
aw_worktime_start_of_day: "{{ aw_worktime_from }}" aw_worktime_start_of_day: "{{ aw_worktime_from }}"
@@ -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_deploy_root: "C:\\Deploy\\AWatch-rus"
aw_windows_server_scheme: "http"
aw_windows_server_host: "10.10.10.13" aw_windows_server_host: "10.10.10.13"
aw_windows_server_port: 5600 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_domain: "SHARKON2025"
aw_windows_users: aw_windows_users:
- user1 - 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_remote_path: "C:\\Windows\\Temp\\aw_validate_phase2_ansible.json"
aw_windows_validation_local_dir: "/tmp/aw-rus-validation" aw_windows_validation_local_dir: "/tmp/aw-rus-validation"
aw_windows_fail_on_validation_error: true
# По умолчанию AFK bucket вычисляется как aw-watcher-afk_<COMPUTERNAME>.
# Задайте явное значение только если watcher пишет в нестандартный bucket.
aw_windows_api_smoke_check_enabled: true
aw_windows_api_smoke_check_bucket: ""
aw_windows_api_smoke_check_limit: 10
@@ -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
@@ -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-ct ansible_host=10.20.30.13 ansible_user=root ansible_port=22
[aw_windows] [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
@@ -1,5 +1,5 @@
--- ---
- name: Provision single Proxmox CT and deploy AWatch-rus - name: Создать один Proxmox CT и развернуть AWatch-rus
hosts: proxmox hosts: proxmox
gather_facts: false gather_facts: false
@@ -17,7 +17,7 @@
- settings/views-default.json - settings/views-default.json
tasks: tasks:
- name: Execute single-CT provisioning workflow - name: Выполнить workflow создания одного CT
ansible.builtin.include_tasks: tasks/provision_ct_and_deploy_aw.yml ansible.builtin.include_tasks: tasks/provision_ct_and_deploy_aw.yml
vars: vars:
ct_id: "{{ proxmox_ct_id }}" ct_id: "{{ proxmox_ct_id }}"
@@ -1,5 +1,5 @@
--- ---
- name: Provision Proxmox CT matrix and deploy AWatch-rus with RU patch - name: Создать матрицу Proxmox CT и развернуть AWatch-rus с RU patch
hosts: proxmox hosts: proxmox
gather_facts: false gather_facts: false
@@ -17,14 +17,14 @@
- settings/views-default.json - settings/views-default.json
tasks: tasks:
- name: Validate CT matrix is provided - name: Проверить, что матрица CT задана
ansible.builtin.assert: ansible.builtin.assert:
that: that:
- proxmox_ct_matrix is defined - proxmox_ct_matrix is defined
- proxmox_ct_matrix | length > 0 - 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 ansible.builtin.include_tasks: tasks/provision_ct_and_deploy_aw.yml
vars: vars:
ct_id: "{{ item.id }}" ct_id: "{{ item.id }}"
@@ -1,5 +1,5 @@
--- ---
- name: Validate required per-CT variables - name: Проверить обязательные переменные CT
ansible.builtin.assert: ansible.builtin.assert:
that: that:
- ct_id is defined - ct_id is defined
@@ -27,14 +27,14 @@
- aw_server_log_dir is defined - aw_server_log_dir is defined
- aw_server_user is defined - aw_server_user is defined
- aw_server_group 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: ansible.builtin.set_fact:
ct_net0: >- 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=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: ansible.builtin.command:
argv: argv:
- pct - pct
@@ -44,7 +44,7 @@
failed_when: false failed_when: false
changed_when: false changed_when: false
- name: Create CT when absent - name: Создать CT, если он отсутствует
ansible.builtin.command: ansible.builtin.command:
argv: argv:
- pct - pct
@@ -78,8 +78,9 @@
- --ostype - --ostype
- debian - debian
when: ct_status_check.rc != 0 when: ct_status_check.rc != 0
no_log: true
- name: Check current CT runtime state - name: Проверить текущее состояние CT
ansible.builtin.command: ansible.builtin.command:
argv: argv:
- pct - pct
@@ -88,7 +89,7 @@
register: ct_runtime_status register: ct_runtime_status
changed_when: false changed_when: false
- name: Start CT when stopped - name: Запустить CT, если он остановлен
ansible.builtin.command: ansible.builtin.command:
argv: argv:
- pct - pct
@@ -96,20 +97,23 @@
- "{{ ct_id }}" - "{{ ct_id }}"
when: "'stopped' in ct_runtime_status.stdout" when: "'stopped' in ct_runtime_status.stdout"
- name: Ensure bootstrap directory on Proxmox host - name: Создать bootstrap каталог на Proxmox host
ansible.builtin.file: ansible.builtin.file:
path: "{{ proxmox_bootstrap_dir }}" path: "{{ item }}"
state: directory state: directory
mode: "0700" 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: ansible.builtin.copy:
src: "{{ aw_repo_root }}/aw-server/{{ item }}" src: "{{ aw_repo_root }}/aw-server/{{ item }}"
dest: "{{ proxmox_bootstrap_dir }}/{{ item }}" dest: "{{ proxmox_bootstrap_dir }}/{{ item }}"
mode: "0644" mode: "0644"
loop: "{{ aw_bootstrap_files }}" loop: "{{ aw_bootstrap_files }}"
- name: Bootstrap CT OS dependencies - name: Установить базовые зависимости ОС внутри CT
ansible.builtin.command: ansible.builtin.command:
argv: argv:
- pct - pct
@@ -122,12 +126,16 @@
set -euo pipefail set -euo pipefail
export DEBIAN_FRONTEND=noninteractive export DEBIAN_FRONTEND=noninteractive
apt-get update apt-get update
apt-get install -y curl ca-certificates bash unzip xz-utils jq rsync openssh-server apt-get install -y curl ca-certificates bash unzip xz-utils jq rsync openssh-server python3
mkdir -p /root/bootstrap /etc/activitywatch mkdir -p /root/bootstrap/settings /etc/activitywatch
systemctl enable ssh || true systemctl enable ssh || true
systemctl restart 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: ansible.builtin.command:
argv: argv:
- pct - pct
@@ -137,7 +145,7 @@
- "/root/bootstrap/{{ item }}" - "/root/bootstrap/{{ item }}"
loop: "{{ aw_bootstrap_files }}" loop: "{{ aw_bootstrap_files }}"
- name: Write AW server env file on Proxmox host temp - name: Записать AW server env во временный каталог Proxmox host
ansible.builtin.copy: ansible.builtin.copy:
dest: "{{ proxmox_bootstrap_dir }}/aw-server.env" dest: "{{ proxmox_bootstrap_dir }}/aw-server.env"
mode: "0600" mode: "0600"
@@ -151,8 +159,9 @@
AW_SERVER_LOG_DIR={{ aw_server_log_dir }} AW_SERVER_LOG_DIR={{ aw_server_log_dir }}
AW_SERVER_USER={{ aw_server_user }} AW_SERVER_USER={{ aw_server_user }}
AW_SERVER_GROUP={{ aw_server_group }} AW_SERVER_GROUP={{ aw_server_group }}
no_log: true
- name: Push AW server env into CT - name: Передать AW server env внутрь CT
ansible.builtin.command: ansible.builtin.command:
argv: argv:
- pct - pct
@@ -160,8 +169,9 @@
- "{{ ct_id }}" - "{{ ct_id }}"
- "{{ proxmox_bootstrap_dir }}/aw-server.env" - "{{ proxmox_bootstrap_dir }}/aw-server.env"
- /etc/activitywatch/aw-server.env - /etc/activitywatch/aw-server.env
no_log: true
- name: Set mode for env inside CT - name: Настроить права env файла внутри CT
ansible.builtin.command: ansible.builtin.command:
argv: argv:
- pct - pct
@@ -172,7 +182,7 @@
- "0600" - "0600"
- /etc/activitywatch/aw-server.env - /etc/activitywatch/aw-server.env
- name: Install server and apply RU patch inside CT - name: Установить сервер и применить RU patch внутри CT
ansible.builtin.command: ansible.builtin.command:
argv: argv:
- pct - pct
@@ -188,7 +198,7 @@
bash /root/bootstrap/apply_webui_ru_patch.sh bash /root/bootstrap/apply_webui_ru_patch.sh
systemctl restart activitywatch-server.service systemctl restart activitywatch-server.service
- name: Validate AW API from inside CT - name: Проверить AW API изнутри CT
ansible.builtin.command: ansible.builtin.command:
argv: argv:
- pct - pct
@@ -199,7 +209,7 @@
- -lc - -lc
- "curl -fsS http://127.0.0.1:{{ aw_server_port }}/api/0/info >/dev/null" - "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: ansible.builtin.command:
argv: argv:
- pct - pct
@@ -210,8 +220,8 @@
- -lc - -lc
- "grep -q 'ru-patch-v5.js' {{ aw_server_webui_dir }}/index.html && grep -q 'sw-cleanup.js' {{ aw_server_webui_dir }}/index.html" - "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: ansible.builtin.debug:
msg: msg:
- "CT {{ ct_id }} is provisioned and configured." - "CT {{ ct_id }} создан и настроен."
- "ActivityWatch endpoint: http://{{ ct_ip | regex_replace('/[0-9]+$', '') }}:{{ aw_server_port }}" - "Endpoint ActivityWatch: http://{{ ct_ip | regex_replace('/[0-9]+$', '') }}:{{ aw_server_port }}"
@@ -20,9 +20,9 @@ $policyPath = if ($config.paths.PSObject.Properties.Name -contains 'policyPath')
$launchScript = [string]$config.paths.launchScript $launchScript = [string]$config.paths.launchScript
$recoveryScript = [string]$config.paths.recoveryScript $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 = @( $requiredFiles = @(
(Join-Path $installRoot 'aw-watcher-afk\aw-watcher-afk.exe'),
(Join-Path $installRoot 'aw-watcher-window\aw-watcher-window.exe'),
$collectorScript, $collectorScript,
$endpointCollectorScript, $endpointCollectorScript,
$sessionCollectorScript, $sessionCollectorScript,
@@ -32,13 +32,24 @@ $requiredFiles = @(
$recoveryScript, $recoveryScript,
$ConfigPath $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 = @( $missingFiles = @(
$requiredFiles | Where-Object { -not (Test-Path -LiteralPath $_) } $requiredFiles | Where-Object { -not (Test-Path -LiteralPath $_) }
) )
$processNames = @('aw-watcher-afk', 'aw-watcher-window') $processNames = @()
$runningProcesses = Get-Process -Name $processNames -ErrorAction SilentlyContinue | Select-Object Name, Id, SessionId 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 | $sessionCollectorProcesses = Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
Where-Object { Where-Object {
($_.Name -ieq 'powershell.exe' -or $_.Name -ieq 'pwsh.exe') -and ($_.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 })) ok = [bool]($tasks.Count -gt 0 -and -not ($tasks | Where-Object { -not $_.present }))
} }
processes = [ordered]@{ processes = [ordered]@{
expected = $processNames
list = @($runningProcesses) list = @($runningProcesses)
sessionCollectors = @($sessionCollectorProcesses) sessionCollectors = @($sessionCollectorProcesses)
ok = [bool]( 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) ($sessionCollectorProcesses.Count -ge 1)
) )
} }
+20 -5
View File
@@ -20,9 +20,9 @@ $policyPath = if ($config.paths.PSObject.Properties.Name -contains 'policyPath')
$launchScript = [string]$config.paths.launchScript $launchScript = [string]$config.paths.launchScript
$recoveryScript = [string]$config.paths.recoveryScript $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 = @( $requiredFiles = @(
(Join-Path $installRoot 'aw-watcher-afk\aw-watcher-afk.exe'),
(Join-Path $installRoot 'aw-watcher-window\aw-watcher-window.exe'),
$collectorScript, $collectorScript,
$endpointCollectorScript, $endpointCollectorScript,
$sessionCollectorScript, $sessionCollectorScript,
@@ -32,13 +32,24 @@ $requiredFiles = @(
$recoveryScript, $recoveryScript,
$ConfigPath $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 = @( $missingFiles = @(
$requiredFiles | Where-Object { -not (Test-Path -LiteralPath $_) } $requiredFiles | Where-Object { -not (Test-Path -LiteralPath $_) }
) )
$processNames = @('aw-watcher-afk', 'aw-watcher-window') $processNames = @()
$runningProcesses = Get-Process -Name $processNames -ErrorAction SilentlyContinue | Select-Object Name, Id, SessionId 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 | $sessionCollectorProcesses = Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
Where-Object { Where-Object {
($_.Name -ieq 'powershell.exe' -or $_.Name -ieq 'pwsh.exe') -and ($_.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 })) ok = [bool]($tasks.Count -gt 0 -and -not ($tasks | Where-Object { -not $_.present }))
} }
processes = [ordered]@{ processes = [ordered]@{
expected = $processNames
list = @($runningProcesses) list = @($runningProcesses)
sessionCollectors = @($sessionCollectorProcesses) sessionCollectors = @($sessionCollectorProcesses)
ok = [bool]( 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) ($sessionCollectorProcesses.Count -ge 1)
) )
} }