--- - name: Collect Debian system metrics and send to Telegram (via SOCKS5 proxy) hosts: all gather_facts: yes become: true vars: telegram_token: "8820292312:AAECFO1AEWOJTh30zNuhyjC5C81o_qJHQcM" telegram_chat_id: "407968638" # SOCKS5-прокси на control node (машинке, откуда запускается Ansible) telegram_proxy: "socks5h://administrator:60NthRegT@192.168.0.218:1080" # Пакеты для метрик (Debian) metrics_packages: - curl - bc - procps - sysstat - lm-sensors - smartmontools tasks: # ---------- 0. PREFLIGHT: зависимости на Debian ---------- - name: Ensure required packages are installed (Debian/Ubuntu) ansible.builtin.apt: name: "{{ metrics_packages }}" state: present update_cache: yes cache_valid_time: 3600 when: ansible_os_family == "Debian" - name: Warn if host is not Debian family ansible.builtin.debug: msg: "WARNING: {{ inventory_hostname }} is {{ ansible_os_family }}, не Debian — часть метрик может быть недоступна" when: ansible_os_family != "Debian" # ---------- 1. Uptime ---------- - name: Gather uptime ansible.builtin.command: uptime -p register: uptime_output changed_when: false # ---------- 2. Load average ---------- - name: Get load average (1, 5, 15 min) ansible.builtin.command: uptime register: load_output changed_when: false - name: Extract load averages ansible.builtin.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' }}" # ---------- 3. CPU idle (vmstat -> mpstat -> top -> /proc/stat) ---------- - name: Get CPU idle via vmstat ansible.builtin.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: Get CPU idle via mpstat (fallback) ansible.builtin.shell: | if command -v mpstat >/dev/null 2>&1; then mpstat 1 1 | awk '/Average/ {print $NF}' else echo "" fi register: cpu_idle_mpstat 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: | 📊 Системные метрики ({{ ansible_date_time.date }} {{ ansible_date_time.time }}) 🖥️ Хост: {{ ansible_hostname }} ({{ ansible_default_ipv4.address | default('N/A') }}) ⏱️ Время работы: {{ 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 }}
# ---------- 14. Проверка доступности Telegram через прокси ---------- - name: Check Telegram API reachability via SOCKS5 proxy (from control node) ansible.builtin.shell: | curl -s -o /dev/null -w "%{http_code}" --max-time 15 \ --proxy "{{ telegram_proxy }}" \ "https://api.telegram.org/bot{{ telegram_token }}/getMe" register: tg_check changed_when: false failed_when: false delegate_to: localhost become: false - name: Fail early if Telegram API is unreachable via proxy ansible.builtin.fail: msg: > Telegram API недоступен через прокси {{ telegram_proxy }}. HTTP-код: {{ tg_check.stdout }}. Проверь прокси, учётку и сеть. when: tg_check.stdout != "200" delegate_to: localhost become: false # ---------- 15. Отправка в Telegram через SOCKS5-прокси ---------- - name: Send to Telegram via SOCKS5 proxy (from control node) ansible.builtin.shell: | set -e response=$(curl -s -w "\n%{http_code}" -X POST \ --proxy "{{ telegram_proxy }}" \ -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 become: false