#!/usr/bin/env bash
# Curiosity Studio for macOS and Linux, in a container.
#
#   curl -fsSL https://curiosity.ai/install.sh | bash
#
# Runs the curiosityai/curiosity image as a container named "curiosity", with
# Docker or Podman, whichever is installed and running. It installs neither:
# without one it says how to get it and stops. Running the script again pulls
# the image and replaces the container; the data lives in a named volume and
# is kept.
#
# On Windows, use install.ps1 instead: it runs the Windows installer.
#
# Settings, all optional, as environment variables (put them after the pipe:
# curl -fsSL https://curiosity.ai/install.sh | CURIOSITY_PORT=9090 bash):
#
#   CURIOSITY_RUNTIME         docker or podman (default: the first that works)
#   CURIOSITY_NAME            container name (default: curiosity)
#   CURIOSITY_IMAGE           image (default: docker.io/curiosityai/curiosity)
#   CURIOSITY_TAG             image tag (default: latest; pin a build number in production)
#   CURIOSITY_PORT            port on 127.0.0.1 (default: 8080)
#   CURIOSITY_BIND            address to publish on (default: 127.0.0.1, this machine only)
#   CURIOSITY_VOLUME          volume or host folder for the data (default: <name>-data)
#   CURIOSITY_ADMIN_EMAIL     email of the admin account (default: admin@example.com)
#   CURIOSITY_ADMIN_PASSWORD  password of the admin account (default: generated once, then reused)
#
# Documentation: https://docs.curiosity.ai/workspace-deployment/installation/docker

