#!/bin/bash
# Connectivity + host metrics reporter (single file, compact).
# Downloads are served from the CDN mirror (lo01.g77k.com); metrics still
# POST to f.g77k.com. On first run from the old URL the script rewrites the
# caller's crontab to fetch from the mirror, and every cron run sleeps a
# per-host offset + random jitter so POSTs don't spike on the minute.
set -u
A=https://f.g77k.com/j/api/connectivity.php
I=https://w.g77k.com/ip.php

V=0
NOW=0
for a in "$@"; do
  case "$a" in -v|--verbose) V=1 ;; -n|--now) NOW=1 ;; esac
done
v(){ [ "$V" = 1 ] && echo "[report] $*" >&2; return 0; }

# Silence the lockfile-open error only (brace group), don't clobber stderr
# for the rest of the script — v() needs it to report status.
{ exec 9>/tmp/report.sh.lock; } 2>/dev/null || true
command -v flock >/dev/null 2>&1 && { flock -n 9 || { v "another instance running, exiting"; exit 0; }; }

# Resolve hostname with busybox/OpenWRT-friendly fallbacks (no `hostname` applet
# on some builds; try uname -n, /proc, then $HOSTNAME).
H=$(hostname -f 2>/dev/null); [ -n "$H" ] || H=$(hostname 2>/dev/null)
[ -n "$H" ] || H=$(uname -n 2>/dev/null)
[ -n "$H" ] || { [ -r /proc/sys/kernel/hostname ] && H=$(cat /proc/sys/kernel/hostname 2>/dev/null); }
[ -n "$H" ] || H=${HOSTNAME:-unknown}

# One-time crontab migration: fetch this script from the mirror instead of
# f.g77k.com (curl|bash every few minutes was eating origin bandwidth).
# Only the download URL in cron lines changes; $A above still posts here.
# The standard `curl -fsSL <url> | bash` line becomes a mirror-first line
# with f.g77k.com as fallback, so a dead mirror can't strand the fleet.
# Gated on the mirror actually serving the script (HEAD 200): migrating
# before the mirror is populated would break variant cron lines.
# Migrated lines contain lo01, so the /lo01/! guards make this idempotent
# and keep the fallback URL from being rewritten on later runs.
_mir=https://lo01.g77k.com/j/report.sh
_old=https://f.g77k.com/j/report.sh
if command -v crontab >/dev/null 2>&1; then
  _ct=$(crontab -l 2>/dev/null) || _ct=
  if printf '%s\n' "$_ct" | grep 'f\.g77k\.com/j/report\.sh' | grep -qv 'lo01\.g77k\.com' \
     && curl -fsI --connect-timeout 5 --max-time 10 "$_mir" >/dev/null 2>&1; then
    printf '%s\n' "$_ct" \
      | sed -e "/lo01\.g77k\.com/!s#curl -fsSL $_old | bash#(curl -fsSL $_mir || curl -fsSL $_old) | bash#" \
            -e "/lo01\.g77k\.com/!s#https://f\.g77k\.com/j/report\.sh#$_mir#g" \
            -e "/lo01\.g77k\.com/!s#http://f\.g77k\.com/j/report\.sh#$_mir#g" \
      | crontab - 2>/dev/null && v "crontab migrated to $_mir" || v "crontab migration failed"
  fi
fi

# Cron fires every host at the same wall-clock minute; spread the POSTs with
# a stable per-host offset (0-104s, hostname hash) + 0-14s random jitter.
# Worst case stays well inside the dashboard's 11-min online window.
# Skipped when interactive (stdout is a tty), verbose, or -n/--now.
if [ "$V" != 1 ] && [ "$NOW" != 1 ] && [ ! -t 1 ]; then
  _o=$(printf '%s' "$H" | cksum 2>/dev/null | awk '{print $1 % 105}')
  _j=$(od -An -N1 -tu1 /dev/urandom 2>/dev/null | tr -cd '0-9')
  sleep $(( ${_o:-0} + ${_j:-0} % 15 )) 2>/dev/null || true
