#!/bin/bash
#
# Purpose : Run Hermes inside its sandbox (sandbox/agent runs this there) and
#           connect its terminal to its gateway.
#
# Hermes' gateway runs the agent; `hermes` in a terminal only talks to it, over
# the gateway's control socket and then a WebSocket on 127.0.0.1:<port>. Each
# sandbox has its own network, so that port can't be reached from another one.
# The gateway's sandbox relays the port to API_SOCKET, and a terminal's sandbox
# listens on the same port and relays to API_SOCKET, so `hermes` reaches the
# gateway as if they shared a network.
#
# Usage: hermes-bridge <hermes command> [arguments]

real="$1"
shift
HERMES_HOME="${HERMES_HOME:-$HOME/.hermes}"
API_SOCKET="$HERMES_HOME/miniserver-api.sock"

# The gateway's answer to `identify` on its control socket, or nothing if no
# gateway is running
identify() {
  local control="$HERMES_HOME/gateway.sock"
  [ -S "$control" ] || control="$(cat "$HERMES_HOME/gateway.sock.path" 2> /dev/null)"
  [ -S "$control" ] || return 1
  printf '{"protocol":1,"verb":"identify","id":1}\n' \
    | socat -t 5 - "UNIX-CONNECT:$control" 2> /dev/null | grep .
}

# The port the gateway's API listens on, once the gateway is ready. Only an
# IPv4 loopback origin is relayed, which is what Hermes uses by default.
api_port() {
  local origin
  origin="$(identify | jq -r 'select(.result.state == "ready") | .result.api_origin // empty' 2> /dev/null)"
  [[ "$origin" =~ ^http://127\.0\.0\.1:([0-9]+)$ ]] || return 1
  echo "${BASH_REMATCH[1]}"
}

if [ "$1" = "gateway" ] && [ "$2" = "run" ]; then
  # A terminal finds no relay, rather than the last gateway's, until this one
  # is up
  rm -f "$API_SOCKET"
  # The relay waits for the gateway, which runs in this process once exec'd;
  # the sandbox ends both together
  (
    for _ in $(seq 480); do
      if port="$(api_port)"; then
        exec socat "UNIX-LISTEN:$API_SOCKET,unlink-early,mode=600,fork" "TCP:127.0.0.1:$port"
      fi
      sleep 0.25
    done
    echo "hermes-bridge: the gateway's API didn't come up; terminals can't reach it" >&2
  ) &
  exec "$real" "$@"
fi

# A terminal: relay the gateway's port, if a gateway is running or its service
# is turned on, waiting for one that is still starting. Without one, hermes
# starts its own gateway inside this sandbox, which needs no relay.
if [ -e "$HOME/.config/systemd/user/default.target.wants/hermes-gateway.service" ] \
    || identify > /dev/null; then
  [ -S "$API_SOCKET" ] && api_port > /dev/null || echo "Waiting for Hermes' gateway..." >&2
  for _ in $(seq 240); do
    [ -S "$API_SOCKET" ] && port="$(api_port)" && break
    port=""
    sleep 0.25
  done
  if [ -n "$port" ]; then
    socat "TCP-LISTEN:$port,bind=127.0.0.1,reuseaddr,fork" "UNIX-CONNECT:$API_SOCKET" &
    # Wait for the listener (state 0A, LISTEN, in /proc/net/tcp) so hermes'
    # first connection doesn't fail
    hex="$(printf ':%04X ' "$port")"
    for _ in $(seq 50); do
      grep -q "$hex.* 0A " /proc/net/tcp && break
      sleep 0.05
    done
  else
    echo "hermes-bridge: Hermes' gateway is running but its API isn't reachable; try: systemctl --user restart hermes-gateway" >&2
  fi
fi
exec "$real" "$@"
