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