#!/usr/bin/env bash
# bos_collect.sh: read-only snapshot of Braiins OS miners for a support ticket.
# Independent helper written for the Miner Triage Desk demo (not a Braiins tool).
#
#   ./bos_collect.sh 10.20.4.37            one miner
#   ./bos_collect.sh -f iplist.txt         one IP per line
#
# Needs grpcurl and jq; nc for the BOSminer API fallback (port 4028).
# Only Get* calls and BOSminer read commands. No config writes, no restarts.
# The password is read from a prompt and never written to the output file.
set -u

USER_NAME="${BOS_USER:-root}"
OUT="bos_collect_$(date +%Y%m%d_%H%M%S).txt"
IPS=()

if [ "${1:-}" = "-f" ] && [ -n "${2:-}" ]; then
  while IFS= read -r line; do
    line="${line%%#*}"; line="$(echo "$line" | tr -d '[:space:]')"
    [ -n "$line" ] && IPS+=("$line")
  done < "$2"
elif [ -n "${1:-}" ]; then
  IPS=("$1")
else
  echo "usage: $0 <ip> | -f <iplist.txt>" >&2; exit 1
fi

for bin in grpcurl jq; do
  command -v "$bin" >/dev/null 2>&1 || { echo "missing $bin (https://github.com/fullstorydev/grpcurl, https://jqlang.org)" >&2; exit 1; }
done

read -r -s -p "Password for ${USER_NAME} on the miners (empty if none): " PW; echo

# Pool passwords are masked before they reach the output file.
mask() { jq 'walk(if type == "object" and has("password") then .password = "***" else . end)' 2>/dev/null || cat; }

READ_CALLS=(
  "MinerService/GetMinerDetails"
  "MinerService/GetMinerStats"
  "MinerService/GetHashboards"
  "MinerService/GetErrors"
  "PoolService/GetPoolGroups"
  "CoolingService/GetCoolingState"
  "LicenseService/GetLicenseState"
  "PerformanceService/GetTunerState"
  "NetworkService/GetNetworkInfo"
)

for ip in "${IPS[@]}"; do
  {
    echo "########## $ip  $(date -u +%Y-%m-%dT%H:%M:%SZ)"
    api=$(grpcurl -plaintext -max-time 8 "$ip:50051" braiins.bos.ApiVersionService/GetApiVersion 2>/dev/null)
    if [ -n "$api" ]; then
      echo "### braiins.bos.ApiVersionService/GetApiVersion"; echo "$api"
      body=$(jq -cn --arg u "$USER_NAME" --arg p "$PW" '{username:$u,password:$p}')
      TOKEN=$(grpcurl -plaintext -max-time 8 -d "$body" "$ip:50051" braiins.bos.v1.AuthenticationService/Login 2>/dev/null | jq -r '.token // empty')
      if [ -z "$TOKEN" ]; then echo "### login failed (wrong password?) - only unauthenticated data collected"; fi
      for call in "${READ_CALLS[@]}"; do
        echo "### braiins.bos.v1.$call"
        grpcurl -plaintext -max-time 10 -H "authorization:$TOKEN" "$ip:50051" "braiins.bos.v1.$call" 2>&1 | mask
      done
    else
      echo "### gRPC port 50051 not answering, trying BOSminer API on 4028"
      if command -v nc >/dev/null 2>&1; then
        echo '{"command":"version+summary+pools+devs+devdetails+fans+temps+tunerstatus"}' | nc -w 8 "$ip" 4028 | tr -d '\000' | mask
        echo
      else
        echo "### nc not installed, skipped"
      fi
    fi
  } >> "$OUT"
  echo "collected $ip"
done

unset PW TOKEN
echo "Done: $OUT. Paste it into the ticket (or the triage desk). Pool passwords are masked; check the file before sending."
