#!/bin/bash # Gather system information from a remote server # Output format: [section] headers followed by key=value pairs # Ensure sbin paths are available (not always in PATH for non-root users) export PATH="$PATH:/usr/sbin:/usr/local/sbin:/sbin" echo "[system]" echo "hostname=$(hostname)" if [ -f /etc/os-release ]; then . /etc/os-release echo "os_name=$NAME" echo "os_version=$VERSION_ID" echo "os_pretty=$PRETTY_NAME" fi echo "kernel=$(uname -r)" echo "arch=$(uname -m)" echo "uptime_seconds=$(cut -d' ' -f1 /proc/uptime 2>/dev/null | cut -d. -f1)" # Detect hypervisor platform if [ -f /etc/pve/.version ] || command -v pveversion &>/dev/null || [ -d /etc/pve ]; then pve_ver=$(pveversion 2>/dev/null | sed 's|pve-manager/||;s| .*||') echo "platform=proxmox" echo "platform_version=${pve_ver:-unknown}" fi # Motherboard (readable without root on most systems) echo "board_vendor=$(cat /sys/class/dmi/id/board_vendor 2>/dev/null || echo 'Unknown')" echo "board_name=$(cat /sys/class/dmi/id/board_name 2>/dev/null || echo 'Unknown')" echo "board_version=$(cat /sys/class/dmi/id/board_version 2>/dev/null || echo 'Unknown')" echo "bios_version=$(cat /sys/class/dmi/id/bios_version 2>/dev/null || echo 'Unknown')" echo "bios_date=$(cat /sys/class/dmi/id/bios_date 2>/dev/null || echo 'Unknown')" echo "[cpu]" echo "model=$(lscpu 2>/dev/null | grep 'Model name' | sed 's/Model name:[[:space:]]*//')" echo "cores=$(nproc 2>/dev/null || echo 0)" echo "sockets=$(lscpu 2>/dev/null | grep 'Socket(s)' | awk '{print $2}')" echo "threads_per_core=$(lscpu 2>/dev/null | grep 'Thread(s) per core' | awk '{print $2}')" # CPU usage - sample /proc/stat with 1 second interval read -r label user1 nice1 system1 idle1 iowait1 irq1 softirq1 steal1 _ < /proc/stat sleep 1 read -r label user2 nice2 system2 idle2 iowait2 irq2 softirq2 steal2 _ < /proc/stat total1=$((user1 + nice1 + system1 + idle1 + iowait1 + irq1 + softirq1 + steal1)) total2=$((user2 + nice2 + system2 + idle2 + iowait2 + irq2 + softirq2 + steal2)) diff_total=$((total2 - total1)) diff_idle=$((idle2 - idle1)) if [ $diff_total -gt 0 ]; then usage_x10=$(( (diff_total - diff_idle) * 1000 / diff_total )) whole=$((usage_x10 / 10)) frac=$((usage_x10 % 10)) echo "usage_percent=${whole}.${frac}" else echo "usage_percent=0.0" fi # Temperatures - try sensors (lm-sensors), fall back to thermal zones echo "[temperatures]" if command -v sensors &>/dev/null; then sensors 2>/dev/null | awk -F'[: ]+' ' /^[^ ]/ { chip=$1 } /°C/ { label=$1 gsub(/^ +| +$/, "", label) match($0, /[+-]([0-9]+\.[0-9]+)°C/, m) if (m[1]+0 > 0) print chip "/" label "=" m[1] } ' else for tz in /sys/class/thermal/thermal_zone*; do [ -f "$tz/temp" ] || continue type=$(cat "$tz/type" 2>/dev/null || echo "unknown") temp_mc=$(cat "$tz/temp" 2>/dev/null || echo 0) temp_c=$((temp_mc / 1000)) temp_frac=$(( (temp_mc % 1000) / 100 )) [ "$temp_c" -gt 0 ] 2>/dev/null && echo "${type}=${temp_c}.${temp_frac}" done fi echo "[memory]" total_kb=$(grep MemTotal /proc/meminfo 2>/dev/null | awk '{print $2}') available_kb=$(grep MemAvailable /proc/meminfo 2>/dev/null | awk '{print $2}') if [ -n "$total_kb" ] && [ -n "$available_kb" ]; then used_kb=$((total_kb - available_kb)) total_mb=$((total_kb / 1024)) used_mb=$((used_kb / 1024)) available_mb=$((available_kb / 1024)) if [ "$total_kb" -gt 0 ]; then usage_x10=$((used_kb * 1000 / total_kb)) whole=$((usage_x10 / 10)) frac=$((usage_x10 % 10)) echo "usage_percent=${whole}.${frac}" else echo "usage_percent=0.0" fi echo "total_mb=$total_mb" echo "used_mb=$used_mb" echo "available_mb=$available_mb" fi # Physical disks lsblk -b -n -o NAME,SIZE,TYPE 2>/dev/null | while read -r name size type; do if [ "$type" = "disk" ]; then echo "[disk:$name]" echo "name=$name" echo "size_bytes=$size" fi done # Mounted filesystem usage df -B1 --output=target,size,used,avail,pcent 2>/dev/null | tail -n +2 | while read -r mount total used avail percent; do case "$mount" in /|/home|/var|/tmp|/boot|/data*|/mnt*|/srv*|/opt*) safename=$(echo "$mount" | tr '/' '_') echo "[disk_usage:${safename}]" echo "mount=$mount" echo "total_bytes=$total" echo "used_bytes=$used" echo "available_bytes=$avail" echo "usage_percent=${percent%\%}" ;; esac done # GPUs gpu_idx=0 while read -r line; do echo "[gpu:$gpu_idx]" echo "description=$line" gpu_idx=$((gpu_idx + 1)) done < <(lspci 2>/dev/null | grep -iE 'vga|3d|display') # Network interfaces for iface in $(ls /sys/class/net/ 2>/dev/null); do [ "$iface" = "lo" ] && continue echo "[net:$iface]" echo "name=$iface" echo "mac=$(cat /sys/class/net/$iface/address 2>/dev/null)" echo "ipv4=$(ip -4 addr show "$iface" 2>/dev/null | grep -oP 'inet \K[0-9.]+' | head -1)" echo "ipv6=$(ip -6 addr show "$iface" 2>/dev/null | grep -oP 'inet6 \K[0-9a-f:]+' | grep -v '^fe80' | head -1)" echo "state=$(cat /sys/class/net/$iface/operstate 2>/dev/null)" speed=$(cat /sys/class/net/$iface/speed 2>/dev/null) [ -n "$speed" ] && [ "$speed" != "-1" ] && echo "speed_mbps=$speed" driver=$(readlink /sys/class/net/$iface/device/driver 2>/dev/null | xargs basename 2>/dev/null) [ -n "$driver" ] && echo "driver=$driver" done echo "[routing]" default_line=$(ip route 2>/dev/null | grep default | head -1) echo "gateway=$(echo "$default_line" | awk '{print $3}')" echo "interface=$(echo "$default_line" | awk '{print $5}')" echo "[dns]" grep -E '^nameserver' /etc/resolv.conf 2>/dev/null | awk '{print "server=" $2}' echo "[tailscale]" if command -v tailscale &>/dev/null; then echo "installed=true" echo "ipv4=$(tailscale ip -4 2>/dev/null)" echo "hostname=$(tailscale status --self --json 2>/dev/null | grep -o '"DNSName":"[^"]*"' | head -1 | cut -d'"' -f4)" else echo "installed=false" fi # --- Container / VM autodiscovery --- # Helper: collect basic stats by execing into a container # Usage: gather_container_stats # e.g. gather_container_stats "pct exec 100 --" gather_container_stats() { local exec_cmd="$1" # Memory local mem_total mem_avail mem_used mem_pct mem_total=$($exec_cmd cat /proc/meminfo 2>/dev/null | awk '/MemTotal/{print int($2/1024)}') mem_avail=$($exec_cmd cat /proc/meminfo 2>/dev/null | awk '/MemAvailable/{print int($2/1024)}') if [ -n "$mem_total" ] && [ -n "$mem_avail" ] && [ "$mem_total" -gt 0 ] 2>/dev/null; then mem_used=$((mem_total - mem_avail)) mem_pct=$((mem_used * 100 / mem_total)) echo "mem_total_mb=$mem_total" echo "mem_used_mb=$mem_used" echo "mem_percent=$mem_pct" fi # Disk (rootfs) - merge wrapped lines, find the / mount local disk_info disk_info=$($exec_cmd df -B1 / 2>/dev/null | awk 'NR>1{line=line $0" "} END{print line}') if [ -n "$disk_info" ]; then # Extract numbers - find the fields: total used avail percent local d_total d_used d_pct d_total=$(echo "$disk_info" | awk '{for(i=1;i<=NF;i++) if($i+0>0 && $i !~ /%/){print $i; exit}}') d_used=$(echo "$disk_info" | awk '{for(i=1;i<=NF;i++) if($i+0>0 && $i !~ /%/){n++; if(n==2){print $i; exit}}}') d_pct=$(echo "$disk_info" | grep -oE '[0-9]+%' | head -1 | tr -d '%') [ -n "$d_total" ] && echo "disk_total=$d_total" [ -n "$d_used" ] && echo "disk_used=$d_used" [ -n "$d_pct" ] && echo "disk_percent=$d_pct" fi # IP local ip ip=$($exec_cmd hostname -I 2>/dev/null | awk '{print $1}') [ -n "$ip" ] && echo "ip=$ip" # OS local os_pretty os_pretty=$($exec_cmd cat /etc/os-release 2>/dev/null | grep PRETTY_NAME | cut -d= -f2 | tr -d '"') [ -n "$os_pretty" ] && echo "os=$os_pretty" # Uptime local uptime_s uptime_s=$($exec_cmd cut -d' ' -f1 /proc/uptime 2>/dev/null | cut -d. -f1) [ -n "$uptime_s" ] && echo "uptime_seconds=$uptime_s" } # Use sudo if available and needed (infmap user won't have direct access) _sudo() { if [ "$(id -u)" -eq 0 ]; then "$@" else sudo -n "$@" 2>/dev/null fi } # Proxmox LXC (pct) if command -v pct &>/dev/null; then _sudo pct list 2>/dev/null | tail -n +2 | while read -r line; do [ -z "$line" ] && continue vmid=$(echo "$line" | awk '{print $1}') status=$(echo "$line" | awk '{print $2}') name=$(echo "$line" | awk '{print $NF}') echo "[container:pct-${vmid}]" echo "type=lxc" echo "platform=proxmox" echo "id=$vmid" echo "name=${name:-$vmid}" echo "status=$status" if [ "$status" = "running" ]; then gather_container_stats "_sudo pct exec $vmid --" fi done fi # Proxmox VMs (qm) if command -v qm &>/dev/null; then _sudo qm list 2>/dev/null | tail -n +2 | while read -r vmid name status _ mem _; do [ -z "$vmid" ] && continue echo "[container:qm-${vmid}]" echo "type=vm" echo "platform=proxmox" echo "id=$vmid" echo "name=${name:-$vmid}" echo "status=$status" [ -n "$mem" ] && echo "mem_allocated_mb=$mem" # VM stats require guest agent - best effort if [ "$status" = "running" ]; then agent_test=$(_sudo qm guest exec "$vmid" -- cat /proc/meminfo 2>/dev/null) if [ -n "$agent_test" ]; then gather_container_stats "_sudo qm guest exec $vmid --" fi fi done fi # Plain LXC (lxc/lxd) if command -v lxc &>/dev/null && ! command -v pct &>/dev/null; then _sudo lxc list --format csv -c nsN 2>/dev/null | while IFS=',' read -r name status network; do [ -z "$name" ] && continue echo "[container:lxc-${name}]" echo "type=lxc" echo "platform=lxd" echo "name=$name" echo "status=$status" if [ "$status" = "RUNNING" ]; then gather_container_stats "_sudo lxc exec $name --" lxd_ip=$(echo "$network" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | head -1) [ -n "$lxd_ip" ] && echo "ip=$lxd_ip" fi done fi # libvirt VMs (virsh) if command -v virsh &>/dev/null; then _sudo virsh list --all --name 2>/dev/null | while read -r name; do [ -z "$name" ] && continue state=$(_sudo virsh domstate "$name" 2>/dev/null | head -1) echo "[container:virsh-${name}]" echo "type=vm" echo "platform=libvirt" echo "name=$name" echo "status=$state" if [ "$state" = "running" ]; then virsh_ip=$(_sudo virsh domifaddr "$name" --source agent 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | head -1) [ -n "$virsh_ip" ] && echo "ip=$virsh_ip" fi done fi # Docker containers if command -v docker &>/dev/null; then _sudo docker ps -a --format '{{.Names}}\t{{.State}}\t{{.Image}}\t{{.Status}}' 2>/dev/null | while IFS=$'\t' read -r name state image status_text; do [ -z "$name" ] && continue echo "[container:docker-${name}]" echo "type=docker" echo "platform=docker" echo "name=$name" echo "status=$state" echo "image=$image" echo "status_text=$status_text" if [ "$state" = "running" ]; then # Get IP from docker inspect docker_ip=$(_sudo docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$name" 2>/dev/null) [ -n "$docker_ip" ] && echo "ip=$docker_ip" # Get memory/cpu stats (one-shot, no stream) stats=$(_sudo docker stats --no-stream --format '{{.MemUsage}}\t{{.MemPerc}}\t{{.CPUPerc}}' "$name" 2>/dev/null) if [ -n "$stats" ]; then mem_pct=$(echo "$stats" | cut -f2 | tr -d '%') cpu_pct=$(echo "$stats" | cut -f3 | tr -d '%') echo "mem_percent=${mem_pct}" echo "cpu_percent=${cpu_pct}" fi fi done fi echo "[end]"