#!/bin/sh
# shieldie-guard installer — https://guard.shieldie.ai
#
#   curl -fsSL https://guard.shieldie.ai/install.sh | sudo sh
#
# Interactive when a terminal is available (it asks on /dev/tty, so it works under `curl | sh`):
#   1. management addresses that can never be auto-banned — your SSH peer and this server's own
#      public addresses are detected and shown; you can add more;
#   2. the web dashboard: generated password (printed once), your own, or off;
#   3. joining the shieldie.cloud shared blocklist (opt-in).
# Downloads the static x86_64 Linux binary for the latest release (or --version vX.Y.Z), verifies its
# SHA-256 against the published checksum, runs the binary's own idempotent `init` and starts the
# service. Without a terminal (or with --yes) it uses the detected addresses and the flags below.
#
#   --admin-ip IP     add a management address (repeatable); detection still runs
#   --no-detect       do not auto-detect addresses (use only --admin-ip)
#   --web             dashboard with a generated password   --web-password  ask for one   --no-web
#   --join-cloud      join shieldie.cloud                    --no-cloud
#   --version V       a specific release                     --no-start      install only
#   --yes / -y        no questions: detected addresses + flags (defaults: no dashboard, no cloud)
set -eu

BASE="${SHIELDIE_BASE_URL:-https://guard.shieldie.ai}"
VERSION="${SHIELDIE_VERSION:-}"
ADMIN_IPS="${SHIELDIE_ADMIN_IP:-}"   # space-separated
DETECT=1
JOIN_CLOUD=""     # "" = ask · yes · no
WEB=""            # "" = ask · generate · prompt · no
NO_START=""
YES=""

usage() { sed -n '2,20p' "$0" 2>/dev/null || echo "see https://guard.shieldie.ai"; }

while [ $# -gt 0 ]; do
  case "$1" in
    --admin-ip) ADMIN_IPS="$ADMIN_IPS ${2:-}"; shift ;;
    --admin-ip=*) ADMIN_IPS="$ADMIN_IPS ${1#*=}" ;;
    --no-detect) DETECT="" ;;
    --join-cloud) JOIN_CLOUD=yes ;;
    --no-cloud) JOIN_CLOUD=no ;;
    --web) WEB=generate ;;
    --web-password) WEB=prompt ;;
    --no-web) WEB=no ;;
    --version) VERSION="${2:-}"; shift ;;
    --version=*) VERSION="${1#*=}" ;;
    --no-start) NO_START=1 ;;
    -y|--yes) YES=1 ;;
    -h|--help) usage; exit 0 ;;
    *) echo "unknown option: $1" >&2; usage >&2; exit 2 ;;
  esac
  shift
done

say()  { printf '%s\n' "$*"; }
die()  { printf 'error: %s\n' "$*" >&2; exit 1; }
need() { command -v "$1" >/dev/null 2>&1 || die "'$1' is required"; }
# An IP literal? (loose check; `init` validates strictly and refuses anything else)
is_ip() { case "$1" in *[!0-9a-fA-F:.]*|"") return 1 ;; *.*.*.*) return 0 ;; *:*) return 0 ;; *) return 1 ;; esac; }
has_ip() { for _x in $ADMIN_IPS; do [ "$_x" = "$1" ] && return 0; done; return 1; }
add_ip() { is_ip "$1" && ! has_ip "$1" && ADMIN_IPS="$ADMIN_IPS $1"; }

[ "$(id -u)" -eq 0 ] || die "run as root:  curl -fsSL $BASE/install.sh | sudo sh"
need curl; need sha256sum; need mktemp
case "$(uname -s)-$(uname -m)" in
  Linux-x86_64) ;;
  *) die "only Linux x86_64 binaries are published for now ($(uname -s) $(uname -m)); build from source instead" ;;
esac
command -v systemctl >/dev/null 2>&1 || die "systemd is required (no systemctl found)"
command -v nft >/dev/null 2>&1 || say "! nftables (nft) not found — install it (apt install nftables) or set firewall = \"ufw\" in the config"

