#!/bin/bash set -uo pipefail # hostinfo.sh - Gather hardware/system info from a remote host # # Output: JSON object with system information # -- Helper to escape JSON strings -- json_escape() { local str="$1" str="${str//\\/\\\\}" str="${str//\"/\\\"}" str="${str//$'\n'/\\n}" str="${str//$'\r'/\\r}" str="${str//$'\t'/\\t}" echo -n "$str" } # -- Gather system info -- HOSTNAME=$(hostname 2>/dev/null || echo "Unknown") CPU=$(grep -m1 'model name' /proc/cpuinfo 2>/dev/null | cut -d: -f2 | xargs || echo "Unknown") CPU_CORES=$(nproc 2>/dev/null || grep -c '^processor' /proc/cpuinfo 2>/dev/null || echo "0") MOTHERBOARD=$(cat /sys/devices/virtual/dmi/id/board_name 2>/dev/null || echo "Unknown") # RAM from free -m (used = total - available) RAM_TOTAL_MB=$(free -m 2>/dev/null | awk 'NR==2{print $2}' || echo "0") RAM_AVAIL_MB=$(free -m 2>/dev/null | awk 'NR==2{print $7}' || echo "0") RAM_USED_MB=$(( RAM_TOTAL_MB - RAM_AVAIL_MB )) # Disk root (strip trailing G from df -BG output) DISK_ROOT_USED_GB=$(df -BG / 2>/dev/null | awk 'NR==2{gsub(/G/,"",$3); print $3}' || echo "0") DISK_ROOT_TOTAL_GB=$(df -BG / 2>/dev/null | awk 'NR==2{gsub(/G/,"",$2); print $2}' || echo "0") # Disk /tank (only if mounted) DISK_TANK_USED_GB="" DISK_TANK_TOTAL_GB="" if [ -d /tank ] && mountpoint -q /tank 2>/dev/null; then DISK_TANK_USED_GB=$(df -BG /tank 2>/dev/null | awk 'NR==2{gsub(/G/,"",$3); print $3}' || echo "") DISK_TANK_TOTAL_GB=$(df -BG /tank 2>/dev/null | awk 'NR==2{gsub(/G/,"",$2); print $2}' || echo "") fi # IP addresses IP_LOCAL=$(hostname -I 2>/dev/null | awk '{print $1}' || echo "Unknown") IP_TAILSCALE="" if command -v tailscale &>/dev/null; then IP_TAILSCALE=$(tailscale ip -4 2>/dev/null || echo "") fi IP_PUBLIC="" if command -v curl &>/dev/null; then IP_PUBLIC=$(curl -s --max-time 3 ifconfig.me 2>/dev/null || echo "") elif command -v wget &>/dev/null; then IP_PUBLIC=$(wget -qO- --timeout=3 ifconfig.me 2>/dev/null || echo "") fi # OS info OS=$(grep PRETTY_NAME /etc/os-release 2>/dev/null | cut -d= -f2 | tr -d '"' || uname -o 2>/dev/null || echo "Unknown") KERNEL=$(uname -r 2>/dev/null || echo "Unknown") UPTIME=$(uptime -p 2>/dev/null || uptime 2>/dev/null || echo "Unknown") # Docker DOCKER_VERSION="" if command -v docker &>/dev/null; then DOCKER_VERSION=$(docker --version 2>/dev/null | awk '{print $3}' | tr -d ',' || echo "") fi # GPU list (may have 0, 1, or multiple) GPU_JSON="[]" if command -v lspci &>/dev/null; then GPU_LINES=$(lspci 2>/dev/null | grep -i 'vga\|3d\|display' | sed 's/^[^ ]* //; s/VGA compatible controller: //; s/3D controller: //; s/Display controller: //' || true) if [ -n "$GPU_LINES" ]; then GPU_JSON="[" first=true while IFS= read -r line; do [ -z "$line" ] && continue if [ "$first" = "true" ]; then first=false else GPU_JSON+="," fi GPU_JSON+="\"" GPU_JSON+=$(json_escape "$line") GPU_JSON+="\"" done <<< "$GPU_LINES" GPU_JSON+="]" fi fi # -- Output JSON -- echo -n '{' echo -n '"hostname":"'; json_escape "$HOSTNAME"; echo -n '"' echo -n ',"cpu":"'; json_escape "$CPU"; echo -n '"' echo -n ',"cpu_cores":"'; json_escape "$CPU_CORES"; echo -n '"' echo -n ',"motherboard":"'; json_escape "$MOTHERBOARD"; echo -n '"' echo -n ',"gpu":'"$GPU_JSON" echo -n ',"ram_used_mb":"'; json_escape "$RAM_USED_MB"; echo -n '"' echo -n ',"ram_total_mb":"'; json_escape "$RAM_TOTAL_MB"; echo -n '"' echo -n ',"disk_root_used_gb":"'; json_escape "$DISK_ROOT_USED_GB"; echo -n '"' echo -n ',"disk_root_total_gb":"'; json_escape "$DISK_ROOT_TOTAL_GB"; echo -n '"' echo -n ',"disk_tank_used_gb":"'; json_escape "$DISK_TANK_USED_GB"; echo -n '"' echo -n ',"disk_tank_total_gb":"'; json_escape "$DISK_TANK_TOTAL_GB"; echo -n '"' echo -n ',"ip_local":"'; json_escape "$IP_LOCAL"; echo -n '"' echo -n ',"ip_tailscale":"'; json_escape "$IP_TAILSCALE"; echo -n '"' echo -n ',"ip_public":"'; json_escape "$IP_PUBLIC"; echo -n '"' echo -n ',"os":"'; json_escape "$OS"; echo -n '"' echo -n ',"kernel":"'; json_escape "$KERNEL"; echo -n '"' echo -n ',"uptime":"'; json_escape "$UPTIME"; echo -n '"' echo -n ',"docker_version":"'; json_escape "$DOCKER_VERSION"; echo -n '"' echo '}'