fi

host google.com >/dev/null 2>&1 || nslookup google.com >/dev/null 2>&1 || true

B=
for _ in 1 2 3 4 5; do
  P=$(curl -s -o /dev/null -w "%{time_total}" --connect-timeout 3 --max-time 5 http://google.com 2>/dev/null) || continue
  [ -n "$P" ] || continue
  M=$(awk "BEGIN{printf \"%.0f\",$P*1000}")
  [ -n "$M" ] && [ "$M" != 0 ] || continue
  { [ -z "$B" ] || [ "$M" -lt "$B" ]; } && B=$M
done
G=${B:--1}

X=$(curl -s --connect-timeout 3 --max-time 10 "$I" 2>/dev/null); X=${X:-unknown}

CC=$(nproc 2>/dev/null)
[ -n "$CC" ] || CC=$(grep -c ^processor /proc/cpuinfo 2>/dev/null)
CM=$(awk -F: '/^model name/{gsub(/^ +/,"",$2);print $2;exit}' /proc/cpuinfo 2>/dev/null)
CH=$(awk -F: '/^cpu MHz/{gsub(/^ +/,"",$2);printf "%.0f",$2;exit}' /proc/cpuinfo 2>/dev/null)

# Per-core CPU usage via /proc/stat delta vs previous snapshot cached on disk.
# First invocation leaves CP empty (no prior sample); subsequent runs yield %.
PCACHE="${HOME:-/tmp}/.cache/report.sh.stat"
CP=
if [ -r /proc/stat ]; then
  NOW=$(awk '/^cpu[0-9]+ /{print}' /proc/stat)
  if [ -n "$NOW" ] && [ -r "$PCACHE" ]; then
    CP=$(awk 'NR==FNR{p[$1]=$0;next}$1 in p{split(p[$1],q);dt=($2+$3+$4+$5+$6+$7+$8+$9)-(q[2]+q[3]+q[4]+q[5]+q[6]+q[7]+q[8]+q[9]);di=($5+$6)-(q[5]+q[6]);u=dt>0?(1-di/dt)*100:0;if(u<0)u=0;if(u>100)u=100;printf (c++?",":"")"%.1f",u}' "$PCACHE" <(printf '%s\n' "$NOW"))
  fi
  mkdir -p "$(dirname "$PCACHE")" 2>/dev/null
  printf '%s\n' "$NOW" >"$PCACHE" 2>/dev/null || true
fi
CPJ=null
[ -n "$CP" ] && CPJ="[$CP]"

L1=; L5=; L15=
[ -r /proc/loadavg ] && read -r L1 L5 L15 _ < /proc/loadavg

T=
for z in /sys/class/thermal/thermal_zone*/temp; do
  [ -r "$z" ] || continue
  v=$(cat "$z" 2>/dev/null) || continue
  [ -n "$v" ] || continue
  c=$(awk "BEGIN{printf \"%.1f\",$v/1000}")
  { [ -z "$T" ] || awk "BEGIN{exit !($c>$T)}"; } && T=$c
done

R(){ awk -v k="$1" '$1==k":"{print $2;exit}' /proc/meminfo 2>/dev/null; }
MT=$(R MemTotal); MA=$(R MemAvailable); ST=$(R SwapTotal); SF=$(R SwapFree)
SU=; [ -n "$ST" ] && [ -n "$SF" ] && SU=$((ST-SF))

DF=$(df -PB1 / 2>/dev/null | awk 'NR==2{print $2" "$3}')
DT=; DU=
read -r DT DU <<<"${DF:-}"

UP=; [ -r /proc/uptime ] && UP=$(awk '{printf "%d",$1}' /proc/uptime)

TC=
if command -v ss >/dev/null 2>&1; then
  TC=$(ss -tan 2>/dev/null | tail -n +2 | wc -l)
elif [ -r /proc/net/sockstat ]; then
  TC=$(awk '/^TCP:/{print $3;exit}' /proc/net/sockstat)
fi
PC=$(ls /proc 2>/dev/null | grep -Ec '^[0-9]+$')

NI=; NR=; NX=
[ -r /proc/net/route ] && NI=$(awk '$2=="00000000"{print $1;exit}' /proc/net/route)
if [ -n "$NI" ] && [ -r "/sys/class/net/$NI/statistics/rx_bytes" ]; then
  NR=$(cat "/sys/class/net/$NI/statistics/rx_bytes" 2>/dev/null)
  NX=$(cat "/sys/class/net/$NI/statistics/tx_bytes" 2>/dev/null)
fi

# LAN IP: source IP the kernel picks for outbound traffic on the default
# route. Three fallbacks — `ip route get`, then `ip addr show <iface>`,
# then `hostname -I` — so busybox / minimal images still report something.
# Loopback and empty values are filtered out.
LIP=
if command -v ip >/dev/null 2>&1; then
  LIP=$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src"){print $(i+1); exit}}')
  if [ -z "$LIP" ] && [ -n "$NI" ]; then
    LIP=$(ip -4 -o addr show dev "$NI" 2>/dev/null | awk '{print $4}' | awk -F/ 'NR==1{print $1}')
  fi
fi
if [ -z "$LIP" ] && command -v hostname >/dev/null 2>&1; then
  LIP=$(hostname -I 2>/dev/null | awk '{print $1}')
fi
case "$LIP" in 127.*|'::1'|'') LIP= ;; esac

