#!/usr/bin/env bash
# frp-ip-whitelist-plugin: install or uninstall systemd service (binary from CDN) and optional frps.toml hook.
#
#   sudo ./install-frp-ip-whitelist-plugin.sh install
#   sudo ./install-frp-ip-whitelist-plugin.sh uninstall
#
# Install env: PLUGIN_DOWNLOAD_URL, INSTALL_DIR (default /usr/local/frps/plugin/frp-ip-whitelist-plugin), PORT, FRPS_TOML, ...
# Web UI auth defaults: PLUGIN_USER=admin, PLUGIN_PASSWORD=Passw0rd@123$ (only if PLUGIN_PASSWORD unset).
# Interactive prompts unless SKIP_PLUGIN_CREDENTIAL_PROMPT=1 or stdin is not a TTY.
# Listen port: prompted when TTY (shows default); set PORT= or SKIP_PLUGIN_PORT_PROMPT=1; SKIP_PORT_CHECK=1 skips busy check.
# Uninstall (non-interactive): UNINSTALL_ALL=1 or REMOVE_DEFAULT / REMOVE_INSTALL_FILES / REMOVE_FRPS_TOML =1

set -euo pipefail

# Colors when stdout is a TTY (avoid escape codes in piped/cron logs)
if [[ -t 1 ]]; then
  NC=$'\033[0m'
  BOLD=$'\033[1m'
  RED=$'\033[0;31m'
  GREEN=$'\033[0;32m'
  YELLOW=$'\033[1;33m'
  BLUE=$'\033[0;34m'
  CYAN=$'\033[0;36m'
  DIM=$'\033[2m'
else
  NC='' BOLD='' RED='' GREEN='' YELLOW='' BLUE='' CYAN='' DIM=''
fi

INSTALL_DIR="${INSTALL_DIR:-/usr/local/frps/plugin/frp-ip-whitelist-plugin}"
BINARY_NAME="${BINARY_NAME:-frp-ip-whitelist-plugin}"
SERVICE_NAME="${SERVICE_NAME:-frp-ip-whitelist-plugin}"

PORT="${PORT:-7300}"
PLUGIN_USER="${PLUGIN_USER:-admin}"
# Default Web UI password when unset; set PLUGIN_PASSWORD= before install to disable auth (empty user+pass).
if [[ -z "${PLUGIN_PASSWORD+x}" ]]; then
  PLUGIN_PASSWORD='Passw0rd@123$'
fi
DEBUG="${DEBUG:-0}"
GEO_DISABLE="${GEO_DISABLE:-0}"

DEFAULT_FILE="/etc/default/${SERVICE_NAME}"
UNIT_FILE="/etc/systemd/system/${SERVICE_NAME}.service"
FRPS_TOML="${FRPS_TOML:-/usr/local/frps/frps.toml}"
# Cartech CDN (override for mirrors): https://dl.cartech.one/dl/CAR%20PROXY%20SERVER/plugin/CAR%20PROXY%20WHITELIST/
PLUGIN_DOWNLOAD_URL="${PLUGIN_DOWNLOAD_URL:-https://dl.cartech.one/dl/CAR%20PROXY%20SERVER/plugin/CAR%20PROXY%20WHITELIST/frp-ip-whitelist-plugin}"

die() {
  echo -e "${RED}Error:${NC} $*" >&2
  exit 1
}

usage() {
  cat << EOF
frp-ip-whitelist-plugin — install or uninstall systemd service (downloads binary; needs wget or curl).

Usage:
  sudo $0 install
  sudo $0 uninstall

Install: optional env PLUGIN_DOWNLOAD_URL, PORT (default 7300), UPDATE_FRPS_TOML, SKIP_FRPS_TOML, SKIP_PLUGIN_CREDENTIAL_PROMPT, SKIP_PLUGIN_PORT_PROMPT, SKIP_PORT_CHECK, FRPS_TOML, ...

Uninstall: optional env UNINSTALL_ALL, REMOVE_DEFAULT, REMOVE_INSTALL_FILES, REMOVE_FRPS_TOML
EOF
}

download_plugin_binary() {
  local tmp="${INSTALL_DIR}/.${BINARY_NAME}.download.$$"
  mkdir -p "${INSTALL_DIR}"
  echo -e "${BLUE}==>${NC} Downloading ${BINARY_NAME} from ${PLUGIN_DOWNLOAD_URL}"
  if command -v wget >/dev/null 2>&1; then
    wget -4 --no-check-certificate -qO "${tmp}" "${PLUGIN_DOWNLOAD_URL}"
  elif command -v curl >/dev/null 2>&1; then
    curl -4fsSL -o "${tmp}" "${PLUGIN_DOWNLOAD_URL}"
  else
    rm -f "${tmp}"
    die "Need wget or curl to download the plugin binary"
  fi
  if [[ ! -s "${tmp}" ]]; then
    rm -f "${tmp}"
    die "Downloaded file is empty"
  fi
  install -m 0755 "${tmp}" "${INSTALL_DIR}/${BINARY_NAME}"
  rm -f "${tmp}"
}

systemd_escape_double() {
  local s=$1
  s=${s//\\/\\\\}
  s=${s//\"/\\\"}
  s=${s//\$/\\\$}
  printf '%s' "$s"
}

write_default_file() {
  local path="$1"
  umask 077
  {
    echo "# ${SERVICE_NAME} — edit and run: systemctl restart ${SERVICE_NAME}"
    echo "PORT=${PORT}"
    echo "DATA_DIR=${INSTALL_DIR}"
    printf 'PLUGIN_USER="%s"\n' "$(systemd_escape_double "$PLUGIN_USER")"
    printf 'PLUGIN_PASSWORD="%s"\n' "$(systemd_escape_double "$PLUGIN_PASSWORD")"
    echo "DEBUG=${DEBUG}"
    echo "GEO_DISABLE=${GEO_DISABLE}"
  } > "${path}"
  chmod 0600 "${path}"
}

prompt_plugin_web_credentials() {
  [[ "${SKIP_PLUGIN_CREDENTIAL_PROMPT:-}" == "1" ]] && return 0
  [[ ! -t 0 ]] && return 0
  local REPLY="" p=""
  echo ""
  echo -e "${CYAN}==>${NC} ${BOLD}(2/2) Web UI${NC} — session login (browser and /api; frps /handler is not protected)."
  echo -e "${DIM}Defaults: username admin, password Passw0rd@123\$ — press Enter to keep current or default.${NC}"
  read -r -p "${YELLOW}Web UI username [${PLUGIN_USER}]: ${NC}" REPLY || true
  if [[ -n "${REPLY:-}" ]]; then
    PLUGIN_USER="${REPLY}"
  fi
  read -r -sp "${YELLOW}Web UI password [Enter = keep default/env]: ${NC}" p || true
  echo ""
  if [[ -n "${p:-}" ]]; then
    PLUGIN_PASSWORD="${p}"
  fi
}

effective_plugin_port() {
  local p
  if [[ -f "${DEFAULT_FILE}" ]] && p=$(grep '^PORT=' "${DEFAULT_FILE}" | head -1 | cut -d= -f2-); then
    echo "${p//[[:space:]]/}"
  else
    echo "${PORT}"
  fi
}

# Same pattern as plugin/install-all.sh — detect TCP listeners on a port.
is_port_in_use() {
  local port="$1"
  ss -tuln 2>/dev/null | grep -qE ":${port}([[:space:]]|$)" || \
    netstat -tuln 2>/dev/null | grep -qE ":${port}[[:space:]]" || \
    lsof -i ":${port}" -sTCP:LISTEN 2>/dev/null | grep -q .
}

print_port_listeners() {
  local port="$1"
  echo -e "${CYAN}    Listening on TCP ${port}:${NC}" >&2
  if command -v ss >/dev/null 2>&1; then
    ss -tulnp 2>/dev/null | grep -E ":${port}([[:space:]]|$)" >&2 || true
  fi
  if command -v lsof >/dev/null 2>&1; then
    lsof -nP -iTCP:"${port}" -sTCP:LISTEN 2>/dev/null >&2 || true
  fi
}

# 0 = OK; 1 = invalid port; 2 = in use by something other than this plugin on same configured port
port_status_for_install() {
  local port="$1"
  if ! [[ "${port}" =~ ^[0-9]+$ ]] || [[ "${port}" -lt 1 ]] || [[ "${port}" -gt 65535 ]]; then
    return 1
  fi
  if ! is_port_in_use "${port}"; then
    return 0
  fi
  if [[ -f "${DEFAULT_FILE}" ]] && systemctl is-active --quiet "${SERVICE_NAME}" 2>/dev/null; then
    local ep
    ep=$(grep '^PORT=' "${DEFAULT_FILE}" | head -1 | cut -d= -f2-)
    ep="${ep//[[:space:]]/}"
    if [[ "${ep}" == "${port}" ]]; then
      return 0
    fi
  fi
  return 2
}

validate_listen_port_noninteractive() {
  [[ "${SKIP_PORT_CHECK:-}" == "1" ]] && return 0
  local st
  # set -e: must not call port_status_for_install bare — non-zero return would exit the script before st=$?
  if port_status_for_install "${PORT}"; then
    st=0
  else
    st=$?
  fi
  if [[ "${st}" -eq 1 ]]; then
    die "Invalid PORT=${PORT} (use 1-65535)"
  fi
  if [[ "${st}" -eq 2 ]]; then
    print_port_listeners "${PORT}"
    die "Port ${PORT} is already in use. Set PORT=, free the port, or SKIP_PORT_CHECK=1."
  fi
}

prompt_plugin_listen_port() {
  local builtin_default="7300"

  if [[ "${SKIP_PORT_CHECK:-}" == "1" ]]; then
    echo -e "${YELLOW}==>${NC} Listen port: ${BOLD}${PORT}${NC} (${YELLOW}SKIP_PORT_CHECK=1${NC} — not checking if port is busy)"
    return 0
  fi

  if [[ "${SKIP_PLUGIN_PORT_PROMPT:-}" == "1" ]] || [[ ! -t 0 ]]; then
    validate_listen_port_noninteractive
    echo -e "${GREEN}==>${NC} Listen port: ${BOLD}${PORT}${NC} (${DIM}non-interactive: set PORT= before install, or use built-in default ${builtin_default}${NC})"
    return 0
  fi

  local candidate="${PORT}"
  if [[ -f "${DEFAULT_FILE}" ]]; then
    local p
    p=$(grep '^PORT=' "${DEFAULT_FILE}" | head -1 | cut -d= -f2-)
    candidate="${p//[[:space:]]/}"
  fi

  echo ""
  echo -e "${CYAN}==>${NC} ${BOLD}(1/2) Listen TCP port${NC} — Web UI and frps httpPlugins (${DIM}http://127.0.0.1:PORT${NC})"
  echo -e "    ${DIM}Built-in default for this plugin:${NC} ${BOLD}${builtin_default}${NC}"
  [[ -f "${DEFAULT_FILE}" ]] && echo -e "    ${DIM}Existing ${DEFAULT_FILE}:${NC} PORT=${candidate}"
  echo -e "    ${DIM}Env PORT=${PORT} — press Enter for [bracket] default, or type another port.${NC}"

  while true; do
    local line="" pick="" st=""
    read -r -p "${YELLOW}Port [${candidate}]: ${NC}" line || true
    pick="${line:-$candidate}"
    pick="${pick//[[:space:]]/}"
    if port_status_for_install "${pick}"; then
      st=0
    else
      st=$?
    fi
    if [[ "${st}" -eq 1 ]]; then
      echo -e "${RED}Invalid port (1-65535). Try again.${NC}" >&2
      continue
    fi
    if [[ "${st}" -eq 2 ]]; then
      echo -e "${RED}Port ${pick} is already in use.${NC}" >&2
      print_port_listeners "${pick}"
      local ans=""
      read -r -p "${YELLOW}Try another port? [y/N] ${NC}" ans || true
      ans="${ans,,}"
      if [[ "${ans}" == "y" || "${ans}" == "yes" ]]; then
        continue
      fi
      die "Aborted (port in use)."
    fi
    PORT="${pick}"
    echo -e "${GREEN}==>${NC} Listen port set to ${BOLD}${PORT}${NC}"
    break
  done
}

kill_listeners_on_port_tcp() {
  local port="${1:?}"
  [[ "${port}" =~ ^[0-9]+$ ]] || return 0
  echo -e "${YELLOW}==>${NC} Freeing TCP port ${BOLD}${port}${NC} (stops any leftover plugin or manual process)"
  if command -v fuser >/dev/null 2>&1; then
    fuser -k "${port}/tcp" 2>/dev/null || true
    return 0
  fi
  if command -v lsof >/dev/null 2>&1; then
    lsof -t -i TCP:"${port}" -s TCP:LISTEN 2>/dev/null | xargs -r kill 2>/dev/null || true
  fi
}

restart_frps() {
  if command -v frps >/dev/null 2>&1; then
    if frps restart; then
      echo -e "${GREEN}==>${NC} frps restarted (frps restart)"
      return 0
    fi
  fi
  if [[ -x /etc/init.d/frps ]]; then
    if /etc/init.d/frps restart; then
      echo -e "${GREEN}==>${NC} frps restarted (/etc/init.d/frps restart)"
      return 0
    fi
  fi
  if systemctl list-unit-files 2>/dev/null | grep -qE '^frps\.service'; then
    if systemctl restart frps; then
      echo -e "${GREEN}==>${NC} frps restarted (systemctl restart frps)"
      return 0
    fi
  fi
  echo -e "${YELLOW}Warning:${NC} could not restart frps. Run: frps restart" >&2
  return 1
}

should_update_frps_toml() {
  if [[ -n "${SKIP_FRPS_TOML:-}" ]]; then
    return 1
  fi
  if [[ "${UPDATE_FRPS_TOML:-}" == "1" || "${UPDATE_FRPS_TOML:-}" == "yes" ]]; then
    return 0
  fi
  if [[ ! -t 0 ]]; then
    echo -e "${YELLOW}==>${NC} Not a TTY; skipping frps.toml (${DIM}set UPDATE_FRPS_TOML=1 to patch ${FRPS_TOML}${NC})."
    return 1
  fi
  local ans
  read -r -p "${YELLOW}Update ${FRPS_TOML} with httpPlugins for ip-whitelist (NewUserConn) and restart frps? [y/N] ${NC}" ans || true
  ans="${ans,,}"
  [[ "${ans}" == "y" || "${ans}" == "yes" ]]
}

apply_frps_toml_plugin_whitelist() {
  local tom="${1:?}"
  local plug_port="${2:?}"
  local bak

  if [[ ! -f "${tom}" ]]; then
    echo "Warning: ${tom} not found; add httpPlugins manually." >&2
    return 1
  fi

  bak="${tom}.bak.$(date +%Y%m%d%H%M%S)"
  cp -a "${tom}" "${bak}"
  echo -e "${BLUE}==>${NC} Backup: ${DIM}${bak}${NC}"

  if grep -q 'name = "ip-whitelist"' "${tom}"; then
    if sed -i "/ip-whitelist/s|addr = \"http://127.0.0.1:[0-9]*\"|addr = \"http://127.0.0.1:${plug_port}\"|" "${tom}"; then
      echo -e "${GREEN}==>${NC} Updated plugin addr port to ${BOLD}${plug_port}${NC} in ${tom}"
      return 0
    fi
    die "sed failed updating ${tom}"
  fi

  if grep -E -q '^[[:space:]]*httpPlugins[[:space:]]*=' "${tom}"; then
    rm -f "${bak}"
    echo "Warning: ${tom} already has httpPlugins without ip-whitelist." >&2
    echo "Add this entry to the httpPlugins array (port ${plug_port}), then restart frps:" >&2
    echo "  { name = \"ip-whitelist\", addr = \"http://127.0.0.1:${plug_port}\", path = \"/handler\", ops = [\"NewUserConn\"] }" >&2
    return 1
  fi

  {
    echo ""
    echo "# Per-proxy source IP allowlist (NewUserConn). frp-ip-whitelist-plugin"
    echo "httpPlugins = ["
    echo "  { name = \"ip-whitelist\", addr = \"http://127.0.0.1:${plug_port}\", path = \"/handler\", ops = [\"NewUserConn\"] }"
    echo "]"
  } >> "${tom}"
  echo -e "${GREEN}==>${NC} Appended httpPlugins block to ${tom}"
  return 0
}

remove_frps_toml_plugin_whitelist() {
  local tom="${1:?}"
  local bak tmp

  if [[ ! -f "${tom}" ]]; then
    echo "Warning: ${tom} not found; skip." >&2
    return 1
  fi

  if ! grep -q 'name = "ip-whitelist"' "${tom}"; then
    echo "No ip-whitelist entry in ${tom}; nothing to remove."
    return 0
  fi

  bak="${tom}.bak.$(date +%Y%m%d%H%M%S)"
  cp -a "${tom}" "${bak}"
  echo -e "${BLUE}==>${NC} Backup: ${DIM}${bak}${NC}"

  tmp="$(mktemp)"
  if command -v perl >/dev/null 2>&1; then
    perl -0777 -ne '
      s/\r\n/\n/g;
      s/\n# Per-proxy source IP allowlist \(NewUserConn\)\. frp-ip-whitelist-plugin\nhttpPlugins = \[\n  \{ name = "ip-whitelist", addr = "http:\/\/127.0.0.1:\d+", path = "\/handler", ops = \["NewUserConn"\] \}\n\]\n/\n/s;
      print;
    ' "${tom}" > "${tmp}"
  else
    die "perl is required to remove the frps.toml block safely; remove httpPlugins manually"
  fi

  if cmp -s "${tom}" "${tmp}"; then
    rm -f "${tmp}"
    echo "Warning: could not auto-remove plugin block (merged or edited config?). Edit ${tom} manually." >&2
    mv "${bak}" "${tom}"
    return 1
  fi

  mv "${tmp}" "${tom}"
  echo -e "${GREEN}==>${NC} Removed ip-whitelist httpPlugins stanza from ${tom}"
  return 0
}

yes_no() {
  local msg="$1"
  if [[ ! -t 0 ]]; then
    return 1
  fi
  local ans
  read -r -p "${YELLOW}${msg} [y/N] ${NC}" ans || true
  ans="${ans,,}"
  [[ "${ans}" == "y" || "${ans}" == "yes" ]]
}

should_remove_on_uninstall() {
  local val="${1:-}"
  local msg="$2"
  [[ "${UNINSTALL_ALL:-}" == "1" ]] && return 0
  [[ "${val}" == "1" ]] && return 0
  yes_no "${msg}"
}

cmd_install() {
  echo ""
  echo -e "${BLUE}=====================================================================${NC}"
  echo -e "${BOLD}${CYAN}  ${SERVICE_NAME}${NC} install"
  echo -e "  ${YELLOW}Step 1:${NC} listen port (change here or set ${BOLD}PORT=${NC} before running)"
  echo -e "  ${YELLOW}Step 2:${NC} Web UI login (username/password)"
  echo -e "${BLUE}=====================================================================${NC}"
  prompt_plugin_listen_port
  echo ""
  prompt_plugin_web_credentials

  echo -e "${BLUE}==>${NC} Installing to ${BOLD}${INSTALL_DIR}${NC}"
  download_plugin_binary

  if [[ ! -f "${INSTALL_DIR}/proxy_ip_whitelist.json" ]]; then
    echo '{"proxies":{}}' > "${INSTALL_DIR}/proxy_ip_whitelist.json"
    chmod 0644 "${INSTALL_DIR}/proxy_ip_whitelist.json"
    echo "    created ${INSTALL_DIR}/proxy_ip_whitelist.json"
  fi
  if [[ ! -f "${INSTALL_DIR}/ip_whitelist_settings.json" ]]; then
    echo '{"strictUnlisted":false}' > "${INSTALL_DIR}/ip_whitelist_settings.json"
    chmod 0644 "${INSTALL_DIR}/ip_whitelist_settings.json"
    echo "    created ${INSTALL_DIR}/ip_whitelist_settings.json"
  fi

  if [[ ! -f "${DEFAULT_FILE}" ]]; then
    write_default_file "${DEFAULT_FILE}"
    echo -e "${GREEN}==>${NC} Created ${DEFAULT_FILE} (${DIM}edit credentials; DATA_DIR=${INSTALL_DIR}${NC})"
  else
    echo -e "${YELLOW}==>${NC} Keeping existing ${DEFAULT_FILE}"
  fi

  cat > "${UNIT_FILE}" << EOF
[Unit]
Description=FRP per-proxy IP whitelist HTTP plugin
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=root
WorkingDirectory=${INSTALL_DIR}
EnvironmentFile=-${DEFAULT_FILE}
ExecStart=${INSTALL_DIR}/${BINARY_NAME}
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

  chmod 0644 "${UNIT_FILE}"

  echo -e "${BLUE}==>${NC} Enabling systemd unit"
  systemctl daemon-reload
  systemctl enable "${SERVICE_NAME}"
  systemctl restart "${SERVICE_NAME}"

  echo ""
  echo -e "${GREEN}Done.${NC} Plugin listening on port from ${BOLD}${DEFAULT_FILE}${NC} (${DIM}default ${PORT}${NC})."
  echo -e "  ${DIM}systemctl status ${SERVICE_NAME}${NC}"
  echo -e "  ${DIM}journalctl -u ${SERVICE_NAME} -f${NC}"

  EFFECTIVE_PORT="$(effective_plugin_port)"

  if should_update_frps_toml; then
    if apply_frps_toml_plugin_whitelist "${FRPS_TOML}" "${EFFECTIVE_PORT}"; then
      restart_frps || true
    fi
  else
    echo ""
    echo -e "${YELLOW}Configure frps${NC} to match plugin port ${BOLD}${EFFECTIVE_PORT}${NC}, e.g. in ${FRPS_TOML}:"
    echo '  httpPlugins = ['
    echo "    { name = \"ip-whitelist\", addr = \"http://127.0.0.1:${EFFECTIVE_PORT}\", path = \"/handler\", ops = [\"NewUserConn\"] }"
    echo '  ]'
    echo "Or use [[httpPlugins]] tables with the same name, addr, path, and ops."
  fi
}

cmd_uninstall() {
  local uninstall_port
  uninstall_port="$(effective_plugin_port)"

  echo -e "${YELLOW}==>${NC} Stopping and disabling ${SERVICE_NAME}"
  systemctl stop "${SERVICE_NAME}" 2>/dev/null || true
  systemctl disable "${SERVICE_NAME}" 2>/dev/null || true

  if [[ -f "${UNIT_FILE}" ]]; then
    rm -f "${UNIT_FILE}"
    echo -e "${GREEN}==>${NC} Removed ${UNIT_FILE}"
  fi

  systemctl daemon-reload

  kill_listeners_on_port_tcp "${uninstall_port}"

  if should_remove_on_uninstall "${REMOVE_DEFAULT:-}" "Remove ${DEFAULT_FILE}?"; then
    rm -f "${DEFAULT_FILE}"
    echo -e "${GREEN}==>${NC} Removed ${DEFAULT_FILE}"
  fi

  if should_remove_on_uninstall "${REMOVE_INSTALL_FILES:-}" "Remove ${INSTALL_DIR}/${BINARY_NAME} and data files (proxy_ip_whitelist.json, ip_whitelist_settings.json)?"; then
    rm -f "${INSTALL_DIR}/${BINARY_NAME}" "${INSTALL_DIR}/proxy_ip_whitelist.json" "${INSTALL_DIR}/ip_whitelist_settings.json"
    echo -e "${GREEN}==>${NC} Removed plugin binary and data files under ${INSTALL_DIR}"
    if [[ -d "${INSTALL_DIR}" ]] && [[ -z "$(ls -A "${INSTALL_DIR}" 2>/dev/null)" ]]; then
      rmdir "${INSTALL_DIR}" 2>/dev/null && echo -e "${GREEN}==>${NC} Removed empty ${INSTALL_DIR}"
    fi
  fi

  if should_remove_on_uninstall "${REMOVE_FRPS_TOML:-}" "Remove ip-whitelist stanza from ${FRPS_TOML} and restart frps?"; then
    if remove_frps_toml_plugin_whitelist "${FRPS_TOML}"; then
      restart_frps || true
    fi
  fi

  echo ""
  echo "Uninstall finished (systemd unit removed)."
}

# --- main ---

if [[ "$(id -u)" -ne 0 ]]; then
  die "Run as root: sudo $0 install|uninstall"
fi

ACTION="${1:-}"
case "${ACTION}" in
  install)
    cmd_install
    ;;
  uninstall)
    cmd_uninstall
    ;;
  -h|--help|help)
    usage
    ;;
  *)
    usage
    exit 1
    ;;
esac