TTY=""
if [ -z "$YES" ] && [ -r /dev/tty ] && [ -w /dev/tty ]; then TTY=1; fi
ask() { # ask "prompt" default -> REPLY
  REPLY="$2"
  [ -n "$TTY" ] || return 0
  printf '%s' "$1" >/dev/tty
  read -r REPLY </dev/tty || REPLY="$2"
  [ -n "$REPLY" ] || REPLY="$2"
}

say "shieldie-guard installer — $BASE"

# ---- 1. management addresses ------------------------------------------------------------------
SSH_PEER=""
if [ -n "${SSH_CONNECTION:-}" ]; then
  SSH_PEER="${SSH_CONNECTION%% *}"
elif [ -n "${SUDO_USER:-}" ] && command -v who >/dev/null 2>&1; then
  SSH_PEER="$(who 2>/dev/null | awk -v u="$SUDO_USER" '$1==u && $NF ~ /^\(/ {gsub(/[()]/,"",$NF); print $NF; exit}')"
fi
is_ip "$SSH_PEER" || SSH_PEER=""
HOST_IPS=""
if [ -n "$DETECT" ]; then
  if command -v ip >/dev/null 2>&1; then
    HOST_IPS="$(ip -o addr show scope global 2>/dev/null | awk '{print $4}' | cut -d/ -f1)"
  elif command -v hostname >/dev/null 2>&1; then
    HOST_IPS="$(hostname -I 2>/dev/null || true)"
  fi
  # Keep public/global addresses only: private, link-local and CGNAT ranges of this box are not
  # management addresses (a NAT'd attacker could otherwise share them).
  HOST_IPS="$(printf '%s\n' $HOST_IPS | grep -vE '^(10\.|127\.|169\.254\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.|100\.(6[4-9]|[7-9][0-9]|1[01][0-9]|12[0-7])\.|fe80:|fc|fd|::1$)' | tr '\n' ' ' || true)"
fi
[ -n "$SSH_PEER" ] && add_ip "$SSH_PEER"
for h in $HOST_IPS; do add_ip "$h"; done
# strip leading space
ADMIN_IPS="$(printf '%s' "$ADMIN_IPS" | sed 's/^ *//')"

say ""
say "Management addresses — never auto-banned, so a rule can never lock you out:"
if [ -n "$SSH_PEER" ]; then say "  · $SSH_PEER   (your SSH session)"; else say "  ! your SSH address could not be detected"; fi
for h in $HOST_IPS; do say "  · $h   (this server)"; done
for x in $ADMIN_IPS; do
  [ "$x" = "$SSH_PEER" ] && continue
  case " $HOST_IPS " in *" $x "*) continue ;; esac
  say "  · $x   (given)"
done
if [ -n "$TTY" ]; then
  ask "Add more (comma/space separated), or Enter to continue: " ""
  for x in $(printf '%s' "$REPLY" | tr ',' ' '); do
    if is_ip "$x"; then add_ip "$x"; else say "  ! '$x' is not an IP address — skipped" >/dev/tty; fi
  done
  ADMIN_IPS="$(printf '%s' "$ADMIN_IPS" | sed 's/^ *//')"
fi
[ -n "$ADMIN_IPS" ] || say "! no management address — the service will be installed but NOT started until you set admin_ips"

# ---- 2. web dashboard --------------------------------------------------------------------------
if [ -z "$WEB" ]; then
  if [ -n "$TTY" ]; then
    say ""
    say "Web dashboard (incidents, bans, AI analyst chat; listens on localhost, reach it over an SSH tunnel):"
    say "  [1] enable with a generated password (shown once)   [2] enable with my own password   [3] skip"
    ask "Choice [1]: " 1
    case "$REPLY" in 2) WEB=prompt ;; 3) WEB=no ;; *) WEB=generate ;; esac
  else
    WEB=no
  fi
fi
WEB_PW=""
if [ "$WEB" = "prompt" ]; then
  [ -n "$TTY" ] || die "--web-password needs a terminal; use --web (generated password) instead"
  while :; do
    printf 'Dashboard password (min 12 chars): ' >/dev/tty
    stty -echo </dev/tty 2>/dev/null || true; read -r WEB_PW </dev/tty || WEB_PW=""; stty echo </dev/tty 2>/dev/null || true; printf '\n' >/dev/tty
    printf 'Repeat it: ' >/dev/tty
    stty -echo </dev/tty 2>/dev/null || true; read -r WEB_PW2 </dev/tty || WEB_PW2=""; stty echo </dev/tty 2>/dev/null || true; printf '\n' >/dev/tty
    [ "$WEB_PW" = "$WEB_PW2" ] || { say "! passwords differ — try again" >/dev/tty; continue; }
    [ "${#WEB_PW}" -ge 12 ] || { say "! too short — at least 12 characters" >/dev/tty; continue; }
    break
  done
fi

# ---- 3. shieldie.cloud -------------------------------------------------------------------------
if [ -z "$JOIN_CLOUD" ]; then
  if [ -n "$TTY" ]; then
    say ""
    say "shieldie.cloud — the shared blocklist: this host reports the addresses its jails banned (IP, jail,"
    say "count; never log lines) and applies the addresses several independent servers agreed on. Opt-in."
    ask "Join shieldie.cloud? [y/N]: " N
    case "$REPLY" in y|Y|yes|YES) JOIN_CLOUD=yes ;; *) JOIN_CLOUD=no ;; esac
  else
    JOIN_CLOUD=no
  fi
fi

# ---- download + verify --------------------------------------------------------------------------
if [ -z "$VERSION" ]; then
  VERSION="$(curl -fsSL "$BASE/releases/latest" | tr -d '[:space:]')"
  [ -n "$VERSION" ] || die "could not read $BASE/releases/latest"
fi
case "$VERSION" in v*) ;; *) VERSION="v$VERSION" ;; esac
NAME="shieldie-guard-${VERSION}-x86_64-unknown-linux-musl"
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
say ""
say "· downloading $NAME"
curl -fsSL -o "$TMP/$NAME" "$BASE/releases/$NAME" || die "download failed: $BASE/releases/$NAME"
curl -fsSL -o "$TMP/$NAME.sha256" "$BASE/releases/$NAME.sha256" || die "checksum download failed"
( cd "$TMP" && sha256sum -c --quiet "$NAME.sha256" ) || die "SHA-256 mismatch — refusing to install"
say "✓ checksum verified"
chmod 0755 "$TMP/$NAME"
"$TMP/$NAME" --version >/dev/null || die "the downloaded binary does not run on this host"

# ---- init ----------------------------------------------------------------------------------------
set -- init
for x in $ADMIN_IPS; do set -- "$@" --admin-ip "$x"; done
[ "$JOIN_CLOUD" = "yes" ] && set -- "$@" --join-cloud
[ "$WEB" = "generate" ] && set -- "$@" --web-password-generate
[ "$WEB" = "prompt" ] && set -- "$@" --web-password
say "· running: shieldie-guard $*"
if [ "$WEB" = "prompt" ]; then
  printf '%s' "$WEB_PW" | "$TMP/$NAME" "$@"
else
  "$TMP/$NAME" "$@" </dev/null
fi
unset WEB_PW WEB_PW2

if [ -z "$NO_START" ] && [ -n "$ADMIN_IPS" ]; then
  if systemctl is-active --quiet shieldie-guard; then
    say "✓ shieldie-guard is running (restarted with the new version if needed)"
  else
    systemctl start shieldie-guard && say "✓ shieldie-guard started"
  fi
  say "  check:  sudo shieldie-guard status   ·   sudo shieldie-guard jails"
else
  say "→ not started: set admin_ips in /etc/shieldie-guard/config.toml first, then  sudo systemctl start shieldie-guard"
fi
say "  AI analyst (optional):  echo -n 'sk-ant-…' | sudo shieldie-guard set-key"