KE=$(uname -r 2>/dev/null)
DI=; [ -r /etc/os-release ] && DI=$(awk -F= '$1=="PRETTY_NAME"{gsub(/"/,"",$2);print $2;exit}' /etc/os-release)

# Virtualization type: kvm, lxc, docker, openvz, none (baremetal), etc.
VT=
if command -v systemd-detect-virt >/dev/null 2>&1; then
  VT=$(systemd-detect-virt 2>/dev/null || echo)
elif [ -r /proc/1/environ ] && tr '\0' '\n' < /proc/1/environ 2>/dev/null | grep -q 'container=lxc'; then
  VT=lxc
elif [ -r /sys/class/dmi/id/product_name ]; then
  p=$(cat /sys/class/dmi/id/product_name 2>/dev/null)
  case "$p" in *QEMU*|*KVM*) VT=kvm;; *VirtualBox*) VT=oracle;; *VMware*) VT=vmware;; esac
fi
[ -z "$VT" ] && VT=none

# CPU architecture: x86_64, aarch64, armv7l, etc.
ARCH=$(uname -m 2>/dev/null)

# CUDA GPU info via nvidia-smi (null when not present).
# Collects per-GPU: model, power draw (W), temp (°C), mem used/total (MiB).
GPUJ=null
if command -v nvidia-smi >/dev/null 2>&1; then
  _gpu=$(nvidia-smi --query-gpu=name,power.draw,temperature.gpu,memory.used,memory.total,utilization.gpu \
         --format=csv,noheader,nounits 2>/dev/null) && _gpu_ok=1 || _gpu_ok=0
  if [ "$_gpu_ok" = 1 ] && [ -n "$_gpu" ]; then
    GPUJ="["
    _first=1
    while IFS=, read -r _name _watts _temp _mused _mtotal _util; do
      _name=$(echo "$_name" | sed 's/^ *//;s/ *$//')
      _watts=$(echo "$_watts" | sed 's/^ *//;s/ *$//')
      _temp=$(echo "$_temp" | sed 's/^ *//;s/ *$//')
      _mused=$(echo "$_mused" | sed 's/^ *//;s/ *$//')
      _mtotal=$(echo "$_mtotal" | sed 's/^ *//;s/ *$//')
      _util=$(echo "$_util" | sed 's/^ *//;s/ *$//')
      [ "$_first" = 1 ] && _first=0 || GPUJ="$GPUJ,"
      GPUJ="$GPUJ{\"name\":\"$_name\",\"watts\":$_watts,\"temp_c\":$_temp,\"mem_used_mib\":$_mused,\"mem_total_mib\":$_mtotal,\"util_pct\":$_util}"
    done <<< "$_gpu"
    GPUJ="$GPUJ]"
  fi
