---
- 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_host_sanitize_cache_bust: "{{ lookup('file', aw_repo_root + '/aw-server/aw-host-sanitize.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' }}"
    aw_db_vacuum_timer_enabled: false

  tasks:
    - name: Refuse inconsistent DLP resource profile
      ansible.builtin.assert:
        that:
          - aw_dlp_profile | default('core_only') in ['core_only', 'light', 'on_demand', 'full']
          - (aw_dlp_profile | default('core_only') == 'core_only') or (aw_dlp_enabled | default(false) | bool)
          - (aw_dlp_enabled | default(false) | bool) or not (
              aw_dlp_influx_enabled | default(false) | bool
              or aw_dlp_ioc_enabled | default(false) | bool
              or aw_dlp_policy_engine_enabled | default(false) | bool
              or aw_dlp_content_analysis_enabled | default(false) | bool
              or aw_dlp_integrations_enabled | default(false) | bool
              or aw_dlp_case_management_enabled | default(false) | bool
              or aw_dlp_compliance_enabled | default(false) | bool
            )
        fail_msg: "Inconsistent DLP profile: keep aw_dlp_enabled=false with all DLP component flags false, or explicitly choose aw_dlp_enabled=true and aw_dlp_profile=light|on_demand|full."

    - 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_security_finding_executor_work_dir | default(aw_server_data_dir ~ '/security-finding-executor') }}"
        - "{{ 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_security_finding_executor_work_dir | default(aw_server_data_dir ~ '/security-finding-executor') }}"
        - "{{ 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 }}
          Environment=AW_WORKTIME_REPORT_DISK_CACHE_DIR={{ aw_worktime_report_disk_cache_dir | default(aw_server_data_dir ~ '/worktime-report-cache') }}
          Environment=AW_WORKTIME_REPORT_DISK_STALE_TTL_SECONDS={{ aw_worktime_report_disk_stale_ttl_seconds | default(86400) }}
          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-host-sanitize.js", dest: "{{ aw_server_webui_dir }}/js/aw-host-sanitize.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: Создать каталог /opt/detmir/bootstrap для apply_webui_ru_patch.sh
          ansible.builtin.file:
            path: /opt/detmir/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: "/opt/detmir/bootstrap/aw-ru-patch.js", mode: "0644" }
            - { src: "{{ aw_repo_root }}/aw-server/aw-sw-cleanup.js", dest: "/opt/detmir/bootstrap/aw-sw-cleanup.js", mode: "0644" }
            - { src: "{{ aw_repo_root }}/aw-server/aw-host-sanitize.js", dest: "/opt/detmir/bootstrap/aw-host-sanitize.js", mode: "0644" }
            - { src: "{{ aw_repo_root }}/aw-server/aw-worktime-panel.js", dest: "/opt/detmir/bootstrap/aw-worktime-panel.js", mode: "0644" }
            - { src: "{{ aw_repo_root }}/aw-server/aw-host-groups.json", dest: "/opt/detmir/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-db-vacuum service до Influx проверок
          ansible.builtin.copy:
            src: "{{ aw_repo_root }}/aw-server/aw-db-vacuum.service"
            dest: /etc/systemd/system/aw-db-vacuum.service
            owner: root
            group: root
            mode: "0644"
          when: aw_db_maintenance_rust_binary_early.stat.exists | default(false)

        - name: Установить aw-db-vacuum timer до Influx проверок
          ansible.builtin.copy:
            src: "{{ aw_repo_root }}/aw-server/aw-db-vacuum.timer"
            dest: /etc/systemd/system/aw-db-vacuum.timer
            owner: root
            group: root
            mode: "0644"
          when: aw_db_maintenance_rust_binary_early.stat.exists | default(false)

        - name: Включить nightly aw-db-vacuum timer до Influx проверок
          ansible.builtin.systemd:
            name: aw-db-vacuum.timer
            enabled: true
            state: started
            daemon_reload: true
          when:
            - aw_db_maintenance_rust_binary_early.stat.exists | default(false)
            - aw_db_vacuum_timer_enabled | bool

        - name: Отключить nightly aw-db-vacuum timer если opt-in не задан
          ansible.builtin.systemd:
            name: aw-db-vacuum.timer
            enabled: false
            state: stopped
            daemon_reload: true
          when:
            - aw_db_maintenance_rust_binary_early.stat.exists | default(false)
            - not (aw_db_vacuum_timer_enabled | bool)

        - 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 runtime values без вывода секретов
          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=', '')) }}"
            aw_existing_monitored_windows_host: "{{ (aw_existing_server_env_raw.content | default('') | b64decode | regex_search('(?m)^AW_MONITORED_WINDOWS_HOST=.*$') | default('', true) | regex_replace('^AW_MONITORED_WINDOWS_HOST=', '')) }}"
            aw_existing_monitored_windows_hostname: "{{ (aw_existing_server_env_raw.content | default('') | b64decode | regex_search('(?m)^AW_MONITORED_WINDOWS_HOSTNAME=.*$') | default('', true) | regex_replace('^AW_MONITORED_WINDOWS_HOSTNAME=', '')) }}"
          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: Выбрать effective monitored Windows target
          ansible.builtin.set_fact:
            aw_effective_monitored_windows_host: >-
              {{
                (aw_monitored_windows_host | default('') | string)
                if (
                  (aw_monitored_windows_host | default('') | string | length) > 0
                  and (aw_monitored_windows_host | default('') | string | regex_search('192\\.0\\.2\\.|198\\.51\\.100\\.|203\\.0\\.113\\.|<|>|WINDOWS_USER_EXAMPLE')) is none
                )
                else aw_existing_monitored_windows_host
              }}
            aw_effective_monitored_windows_hostname: >-
              {{
                (aw_monitored_windows_hostname | default('') | string)
                if (
                  (aw_monitored_windows_hostname | default('') | string | length) > 0
                  and (aw_monitored_windows_hostname | default('') | string | regex_search('192\\.0\\.2\\.|198\\.51\\.100\\.|203\\.0\\.113\\.|<|>|WINDOWS_USER_EXAMPLE')) is none
                )
                else aw_existing_monitored_windows_hostname
              }}
          no_log: true

        - name: Выбрать effective worktime host
          ansible.builtin.set_fact:
            aw_effective_worktime_host: >-
              {{
                (aw_worktime_host | default('') | string)
                if (
                  (aw_worktime_host | default('') | string | length) > 0
                  and (aw_worktime_host | default('') | string | regex_search('192\\.0\\.2\\.|198\\.51\\.100\\.|203\\.0\\.113\\.|<|>|WINDOWS_USER_EXAMPLE')) is none
                )
                else aw_effective_monitored_windows_hostname
              }}
          no_log: true

        - name: Проверить monitored Windows target для production healthd
          ansible.builtin.assert:
            that:
              - (aw_effective_monitored_windows_host | default('') | string | length) > 0
              - (aw_effective_monitored_windows_host | default('') | string | regex_search('192\\.0\\.2\\.|198\\.51\\.100\\.|203\\.0\\.113\\.|<|>|WINDOWS_USER_EXAMPLE')) is none
              - (aw_effective_monitored_windows_hostname | default('') | string | length) > 0
              - (aw_effective_monitored_windows_hostname | default('') | string | regex_search('192\\.0\\.2\\.|198\\.51\\.100\\.|203\\.0\\.113\\.|<|>|WINDOWS_USER_EXAMPLE')) is none
              - (aw_effective_worktime_host | default('') | string | length) > 0
              - (aw_effective_worktime_host | default('') | string | regex_search('192\\.0\\.2\\.|198\\.51\\.100\\.|203\\.0\\.113\\.|<|>|WINDOWS_USER_EXAMPLE')) is none
            fail_msg: "aw-rus-healthd получил public example/TEST-NET Windows target. Задайте live значения в private inventory/env или сохраните их в runtime /etc/activitywatch/aw-server.env."
          when: aw_server_post_deploy_health_check_enabled | default(true) | bool

        - name: Проверить Influx token для AW worktime exporter
          ansible.builtin.assert:
            that:
              - aw_effective_worktime_influx_token is defined
              - aw_effective_worktime_influx_token | length > 0
              - (aw_effective_worktime_influx_token | string | lower | regex_search('^(change_me|changeme|replace-me|replace_me|token|secret|password|api_key|influx_token|write_token|your_.*|<.*>)$')) is none
            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
              - (aw_effective_dlp_influx_token | string | lower | regex_search('^(change_me|changeme|replace-me|replace_me|token|secret|password|api_key|influx_token|write_token|your_.*|<.*>)$')) is none
            fail_msg: "aw_dlp_influx_enabled=true, но token пуст и в локальном env, и в текущем /etc/activitywatch/aw-server.env. Exporter будет падать и Grafana не получит DLP-ряды."
          when:
            - aw_dlp_enabled | default(false) | bool
            - aw_dlp_influx_enabled | default(false) | bool

        - name: Проверить destination для AW worktime Influx exporter
          ansible.builtin.assert:
            that:
              - (aw_worktime_influx_url | default('') | string | length) > 0
              - "'192.0.2.' not in (aw_worktime_influx_url | default('') | string)"
              - "'198.51.100.' not in (aw_worktime_influx_url | default('') | string)"
              - "'203.0.113.' not in (aw_worktime_influx_url | default('') | string)"
              - (aw_worktime_influx_org | default('') | string | length) > 0
              - (aw_worktime_influx_org | default('') | string | lower | regex_search('example|change_me|changeme|replace-me|replace_me|your_|<|>')) is none
              - (aw_worktime_influx_bucket | default('') | string | length) > 0
              - (aw_worktime_influx_bucket | default('') | string | lower | regex_search('example|change_me|changeme|replace-me|replace_me|your_|<|>')) is none
              - (aw_worktime_influx_hosts | default('') | string | length) > 0
              - "'WINDOWS_USER_EXAMPLE' not in (aw_worktime_influx_hosts | default('') | string)"
            fail_msg: "aw_worktime_influx_enabled=true, но URL/org/bucket/hosts похожи на public example/TEST-NET значения. Задайте live значения в private inventory/env, не в public repo."
          when: aw_worktime_influx_enabled | default(false) | bool

        - name: Проверить destination для AW DLP Influx exporter
          ansible.builtin.assert:
            that:
              - (aw_dlp_influx_url | default('') | string | length) > 0
              - "'192.0.2.' not in (aw_dlp_influx_url | default('') | string)"
              - "'198.51.100.' not in (aw_dlp_influx_url | default('') | string)"
              - "'203.0.113.' not in (aw_dlp_influx_url | default('') | string)"
              - (aw_dlp_influx_org | default('') | string | length) > 0
              - (aw_dlp_influx_org | default('') | string | lower | regex_search('example|change_me|changeme|replace-me|replace_me|your_|<|>')) is none
              - (aw_dlp_influx_bucket | default('') | string | length) > 0
              - (aw_dlp_influx_bucket | default('') | string | lower | regex_search('example|change_me|changeme|replace-me|replace_me|your_|<|>')) is none
              - (aw_dlp_influx_hosts | default('') | string | length) > 0
              - "'WINDOWS_USER_EXAMPLE' not in (aw_dlp_influx_hosts | default('') | string)"
            fail_msg: "aw_dlp_influx_enabled=true, но URL/org/bucket/hosts похожи на public example/TEST-NET значения. Задайте live значения в private inventory/env, не в public repo."
          when:
            - aw_dlp_enabled | default(false) | bool
            - 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_WORKTIME_HOST={{ aw_effective_worktime_host | default(aw_effective_monitored_windows_hostname | default('HOST-EXAMPLE')) }}
              AW_WORKTIME_EVENTS_LIMIT={{ aw_worktime_events_limit | default(5000) }}
              AW_WORKTIME_AW_HTTP_TIMEOUT_SECONDS={{ aw_worktime_aw_http_timeout_seconds | default(6) }}
              AW_WORKTIME_EVENTS_CACHE_TTL_SECONDS={{ aw_worktime_events_cache_ttl_seconds | default(300) }}
              AW_WORKTIME_REPORT_CACHE_TTL_SECONDS={{ aw_worktime_report_cache_ttl_seconds | default(300) }}
              AW_WORKTIME_REPORT_STALE_TTL_SECONDS={{ aw_worktime_report_stale_ttl_seconds | default(3600) }}
              AW_WORKTIME_REPORT_DISK_CACHE_DIR={{ aw_worktime_report_disk_cache_dir | default(aw_server_data_dir ~ '/worktime-report-cache') }}
              AW_WORKTIME_REPORT_DISK_STALE_TTL_SECONDS={{ aw_worktime_report_disk_stale_ttl_seconds | default(86400) }}
              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(aw_effective_monitored_windows_hostname | default('HOST-EXAMPLE')) }}
              AW_WORKTIME_INFLUX_DAYS={{ aw_worktime_influx_days | default('today,yesterday') }}
              AW_WORKTIME_INFLUX_TOKEN={{ aw_effective_worktime_influx_token | default('') }}
              AW_WORKTIME_MANAGEMENT_HISTORY_DIR={{ aw_worktime_management_history_dir | default(aw_server_data_dir ~ '/worktime-management-history') }}
              AW_WORKTIME_MANAGEMENT_HISTORY_DAYS={{ aw_worktime_management_history_days | default(31) }}
              AW_WORKTIME_MANAGEMENT_HISTORY_RETENTION_DAYS={{ aw_worktime_management_history_retention_days | default(120) }}
              AW_WORKTIME_MANAGER_OVERLOAD_COVERAGE_PCT={{ aw_worktime_manager_overload_coverage_pct | default(115) }}
              AW_WORKTIME_MANAGER_TREND_MIN_POINTS={{ aw_worktime_manager_trend_min_points | default(3) }}
              AW_WORKTIME_MANAGER_TREND_DELTA_PCT={{ aw_worktime_manager_trend_delta_pct | default(10) }}
              AW_WORKTIME_MANAGER_OFF_HOURS_THRESHOLD_SECONDS={{ aw_worktime_manager_off_hours_threshold_seconds | default(1800) }}
              AW_WORKTIME_MANAGER_INTERPRETATION_POLICY={{ aw_worktime_interpretation_policy_path | default('/etc/activitywatch/worktime-interpretation-policy.json') }}
              AW_DLP_ENABLED={{ 'true' if (aw_dlp_enabled | default(false) | bool) else 'false' }}
              AW_DLP_PROFILE={{ aw_dlp_profile | default('core_only') }}
              AW_DLP_DISABLED_REASON={{ aw_dlp_disabled_reason | default('') }}
              AW_DLP_DISABLED_SINCE={{ aw_dlp_disabled_since | default('') }}
              AW_DLP_GUARD_ENABLED={{ 'true' if (aw_dlp_light_guard_enabled | default(true) | bool) else 'false' }}
              AW_DLP_GUARD_STATE_DIR={{ aw_dlp_light_guard_state_dir | default(aw_server_data_dir ~ '/health') }}
              AW_DLP_GUARD_LOAD_RATIO={{ aw_dlp_light_guard_load_ratio | default('1.50') }}
              AW_DLP_GUARD_MEM_AVAILABLE_PCT_MIN={{ aw_dlp_light_guard_mem_available_pct_min | default('15') }}
              AW_DLP_GUARD_IOWAIT_PCT_MAX={{ aw_dlp_light_guard_iowait_pct_max | default('20') }}
              AW_DLP_GUARD_STRIKES_REQUIRED={{ aw_dlp_light_guard_strikes_required | default(3) }}
              AW_DLP_CONTROL_BIN=/usr/local/bin/detmir-dlp-runtime-control
              AW_CONTAINMENT_ENABLED={{ 'true' if (aw_containment_enabled | default(false) | bool) else 'false' }}
              AW_CONTAINMENT_MODE={{ aw_containment_mode | default('shadow') }}
              AW_CONTAINMENT_POLICY={{ aw_containment_policy_path | default('/etc/activitywatch/containment-policy.json') }}
              AW_CONTAINMENT_DEFAULT_TTL_MINUTES={{ aw_containment_default_ttl_minutes | default(60) }}
              AW_CONTAINMENT_REQUIRE_ADMIN_CHANNEL_CHECK={{ 'true' if (aw_containment_require_admin_channel_check | default(true) | bool) else 'false' }}
              AW_CONTAINMENT_ALLOW_AUTO_FOR_SERVERS={{ 'true' if (aw_containment_allow_auto_for_servers | default(false) | bool) else 'false' }}
              AW_DLP_INFLUX_ENABLED={{ 'true' if ((aw_dlp_enabled | default(false) | bool) and (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(aw_effective_monitored_windows_hostname | default('HOST-EXAMPLE')) }}
              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_effective_monitored_windows_host }}
              AW_MONITORED_WINDOWS_HOSTNAME={{ aw_effective_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_HEALTH_RDP_TCP_REQUIRED={{ 'true' if (aw_rus_health_rdp_tcp_required | default(true) | bool) else 'false' }}
              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_effective_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('') }}
              AW_SECURITY_FINDING_INBOX_ENABLED={{ 'true' if (aw_security_finding_inbox_enabled | default(false) | bool) else 'false' }}
              AW_SECURITY_FINDING_INBOX_REQUIRED={{ 'true' if (aw_security_finding_inbox_required | default(false) | bool) else 'false' }}
              AW_SECURITY_FINDING_INBOX_BIN={{ aw_security_finding_inbox_bin | default('/usr/local/bin/security-finding-inbox') }}
              AW_SECURITY_FINDING_INBOX_MIN_SEVERITY={{ aw_security_finding_inbox_min_severity | default('medium') }}
              AW_SECURITY_FINDING_EXECUTOR_WORK_DIR={{ aw_security_finding_executor_work_dir | default(aw_server_data_dir ~ '/security-finding-executor') }}
              AW_SECURITY_FINDING_EXECUTOR_LOCK={{ aw_security_finding_executor_lock | default('/var/lock/aw-security-finding-executor.lock') }}
              AW_CONTAINMENT_ENGINE_BIN={{ aw_containment_engine_bin | default('/usr/local/bin/containment-engine') }}
              AW_CONTAINMENT_MANAGEMENT_ALLOWLIST={{ aw_containment_management_allowlist | default('') }}
              AW_CONTAINMENT_BLOCKED_REMOTE_ADDRESSES={{ aw_containment_blocked_remote_addresses | default('') }}

        - name: Установить runtime control для optional DLP контура
          ansible.builtin.copy:
            src: "{{ aw_repo_root }}/scripts/detmir_dlp_runtime_control.sh"
            dest: /usr/local/bin/detmir-dlp-runtime-control
            owner: root
            group: root
            mode: "0755"

        - name: Установить load guard для lightweight DLP контура
          ansible.builtin.copy:
            src: "{{ aw_repo_root }}/scripts/detmir_dlp_load_guard.sh"
            dest: /usr/local/bin/detmir-dlp-load-guard
            owner: root
            group: root
            mode: "0755"

        - name: Установить systemd unit DLP load guard
          ansible.builtin.copy:
            dest: /etc/systemd/system/detmir-dlp-load-guard.service
            owner: root
            group: root
            mode: "0644"
            content: |
              [Unit]
              Description=DetMir lightweight DLP load guard
              After=activitywatch-server.service

              [Service]
              Type=oneshot
              EnvironmentFile=-/etc/activitywatch/aw-server.env
              ExecStart=/usr/local/bin/detmir-dlp-load-guard
              Nice=10
              IOSchedulingClass=best-effort
              IOSchedulingPriority=7
              TimeoutStartSec=45

        - name: Установить systemd timer DLP load guard
          ansible.builtin.copy:
            dest: /etc/systemd/system/detmir-dlp-load-guard.timer
            owner: root
            group: root
            mode: "0644"
            content: |
              [Unit]
              Description=Run DetMir lightweight DLP load guard

              [Timer]
              OnBootSec=3min
              OnUnitActiveSec=1min
              AccuracySec=30s
              Persistent=false

              [Install]
              WantedBy=timers.target

        - name: Включить DLP load guard timer
          ansible.builtin.systemd:
            name: detmir-dlp-load-guard.timer
            enabled: true
            state: started
            daemon_reload: true
          when: aw_dlp_light_guard_enabled | default(true) | bool

        - name: Отключить DLP load guard timer, если guard явно выключен
          ansible.builtin.systemd:
            name: detmir-dlp-load-guard.timer
            enabled: false
            state: stopped
            daemon_reload: true
          failed_when: false
          when: not (aw_dlp_light_guard_enabled | default(true) | bool)

        - name: Создать каталог containment policy
          ansible.builtin.file:
            path: "{{ (aw_containment_policy_path | default('/etc/activitywatch/containment-policy.json')) | dirname }}"
            state: directory
            owner: root
            group: root
            mode: "0755"

        - name: Установить default containment policy, если live policy отсутствует
          ansible.builtin.copy:
            src: "{{ aw_repo_root }}/configs/containment-policy.example.json"
            dest: "{{ aw_containment_policy_path | default('/etc/activitywatch/containment-policy.json') }}"
            owner: root
            group: root
            mode: "0644"
            force: false

        - 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_enabled | default(false) | bool
            - 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_enabled | default(false) | bool
            - 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(false) | 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(false) | 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(false) | 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(false) | 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(false) | 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(false) | 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(false) | 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_enabled | default(false) | bool
            - aw_dlp_integrations_enabled | default(false) | 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_enabled | default(false) | bool
            - aw_dlp_integrations_enabled | default(false) | 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_enabled | default(false) | bool
            - aw_dlp_integrations_enabled | default(false) | 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_enabled | default(false) | bool
            - aw_dlp_integrations_enabled | default(false) | 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_enabled | default(false) | bool
            - aw_dlp_integrations_enabled | default(false) | 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_enabled | default(false) | bool
            - aw_dlp_integrations_enabled | default(false) | 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(false) | 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(false) | 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(false) | 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(false) | 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(false) | 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(false) | 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(false) | 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(false) | 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(false) | 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(false) | 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(false) | 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(false) | 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_enabled | default(false) | bool
            - aw_dlp_case_management_enabled | default(false) | 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_enabled | default(false) | bool
            - aw_dlp_case_management_enabled | default(false) | 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(false) | 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(false) | 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(false) | 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_enabled | default(false) | bool
            - aw_dlp_compliance_enabled | default(false) | 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_enabled | default(false) | bool
            - aw_dlp_compliance_enabled | default(false) | 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_enabled | default(false) | bool
            - aw_dlp_compliance_enabled | default(false) | 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_enabled | default(false) | bool
            - aw_dlp_compliance_enabled | default(false) | 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(false) | 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: Создать каталог daily history для AW worktime management
          ansible.builtin.file:
            path: "{{ aw_worktime_management_history_dir | default(aw_server_data_dir ~ '/worktime-management-history') }}"
            state: directory
            owner: "{{ aw_server_user }}"
            group: "{{ aw_server_group }}"
            mode: "0750"

        - name: Установить initial policy интерпретации Workforce если отсутствует
          ansible.builtin.copy:
            dest: "{{ aw_worktime_interpretation_policy_path | default('/etc/activitywatch/worktime-interpretation-policy.json') }}"
            owner: root
            group: root
            mode: "0644"
            force: false
            content: "{{ lookup('file', aw_repo_root + '/configs/worktime-interpretation-policy.example.json') }}"

        - 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 containment-engine
          ansible.builtin.stat:
            path: "{{ aw_rust_release_dir }}/containment-engine"
          delegate_to: localhost
          register: containment_engine_rust_binary
          become: false

        - name: Установить Rust containment-engine
          ansible.builtin.copy:
            src: "{{ aw_rust_release_dir }}/containment-engine"
            dest: /usr/local/bin/containment-engine
            owner: root
            group: root
            mode: "0755"
          when: containment_engine_rust_binary.stat.exists | default(false)

        - name: Проверить локальный Rust security-finding-inbox
          ansible.builtin.stat:
            path: "{{ aw_rust_release_dir }}/security-finding-inbox"
          delegate_to: localhost
          register: security_finding_inbox_rust_binary
          become: false

        - name: Установить Rust security-finding-inbox
          ansible.builtin.copy:
            src: "{{ aw_rust_release_dir }}/security-finding-inbox"
            dest: /usr/local/bin/security-finding-inbox
            owner: root
            group: root
            mode: "0755"
          when: security_finding_inbox_rust_binary.stat.exists | default(false)

        - name: Установить systemd unit Security Finding Inbox executor
          ansible.builtin.copy:
            src: "{{ aw_repo_root }}/ops/systemd/aw-security-finding-executor.service"
            dest: /etc/systemd/system/aw-security-finding-executor.service
            owner: root
            group: root
            mode: "0644"
          notify: Перезагрузить systemd

        - 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_enabled | default(false) | bool
            - 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_enabled | default(false) | bool
            - 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_enabled | default(false) | bool
            - 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_enabled | default(false) | bool
            - aw_dlp_influx_enabled | default(false) | bool

        - name: Проверить локальный Rust DetMir readiness checker
          ansible.builtin.stat:
            path: "{{ aw_rust_release_dir }}/detmir-readiness"
          delegate_to: localhost
          register: detmir_readiness_rust_binary
          become: false

        - name: Требовать Rust DetMir readiness checker artifact
          ansible.builtin.assert:
            that:
              - detmir_readiness_rust_binary.stat.exists | default(false)
            fail_msg: "Missing Rust artifact: {{ aw_rust_release_dir }}/detmir-readiness"

        - name: Установить Rust DetMir readiness checker
          ansible.builtin.copy:
            src: "{{ aw_rust_release_dir }}/detmir-readiness"
            dest: /usr/local/bin/detmir-readiness
            owner: root
            group: root
            mode: "0755"
          when: detmir_readiness_rust_binary.stat.exists | default(false)

        - name: Создать private каталог DetMir readiness signing
          ansible.builtin.file:
            path: /etc/detmir-readiness
            state: directory
            owner: root
            group: root
            mode: "0700"

        - name: Проверить private key DetMir readiness signing
          ansible.builtin.stat:
            path: /etc/detmir-readiness/signing-key.pem
          register: detmir_readiness_signing_key

        - name: Сгенерировать private key DetMir readiness signing
          ansible.builtin.command:
            argv:
              - openssl
              - genpkey
              - -algorithm
              - RSA
              - -pkeyopt
              - rsa_keygen_bits:3072
              - -out
              - /etc/detmir-readiness/signing-key.pem
          when: not (detmir_readiness_signing_key.stat.exists | default(false))
          no_log: true

        - name: Зафиксировать права private key DetMir readiness signing
          ansible.builtin.file:
            path: /etc/detmir-readiness/signing-key.pem
            owner: root
            group: root
            mode: "0600"

        - name: Создать private env DetMir readiness
          ansible.builtin.copy:
            dest: /etc/detmir-readiness.env
            content: ""
            owner: root
            group: root
            mode: "0600"
            force: false

        - name: Настроить private env DetMir readiness signing
          ansible.builtin.blockinfile:
            path: /etc/detmir-readiness.env
            marker: "# {mark} ANSIBLE MANAGED DETMIR READINESS"
            block: |
              DETMIR_READINESS_SIGNING_KEY=/etc/detmir-readiness/signing-key.pem
              DETMIR_READINESS_REQUIRE_SIGNATURE=true
              DETMIR_READINESS_RETENTION_DAYS={{ detmir_readiness_retention_days | default(30) }}
            owner: root
            group: root
            mode: "0600"

        - name: Установить systemd service DetMir readiness bundle
          ansible.builtin.copy:
            src: "{{ aw_repo_root }}/aw-server/detmir-readiness.service"
            dest: /etc/systemd/system/detmir-readiness.service
            owner: root
            group: root
            mode: "0644"

        - name: Установить systemd timer DetMir readiness bundle
          ansible.builtin.copy:
            src: "{{ aw_repo_root }}/aw-server/detmir-readiness.timer"
            dest: /etc/systemd/system/detmir-readiness.timer
            owner: root
            group: root
            mode: "0644"

        - name: Создать каталог Prometheus rules для DetMir readiness
          ansible.builtin.file:
            path: /etc/prometheus/rules
            state: directory
            owner: root
            group: root
            mode: "0755"

        - name: Установить Prometheus alerts DetMir readiness
          ansible.builtin.copy:
            src: "{{ aw_repo_root }}/aw-server/detmir-readiness-alerts.yml"
            dest: /etc/prometheus/rules/detmir-readiness-alerts.yml
            owner: root
            group: root
            mode: "0644"

        - 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: Включить daily DetMir readiness bundle timer
          ansible.builtin.systemd:
            name: detmir-readiness.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(false) | bool

        - name: Включить и перезапустить timer syslog forwarder
          ansible.builtin.systemd:
            name: aw-dlp-syslog-forwarder.timer
            enabled: true
            state: restarted
          when: aw_dlp_integrations_enabled | default(false) | bool

        - name: Включить и перезапустить timer webhook sender
          ansible.builtin.systemd:
            name: aw-dlp-webhook-sender.timer
            enabled: true
            state: restarted
          when: aw_dlp_integrations_enabled | default(false) | 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(false) | 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(false) | 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(false) | 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: '<script[^>]+(?:ru-patch-v5\.js|sw-cleanup\.js|aw-host-sanitize\.js|aw-ru-patch\.js|aw-sw-cleanup\.js)[^>]*></script>'
            replace: ''

        - name: Добавить cleanup script RU patch в index.html
          ansible.builtin.replace:
            path: "{{ aw_server_webui_dir }}/index.html"
            regexp: '</head>'
            replace: '<script src="/js/sw-cleanup.js?v={{ aw_sw_cleanup_cache_bust }}"></script></head>'

        - name: Добавить ранний guard hostname перед WebUI bundle
          ansible.builtin.replace:
            path: "{{ aw_server_webui_dir }}/index.html"
            regexp: '(<script defer="defer" src="/js/chunk-vendors[^"]+"></script>)'
            replace: '<script src="/js/aw-host-sanitize.js?v={{ aw_host_sanitize_cache_bust }}"></script>\1'

        - name: Добавить загрузчик RU patch перед закрытием body
          ansible.builtin.replace:
            path: "{{ aw_server_webui_dir }}/index.html"
            regexp: '</body>'
            replace: '<script defer="defer" src="/js/ru-patch-v5.js?v={{ aw_ru_patch_cache_bust }}"></script></body>'

        - 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: /var/lib/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: /var/lib/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
              - /var/lib/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: /var/lib/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: Удалить stale drop-in, переопределяющий lightweight DLP aggregator
          ansible.builtin.file:
            path: /etc/systemd/system/activitywatch-dlp-aggregator.service.d/20-rust-switch.conf
            state: absent

        - name: Установить systemd unit для агрегатора
          ansible.builtin.copy:
            dest: /etc/systemd/system/activitywatch-dlp-aggregator.service
            owner: root
            group: root
            mode: "0644"
            content: |
              [Unit]
              Description=ActivityWatch Lightweight 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 \
                --bucket-prefixes {{ aw_dlp_aggregator_bucket_prefixes | default('aw-file-operations_,aw-dlp-incidents_') }} \
                --lookback-hours {{ aw_dlp_aggregator_lookback_hours | default(2) }} \
                --overlap-seconds {{ aw_dlp_aggregator_overlap_seconds | default(60) }} \
                --limit {{ aw_dlp_aggregator_limit | default(500) }} \
                --timeout {{ aw_dlp_aggregator_timeout_seconds | default(8) }}
              Nice=10
              IOSchedulingClass=best-effort
              IOSchedulingPriority=7
              CPUQuota={{ aw_dlp_aggregator_cpu_quota | default('10%') }}
              MemoryMax={{ aw_dlp_aggregator_memory_max | default('256M') }}
              TimeoutStartSec={{ (aw_dlp_aggregator_timeout_seconds | default(8) | int) + 15 }}

              [Install]
              WantedBy=multi-user.target

        - name: Установить systemd timer для агрегатора
          ansible.builtin.copy:
            dest: /etc/systemd/system/activitywatch-dlp-aggregator.timer
            content: |
              [Unit]
              Description=Run ActivityWatch Lightweight DLP Aggregator

              [Timer]
              OnCalendar={{ aw_dlp_aggregator_on_calendar | default('*:3/15: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
      when:
        - aw_dlp_enabled | default(false) | bool
        - aw_dlp_light_collector_enabled | default(false) | bool

    - name: Настроить IOC enrichment из Hayabusa Sigma
      when:
        - aw_dlp_enabled | default(false) | bool
        - 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: Проверить наличие upload user awops для Hayabusa drop-zone
          ansible.builtin.command:
            cmd: id -u awops
          register: aw_hayabusa_drop_upload_user
          changed_when: false
          failed_when: false

        - name: Разрешить awops писать в Hayabusa drop-zone
          ansible.builtin.file:
            path: /opt/activitywatch/aw-rus-ops/drop
            state: directory
            owner: awops
            group: awops
            mode: "0750"
          when: aw_hayabusa_drop_upload_user.rc == 0

        - 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
