feat(ansible): add hardened matrix rollout for Proxmox CTs with RU patch

This commit is contained in:
igor04091968
2026-04-25 15:57:30 +03:00
parent 9e53272ce9
commit 016a323afc
8 changed files with 354 additions and 214 deletions
+14
View File
@@ -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.
@@ -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"
+22 -214
View File
@@ -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 }}"
@@ -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 }}"
@@ -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 }}"