diff --git a/playbooks/statistica.yml b/playbooks/statistica.yml
index 8c4eb8d..246d153 100644
--- a/playbooks/statistica.yml
+++ b/playbooks/statistica.yml
@@ -1,278 +1,63 @@
---
-- name: Collect system metrics and send to Telegram (with optional proxy)
- hosts: all
- gather_facts: yes
+- name: Update AdGuard VPN exclusions
+ hosts: your_nuc
+ gather_facts: no
vars:
+ exclusions_url: "https://raw.githubusercontent.com/lib4u/amnezia-tunneling-ru/main/amnezia.json"
telegram_token: "8820292312:AAECFO1AEWOJTh30zNuhyjC5C81o_qJHQcM"
telegram_chat_id: "407968638"
- # Прокси можно переопределить в инвентаре или оставить пустым
- proxy: "socks5h://administrator:60NthRegT@192.168.0.218:1080"
-
tasks:
- - name: Gather uptime
- command: uptime -p
- register: uptime_output
- changed_when: false
+ - name: Download current exclusion list
+ ansible.builtin.uri:
+ url: "{{ exclusions_url }}"
+ return_content: yes
+ register: exclusions_raw
- # --- Получение нагрузки (load average) ---
- - name: Get load average (1, 5, 15 min)
- command: uptime
- register: load_output
- changed_when: false
+ - name: Extract domains from JSON (amnezia.json format)
+ 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 load averages
- set_fact:
- load1: "{{ (load_output.stdout.split('load average:')[1].split(',')[0] | trim) if 'load average:' in load_output.stdout else 'N/A' }}"
- load5: "{{ (load_output.stdout.split('load average:')[1].split(',')[1] | trim) if 'load average:' in load_output.stdout else 'N/A' }}"
- load15: "{{ (load_output.stdout.split('load average:')[1].split(',')[2] | trim) if 'load average:' in load_output.stdout else 'N/A' }}"
- when: "'load average:' in load_output.stdout"
+ - 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
- # --- НАДЁЖНОЕ получение CPU idle через vmstat (с fallback'ами) ---
- - name: Get CPU idle via vmstat
- shell: |
- 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: Count domains
+ ansible.builtin.debug:
+ msg: "Found {{ domains | length }} domains to exclude"
- - name: Get CPU idle via mpstat (fallback)
- shell: |
- if command -v mpstat >/dev/null 2>&1; then
- mpstat 1 1 | awk '/Average/ {print $12}'
- else
- echo ""
- fi
- register: cpu_idle_mpstat
- changed_when: false
- failed_when: false
- when: cpu_idle_vmstat.stdout == ""
+ - name: Clear existing exclusions
+ ansible.builtin.command:
+ cmd: adguardvpn-cli site-exclusions clear
+ changed_when: true
- - name: Get CPU idle via top (fallback)
- 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: Set exclusion mode to GENERAL
+ ansible.builtin.command:
+ cmd: adguardvpn-cli site-exclusions mode general
+ changed_when: true
- - name: Get CPU idle via /proc/stat (last resort)
- 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))
- if command -v bc >/dev/null 2>&1; then
- echo "scale=1; ($diff_idle * 100) / $diff_total" | bc
- else
- awk -v diff_idle="$diff_idle" -v diff_total="$diff_total" 'BEGIN {printf "%.1f", (diff_idle * 100) / diff_total}'
- fi
- 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: Add each domain as exclusion
+ ansible.builtin.command:
+ cmd: "adguardvpn-cli site-exclusions add {{ item }}"
+ loop: "{{ domains }}"
+ when: domains | length > 0
+ loop_control:
+ pause: 0.1
+ changed_when: true
- - name: Set final idle with validation
- 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 -%}
+ - name: Restart VPN to apply changes
+ ansible.builtin.command:
+ cmd: "adguardvpn-cli disconnect && adguardvpn-cli connect"
+ changed_when: true
- # --- Память (исправлено: используем available вместо free) ---
- - name: Get memory usage in human-readable
- shell: |
- free -h | awk '/Mem:/ {printf "Занято: %s, Всего: %s, Доступно: %s", $3, $2, $6}'
- register: memory_usage_human
- changed_when: false
-
- # --- Диск (убрана лишняя запятая) ---
- - name: Get disk usage for root
- shell: |
- df -h / | awk 'NR==2{printf "Занято: %s, Всего: %s, Свободно: %s, Использовано: %s", $3, $2, $4, $5}' | sed 's/, $//'
- register: disk_usage
- changed_when: false
-
- # --- Частота CPU ---
- - name: Get CPU frequency
- 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
- set_fact:
- cpu_freq_text: "{{ cpu_freq.stdout if cpu_freq.stdout != '' else '' }}"
-
- # --- Топ-5 процессов ---
- - name: Get top 5 processes by memory
- 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
- set_fact:
- top_procs_joined: "{{ top_procs.stdout_lines | join('\n') }}"
-
- # --- Температура CPU ---
- - name: Get CPU temperature from thermal zone
- 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
- shell: |
- sensors | 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
- set_fact:
- cpu_temp_final: "{{ cpu_temp.stdout if cpu_temp.stdout != '' else cpu_temp_sensors.stdout }}"
-
- # --- Температура диска ---
- - name: Check if smartctl exists
- command: which smartctl
- register: smartctl_check
- changed_when: false
- failed_when: false
-
- - name: Get list of block devices
- shell: |
- lsblk -dn -o NAME | grep -E '^(sd|hd|nvme)' | head -1
- register: disk_device
- changed_when: false
- failed_when: false
-
- - name: Get disk temperature for first found disk
- shell: |
- smartctl -A /dev/{{ disk_device.stdout }} | 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)
- set_fact:
- disk_temp_text: "{{ (disk_temp | default({})).stdout | default('') }}"
- disk_device_name: "{{ (disk_device | default({})).stdout | default('') }}"
-
- # --- Расчёт процентов ---
- - name: Calculate CPU usage %
- set_fact:
- cpu_usage: "{{ 100 - (cpu_idle_final | float) }}"
-
- - name: Extract disk used percentage
- set_fact:
- disk_used_percent: "{{ disk_usage.stdout.split('Использовано:')[1].strip().replace('%', '') | float }}"
-
- - name: Extract memory percentage
- shell: |
- free -m | awk 'NR==2{printf "%.0f", $3/$2*100}'
- register: mem_percent
- changed_when: false
-
- # --- Прогресс-бары ---
- - name: Build progress bars (20 chars)
- 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
- 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)) }}"
-
- # --- Индикаторы здоровья ---
- - name: Set health indicators
- 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 %}"
-
- # --- Формирование сообщения (HTML) ---
- - name: Build message (HTML)
- set_fact:
- message_html: |
- 📊 Системные метрики ({{ ansible_date_time.date }} {{ ansible_date_time.time }})
- 🖥️ Хост: {{ ansible_hostname }} ({{ ansible_default_ipv4.address }})
- ⏱️ Время работы: {{ uptime_output.stdout }}
- 📈 Нагрузка (1,5,15 мин): {{ load1 | default('N/A') }} , {{ load5 | default('N/A') }} , {{ load15 | default('N/A') }}
- ───────────────
- 💻 CPU: {{ (cpu_usage | float) | round(1) }}% {{ cpu_health }}{% if cpu_freq_text != '' %} ({{ cpu_freq_text }}){% endif %}
-
- {{ cpu_progress }}
- ───────────────
- 🧠 Память: {{ mem_percent.stdout }}% {{ mem_health }} ({{ memory_usage_human.stdout }})
- {{ mem_progress }}
- ───────────────
- 💾 Диск /: {{ (disk_used_percent | float) | round(1) }}% {{ disk_health }} ({{ disk_usage.stdout.split('Использовано:')[0].strip() }})
- {{ disk_progress }}
- ───────────────
- 🌡️ Температура CPU: {{ cpu_temp_final | default('недоступно') }}
- {% if disk_temp_text != "" %}
- 💽 Температура диска ({{ disk_device_name }}): {{ disk_temp_text }}
- {% endif %}
- ───────────────
- 🔥 Топ-5 процессов по памяти:
-
{{ top_procs_joined }}
-
- # --- Отправка в Telegram с проверкой HTTP-кода и поддержкой опционального прокси ---
- - name: Send to Telegram via proxy
- shell: |
- set -e
- if [ -n "{{ proxy }}" ]; then
- proxy_opt="--socks5-hostname {{ proxy }}"
- else
- proxy_opt=""
- 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 ]; then
- if echo "$body" | grep -q '"ok":true'; then
- echo "Message sent successfully"
- exit 0
- else
- echo "ERROR: Telegram API returned ok=false: $body" >&2
- exit 1
- fi
- else
- echo "ERROR: HTTP error $http_code: $body" >&2
- exit 1
- fi
- changed_when: false
- register: tg_response
- failed_when: tg_response.rc != 0
\ No newline at end of file
+ - name: Send Telegram notification
+ community.general.telegram:
+ token: "{{ telegram_token }}"
+ api_args:
+ chat_id: "{{ telegram_chat_id }}"
+ text: "✅ Список исключений обновлён. Добавлено {{ domains | length }} доменов."
+ use_proxy: no # явно отключаем прокси
+ delegate_to: localhost
+ when: telegram_token is defined and telegram_chat_id is defined
\ No newline at end of file