Обновить playbooks/statistica.yml
This commit is contained in:
+277
-51
@@ -1,65 +1,291 @@
|
|||||||
---
|
---
|
||||||
- name: Update AdGuard VPN exclusions
|
- name: Collect Debian system metrics and send to Telegram (with optional proxy)
|
||||||
hosts: all
|
hosts: all
|
||||||
gather_facts: no
|
gather_facts: yes
|
||||||
|
become: true
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
exclusions_url: "https://raw.githubusercontent.com/lib4u/amnezia-tunneling-ru/main/amnezia.json"
|
|
||||||
telegram_token: "8820292312:AAECFO1AEWOJTh30zNuhyjC5C81o_qJHQcM"
|
telegram_token: "8820292312:AAECFO1AEWOJTh30zNuhyjC5C81o_qJHQcM"
|
||||||
telegram_chat_id: "407968638"
|
telegram_chat_id: "407968638"
|
||||||
|
# Прокси: socks5h://user:pass@host:port — оставь пустым, если не нужен
|
||||||
|
proxy: ""
|
||||||
|
# Пакеты, нужные для метрик (Debian)
|
||||||
|
metrics_packages:
|
||||||
|
- curl
|
||||||
|
- bc
|
||||||
|
- procps
|
||||||
|
- sysstat
|
||||||
|
- lm-sensors
|
||||||
|
- smartmontools
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Download current exclusion list
|
# ---------- 0. PREFLIGHT: ставим зависимости на Debian ----------
|
||||||
ansible.builtin.uri:
|
- name: Ensure required packages are installed (Debian/Ubuntu)
|
||||||
url: "{{ exclusions_url }}"
|
ansible.builtin.apt:
|
||||||
return_content: yes
|
name: "{{ metrics_packages }}"
|
||||||
register: exclusions_raw
|
state: present
|
||||||
|
update_cache: yes
|
||||||
|
cache_valid_time: 3600
|
||||||
|
when: ansible_os_family == "Debian"
|
||||||
|
register: apt_result
|
||||||
|
|
||||||
- name: Extract domains from JSON (amnezia.json format)
|
- name: Warn if host is not Debian family
|
||||||
ansible.builtin.set_fact:
|
|
||||||
domains: "{{ exclusions_raw.content | from_json | json_query('domains') }}"
|
|
||||||
when: exclusions_raw.content | from_json | type_debug == 'dict'
|
|
||||||
ignore_errors: yes
|
|
||||||
|
|
||||||
- name: Extract domains from plain text (fallback)
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
domains: "{{ exclusions_raw.content | split('\n') | reject('match', '^#') | reject('match', '^$') | list }}"
|
|
||||||
when: domains is not defined
|
|
||||||
|
|
||||||
- name: Count domains
|
|
||||||
ansible.builtin.debug:
|
ansible.builtin.debug:
|
||||||
msg: "Found {{ domains | length }} domains to exclude"
|
msg: "WARNING: {{ inventory_hostname }} is {{ ansible_os_family }}, не Debian — часть метрик может быть недоступна"
|
||||||
|
when: ansible_os_family != "Debian"
|
||||||
|
|
||||||
- name: Clear existing exclusions
|
# ---------- 1. Uptime ----------
|
||||||
ansible.builtin.command:
|
- name: Gather uptime
|
||||||
cmd: adguardvpn-cli site-exclusions clear
|
ansible.builtin.command: uptime -p
|
||||||
changed_when: true
|
register: uptime_output
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
- name: Set exclusion mode to GENERAL
|
# ---------- 2. Load average ----------
|
||||||
ansible.builtin.command:
|
- name: Get load average (1, 5, 15 min)
|
||||||
cmd: adguardvpn-cli site-exclusions mode general
|
ansible.builtin.command: uptime
|
||||||
changed_when: true
|
register: load_output
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
- name: Add each domain as exclusion
|
- name: Extract load averages
|
||||||
ansible.builtin.command:
|
ansible.builtin.set_fact:
|
||||||
cmd: "adguardvpn-cli site-exclusions add {{ item }}"
|
load1: "{{ (load_output.stdout.split('load average:')[1].split(',')[0] | trim) if 'load average:' in load_output.stdout else 'N/A' }}"
|
||||||
loop: "{{ domains }}"
|
load5: "{{ (load_output.stdout.split('load average:')[1].split(',')[1] | trim) if 'load average:' in load_output.stdout else 'N/A' }}"
|
||||||
when: domains | length > 0
|
load15: "{{ (load_output.stdout.split('load average:')[1].split(',')[2] | trim) if 'load average:' in load_output.stdout else 'N/A' }}"
|
||||||
loop_control:
|
|
||||||
pause: 0.1
|
|
||||||
changed_when: true
|
|
||||||
|
|
||||||
- name: Restart VPN to apply changes
|
# ---------- 3. CPU idle (vmstat -> mpstat -> top -> /proc/stat) ----------
|
||||||
ansible.builtin.command:
|
- name: Get CPU idle via vmstat
|
||||||
cmd: "adguardvpn-cli disconnect && adguardvpn-cli connect"
|
ansible.builtin.shell: |
|
||||||
changed_when: true
|
if command -v vmstat >/dev/null 2>&1; then
|
||||||
|
vmstat 1 2 | tail -1 | awk '{print $15}'
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
register: cpu_idle_vmstat
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
|
||||||
- name: Send Telegram notification via API
|
- name: Get CPU idle via mpstat (fallback)
|
||||||
ansible.builtin.uri:
|
ansible.builtin.shell: |
|
||||||
url: "https://api.telegram.org/bot{{ telegram_token }}/sendMessage"
|
if command -v mpstat >/dev/null 2>&1; then
|
||||||
method: POST
|
mpstat 1 1 | awk '/Average/ {print $NF}'
|
||||||
body_format: json
|
else
|
||||||
body:
|
echo ""
|
||||||
chat_id: "{{ telegram_chat_id }}"
|
fi
|
||||||
text: "✅ Список исключений обновлён. Добавлено {{ domains | length }} доменов."
|
register: cpu_idle_mpstat
|
||||||
disable_web_page_preview: yes
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
when: cpu_idle_vmstat.stdout == ""
|
||||||
|
|
||||||
|
- name: Get CPU idle via top (fallback)
|
||||||
|
ansible.builtin.shell: |
|
||||||
|
top -bn2 | grep "Cpu(s)" | tail -1 | sed 's/.*, *\([0-9.]*\)%* id.*/\1/'
|
||||||
|
register: cpu_idle_top
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
when: cpu_idle_vmstat.stdout == "" and cpu_idle_mpstat.stdout == ""
|
||||||
|
|
||||||
|
- name: Get CPU idle via /proc/stat (last resort)
|
||||||
|
ansible.builtin.shell: |
|
||||||
|
prev_idle=$(awk '/^cpu / {print $5}' /proc/stat)
|
||||||
|
prev_total=$(awk '/^cpu / {print $2+$3+$4+$5+$6+$7+$8}' /proc/stat)
|
||||||
|
sleep 1
|
||||||
|
curr_idle=$(awk '/^cpu / {print $5}' /proc/stat)
|
||||||
|
curr_total=$(awk '/^cpu / {print $2+$3+$4+$5+$6+$7+$8}' /proc/stat)
|
||||||
|
diff_idle=$((curr_idle - prev_idle))
|
||||||
|
diff_total=$((curr_total - prev_total))
|
||||||
|
awk -v i="$diff_idle" -v t="$diff_total" 'BEGIN {printf "%.1f", (i*100)/t}'
|
||||||
|
register: cpu_idle_proc
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
when: cpu_idle_vmstat.stdout == "" and cpu_idle_mpstat.stdout == "" and cpu_idle_top.stdout == ""
|
||||||
|
|
||||||
|
- name: Set final idle with validation
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
cpu_idle_final: >-
|
||||||
|
{%- set idle = (cpu_idle_vmstat.stdout | float) if cpu_idle_vmstat.stdout != "" else
|
||||||
|
(cpu_idle_mpstat.stdout | float) if cpu_idle_mpstat.stdout != "" else
|
||||||
|
(cpu_idle_top.stdout | float) if cpu_idle_top.stdout != "" else
|
||||||
|
(cpu_idle_proc.stdout | float) if cpu_idle_proc.stdout != "" else 90 -%}
|
||||||
|
{%- if idle > 100 or idle < 0 -%}{{ 90 }}{%- else -%}{{ idle }}{%- endif -%}
|
||||||
|
|
||||||
|
# ---------- 4. Память ----------
|
||||||
|
- name: Get memory usage in human-readable
|
||||||
|
ansible.builtin.shell: |
|
||||||
|
free -h | awk '/Mem:/ {printf "Занято: %s, Всего: %s, Доступно: %s", $3, $2, $6}'
|
||||||
|
register: memory_usage_human
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
# ---------- 5. Диск ----------
|
||||||
|
- name: Get disk usage for root
|
||||||
|
ansible.builtin.shell: |
|
||||||
|
df -h / | awk 'NR==2{printf "Занято: %s, Всего: %s, Свободно: %s, Использовано: %s", $3, $2, $4, $5}'
|
||||||
|
register: disk_usage
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
# ---------- 6. Частота CPU ----------
|
||||||
|
- name: Get CPU frequency
|
||||||
|
ansible.builtin.shell: |
|
||||||
|
if [ -f /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq ]; then
|
||||||
|
awk '{printf "%.2f GHz", $1/1000000}' /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq
|
||||||
|
elif command -v lscpu >/dev/null 2>&1; then
|
||||||
|
lscpu | grep "MHz" | awk '{print $3}' | head -1 | awk '{printf "%.2f GHz", $1/1000}'
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
register: cpu_freq
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
|
||||||
|
- name: Set CPU frequency text
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
cpu_freq_text: "{{ cpu_freq.stdout if cpu_freq.stdout != '' else '' }}"
|
||||||
|
|
||||||
|
# ---------- 7. Топ-5 процессов ----------
|
||||||
|
- name: Get top 5 processes by memory
|
||||||
|
ansible.builtin.shell: |
|
||||||
|
ps aux --sort=-%mem | head -6 | tail -5 | awk '{print $11 " (" $4 "%)"}'
|
||||||
|
register: top_procs
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Join top processes into a single string
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
top_procs_joined: "{{ top_procs.stdout_lines | join('\n') }}"
|
||||||
|
|
||||||
|
# ---------- 8. Температура CPU ----------
|
||||||
|
- name: Get CPU temperature from thermal zone
|
||||||
|
ansible.builtin.shell: |
|
||||||
|
cat /sys/class/thermal/thermal_zone0/temp 2>/dev/null | awk '{printf "%.1f°C", $1/1000}'
|
||||||
|
register: cpu_temp
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
|
||||||
|
- name: Fallback to sensors if thermal zone not available
|
||||||
|
ansible.builtin.shell: |
|
||||||
|
sensors 2>/dev/null | grep -i "Package id 0" | awk '{print $4}' | sed 's/+//g' | sed 's/°C//g' | awk '{printf "%.1f°C", $1}'
|
||||||
|
register: cpu_temp_sensors
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
when: cpu_temp.stdout == ""
|
||||||
|
|
||||||
|
- name: Set final CPU temperature
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
cpu_temp_final: "{{ cpu_temp.stdout if cpu_temp.stdout != '' else (cpu_temp_sensors.stdout | default('')) }}"
|
||||||
|
|
||||||
|
# ---------- 9. Температура диска ----------
|
||||||
|
- name: Check if smartctl exists
|
||||||
|
ansible.builtin.command: which smartctl
|
||||||
|
register: smartctl_check
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
|
||||||
|
- name: Get list of block devices
|
||||||
|
ansible.builtin.shell: |
|
||||||
|
lsblk -dn -o NAME | grep -E '^(sd|hd|nvme|vd|xvd)' | head -1
|
||||||
|
register: disk_device
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
|
||||||
|
- name: Get disk temperature for first found disk
|
||||||
|
ansible.builtin.shell: |
|
||||||
|
smartctl -A /dev/{{ disk_device.stdout }} 2>/dev/null \
|
||||||
|
| grep -i "Temperature" | awk '{print $10}' \
|
||||||
|
| sed 's/°C//g' | awk '{printf "%.1f°C", $1}'
|
||||||
|
register: disk_temp
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
when: smartctl_check.rc == 0 and disk_device.stdout != ""
|
||||||
|
|
||||||
|
- name: Set disk temperature variables (safe)
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
disk_temp_text: "{{ (disk_temp | default({})).stdout | default('') }}"
|
||||||
|
disk_device_name: "{{ (disk_device | default({})).stdout | default('') }}"
|
||||||
|
|
||||||
|
# ---------- 10. Расчёт процентов ----------
|
||||||
|
- name: Calculate CPU usage %
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
cpu_usage: "{{ 100 - (cpu_idle_final | float) }}"
|
||||||
|
|
||||||
|
- name: Extract disk used percentage
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
disk_used_percent: "{{ disk_usage.stdout.split('Использовано:')[1].strip().replace('%', '') | float }}"
|
||||||
|
|
||||||
|
- name: Extract memory percentage
|
||||||
|
ansible.builtin.shell: |
|
||||||
|
free -m | awk 'NR==2{printf "%.0f", $3/$2*100}'
|
||||||
|
register: mem_percent
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
# ---------- 11. Прогресс-бары ----------
|
||||||
|
- name: Build progress bars (20 chars)
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
bar_length: 20
|
||||||
|
cpu_bar: "{{ ((cpu_usage | float / 5) | round(0) | int) if (cpu_usage | float) < 100 else 20 }}"
|
||||||
|
mem_bar: "{{ ((mem_percent.stdout | float / 5) | round(0) | int) if (mem_percent.stdout | float) < 100 else 20 }}"
|
||||||
|
disk_bar: "{{ ((disk_used_percent | float / 5) | round(0) | int) if (disk_used_percent | float) < 100 else 20 }}"
|
||||||
|
|
||||||
|
- name: Create bar strings
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
cpu_progress: "{{ '█' * (cpu_bar | int) + '░' * (bar_length - (cpu_bar | int)) }}"
|
||||||
|
mem_progress: "{{ '█' * (mem_bar | int) + '░' * (bar_length - (mem_bar | int)) }}"
|
||||||
|
disk_progress: "{{ '█' * (disk_bar | int) + '░' * (bar_length - (disk_bar | int)) }}"
|
||||||
|
|
||||||
|
# ---------- 12. Индикаторы ----------
|
||||||
|
- name: Set health indicators
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
cpu_health: "{% if (cpu_usage | float) < 50 %}🟢{% elif (cpu_usage | float) < 80 %}🟡{% else %}🔴{% endif %}"
|
||||||
|
mem_health: "{% if (mem_percent.stdout | float) < 50 %}🟢{% elif (mem_percent.stdout | float) < 80 %}🟡{% else %}🔴{% endif %}"
|
||||||
|
disk_health: "{% if (disk_used_percent | float) < 70 %}🟢{% elif (disk_used_percent | float) < 85 %}🟡{% else %}🔴{% endif %}"
|
||||||
|
|
||||||
|
# ---------- 13. Формирование сообщения ----------
|
||||||
|
- name: Build message (HTML)
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
message_html: |
|
||||||
|
<b>📊 Системные метрики</b> <i>({{ ansible_date_time.date }} {{ ansible_date_time.time }})</i>
|
||||||
|
<b>🖥️ Хост:</b> <code>{{ ansible_hostname }}</code> ({{ ansible_default_ipv4.address | default('N/A') }})
|
||||||
|
<b>⏱️ Время работы:</b> {{ uptime_output.stdout }}
|
||||||
|
<b>📈 Нагрузка (1,5,15 мин):</b> {{ load1 | default('N/A') }} , {{ load5 | default('N/A') }} , {{ load15 | default('N/A') }}
|
||||||
|
───────────────
|
||||||
|
<b>💻 CPU:</b> {{ (cpu_usage | float) | round(1) }}% {{ cpu_health }}{% if cpu_freq_text != '' %} ({{ cpu_freq_text }}){% endif %}
|
||||||
|
<code>{{ cpu_progress }}</code>
|
||||||
|
───────────────
|
||||||
|
<b>🧠 Память:</b> {{ mem_percent.stdout }}% {{ mem_health }} ({{ memory_usage_human.stdout }})
|
||||||
|
<code>{{ mem_progress }}</code>
|
||||||
|
───────────────
|
||||||
|
<b>💾 Диск /:</b> {{ (disk_used_percent | float) | round(1) }}% {{ disk_health }} ({{ disk_usage.stdout.split('Использовано:')[0].strip() }})
|
||||||
|
<code>{{ disk_progress }}</code>
|
||||||
|
───────────────
|
||||||
|
<b>🌡️ Температура CPU:</b> {{ cpu_temp_final | default('недоступно') }}
|
||||||
|
{% if disk_temp_text != "" %}
|
||||||
|
<b>💽 Температура диска ({{ disk_device_name }}):</b> {{ disk_temp_text }}
|
||||||
|
{% endif %}
|
||||||
|
───────────────
|
||||||
|
<b>🔥 Топ-5 процессов по памяти:</b>
|
||||||
|
<pre>{{ top_procs_joined }}</pre>
|
||||||
|
|
||||||
|
# ---------- 14. Отправка в Telegram ----------
|
||||||
|
- name: Send to Telegram via proxy
|
||||||
|
ansible.builtin.shell: |
|
||||||
|
set -e
|
||||||
|
proxy_opt=""
|
||||||
|
if [ -n "{{ proxy }}" ]; then
|
||||||
|
proxy_opt="--socks5-hostname {{ proxy }}"
|
||||||
|
fi
|
||||||
|
response=$(curl -s -w "\n%{http_code}" -X POST $proxy_opt \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"chat_id": "{{ telegram_chat_id }}", "text": {{ message_html | to_json }}, "parse_mode": "HTML"}' \
|
||||||
|
"https://api.telegram.org/bot{{ telegram_token }}/sendMessage" 2>&1)
|
||||||
|
http_code=$(echo "$response" | tail -n1)
|
||||||
|
body=$(echo "$response" | sed '$d')
|
||||||
|
echo "HTTP code: $http_code"
|
||||||
|
echo "Response body: $body"
|
||||||
|
if [ "$http_code" -eq 200 ] && echo "$body" | grep -q '"ok":true'; then
|
||||||
|
echo "Message sent successfully"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "ERROR: Telegram send failed" >&2
|
||||||
|
exit 1
|
||||||
|
changed_when: false
|
||||||
|
register: tg_response
|
||||||
|
failed_when: tg_response.rc != 0
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
when: telegram_token is defined and telegram_chat_id is defined
|
become: false
|
||||||
Reference in New Issue
Block a user