--- # Builtin Playbook: Collecte d'informations système complètes # Ce playbook collecte CPU, mémoire, disque, OS et réseau en une seule exécution # Les résultats sont formatés en JSON pour être parsés par l'application - name: Collect Complete System Information hosts: all become: false gather_facts: true vars: _builtin_playbook: true _builtin_id: collect_system_info _collect_metrics: true tasks: - name: Gather additional facts ansible.builtin.setup: gather_subset: - hardware - network - virtual - name: Get CPU load averages ansible.builtin.shell: cat /proc/loadavg | awk '{print $1, $2, $3}' register: cpu_load changed_when: false ignore_errors: true - name: Parse CPU load parts safely ansible.builtin.set_fact: cpu_load_parts: "{{ cpu_load.stdout.split() if (cpu_load is defined and cpu_load.stdout is defined) else [] }}" - name: Get CPU temperature (if available) ansible.builtin.shell: | temp="" # 1) Essayer les capteurs standard du noyau (/sys/class/thermal) for z in /sys/class/thermal/thermal_zone*/temp; do if [ -f "$z" ]; then raw=$(cat "$z" 2>/dev/null || echo "") if [ -n "$raw" ]; then temp=$(awk -v v="$raw" 'BEGIN { if (v != "" && v != "0") printf "%.1f", v/1000; }') if [ -n "$temp" ]; then break fi fi fi done # 2) Spécifique Raspberry Pi : vcgencmd measure_temp if [ -z "$temp" ] && command -v vcgencmd >/dev/null 2>&1; then raw=$(vcgencmd measure_temp 2>/dev/null | sed 's/[^0-9\.]*//g') if [ -n "$raw" ]; then temp=$raw fi fi # 3) Utiliser lm-sensors si disponible if [ -z "$temp" ] && command -v sensors >/dev/null 2>&1; then raw=$(sensors 2>/dev/null | awk '/^Package id 0:|^Tctl:|^CPU Temp:|^temp1:/{gsub("+","",$2); gsub("°C","",$2); print $2; exit}') if [ -n "$raw" ]; then temp=$raw fi fi if [ -z "$temp" ]; then echo "null" else printf "%.1f" "$temp" fi register: cpu_temp changed_when: false ignore_errors: true - name: Get memory info from /proc/meminfo (Linux) ansible.builtin.shell: | awk '/MemTotal/{total=$2} /MemFree/{free=$2} /MemAvailable/{avail=$2} /Buffers/{buf=$2} /^Cached/{cache=$2} /SwapTotal/{stotal=$2} /SwapFree/{sfree=$2} END{ used=total-avail; usage=used/total*100; sused=stotal-sfree; susage=(stotal>0)?sused/stotal*100:0; printf "{\"total_mb\":%d,\"used_mb\":%d,\"free_mb\":%d,\"usage_percent\":%.1f,\"swap_total_mb\":%d,\"swap_used_mb\":%d,\"swap_usage_percent\":%.1f}", total/1024, used/1024, avail/1024, usage, stotal/1024, sused/1024, susage }' /proc/meminfo 2>/dev/null || echo '' register: memory_info_linux changed_when: false ignore_errors: true - name: Get memory info using sysctl (FreeBSD/TrueNAS) ansible.builtin.shell: | total=$(sysctl -n hw.physmem 2>/dev/null || echo 0) free=$(sysctl -n vm.stats.vm.v_free_count 2>/dev/null || echo 0) pagesize=$(sysctl -n hw.pagesize 2>/dev/null || echo 4096) swap_total=$(swapinfo -k 2>/dev/null | awk 'NR==2 {print $2}' || echo 0) swap_used=$(swapinfo -k 2>/dev/null | awk 'NR==2 {print $3}' || echo 0) total_mb=$((total / 1024 / 1024)) free_mb=$((free * pagesize / 1024 / 1024)) used_mb=$((total_mb - free_mb)) usage=$(awk -v used=$used_mb -v total=$total_mb 'BEGIN {if(total>0) printf "%.1f", used/total*100; else print "0.0"}') swap_total_mb=$((swap_total / 1024)) swap_used_mb=$((swap_used / 1024)) swap_usage=$(awk -v used=$swap_used_mb -v total=$swap_total_mb 'BEGIN {if(total>0) printf "%.1f", used/total*100; else print "0.0"}') printf '{"total_mb":%d,"used_mb":%d,"free_mb":%d,"usage_percent":%s,"swap_total_mb":%d,"swap_used_mb":%d,"swap_usage_percent":%s}' \ $total_mb $used_mb $free_mb $usage $swap_total_mb $swap_used_mb $swap_usage register: memory_info_bsd changed_when: false ignore_errors: true when: memory_info_linux.stdout == '' - name: Set memory info variable ansible.builtin.set_fact: memory_info_json: "{{ memory_info_linux.stdout if memory_info_linux.stdout != '' else (memory_info_bsd.stdout | default('{\"total_mb\":0,\"used_mb\":0,\"free_mb\":0,\"usage_percent\":0.0,\"swap_total_mb\":0,\"swap_used_mb\":0,\"swap_usage_percent\":0.0}')) }}" - name: Parse memory info JSON ansible.builtin.set_fact: memory_info_parsed: "{{ memory_info_json if (memory_info_json is mapping) else (memory_info_json | from_json) }}" - name: Get disk usage for all mount points ansible.builtin.shell: | if df --help 2>/dev/null | grep -q -- '--output'; then df -BG --output=source,fstype,target,size,used,avail,pcent -x tmpfs -x devtmpfs -x squashfs 2>/dev/null | tail -n +2 | awk '{ gsub("G",""); gsub("%",""); dev=$1; fs=$2; mnt=$3; gsub(/\\/,"\\\\",dev); gsub(/\"/,"\\\"",dev); gsub(/\\/,"\\\\",fs); gsub(/\"/,"\\\"",fs); gsub(/\\/,"\\\\",mnt); gsub(/\"/,"\\\"",mnt); printf "{\"device\":\"%s\",\"filesystem\":\"%s\",\"mount\":\"%s\",\"total_gb\":%s,\"used_gb\":%s,\"free_gb\":%s,\"usage_percent\":%s}\n", dev, fs, mnt, $4, $5, $6, $7 }' | paste -sd "," | awk '{print "["$0"]"}' else echo "[]" fi register: disk_info changed_when: false ignore_errors: true - name: Parse disk usage JSON safely block: - name: Parse disk usage JSON ansible.builtin.set_fact: disk_info_parsed: "{{ ((disk_info.stdout | default('[]', true) | trim) if ((disk_info.stdout | default('', true) | trim) | regex_search('^\\[')) else '[]') | from_json }}" rescue: - name: Fallback disk_info_parsed ansible.builtin.set_fact: disk_info_parsed: [] - name: Get disk devices layout (lsblk JSON if available) ansible.builtin.shell: | if command -v lsblk >/dev/null 2>&1; then tmp="/tmp/lsblk_${$}.json" if lsblk -J -b -o NAME,TYPE,SIZE,FSTYPE,MOUNTPOINT,MODEL,SERIAL,UUID >"$tmp" 2>/dev/null; then cat "$tmp" else echo '{"blockdevices":[]}' fi rm -f "$tmp" >/dev/null 2>&1 || true else echo '{"blockdevices":[]}' fi register: disk_devices changed_when: false ignore_errors: true - name: Parse disk devices JSON safely block: - name: Parse disk devices JSON ansible.builtin.set_fact: disk_devices_parsed: "{{ ((((disk_devices.stdout | default('{\"blockdevices\":[]}', true) | trim) if ((disk_devices.stdout | default('', true) | trim) | regex_search('^\\{')) else '{\"blockdevices\":[]}') | from_json).blockdevices) | default([], true) }}" rescue: - name: Fallback disk_devices_parsed ansible.builtin.set_fact: disk_devices_parsed: [] - name: Get LVM info (pvs/vgs/lvs) as JSON (best-effort) ansible.builtin.shell: | if command -v pvs >/dev/null 2>&1 && command -v vgs >/dev/null 2>&1 && command -v lvs >/dev/null 2>&1; then pvs --reportformat json 2>/dev/null || echo '{"report":[]}' echo '---' vgs --reportformat json 2>/dev/null || echo '{"report":[]}' echo '---' lvs --reportformat json 2>/dev/null || echo '{"report":[]}' else echo '{"report":[]}' echo '---' echo '{"report":[]}' echo '---' echo '{"report":[]}' fi register: lvm_info changed_when: false ignore_errors: true - name: Parse LVM JSON safely block: - name: Parse LVM JSON ansible.builtin.set_fact: _lvm_chunks: "{{ (lvm_info.stdout | default('{\"report\":[]}\n---\n{\"report\":[]}\n---\n{\"report\":[]}', true)).split('---') }}" lvm_info_parsed: pvs: "{{ (_lvm_chunks[0] | trim | from_json).report[0].pv | default([], true) if (_lvm_chunks | length) > 0 else [] }}" vgs: "{{ (_lvm_chunks[1] | trim | from_json).report[0].vg | default([], true) if (_lvm_chunks | length) > 1 else [] }}" lvs: "{{ (_lvm_chunks[2] | trim | from_json).report[0].lv | default([], true) if (_lvm_chunks | length) > 2 else [] }}" rescue: - name: Fallback lvm_info_parsed ansible.builtin.set_fact: lvm_info_parsed: pvs: [] vgs: [] lvs: [] - name: Get ZFS pools info as JSON (best-effort) ansible.builtin.shell: | if command -v zpool >/dev/null 2>&1; then zpool list -H -o name,size,alloc,free,cap 2>/dev/null | python3 -c 'import sys,json; rows=[]; [rows.append({"name":p[0],"size":p[1],"alloc":p[2],"free":p[3],"cap":p[4]}) for p in (l.strip().split() for l in sys.stdin) if len(p)>=5]; print(json.dumps(rows))' 2>/dev/null || echo '[]' else echo '[]' fi register: zfs_pools changed_when: false ignore_errors: true - name: Get ZFS datasets info as JSON (best-effort) ansible.builtin.shell: | if command -v zfs >/dev/null 2>&1; then zfs list -H -o name,used,avail,refer,mountpoint 2>/dev/null | python3 -c 'import sys,json; rows=[]; [rows.append({"name":p[0],"used":p[1],"avail":p[2],"refer":p[3],"mountpoint":p[4]}) for p in ((line.rstrip("\\n").split("\\t") if "\\t" in line else line.strip().split()) for line in sys.stdin) if len(p)>=5]; print(json.dumps(rows))' 2>/dev/null || echo '[]' else echo '[]' fi register: zfs_datasets changed_when: false ignore_errors: true - name: Parse ZFS JSON safely block: - name: Parse ZFS JSON ansible.builtin.set_fact: zfs_info_parsed: pools: "{{ ((zfs_pools.stdout | default('[]', true) | trim) if ((zfs_pools.stdout | default('', true) | trim) | regex_search('^\\[')) else '[]') | from_json }}" datasets: "{{ ((zfs_datasets.stdout | default('[]', true) | trim) if ((zfs_datasets.stdout | default('', true) | trim) | regex_search('^\\[')) else '[]') | from_json }}" rescue: - name: Fallback zfs_info_parsed ansible.builtin.set_fact: zfs_info_parsed: pools: [] datasets: [] - name: Get root partition info specifically ansible.builtin.shell: | if df -BG / >/dev/null 2>&1; then df -BG / 2>/dev/null | tail -1 | awk '{gsub("G",""); gsub("%",""); printf "{\"total_gb\":%s,\"used_gb\":%s,\"usage_percent\":%s}", $2, $3, $5}' elif df -kP / >/dev/null 2>&1; then df -kP / 2>/dev/null | tail -1 | awk '{total_gb=$2/1024/1024; used_gb=$3/1024/1024; gsub("%",""); printf "{\"total_gb\":%.1f,\"used_gb\":%.1f,\"usage_percent\":%s}", total_gb, used_gb, $5}' else echo '{"total_gb":0,"used_gb":0,"usage_percent":0}' fi register: disk_root changed_when: false ignore_errors: true - name: Parse root partition JSON safely block: - name: Parse root partition JSON ansible.builtin.set_fact: disk_root_parsed: "{{ ((disk_root.stdout | default('{\"total_gb\":0,\"used_gb\":0,\"usage_percent\":0}', true) | trim) if ((disk_root.stdout | default('', true) | trim) | regex_search('^\\{')) else '{\"total_gb\":0,\"used_gb\":0,\"usage_percent\":0}') | from_json }}" rescue: - name: Fallback disk_root_parsed ansible.builtin.set_fact: disk_root_parsed: total_gb: 0 used_gb: 0 usage_percent: 0 - name: Get CPU current frequency in MHz (best-effort) ansible.builtin.shell: | (awk -F: '/cpu MHz/{gsub(/ /, "", $2); print $2; exit}' /proc/cpuinfo 2>/dev/null || echo '') register: cpu_mhz changed_when: false ignore_errors: true - name: Get CPU max frequency in MHz (best-effort) ansible.builtin.shell: | if [ -f /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq ]; then awk '{printf "%.1f", $1/1000}' /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq 2>/dev/null else echo "" fi register: cpu_max_mhz changed_when: false ignore_errors: true - name: Get CPU min frequency in MHz (best-effort) ansible.builtin.shell: | if [ -f /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq ]; then awk '{printf "%.1f", $1/1000}' /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq 2>/dev/null else echo "" fi register: cpu_min_mhz changed_when: false ignore_errors: true - name: Get system uptime in seconds ansible.builtin.shell: cat /proc/uptime | awk '{print int($1)}' register: uptime_seconds changed_when: false ignore_errors: true - name: Get human-readable uptime ansible.builtin.shell: uptime -p 2>/dev/null || uptime | sed 's/.*up //' | sed 's/,.*//' register: uptime_human changed_when: false ignore_errors: true - name: Get network interfaces info ansible.builtin.shell: | ip -j addr show 2>/dev/null | python3 -c " import sys, json try: data = json.load(sys.stdin) result = [] for iface in data: if iface.get('ifname') not in ['lo']: info = {'name': iface.get('ifname'), 'state': iface.get('operstate', 'unknown')} for addr in iface.get('addr_info', []): if addr.get('family') == 'inet': info['ip_address'] = addr.get('local') if iface.get('address'): info['mac_address'] = iface.get('address') result.append(info) print(json.dumps(result)) except: print('[]') " 2>/dev/null || echo "[]" register: network_info changed_when: false ignore_errors: true - name: Parse network info JSON safely block: - name: Parse network info JSON ansible.builtin.set_fact: network_info_parsed: "{{ ((network_info.stdout | default('[]', true) | trim) if ((network_info.stdout | default('', true) | trim) | regex_search('^\\[')) else '[]') | from_json }}" rescue: - name: Fallback network_info_parsed ansible.builtin.set_fact: network_info_parsed: [] - name: Build metrics JSON output ansible.builtin.set_fact: metrics_output: host: "{{ inventory_hostname }}" data: # CPU info cpu_count: "{{ ansible_processor_vcpus | default(ansible_processor_count, true) | default(1) }}" cpu_model: "{{ ansible_processor[2] | default('Unknown', true) if (ansible_processor is defined and ansible_processor | length > 2) else 'Unknown' }}" cpu_sockets: "{{ ansible_processor_count | default(1, true) | int }}" cpu_threads_per_core: "{{ ansible_processor_threads_per_core | default('', true) }}" cpu_cores: "{{ (ansible_processor_cores | default(0, true) | int) * (ansible_processor_count | default(1, true) | int) if (ansible_processor_cores is defined and ansible_processor_cores | int > 0) else '' }}" cpu_threads: "{{ ansible_processor_vcpus | default('', true) }}" cpu_mhz: "{{ cpu_mhz.stdout | default('', true) }}" cpu_max_mhz: "{{ cpu_max_mhz.stdout | default('', true) }}" cpu_min_mhz: "{{ cpu_min_mhz.stdout | default('', true) }}" cpu_load_1m: "{{ (cpu_load_parts[0] if (cpu_load_parts | length > 0) else '0') | float }}" cpu_load_5m: "{{ (cpu_load_parts[1] if (cpu_load_parts | length > 1) else '0') | float }}" cpu_load_15m: "{{ (cpu_load_parts[2] if (cpu_load_parts | length > 2) else '0') | float }}" cpu_temperature: "{{ cpu_temp.stdout if (cpu_temp.stdout is defined and cpu_temp.stdout != 'null') else '' }}" # Memory info memory_total_mb: "{{ memory_info_parsed.total_mb | default(0, true) | int }}" memory_used_mb: "{{ memory_info_parsed.used_mb | default(0, true) | int }}" memory_free_mb: "{{ memory_info_parsed.free_mb | default(0, true) | int }}" memory_usage_percent: "{{ memory_info_parsed.usage_percent | default(0, true) | float }}" swap_total_mb: "{{ memory_info_parsed.swap_total_mb | default(0, true) | int }}" swap_used_mb: "{{ memory_info_parsed.swap_used_mb | default(0, true) | int }}" swap_usage_percent: "{{ memory_info_parsed.swap_usage_percent | default(0, true) | float }}" # Disk info disk_info: "{{ disk_info_parsed | default([], true) }}" disk_devices: "{{ disk_devices_parsed | default([], true) }}" disk_root_total_gb: "{{ disk_root_parsed.total_gb | default(0, true) | float }}" disk_root_used_gb: "{{ disk_root_parsed.used_gb | default(0, true) | float }}" disk_root_usage_percent: "{{ disk_root_parsed.usage_percent | default(0, true) | float }}" # Storage stacks lvm_info: "{{ lvm_info_parsed | default({'pvs':[], 'vgs':[], 'lvs':[]}, true) }}" zfs_info: "{{ zfs_info_parsed | default({'pools':[], 'datasets':[]}, true) }}" # System info os_name: "{{ ansible_distribution | default('Unknown', true) }}" os_version: "{{ ansible_distribution_version | default('', true) }}" kernel_version: "{{ ansible_kernel | default('', true) }}" hostname: "{{ ansible_hostname | default(inventory_hostname, true) }}" uptime_seconds: "{{ uptime_seconds.stdout | default('0', true) | int }}" uptime_human: "{{ uptime_human.stdout | default('unknown', true) }}" # Network info network_info: "{{ network_info_parsed | default([], true) }}" - name: Output metrics in parseable format ansible.builtin.debug: msg: "METRICS_JSON_START:{{ metrics_output | to_json }}:METRICS_JSON_END"