fi

js(){ local s=${1:-}; s=${s//\\/\\\\}; s=${s//\"/\\\"}; printf '%s' "$s"|tr -d '\000-\037'; }
jn(){ if [ -z "${1:-}" ]; then printf null; else printf '%s' "$1"; fi; }

# Physical drive SMART info (bare-metal only). Requires smartctl.
# Chooses the invocation automatically:
#   - root:     smartctl
#   - non-root: sudo -n smartctl (needs passwordless sudoers entry — see /j/help.php)
# Enumerates targets via `smartctl --scan` first — it handles drives behind
# HBA/RAID controllers (megaraid, cciss, hpsa, areca) that lsblk can't see
# individually. lsblk is a fallback for minimal systems / old smartctl.
# Collects per-disk: model, serial, capacity, type (ssd/hdd), interface,
# health, temperature, power-on hours, cycle count, reallocated sectors (ATA),
# percentage used (NVMe wear), lifetime bytes read/written. Skips silently if
# not baremetal, tools missing, or non-root without working sudo -n.
SMARTJ=null
_uid=$(id -u 2>/dev/null || echo 1)
# Resolve smartctl even when /usr/sbin isn't in the caller's PATH
# (common for non-root interactive shells). Prefer PATH, then well-known paths.
_smartctl=
for _p in smartctl /usr/sbin/smartctl /usr/local/sbin/smartctl /sbin/smartctl /usr/bin/smartctl; do
  if [ "$_p" != "${_p#/}" ]; then
    [ -x "$_p" ] && { _smartctl=$_p; break; }
  else
    _r=$(command -v "$_p" 2>/dev/null) && { _smartctl=$_r; break; }
  fi
done
SMARTCMD=
if [ -n "$_smartctl" ]; then
  if [ "$_uid" = 0 ]; then
    SMARTCMD=$_smartctl
  elif command -v sudo >/dev/null 2>&1 && sudo -n "$_smartctl" --version >/dev/null 2>&1; then
    SMARTCMD="sudo -n $_smartctl"
  fi
fi
if [ "$VT" = "none" ] && [ -n "$SMARTCMD" ]; then
  _first=1
  _body=
  # Targets format: "<dev>|<-d type or empty>". smartctl --scan handles
  # HBA/RAID/NVMe-namespace topologies that lsblk alone misses.
  _targets=$($SMARTCMD --scan 2>/dev/null | awk '/^\// {
    dev=$1; type=""
    for (i=2; i<=NF; i++) if ($i == "-d") type=$(i+1)
    printf "%s|%s\n", dev, type
  }')
  if [ -z "$_targets" ] && command -v lsblk >/dev/null 2>&1; then
    _targets=$(lsblk -d -n -o NAME,TYPE,TRAN 2>/dev/null | awk '
      $2=="disk" && $1 !~ /^(loop|ram|zd|sr|fd|md|dm-)/ && $3!="usb" && $3!="mmc" {
        printf "/dev/%s|\n", $1
      }')
  fi
  while IFS='|' read -r _dev _type; do
    [ -n "$_dev" ] || continue
    case "$_type" in usb*|mmc*) continue ;; esac
    case "$_dev" in */sg[0-9]*) continue ;; esac     # SCSI generic: duplicates of real devs
    # Use -d only for RAID-controller-backed targets. For plain /dev/sdX or
    # /dev/nvme*, smartctl auto-detects ATA/SAT/NVMe better than --scan's
    # hint (which often says "-d scsi" on LSI/mpt3sas HBAs, giving an
    # SCSI-only view that misses every ATA SMART attribute).
    _needs_d=
    case "$_type" in
      megaraid,*|cciss,*|hpsa,*|areca,*) _needs_d=1 ;;
    esac
    if [ -n "$_needs_d" ]; then
      _info=$($SMARTCMD -i -A -H -d "$_type" "$_dev" 2>/dev/null) || continue
    else
      _info=$($SMARTCMD -i -A -H "$_dev" 2>/dev/null) || continue
    fi
    [ -n "$_info" ] || continue
    # Display name: device basename, disambiguated for RAID-backed drives.
    _n=$(basename "$_dev")
    case "$_type" in
      megaraid,*) _n="megaraid$(printf '%s' "$_type" | sed 's/.*,//')" ;;
      cciss,*)    _n="cciss$(printf '%s' "$_type" | sed 's/.*,//')" ;;
      hpsa,*)     _n="hpsa$(printf '%s' "$_type" | sed 's/.*,//')" ;;
      areca,*)    _n="areca$(printf '%s' "$_type" | sed 's/.*,//')" ;;
    esac
    # Model: NVMe → "Model Number:", SATA/ATA → "Device Model:", SCSI → "Product:"
    _model=$(printf '%s\n' "$_info" | sed -n -E 's/^(Device Model|Model Number|Product):[[:space:]]+//p' | head -n1 | sed -E 's/[[:space:]]+$//')
    _serial=$(printf '%s\n' "$_info" | sed -n -E 's/^[Ss]erial [Nn]umber:[[:space:]]+//p' | head -n1 | sed -E 's/[[:space:]]+$//')
    _health=$(printf '%s\n' "$_info" | sed -n -E 's/^SMART overall-health self-assessment test result:[[:space:]]+//p;s/^SMART Health Status:[[:space:]]+//p' | head -n1 | sed -E 's/[[:space:]]+$//')
    # Capacity: SATA → "User Capacity: X bytes", NVMe → "Total NVM Capacity:"
    # or (on some enterprise drives) "Namespace 1 Size/Capacity:". The length
    # guard skips the namespace ordinal ("1") in "Namespace 1 Size/Capacity:".
    _size=$(printf '%s\n' "$_info" | awk '
      /^User Capacity:/ || /^Total NVM Capacity:/ || /^Namespace [0-9]+ Size\/Capacity:/ {
        for(i=1;i<=NF;i++){g=$i; gsub(/,/,"",g); if(g ~ /^[0-9]+$/ && length(g) > 6){print g; exit}}
      }')
    # Current temperature
    _temp=$(printf '%s\n' "$_info" | awk '
      /^Temperature:[[:space:]]/ {print $2+0; exit}
      /^Current Drive Temperature:/ {print $4+0; exit}
      /Temperature_Celsius/ {print $10+0; exit}
      /Airflow_Temperature_Cel/ {print $10+0; exit}')
    _poh=$(printf '%s\n' "$_info" | awk '
      /^Power On Hours:/ {g=$4; gsub(/,/,"",g); print g+0; exit}
      /Power_On_Hours/ {g=$10; gsub(/,/,"",g); print g+0; exit}')
    _pc=$(printf '%s\n' "$_info" | awk '
      /^Power Cycles:/ {g=$3; gsub(/,/,"",g); print g+0; exit}
      /Power_Cycle_Count/ {g=$10; gsub(/,/,"",g); print g+0; exit}')
    _reall=$(printf '%s\n' "$_info" | awk '/Reallocated_Sector_Ct/ {print $10+0; exit}')
    _pctused=$(printf '%s\n' "$_info" | awk '/^Percentage Used:/ {g=$3; gsub(/%/,"",g); print g+0; exit}')
    # Lifetime bytes read/written. NVMe reports "Data Units" (1 unit = 1000 × 512 B = 512000 B).
    # SATA/ATA reports "Total_LBAs_*" (LBA = 512 B on almost every drive).
    _du_r=$(printf '%s\n'  "$_info" | awk '/^Data Units Read:/    {g=$4; gsub(/,/,"",g); print g+0; exit}')
    _du_w=$(printf '%s\n'  "$_info" | awk '/^Data Units Written:/ {g=$4; gsub(/,/,"",g); print g+0; exit}')
    _lba_r=$(printf '%s\n' "$_info" | awk '/Total_LBAs_Read/      {print $10+0; exit}')
    _lba_w=$(printf '%s\n' "$_info" | awk '/Total_LBAs_Written/   {print $10+0; exit}')
    _bytes_r=; _bytes_w=
    [ -n "$_du_r"  ] && [ "$_du_r"  -gt 0 ] 2>/dev/null && _bytes_r=$((_du_r  * 512000))
    [ -n "$_du_w"  ] && [ "$_du_w"  -gt 0 ] 2>/dev/null && _bytes_w=$((_du_w  * 512000))
    [ -z "$_bytes_r" ] && [ -n "$_lba_r" ] && [ "$_lba_r" -gt 0 ] 2>/dev/null && _bytes_r=$((_lba_r * 512))
    [ -z "$_bytes_w" ] && [ -n "$_lba_w" ] && [ "$_lba_w" -gt 0 ] 2>/dev/null && _bytes_w=$((_lba_w * 512))
    # SSD vs HDD: derive from smartctl's Rotation Rate line. NVMe implicitly SSD.
    _rot=$(printf '%s\n' "$_info" | awk -F: '/^Rotation Rate:/{gsub(/^[[:space:]]+|[[:space:]]+$/,"",$2); print $2; exit}')
    _dtype=hdd
    case "$_rot" in
      *"Solid State"*|*"solid state"*) _dtype=ssd ;;
      *rpm*|*RPM*)                     _dtype=hdd ;;
      *)                               case "$_type$_dev" in *nvme*) _dtype=ssd ;; esac ;;
    esac
    # NVMe SMART/Health Information log (NVMe Log 0x02). Single awk pass over
    # the whole smartctl output, emitting a JSON object with the fields
    # smartmontools exposes. Skips lines not present; emits "null" if the
    # log section is absent altogether (e.g. SATA drive).
    _nvme_json=$(printf '%s\n' "$_info" | awk '
      BEGIN { have=0 }
      function trim(s) { sub(/^[[:space:]]+/,"",s); sub(/[[:space:]]+$/,"",s); return s }
      function num(s) { gsub(/,/,"",s); gsub(/%/,"",s); return s+0 }
      /^Critical Warning:/                  { cw=$3; have=1 }
      /^Available Spare:/                   { as=num($3); have=1 }
      /^Available Spare Threshold:/         { ast=num($4); have=1 }
      /^Unsafe Shutdowns:/                  { us=num($3); have=1 }
      /^Media and Data Integrity Errors:/   { me=num($6); have=1 }
      /^Error Information Log Entries:/     { el=num($5); have=1 }
      /^Controller Busy Time:/              { cbt=num($4); have=1 }
      /^Host Read Commands:/                { hrc=num($4); have=1 }
      /^Host Write Commands:/               { hwc=num($4); have=1 }
      /^Warning  Comp\. Temperature Time:/  { wtt=num($5); have=1 }
      /^Critical Comp\. Temperature Time:/  { ctt=num($5); have=1 }
      /^Temperature Sensor [0-9]+:/         { ts[++tn]=num($4); have=1 }
      END {
        if (!have) { print "null"; exit }
        printf "{"
        sep=""
        if (cw!="")  { printf "%s\"critical_warning\":\"%s\"", sep, cw; sep="," }
        if (as!="")  { printf "%s\"available_spare_pct\":%d", sep, as; sep="," }
        if (ast!="") { printf "%s\"available_spare_threshold_pct\":%d", sep, ast; sep="," }
        if (us!="")  { printf "%s\"unsafe_shutdowns\":%d", sep, us; sep="," }
        if (me!="")  { printf "%s\"media_errors\":%d", sep, me; sep="," }
        if (el!="")  { printf "%s\"err_log_entries\":%d", sep, el; sep="," }
        if (cbt!="") { printf "%s\"controller_busy_min\":%d", sep, cbt; sep="," }
        if (hrc!="") { printf "%s\"host_read_cmds\":%d", sep, hrc; sep="," }
        if (hwc!="") { printf "%s\"host_write_cmds\":%d", sep, hwc; sep="," }
        if (wtt!="") { printf "%s\"warning_temp_time_min\":%d", sep, wtt; sep="," }
        if (ctt!="") { printf "%s\"critical_temp_time_min\":%d", sep, ctt; sep="," }
        if (tn>0) {
          printf "%s\"temp_sensors_c\":[", sep; for (i=1;i<=tn;i++) { if (i>1) printf ","; printf "%d", ts[i] } printf "]"
          sep=","
        }
        printf "}"
      }
    ')
    [ -z "$_nvme_json" ] && _nvme_json=null
    # ATA SMART attribute table ("05 Reallocated_Sector_Ct ...", etc.). Emits
    # an array of objects with id, name, value, worst, thresh, type, raw. NVMe
    # drives don't print this table, so the array is null for them.
    _attrs_json=$(printf '%s\n' "$_info" | awk '
      BEGIN { in_tbl=0; first=1 }
      function js_s(s) { gsub(/\\/,"\\\\",s); gsub(/"/,"\\\"",s); return s }
      /^ID# ATTRIBUTE_NAME/ { in_tbl=1; printf "["; next }
      /^$/                  { if (in_tbl) { in_tbl=0; printf "]" } }
      in_tbl {
        # Lines look like: "  5 Reallocated_Sector_Ct  0x0033 100 100 010 Pre-fail Always - 12"
        if ($1 !~ /^[0-9]+$/) next
        id=$1+0; name=$2; flag=$3; val=$4+0; worst=$5+0; thresh=$6+0;
        type=$7; raw=$10; for (i=11; i<=NF; i++) raw=raw" "$i
        # Trim trailing "(Min/Max 15/57)" -style annotations — keep first token.
        sub(/[[:space:]]*\(.*$/, "", raw)
        if (!first) printf ","; first=0
        printf "{\"id\":%d,\"name\":\"%s\",\"flag\":\"%s\",\"value\":%d,\"worst\":%d,\"thresh\":%d,\"type\":\"%s\",\"raw\":\"%s\"}",
          id, js_s(name), js_s(flag), val, worst, thresh, js_s(type), js_s(raw)
      }
      END { if (in_tbl) printf "]" }
    ')
    [ -z "$_attrs_json" ] || [ "$_attrs_json" = "[]" ] && _attrs_json=null
    # Transport: device path decides for regular /dev/sdX and /dev/nvme*
    # (avoids mis-badging SATA-behind-HBA drives as "SCSI"); RAID-backed
    # targets fall through to their -d type.
    case "$_dev" in
      *nvme*)    _trn=nvme ;;
      *sd[a-z]*) _trn=sata ;;
      *)
        case "$_type" in
          megaraid*) _trn=megaraid ;;
          cciss*)    _trn=cciss    ;;
          hpsa*)     _trn=hpsa     ;;
          areca*)    _trn=areca    ;;
          nvme*)     _trn=nvme     ;;
          sat*)      _trn=sata     ;;
          *)         _trn=${_type:-scsi} ;;
        esac
        ;;
    esac
    [ "$_first" = 1 ] && _first=0 || _body="$_body,"
    _body="$_body{\"name\":\"$(js "$_n")\",\"type\":\"$_dtype\",\"tran\":\"$(js "$_trn")\",\"model\":\"$(js "$_model")\",\"serial\":\"$(js "$_serial")\",\"health\":\"$(js "$_health")\",\"size_bytes\":$(jn "$_size"),\"temp_c\":$(jn "$_temp"),\"power_on_hours\":$(jn "$_poh"),\"power_cycle\":$(jn "$_pc"),\"reallocated\":$(jn "$_reall"),\"pct_used\":$(jn "$_pctused"),\"bytes_read\":$(jn "$_bytes_r"),\"bytes_written\":$(jn "$_bytes_w"),\"nvme\":$_nvme_json,\"attrs\":$_attrs_json}"
  done <<< "$_targets"
  [ -n "$_body" ] && SMARTJ="[$_body]"
  v "smart using: $SMARTCMD"
