#!/bin/bash
set -euo pipefail

# 3B self-hosted bootstrap, https://sh.3b.dev/bootstrap.sh
#
#   curl -fsSL https://sh.3b.dev/bootstrap.sh | sudo bash
#
# Installs 3B on a single server.
#
# Non-interactive use: export CONNECTIVITY, VERSION, REGISTRY_USERNAME, and
# REGISTRY_PASSWORD.

export PATH="/usr/local/bin:$PATH"

OCI_REGISTRY=oci.tines.com
WORKDIR="${WORKDIR:-/opt/3b}"
ORAS_VERSION=1.2.3
ORAS_LINUX_AMD64_SHA256=b4efc97a91f471f323f193ea4b4d63d8ff443ca3aab514151a30751330852827
TTY=/dev/tty
DEFAULT_VERSION="v1.0.12"

CONNECTIVITY="${CONNECTIVITY:-}"
VERSION="${VERSION:-}"
REGISTRY_USERNAME="${REGISTRY_USERNAME:-}"
REGISTRY_PASSWORD="${REGISTRY_PASSWORD:-}"

if [ -t 1 ] && command -v tput >/dev/null 2>&1 && [ "$(tput colors 2>/dev/null || echo 0)" -ge 8 ]; then
  BOLD=$(tput bold); DIM=$(tput dim); RED=$(tput setaf 1); GREEN=$(tput setaf 2); CYAN=$(tput setaf 6); RESET=$(tput sgr0)
else
  BOLD=""; DIM=""; RED=""; GREEN=""; CYAN=""; RESET=""
fi

die() { printf '\n%sError:%s %s\n' "${RED}${BOLD}" "$RESET" "$*" >&2; exit 1; }
have_tty() { [ -r "$TTY" ] && [ -w "$TTY" ]; }
heading() { printf '\n%s==>%s %s%s%s\n' "${BOLD}${CYAN}" "$RESET" "$BOLD" "$1" "$RESET"; }
note() { printf '%s%s%s\n' "$DIM" "$1" "$RESET"; }

ask() {
  local var="$1" question="$2" default="${3:-}" reply
  [ -n "${!var:-}" ] && return 0
  have_tty || die "No terminal available to prompt for ${var}; set it as an environment variable and re-run"
  if [ -n "$default" ]; then
    printf '%s [%s]: ' "$question" "$default" > "$TTY"
    IFS= read -r reply < "$TTY"
    reply="${reply:-$default}"
  else
    printf '%s: ' "$question" > "$TTY"
    IFS= read -r reply < "$TTY"
  fi
  printf -v "$var" '%s' "$reply"
}

ask_secret() {
  local var="$1" question="$2" reply
  [ -n "${!var:-}" ] && return 0
  have_tty || die "No terminal available to prompt for ${var}; set it as an environment variable and re-run"
  printf '%s: ' "$question" > "$TTY"
  IFS= read -r -s reply < "$TTY"
  printf '\n' > "$TTY"
  printf -v "$var" '%s' "$reply"
}
step() {
  local label="$1"; shift
  local log
  log="$(mktemp)"
  printf '    %s… ' "$label"
  if "$@" >"$log" 2>&1; then
    printf '%sok%s\n' "$GREEN" "$RESET"
    rm -f "$log"
    return 0
  fi
  printf '%sfailed%s\n' "$RED" "$RESET"
  cat "$log" >&2
  rm -f "$log"
  return 1
}

pkg_install() {
  local pkg="$1"
  if command -v apt-get >/dev/null 2>&1; then
    apt-get update -y && apt-get install -y "$pkg"
  elif command -v dnf >/dev/null 2>&1; then
    dnf install -y "$pkg"
  elif command -v yum >/dev/null 2>&1; then
    yum install -y "$pkg"
  else
    echo "no supported package manager (apt-get, dnf, yum) found" >&2
    return 1
  fi
}

ensure_cmd() {
  local cmd="$1" pkg="${2:-$1}"
  command -v "$cmd" >/dev/null 2>&1 && return 0
  step "Installing ${pkg}" pkg_install "$pkg" || die "Could not install ${pkg}; install it and re-run"
}

oras_install() {
  local tmp
  tmp="$(mktemp -d)"
  curl --proto '=https' --tlsv1.2 -fsSLo "$tmp/oras.tar.gz" \
    "https://github.com/oras-project/oras/releases/download/v${ORAS_VERSION}/oras_${ORAS_VERSION}_linux_amd64.tar.gz"
  echo "${ORAS_LINUX_AMD64_SHA256}  ${tmp}/oras.tar.gz" | sha256sum -c -
  tar -xzf "$tmp/oras.tar.gz" -C "$tmp" oras
  install -m 0755 "$tmp/oras" /usr/local/bin/oras
  rm -rf "$tmp"
}

