#!/usr/bin/env bash
# ServLoci universal trading VPS installer.
# Supports current Ubuntu and Debian images on AWS, GCP, Azure, Oracle Cloud,
# Vultr, and other providers. It does not collect credentials or change SSH/
# firewall settings.
set -Eeuo pipefail

readonly INSTALL_ROOT="${SERVLOCI_INSTALL_ROOT:-/opt/servloci}"
readonly VENV_ROOT="${INSTALL_ROOT}/venvs"
readonly SDK_SPEC="${SERVLOCI_SDK_SPEC:-trading-static-ip[socks]>=0.3.1}"
readonly REQUESTED_BROKERS="${SERVLOCI_BROKERS:-all}"
readonly ENROLL_TOKEN="${SERVLOCI_ENROLL_TOKEN:-}"
readonly PROVIDER="${SERVLOCI_PROVIDER:-other}"
readonly SERVICE_URL="${SERVLOCI_SERVICE_URL:-https://comm.servloci.in}"
readonly INSTALLER_VERSION="1.1.0"

declare -Ar BROKER_PACKAGES=(
  [dhan]="dhanhq"
  [kite]="kiteconnect"
  [groww]="growwapi"
  [fyers]="fyers-apiv3"
  [upstox]="upstox-python-sdk"
  [icicidirect]="breeze-connect"
  [kotak]="git+https://github.com/Kotak-Neo/Kotak-neo-api-v2.git@v2.0.1#egg=neo_api_client"
)
readonly ALL_BROKERS=(dhan kite groww fyers upstox icicidirect kotak)

log() { printf '[servloci] %s\n' "$*"; }
die() { printf '[servloci] ERROR: %s\n' "$*" >&2; exit 1; }

if [[ ${EUID} -ne 0 ]]; then
  die "run as root: sudo bash install.sh"
fi

[[ -r /etc/os-release ]] || die "cannot detect Linux distribution"
# shellcheck disable=SC1091
source /etc/os-release
os_family="${ID:-} ${ID_LIKE:-}"
if [[ " ${os_family} " != *" ubuntu "* && " ${os_family} " != *" debian "* ]]; then
  die "unsupported OS '${PRETTY_NAME:-unknown}'. Use Ubuntu 22.04/24.04 or Debian 12."
fi

case "$(uname -m)" in
  x86_64|aarch64|arm64) ;;
  *) die "unsupported CPU architecture: $(uname -m)" ;;
esac

select_brokers() {
  if [[ "${REQUESTED_BROKERS}" == "all" ]]; then
    printf '%s\n' "${ALL_BROKERS[@]}"
    return
  fi
  local raw broker
  IFS=',' read -ra raw <<< "${REQUESTED_BROKERS}"
  for broker in "${raw[@]}"; do
    broker="${broker//[[:space:]]/}"
    [[ -n "${BROKER_PACKAGES[${broker}]:-}" ]] || die "unknown broker '${broker}'"
    printf '%s\n' "${broker}"
  done
}

mapfile -t brokers < <(select_brokers)
[[ ${#brokers[@]} -gt 0 ]] || die "no brokers selected"

export DEBIAN_FRONTEND=noninteractive
log "Installing operating-system prerequisites on ${PRETTY_NAME:-Linux}…"
apt-get update -qq
apt-get install -y -qq --no-install-recommends \
  ca-certificates curl git jq build-essential \
  python3 python3-pip python3-venv

install_env() {
  local broker="$1" package="$2" env_dir="${VENV_ROOT}/${broker}"
  log "Preparing ${broker} environment (${package})…"
  python3 -m venv "${env_dir}"
  "${env_dir}/bin/python" -m pip install --quiet --disable-pip-version-check --upgrade pip setuptools wheel
  "${env_dir}/bin/python" -m pip install --quiet --disable-pip-version-check --upgrade "${SDK_SPEC}" "${package}"
}

install -d -m 0755 "${VENV_ROOT}" "${INSTALL_ROOT}/examples"
failed=()
for broker in "${brokers[@]}"; do
  if ! install_env "${broker}" "${BROKER_PACKAGES[${broker}]}"; then
    failed+=("${broker}")
  fi
done

install -m 0755 /dev/stdin /usr/local/bin/servloci-python <<'WRAPPER'
#!/usr/bin/env bash
set -euo pipefail
root="${SERVLOCI_INSTALL_ROOT:-/opt/servloci}"
broker="${1:-}"
if [[ -z "${broker}" || ! -x "${root}/venvs/${broker}/bin/python" ]]; then
  echo "Usage: servloci-python <dhan|kite|groww|fyers|upstox|icicidirect|kotak> [python arguments]" >&2
  exit 2
fi
shift
exec "${root}/venvs/${broker}/bin/python" "$@"
WRAPPER

install -m 0755 /dev/stdin /usr/local/bin/servloci-pip <<'WRAPPER'
#!/usr/bin/env bash
set -euo pipefail
root="${SERVLOCI_INSTALL_ROOT:-/opt/servloci}"
broker="${1:-}"
if [[ -z "${broker}" || ! -x "${root}/venvs/${broker}/bin/python" ]]; then
  echo "Usage: servloci-pip <dhan|kite|groww|fyers|upstox|icicidirect|kotak> [pip arguments]" >&2
  exit 2
fi
shift
exec "${root}/venvs/${broker}/bin/python" -m pip "$@"
WRAPPER

install -m 0755 /dev/stdin /usr/local/bin/servloci-doctor <<'DOCTOR'
#!/usr/bin/env bash
set -uo pipefail
root="${SERVLOCI_INSTALL_ROOT:-/opt/servloci}"
printf 'ServLoci VPS doctor\n'
printf 'OS:     '; . /etc/os-release && printf '%s\n' "${PRETTY_NAME:-unknown}"
printf 'Kernel: %s %s\n' "$(uname -s)" "$(uname -m)"
printf 'IPv4:   '; curl -4fsS --max-time 8 https://api.ipify.org || printf 'not available'
printf '\nIPv6:   '; curl -6fsS --max-time 8 https://api6.ipify.org || printf 'not available'
printf '\nEnvironments:\n'
found=0
for env_dir in "${root}"/venvs/*; do
  [[ -x "${env_dir}/bin/python" ]] || continue
  found=1
  broker="$(basename "${env_dir}")"
  version="$(${env_dir}/bin/python -c 'import sys; print(".".join(map(str, sys.version_info[:3])))' 2>/dev/null || printf broken)"
  printf '  %-12s Python %s\n' "${broker}" "${version}"
done
[[ ${found} -eq 1 ]] || printf '  none\n'
DOCTOR

cat > "${INSTALL_ROOT}/README.txt" <<'README'
ServLoci trading VPS tools

Run a script:
  servloci-python dhan bot.py

Install an extra package into one broker environment:
  sudo servloci-pip dhan install pandas

Check public IPv4/IPv6 and installed environments:
  servloci-doctor

Broker credentials are not managed by this installer. Store them in your
cloud secret manager or a root/user-readable environment file (mode 0600),
never in source control.
README
chmod 0644 "${INSTALL_ROOT}/README.txt"

if [[ ${#failed[@]} -gt 0 ]]; then
  printf '[servloci] Installation finished with failures: %s\n' "${failed[*]}" >&2
  printf '[servloci] Re-run the installer, or install one package with servloci-pip.\n' >&2
  exit 1
fi

machine_facts_json() {
  local ipv4 ipv6 broker_json
  ipv4="$(curl -4fsS --max-time 8 https://api.ipify.org 2>/dev/null || true)"
  ipv6="$(curl -6fsS --max-time 8 https://api6.ipify.org 2>/dev/null || true)"
  broker_json="$(for env_dir in "${VENV_ROOT}"/*; do
    [[ -x "${env_dir}/bin/python" ]] && basename "${env_dir}"
  done | jq -Rsc 'split("\n") | map(select(length > 0))')"
  jq -n \
    --arg hostname "$(hostname -f 2>/dev/null || hostname)" \
    --arg provider "${PROVIDER}" \
    --arg os_name "${PRETTY_NAME:-Linux}" \
    --arg architecture "$(uname -m)" \
    --arg ipv4 "${ipv4}" \
    --arg ipv6 "${ipv6}" \
    --arg installer_version "${INSTALLER_VERSION}" \
    --argjson brokers "${broker_json}" \
    '{hostname:$hostname,provider:$provider,os_name:$os_name,architecture:$architecture,ipv4:$ipv4,ipv6:$ipv6,brokers:$brokers,installer_version:$installer_version}'
}

install_agent() {
  local machine_id="$1" machine_token="$2"
  install -d -m 0700 /etc/servloci
  umask 077
  {
    printf 'SERVLOCI_SERVICE_URL=%s\n' "${SERVICE_URL}"
    printf 'SERVLOCI_MACHINE_ID=%s\n' "${machine_id}"
    printf 'SERVLOCI_MACHINE_TOKEN=%s\n' "${machine_token}"
    printf 'SERVLOCI_PROVIDER=%s\n' "${PROVIDER}"
    printf 'SERVLOCI_INSTALL_ROOT=%s\n' "${INSTALL_ROOT}"
    printf 'SERVLOCI_INSTALLER_VERSION=%s\n' "${INSTALLER_VERSION}"
  } > /etc/servloci/agent.env
  chmod 0600 /etc/servloci/agent.env

  install -m 0755 /dev/stdin /usr/local/bin/servloci-agent <<'AGENT'
#!/usr/bin/env bash
set -Eeuo pipefail
# shellcheck disable=SC1091
source /etc/servloci/agent.env
root="${SERVLOCI_INSTALL_ROOT:-/opt/servloci}"
ipv4="$(curl -4fsS --max-time 8 https://api.ipify.org 2>/dev/null || true)"
ipv6="$(curl -6fsS --max-time 8 https://api6.ipify.org 2>/dev/null || true)"
brokers="$(for env_dir in "${root}"/venvs/*; do
  [[ -x "${env_dir}/bin/python" ]] && basename "${env_dir}"
done | jq -Rsc 'split("\n") | map(select(length > 0))')"
. /etc/os-release
payload="$(jq -n \
  --arg hostname "$(hostname -f 2>/dev/null || hostname)" \
  --arg provider "${SERVLOCI_PROVIDER:-other}" \
  --arg os_name "${PRETTY_NAME:-Linux}" \
  --arg architecture "$(uname -m)" \
  --arg ipv4 "${ipv4}" \
  --arg ipv6 "${ipv6}" \
  --arg installer_version "${SERVLOCI_INSTALLER_VERSION:-unknown}" \
  --argjson brokers "${brokers}" \
  '{hostname:$hostname,provider:$provider,os_name:$os_name,architecture:$architecture,ipv4:$ipv4,ipv6:$ipv6,brokers:$brokers,installer_version:$installer_version}')"
curl -fsS --max-time 20 \
  -X POST "${SERVLOCI_SERVICE_URL}/api/installations/${SERVLOCI_MACHINE_ID}/heartbeat" \
  -H "Authorization: Bearer ${SERVLOCI_MACHINE_TOKEN}" \
  -H 'Content-Type: application/json' \
  --data-binary "${payload}" >/dev/null
AGENT

  install -m 0644 /dev/stdin /etc/systemd/system/servloci-agent.service <<'UNIT'
[Unit]
Description=ServLoci installation inventory heartbeat
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/servloci-agent
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadOnlyPaths=/opt/servloci /etc/servloci
UNIT

  install -m 0644 /dev/stdin /etc/systemd/system/servloci-agent.timer <<'TIMER'
[Unit]
Description=Report ServLoci installation health every five minutes

[Timer]
OnBootSec=2min
OnUnitActiveSec=5min
RandomizedDelaySec=30s
Persistent=true

[Install]
WantedBy=timers.target
TIMER

  install -m 0755 /dev/stdin /usr/local/bin/servloci-agent-uninstall <<'UNINSTALL'
#!/usr/bin/env bash
set -Eeuo pipefail
if [[ ${EUID} -ne 0 ]]; then
  echo "run as root: sudo servloci-agent-uninstall" >&2
  exit 1
fi
if command -v systemctl >/dev/null 2>&1; then
  systemctl disable --now servloci-agent.timer 2>/dev/null || true
fi
rm -f /etc/systemd/system/servloci-agent.timer
rm -f /etc/systemd/system/servloci-agent.service
rm -f /etc/servloci/agent.env
if command -v systemctl >/dev/null 2>&1; then
  systemctl daemon-reload 2>/dev/null || true
fi
echo "ServLoci machine heartbeat removed. Broker environments were kept."
UNINSTALL

  /usr/local/bin/servloci-agent
  if [[ -d /run/systemd/system ]] && command -v systemctl >/dev/null 2>&1; then
    systemctl daemon-reload
    systemctl enable --now servloci-agent.timer
    log "Managed heartbeat enabled (every five minutes)."
  else
    log "Systemd is unavailable; run servloci-agent manually to refresh portal status."
  fi
}

if [[ -n "${ENROLL_TOKEN}" ]]; then
  log "Registering this machine with the ServLoci installation service…"
  enroll_response="$(curl -fsS --max-time 30 \
    -X POST "${SERVICE_URL}/api/installations/enroll" \
    -H "Authorization: Bearer ${ENROLL_TOKEN}" \
    -H 'Content-Type: application/json' \
    --data-binary "$(machine_facts_json)")" || die "installation succeeded, but service enrollment failed"
  machine_id="$(jq -er '.machine_id' <<<"${enroll_response}")" || die "enrollment response did not contain a machine id"
  machine_token="$(jq -er '.machine_token' <<<"${enroll_response}")" || die "enrollment response did not contain a machine token"
  install_agent "${machine_id}" "${machine_token}"
  log "Machine registered: ${machine_id}"
fi

log "Installation complete. No broker credentials were requested or stored."
printf '\nNext commands:\n'
printf '  servloci-doctor\n'
printf '  servloci-python %s --version\n' "${brokers[0]}"
printf '  servloci-python %s bot.py\n' "${brokers[0]}"