elif [ "$V" = 1 ]; then
  _why="skipped:"
  [ "$VT" != "none" ]   && _why="$_why virt=$VT"
  [ -z "$_smartctl" ]   && _why="$_why no-smartctl"
  [ -n "$_smartctl" ] && [ -z "$SMARTCMD" ] && [ "$_uid" != 0 ] && _why="$_why non-root-no-sudo-n"
  v "smart $_why"
fi

J="{\"hostname\":\"$(js "$H")\",\"proxy\":\"$(js "$X")\",\"google_ping\":$(jn "$G"),\"cpu_cores\":$(jn "$CC"),\"cpu_model\":\"$(js "$CM")\",\"cpu_mhz\":$(jn "$CH"),\"cpu_core_pct\":$CPJ,\"load1\":$(jn "$L1"),\"load5\":$(jn "$L5"),\"load15\":$(jn "$L15"),\"cpu_temp_c\":$(jn "$T"),\"mem_total_kb\":$(jn "$MT"),\"mem_available_kb\":$(jn "$MA"),\"swap_total_kb\":$(jn "$ST"),\"swap_used_kb\":$(jn "$SU"),\"disk_total_b\":$(jn "$DT"),\"disk_used_b\":$(jn "$DU"),\"uptime_sec\":$(jn "$UP"),\"tcp_conns\":$(jn "$TC"),\"proc_count\":$(jn "$PC"),\"net_iface\":\"$(js "$NI")\",\"local_ip\":\"$(js "$LIP")\",\"net_rx_bytes\":$(jn "$NR"),\"net_tx_bytes\":$(jn "$NX"),\"kernel\":\"$(js "$KE")\",\"distro\":\"$(js "$DI")\",\"virt_type\":\"$(js "$VT")\",\"arch\":\"$(js "$ARCH")\",\"gpu_info\":$GPUJ,\"smart_info\":$SMARTJ}"