ensure_oras() {
  command -v oras >/dev/null 2>&1 && return 0
  step "Installing oras ${ORAS_VERSION}" oras_install || die "Could not install oras"
}

oras_login() {
  printf '%s' "$REGISTRY_PASSWORD" | oras login "$OCI_REGISTRY" -u "$REGISTRY_USERNAME" --password-stdin
}

printf '\n%s%s' "$BOLD" "$CYAN"
cat <<'ART'
⠀⢀⣤⣤⣄⠀⠀⣠⣤⣤⡀⠀
⠀⣿⣿⣿⣿⡇⢸⣿⣿⣿⣿⠀
⠀⠙⠿⠿⢿⣷⡈⠻⠿⠿⠋⠀
⠀⣠⣶⣶⣦⡈⢿⣷⣶⣶⣄⠀
⠀⣿⣿⣿⣿⡇⢸⣿⣿⣿⣿⠀
⠀⠈⠛⠛⠋⠀⠀⠙⠛⠛⠁
ART
printf '%s\n%s3B self-hosted installer%s\n' "$RESET" "${BOLD}${CYAN}" "$RESET"

heading "Checking the host"
[ "$(id -u)" -eq 0 ] || die "Run as root (pipe to 'sudo bash')"
[ "$(uname -s)" = Linux ] || die "Linux only"
case "$(uname -m)" in
  x86_64|amd64) ;;
  *) die "amd64 hosts only" ;;
esac
note "    running as root on amd64 Linux"
ensure_cmd curl
ensure_cmd tar

heading "Configuration"
if have_tty && [ -z "$CONNECTIVITY" ]; then
  note "    install     set up 3B on this server now"
  note "    airgapped   download the bundle to move to an offline server"
fi
while :; do
  ask CONNECTIVITY "Install or airgapped?" install
  case "$CONNECTIVITY" in
    install|airgapped) break ;;
    *) note "    Enter 'install' or 'airgapped'."; CONNECTIVITY="" ;;
  esac
done
ask VERSION "Release version (e.g. v1.0.10)" "$DEFAULT_VERSION"
[ -n "$VERSION" ] || die "A release version is required"

heading "Registry access"
if have_tty && { [ -z "$REGISTRY_USERNAME" ] || [ -z "$REGISTRY_PASSWORD" ]; }; then
  note "    Sign in to ${OCI_REGISTRY} with your Tines tenant name and API key."
fi
ask REGISTRY_USERNAME "Tines tenant name (oci.tines.com username)"
ask_secret REGISTRY_PASSWORD "Tines Docker Registry API key"
[ -n "$REGISTRY_USERNAME" ] && [ -n "$REGISTRY_PASSWORD" ] || die "Both the tenant name and API key are required"
ensure_oras
step "Logging in to ${OCI_REGISTRY}" oras_login \
  || die "Login to ${OCI_REGISTRY} failed; check the tenant name and API key"

if [ "$CONNECTIVITY" = airgapped ]; then
  REF="${OCI_REGISTRY}/3b/bundles/self-hosted:${VERSION}"
  ZIP="3b-self-hosted-${VERSION}.zip"
  heading "Downloading the release"
  oras pull "$REF"
  [ -f "$ZIP" ] || die "Expected ${ZIP} after pull but it was not found"
  heading "Bundle ready"
  printf '    %s\n' "${PWD}/${ZIP}"
  note "    Move it to the air-gapped server, then run:"
  printf '      unzip %s -d /opt/3b && cd /opt/3b && sudo bash setup.sh\n\n' "$ZIP"
  exit 0
fi

REF="${OCI_REGISTRY}/3b/bundles/self-hosted-online:${VERSION}"
ZIP="3b-self-hosted-online-${VERSION}.zip"

ensure_cmd unzip

heading "Downloading the release"
mkdir -p "$WORKDIR"
cd "$WORKDIR"
oras pull "$REF"
[ -f "$ZIP" ] || die "Expected ${ZIP} after pull but it was not found"
step "Extracting bundle" unzip -o "$ZIP"
rm -f "$ZIP"

heading "Running setup"
export REGISTRY="$OCI_REGISTRY"
export REGISTRY_USERNAME REGISTRY_PASSWORD
if have_tty; then
  bash "$WORKDIR/setup.sh" < "$TTY"
else
  bash "$WORKDIR/setup.sh"
fi
