Удалить playbooks/statistica.yml
This commit is contained in:
@@ -1,569 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Weekly Debian server report (with trends) → Telegram via SOCKS5
|
|
||||||
hosts: all
|
|
||||||
gather_facts: yes
|
|
||||||
become: true
|
|
||||||
|
|
||||||
vars:
|
|
||||||
telegram_token: "8820292312:AAECFO1AEWOJTh30zNuhyjC5C81o_qJHQcM"
|
|
||||||
telegram_chat_id: "407968638"
|
|
||||||
telegram_proxy: "socks5h://administrator:60NthRegT@192.168.0.218:1080"
|
|
||||||
|
|
||||||
snapshot_dir: "/var/lib/ansible-metrics"
|
|
||||||
|
|
||||||
metrics_packages:
|
|
||||||
- curl
|
|
||||||
- bc
|
|
||||||
- procps
|
|
||||||
- sysstat
|
|
||||||
- lm-sensors
|
|
||||||
- smartmontools
|
|
||||||
- gawk
|
|
||||||
- coreutils
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
# ==================================================================
|
|
||||||
# 0. PREFLIGHT
|
|
||||||
# ==================================================================
|
|
||||||
- name: Ensure required packages are installed (Debian)
|
|
||||||
ansible.builtin.apt:
|
|
||||||
name: "{{ metrics_packages }}"
|
|
||||||
state: present
|
|
||||||
update_cache: yes
|
|
||||||
cache_valid_time: 3600
|
|
||||||
when: ansible_os_family == "Debian"
|
|
||||||
|
|
||||||
- name: Enable and start sysstat (needed for sar)
|
|
||||||
ansible.builtin.service:
|
|
||||||
name: sysstat
|
|
||||||
state: started
|
|
||||||
enabled: yes
|
|
||||||
when: ansible_os_family == "Debian"
|
|
||||||
failed_when: false
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- 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. СНАПШОТ ПРОШЛОЙ НЕДЕЛИ (на целевом хосте)
|
|
||||||
# ==================================================================
|
|
||||||
- name: Ensure snapshot dir exists on target host
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ snapshot_dir }}"
|
|
||||||
state: directory
|
|
||||||
mode: "0755"
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Read previous week snapshot
|
|
||||||
ansible.builtin.slurp:
|
|
||||||
src: "{{ snapshot_dir }}/{{ inventory_hostname }}.json"
|
|
||||||
register: prev_snapshot_raw
|
|
||||||
failed_when: false
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Parse previous snapshot (or empty dict on first run)
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
prev: "{{ (prev_snapshot_raw.content | b64decode | from_json) if prev_snapshot_raw.content is defined else {} }}"
|
|
||||||
|
|
||||||
# ==================================================================
|
|
||||||
# 2. БАЗОВЫЕ МЕТРИКИ
|
|
||||||
# ==================================================================
|
|
||||||
- name: Gather uptime
|
|
||||||
ansible.builtin.command: uptime -p
|
|
||||||
register: uptime_output
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
# Load average — из /proc/loadavg, без локали
|
|
||||||
- name: Read load average from /proc/loadavg
|
|
||||||
ansible.builtin.slurp:
|
|
||||||
src: /proc/loadavg
|
|
||||||
register: loadavg_raw
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Extract load averages
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
load1: "{{ (loadavg_raw.content | b64decode).split()[0] | default('N/A') }}"
|
|
||||||
load5: "{{ (loadavg_raw.content | b64decode).split()[1] | default('N/A') }}"
|
|
||||||
load15: "{{ (loadavg_raw.content | b64decode).split()[2] | default('N/A') }}"
|
|
||||||
|
|
||||||
# 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
|
|
||||||
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
|
|
||||||
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
|
|
||||||
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 -%}
|
|
||||||
|
|
||||||
# Память
|
|
||||||
- name: Get memory usage human
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
free -h | awk '/Mem:/ {printf "Занято: %s, Всего: %s, Доступно: %s", $3, $2, $6}'
|
|
||||||
register: memory_usage_human
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Get memory percentage
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
free -m | awk 'NR==2{printf "%.0f", $3/$2*100}'
|
|
||||||
register: mem_percent
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
# Диск
|
|
||||||
- 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
|
|
||||||
|
|
||||||
# Частота 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 | default('') }}"
|
|
||||||
|
|
||||||
# Температура 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
|
|
||||||
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('')) }}"
|
|
||||||
|
|
||||||
# Температура и SMART диска
|
|
||||||
- name: Check if smartctl exists
|
|
||||||
ansible.builtin.command: which smartctl
|
|
||||||
register: smartctl_check
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Get first block device
|
|
||||||
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
|
|
||||||
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: Get disk SMART health
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
smartctl -H /dev/{{ disk_device.stdout }} 2>/dev/null \
|
|
||||||
| grep -i "SMART overall-health" | awk -F: '{print $2}' | xargs
|
|
||||||
register: smart_health
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
when: smartctl_check.rc == 0 and disk_device.stdout != ""
|
|
||||||
|
|
||||||
- name: Get reallocated sectors
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
smartctl -A /dev/{{ disk_device.stdout }} 2>/dev/null \
|
|
||||||
| awk '/Reallocated_Sector_Ct/ {print $NF}' | head -1
|
|
||||||
register: reallocated
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
when: smartctl_check.rc == 0 and disk_device.stdout != ""
|
|
||||||
|
|
||||||
- name: Set disk-related facts (safe)
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
disk_temp_text: "{{ (disk_temp | default({})).stdout | default('') }}"
|
|
||||||
disk_device_name: "{{ (disk_device | default({})).stdout | default('') }}"
|
|
||||||
smart_health_text: "{{ (smart_health | default({})).stdout | default('N/A') }}"
|
|
||||||
reallocated_text: "{{ (reallocated | default({})).stdout | default('0') }}"
|
|
||||||
|
|
||||||
# ==================================================================
|
|
||||||
# 3. НЕДЕЛЬНЫЕ АГРЕГАТЫ (sar)
|
|
||||||
# ==================================================================
|
|
||||||
- name: Weekly CPU avg/peak from sar
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
{
|
|
||||||
for i in $(seq 0 6); do
|
|
||||||
d=$(date -d "$i days ago" +%d)
|
|
||||||
f="/var/log/sysstat/sa$d"
|
|
||||||
[ -f "$f" ] && sar -u -f "$f" 2>/dev/null
|
|
||||||
done
|
|
||||||
} | awk '/^[0-9]/ {
|
|
||||||
cpu=100-$NF
|
|
||||||
sum+=cpu; n++
|
|
||||||
if (cpu>max) max=cpu
|
|
||||||
} END {
|
|
||||||
if (n>0) printf "%.1f %.1f %d", sum/n, max, n;
|
|
||||||
else print "N/A N/A 0"
|
|
||||||
}'
|
|
||||||
register: cpu_week
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
# sar -r: $4 = %memused (уже проценты!)
|
|
||||||
- name: Weekly memory peak from sar
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
{
|
|
||||||
for i in $(seq 0 6); do
|
|
||||||
d=$(date -d "$i days ago" +%d)
|
|
||||||
f="/var/log/sysstat/sa$d"
|
|
||||||
[ -f "$f" ] && sar -r -f "$f" 2>/dev/null
|
|
||||||
done
|
|
||||||
} | awk '/^[0-9]/ {
|
|
||||||
if ($4+0 > max) max=$4+0
|
|
||||||
} END {
|
|
||||||
if (max>0) printf "%.1f", max;
|
|
||||||
else print "N/A"
|
|
||||||
}'
|
|
||||||
register: mem_week_peak
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Parse weekly CPU values
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
cpu_week_avg: "{{ (cpu_week.stdout.split()[0]) if cpu_week.stdout.split() | length >= 1 else 'N/A' }}"
|
|
||||||
cpu_week_peak: "{{ (cpu_week.stdout.split()[1]) if cpu_week.stdout.split() | length >= 2 else 'N/A' }}"
|
|
||||||
cpu_week_samples: "{{ (cpu_week.stdout.split()[2]) if cpu_week.stdout.split() | length >= 3 else '0' }}"
|
|
||||||
mem_week_peak_val: "{{ mem_week_peak.stdout | default('N/A') }}"
|
|
||||||
|
|
||||||
# ==================================================================
|
|
||||||
# 4. СОСТОЯНИЕ СИСТЕМЫ
|
|
||||||
# ==================================================================
|
|
||||||
- name: Count upgradable packages
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
apt list --upgradable 2>/dev/null | tail -n +2 | grep -c upgradable || true
|
|
||||||
register: upgradable_count
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Count security updates
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
apt list --upgradable 2>/dev/null | grep -ci security || true
|
|
||||||
register: security_count
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Check if reboot required
|
|
||||||
ansible.builtin.stat:
|
|
||||||
path: /var/run/reboot-required
|
|
||||||
register: reboot_required
|
|
||||||
|
|
||||||
- name: Count failed systemd units
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
systemctl --failed --no-legend 2>/dev/null | wc -l
|
|
||||||
register: failed_units
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Count reboots in last 7 days
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
last -x reboot --since "7 days ago" 2>/dev/null | grep -c "^reboot" || true
|
|
||||||
register: reboot_count
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Count failed SSH logins in last 7 days
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
journalctl --since "7 days ago" -u ssh --no-pager 2>/dev/null \
|
|
||||||
| grep -c "Failed password" || true
|
|
||||||
register: failed_ssh
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Count OOM kills in last 7 days
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
journalctl --since "7 days ago" -k --no-pager 2>/dev/null \
|
|
||||||
| grep -ci "out of memory" || true
|
|
||||||
register: oom_kills
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Count I/O errors in last 7 days
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
journalctl --since "7 days ago" -k --no-pager 2>/dev/null \
|
|
||||||
| grep -ci "I/O error" || true
|
|
||||||
register: io_errors
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Top 5 by memory now
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
ps aux --sort=-%mem | head -6 | tail -5 | awk '{print $11 " (" $4 "%)"}'
|
|
||||||
register: top_procs
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Top 5 long-running processes
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
ps -eo etimes,comm --sort=-etimes | awk 'NR>1 && $1>604800 {print $2 " (uptime " int($1/86400) "d)"}' | head -5
|
|
||||||
register: top_long
|
|
||||||
changed_when: false
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Join top lists
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
top_procs_joined: "{{ top_procs.stdout_lines | join('\n') }}"
|
|
||||||
top_long_joined: "{{ top_long.stdout_lines | join('\n') if top_long.stdout_lines | length > 0 else '(нет процессов с аптаймом > 7 дней)' }}"
|
|
||||||
|
|
||||||
# ==================================================================
|
|
||||||
# 5. РАСЧЁТЫ, ДЕЛЬТЫ, HEALTH-SCORE
|
|
||||||
# ==================================================================
|
|
||||||
- name: Calculate CPU usage %
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
cpu_usage: "{{ 100 - (cpu_idle_final | float) }}"
|
|
||||||
|
|
||||||
- name: Extract disk used %
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
disk_used_percent: "{{ disk_usage.stdout.split('Использовано:')[1].strip().replace('%', '') | float }}"
|
|
||||||
|
|
||||||
- name: Compute deltas vs previous week
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
disk_delta: "{{ ((disk_used_percent | float) - (prev.disk_used_pct | float)) | round(1) }}"
|
|
||||||
mem_delta: "{{ ((mem_percent.stdout | float) - (prev.mem_pct | float)) | round(1) }}"
|
|
||||||
when:
|
|
||||||
- prev.disk_used_pct is defined
|
|
||||||
- prev.mem_pct is defined
|
|
||||||
|
|
||||||
- name: Format delta strings with arrows
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
disk_delta_str: >-
|
|
||||||
{%- if disk_delta is defined -%}
|
|
||||||
{%- set d = disk_delta | float -%}
|
|
||||||
{%- if d > 1 %} (+{{ d }}% ⬆️)
|
|
||||||
{%- elif d < -1 %} ({{ d }}% ⬇️)
|
|
||||||
{%- else %} (≈ без изменений){%- endif -%}
|
|
||||||
{%- else -%} (нет данных за прошлую неделю){%- endif -%}
|
|
||||||
mem_delta_str: >-
|
|
||||||
{%- if mem_delta is defined -%}
|
|
||||||
{%- set d = mem_delta | float -%}
|
|
||||||
{%- if d > 3 %} (+{{ d }}% ⬆️)
|
|
||||||
{%- elif d < -3 %} ({{ d }}% ⬇️)
|
|
||||||
{%- else %} (≈ без изменений){%- endif -%}
|
|
||||||
{%- else -%} (нет данных){%- endif -%}
|
|
||||||
|
|
||||||
- name: Set component health
|
|
||||||
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) < 60 %}🟢{% elif (mem_percent.stdout | float) < 85 %}🟡{% else %}🔴{% endif %}"
|
|
||||||
disk_health: "{% if (disk_used_percent | float) < 70 %}🟢{% elif (disk_used_percent | float) < 85 %}🟡{% else %}🔴{% endif %}"
|
|
||||||
|
|
||||||
- name: Compute warnings list
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
warnings_list: >-
|
|
||||||
{{
|
|
||||||
(
|
|
||||||
(['Диск > 80%'] if (disk_used_percent | float) > 80 else []) +
|
|
||||||
(['Диск вырос > 5% за неделю'] if (disk_delta is defined and (disk_delta | float) > 5) else []) +
|
|
||||||
(['Требуется перезагрузка'] if reboot_required.stat.exists else []) +
|
|
||||||
(['Security-обновления не установлены'] if (security_count.stdout | int) > 0 else []) +
|
|
||||||
(['Failed systemd units: ' ~ failed_units.stdout] if (failed_units.stdout | int) > 0 else []) +
|
|
||||||
(['OOM-kills за неделю'] if (oom_kills.stdout | int) > 0 else []) +
|
|
||||||
(['I/O errors в ядре'] if (io_errors.stdout | int) > 0 else []) +
|
|
||||||
(['SMART не PASSED'] if (smart_health_text != 'PASSED' and smart_health_text != 'N/A' and smart_health_text != '') else [])
|
|
||||||
) | list
|
|
||||||
}}
|
|
||||||
|
|
||||||
- name: Compute overall status emoji
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
overall_status: >-
|
|
||||||
{%- set n = warnings_list | length -%}
|
|
||||||
{{ '🔴 Требует внимания' if n >= 3 else ('🟡 Есть предупреждения' if n > 0 else '🟢 Всё в порядке') }}
|
|
||||||
|
|
||||||
# ==================================================================
|
|
||||||
# 6. ФОРМИРОВАНИЕ СООБЩЕНИЯ
|
|
||||||
# ==================================================================
|
|
||||||
- name: Build weekly report message (HTML)
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
message_html: |
|
|
||||||
<b>📅 Еженедельный отчёт: {{ ansible_hostname }}</b>
|
|
||||||
<b>Статус:</b> {{ overall_status }} {% if warnings_list | length > 0 %}({{ warnings_list | length }} ⚠️){% endif %}
|
|
||||||
<i>{{ ansible_date_time.date }} {{ ansible_date_time.time }} · {{ ansible_default_ipv4.address | default('N/A') }}</i>
|
|
||||||
|
|
||||||
{% if warnings_list | length > 0 %}
|
|
||||||
<b>⚠️ Предупреждения:</b>
|
|
||||||
{% for w in warnings_list %}• {{ w }}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
───────────────
|
|
||||||
<b>📊 Средние и пиковые за неделю</b>
|
|
||||||
<b>💻 CPU:</b> avg {{ cpu_week_avg }}% · peak {{ cpu_week_peak }}% <i>({{ cpu_week_samples }} замеров)</i>
|
|
||||||
<b>🧠 RAM:</b> peak {{ mem_week_peak_val }}%
|
|
||||||
───────────────
|
|
||||||
<b>📈 Текущее состояние</b>
|
|
||||||
<b>⏱️ Аптайм:</b> {{ uptime_output.stdout }}
|
|
||||||
<b>📉 Load avg (1,5,15):</b> {{ load1 }} , {{ load5 }} , {{ load15 }}
|
|
||||||
|
|
||||||
<b>💻 CPU сейчас:</b> {{ (cpu_usage | float) | round(1) }}% {{ cpu_health }}{% if cpu_freq_text != '' %} ({{ cpu_freq_text }}){% endif %}
|
|
||||||
<b>🧠 RAM сейчас:</b> {{ mem_percent.stdout }}% {{ mem_health }} <i>({{ memory_usage_human.stdout }})</i>{{ mem_delta_str }}
|
|
||||||
<b>💾 Диск /:</b> {{ (disk_used_percent | float) | round(1) }}% {{ disk_health }} <i>({{ disk_usage.stdout.split('Использовано:')[0].strip() }})</i>{{ disk_delta_str }}
|
|
||||||
───────────────
|
|
||||||
<b>🌡️ Температуры</b>
|
|
||||||
<b>CPU:</b> {{ cpu_temp_final | default('недоступно') }}
|
|
||||||
{% if disk_temp_text != "" %}<b>Диск {{ disk_device_name }}:</b> {{ disk_temp_text }} · SMART: {{ smart_health_text }} · realloc: {{ reallocated_text }}{% endif %}
|
|
||||||
───────────────
|
|
||||||
<b>🛠️ Обслуживание</b>
|
|
||||||
<b>📦 Обновлений:</b> {{ upgradable_count.stdout }} (🔴 security: {{ security_count.stdout }})
|
|
||||||
<b>♻️ Требуется reboot:</b> {{ 'ДА ⚠️' if reboot_required.stat.exists else 'нет' }}
|
|
||||||
<b>🔁 Перезагрузок за неделю:</b> {{ reboot_count.stdout }}
|
|
||||||
<b>🚨 Failed units:</b> {{ failed_units.stdout }}
|
|
||||||
───────────────
|
|
||||||
<b>🔐 Безопасность и ошибки за неделю</b>
|
|
||||||
<b>🔓 Неудачных SSH-логинов:</b> {{ failed_ssh.stdout }}
|
|
||||||
<b>💥 OOM-kills:</b> {{ oom_kills.stdout }}
|
|
||||||
<b>🗄️ I/O errors:</b> {{ io_errors.stdout }}
|
|
||||||
───────────────
|
|
||||||
<b>🔥 Топ-5 процессов по памяти (сейчас):</b>
|
|
||||||
<pre>{{ top_procs_joined }}</pre>
|
|
||||||
<b>🕰️ Топ-5 долгоживущих процессов (> 7 дней):</b>
|
|
||||||
<pre>{{ top_long_joined }}</pre>
|
|
||||||
|
|
||||||
# ==================================================================
|
|
||||||
# 7. СОХРАНЯЕМ СНАПШОТ
|
|
||||||
# ==================================================================
|
|
||||||
- name: Save snapshot for next week
|
|
||||||
ansible.builtin.copy:
|
|
||||||
content: |
|
|
||||||
{{
|
|
||||||
{
|
|
||||||
'ts': ansible_date_time.iso8601,
|
|
||||||
'disk_used_pct': disk_used_percent | float,
|
|
||||||
'mem_pct': mem_percent.stdout | float,
|
|
||||||
'cpu_now': cpu_usage | float,
|
|
||||||
'cpu_week_avg': cpu_week_avg,
|
|
||||||
'cpu_week_peak': cpu_week_peak
|
|
||||||
} | to_nice_json
|
|
||||||
}}
|
|
||||||
dest: "{{ snapshot_dir }}/{{ inventory_hostname }}.json"
|
|
||||||
mode: "0644"
|
|
||||||
become: true
|
|
||||||
|
|
||||||
# ==================================================================
|
|
||||||
# 8. ОТПРАВКА В TELEGRAM через SOCKS5
|
|
||||||
# Тело запроса пишем в tmpfile — никаких проблем с кавычками
|
|
||||||
# ==================================================================
|
|
||||||
- name: Check Telegram API reachability via SOCKS5 proxy
|
|
||||||
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 if Telegram API unreachable
|
|
||||||
ansible.builtin.fail:
|
|
||||||
msg: >
|
|
||||||
Telegram API недоступен через прокси {{ telegram_proxy }}.
|
|
||||||
HTTP-код: {{ tg_check.stdout }}.
|
|
||||||
when: tg_check.stdout != "200"
|
|
||||||
delegate_to: localhost
|
|
||||||
become: false
|
|
||||||
|
|
||||||
- name: Send report to Telegram (via tmpfile — safe from quoting issues)
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
set -e
|
|
||||||
tmp=$(mktemp)
|
|
||||||
trap 'rm -f "$tmp"' EXIT
|
|
||||||
|
|
||||||
cat > "$tmp" <<'JSON_EOF'
|
|
||||||
{{ {'chat_id': telegram_chat_id, 'text': message_html, 'parse_mode': 'HTML', 'disable_web_page_preview': true} | to_json }}
|
|
||||||
JSON_EOF
|
|
||||||
|
|
||||||
response=$(curl -s -w "\n%{http_code}" -X POST \
|
|
||||||
--proxy "{{ telegram_proxy }}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
--data-binary @"$tmp" \
|
|
||||||
"https://api.telegram.org/bot{{ telegram_token }}/sendMessage")
|
|
||||||
|
|
||||||
http_code=$(echo "$response" | tail -n1)
|
|
||||||
body=$(echo "$response" | sed '$d')
|
|
||||||
echo "HTTP code: $http_code"
|
|
||||||
echo "Body: $body"
|
|
||||||
if [ "$http_code" -eq 200 ] && echo "$body" | grep -q '"ok":true'; then
|
|
||||||
echo "OK"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
echo "ERROR: send failed"
|
|
||||||
exit 1
|
|
||||||
changed_when: false
|
|
||||||
register: tg_response
|
|
||||||
failed_when: tg_response.rc != 0
|
|
||||||
delegate_to: localhost
|
|
||||||
become: false
|
|
||||||
Reference in New Issue
Block a user