--- - name: Развернуть сервер AWatch-rus hosts: aw_server become: true gather_facts: true vars: aw_release_root: "/opt/activitywatch/releases" aw_release_dir: "{{ aw_release_root }}/{{ aw_server_version }}" aw_archive_path: "/tmp/activitywatch-{{ aw_server_version }}.zip" aw_bootstrap_dir: "/tmp/aw-rus-bootstrap" aw_release_install_dir: "{{ aw_release_root }}/aw-server-rust-{{ aw_server_version }}" aw_ru_patch_cache_bust: "{{ lookup('file', aw_repo_root + '/aw-server/aw-ru-patch.js') | hash('sha1') | truncate(12, true, '') }}" aw_sw_cleanup_cache_bust: "{{ lookup('file', aw_repo_root + '/aw-server/aw-sw-cleanup.js') | hash('sha1') | truncate(12, true, '') }}" aw_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_rust_release_dir: "{{ (lookup('env', 'CARGO_TARGET_DIR') | default(aw_repo_root + '/adk-rust/target', true)) + '/release' }}" tasks: - name: Установить базовые пакеты ansible.builtin.apt: name: - curl - python3-venv - python3-pip - rsync - tesseract-ocr - tesseract-ocr-rus - unzip - jq state: present update_cache: true - name: Установить пакеты для browser smoke проверки ansible.builtin.apt: name: - chromium - nodejs - node-playwright state: present when: aw_browser_smoke_enabled | default(true) | bool - name: Создать системную группу сервиса ansible.builtin.group: name: "{{ aw_server_group }}" system: true state: present - name: Создать системную учётную запись сервиса ansible.builtin.user: name: "{{ aw_server_user }}" group: "{{ aw_server_group }}" home: "{{ aw_server_data_dir }}" shell: /usr/sbin/nologin system: true create_home: false - name: Создать обязательные каталоги ansible.builtin.file: path: "{{ item }}" state: directory mode: "0755" loop: - "{{ aw_release_root }}" - "{{ aw_release_dir }}" - "{{ aw_release_install_dir }}" - /opt/activitywatch - /opt/activitywatch/bin - "{{ aw_server_webui_dir }}" - "{{ aw_server_webui_dir }}/js" - "{{ aw_server_data_dir }}" - "{{ aw_server_db_path | dirname }}" - "{{ aw_server_data_dir }}/.config" - "{{ aw_server_data_dir }}/.config/activitywatch" - "{{ aw_server_data_dir }}/.config/activitywatch/aw-server-rust" - "{{ aw_server_data_dir }}/backups" - "{{ aw_server_data_dir }}/slo" - "{{ aw_server_data_dir }}/browser-smoke" - "{{ aw_rus_health_state_dir }}" - "{{ aw_rus_health_validation_dir }}" - "{{ aw_server_log_dir }}" - /etc/activitywatch - /usr/local/lib/aw-rus-browser-smoke - "{{ aw_bootstrap_dir }}" - name: Настроить каталоги ActivityWatch с владельцем сервиса ansible.builtin.file: path: "{{ item }}" state: directory owner: "{{ aw_server_user }}" group: "{{ aw_server_group }}" mode: "0755" loop: - /opt/activitywatch - /opt/activitywatch/bin - "{{ aw_release_root }}" - "{{ aw_release_dir }}" - "{{ aw_release_install_dir }}" - "{{ aw_server_webui_dir }}" - "{{ aw_server_webui_dir }}/js" - "{{ aw_server_data_dir }}" - "{{ aw_server_db_path | dirname }}" - "{{ aw_server_data_dir }}/.config" - "{{ aw_server_data_dir }}/.config/activitywatch" - "{{ aw_server_data_dir }}/.config/activitywatch/aw-server-rust" - "{{ aw_server_data_dir }}/backups" - "{{ aw_server_data_dir }}/slo" - "{{ aw_server_data_dir }}/browser-smoke" - "{{ aw_rus_health_state_dir }}" - "{{ aw_rus_health_validation_dir }}" - "{{ aw_server_log_dir }}" - name: Проверить существующие health state files ansible.builtin.stat: path: "{{ item }}" loop: - "{{ aw_rus_health_state_dir }}/aw-rus-health.json" - "{{ aw_rus_health_state_dir }}/aw-rus-health.txt" - "{{ aw_rus_health_state_dir }}/dlp-health-check-counters.json" register: aw_rus_health_state_files - name: Нормализовать владельца существующих health state files ansible.builtin.file: path: "{{ item.stat.path }}" owner: "{{ aw_server_user }}" group: "{{ aw_server_group }}" mode: "0644" loop: "{{ aw_rus_health_state_files.results }}" when: item.stat.exists | default(false) - name: Удалить устаревшие Python entrypoints после Rust migration ansible.builtin.file: path: "{{ item }}" state: absent loop: - /opt/activitywatch/aw-rus-ops/health-check.sh - /opt/activitywatch/aw-rus-ops/hayabusa/aw-hayabusa-autoprocess.py - /opt/activitywatch/aw-rus-ops/hayabusa/aw-hayabusa-case-alert.py - /opt/activitywatch/aw-rus-ops/hayabusa/aw-hayabusa-from-windows.py - /opt/activitywatch/aw-rus-ops/hayabusa/aw-hayabusa-link-case.py - /opt/activitywatch/dlp-case-management/.venv - /opt/activitywatch/dlp-case-management/case_rules.py - /opt/activitywatch/dlp-case-management/case_schema.py - /opt/activitywatch/dlp-case-management/case_service.py - /opt/activitywatch/dlp-case-management/case_storage.py - /opt/activitywatch/dlp-case-management/evidence_chain.py - /opt/activitywatch/dlp-compliance/compliance_scheduler.py - /opt/activitywatch/dlp-compliance/report_generator.py - /opt/activitywatch/dlp-compliance/.venv - /opt/activitywatch/dlp-integrations/cef_exporter.py - /opt/activitywatch/dlp-integrations/syslog_forwarder.py - /opt/activitywatch/dlp-integrations/webhook_sender.py - /opt/activitywatch/dlp-integrations/.venv - /opt/activitywatch/dlp-policy-engine/.venv - /opt/activitywatch/dlp-policy-engine/policy_distributor.py - /opt/activitywatch/dlp-policy-engine/policy_schema.py - /opt/activitywatch/dlp-policy-engine/policy_service.py - /opt/activitywatch/dlp-policy-engine/policy_storage.py - /opt/activitywatch/scripts/aggregate_dlp_events.py - /usr/local/bin/aw-dlp-influx-exporter.py - /usr/local/bin/aw-extract-ioc-from-sigma.py - /usr/local/bin/aw-rus-healthd.py - /usr/local/bin/aw-slo-monitor.py - /usr/local/bin/aw-worktime-afk-bridge.py - /usr/local/bin/aw-worktime-api.py - /usr/local/bin/aw-worktime-influx-exporter.py - /usr/local/bin/aw-worktime-ui-bridge.py - /usr/local/bin/dlp-admin-cli.py - /usr/local/bin/merge_aw_server_dbs.py - name: Установить prune script для локального state ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/aw-prune-local-state.sh" dest: /usr/local/bin/aw-prune-local-state.sh owner: root group: root mode: "0755" - name: Положить legacy prune script в server-side ops bundle ansible.builtin.copy: src: "{{ aw_repo_root }}/scripts/legacy/aw-prune-local-state.sh" dest: /opt/activitywatch/aw-rus-ops/aw-prune-local-state.sh owner: root group: root mode: "0755" - name: Проверить локальный Rust prune local state ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/aw-prune-local-state" delegate_to: localhost register: aw_prune_local_state_rust_binary become: false - name: Установить Rust prune local state ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/aw-prune-local-state" dest: /usr/local/bin/aw-prune-local-state-rust owner: root group: root mode: "0755" when: aw_prune_local_state_rust_binary.stat.exists | default(false) - name: Ограничить рост journald на aw-server ansible.builtin.copy: dest: /etc/systemd/journald.conf.d/aw-rus-retention.conf owner: root group: root mode: "0644" content: | [Journal] SystemMaxUse={{ aw_server_journal_system_max_use }} RuntimeMaxUse={{ aw_server_journal_runtime_max_use }} SystemKeepFree={{ aw_server_journal_system_keep_free }} register: aw_journald_dropin - name: Установить systemd service prune локального state ansible.builtin.copy: dest: /etc/systemd/system/aw-prune-local-state.service owner: root group: root mode: "0644" content: | [Unit] Description=Prune ActivityWatch local backups and temp state [Service] Type=oneshot Environment=AW_DATA_DIR={{ aw_server_data_dir }} Environment=AW_BACKUP_RETENTION_DAYS={{ aw_server_backup_retention_days }} Environment=AW_BACKUP_KEEP_LAST_DB={{ aw_server_backup_keep_last_db }} Environment=AW_BACKUP_KEEP_LAST_JSON={{ aw_server_backup_keep_last_json }} ExecStart=/usr/local/bin/aw-prune-local-state.sh - name: Установить systemd timer prune локального state ansible.builtin.copy: dest: /etc/systemd/system/aw-prune-local-state.timer owner: root group: root mode: "0644" content: | [Unit] Description=Daily prune of ActivityWatch local backups and temp state [Timer] OnCalendar=*-*-* 04:40:00 Persistent=true [Install] WantedBy=timers.target - name: Перечитать systemd после retention unit/drop-in ansible.builtin.systemd: daemon_reload: true - name: Включить и запустить timer prune локального state ansible.builtin.systemd: name: aw-prune-local-state.timer enabled: true state: started - name: Применить journald retention без простоя ansible.builtin.command: argv: - systemctl - restart - systemd-journald when: aw_journald_dropin.changed failed_when: false - name: Сжать существующий journald до нового лимита ansible.builtin.command: argv: - journalctl - --vacuum-size={{ aw_server_journal_system_max_use }} changed_when: true failed_when: false - name: (Check mode) Пропустить установку релиза ActivityWatch ansible.builtin.debug: msg: "ansible_check_mode=true: download/unarchive/install of ActivityWatch release is skipped." when: ansible_check_mode - name: Установить релиз ActivityWatch (download/unarchive/install) when: not ansible_check_mode block: - name: Скачать архив релиза ActivityWatch ansible.builtin.get_url: url: "{{ aw_server_download_url }}" dest: "{{ aw_archive_path }}" mode: "0644" - name: Распаковать релиз ActivityWatch ansible.builtin.unarchive: src: "{{ aw_archive_path }}" dest: "{{ aw_release_dir }}" remote_src: true extra_opts: ["-o"] - name: Удалить временный архив ActivityWatch после распаковки ansible.builtin.file: path: "{{ aw_archive_path }}" state: absent - name: Найти распакованный каталог ActivityWatch ansible.builtin.find: paths: "{{ aw_release_dir }}" recurse: true file_type: directory patterns: "activitywatch*" register: aw_release_find - name: Найти бинарный файл AW server ansible.builtin.find: paths: "{{ aw_release_dir }}" recurse: true file_type: file patterns: - aw-server-rust - aw-server register: aw_server_binary_find - name: Найти index.html WebUI ansible.builtin.find: paths: "{{ aw_release_dir }}" recurse: true file_type: file patterns: - index.html register: aw_webui_index_find - name: Сохранить пути распакованного релиза (binary + webui index) ansible.builtin.set_fact: aw_release_extracted: "{{ (aw_release_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list | first) | default('') }}" aw_server_binary_path: >- {{ ( ( (aw_server_binary_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list) | select('match', '.*/aw-server-rust$') | list | first ) | default( ( (aw_server_binary_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list | first) ), true ) ) | default('') }} aw_webui_index_path: >- {{ ( ( (aw_webui_index_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list) | select('search', '/static/index\\.html$') | list | first ) | default( ( (aw_webui_index_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list | first) ), true ) ) | default('') }} - name: Сохранить каталог WebUI (dirname index.html) ansible.builtin.set_fact: aw_webui_source_path: "{{ aw_webui_index_path | dirname }}" - name: Проверить, что компоненты релиза найдены ansible.builtin.assert: that: - aw_release_extracted is defined - aw_release_extracted | length > 0 - aw_server_binary_path is defined - aw_server_binary_path | length > 0 - aw_webui_source_path is defined - aw_webui_source_path | length > 0 fail_msg: "Не удалось найти бинарный файл или WebUI в распакованном релизе ActivityWatch." - name: Создать каталог установленного релиза ansible.builtin.file: path: "{{ aw_release_install_dir }}" state: directory owner: "{{ aw_server_user }}" group: "{{ aw_server_group }}" mode: "0755" - name: Установить бинарный файл AW server ansible.builtin.copy: remote_src: true src: "{{ aw_server_binary_path }}" dest: "{{ aw_release_install_dir }}/aw-server-rust" owner: "{{ aw_server_user }}" group: "{{ aw_server_group }}" mode: "0755" - name: Создать ссылку на активный бинарный файл AW server ansible.builtin.file: src: "{{ aw_release_install_dir }}/aw-server-rust" dest: /opt/activitywatch/bin/aw-server-rust owner: "{{ aw_server_user }}" group: "{{ aw_server_group }}" state: link force: true - name: Синхронизировать WebUI в RU каталог ansible.builtin.command: cmd: "rsync -a {{ aw_webui_source_path }}/ {{ aw_server_webui_dir }}/" - name: Настроить владельца файлов /opt/activitywatch ansible.builtin.file: path: /opt/activitywatch state: directory owner: "{{ aw_server_user }}" group: "{{ aw_server_group }}" recurse: true - name: Установить systemd service из шаблона репозитория ansible.builtin.copy: 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: (Check mode) Пропустить WebUI patch и запуск сервиса ansible.builtin.debug: msg: "ansible_check_mode=true: WebUI patch + service start + API checks are skipped." when: ansible_check_mode - name: Применить WebUI RU patch и запустить сервис when: not ansible_check_mode block: - name: Скопировать RU patch файлы WebUI из репозитория ansible.builtin.copy: src: "{{ item.src }}" dest: "{{ item.dest }}" mode: "{{ item.mode }}" owner: "{{ aw_server_user }}" group: "{{ aw_server_group }}" loop: - { src: "{{ aw_repo_root }}/aw-server/aw-ru-patch.js", dest: "{{ aw_server_webui_dir }}/js/ru-patch-v5.js", mode: "0644" } - { src: "{{ aw_repo_root }}/aw-server/aw-sw-cleanup.js", dest: "{{ aw_server_webui_dir }}/js/sw-cleanup.js", mode: "0644" } - { src: "{{ aw_repo_root }}/aw-server/aw-worktime-panel.js", dest: "{{ aw_server_webui_dir }}/js/aw-worktime-panel.js", mode: "0644" } - { src: "{{ aw_repo_root }}/aw-server/aw-host-groups.json", dest: "{{ aw_server_webui_dir }}/js/aw-host-groups.json", mode: "0644" } - name: Создать каталог /root/bootstrap для apply_webui_ru_patch.sh ansible.builtin.file: path: /root/bootstrap state: directory mode: "0755" - name: Скопировать RU patch файлы для apply_webui_ru_patch.sh (хотфиксы compiled JS чанков) ansible.builtin.copy: src: "{{ item.src }}" dest: "{{ item.dest }}" mode: "{{ item.mode }}" loop: - { src: "{{ aw_repo_root }}/aw-server/aw-ru-patch.js", dest: "/root/bootstrap/aw-ru-patch.js", mode: "0644" } - { src: "{{ aw_repo_root }}/aw-server/aw-sw-cleanup.js", dest: "/root/bootstrap/aw-sw-cleanup.js", mode: "0644" } - { src: "{{ aw_repo_root }}/aw-server/aw-worktime-panel.js", dest: "/root/bootstrap/aw-worktime-panel.js", mode: "0644" } - { src: "{{ aw_repo_root }}/aw-server/aw-host-groups.json", dest: "/root/bootstrap/aw-host-groups.json", mode: "0644" } - name: Скопировать apply_webui_ru_patch.sh скрипт ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/apply_webui_ru_patch.sh" dest: /opt/activitywatch/aw-server/apply_webui_ru_patch.sh mode: "0755" - name: Создать server-side ops bundle для ранних health helpers ansible.builtin.file: path: /opt/activitywatch/aw-rus-ops state: directory owner: root group: root mode: "0755" - name: Установить wrapper aw-health-check до Influx проверок ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/aw-health-check-wrapper.sh" dest: /usr/local/bin/aw-health-check owner: root group: root mode: "0755" - name: Проверить локальный Rust aw-health-check до Influx проверок ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/aw-health-check" delegate_to: localhost register: aw_health_check_rust_binary_early become: false - name: Установить Rust aw-health-check до Influx проверок ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/aw-health-check" dest: /usr/local/bin/aw-health-check-rust owner: root group: root mode: "0755" when: aw_health_check_rust_binary_early.stat.exists | default(false) - name: Требовать Rust aw-health-check до Influx проверок ansible.builtin.assert: that: - aw_health_check_rust_binary_early.stat.exists | default(false) fail_msg: "Rust aw-health-check artifact is required at {{ aw_rust_release_dir }}/aw-health-check" - name: Проверить локальный Rust aw-db-health до Influx проверок ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/aw-db-health" delegate_to: localhost register: aw_db_health_rust_binary_early become: false - name: Установить Rust aw-db-health до Influx проверок ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/aw-db-health" dest: /usr/local/bin/aw-db-health owner: root group: root mode: "0755" when: aw_db_health_rust_binary_early.stat.exists | default(false) - name: Проверить локальный Rust aw-ensure-reliability до Influx проверок ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/aw-ensure-reliability" delegate_to: localhost register: aw_ensure_reliability_rust_binary_early become: false - name: Установить Rust aw-ensure-reliability до Influx проверок ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/aw-ensure-reliability" dest: /usr/local/bin/aw-ensure-reliability owner: root group: root mode: "0755" when: aw_ensure_reliability_rust_binary_early.stat.exists | default(false) - name: Проверить локальный Rust aw-db-maintenance до Influx проверок ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/aw-db-maintenance" delegate_to: localhost register: aw_db_maintenance_rust_binary_early become: false - name: Установить Rust aw-db-maintenance до Influx проверок ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/aw-db-maintenance" dest: /usr/local/bin/aw-db-maintenance owner: root group: root mode: "0755" when: aw_db_maintenance_rust_binary_early.stat.exists | default(false) - name: Установить aw-db-maintenance service до Influx проверок ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/aw-db-maintenance.service" dest: /etc/systemd/system/aw-db-maintenance.service owner: root group: root mode: "0644" when: aw_db_maintenance_rust_binary_early.stat.exists | default(false) - name: Установить aw-db-maintenance timer до Influx проверок ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/aw-db-maintenance.timer" dest: /etc/systemd/system/aw-db-maintenance.timer owner: root group: root mode: "0644" when: aw_db_maintenance_rust_binary_early.stat.exists | default(false) - name: Включить weekly aw-db-maintenance timer до Influx проверок ansible.builtin.systemd: name: aw-db-maintenance.timer enabled: true state: started daemon_reload: true when: aw_db_maintenance_rust_binary_early.stat.exists | default(false) - name: Прочитать текущий aw-server.env для сохранения Influx token ansible.builtin.slurp: path: /etc/activitywatch/aw-server.env register: aw_existing_server_env_raw failed_when: false no_log: true - name: Подготовить effective Influx tokens без вывода секретов ansible.builtin.set_fact: aw_existing_server_env_text: "{{ aw_existing_server_env_raw.content | default('') | b64decode }}" aw_existing_worktime_influx_token: "{{ (aw_existing_server_env_raw.content | default('') | b64decode | regex_search('(?m)^AW_WORKTIME_INFLUX_TOKEN=.*$') | default('', true) | regex_replace('^AW_WORKTIME_INFLUX_TOKEN=', '')) }}" aw_existing_dlp_influx_token: "{{ (aw_existing_server_env_raw.content | default('') | b64decode | regex_search('(?m)^AW_DLP_INFLUX_TOKEN=.*$') | default('', true) | regex_replace('^AW_DLP_INFLUX_TOKEN=', '')) }}" no_log: true - name: Выбрать effective Influx tokens ansible.builtin.set_fact: aw_effective_worktime_influx_token: "{{ (aw_worktime_influx_token | default('') | string) if ((aw_worktime_influx_token | default('') | string | length) > 0) else aw_existing_worktime_influx_token }}" aw_effective_dlp_influx_token: "{{ (aw_dlp_influx_token | default('') | string) if ((aw_dlp_influx_token | default('') | string | length) > 0) else aw_existing_dlp_influx_token }}" no_log: true - name: Проверить Influx token для AW worktime exporter ansible.builtin.assert: that: - aw_effective_worktime_influx_token is defined - aw_effective_worktime_influx_token | length > 0 fail_msg: "aw_worktime_influx_enabled=true, но token пуст и в локальном env, и в текущем /etc/activitywatch/aw-server.env. Exporter будет падать и Grafana не получит worktime-ряды." when: aw_worktime_influx_enabled | default(false) | bool - name: Проверить Influx token для AW DLP exporter ansible.builtin.assert: that: - aw_effective_dlp_influx_token is defined - aw_effective_dlp_influx_token | length > 0 fail_msg: "aw_dlp_influx_enabled=true, но token пуст и в локальном env, и в текущем /etc/activitywatch/aw-server.env. Exporter будет падать и Grafana не получит DLP-ряды." when: aw_dlp_influx_enabled | default(false) | bool - name: Записать /etc/activitywatch/aw-server.env перед хотфиксами ansible.builtin.copy: dest: /etc/activitywatch/aw-server.env mode: "0640" owner: root group: root content: | AW_SERVER_BIND_HOST={{ aw_server_bind_host }} AW_SERVER_PORT={{ aw_server_port }} AW_SERVER_DATA_DIR={{ aw_server_data_dir }} AW_SERVER_DB_PATH={{ aw_server_db_path }} AW_SERVER_LOG_DIR={{ aw_server_log_dir }} AW_SERVER_WEBUI_DIR={{ aw_server_webui_dir }} AW_SERVER_USER={{ aw_server_user }} AW_SERVER_GROUP={{ aw_server_group }} AW_WORKTIME_REPORT_BASE={{ aw_worktime_report_base }} AW_WORKTIME_TZ={{ aw_worktime_timezone }} AW_DLP_IOC_DIR={{ aw_dlp_ioc_workdir }}/output AW_DLP_POLICY_ENGINE_BIND_HOST={{ aw_dlp_policy_engine_bind_host }} AW_DLP_POLICY_ENGINE_PORT={{ aw_dlp_policy_engine_port }} AW_DLP_POLICY_ENGINE_DB_PATH={{ aw_dlp_policy_engine_db_path }} AW_DLP_CASE_BIND_HOST={{ aw_dlp_case_bind_host }} AW_DLP_CASE_PORT={{ aw_dlp_case_port }} AW_DLP_CASE_DB_PATH={{ aw_dlp_case_db_path }} AW_DLP_COMPLIANCE_REPORT_DIR={{ aw_dlp_compliance_report_dir }} AW_DLP_COMPLIANCE_TEMPLATE={{ aw_dlp_compliance_template_path }} AW_SERVER_URL=http://127.0.0.1:5600 XDG_DATA_HOME={{ aw_server_data_dir }}/.local/share XDG_CONFIG_HOME={{ aw_server_data_dir }}/.config AW_WORKTIME_INFLUX_ENABLED={{ 'true' if (aw_worktime_influx_enabled | default(false) | bool) else 'false' }} AW_WORKTIME_INFLUX_URL={{ aw_worktime_influx_url | default('') }} AW_WORKTIME_INFLUX_ORG={{ aw_worktime_influx_org | default('proxmox') }} AW_WORKTIME_INFLUX_BUCKET={{ aw_worktime_influx_bucket | default('aw_metrics') }} AW_WORKTIME_INFLUX_HOSTS={{ aw_worktime_influx_hosts | default('SHARKON2025') }} AW_WORKTIME_INFLUX_DAYS={{ aw_worktime_influx_days | default('today,yesterday') }} AW_WORKTIME_INFLUX_TOKEN={{ aw_effective_worktime_influx_token | default('') }} AW_DLP_INFLUX_ENABLED={{ 'true' if (aw_dlp_influx_enabled | default(false) | bool) else 'false' }} AW_DLP_INFLUX_URL={{ aw_dlp_influx_url | default('') }} AW_DLP_INFLUX_ORG={{ aw_dlp_influx_org | default('proxmox') }} AW_DLP_INFLUX_BUCKET={{ aw_dlp_influx_bucket | default('aw_metrics') }} AW_DLP_INFLUX_HOSTS={{ aw_dlp_influx_hosts | default('SHARKON2025') }} AW_DLP_INFLUX_LOOKBACK_DAYS={{ aw_dlp_influx_lookback_days | default(30) }} AW_DLP_INFLUX_EVENT_LIMIT={{ aw_dlp_influx_event_limit | default(2000) }} AW_DLP_INFLUX_TOKEN={{ aw_effective_dlp_influx_token | default('') }} AW_DLP_AW_API_BASE=http://127.0.0.1:5600/api/0 AW_DLP_CASE_API_BASE=http://127.0.0.1:5602/api/0/dlp/cases AW_EXPECT_START_OF_DAY={{ aw_worktime_start_of_day | default('') }} AW_EXPECT_ALWAYS_ACTIVE_PATTERN={{ aw_server_always_active_pattern | default('') }} AW_EXPECT_LANDINGPAGE={{ aw_server_landingpage | default('') }} AW_HEALTH_STRICT_FILEOPS={{ aw_health_strict_fileops | default(0) }} AW_MONITORED_WINDOWS_HOST={{ aw_monitored_windows_host }} AW_MONITORED_WINDOWS_HOSTNAME={{ aw_monitored_windows_hostname }} AW_RUS_HEALTH_WORKTIME_API={{ aw_rus_health_worktime_api_base | default('http://127.0.0.1:5610') }} AW_RUS_HEALTH_STATE_DIR={{ aw_rus_health_state_dir }} AW_RUS_HEALTH_VALIDATION_DIR={{ aw_rus_health_validation_dir }} AW_RUS_HEALTH_SESSION_EVENTS_MAX_AGE_SECONDS={{ aw_rus_health_session_events_max_age_seconds | default(86400) }} AW_RUS_HEALTH_GUARD_MAX_AGE_SECONDS={{ aw_rus_health_guard_max_age_seconds | default(300) }} AW_RUS_HEALTH_GUARD_REQUIRED={{ 1 if (aw_rus_health_guard_required | default(true) | bool) else 0 }} AW_RUS_SLO_STATE_DIR={{ aw_server_data_dir }}/slo AW_RUS_SLO_AW_BASE=http://127.0.0.1:5600 AW_RUS_SLO_WORKTIME_BASE={{ aw_rus_health_worktime_api_base | default('http://127.0.0.1:5610') }} AW_RUS_SLO_TARGET_PERCENT={{ aw_rus_slo_target_percent | default('99.97') }} AW_BROWSER_SMOKE_AW_BASE=http://127.0.0.1:5600 AW_BROWSER_SMOKE_WORKTIME_BASE={{ aw_rus_health_worktime_api_base | default('http://127.0.0.1:5610') }} AW_BROWSER_SMOKE_HOST={{ aw_monitored_windows_hostname }} AW_BROWSER_SMOKE_OUTPUT_DIR={{ aw_server_data_dir }}/browser-smoke AW_BROWSER_SMOKE_ENGINE={{ aw_browser_smoke_engine | default('chromium-cli') }} AW_BROWSER_SMOKE_TIMEOUT_MS={{ aw_browser_smoke_timeout_ms | default(20000) }} AW_BROWSER_SMOKE_RENDER_TIMEOUT_MS={{ aw_browser_smoke_render_timeout_ms | default(15000) }} AW_HAYABUSA_AUTO_CASE_ENABLED={{ 'true' if (aw_hayabusa_auto_case_enabled | default(true) | bool) else 'false' }} AW_HAYABUSA_AUTO_CASE_MIN_SEVERITY={{ aw_hayabusa_auto_case_min_severity | default('medium') }} AW_HAYABUSA_TELEGRAM_ENABLED={{ 'true' if (aw_hayabusa_telegram_enabled | default(false) | bool) else 'false' }} AW_HAYABUSA_TELEGRAM_MIN_SEVERITY={{ aw_hayabusa_telegram_min_severity | default('high') }} AW_HAYABUSA_TELEGRAM_BOT_TOKEN={{ aw_hayabusa_telegram_bot_token | default('') }} AW_HAYABUSA_TELEGRAM_CHAT_IDS={{ aw_hayabusa_telegram_chat_ids | default('') }} - name: Создать каталог DLP policy engine ansible.builtin.file: path: /opt/activitywatch/dlp-policy-engine state: directory owner: "{{ aw_server_user }}" group: "{{ aw_server_group }}" mode: "0755" when: aw_dlp_policy_engine_enabled | default(false) | bool - name: Установить systemd unit DLP policy engine ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/dlp-policy-engine/dlp-policy-engine.service" dest: /etc/systemd/system/aw-dlp-policy-engine.service owner: root group: root mode: "0644" when: aw_dlp_policy_engine_enabled | default(false) | bool - name: Проверить локальный Rust DLP policy engine ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/dlp-policy-engine" delegate_to: localhost register: aw_dlp_policy_engine_rust_binary become: false when: aw_dlp_policy_engine_enabled | default(false) | bool - name: Требовать Rust DLP policy engine artifact ansible.builtin.assert: that: - aw_dlp_policy_engine_rust_binary.stat.exists | default(false) fail_msg: "Missing Rust artifact: {{ aw_rust_release_dir }}/dlp-policy-engine" when: aw_dlp_policy_engine_enabled | default(false) | bool - name: Установить Rust DLP policy engine ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/dlp-policy-engine" dest: /usr/local/bin/aw-dlp-policy-engine-rust owner: root group: root mode: "0755" when: - aw_dlp_policy_engine_enabled | default(false) | bool - aw_dlp_policy_engine_rust_binary.stat.exists | default(false) - name: Создать каталог DLP content analysis ansible.builtin.file: path: /opt/activitywatch/dlp-content-analysis state: directory owner: "{{ aw_server_user }}" group: "{{ aw_server_group }}" mode: "0755" when: aw_dlp_content_analysis_enabled | default(true) | bool - name: Скопировать файлы DLP content analysis ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/dlp-content-analysis/" dest: /opt/activitywatch/dlp-content-analysis/ owner: "{{ aw_server_user }}" group: "{{ aw_server_group }}" mode: "0644" when: aw_dlp_content_analysis_enabled | default(true) | bool - name: Установить wrapper запуска DLP content analysis через virtualenv ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/dlp-content-analysis/aw-dlp-content-analyzer.sh" dest: /usr/local/bin/aw-dlp-content-analyzer owner: root group: root mode: "0755" when: aw_dlp_content_analysis_enabled | default(true) | bool - name: Проверить локальный Rust DLP content analyzer ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/dlp-content-analyzer" delegate_to: localhost register: dlp_content_analyzer_rust_binary become: false when: aw_dlp_content_analysis_enabled | default(true) | bool - name: Установить Rust DLP content analyzer ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/dlp-content-analyzer" dest: /usr/local/bin/aw-dlp-content-analyzer-rust owner: root group: root mode: "0755" when: - aw_dlp_content_analysis_enabled | default(true) | bool - dlp_content_analyzer_rust_binary.stat.exists | default(false) - name: Создать virtualenv DLP content analysis ansible.builtin.command: cmd: python3 -m venv /opt/activitywatch/dlp-content-analysis/.venv args: creates: /opt/activitywatch/dlp-content-analysis/.venv/bin/python when: aw_dlp_content_analysis_enabled | default(true) | bool - name: Установить зависимости DLP content analysis ansible.builtin.pip: requirements: /opt/activitywatch/dlp-content-analysis/requirements.txt virtualenv: /opt/activitywatch/dlp-content-analysis/.venv when: aw_dlp_content_analysis_enabled | default(true) | bool - name: Создать каталог DLP integrations ansible.builtin.file: path: /opt/activitywatch/dlp-integrations state: directory owner: "{{ aw_server_user }}" group: "{{ aw_server_group }}" mode: "0755" when: aw_dlp_integrations_enabled | default(true) | bool - name: Скопировать файлы DLP integrations ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/dlp-integrations/{{ item }}" dest: "/opt/activitywatch/dlp-integrations/{{ item }}" owner: "{{ aw_server_user }}" group: "{{ aw_server_group }}" mode: "0644" loop: - cef-config.yaml - syslog-forwarder-config.yaml - webhook-config.yaml when: aw_dlp_integrations_enabled | default(true) | bool - name: Создать state каталог DLP integrations ansible.builtin.file: path: /var/lib/activitywatch/dlp-integrations state: directory owner: "{{ aw_server_user }}" group: "{{ aw_server_group }}" mode: "0755" when: aw_dlp_integrations_enabled | default(true) | bool - name: Установить systemd unit CEF exporter ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/dlp-integrations/cef-exporter.service" dest: /etc/systemd/system/aw-dlp-cef-exporter.service owner: root group: root mode: "0644" when: aw_dlp_integrations_enabled | default(true) | bool - name: Установить systemd timer CEF exporter ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/dlp-integrations/cef-exporter.timer" dest: /etc/systemd/system/aw-dlp-cef-exporter.timer owner: root group: root mode: "0644" when: aw_dlp_integrations_enabled | default(true) | bool - name: Проверить локальный Rust CEF exporter ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/dlp-cef-exporter" delegate_to: localhost register: dlp_cef_exporter_rust_binary become: false when: aw_dlp_integrations_enabled | default(true) | bool - name: Требовать Rust CEF exporter artifact ansible.builtin.assert: that: - dlp_cef_exporter_rust_binary.stat.exists | default(false) fail_msg: "Missing Rust artifact: {{ aw_rust_release_dir }}/dlp-cef-exporter" when: aw_dlp_integrations_enabled | default(true) | bool - name: Установить Rust CEF exporter ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/dlp-cef-exporter" dest: /usr/local/bin/dlp-cef-exporter-rust owner: root group: root mode: "0755" when: - aw_dlp_integrations_enabled | default(true) | bool - dlp_cef_exporter_rust_binary.stat.exists | default(false) - name: Установить systemd unit syslog forwarder ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/dlp-integrations/syslog-forwarder.service" dest: /etc/systemd/system/aw-dlp-syslog-forwarder.service owner: root group: root mode: "0644" when: aw_dlp_integrations_enabled | default(true) | bool - name: Установить systemd timer syslog forwarder ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/dlp-integrations/syslog-forwarder.timer" dest: /etc/systemd/system/aw-dlp-syslog-forwarder.timer owner: root group: root mode: "0644" when: aw_dlp_integrations_enabled | default(true) | bool - name: Проверить локальный Rust syslog forwarder ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/dlp-syslog-forwarder" delegate_to: localhost register: dlp_syslog_forwarder_rust_binary become: false when: aw_dlp_integrations_enabled | default(true) | bool - name: Требовать Rust syslog forwarder artifact ansible.builtin.assert: that: - dlp_syslog_forwarder_rust_binary.stat.exists | default(false) fail_msg: "Missing Rust artifact: {{ aw_rust_release_dir }}/dlp-syslog-forwarder" when: aw_dlp_integrations_enabled | default(true) | bool - name: Установить Rust syslog forwarder ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/dlp-syslog-forwarder" dest: /usr/local/bin/dlp-syslog-forwarder-rust owner: root group: root mode: "0755" when: - aw_dlp_integrations_enabled | default(true) | bool - dlp_syslog_forwarder_rust_binary.stat.exists | default(false) - name: Установить systemd unit webhook sender ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/dlp-integrations/webhook-sender.service" dest: /etc/systemd/system/aw-dlp-webhook-sender.service owner: root group: root mode: "0644" when: aw_dlp_integrations_enabled | default(true) | bool - name: Установить systemd timer webhook sender ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/dlp-integrations/webhook-sender.timer" dest: /etc/systemd/system/aw-dlp-webhook-sender.timer owner: root group: root mode: "0644" when: aw_dlp_integrations_enabled | default(true) | bool - name: Проверить локальный Rust webhook sender ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/dlp-webhook-sender" delegate_to: localhost register: dlp_webhook_sender_rust_binary become: false when: aw_dlp_integrations_enabled | default(true) | bool - name: Требовать Rust webhook sender artifact ansible.builtin.assert: that: - dlp_webhook_sender_rust_binary.stat.exists | default(false) fail_msg: "Missing Rust artifact: {{ aw_rust_release_dir }}/dlp-webhook-sender" when: aw_dlp_integrations_enabled | default(true) | bool - name: Установить Rust webhook sender ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/dlp-webhook-sender" dest: /usr/local/bin/dlp-webhook-sender-rust owner: root group: root mode: "0755" when: - aw_dlp_integrations_enabled | default(true) | bool - dlp_webhook_sender_rust_binary.stat.exists | default(false) - name: Создать каталог DLP case management ansible.builtin.file: path: /opt/activitywatch/dlp-case-management state: directory owner: "{{ aw_server_user }}" group: "{{ aw_server_group }}" mode: "0755" when: aw_dlp_case_management_enabled | default(true) | bool - name: Установить systemd unit DLP case management ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/dlp-case-management/case-service.service" dest: /etc/systemd/system/aw-dlp-case-management.service owner: root group: root mode: "0644" when: aw_dlp_case_management_enabled | default(true) | bool - name: Проверить локальный Rust DLP case management ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/dlp-case-management" delegate_to: localhost register: aw_dlp_case_management_rust_binary become: false when: aw_dlp_case_management_enabled | default(true) | bool - name: Требовать Rust DLP case management artifact ansible.builtin.assert: that: - aw_dlp_case_management_rust_binary.stat.exists | default(false) fail_msg: "Missing Rust artifact: {{ aw_rust_release_dir }}/dlp-case-management" when: aw_dlp_case_management_enabled | default(true) | bool - name: Установить Rust DLP case management ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/dlp-case-management" dest: /usr/local/bin/aw-dlp-case-management-rust owner: root group: root mode: "0755" when: - aw_dlp_case_management_enabled | default(true) | bool - aw_dlp_case_management_rust_binary.stat.exists | default(false) - name: Создать каталоги DLP compliance ansible.builtin.file: path: "{{ item }}" state: directory owner: "{{ aw_server_user }}" group: "{{ aw_server_group }}" mode: "0755" loop: - /opt/activitywatch/dlp-compliance - /opt/activitywatch/dlp-compliance/templates - "{{ aw_dlp_compliance_report_dir }}" when: aw_dlp_compliance_enabled | default(true) | bool - name: Скопировать файлы DLP compliance ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/dlp-compliance/{{ item.src }}" dest: "{{ item.dest }}" owner: "{{ aw_server_user }}" group: "{{ aw_server_group }}" mode: "{{ item.mode }}" loop: - { src: "templates/152-fz-report.html", dest: "/opt/activitywatch/dlp-compliance/templates/152-fz-report.html", mode: "0644" } - { src: "templates/pci-dss-report.html", dest: "/opt/activitywatch/dlp-compliance/templates/pci-dss-report.html", mode: "0644" } - { src: "report-scheduler.service", dest: "/etc/systemd/system/aw-dlp-report-scheduler.service", mode: "0644" } - { src: "report-scheduler.timer", dest: "/etc/systemd/system/aw-dlp-report-scheduler.timer", mode: "0644" } when: aw_dlp_compliance_enabled | default(true) | bool - name: Проверить локальный Rust DLP compliance ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/dlp-compliance" delegate_to: localhost register: aw_dlp_compliance_rust_binary become: false when: aw_dlp_compliance_enabled | default(true) | bool - name: Требовать Rust DLP compliance artifact ansible.builtin.assert: that: - aw_dlp_compliance_rust_binary.stat.exists | default(false) fail_msg: "Missing Rust artifact: {{ aw_rust_release_dir }}/dlp-compliance" when: aw_dlp_compliance_enabled | default(true) | bool - name: Установить Rust DLP compliance ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/dlp-compliance" dest: /usr/local/bin/aw-dlp-compliance-rust owner: root group: root mode: "0755" when: - aw_dlp_compliance_enabled | default(true) | bool - aw_dlp_compliance_rust_binary.stat.exists | default(false) - name: Проверить локальный Rust dlp-admin-cli ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/dlp-admin-cli" delegate_to: localhost register: aw_dlp_admin_cli_rust_binary - name: Требовать Rust dlp-admin-cli artifact ansible.builtin.assert: that: - aw_dlp_admin_cli_rust_binary.stat.exists | default(false) fail_msg: "Missing Rust artifact: {{ aw_rust_release_dir }}/dlp-admin-cli" - name: Установить Rust dlp-admin-cli ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/dlp-admin-cli" dest: /usr/local/bin/dlp-admin-cli owner: root group: root mode: "0755" when: aw_dlp_admin_cli_rust_binary.stat.exists - name: Проверить локальный Rust AW worktime API ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/worktime-api" delegate_to: localhost register: aw_worktime_api_rust_binary become: false - name: Требовать Rust AW worktime API artifact ansible.builtin.assert: that: - aw_worktime_api_rust_binary.stat.exists | default(false) fail_msg: "Missing Rust artifact: {{ aw_rust_release_dir }}/worktime-api" - name: Установить Rust AW worktime API ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/worktime-api" dest: /usr/local/bin/aw-worktime-api-rust owner: root group: root mode: "0755" when: aw_worktime_api_rust_binary.stat.exists | default(false) - name: Установить systemd unit AW worktime API ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/aw-worktime-api.service" dest: /etc/systemd/system/aw-worktime-api.service owner: root group: root mode: "0644" - name: Проверить локальный Rust AW worktime UI bridge ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/worktime-ui-bridge" delegate_to: localhost register: aw_worktime_ui_bridge_rust_binary become: false - name: Требовать Rust AW worktime UI bridge artifact ansible.builtin.assert: that: - aw_worktime_ui_bridge_rust_binary.stat.exists | default(false) fail_msg: "Missing Rust artifact: {{ aw_rust_release_dir }}/worktime-ui-bridge" - name: Установить Rust AW worktime UI bridge ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/worktime-ui-bridge" dest: /usr/local/bin/aw-worktime-ui-bridge-rust owner: root group: root mode: "0755" when: aw_worktime_ui_bridge_rust_binary.stat.exists | default(false) - name: Проверить локальный Rust AW worktime autoheal ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/worktime-autoheal" delegate_to: localhost register: aw_worktime_autoheal_rust_binary become: false - name: Требовать Rust AW worktime autoheal artifact ansible.builtin.assert: that: - aw_worktime_autoheal_rust_binary.stat.exists | default(false) fail_msg: "Missing Rust artifact: {{ aw_rust_release_dir }}/worktime-autoheal" - name: Установить Rust AW worktime autoheal ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/worktime-autoheal" dest: /usr/local/bin/aw-worktime-autoheal-rust owner: root group: root mode: "0755" when: aw_worktime_autoheal_rust_binary.stat.exists | default(false) - name: Проверить локальный Rust AW worktime prewarm ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/worktime-prewarm" delegate_to: localhost register: aw_worktime_prewarm_rust_binary become: false - name: Требовать Rust AW worktime prewarm artifact ansible.builtin.assert: that: - aw_worktime_prewarm_rust_binary.stat.exists | default(false) fail_msg: "Missing Rust artifact: {{ aw_rust_release_dir }}/worktime-prewarm" - name: Установить Rust AW worktime prewarm ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/worktime-prewarm" dest: /usr/local/bin/aw-worktime-prewarm-rust owner: root group: root mode: "0755" when: aw_worktime_prewarm_rust_binary.stat.exists | default(false) - name: Создать server-side ops bundle для AW ops helpers ansible.builtin.file: path: /opt/activitywatch/aw-rus-ops state: directory owner: root group: root mode: "0755" - name: Установить скрипт aw-health-check ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/aw-health-check-wrapper.sh" dest: /usr/local/bin/aw-health-check owner: root group: root mode: "0755" - name: Проверить локальный Rust aw-health-check ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/aw-health-check" delegate_to: localhost register: aw_health_check_rust_binary become: false - name: Установить Rust aw-health-check ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/aw-health-check" dest: /usr/local/bin/aw-health-check-rust owner: root group: root mode: "0755" when: aw_health_check_rust_binary.stat.exists | default(false) - name: Требовать Rust aw-health-check artifact ansible.builtin.assert: that: - aw_health_check_rust_binary.stat.exists | default(false) fail_msg: "Rust aw-health-check artifact is required at {{ aw_rust_release_dir }}/aw-health-check" - name: Проверить локальный Rust aw-db-health ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/aw-db-health" delegate_to: localhost register: aw_db_health_rust_binary become: false - name: Установить Rust aw-db-health ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/aw-db-health" dest: /usr/local/bin/aw-db-health owner: root group: root mode: "0755" when: aw_db_health_rust_binary.stat.exists | default(false) - name: Установить скрипт check-aw-data ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/check-aw-data-wrapper.sh" dest: /usr/local/bin/check-aw-data owner: root group: root mode: "0755" - name: Положить legacy check-aw-data в server-side ops bundle ansible.builtin.copy: src: "{{ aw_repo_root }}/scripts/legacy/check-aw-data.sh" dest: /opt/activitywatch/aw-rus-ops/check-aw-data.sh owner: root group: root mode: "0755" - name: Проверить локальный Rust check-aw-data ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/check-aw-data" delegate_to: localhost register: check_aw_data_rust_binary become: false - name: Установить Rust check-aw-data ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/check-aw-data" dest: /usr/local/bin/check-aw-data-rust owner: root group: root mode: "0755" when: check_aw_data_rust_binary.stat.exists | default(false) - name: Проверить локальный Rust dlp-health-check ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/dlp-health-check" delegate_to: localhost register: dlp_health_check_rust_binary become: false - name: Установить Rust dlp-health-check ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/dlp-health-check" dest: /usr/local/bin/dlp-health-check owner: root group: root mode: "0755" when: dlp_health_check_rust_binary.stat.exists | default(false) - name: Проверить локальный Rust AW-RUS healthd ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/aw-rus-healthd" delegate_to: localhost register: aw_rus_healthd_rust_binary become: false - name: Проверить локальный Rust AW-RUS SLO monitor ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/aw-slo-monitor" delegate_to: localhost register: aw_slo_monitor_rust_binary become: false - name: Требовать Rust dlp-health-check artifact ansible.builtin.assert: that: - dlp_health_check_rust_binary.stat.exists | default(false) fail_msg: "Missing Rust artifact: {{ aw_rust_release_dir }}/dlp-health-check" - name: Требовать Rust AW-RUS healthd artifact ansible.builtin.assert: that: - aw_rus_healthd_rust_binary.stat.exists | default(false) fail_msg: "Missing Rust artifact: {{ aw_rust_release_dir }}/aw-rus-healthd" - name: Требовать Rust AW-RUS SLO monitor artifact ansible.builtin.assert: that: - aw_slo_monitor_rust_binary.stat.exists | default(false) fail_msg: "Missing Rust artifact: {{ aw_rust_release_dir }}/aw-slo-monitor" - name: Установить Rust AW-RUS healthd ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/aw-rus-healthd" dest: /usr/local/bin/aw-rus-healthd-rust owner: root group: root mode: "0755" - name: Установить Rust AW-RUS SLO monitor ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/aw-slo-monitor" dest: /usr/local/bin/aw-slo-monitor-rust owner: root group: root mode: "0755" when: aw_slo_monitor_rust_binary.stat.exists | default(false) - name: Установить browser smoke скрипт ansible.builtin.copy: src: "{{ aw_repo_root }}/scripts/aw-webui-browser-smoke.mjs" dest: /usr/local/lib/aw-rus-browser-smoke/aw-webui-browser-smoke.mjs owner: root group: root mode: "0755" when: aw_browser_smoke_enabled | default(true) | bool - name: Установить systemd unit AW worktime UI bridge ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/aw-worktime-ui-bridge.service" dest: /etc/systemd/system/aw-worktime-ui-bridge.service owner: root group: root mode: "0644" - name: Установить systemd timer AW worktime UI bridge ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/aw-worktime-ui-bridge.timer" dest: /etc/systemd/system/aw-worktime-ui-bridge.timer owner: root group: root mode: "0644" - name: Установить systemd unit AW worktime autoheal ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/aw-worktime-autoheal.service" dest: /etc/systemd/system/aw-worktime-autoheal.service owner: root group: root mode: "0644" - name: Установить systemd timer AW worktime autoheal ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/aw-worktime-autoheal.timer" dest: /etc/systemd/system/aw-worktime-autoheal.timer owner: root group: root mode: "0644" - name: Установить systemd unit AW worktime prewarm ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/aw-worktime-prewarm.service" dest: /etc/systemd/system/aw-worktime-prewarm.service owner: root group: root mode: "0644" - name: Установить systemd timer AW worktime prewarm ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/aw-worktime-prewarm.timer" dest: /etc/systemd/system/aw-worktime-prewarm.timer owner: root group: root mode: "0644" - name: Установить systemd unit AW-RUS healthd ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/aw-rus-healthd.service" dest: /etc/systemd/system/aw-rus-healthd.service owner: root group: root mode: "0644" - name: Установить systemd timer AW-RUS healthd ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/aw-rus-healthd.timer" dest: /etc/systemd/system/aw-rus-healthd.timer owner: root group: root mode: "0644" - name: Установить systemd unit AW-RUS SLO monitor ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/aw-slo-monitor.service" dest: /etc/systemd/system/aw-slo-monitor.service owner: root group: root mode: "0644" - name: Установить systemd timer AW-RUS SLO monitor ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/aw-slo-monitor.timer" dest: /etc/systemd/system/aw-slo-monitor.timer owner: root group: root mode: "0644" - name: Установить systemd unit AW-RUS browser smoke ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/aw-browser-smoke.service" dest: /etc/systemd/system/aw-browser-smoke.service owner: root group: root mode: "0644" when: aw_browser_smoke_enabled | default(true) | bool - name: Установить systemd timer AW-RUS browser smoke ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/aw-browser-smoke.timer" dest: /etc/systemd/system/aw-browser-smoke.timer owner: root group: root mode: "0644" when: aw_browser_smoke_enabled | default(true) | bool - name: Установить systemd unit AW worktime Influx exporter ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/aw-worktime-influx-exporter.service" dest: /etc/systemd/system/aw-worktime-influx-exporter.service owner: root group: root mode: "0644" when: aw_worktime_influx_enabled | default(false) | bool - name: Проверить локальный Rust AW worktime Influx exporter ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/worktime-influx-exporter" delegate_to: localhost register: aw_worktime_influx_exporter_rust_binary become: false when: aw_worktime_influx_enabled | default(false) | bool - name: Требовать Rust AW worktime Influx exporter artifact ansible.builtin.assert: that: - aw_worktime_influx_exporter_rust_binary.stat.exists | default(false) fail_msg: "Missing Rust artifact: {{ aw_rust_release_dir }}/worktime-influx-exporter" when: aw_worktime_influx_enabled | default(false) | bool - name: Установить Rust AW worktime Influx exporter ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/worktime-influx-exporter" dest: /usr/local/bin/aw-worktime-influx-exporter-rust owner: root group: root mode: "0755" when: - aw_worktime_influx_enabled | default(false) | bool - aw_worktime_influx_exporter_rust_binary.stat.exists | default(false) - name: Установить systemd timer AW worktime Influx exporter ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/aw-worktime-influx-exporter.timer" dest: /etc/systemd/system/aw-worktime-influx-exporter.timer owner: root group: root mode: "0644" when: aw_worktime_influx_enabled | default(false) | bool - name: Установить systemd unit AW DLP Influx exporter ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/aw-dlp-influx-exporter.service" dest: /etc/systemd/system/aw-dlp-influx-exporter.service owner: root group: root mode: "0644" when: aw_dlp_influx_enabled | default(false) | bool - name: Проверить локальный Rust AW DLP Influx exporter ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/dlp-influx-exporter" delegate_to: localhost register: aw_dlp_influx_exporter_rust_binary become: false when: aw_dlp_influx_enabled | default(false) | bool - name: Требовать Rust AW DLP Influx exporter artifact ansible.builtin.assert: that: - aw_dlp_influx_exporter_rust_binary.stat.exists | default(false) fail_msg: "Missing Rust artifact: {{ aw_rust_release_dir }}/dlp-influx-exporter" when: aw_dlp_influx_enabled | default(false) | bool - name: Установить Rust AW DLP Influx exporter ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/dlp-influx-exporter" dest: /usr/local/bin/aw-dlp-influx-exporter-rust owner: root group: root mode: "0755" when: - aw_dlp_influx_enabled | default(false) | bool - aw_dlp_influx_exporter_rust_binary.stat.exists | default(false) - name: Установить systemd timer AW DLP Influx exporter ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/aw-dlp-influx-exporter.timer" dest: /etc/systemd/system/aw-dlp-influx-exporter.timer owner: root group: root mode: "0644" when: aw_dlp_influx_enabled | default(false) | bool - name: Перезагрузить systemd после установки AW worktime API ansible.builtin.systemd: daemon_reload: true - name: Включить таймер AW-RUS healthd ansible.builtin.systemd: name: aw-rus-healthd.timer enabled: true state: started - name: Включить таймер AW-RUS SLO monitor ansible.builtin.systemd: name: aw-slo-monitor.timer enabled: true state: started - name: Включить таймер AW-RUS browser smoke ansible.builtin.systemd: name: aw-browser-smoke.timer enabled: true state: started when: aw_browser_smoke_enabled | default(true) | bool - name: Включить и перезапустить DLP policy engine ansible.builtin.systemd: name: aw-dlp-policy-engine.service enabled: true state: restarted when: aw_dlp_policy_engine_enabled | default(false) | bool - name: Включить и перезапустить timer CEF exporter ansible.builtin.systemd: name: aw-dlp-cef-exporter.timer enabled: true state: restarted when: aw_dlp_integrations_enabled | default(true) | bool - name: Включить и перезапустить timer syslog forwarder ansible.builtin.systemd: name: aw-dlp-syslog-forwarder.timer enabled: true state: restarted when: aw_dlp_integrations_enabled | default(true) | bool - name: Включить и перезапустить timer webhook sender ansible.builtin.systemd: name: aw-dlp-webhook-sender.timer enabled: true state: restarted when: aw_dlp_integrations_enabled | default(true) | bool - name: Включить и перезапустить DLP case management ansible.builtin.systemd: name: aw-dlp-case-management.service enabled: true state: restarted when: aw_dlp_case_management_enabled | default(true) | bool - name: Включить и перезапустить timer DLP compliance report ansible.builtin.systemd: name: aw-dlp-report-scheduler.timer enabled: true state: restarted when: aw_dlp_compliance_enabled | default(true) | bool - name: Выполнить разовый прогон DLP compliance report ansible.builtin.systemd: name: aw-dlp-report-scheduler.service state: started failed_when: false when: aw_dlp_compliance_enabled | default(true) | bool - name: Включить и перезапустить AW worktime API ansible.builtin.systemd: name: aw-worktime-api.service enabled: true state: restarted - name: Отключить legacy timer aw-worktime-afk-bridge (если есть) ansible.builtin.systemd: name: aw-worktime-afk-bridge.timer enabled: false state: stopped failed_when: false - name: Включить и перезапустить AW worktime UI bridge timer ansible.builtin.systemd: name: aw-worktime-ui-bridge.timer enabled: true state: restarted - name: Выполнить разовый прогон AW worktime UI bridge ansible.builtin.systemd: name: aw-worktime-ui-bridge.service state: started failed_when: false - name: Включить и перезапустить AW worktime autoheal timer ansible.builtin.systemd: name: aw-worktime-autoheal.timer enabled: true state: restarted - name: Выполнить разовый прогон AW worktime autoheal ansible.builtin.systemd: name: aw-worktime-autoheal.service state: started failed_when: false - name: Включить и перезапустить AW worktime prewarm timer ansible.builtin.systemd: name: aw-worktime-prewarm.timer enabled: true state: restarted - name: Выполнить разовый прогон AW worktime prewarm ansible.builtin.systemd: name: aw-worktime-prewarm.service state: started failed_when: false - name: Включить и перезапустить AW worktime Influx exporter timer ansible.builtin.systemd: name: aw-worktime-influx-exporter.timer enabled: true state: restarted when: aw_worktime_influx_enabled | default(false) | bool - name: Выполнить разовый прогон AW worktime Influx exporter ansible.builtin.systemd: name: aw-worktime-influx-exporter.service state: started when: aw_worktime_influx_enabled | default(false) | bool - name: Включить и перезапустить AW DLP Influx exporter timer ansible.builtin.systemd: name: aw-dlp-influx-exporter.timer enabled: true state: restarted when: aw_dlp_influx_enabled | default(false) | bool - name: Выполнить разовый прогон AW DLP Influx exporter ansible.builtin.systemd: name: aw-dlp-influx-exporter.service state: started when: aw_dlp_influx_enabled | default(false) | bool - name: Применить хотфиксы compiled JS чанков (Trends, Timespiral, Category helper) ansible.builtin.command: cmd: "/opt/activitywatch/aw-server/apply_webui_ru_patch.sh" register: apply_ru_patch_result failed_when: false - name: Вывести результат применения хотфиксов ansible.builtin.debug: msg: "apply_webui_ru_patch.sh: {{ apply_ru_patch_result.stdout }}" - name: Проверить наличие index.html после копирования ansible.builtin.stat: path: "{{ aw_server_webui_dir }}/index.html" register: aw_webui_ru_index - name: Проверить, что index.html доступен для RU patch ansible.builtin.assert: that: - aw_webui_ru_index.stat.exists - (aw_webui_ru_index.stat.size | default(0) | int) > 0 fail_msg: "Не найден index.html WebUI для применения RU patch." - name: Проверить, что index.html не опустел после применения RU patch ansible.builtin.stat: path: "{{ aw_server_webui_dir }}/index.html" register: aw_webui_ru_index_after_patch - name: Подтвердить, что index.html остался непустым ansible.builtin.assert: that: - aw_webui_ru_index_after_patch.stat.exists - (aw_webui_ru_index_after_patch.stat.size | default(0) | int) > 0 fail_msg: "index.html WebUI пуст после RU patch." - name: Удалить старые теги RU patch из index.html ansible.builtin.replace: path: "{{ aw_server_webui_dir }}/index.html" regexp: ']+(?:ru-patch-v5\.js|sw-cleanup\.js|aw-ru-patch\.js|aw-sw-cleanup\.js)[^>]*>' replace: '' - name: Добавить cleanup script RU patch в index.html ansible.builtin.replace: path: "{{ aw_server_webui_dir }}/index.html" regexp: '' replace: '' - name: Добавить загрузчик RU patch перед закрытием body ansible.builtin.replace: path: "{{ aw_server_webui_dir }}/index.html" regexp: '' replace: '' - name: Check Rust merge AW DB binary artifact ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/merge-aw-server-dbs" register: aw_merge_db_rust_binary delegate_to: localhost become: false - name: Требовать Rust merge AW DB artifact ansible.builtin.assert: that: - aw_merge_db_rust_binary.stat.exists | default(false) fail_msg: "Missing Rust artifact: {{ aw_rust_release_dir }}/merge-aw-server-dbs" when: aw_legacy_db_merge_enabled | default(false) | bool - name: Install Rust merge AW DB binary ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/merge-aw-server-dbs" dest: /usr/local/bin/merge-aw-server-dbs owner: root group: root mode: "0755" when: aw_merge_db_rust_binary.stat.exists | default(false) - name: Проверить наличие legacy root DB ansible.builtin.stat: path: /root/.local/share/activitywatch/aw-server-rust/sqlite.db register: aw_legacy_root_db - name: Проверить наличие target DB ansible.builtin.stat: path: "{{ aw_server_db_path }}" register: aw_target_db - name: Остановить сервис перед merge server DB ansible.builtin.systemd: name: activitywatch-server.service state: stopped when: - aw_legacy_db_merge_enabled | default(false) | bool - aw_legacy_root_db.stat.exists | default(false) - name: Создать backup каталоги server DB ansible.builtin.file: path: "{{ aw_server_data_dir }}/backups/db" state: directory owner: "{{ aw_server_user }}" group: "{{ aw_server_group }}" mode: "0755" when: - aw_legacy_db_merge_enabled | default(false) | bool - aw_legacy_root_db.stat.exists | default(false) - name: Backup target DB перед merge ansible.builtin.copy: remote_src: true src: "{{ aw_server_db_path }}" dest: "{{ aw_server_data_dir }}/backups/db/target-before-merge-{{ ansible_date_time.iso8601_basic_short }}.sqlite.db" owner: "{{ aw_server_user }}" group: "{{ aw_server_group }}" mode: "0644" when: - aw_legacy_db_merge_enabled | default(false) | bool - aw_legacy_root_db.stat.exists | default(false) - aw_target_db.stat.exists | default(false) - name: Backup legacy root DB перед merge ansible.builtin.copy: remote_src: true src: /root/.local/share/activitywatch/aw-server-rust/sqlite.db dest: "{{ aw_server_data_dir }}/backups/db/legacy-root-{{ ansible_date_time.iso8601_basic_short }}.sqlite.db" owner: "{{ aw_server_user }}" group: "{{ aw_server_group }}" mode: "0644" when: - aw_legacy_db_merge_enabled | default(false) | bool - aw_legacy_root_db.stat.exists | default(false) - name: Merge legacy root DB в target DB ansible.builtin.command: argv: - /usr/local/bin/merge-aw-server-dbs - --base - /root/.local/share/activitywatch/aw-server-rust/sqlite.db - --overlay - "{{ aw_server_db_path }}" - --output - "{{ aw_server_db_path }}.merged" register: aw_merge_result failed_when: false when: - aw_legacy_db_merge_enabled | default(false) | bool - aw_legacy_root_db.stat.exists | default(false) - aw_target_db.stat.exists | default(false) - name: Показать результат merge legacy root DB ansible.builtin.debug: msg: "{{ aw_merge_result.stdout | default(aw_merge_result.stderr | default('merge not executed')) }}" when: - aw_legacy_db_merge_enabled | default(false) | bool - aw_legacy_root_db.stat.exists | default(false) - aw_target_db.stat.exists | default(false) - name: Install merged DB as active target DB ansible.builtin.copy: remote_src: true src: "{{ aw_server_db_path }}.merged" dest: "{{ aw_server_db_path }}" owner: "{{ aw_server_user }}" group: "{{ aw_server_group }}" mode: "0644" when: - aw_legacy_db_merge_enabled | default(false) | bool - aw_legacy_root_db.stat.exists | default(false) - aw_target_db.stat.exists | default(false) - aw_merge_result is defined - (aw_merge_result.rc | default(1) | int) == 0 - name: Скопировать legacy root DB в target DB если target ещё не существует ansible.builtin.copy: remote_src: true src: /root/.local/share/activitywatch/aw-server-rust/sqlite.db dest: "{{ aw_server_db_path }}" owner: "{{ aw_server_user }}" group: "{{ aw_server_group }}" mode: "0644" when: - aw_legacy_db_merge_enabled | default(false) | bool - aw_legacy_root_db.stat.exists | default(false) - not (aw_target_db.stat.exists | default(false)) - name: Записать aw-server-rust config.toml с разрешёнными CORS origin ansible.builtin.copy: dest: "{{ aw_server_data_dir }}/.config/activitywatch/aw-server-rust/config.toml" owner: "{{ aw_server_user }}" group: "{{ aw_server_group }}" mode: "0644" content: | cors = [ {% for origin in aw_server_cors_origins | default([]) %} "{{ origin }}"{% if not loop.last %},{% endif %} {% endfor %} ] - name: Включить и запустить сервис ansible.builtin.systemd: name: activitywatch-server.service enabled: true state: restarted daemon_reload: true - name: Дождаться ответа API ansible.builtin.uri: url: "http://127.0.0.1:{{ aw_server_port }}/api/0/info" method: GET status_code: 200 register: aw_api retries: 10 delay: 3 until: aw_api.status == 200 - name: Считать текущие server-side settings ansible.builtin.uri: url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/" method: GET status_code: 200 register: aw_settings_current when: aw_apply_worktime_settings | default(false) | bool - name: Считать текущие server-side views ansible.builtin.uri: url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/views" method: GET status_code: 200 register: aw_views_current when: aw_apply_worktime_settings | default(false) | bool - name: Считать текущие server-side classes ansible.builtin.uri: url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/classes" method: GET status_code: 200 register: aw_classes_current when: aw_apply_worktime_settings | default(false) | bool - name: Создать backup текущих server-side settings/views/classes ansible.builtin.copy: dest: "{{ aw_server_data_dir }}/backups/{{ item.name }}-{{ ansible_date_time.iso8601_basic_short }}.json" owner: "{{ aw_server_user }}" group: "{{ aw_server_group }}" mode: "0644" content: "{{ item.payload | to_nice_json }}" loop: - name: settings payload: "{{ aw_settings_current.json | default({}) }}" - name: views payload: "{{ aw_views_current.json | default(none) }}" - name: classes payload: "{{ aw_classes_current.json | default(none) }}" when: aw_apply_worktime_settings | default(false) | bool - name: Настроить DLP Aggregator (Phase 2) block: - name: Создать каталог для скриптов ansible.builtin.file: path: "/opt/activitywatch/scripts" state: directory owner: root group: root mode: "0755" - name: Проверить локальный Rust DLP aggregator ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/dlp-aggregator" delegate_to: localhost register: dlp_aggregator_rust_binary become: false - name: Требовать Rust DLP aggregator artifact ansible.builtin.assert: that: - dlp_aggregator_rust_binary.stat.exists | default(false) fail_msg: "Missing Rust artifact: {{ aw_rust_release_dir }}/dlp-aggregator" - name: Установить Rust DLP aggregator ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/dlp-aggregator" dest: /usr/local/bin/dlp-aggregator-rust owner: root group: root mode: "0755" when: dlp_aggregator_rust_binary.stat.exists | default(false) - name: Установить systemd unit для агрегатора ansible.builtin.copy: dest: /etc/systemd/system/activitywatch-dlp-aggregator.service owner: root group: root mode: "0644" content: | [Unit] Description=ActivityWatch DLP Event Aggregator After=activitywatch-server.service [Service] Type=oneshot User={{ aw_server_user }} WorkingDirectory={{ aw_server_data_dir }} ExecStart=/usr/local/bin/dlp-aggregator-rust \ --aw-url http://127.0.0.1:{{ aw_server_port }}/api/0 \ --sqlite-path {{ aw_server_data_dir }}/dlp_warehouse.sqlite \ --state-path {{ aw_server_data_dir }}/dlp-aggregator-state.json [Install] WantedBy=multi-user.target - name: Установить systemd timer для агрегатора ansible.builtin.copy: dest: /etc/systemd/system/activitywatch-dlp-aggregator.timer content: | [Unit] Description=Run ActivityWatch DLP Aggregator every 5 minutes [Timer] OnCalendar=*:3/10:10 AccuracySec=30s RandomizedDelaySec=30s Persistent=false [Install] WantedBy=timers.target - name: Включить и запустить таймер агрегатора ansible.builtin.systemd: name: activitywatch-dlp-aggregator.timer enabled: true state: started daemon_reload: true - name: Настроить IOC enrichment из Hayabusa Sigma when: aw_dlp_ioc_enabled | default(false) | bool block: - name: Создать каталог IOC enrichment ansible.builtin.file: path: "{{ aw_dlp_ioc_workdir }}/{{ item }}" state: directory owner: "{{ aw_server_user }}" group: "{{ aw_server_group }}" mode: "0755" loop: - "" - output - tmp - name: Проверить локальный Rust extractor IOC из Sigma ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/extract-ioc-from-sigma" delegate_to: localhost register: aw_extract_ioc_from_sigma_rust_binary become: false - name: Требовать Rust extractor IOC из Sigma ansible.builtin.assert: that: - aw_extract_ioc_from_sigma_rust_binary.stat.exists | default(false) fail_msg: "Missing Rust artifact: {{ aw_rust_release_dir }}/extract-ioc-from-sigma" - name: Установить Rust extractor IOC из Sigma ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/extract-ioc-from-sigma" dest: /usr/local/bin/aw-extract-ioc-from-sigma owner: root group: root mode: "0755" - name: Установить wrapper refresh IOC ansible.builtin.copy: dest: /usr/local/bin/aw-dlp-ioc-refresh.sh owner: root group: root mode: "0755" content: | #!/usr/bin/env bash set -euo pipefail WORKDIR="{{ aw_dlp_ioc_workdir }}" OUTDIR="${WORKDIR}/output" TMPDIR="${WORKDIR}/tmp" RULES_DIR="${TMPDIR}/hayabusa-rules" ZIP_PATH="${TMPDIR}/hayabusa-rules.zip" mkdir -p "${OUTDIR}" "${TMPDIR}" rm -rf "${RULES_DIR}" curl -fsSL -o "${ZIP_PATH}" "{{ aw_dlp_ioc_rules_zip_url }}" unzip -q -o "${ZIP_PATH}" -d "${TMPDIR}" EXTRACTED_DIR=$(find "${TMPDIR}" -maxdepth 1 -type d -name 'hayabusa-rules-*' | head -n1) if [ -z "${EXTRACTED_DIR}" ]; then echo "ERROR: hayabusa-rules archive extraction failed" >&2 exit 1 fi mv "${EXTRACTED_DIR}" "${RULES_DIR}" /usr/local/bin/aw-extract-ioc-from-sigma \ --rules-root "${RULES_DIR}" \ --out-dir "${OUTDIR}" ln -sfn "${OUTDIR}" "${WORKDIR}/latest" - name: Установить systemd unit IOC refresh ansible.builtin.copy: dest: /etc/systemd/system/aw-dlp-ioc-refresh.service owner: root group: root mode: "0644" content: | [Unit] Description=Refresh DLP IOC blacklist from Hayabusa Sigma rules After=network-online.target Wants=network-online.target [Service] Type=oneshot ExecStart=/usr/local/bin/aw-dlp-ioc-refresh.sh - name: Установить systemd timer IOC refresh ansible.builtin.copy: dest: /etc/systemd/system/aw-dlp-ioc-refresh.timer owner: root group: root mode: "0644" content: | [Unit] Description=Run DLP IOC refresh from Hayabusa rules [Timer] OnBootSec={{ aw_dlp_ioc_refresh_on_boot_sec }} OnUnitActiveSec={{ aw_dlp_ioc_refresh_interval }} Persistent=true Unit=aw-dlp-ioc-refresh.service [Install] WantedBy=timers.target - name: Включить и запустить IOC refresh timer ansible.builtin.systemd: name: aw-dlp-ioc-refresh.timer enabled: true state: started daemon_reload: true - name: Выполнить принудительный refresh IOC ansible.builtin.systemd: name: aw-dlp-ioc-refresh.service state: started - name: Проверить наличие IOC артефактов после refresh ansible.builtin.stat: path: "{{ aw_dlp_ioc_workdir }}/output/{{ item }}" register: aw_dlp_ioc_artifacts loop: - ioc_blacklist.json - ioc_blacklist.csv - ioc_blacklist.sql - name: Assert по IOC артефактам ansible.builtin.assert: that: - item.stat.exists - (item.stat.size | int) > 100 fail_msg: "Не сгенерирован IOC артефакт: {{ item.stat.path | default('unknown') }}" loop: "{{ aw_dlp_ioc_artifacts.results }}" - name: Применить базовые worktime settings (classes) ansible.builtin.uri: url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/classes" method: POST body: "{{ aw_worktime_classes }}" body_format: json status_code: [200, 201] when: aw_apply_worktime_settings | default(false) | bool - name: Применить базовые views для DLP и worktime ansible.builtin.uri: url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/views" method: POST body: "{{ aw_default_views }}" body_format: json status_code: [200, 201] when: aw_apply_worktime_settings | default(false) | bool - name: Вычислить worktime durationDefault из aw_worktime_from/to ansible.builtin.set_fact: aw_worktime_duration_default_derived: >- {{ ( ( (((aw_worktime_to | default('17:00')).split(':')[0] | int) * 60 + ((aw_worktime_to | default('17:00')).split(':')[1] | int)) - (((aw_worktime_from | default('00:00')).split(':')[0] | int) * 60 + ((aw_worktime_from | default('00:00')).split(':')[1] | int)) ) * 60 ) }} when: aw_apply_worktime_settings | default(false) | bool - name: Нормализовать durationDefault для ночных смен ansible.builtin.set_fact: aw_worktime_duration_default_effective: >- {{ (aw_worktime_duration_default_derived | int) if (aw_worktime_duration_default_derived | int) > 0 else ((aw_worktime_duration_default_derived | int) + 86400) }} when: aw_apply_worktime_settings | default(false) | bool - name: Проверить корректность durationDefault ansible.builtin.assert: that: - aw_worktime_duration_default_effective | int > 0 - aw_worktime_duration_default_effective | int <= 86400 fail_msg: "Некорректный интервал рабочего времени: {{ aw_worktime_from }}..{{ aw_worktime_to }}" when: aw_apply_worktime_settings | default(false) | bool - name: Применить базовый период worktime (startOfDay) ansible.builtin.uri: url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/startOfDay" method: POST body: "\"{{ aw_worktime_start_of_day }}\"" headers: Content-Type: application/json status_code: [200, 201] when: aw_apply_worktime_settings | default(false) | bool - name: Применить базовый период worktime (durationDefault seconds) ansible.builtin.uri: url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/durationDefault" method: POST body: "{{ aw_worktime_duration_default_effective | string }}" headers: Content-Type: application/json status_code: [200, 201] when: aw_apply_worktime_settings | default(false) | bool - name: Применить always_active_pattern для fallback без AFK ansible.builtin.uri: url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/always_active_pattern" method: POST body: "\"{{ aw_server_always_active_pattern }}\"" headers: Content-Type: application/json status_code: [200, 201] when: - aw_apply_worktime_settings | default(false) | bool - (aw_server_always_active_pattern | default('') | string | length) > 0 - name: Применить landingpage профиля ansible.builtin.uri: url: "http://127.0.0.1:{{ aw_server_port }}/api/0/settings/landingpage" method: POST body: "\"{{ aw_server_landingpage }}\"" headers: Content-Type: application/json status_code: [200, 201] when: - aw_apply_worktime_settings | default(false) | bool - (aw_server_landingpage | default('') | string | length) > 0 - name: Обновить IOC blacklist из hayabusa (если включено) when: - aw_hayabusa_ioc_refresh_enabled | default(false) | bool - aw_hayabusa_rules_root is defined - aw_hayabusa_ioc_output_dir is defined block: - name: Создать выходную директорию для IOC ansible.builtin.file: path: "{{ aw_hayabusa_ioc_output_dir }}" state: directory mode: "0755" owner: "{{ aw_server_user }}" group: "{{ aw_server_group }}" - name: Обновить IOC blacklist из hayabusa правил ansible.builtin.shell: cmd: | set -euo pipefail {{ aw_repo_root }}/scripts/build_dlp_ioc_from_hayabusa.sh \ "{{ aw_hayabusa_rules_root }}" \ "{{ aw_hayabusa_ioc_output_dir }}" args: chdir: "{{ aw_repo_root }}" register: aw_hayabusa_ioc_refresh_result changed_when: "'IOC artifacts generated' in aw_hayabusa_ioc_refresh_result.stdout" - name: Показать результат обновления IOC ansible.builtin.debug: msg: "{{ aw_hayabusa_ioc_refresh_result.stdout }}" when: aw_hayabusa_ioc_refresh_result.stdout is defined - name: Установить server-side Hayabusa runner when: aw_hayabusa_runner_enabled | default(false) | bool block: - name: Создать каталоги Hayabusa ansible.builtin.file: path: "{{ item }}" state: directory owner: root group: root mode: "0755" loop: - "{{ aw_hayabusa_root }}" - "{{ aw_hayabusa_root }}/releases" - "{{ aw_hayabusa_release_dir }}" - "{{ aw_hayabusa_reports_dir }}" - "{{ aw_hayabusa_state_dir }}" - "{{ aw_hayabusa_inbox_dir }}" - "{{ aw_hayabusa_archive_dir }}" - "{{ aw_hayabusa_incoming_dir }}" - "{{ aw_hayabusa_staging_dir }}" - "{{ aw_hayabusa_archive_packages_dir }}" - "{{ aw_hayabusa_archive_extracted_dir }}" - "{{ aw_hayabusa_logs_dir }}" - name: Скачать архив Hayabusa ansible.builtin.get_url: url: "{{ aw_hayabusa_download_url }}" dest: "{{ aw_hayabusa_archive_path }}" mode: "0644" - name: Распаковать pinned Hayabusa release ansible.builtin.unarchive: src: "{{ aw_hayabusa_archive_path }}" dest: "{{ aw_hayabusa_release_dir }}" remote_src: true creates: "{{ aw_hayabusa_release_dir }}/{{ aw_hayabusa_binary_name }}" - name: Удалить временный архив Hayabusa после распаковки ansible.builtin.file: path: "{{ aw_hayabusa_archive_path }}" state: absent - name: Нормализовать права release Hayabusa ansible.builtin.file: path: "{{ aw_hayabusa_release_dir }}" state: directory owner: root group: root mode: "0755" recurse: true - name: Сделать бинарь Hayabusa исполняемым ansible.builtin.file: path: "{{ aw_hayabusa_release_dir }}/{{ aw_hayabusa_binary_name }}" owner: root group: root mode: "0755" state: file - name: Создать canonical symlink на текущий релиз Hayabusa ansible.builtin.file: src: "{{ aw_hayabusa_release_dir }}" dest: "{{ aw_hayabusa_current_link }}" state: link force: true - name: Создать canonical symlink на бинарь Hayabusa ansible.builtin.file: src: "{{ aw_hayabusa_binary_name }}" dest: "{{ aw_hayabusa_release_dir }}/hayabusa" state: link force: true - name: Установить wrapper aw-hayabusa ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/hayabusa/aw-hayabusa.sh" dest: /usr/local/bin/aw-hayabusa owner: root group: root mode: "0755" - name: Создать server-side ops bundle для Hayabusa ansible.builtin.file: path: "{{ item }}" state: directory owner: root group: root mode: "0755" loop: - /opt/activitywatch/aw-rus-ops - /opt/activitywatch/aw-rus-ops/hayabusa - /opt/activitywatch/aw-rus-ops/ansible - /opt/activitywatch/aw-rus-ops/drop - name: Положить исходный wrapper в server-side ops bundle ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/hayabusa/aw-hayabusa.sh" dest: /opt/activitywatch/aw-rus-ops/hayabusa/aw-hayabusa.sh owner: root group: root mode: "0755" - name: Проверить локальный Rust helper link-case для Hayabusa ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/aw-hayabusa-link-case-rust" delegate_to: localhost register: aw_hayabusa_link_case_rust_binary become: false - name: Установить Rust helper link-case для Hayabusa ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/aw-hayabusa-link-case-rust" dest: /usr/local/bin/aw-hayabusa-link-case-rust owner: root group: root mode: "0755" when: aw_hayabusa_link_case_rust_binary.stat.exists | default(false) - name: Требовать Rust helper link-case для Hayabusa ansible.builtin.assert: that: - aw_hayabusa_link_case_rust_binary.stat.exists | default(false) fail_msg: "Missing Rust artifact: {{ aw_rust_release_dir }}/aw-hayabusa-link-case-rust" - name: Установить helper link-case для Hayabusa ansible.builtin.copy: content: | #!/usr/bin/env bash set -euo pipefail exec /usr/local/bin/aw-hayabusa-link-case-rust "$@" dest: /usr/local/bin/aw-hayabusa-link-case owner: root group: root mode: "0755" - name: Проверить локальный Rust helper from-windows для Hayabusa ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/aw-hayabusa-from-windows-rust" delegate_to: localhost register: aw_hayabusa_from_windows_rust_binary become: false - name: Установить Rust helper from-windows для Hayabusa ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/aw-hayabusa-from-windows-rust" dest: /usr/local/bin/aw-hayabusa-from-windows-rust owner: root group: root mode: "0755" when: aw_hayabusa_from_windows_rust_binary.stat.exists | default(false) - name: Требовать Rust helper from-windows для Hayabusa ansible.builtin.assert: that: - aw_hayabusa_from_windows_rust_binary.stat.exists | default(false) fail_msg: "Missing Rust artifact: {{ aw_rust_release_dir }}/aw-hayabusa-from-windows-rust" - name: Установить helper from-windows для Hayabusa ansible.builtin.copy: content: | #!/usr/bin/env bash set -euo pipefail exec /usr/local/bin/aw-hayabusa-from-windows-rust "$@" dest: /usr/local/bin/aw-hayabusa-from-windows owner: root group: root mode: "0755" - name: Положить README Hayabusa в server-side ops bundle ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/hayabusa/README.md" dest: /opt/activitywatch/aw-rus-ops/hayabusa/README.md owner: root group: root mode: "0644" - name: Проверить локальный Rust helper autoprocess для Hayabusa ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/aw-hayabusa-autoprocess-rust" delegate_to: localhost register: aw_hayabusa_autoprocess_rust_binary become: false - name: Установить Rust helper autoprocess для Hayabusa ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/aw-hayabusa-autoprocess-rust" dest: /usr/local/bin/aw-hayabusa-autoprocess-rust owner: root group: root mode: "0755" when: aw_hayabusa_autoprocess_rust_binary.stat.exists | default(false) - name: Требовать Rust helper autoprocess для Hayabusa ansible.builtin.assert: that: - aw_hayabusa_autoprocess_rust_binary.stat.exists | default(false) fail_msg: "Missing Rust artifact: {{ aw_rust_release_dir }}/aw-hayabusa-autoprocess-rust" - name: Установить helper autoprocess для Hayabusa ansible.builtin.copy: content: | #!/usr/bin/env bash set -euo pipefail exec /usr/local/bin/aw-hayabusa-autoprocess-rust "$@" dest: /usr/local/bin/aw-hayabusa-autoprocess owner: root group: root mode: "0755" - name: Проверить локальный Rust helper case-alert для Hayabusa ansible.builtin.stat: path: "{{ aw_rust_release_dir }}/aw-hayabusa-case-alert-rust" delegate_to: localhost register: aw_hayabusa_case_alert_rust_binary become: false - name: Установить Rust helper case-alert для Hayabusa ansible.builtin.copy: src: "{{ aw_rust_release_dir }}/aw-hayabusa-case-alert-rust" dest: /usr/local/bin/aw-hayabusa-case-alert-rust owner: root group: root mode: "0755" when: aw_hayabusa_case_alert_rust_binary.stat.exists | default(false) - name: Требовать Rust helper case-alert для Hayabusa ansible.builtin.assert: that: - aw_hayabusa_case_alert_rust_binary.stat.exists | default(false) fail_msg: "Missing Rust artifact: {{ aw_rust_release_dir }}/aw-hayabusa-case-alert-rust" - name: Установить helper case-alert для Hayabusa ansible.builtin.copy: content: | #!/usr/bin/env bash set -euo pipefail exec /usr/local/bin/aw-hayabusa-case-alert-rust "$@" dest: /usr/local/bin/aw-hayabusa-case-alert owner: root group: root mode: "0755" - name: Установить systemd unit aw-hayabusa-drop.service ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/aw-hayabusa-drop.service" dest: /etc/systemd/system/aw-hayabusa-drop.service owner: root group: root mode: "0644" notify: Перезагрузить systemd - name: Установить systemd unit aw-hayabusa-drop.path ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/aw-hayabusa-drop.path" dest: /etc/systemd/system/aw-hayabusa-drop.path owner: root group: root mode: "0644" notify: Перезагрузить systemd - name: Включить и запустить aw-hayabusa-drop.path ansible.builtin.systemd: name: aw-hayabusa-drop.path enabled: true state: started daemon_reload: true - name: Проверить server-side runner через doctor ansible.builtin.command: cmd: /usr/local/bin/aw-hayabusa doctor changed_when: false - name: Проверить загрузку profiles Hayabusa ansible.builtin.shell: /usr/local/bin/aw-hayabusa profiles > /tmp/aw-hayabusa-profiles.txt args: executable: /bin/bash changed_when: false - name: Проверить наличие standard profile у Hayabusa ansible.builtin.shell: "grep -q 'standard:' /tmp/aw-hayabusa-profiles.txt" args: executable: /bin/bash changed_when: false - name: Post-deploy health gate (aw-rus-healthd) when: - not ansible_check_mode - aw_server_post_deploy_health_check_enabled | default(true) | bool block: - name: Запустить /usr/local/bin/aw-rus-healthd-rust --json ansible.builtin.command: cmd: /usr/local/bin/aw-rus-healthd-rust --json register: aw_post_deploy_health changed_when: false failed_when: false - name: Показать результат aw-rus-healthd ansible.builtin.debug: msg: "{{ aw_post_deploy_health.stdout }}" handlers: - name: Перезагрузить systemd ansible.builtin.systemd: daemon_reload: true - name: Перезапустить activitywatch ansible.builtin.systemd: name: activitywatch-server.service state: restarted