#!/usr/bin/bash
#
# ups/setup.sh — idempotent Network UPS Tools (NUT) setup on pfv-tsys1
#
# Installs NUT, configures two USB HID UPS units (APC + Tripp Lite) pinned by
# USB serial, runs upsd as a network server for Home Assistant polling, and
# runs upsmon locally so the hypervisor can shut down gracefully on battery.
#
# Designed to run ON the target host (pfv-tsys1) as root, idempotent.
#
# Usage:
#   PROX_HOST=pfv-tsys1 bash tests/remote.sh prox-file ups/setup.sh
#
# Overrides (defaults suit pfv-tsys1):
#   APC_SERIAL         APC UPS USB serial            (default AS1213210423)
#  APC_VID/APC_PID     APC vendor/product ID          (default 051d / 0003)
#   APC_NAME           NUT section name for APC       (default apc-smartups-c1500)
#  TRIPP_SERIAL        Tripp Lite UPS USB serial      (default 2352CVLSM871900694)
#  TRIPP_VID/TRIPP_PID Tripp Lite vendor/product      (default 09ae / 3016)
#  TRIPP_NAME          NUT section name for Tripp     (default tripp-lite-ups)
#  TRIPP_SUBDRIVER     Forced HID subdriver for Tripp (default "TrippLite HID 0.85")
#  TRIPP_ENABLED       Set to 0 to disable Tripp Lite (default 1)
#  NUT_LISTEN_IPS      space-separated upsd LISTEN IPs (default: auto Tailscale + 127.0.0.1)
#  HA_USER             upsd username for HA           (default homeassistant)
#  HA_PASSWORD         upsd password for HA           (default: reuse or generate)
#  MON_USER            upsd username for local upsmon (default monuser)
#  MON_PASSWORD        upsd password for upsmon       (default: reuse or generate)
#
set -euo pipefail

# --- Config (overridable via env) ---
APC_SERIAL="${APC_SERIAL:-AS1213210423}"
APC_VID="${APC_VID:-051d}"
APC_PID="${APC_PID:-0003}"
APC_NAME="${APC_NAME:-apc-smartups-c1500}"

TRIPP_SERIAL="${TRIPP_SERIAL:-2352CVLSM871900694}"
TRIPP_VID="${TRIPP_VID:-09ae}"
TRIPP_PID="${TRIPP_PID:-3016}"
TRIPP_NAME="${TRIPP_NAME:-tripp-lite-ups}"
TRIPP_SUBDRIVER="${TRIPP_SUBDRIVER:-TrippLite HID 0.85}"
TRIPP_ENABLED="${TRIPP_ENABLED:-1}"

HA_USER="${HA_USER:-homeassistant}"
MON_USER="${MON_USER:-monuser}"
NUT_PORT="${NUT_PORT:-3493}"
UDEV_RULE="/etc/udev/rules.d/99-nut-ups.rules"

UPS_CONF="/etc/nut/ups.conf"
UPSD_CONF="/etc/nut/upsd.conf"
UPSD_USERS="/etc/nut/upsd.users"
UPS_CONF_MON="/etc/nut/upsmon.conf"
NUT_CONF="/etc/nut/nut.conf"

# --- Helpers ---
gen_pw() { head -c 24 /dev/urandom | base64 | tr -d '/+=' | cut -c1-20; }

# Reuse existing passwords if config already present (idempotent re-runs)
extract_pw() {  # $1=user
  if [ -f "$UPSD_USERS" ]; then
    awk -v u="[$1]" '
      $0==u {inblk=1; next}
      /^\[/ {inblk=0}
      inblk && $1=="password" {gsub(/"/,"",$3); print $3; exit}
    ' "$UPSD_USERS" 2>/dev/null
  fi
}

echo "============================================"
echo "  NUT UPS Setup on $(hostname)"
echo "  APC:        $APC_NAME  ($APC_VID:$APC_PID serial $APC_SERIAL)"
if [ "$TRIPP_ENABLED" = "1" ]; then
  echo "  Tripp Lite: $TRIPP_NAME ($TRIPP_VID:$TRIPP_PID serial $TRIPP_SERIAL)"
else
  echo "  Tripp Lite: DISABLED (TRIPP_ENABLED=0)"
fi
echo "============================================"

# --- 0. Resolve / generate passwords (idempotent) ---
MON_PASSWORD="${MON_PASSWORD:-$(extract_pw "$MON_USER")}"
HA_PASSWORD="${HA_PASSWORD:-$(extract_pw "$HA_USER")}"
[ -n "$MON_PASSWORD" ] || MON_PASSWORD="$(gen_pw)"
[ -n "$HA_PASSWORD" ] || HA_PASSWORD="$(gen_pw)"

# --- 0b. Auto-detect listen IPs for upsd ---
# Tailscale IP: tailnet clients (workstation, etc.)
# LAN IP: HAOS VMs where Tailscale runs as an isolated add-on (HA container
#         cannot route to Tailscale IPs, so the shared-LAN bridge is required)
if [ -z "${NUT_LISTEN_IPS:-}" ]; then
  NUT_LISTEN_IPS="127.0.0.1"
  TS_IP=$(tailscale ip -4 2>/dev/null || true)
  if [ -n "$TS_IP" ]; then
    NUT_LISTEN_IPS="${NUT_LISTEN_IPS} ${TS_IP}"
  else
    echo "  WARNING: No Tailscale IP detected."
  fi
  if [ "${NUT_INCLUDE_LAN:-1}" = "1" ]; then
    LAN_IP=$(ip -4 addr show vmbr0 2>/dev/null | awk '/scope global/{print $2}' | cut -d/ -f1 | head -1)
    if [ -z "$LAN_IP" ]; then
      LAN_IP=$(hostname -I 2>/dev/null | awk '{print $1}')
    fi
    if [ -n "$LAN_IP" ]; then
      NUT_LISTEN_IPS="${NUT_LISTEN_IPS} ${LAN_IP}"
    fi
  fi
fi
echo "  upsd LISTEN IPs: ${NUT_LISTEN_IPS:-<none>}"

# --- 1. Install NUT ---
echo ""
echo "--- [1/8] Installing NUT (nut-server, nut-client) ---"
if dpkg -l nut-server 2>/dev/null | grep -q '^ii'; then
  echo "  NUT already installed: $(dpkg -l nut-server | awk '/^ii/{print $3}')"
else
  apt-get update -qq && apt-get install -y -qq nut-server nut-client
fi
mkdir -p /etc/nut

# --- 2. udev rules: grant nut group access to BOTH raw USB + hidraw devices ---
# CRITICAL: usbhid-ups opens /dev/bus/usb/BBB/DDD (raw USB), not /dev/hidraw.
# The driver drops to the nut user via setuid(), so the nut group needs write
# access to the raw USB device files. Matching on subsystem=="usb" by VID:PID
# is required because ATTRS{serial} does not reliably traverse for usb devices.
echo ""
echo "--- [2/8] Writing udev rules (raw USB + hidraw, group nut) ---"
{
  echo "# Stable permissions for NUT USB HID UPS units"
  echo "# Generated by ups/setup.sh — grants the 'nut' group access to both"
  echo "# the raw USB device files (/dev/bus/usb) and hidraw devices."
  echo "# Match BOTH subsystems: the usbhid-ups driver opens the raw USB device"
  echo "# after dropping to the nut user via setuid()."
  echo ""
  echo "# APC Smart-UPS C 1500 ($APC_VID:$APC_PID)"
  echo "SUBSYSTEM==\"usb\",    ATTR{idVendor}==\"$APC_VID\", ATTR{idProduct}==\"$APC_PID\", GROUP=\"nut\", MODE=\"0664\""
  echo "SUBSYSTEM==\"hidraw\", ATTRS{serial}==\"$APC_SERIAL\",                   GROUP=\"nut\", MODE=\"0660\""
  echo ""
  echo "# Tripp Lite UPS ($TRIPP_VID:$TRIPP_PID)"
  echo "SUBSYSTEM==\"usb\",    ATTR{idVendor}==\"$TRIPP_VID\", ATTR{idProduct}==\"$TRIPP_PID\", GROUP=\"nut\", MODE=\"0664\""
  echo "SUBSYSTEM==\"hidraw\", ATTRS{serial}==\"$TRIPP_SERIAL\",                   GROUP=\"nut\", MODE=\"0660\""
} > "$UDEV_RULE"
echo "  Written: $UDEV_RULE"
udevadm control --reload-rules 2>/dev/null || true
udevadm trigger --subsystem-match=usb 2>/dev/null || true
udevadm trigger --subsystem-match=hidraw 2>/dev/null || true
sleep 1

# --- 3. ups.conf ---
echo ""
echo "--- [3/8] Writing ups.conf ---"
{
  echo "# NUT UPS devices — generated by ups/setup.sh on $(date)"
  echo ""
  echo "maxretry = 3"
  echo ""
  echo "[${APC_NAME}]"
  echo "    driver = usbhid-ups"
  echo "    port = auto"
  echo "    vendorid = ${APC_VID}"
  echo "    productid = ${APC_PID}"
  echo "    serial = ${APC_SERIAL}"
  echo "    desc = \"APC Smart-UPS C 1500\""
  if [ "$TRIPP_ENABLED" = "1" ]; then
    echo ""
    echo "[${TRIPP_NAME}]"
    echo "    driver = usbhid-ups"
    echo "    port = auto"
    echo "    vendorid = ${TRIPP_VID}"
    echo "    productid = ${TRIPP_PID}"
    echo "    serial = ${TRIPP_SERIAL}"
    echo "    subdriver = \"${TRIPP_SUBDRIVER}\""
    echo "    desc = \"Tripp Lite UPS\""
  fi
} > "$UPS_CONF"
echo "  Written: $UPS_CONF"

# --- 4. upsd.conf: network server (localhost + Tailscale for HA) ---
echo ""
echo "--- [4/8] Writing upsd.conf ---"
{
  echo "# NUT upsd — generated by ups/setup.sh on $(date)"
  for ip in $NUT_LISTEN_IPS; do
    echo "LISTEN ${ip} ${NUT_PORT}"
  done
  echo "MAXAGE 25"
} > "$UPSD_CONF"
echo "  Written: $UPSD_CONF (LISTEN: $(echo "$NUT_LISTEN_IPS" | tr '\n' ' '))"

# --- 5. upsd.users: monuser (master) + homeassistant (read-only monitor) ---
echo ""
echo "--- [5/8] Writing upsd.users ---"
cat > "$UPSD_USERS" <<USERS
# NUT upsd users — generated by ups/setup.sh on $(date)
# monuser       : local upsmon (master) — graceful hypervisor shutdown
# ${HA_USER} : Home Assistant NUT integration (read-only polling)

[${MON_USER}]
    password = "${MON_PASSWORD}"
    upsmon master

[${HA_USER}]
    password = "${HA_PASSWORD}"
    upsmon slave
USERS
echo "  Written: $UPSD_USERS"

# --- 6. upsmon.conf + nut.conf ---
echo ""
echo "--- [6/8] Writing upsmon.conf + nut.conf ---"
{
  echo "# NUT upsmon (local monitor) — generated by ups/setup.sh on $(date)"
  echo "# Monitors the APC UPS as master. On battery-low, shuts the host down."
  echo ""
  echo "MONITOR ${APC_NAME}@localhost 1 ${MON_USER} \"${MON_PASSWORD}\" master"
  if [ "$TRIPP_ENABLED" = "1" ]; then
    echo "MONITOR ${TRIPP_NAME}@localhost 1 ${MON_USER} \"${MON_PASSWORD}\" master"
  fi
  echo ""
  echo "SHUTDOWNCMD \"/sbin/shutdown -h now\""
  echo "POWERDOWNFLAG /etc/killpower"
  echo "NOTIFYFLAG ONLINE  SYSLOG+WALL"
  echo "NOTIFYFLAG ONBATT  SYSLOG+WALL"
  echo "NOTIFYFLAG LOWBATT SYSLOG+WALL"
  echo "POLLFREQ 15"
  echo "POLLFREQALERT 5"
  echo "HOSTSYNC 15"
} > "$UPS_CONF_MON"

cat > "$NUT_CONF" <<NUTCONF
# NUT mode — generated by ups/setup.sh on $(date)
# netserver = this host runs drivers + upsd; serves UPS data to clients (HA).
MODE=netserver
NUTCONF
echo "  Written: $UPS_CONF_MON + $NUT_CONF"

# --- 6b. Fix ownership/permissions (Debian: nut group must read config) ---
chown root:nut "$UPS_CONF" "$UPSD_CONF" "$UPSD_USERS" "$UPS_CONF_MON" 2>/dev/null || true
chmod 640 "$UPS_CONF" "$UPSD_CONF" "$UPSD_USERS" "$UPS_CONF_MON" 2>/dev/null || true
chmod 644 "$NUT_CONF" 2>/dev/null || true

# --- 7. Start services (Debian uses templated nut-driver@<name> units) ---
echo ""
echo "--- [7/8] Starting NUT services ---"

# Re-read ups.conf to generate per-UPS driver instances
systemctl restart nut-driver-enumerator 2>/dev/null || true
sleep 2

# Start per-UPS driver instances
systemctl restart "nut-driver@${APC_NAME}" 2>/dev/null || true
if [ "$TRIPP_ENABLED" = "1" ]; then
  systemctl restart "nut-driver@${TRIPP_NAME}" 2>/dev/null || true
else
  systemctl stop "nut-driver@${TRIPP_NAME}" 2>/dev/null || true
  systemctl mask "nut-driver@${TRIPP_NAME}" 2>/dev/null || true
fi
sleep 3
systemctl restart nut-server 2>/dev/null || true
sleep 1
systemctl restart nut-monitor 2>/dev/null || true

echo ""
echo "  Service status:"
for svc in "nut-driver@${APC_NAME}" "nut-driver@${TRIPP_NAME}" nut-server nut-monitor; do
  if systemctl list-unit-files "$svc" >/dev/null 2>&1; then
    printf "    %-42s " "$svc"
    systemctl is-active "$svc" 2>/dev/null || echo "(unknown)"
  fi
done

# --- 8. Validate ---
echo ""
echo "--- [8/8] Validation ---"
echo ""
echo "  upsc — ${APC_NAME}:"
upsc "${APC_NAME}@localhost" 2>&1 | head -25 || echo "    (APC UPS not responding yet)"
if [ "$TRIPP_ENABLED" = "1" ]; then
  echo ""
  echo "  upsc — ${TRIPP_NAME}:"
  upsc "${TRIPP_NAME}@localhost" 2>&1 | head -25 || echo "    (Tripp Lite UPS not responding yet)"
fi

echo ""
echo "============================================"
echo "  Setup complete."
echo ""
echo "  Home Assistant NUT integration:"
echo "    Host:     $(echo "$NUT_LISTEN_IPS" | awk '{print $2}')   (or any LISTEN IP above)"
echo "    Port:     ${NUT_PORT}"
echo "    Username: ${HA_USER}"
echo "    Password: ${HA_PASSWORD}"
if [ "$TRIPP_ENABLED" = "1" ]; then
  echo "    UPS names: ${APC_NAME}, ${TRIPP_NAME}"
else
  echo "    UPS names: ${APC_NAME}"
fi
echo ""
echo "  Save the HA password now — it is stored in ${UPSD_USERS}."
echo "============================================"