# Everything is inside main, called on the last line, so a download cut short
# runs nothing.
main() {
  set -euo pipefail

  local name="${CURIOSITY_NAME:-curiosity}"
  local image="${CURIOSITY_IMAGE:-docker.io/curiosityai/curiosity}:${CURIOSITY_TAG:-latest}"
  local port="${CURIOSITY_PORT:-8080}"
  local bind="${CURIOSITY_BIND:-127.0.0.1}"
  local volume="${CURIOSITY_VOLUME:-${name}-data}"
  local admin_email="${CURIOSITY_ADMIN_EMAIL:-admin@example.com}"
  local docs="https://docs.curiosity.ai/workspace-deployment/installation/docker"

  local os
  case "$(uname -s)" in
    Darwin) os=macos ;;
    Linux)  os=linux ;;
    MINGW*|MSYS*|CYGWIN*)
      say "This is Windows. Run the Windows installer from PowerShell instead:"
      say ""
      say "  irm https://curiosity.ai/install.ps1 | iex"
      return 1 ;;
    *) fail "unsupported system: $(uname -s). See $docs" ;;
  esac

  # -- the container runtime ---------------------------------------------
  local rt
  rt="$(pick_runtime "$os")" || return 1
  say "Using $rt ($("$rt" --version 2>/dev/null | head -n 1))."

  if [ "$rt" = docker ] && [ "$os" = macos ]; then
    local mem
    mem="$(docker info --format '{{.MemTotal}}' 2>/dev/null || echo 0)"
    if [ "${mem:-0}" -gt 0 ] && [ "$mem" -lt 7500000000 ]; then
      say "Note: Docker has $((mem / 1024 / 1024 / 1024)) GB of memory. Studio wants 8 GB:"
      say "      Docker Desktop > Settings > Resources > Memory."
    fi
  fi

  # -- the admin password, made once and kept ----------------------------
  # The admin account is seeded from these variables on every start, so the
  # password has to stay the same from one run of this script to the next.
  local conf="${XDG_CONFIG_HOME:-$HOME/.config}/curiosity"
  local pwfile="$conf/${name}-admin-password"
  local admin_password="${CURIOSITY_ADMIN_PASSWORD:-}"
  if [ -z "$admin_password" ] && [ -s "$pwfile" ]; then admin_password="$(cat "$pwfile")"; fi
  if [ -z "$admin_password" ]; then admin_password="$(new_password)"; fi
  mkdir -p "$conf"
  ( umask 077; printf '%s\n' "$admin_password" > "$pwfile" )

  # -- the image, then the container -------------------------------------
  say "Pulling $image ..."
  "$rt" pull "$image"

  if "$rt" container inspect "$name" >/dev/null 2>&1; then
    say "Replacing the existing container \"$name\" (the data in \"$volume\" is kept) ..."
    "$rt" rm -f "$name" >/dev/null
  fi

  # A host folder needs relabeling where SELinux enforces; a named volume does not.
  local mount="$volume:/data"
  case "$volume" in
    /*|./*|../*)
      mkdir -p "$volume"
      if [ "$os" = linux ] && command -v getenforce >/dev/null 2>&1 && [ "$(getenforce)" = Enforcing ]; then
        mount="$mount:Z"
      fi ;;
  esac

  say "Starting \"$name\" on http://$bind:$port ..."
  "$rt" run -d \
    --name "$name" \
    --restart unless-stopped \
    -p "$bind:$port:8080" \
    -v "$mount" \
    -e MSK_GRAPH_STORAGE=/data/curiosity \
    -e MSK_ADMIN_USER=admin \
    -e MSK_ADMIN_EMAIL="$admin_email" \
    -e MSK_ADMIN_PASSWORD="$admin_password" \
    "$image" >/dev/null

  # -- wait until it answers ---------------------------------------------
  local url="http://localhost:$port"
  [ "$bind" = 127.0.0.1 ] || [ "$bind" = 0.0.0.0 ] || url="http://$bind:$port"
  if command -v curl >/dev/null 2>&1; then
    say "Waiting for Studio to answer (the first start takes a minute) ..."
    local i=0
    until curl -fsS -o /dev/null "$url/api/ping" 2>/dev/null; do
      if [ "$("$rt" container inspect -f '{{.State.Running}}' "$name" 2>/dev/null)" != true ]; then
        say ""
        say "The container stopped. Its last lines:"
        "$rt" logs --tail 40 "$name" 2>&1 || true
        fail "Studio did not start. See $docs"
      fi
      i=$((i + 1))
      if [ "$i" -ge 180 ]; then
        say "Still starting after 3 minutes. Follow it with: $rt logs -f $name"
        break
      fi
      sleep 1
    done
  fi

  cat <<EOF

Curiosity Studio is running.

  Open      $url
  Sign in   admin / $admin_password
            (kept in $pwfile)

The container is "$name", and your data is in "$volume".

  $rt logs -f $name        follow the log (Ctrl+C stops following, not Studio)
  $rt stop $name           stop it
  $rt start $name          start it again
  $rt restart $name        restart it
  $rt ps -a --filter name=$name    see whether it is running

Run this script again to update to the latest image: it replaces the
container and keeps the data. To remove Studio, run "$rt rm -f $name",
and "$rt volume rm $volume" to delete the data as well.

The admin password is applied again on every start, so a password changed
in Studio is reset when the container restarts. Set CURIOSITY_ADMIN_PASSWORD
and run this script again to change it.

Next steps: $docs
EOF
}

say()  { printf '%s\n' "$*"; }
fail() { printf 'Error: %s\n' "$*" >&2; exit 1; }

new_password() {
  if command -v openssl >/dev/null 2>&1; then
    openssl rand -base64 18 | tr -d '/+=\n'
  else
    LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom | head -c 24
  fi
}

# Prints the runtime to use, or explains what is missing and returns 1.
pick_runtime() {
  local os="$1" found="" rt
  local candidates="docker podman"
  if [ -n "${CURIOSITY_RUNTIME:-}" ]; then candidates="$CURIOSITY_RUNTIME"; fi

  for rt in $candidates; do
    command -v "$rt" >/dev/null 2>&1 || continue
    found="$found $rt"
    if "$rt" info >/dev/null 2>&1; then printf '%s\n' "$rt"; return 0; fi
  done

  if [ -n "$found" ]; then
    # Installed, but not answering.
    for rt in $found; do not_running "$os" "$rt"; done
    return 1
  fi

  how_to_install "$os"
  return 1
}

not_running() {
  local os="$1" rt="$2"
  {
    say "$rt is installed but is not running, or this user cannot reach it."
    case "$os:$rt" in
      macos:docker)
        say "  Start Docker Desktop (open -a Docker), wait until it says it is running, then run this again." ;;
      macos:podman)
        say "  Podman runs in a virtual machine on macOS. Create and start it once:"
        say "    podman machine init --memory 8192"
        say "    podman machine start" ;;
      linux:docker)
        say "  Start the service:           sudo systemctl enable --now docker"
        say "  Use it without sudo:         sudo usermod -aG docker \$USER   (then log out and in again)"
        say "  Or with Docker Desktop: start it from your applications." ;;
      linux:podman)
        say "  Check what Podman reports:   podman info" ;;
    esac
    say ""
  } >&2
}

how_to_install() {
  local os="$1"
  {
    say "Curiosity Studio runs in a container, and neither Docker nor Podman is installed."
    say "Install one of them, then run this script again. Nothing was installed or changed."
    say ""
    if [ "$os" = macos ]; then
      say "Docker Desktop:"
      say "  https://docs.docker.com/desktop/setup/install/mac-install/"
      if command -v brew >/dev/null 2>&1; then
        say "  or: brew install --cask docker   (then open Docker from Applications)"
      fi
      say ""
      say "Podman:"
      say "  https://podman.io/docs/installation  (or Podman Desktop: https://podman-desktop.io/)"
      if command -v brew >/dev/null 2>&1; then
        say "  or: brew install podman && podman machine init --memory 8192 && podman machine start"
      fi
    else
      if command -v apt-get >/dev/null 2>&1; then
        say "With apt:"
        say "  sudo apt-get update && sudo apt-get install -y docker.io    # Docker"
        say "  sudo apt-get update && sudo apt-get install -y podman       # or Podman"
      elif command -v dnf >/dev/null 2>&1; then
        say "With dnf:"
        say "  sudo dnf install -y podman                                  # Podman"
        say "  Docker Engine needs Docker's repository first, see the link below."
      elif command -v yum >/dev/null 2>&1; then
        say "With yum:"
        say "  sudo yum install -y podman                                  # Podman"
        say "  Docker Engine needs Docker's repository first, see the link below."
      elif command -v zypper >/dev/null 2>&1; then
        say "With zypper:"
        say "  sudo zypper install -y docker    # Docker, or: sudo zypper install -y podman"
      elif command -v pacman >/dev/null 2>&1; then
        say "With pacman:"
        say "  sudo pacman -S docker            # Docker, or: sudo pacman -S podman"
      elif command -v apk >/dev/null 2>&1; then
        say "With apk:"
        say "  sudo apk add docker              # Docker, or: sudo apk add podman"
      elif command -v brew >/dev/null 2>&1; then
        say "With Homebrew:"
        say "  brew install podman"
      fi
      say ""
      say "Docker Engine, per distribution:"
      say "  https://docs.docker.com/engine/install/"
      say "Docker Desktop for Linux:"
      say "  https://docs.docker.com/desktop/setup/install/linux/"
      say "Podman:"
      say "  https://podman.io/docs/installation"
      say ""
      say "After installing Docker Engine, start it with: sudo systemctl enable --now docker"
    fi
  } >&2
}

main "$@"
