#!/bin/sh
#
# ssh-reverse 6.0.1
# Copyright (c)2021-2026 John Lawson & Sons
# All Rights Reserved
#

set -eu
umask 077

# ---------------------------------------------------------------------
# Get unique device ID
# ---------------------------------------------------------------------
ID="$(tr -d '\0' < /sys/firmware/devicetree/base/serial-number 2>/dev/null \
      | tr '[:lower:]' '[:upper:]' || true)"

[ -n "${ID:-}" ] || \
ID="$(cat /etc/machine-id 2>/dev/null | tr '[:lower:]' '[:upper:]' || true)"

[ -n "${ID:-}" ] || exit 0

API_URL="https://api.isignage.app/devices/$ID/port"
RUNDIR="/run/ssh-reverse"
PORTFILE="$RUNDIR/port"
ERRFILE="$RUNDIR/ssh.err"

mkdir -p "$RUNDIR"

# ---------------------------------------------------------------------
# Local service to expose on the device
# ---------------------------------------------------------------------
LOCAL_PORT=65535

# ---------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------
fetch_port() {
  PORT="$(
    curl -fsS \
      --user-agent "iSIGNAGE" \
      --connect-timeout 5 \
      --max-time 10 \
      --retry 3 \
      --retry-delay 1 \
      --retry-all-errors \
      "$API_URL" \
    | tr -d '\r\n'
  )"

  case "$PORT" in
    ''|*[!0-9]*)
      return 1
    ;;
  esac

  if [ "$PORT" -lt 49152 ] 2>/dev/null || [ "$PORT" -gt 65535 ] 2>/dev/null; then
    return 1
  fi

  printf '%s\n' "$PORT" > "$PORTFILE"
  echo "$PORT"
  return 0
}

# ---------------------------------------------------------------------
# Main loop: connect forever with sensible backoff
# ---------------------------------------------------------------------

# Backoff for stale server-side tunnel holding the remote port
fwd_fail_delay=5
fwd_fail_max=300

# Backoff for API/DNS/network/SSH failures
net_fail_delay=5
net_fail_max=60

while :; do
  if ! PORT="$(fetch_port 2>/dev/null)"; then
    sleep "$net_fail_delay"
    net_fail_delay=$((net_fail_delay * 2))
    [ "$net_fail_delay" -gt "$net_fail_max" ] && net_fail_delay="$net_fail_max"
    continue
  fi

  : > "$ERRFILE" || true

  start_ts="$(date +%s)"

  ssh \
    -o BatchMode=yes \
    -o ConnectTimeout=10 \
    -o ExitOnForwardFailure=yes \
    -o ServerAliveInterval=15 \
    -o ServerAliveCountMax=2 \
    -o StrictHostKeyChecking=yes \
    -o UserKnownHostsFile=/root/.ssh/known_hosts \
    -N \
    -R "${PORT}:localhost:${LOCAL_PORT}" \
    root@ssh.isignage.uk \
    2>"$ERRFILE" || true

  end_ts="$(date +%s)"
  runtime=$((end_ts - start_ts))

  # If the tunnel stayed up for a while, treat it as a successful connection
  # and reset both backoffs.
  if [ "$runtime" -gt 120 ]; then
    net_fail_delay=5
    fwd_fail_delay=5
  fi

  if grep -q "remote port forwarding failed for listen port" "$ERRFILE" 2>/dev/null; then
    sleep "$fwd_fail_delay"
    fwd_fail_delay=$((fwd_fail_delay * 2))
    [ "$fwd_fail_delay" -gt "$fwd_fail_max" ] && fwd_fail_delay="$fwd_fail_max"
    continue
  fi

  sleep "$net_fail_delay"
  net_fail_delay=$((net_fail_delay * 2))
  [ "$net_fail_delay" -gt "$net_fail_max" ] && net_fail_delay="$net_fail_max"
done
