From 016a323afc312a2ff6f82f3ae1c519544b317b8c Mon Sep 17 00:00:00 2001 From: igor04091968 Date: Sat, 25 Apr 2026 15:57:30 +0300 Subject: [PATCH] feat(ansible): add hardened matrix rollout for Proxmox CTs with RU patch --- README.md | 1 + ansible/README.md | 14 ++ ansible/group_vars/proxmox-matrix.example.yml | 38 +++ .../provision_proxmox_ct_and_deploy_aw.yml | 236 ++---------------- ...vision_proxmox_ct_matrix_and_deploy_aw.yml | 47 ++++ ansible/tasks/provision_ct_and_deploy_aw.yml | 217 ++++++++++++++++ docs/FULL_DEPLOYMENT_MANUAL_RU.md | 8 + docs/deployment.md | 7 + 8 files changed, 354 insertions(+), 214 deletions(-) create mode 100644 ansible/group_vars/proxmox-matrix.example.yml create mode 100644 ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml create mode 100644 ansible/tasks/provision_ct_and_deploy_aw.yml diff --git a/README.md b/README.md index 5345de4..02b6248 100755 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ Для полного Ansible-сценария “с нуля” в Proxmox используйте: - `ansible/provision_proxmox_ct_and_deploy_aw.yml` +- `ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml` (массово по матрице CT) Скрипты `proxmox/create-ct.sh` и `proxmox/push-aw-artifacts.sh` по умолчанию читают: diff --git a/ansible/README.md b/ansible/README.md index 247e5da..393cd01 100644 --- a/ansible/README.md +++ b/ansible/README.md @@ -9,9 +9,11 @@ - `/home/igor/tmp/AWatch-rus/ansible/deploy_aw_server.yml` — основной playbook. - `/home/igor/tmp/AWatch-rus/ansible/provision_proxmox_ct_and_deploy_aw.yml` — full-stack playbook для Proxmox. +- `/home/igor/tmp/AWatch-rus/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml` — массовый full-stack playbook (несколько CT). - `/home/igor/tmp/AWatch-rus/ansible/inventory.example.ini` — шаблон inventory. - `/home/igor/tmp/AWatch-rus/ansible/group_vars/all.example.yml` — шаблон переменных. - `/home/igor/tmp/AWatch-rus/ansible/group_vars/proxmox.example.yml` — шаблон переменных CT в Proxmox. +- `/home/igor/tmp/AWatch-rus/ansible/group_vars/proxmox-matrix.example.yml` — шаблон матрицы CT. ## Быстрый запуск @@ -40,6 +42,18 @@ cd /home/igor/tmp/AWatch-rus/ansible ansible-playbook -i inventory.ini provision_proxmox_ct_and_deploy_aw.yml ``` +## Массовый запуск (матрица CT) + +1. Подготовьте матрицу: + - `cp /home/igor/tmp/AWatch-rus/ansible/group_vars/proxmox-matrix.example.yml /home/igor/tmp/AWatch-rus/ansible/group_vars/proxmox-matrix.yml` +2. Заполните `proxmox-matrix.yml`. +3. Запустите: + +```bash +cd /home/igor/tmp/AWatch-rus/ansible +ansible-playbook -i inventory.ini provision_proxmox_ct_matrix_and_deploy_aw.yml +``` + ## Результат - Установлен ActivityWatch Server. diff --git a/ansible/group_vars/proxmox-matrix.example.yml b/ansible/group_vars/proxmox-matrix.example.yml new file mode 100644 index 0000000..ffe4b88 --- /dev/null +++ b/ansible/group_vars/proxmox-matrix.example.yml @@ -0,0 +1,38 @@ +proxmox_ct_matrix: + - id: "203" + hostname: "activitywatch-user1" + storage: "local-lvm" + template: "local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst" + rootfs_size: "8G" + cores: "2" + memory: "2048" + swap: "512" + bridge: "vmbr10" + ip: "10.20.30.13/24" + gw: "10.20.30.1" + vlan: "" + nameserver: "1.1.1.1 8.8.8.8" + searchdomain: "example.internal" + password: "CHANGE_ME" + unprivileged: "1" + onboot: "1" + features: "nesting=1,keyctl=1" + + - id: "204" + hostname: "activitywatch-user2" + storage: "local-lvm" + template: "local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst" + rootfs_size: "8G" + cores: "2" + memory: "2048" + swap: "512" + bridge: "vmbr10" + ip: "10.20.30.14/24" + gw: "10.20.30.1" + vlan: "" + nameserver: "1.1.1.1 8.8.8.8" + searchdomain: "example.internal" + password: "CHANGE_ME" + unprivileged: "1" + onboot: "1" + features: "nesting=1,keyctl=1" diff --git a/ansible/provision_proxmox_ct_and_deploy_aw.yml b/ansible/provision_proxmox_ct_and_deploy_aw.yml index 8b96c0e..0c08284 100644 --- a/ansible/provision_proxmox_ct_and_deploy_aw.yml +++ b/ansible/provision_proxmox_ct_and_deploy_aw.yml @@ -1,12 +1,10 @@ --- -- name: Provision Proxmox CT and deploy AWatch-rus +- name: Provision single Proxmox CT and deploy AWatch-rus hosts: proxmox gather_facts: false vars: proxmox_bootstrap_dir: "/tmp/aw-rus-bootstrap" - proxmox_ct_net0: >- - name=eth0,bridge={{ proxmox_ct_bridge }},ip={{ proxmox_ct_ip }},gw={{ proxmox_ct_gw }}{% if proxmox_ct_vlan | default('') | length > 0 %},tag={{ proxmox_ct_vlan }}{% endif %} aw_bootstrap_files: - install_aw_server.sh - apply_webui_ru_patch.sh @@ -16,214 +14,24 @@ - aw-sw-cleanup.js tasks: - - name: Validate required Proxmox variables - ansible.builtin.assert: - that: - - proxmox_ct_id is defined - - proxmox_ct_hostname is defined - - proxmox_ct_storage is defined - - proxmox_ct_template is defined - - proxmox_ct_rootfs_size is defined - - proxmox_ct_cores is defined - - proxmox_ct_memory is defined - - proxmox_ct_swap is defined - - proxmox_ct_bridge is defined - - proxmox_ct_ip is defined - - proxmox_ct_gw is defined - - proxmox_ct_password is defined - - proxmox_ct_unprivileged is defined - - proxmox_ct_onboot is defined - - proxmox_ct_features is defined - - aw_repo_root is defined - - aw_server_version is defined - - aw_server_download_url is defined - - aw_server_bind_host is defined - - aw_server_port is defined - - aw_server_webui_dir is defined - - aw_server_data_dir is defined - - aw_server_log_dir is defined - - aw_server_user is defined - - aw_server_group is defined - fail_msg: "Missing required variables in ansible/group_vars/*.yml" - - - name: Check whether CT already exists - ansible.builtin.command: - argv: - - pct - - status - - "{{ proxmox_ct_id }}" - register: ct_status_check - failed_when: false - changed_when: false - - - name: Create CT when absent - ansible.builtin.command: - argv: - - pct - - create - - "{{ proxmox_ct_id }}" - - "{{ proxmox_ct_template }}" - - --hostname - - "{{ proxmox_ct_hostname }}" - - --cores - - "{{ proxmox_ct_cores }}" - - --memory - - "{{ proxmox_ct_memory }}" - - --swap - - "{{ proxmox_ct_swap }}" - - --rootfs - - "{{ proxmox_ct_storage }}:{{ proxmox_ct_rootfs_size }}" - - --password - - "{{ proxmox_ct_password }}" - - --unprivileged - - "{{ proxmox_ct_unprivileged }}" - - --onboot - - "{{ proxmox_ct_onboot }}" - - --features - - "{{ proxmox_ct_features }}" - - --net0 - - "{{ proxmox_ct_net0 }}" - - --nameserver - - "{{ proxmox_ct_nameserver | default('') }}" - - --searchdomain - - "{{ proxmox_ct_searchdomain | default('') }}" - - --ostype - - debian - when: ct_status_check.rc != 0 - - - name: Check current CT runtime state - ansible.builtin.command: - argv: - - pct - - status - - "{{ proxmox_ct_id }}" - register: ct_runtime_status - changed_when: false - - - name: Start CT when stopped - ansible.builtin.command: - argv: - - pct - - start - - "{{ proxmox_ct_id }}" - when: "'stopped' in ct_runtime_status.stdout" - - - name: Ensure bootstrap directory on Proxmox host - ansible.builtin.file: - path: "{{ proxmox_bootstrap_dir }}" - state: directory - mode: "0700" - - - name: Copy AW bootstrap files to Proxmox host temp - ansible.builtin.copy: - src: "{{ aw_repo_root }}/aw-server/{{ item }}" - dest: "{{ proxmox_bootstrap_dir }}/{{ item }}" - mode: "0644" - loop: "{{ aw_bootstrap_files }}" - - - name: Bootstrap CT OS dependencies - ansible.builtin.command: - argv: - - pct - - exec - - "{{ proxmox_ct_id }}" - - -- - - bash - - -lc - - | - set -euo pipefail - export DEBIAN_FRONTEND=noninteractive - apt-get update - apt-get install -y curl ca-certificates bash unzip xz-utils jq rsync openssh-server - mkdir -p /root/bootstrap /etc/activitywatch - systemctl enable ssh || true - systemctl restart ssh || true - - - name: Push bootstrap files into CT - ansible.builtin.command: - argv: - - pct - - push - - "{{ proxmox_ct_id }}" - - "{{ proxmox_bootstrap_dir }}/{{ item }}" - - "/root/bootstrap/{{ item }}" - loop: "{{ aw_bootstrap_files }}" - - - name: Write AW server env file on Proxmox host temp - ansible.builtin.copy: - dest: "{{ proxmox_bootstrap_dir }}/aw-server.env" - mode: "0600" - content: | - AW_SERVER_VERSION={{ aw_server_version }} - AW_SERVER_DOWNLOAD_URL={{ aw_server_download_url }} - AW_SERVER_BIND_HOST={{ aw_server_bind_host }} - AW_SERVER_PORT={{ aw_server_port }} - AW_SERVER_WEBUI_DIR={{ aw_server_webui_dir }} - AW_SERVER_DATA_DIR={{ aw_server_data_dir }} - AW_SERVER_LOG_DIR={{ aw_server_log_dir }} - AW_SERVER_USER={{ aw_server_user }} - AW_SERVER_GROUP={{ aw_server_group }} - - - name: Push AW server env into CT - ansible.builtin.command: - argv: - - pct - - push - - "{{ proxmox_ct_id }}" - - "{{ proxmox_bootstrap_dir }}/aw-server.env" - - /etc/activitywatch/aw-server.env - - - name: Set mode for env inside CT - ansible.builtin.command: - argv: - - pct - - exec - - "{{ proxmox_ct_id }}" - - -- - - chmod - - "0600" - - /etc/activitywatch/aw-server.env - - - name: Install server and apply RU patch inside CT - ansible.builtin.command: - argv: - - pct - - exec - - "{{ proxmox_ct_id }}" - - -- - - bash - - -lc - - | - set -euo pipefail - chmod +x /root/bootstrap/install_aw_server.sh /root/bootstrap/apply_webui_ru_patch.sh - bash /root/bootstrap/install_aw_server.sh - bash /root/bootstrap/apply_webui_ru_patch.sh - systemctl restart activitywatch-server.service - - - name: Validate AW API from inside CT - ansible.builtin.command: - argv: - - pct - - exec - - "{{ proxmox_ct_id }}" - - -- - - bash - - -lc - - "curl -fsS http://127.0.0.1:{{ aw_server_port }}/api/0/info >/dev/null" - - - name: Validate RU patch hooks in index - ansible.builtin.command: - argv: - - pct - - exec - - "{{ proxmox_ct_id }}" - - -- - - bash - - -lc - - "grep -q 'ru-patch-v5.js' {{ aw_server_webui_dir }}/index.html && grep -q 'sw-cleanup.js' {{ aw_server_webui_dir }}/index.html" - - - name: Show final endpoint - ansible.builtin.debug: - msg: - - "CT {{ proxmox_ct_id }} is provisioned and configured." - - "ActivityWatch endpoint: http://{{ proxmox_ct_ip | regex_replace('/[0-9]+$', '') }}:{{ aw_server_port }}" + - name: Execute single-CT provisioning workflow + ansible.builtin.include_tasks: tasks/provision_ct_and_deploy_aw.yml + vars: + ct_id: "{{ proxmox_ct_id }}" + ct_hostname: "{{ proxmox_ct_hostname }}" + ct_storage: "{{ proxmox_ct_storage }}" + ct_template: "{{ proxmox_ct_template }}" + ct_rootfs_size: "{{ proxmox_ct_rootfs_size }}" + ct_cores: "{{ proxmox_ct_cores }}" + ct_memory: "{{ proxmox_ct_memory }}" + ct_swap: "{{ proxmox_ct_swap }}" + ct_bridge: "{{ proxmox_ct_bridge }}" + ct_ip: "{{ proxmox_ct_ip }}" + ct_gw: "{{ proxmox_ct_gw }}" + ct_vlan: "{{ proxmox_ct_vlan | default('') }}" + ct_nameserver: "{{ proxmox_ct_nameserver | default('') }}" + ct_searchdomain: "{{ proxmox_ct_searchdomain | default('') }}" + ct_password: "{{ proxmox_ct_password }}" + ct_unprivileged: "{{ proxmox_ct_unprivileged }}" + ct_onboot: "{{ proxmox_ct_onboot }}" + ct_features: "{{ proxmox_ct_features }}" diff --git a/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml b/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml new file mode 100644 index 0000000..d343ad9 --- /dev/null +++ b/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml @@ -0,0 +1,47 @@ +--- +- name: Provision Proxmox CT matrix and deploy AWatch-rus with RU patch + hosts: proxmox + gather_facts: false + + vars: + proxmox_bootstrap_dir: "/tmp/aw-rus-bootstrap" + aw_bootstrap_files: + - install_aw_server.sh + - apply_webui_ru_patch.sh + - activitywatch-server.service + - aw-server.env.example + - aw-ru-patch.js + - aw-sw-cleanup.js + + tasks: + - name: Validate CT matrix is provided + ansible.builtin.assert: + that: + - proxmox_ct_matrix is defined + - proxmox_ct_matrix | length > 0 + fail_msg: "Define proxmox_ct_matrix in group_vars/proxmox-matrix.yml" + + - name: Execute provisioning workflow for each CT + ansible.builtin.include_tasks: tasks/provision_ct_and_deploy_aw.yml + vars: + ct_id: "{{ item.id }}" + ct_hostname: "{{ item.hostname }}" + ct_storage: "{{ item.storage }}" + ct_template: "{{ item.template }}" + ct_rootfs_size: "{{ item.rootfs_size }}" + ct_cores: "{{ item.cores }}" + ct_memory: "{{ item.memory }}" + ct_swap: "{{ item.swap }}" + ct_bridge: "{{ item.bridge }}" + ct_ip: "{{ item.ip }}" + ct_gw: "{{ item.gw }}" + ct_vlan: "{{ item.vlan | default('') }}" + ct_nameserver: "{{ item.nameserver | default('') }}" + ct_searchdomain: "{{ item.searchdomain | default('') }}" + ct_password: "{{ item.password }}" + ct_unprivileged: "{{ item.unprivileged }}" + ct_onboot: "{{ item.onboot }}" + ct_features: "{{ item.features }}" + loop: "{{ proxmox_ct_matrix }}" + loop_control: + label: "ct={{ item.id }} host={{ item.hostname }} ip={{ item.ip }}" diff --git a/ansible/tasks/provision_ct_and_deploy_aw.yml b/ansible/tasks/provision_ct_and_deploy_aw.yml new file mode 100644 index 0000000..4cceb17 --- /dev/null +++ b/ansible/tasks/provision_ct_and_deploy_aw.yml @@ -0,0 +1,217 @@ +--- +- name: Validate required per-CT variables + ansible.builtin.assert: + that: + - ct_id is defined + - ct_hostname is defined + - ct_storage is defined + - ct_template is defined + - ct_rootfs_size is defined + - ct_cores is defined + - ct_memory is defined + - ct_swap is defined + - ct_bridge is defined + - ct_ip is defined + - ct_gw is defined + - ct_password is defined + - ct_unprivileged is defined + - ct_onboot is defined + - ct_features is defined + - aw_repo_root is defined + - aw_server_version is defined + - aw_server_download_url is defined + - aw_server_bind_host is defined + - aw_server_port is defined + - aw_server_webui_dir is defined + - aw_server_data_dir is defined + - aw_server_log_dir is defined + - aw_server_user is defined + - aw_server_group is defined + fail_msg: "Missing required variables for CT provisioning/deploy." + +- name: Build CT network string + ansible.builtin.set_fact: + ct_net0: >- + name=eth0,bridge={{ ct_bridge }},ip={{ ct_ip }},gw={{ ct_gw }}{% if (ct_vlan | default('') | string | length) > 0 %},tag={{ ct_vlan }}{% endif %} + +- name: Check whether CT already exists + ansible.builtin.command: + argv: + - pct + - status + - "{{ ct_id }}" + register: ct_status_check + failed_when: false + changed_when: false + +- name: Create CT when absent + ansible.builtin.command: + argv: + - pct + - create + - "{{ ct_id }}" + - "{{ ct_template }}" + - --hostname + - "{{ ct_hostname }}" + - --cores + - "{{ ct_cores }}" + - --memory + - "{{ ct_memory }}" + - --swap + - "{{ ct_swap }}" + - --rootfs + - "{{ ct_storage }}:{{ ct_rootfs_size }}" + - --password + - "{{ ct_password }}" + - --unprivileged + - "{{ ct_unprivileged }}" + - --onboot + - "{{ ct_onboot }}" + - --features + - "{{ ct_features }}" + - --net0 + - "{{ ct_net0 }}" + - --nameserver + - "{{ ct_nameserver | default('') }}" + - --searchdomain + - "{{ ct_searchdomain | default('') }}" + - --ostype + - debian + when: ct_status_check.rc != 0 + +- name: Check current CT runtime state + ansible.builtin.command: + argv: + - pct + - status + - "{{ ct_id }}" + register: ct_runtime_status + changed_when: false + +- name: Start CT when stopped + ansible.builtin.command: + argv: + - pct + - start + - "{{ ct_id }}" + when: "'stopped' in ct_runtime_status.stdout" + +- name: Ensure bootstrap directory on Proxmox host + ansible.builtin.file: + path: "{{ proxmox_bootstrap_dir }}" + state: directory + mode: "0700" + +- name: Copy AW bootstrap files to Proxmox host temp + ansible.builtin.copy: + src: "{{ aw_repo_root }}/aw-server/{{ item }}" + dest: "{{ proxmox_bootstrap_dir }}/{{ item }}" + mode: "0644" + loop: "{{ aw_bootstrap_files }}" + +- name: Bootstrap CT OS dependencies + ansible.builtin.command: + argv: + - pct + - exec + - "{{ ct_id }}" + - -- + - bash + - -lc + - | + set -euo pipefail + export DEBIAN_FRONTEND=noninteractive + apt-get update + apt-get install -y curl ca-certificates bash unzip xz-utils jq rsync openssh-server + mkdir -p /root/bootstrap /etc/activitywatch + systemctl enable ssh || true + systemctl restart ssh || true + +- name: Push bootstrap files into CT + ansible.builtin.command: + argv: + - pct + - push + - "{{ ct_id }}" + - "{{ proxmox_bootstrap_dir }}/{{ item }}" + - "/root/bootstrap/{{ item }}" + loop: "{{ aw_bootstrap_files }}" + +- name: Write AW server env file on Proxmox host temp + ansible.builtin.copy: + dest: "{{ proxmox_bootstrap_dir }}/aw-server.env" + mode: "0600" + content: | + AW_SERVER_VERSION={{ aw_server_version }} + AW_SERVER_DOWNLOAD_URL={{ aw_server_download_url }} + AW_SERVER_BIND_HOST={{ aw_server_bind_host }} + AW_SERVER_PORT={{ aw_server_port }} + AW_SERVER_WEBUI_DIR={{ aw_server_webui_dir }} + AW_SERVER_DATA_DIR={{ aw_server_data_dir }} + AW_SERVER_LOG_DIR={{ aw_server_log_dir }} + AW_SERVER_USER={{ aw_server_user }} + AW_SERVER_GROUP={{ aw_server_group }} + +- name: Push AW server env into CT + ansible.builtin.command: + argv: + - pct + - push + - "{{ ct_id }}" + - "{{ proxmox_bootstrap_dir }}/aw-server.env" + - /etc/activitywatch/aw-server.env + +- name: Set mode for env inside CT + ansible.builtin.command: + argv: + - pct + - exec + - "{{ ct_id }}" + - -- + - chmod + - "0600" + - /etc/activitywatch/aw-server.env + +- name: Install server and apply RU patch inside CT + ansible.builtin.command: + argv: + - pct + - exec + - "{{ ct_id }}" + - -- + - bash + - -lc + - | + set -euo pipefail + chmod +x /root/bootstrap/install_aw_server.sh /root/bootstrap/apply_webui_ru_patch.sh + bash /root/bootstrap/install_aw_server.sh + bash /root/bootstrap/apply_webui_ru_patch.sh + systemctl restart activitywatch-server.service + +- name: Validate AW API from inside CT + ansible.builtin.command: + argv: + - pct + - exec + - "{{ ct_id }}" + - -- + - bash + - -lc + - "curl -fsS http://127.0.0.1:{{ aw_server_port }}/api/0/info >/dev/null" + +- name: Validate RU patch hooks in index + ansible.builtin.command: + argv: + - pct + - exec + - "{{ ct_id }}" + - -- + - bash + - -lc + - "grep -q 'ru-patch-v5.js' {{ aw_server_webui_dir }}/index.html && grep -q 'sw-cleanup.js' {{ aw_server_webui_dir }}/index.html" + +- name: Show final endpoint + ansible.builtin.debug: + msg: + - "CT {{ ct_id }} is provisioned and configured." + - "ActivityWatch endpoint: http://{{ ct_ip | regex_replace('/[0-9]+$', '') }}:{{ aw_server_port }}" diff --git a/docs/FULL_DEPLOYMENT_MANUAL_RU.md b/docs/FULL_DEPLOYMENT_MANUAL_RU.md index 262921f..09b3cad 100755 --- a/docs/FULL_DEPLOYMENT_MANUAL_RU.md +++ b/docs/FULL_DEPLOYMENT_MANUAL_RU.md @@ -19,6 +19,7 @@ - `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/windows/browser-domains-native-collector.ps1` - `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/ansible/deploy_aw_server.yml` - `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/ansible/provision_proxmox_ct_and_deploy_aw.yml` +- `/mnt/usb_hdd2/Projects/ActivityWatch-Russian/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml` --- @@ -75,6 +76,13 @@ ansible-playbook -i inventory.ini provision_proxmox_ct_and_deploy_aw.yml - применение RU Web UI patch; - проверку API. +Для массового режима (несколько CT): + +```bash +cd /mnt/usb_hdd2/Projects/ActivityWatch-Russian/ansible +ansible-playbook -i inventory.ini provision_proxmox_ct_matrix_and_deploy_aw.yml +``` + ### 2.1 Создать LXC контейнер На узле Proxmox: diff --git a/docs/deployment.md b/docs/deployment.md index a069f68..0a291d8 100755 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -9,6 +9,13 @@ ansible-playbook -i inventory.ini provision_proxmox_ct_and_deploy_aw.yml Этот playbook создаёт CT в Proxmox и полностью разворачивает ActivityWatch внутри контейнера. +Массовый вариант по матрице CT: + +```sh +cd /home/igor/tmp/AWatch-rus/ansible +ansible-playbook -i inventory.ini provision_proxmox_ct_matrix_and_deploy_aw.yml +``` + ## 1. Подготовить env-файлы На рабочей машине оператора: