diff --git a/install-kit-awindows-20260427-211240/ansible/README.md b/install-kit-awindows-20260427-211240/ansible/README.md
index b22fc6c..3c9d42a 100644
--- a/install-kit-awindows-20260427-211240/ansible/README.md
+++ b/install-kit-awindows-20260427-211240/ansible/README.md
@@ -31,6 +31,15 @@ cd ansible
ansible-playbook -i inventory.ini deploy_aw_server.yml
```
+## Секреты (пароли) безопасно
+
+Рекомендуемый способ не хранить пароли в репозитории — перед запуском экспортировать их в переменные окружения:
+
+- Linux `aw_server` (SSH пароль root): `AW_SSH_PASSWORD`
+- Windows `aw_windows` (WinRM пароль): `AW_WINRM_PASSWORD`
+
+В `group_vars/aw_server.yml` и `group_vars/windows.yml` они читаются через `lookup('env', ...)`.
+
## Полный установочный playbook (всё за один запуск)
Если нужно прогнать полный цикл одной командой:
@@ -149,3 +158,13 @@ Playbook:
- Для полного сценария CT создаётся автоматически через `pct create`.
- На Windows/RDP host развёрнуты AFK/window watchers, browser domain collector, DLP endpoint collector и worktime session collector.
- Проверочный JSON-отчёт Windows playbook должен иметь `overallOk=true`.
+
+## Prod rollout одной командой
+
+Для ручного запуска с dry-run и логированием используйте:
+
+```bash
+bash scripts/prod_rollout.sh
+```
+
+Скрипт попросит `AW_SSH_PASSWORD` и `AW_WINRM_PASSWORD` интерактивно (ввод скрыт) и сложит логи в `.rollout-logs/`.
diff --git a/install-kit-awindows-20260427-211240/ansible/deploy_aw_server.yml b/install-kit-awindows-20260427-211240/ansible/deploy_aw_server.yml
index b136df7..996eb4b 100644
--- a/install-kit-awindows-20260427-211240/ansible/deploy_aw_server.yml
+++ b/install-kit-awindows-20260427-211240/ansible/deploy_aw_server.yml
@@ -54,6 +54,11 @@
- "{{ 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_log_dir }}"
- /etc/activitywatch
- "{{ aw_bootstrap_dir }}"
@@ -74,100 +79,147 @@
- "{{ 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_log_dir }}"
- - name: Скачать архив релиза ActivityWatch
- ansible.builtin.get_url:
- url: "{{ aw_server_download_url }}"
- dest: "{{ aw_archive_path }}"
- mode: "0644"
+ - name: (Check mode) Пропустить установку релиза ActivityWatch
+ ansible.builtin.debug:
+ msg: "ansible_check_mode=true: download/unarchive/install of ActivityWatch release is skipped."
+ when: ansible_check_mode
- - name: Распаковать релиз ActivityWatch
- ansible.builtin.unarchive:
- src: "{{ aw_archive_path }}"
- dest: "{{ aw_release_dir }}"
- remote_src: true
- extra_opts: ["-o"]
+ - name: Установить релиз ActivityWatch (download/unarchive/install)
+ when: not ansible_check_mode
+ block:
+ - name: Скачать архив релиза ActivityWatch
+ ansible.builtin.get_url:
+ url: "{{ aw_server_download_url }}"
+ dest: "{{ aw_archive_path }}"
+ mode: "0644"
- - name: Найти распакованный каталог ActivityWatch
- ansible.builtin.find:
- paths: "{{ aw_release_dir }}"
- file_type: directory
- patterns: "activitywatch*"
- register: aw_release_find
+ - name: Распаковать релиз ActivityWatch
+ ansible.builtin.unarchive:
+ src: "{{ aw_archive_path }}"
+ dest: "{{ aw_release_dir }}"
+ remote_src: true
+ extra_opts: ["-o"]
- - name: Найти бинарный файл AW server
- ansible.builtin.find:
- paths: "{{ aw_release_dir }}"
- file_type: file
- patterns:
- - aw-server-rust
- - aw-server
- register: aw_server_binary_find
+ - name: Найти распакованный каталог ActivityWatch
+ ansible.builtin.find:
+ paths: "{{ aw_release_dir }}"
+ recurse: true
+ file_type: directory
+ patterns: "activitywatch*"
+ register: aw_release_find
- - name: Найти каталог WebUI
- ansible.builtin.find:
- paths: "{{ aw_release_dir }}"
- file_type: directory
- patterns:
- - aw-webui
- - webui
- register: aw_webui_dir_find
+ - name: Найти бинарный файл AW server
+ ansible.builtin.find:
+ paths: "{{ aw_release_dir }}"
+ recurse: true
+ file_type: file
+ patterns:
+ - aw-server-rust
+ - aw-server
+ register: aw_server_binary_find
- - name: Сохранить пути распакованного релиза
- ansible.builtin.set_fact:
- aw_release_extracted: "{{ (aw_release_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list | first) | default('') }}"
- aw_server_binary_path: "{{ (aw_server_binary_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list | first) | default('') }}"
- aw_webui_source_path: "{{ (aw_webui_dir_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list | first) | default('') }}"
+ - name: Найти index.html WebUI
+ ansible.builtin.find:
+ paths: "{{ aw_release_dir }}"
+ recurse: true
+ file_type: file
+ patterns:
+ - index.html
+ register: aw_webui_index_find
- - name: Проверить, что компоненты релиза найдены
- ansible.builtin.assert:
- that:
- - aw_release_extracted is defined
- - aw_release_extracted | length > 0
- - aw_server_binary_path is defined
- - aw_server_binary_path | length > 0
- - aw_webui_source_path is defined
- - aw_webui_source_path | length > 0
- fail_msg: "Не удалось найти бинарный файл или WebUI в распакованном релизе ActivityWatch."
+ - name: Сохранить пути распакованного релиза (binary + webui index)
+ ansible.builtin.set_fact:
+ aw_release_extracted: "{{ (aw_release_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list | first) | default('') }}"
+ aw_server_binary_path: >-
+ {{
+ (
+ (
+ (aw_server_binary_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list)
+ | select('match', '.*/aw-server-rust$') | list | first
+ )
+ | default(
+ (
+ (aw_server_binary_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list | first)
+ ),
+ true
+ )
+ ) | default('')
+ }}
+ aw_webui_index_path: >-
+ {{
+ (
+ (
+ (aw_webui_index_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list)
+ | select('search', '/static/index\\.html$') | list | first
+ )
+ | default(
+ (
+ (aw_webui_index_find.files | default([]) | sort(attribute='path') | map(attribute='path') | list | first)
+ ),
+ true
+ )
+ ) | default('')
+ }}
- - name: Создать каталог установленного релиза
- ansible.builtin.file:
- path: "{{ aw_release_install_dir }}"
- state: directory
- owner: "{{ aw_server_user }}"
- group: "{{ aw_server_group }}"
- mode: "0755"
+ - name: Сохранить каталог WebUI (dirname index.html)
+ ansible.builtin.set_fact:
+ aw_webui_source_path: "{{ aw_webui_index_path | dirname }}"
- - name: Установить бинарный файл AW server
- ansible.builtin.copy:
- remote_src: true
- src: "{{ aw_server_binary_path }}"
- dest: "{{ aw_release_install_dir }}/aw-server-rust"
- owner: "{{ aw_server_user }}"
- group: "{{ aw_server_group }}"
- mode: "0755"
+ - name: Проверить, что компоненты релиза найдены
+ ansible.builtin.assert:
+ that:
+ - aw_release_extracted is defined
+ - aw_release_extracted | length > 0
+ - aw_server_binary_path is defined
+ - aw_server_binary_path | length > 0
+ - aw_webui_source_path is defined
+ - aw_webui_source_path | length > 0
+ fail_msg: "Не удалось найти бинарный файл или WebUI в распакованном релизе ActivityWatch."
- - name: Создать ссылку на активный бинарный файл AW server
- ansible.builtin.file:
- src: "{{ aw_release_install_dir }}/aw-server-rust"
- dest: /opt/activitywatch/bin/aw-server-rust
- owner: "{{ aw_server_user }}"
- group: "{{ aw_server_group }}"
- state: link
- force: true
+ - name: Создать каталог установленного релиза
+ ansible.builtin.file:
+ path: "{{ aw_release_install_dir }}"
+ state: directory
+ owner: "{{ aw_server_user }}"
+ group: "{{ aw_server_group }}"
+ mode: "0755"
- - name: Синхронизировать WebUI в RU каталог
- ansible.builtin.command:
- cmd: "rsync -a {{ aw_webui_source_path }}/ {{ aw_server_webui_dir }}/"
+ - name: Установить бинарный файл AW server
+ ansible.builtin.copy:
+ remote_src: true
+ src: "{{ aw_server_binary_path }}"
+ dest: "{{ aw_release_install_dir }}/aw-server-rust"
+ owner: "{{ aw_server_user }}"
+ group: "{{ aw_server_group }}"
+ mode: "0755"
- - name: Настроить владельца файлов /opt/activitywatch
- ansible.builtin.file:
- path: /opt/activitywatch
- state: directory
- owner: "{{ aw_server_user }}"
- group: "{{ aw_server_group }}"
- recurse: true
+ - name: Создать ссылку на активный бинарный файл AW server
+ ansible.builtin.file:
+ src: "{{ aw_release_install_dir }}/aw-server-rust"
+ dest: /opt/activitywatch/bin/aw-server-rust
+ owner: "{{ aw_server_user }}"
+ group: "{{ aw_server_group }}"
+ state: link
+ force: true
+
+ - name: Синхронизировать WebUI в RU каталог
+ ansible.builtin.command:
+ cmd: "rsync -a {{ aw_webui_source_path }}/ {{ aw_server_webui_dir }}/"
+
+ - name: Настроить владельца файлов /opt/activitywatch
+ ansible.builtin.file:
+ path: /opt/activitywatch
+ state: directory
+ owner: "{{ aw_server_user }}"
+ group: "{{ aw_server_group }}"
+ recurse: true
- name: Установить systemd service из шаблона репозитория
ansible.builtin.copy:
@@ -184,143 +236,401 @@
- Перезагрузить systemd
- Перезапустить activitywatch
- - name: Скопировать RU patch файлы WebUI из репозитория
- ansible.builtin.copy:
- src: "{{ item.src }}"
- dest: "{{ item.dest }}"
- mode: "{{ item.mode }}"
- owner: "{{ aw_server_user }}"
- group: "{{ aw_server_group }}"
- loop:
- - { src: "{{ aw_repo_root }}/aw-server/aw-ru-patch.js", dest: "{{ aw_server_webui_dir }}/js/ru-patch-v5.js", mode: "0644" }
- - { src: "{{ aw_repo_root }}/aw-server/aw-sw-cleanup.js", dest: "{{ aw_server_webui_dir }}/js/sw-cleanup.js", mode: "0644" }
- - { src: "{{ aw_repo_root }}/aw-server/aw-worktime-panel.js", dest: "{{ aw_server_webui_dir }}/js/aw-worktime-panel.js", mode: "0644" }
- - { src: "{{ aw_repo_root }}/aw-server/aw-host-groups.json", dest: "{{ aw_server_webui_dir }}/js/aw-host-groups.json", mode: "0644" }
-
- - name: Создать каталог /root/bootstrap для apply_webui_ru_patch.sh
- ansible.builtin.file:
- path: /root/bootstrap
- state: directory
- mode: "0755"
-
- - name: Скопировать RU patch файлы для apply_webui_ru_patch.sh (хотфиксы compiled JS чанков)
- ansible.builtin.copy:
- src: "{{ item.src }}"
- dest: "{{ item.dest }}"
- mode: "{{ item.mode }}"
- loop:
- - { src: "{{ aw_repo_root }}/aw-server/aw-ru-patch.js", dest: "/root/bootstrap/aw-ru-patch.js", mode: "0644" }
- - { src: "{{ aw_repo_root }}/aw-server/aw-sw-cleanup.js", dest: "/root/bootstrap/aw-sw-cleanup.js", mode: "0644" }
- - { src: "{{ aw_repo_root }}/aw-server/aw-worktime-panel.js", dest: "/root/bootstrap/aw-worktime-panel.js", mode: "0644" }
- - { src: "{{ aw_repo_root }}/aw-server/aw-host-groups.json", dest: "/root/bootstrap/aw-host-groups.json", mode: "0644" }
-
- - name: Скопировать apply_webui_ru_patch.sh скрипт
- ansible.builtin.copy:
- src: "{{ aw_repo_root }}/aw-server/apply_webui_ru_patch.sh"
- dest: /opt/activitywatch/aw-server/apply_webui_ru_patch.sh
- mode: "0755"
-
- - name: Записать /etc/activitywatch/aw-server.env перед хотфиксами
- ansible.builtin.copy:
- dest: /etc/activitywatch/aw-server.env
- mode: "0640"
- owner: root
- group: root
- content: |
- AW_SERVER_BIND_HOST={{ aw_server_bind_host }}
- AW_SERVER_PORT={{ aw_server_port }}
- AW_SERVER_DATA_DIR={{ aw_server_data_dir }}
- AW_SERVER_DB_PATH={{ aw_server_db_path }}
- AW_SERVER_LOG_DIR={{ aw_server_log_dir }}
- AW_SERVER_WEBUI_DIR={{ aw_server_webui_dir }}
- AW_SERVER_USER={{ aw_server_user }}
- AW_SERVER_GROUP={{ aw_server_group }}
- AW_WORKTIME_REPORT_BASE={{ aw_worktime_report_base }}
- AW_WORKTIME_TZ={{ aw_worktime_timezone }}
- XDG_DATA_HOME={{ aw_server_data_dir }}/.local/share
- XDG_CONFIG_HOME={{ aw_server_data_dir }}/.config
-
- - name: Установить скрипт AW worktime API
- ansible.builtin.copy:
- src: "{{ aw_repo_root }}/aw-server/aw-worktime-api.py"
- dest: /usr/local/bin/aw-worktime-api.py
- owner: root
- group: root
- mode: "0755"
-
- - name: Установить systemd unit AW worktime API
- ansible.builtin.copy:
- src: "{{ aw_repo_root }}/aw-server/aw-worktime-api.service"
- dest: /etc/systemd/system/aw-worktime-api.service
- owner: root
- group: root
- mode: "0644"
-
- - name: Перезагрузить systemd после установки AW worktime API
- ansible.builtin.systemd:
- daemon_reload: true
-
- - name: Включить и перезапустить AW worktime API
- ansible.builtin.systemd:
- name: aw-worktime-api.service
- enabled: true
- state: restarted
-
- - name: Применить хотфиксы compiled JS чанков (Trends, Timespiral, Category helper)
- ansible.builtin.command:
- cmd: "/opt/activitywatch/aw-server/apply_webui_ru_patch.sh"
- register: apply_ru_patch_result
- failed_when: false
-
- - name: Вывести результат применения хотфиксов
+ - name: (Check mode) Пропустить WebUI patch и запуск сервиса
ansible.builtin.debug:
- msg: "apply_webui_ru_patch.sh: {{ apply_ru_patch_result.stdout }}"
+ msg: "ansible_check_mode=true: WebUI patch + service start + API checks are skipped."
+ when: ansible_check_mode
- - name: Проверить наличие index.html после копирования
- ansible.builtin.stat:
- path: "{{ aw_server_webui_dir }}/index.html"
- register: aw_webui_ru_index
+ - name: Применить WebUI RU patch и запустить сервис
+ when: not ansible_check_mode
+ block:
+ - name: Скопировать RU patch файлы WebUI из репозитория
+ ansible.builtin.copy:
+ src: "{{ item.src }}"
+ dest: "{{ item.dest }}"
+ mode: "{{ item.mode }}"
+ owner: "{{ aw_server_user }}"
+ group: "{{ aw_server_group }}"
+ loop:
+ - { src: "{{ aw_repo_root }}/aw-server/aw-ru-patch.js", dest: "{{ aw_server_webui_dir }}/js/ru-patch-v5.js", mode: "0644" }
+ - { src: "{{ aw_repo_root }}/aw-server/aw-sw-cleanup.js", dest: "{{ aw_server_webui_dir }}/js/sw-cleanup.js", mode: "0644" }
+ - { src: "{{ aw_repo_root }}/aw-server/aw-worktime-panel.js", dest: "{{ aw_server_webui_dir }}/js/aw-worktime-panel.js", mode: "0644" }
+ - { src: "{{ aw_repo_root }}/aw-server/aw-host-groups.json", dest: "{{ aw_server_webui_dir }}/js/aw-host-groups.json", mode: "0644" }
- - name: Проверить, что index.html доступен для RU patch
- ansible.builtin.assert:
- that:
- - aw_webui_ru_index.stat.exists
- fail_msg: "Не найден index.html WebUI для применения RU patch."
+ - name: Создать каталог /root/bootstrap для apply_webui_ru_patch.sh
+ ansible.builtin.file:
+ path: /root/bootstrap
+ state: directory
+ mode: "0755"
- - name: Удалить старые теги RU patch из index.html
- ansible.builtin.replace:
- path: "{{ aw_server_webui_dir }}/index.html"
- regexp: ''
- replace: ''
+ - name: Скопировать RU patch файлы для apply_webui_ru_patch.sh (хотфиксы compiled JS чанков)
+ ansible.builtin.copy:
+ src: "{{ item.src }}"
+ dest: "{{ item.dest }}"
+ mode: "{{ item.mode }}"
+ loop:
+ - { src: "{{ aw_repo_root }}/aw-server/aw-ru-patch.js", dest: "/root/bootstrap/aw-ru-patch.js", mode: "0644" }
+ - { src: "{{ aw_repo_root }}/aw-server/aw-sw-cleanup.js", dest: "/root/bootstrap/aw-sw-cleanup.js", mode: "0644" }
+ - { src: "{{ aw_repo_root }}/aw-server/aw-worktime-panel.js", dest: "/root/bootstrap/aw-worktime-panel.js", mode: "0644" }
+ - { src: "{{ aw_repo_root }}/aw-server/aw-host-groups.json", dest: "/root/bootstrap/aw-host-groups.json", mode: "0644" }
- - name: Добавить cleanup script RU patch в index.html
- ansible.builtin.replace:
- path: "{{ aw_server_webui_dir }}/index.html"
- regexp: ''
- replace: ''
+ - name: Скопировать apply_webui_ru_patch.sh скрипт
+ ansible.builtin.copy:
+ src: "{{ aw_repo_root }}/aw-server/apply_webui_ru_patch.sh"
+ dest: /opt/activitywatch/aw-server/apply_webui_ru_patch.sh
+ mode: "0755"
- - name: Добавить загрузчик RU patch перед закрытием body
- ansible.builtin.replace:
- path: "{{ aw_server_webui_dir }}/index.html"
- regexp: '