#!/usr/bin/env bash
# AirUSB — Linux connect helper
# Finds your AirUSB bridge on the LAN and attaches its USB device (printer or
# scanner) to this machine over USB/IP, so it shows up as a local USB device.
#
# Usage:
#   sudo ./airusb-linux.sh                 # auto-discover + attach
#   sudo ./airusb-linux.sh <ip|hostname>   # attach a specific device
#   sudo ./airusb-linux.sh --watch [host]  # attach and auto-reconnect (recommended)
#   ./airusb-linux.sh --detach             # detach all AirUSB devices
#
# After it attaches: your printer appears in your normal print settings, and
# scanners work via SANE (scanimage / Document Scanner). Keep the AirUSB near
# your router — scanning needs a good Wi-Fi signal.

WATCH=0
DETACH=0
HOST=""
for a in "$@"; do
  case "$a" in
    --watch)  WATCH=1 ;;
    --detach) DETACH=1 ;;
    -h|--help) sed -n '2,18p' "$0" | sed 's/^# \?//'; exit 0 ;;
    -*) echo "unknown option: $a"; exit 2 ;;
    *)  HOST="$a" ;;
  esac
done

have() { command -v "$1" >/dev/null 2>&1; }

# usbip attach + module load need root — re-run under sudo if we aren't.
if [ "$(id -u)" -ne 0 ]; then
  exec sudo -E bash "$0" "$@"
fi

if ! have usbip; then
  echo "'usbip' is not installed. Install it for your distro:"
  echo "  Debian/Ubuntu : sudo apt install linux-tools-generic   (or linux-tools-\$(uname -r))"
  echo "  Fedora        : sudo dnf install usbip"
  echo "  Arch          : sudo pacman -S usbip"
  exit 1
fi

# Client-side kernel module (name varies by kernel version)
modprobe vhci-hcd 2>/dev/null || modprobe vhci_hcd 2>/dev/null || true

is_attached() { usbip port 2>/dev/null | grep -qE "<Port in Use>"; }

if [ "$DETACH" -eq 1 ]; then
  mapfile -t ports < <(usbip port 2>/dev/null | sed -nE 's/^Port ([0-9]+): <Port in Use>.*/\1/p')
  if [ "${#ports[@]}" -eq 0 ]; then echo "Nothing attached."; exit 0; fi
  for p in "${ports[@]}"; do echo "Detaching port $p"; usbip detach -p "$p"; done
  exit 0
fi

discover() {
  # Prefer mDNS (the bridge advertises _usbip._tcp as air-usb-XXXX.local)
  if have avahi-browse; then
    avahi-browse -rpt _usbip._tcp 2>/dev/null \
      | awk -F';' '/^=/ && /air-usb/ {print $8; exit}'
    return
  fi
  # Fall back to the AirUSB UDP discovery beacon (port 55123)
  if have python3; then
    python3 - <<'PY'
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
s.settimeout(2)
try:
    s.sendto(b"AIRUSB_DISCOVER", ("255.255.255.255", 55123))
    data, _ = s.recvfrom(2048)
    print(data.decode(errors="replace").split("|")[1])   # ip
except Exception:
    pass
PY
  fi
}

if [ -z "$HOST" ]; then
  echo "Searching for an AirUSB on your network..."
  HOST="$(discover)"
  if [ -z "$HOST" ]; then
    echo "Couldn't find one automatically. Re-run with the device's IP or name:"
    echo "  sudo $0 192.168.1.50        (or: sudo $0 air-usb-XXXX.local)"
    echo "Tip: the air-usb-XXXX name is on the Wi-Fi setup page / your router's device list."
    exit 1
  fi
fi
echo "Using AirUSB at: $HOST"

attach_once() {
  local busid
  busid="$(usbip list -r "$HOST" 2>/dev/null \
            | awk -F: '/^ *[0-9]+-[0-9]+:/ {gsub(/ /,"",$1); print $1; exit}')"
  if [ -z "$busid" ]; then
    echo "No device exported by $HOST — is a printer/scanner plugged into the AirUSB's USB port?"
    return 1
  fi
  if is_attached; then return 0; fi
  echo "Attaching (busid $busid)..."
  usbip attach -r "$HOST" -b "$busid"
}

if [ "$WATCH" -eq 1 ]; then
  echo "Watch mode: keeping the device attached. Press Ctrl-C to stop."
  trap 'echo; echo "Stopped (device stays attached). Run with --detach to remove it."; exit 0' INT
  while true; do
    if ! is_attached; then
      attach_once || echo "  not reachable — retrying in 5s..."
    fi
    sleep 5
  done
else
  if attach_once; then
    echo
    echo "Done. Your printer/scanner is now a local USB device:"
    echo "  - Printing: it should appear in your printer settings (CUPS) automatically."
    echo "  - Scanning: use Document Scanner or 'scanimage -L' (SANE)."
    echo "  - If it drops after a reboot/sleep, re-run this script (or use --watch)."
  fi
fi