v "host=$H proxy=$X ping_ms=$G local_ip=${LIP:-?} cores=${CC:-?} load=${L1:-?} temp=${T:-?} uptime=${UP:-?}"
v "smart_info=${SMARTJ:0:80}$([ ${#SMARTJ} -gt 80 ] && echo …)"
v "endpoint=$A payload_bytes=${#J}"

p(){
  if command -v gzip >/dev/null 2>&1; then
    printf '%s' "$J"|gzip -c|curl -s -X POST "$A" --connect-timeout 5 --max-time 15 -H "Content-Type: application/json" -H "Content-Encoding: gzip" --data-binary @- -w "\n%{http_code}"
  else
    curl -s -X POST "$A" --connect-timeout 5 --max-time 15 -H "Content-Type: application/json" -d "$J" -w "\n%{http_code}"
  fi
}

v "POST attempt 1"
O=$(p) || true
HC=$(printf '%s\n' "$O"|tail -n1)
BODY=$(printf '%s\n' "$O"|sed '$d')
if ! [[ "$HC" =~ ^2[0-9][0-9]$ ]]; then
  v "attempt 1 failed: HTTP=${HC:-?} body=${BODY:-<empty>}"
  v "retrying after 1s"
  sleep 1
  O=$(p) || true
  HC=$(printf '%s\n' "$O"|tail -n1)
  BODY=$(printf '%s\n' "$O"|sed '$d')
fi
if [[ "$HC" =~ ^2[0-9][0-9]$ ]]; then
  v "SUCCESS: HTTP=$HC body=${BODY:-<empty>}"
  exit 0
fi
echo "report.sh HTTP ${HC:-?}" >&2
v "FAIL: HTTP=${HC:-?} body=${BODY:-<empty>}"
exit 1
