#!/bin/bash #

Get Firebolt with:

curl -fsSL https://get.firebolt.io/ | bash


set -e

# Parse command line arguments
AUTO_RUN=false
if [ "$1" = "--auto-run" ]; then
    AUTO_RUN=true
elif [ -n "$1" ]; then
    echo "Unknown option: $1"
    echo "Usage: $0 [--auto-run]" 1>&2
    exit 1
fi

# When the script is piped in (e.g. 'curl | bash'), stdin is not a terminal
# and the user cannot answer prompts through it, so run without prompting.
if [ ! -t 0 ]; then
    AUTO_RUN=true
fi

banner() {
    echo -e "\e[31m"
    echo "███████╗██╗██████╗ ███████╗██████╗  ██████╗ ██╗  ████████╗"
    echo "██╔════╝██║██╔══██╗██╔════╝██╔══██╗██╔═══██╗██║  ╚══██╔══╝"
    echo "█████╗  ██║██████╔╝█████╗  ██████╔╝██║   ██║██║     ██║   "
    echo "██╔══╝  ██║██╔══██╗██╔══╝  ██╔══██╗██║   ██║██║     ██║   "
    echo "██║     ██║██║  ██║███████╗██████╔╝╚██████╔╝███████╗██║   "
    echo "╚═╝     ╚═╝╚═╝  ╚═╝╚══════╝╚═════╝  ╚═════╝ ╚══════╝╚═╝   "
    echo -e "\e[0m"
    echo "       The Analytical Database for Engineers"
    echo "       © 2026 Firebolt Analytics Inc (https://firebolt.io)"
    echo ""
    echo "       🔥🔥🔥 Setup script for Firebolt 🔥🔥🔥"
    echo ""
}

IS_MACOS=0
if [ "$(uname)" = "Darwin" ]; then
    IS_MACOS=1
fi

##############################################################################
# Native binary installation (Linux)
##############################################################################

# GitHub repository the release binaries are published to, and the release to
# install - allow specifying overrides via env variables. FIREBOLT_VERSION
# accepts a release tag (e.g. "v5.0.0-pre.0.20260815074041.524de236b514") or
# "latest".
FIREBOLT_REPO="${FIREBOLT_REPO:-firebolt-db/firebolt-core}"
FIREBOLT_VERSION="${FIREBOLT_VERSION:-latest}"
INSTALL_ROOT="${FIREBOLT_INSTALL_ROOT:-$HOME/.firebolt}"

# Retry failed GitHub requests; --retry-all-errors also retries
# connection-level failures, not just transient HTTP errors.
CURL_RETRY_ARGS=( --retry 3 --retry-delay 5 --retry-all-errors )

NATIVE_ARCH=""
RELEASE_TAG=""
FIREBOLT_BIN=""

# Decide whether this machine can run the native binaries. Prints the reason
# and returns 1 when it cannot, in which case we fall back to Docker.
native_install_supported() {
    if [ "${FIREBOLT_USE_DOCKER:-0}" = "1" ]; then
        echo "[🐳] FIREBOLT_USE_DOCKER=1 is set, using the Docker-based setup"
        return 1
    fi

    if [ $IS_MACOS -eq 1 ]; then
        # No native macOS binaries are published yet.
        return 1
    fi

    case "$(uname -m)" in
        x86_64|amd64)  NATIVE_ARCH="amd64" ;;
        aarch64|arm64) NATIVE_ARCH="arm64" ;;
        *)
            echo "[⚠️] No native Firebolt binaries for '$(uname -m)', falling back to Docker"
            return 1
            ;;
    esac

    return 0
}

resolve_release_tag() {
    if [ "$FIREBOLT_VERSION" != "latest" ]; then
        RELEASE_TAG="$FIREBOLT_VERSION"
        return 0
    fi

    # 'releases/latest' redirects to 'releases/tag/'; resolving the tag
    # up front lets installs land in a per-version directory, so newer
    # versions can be installed side by side.
    echo "[🔥] Looking up the latest Firebolt release"
    local final_url
    if ! final_url=$(curl -fsSLI "${CURL_RETRY_ARGS[@]}" -o /dev/null -w '%{url_effective}' "https://github.com/$FIREBOLT_REPO/releases/latest"); then
        echo "[❌] Could not reach https://github.com/$FIREBOLT_REPO/releases/latest"
        return 1
    fi
    RELEASE_TAG="${final_url##*/}"
    if [ -z "$RELEASE_TAG" ] || [ "$RELEASE_TAG" = "releases" ]; then
        echo "[❌] Could not determine the latest release tag from '$final_url'"
        return 1
    fi
    echo "[🔥] Latest release: $RELEASE_TAG ✅"
}

extract_tarball() {
    # $1: tarball, $2: destination directory. The tarball has a single
    # 'packdb-package/' top-level directory that we strip.
    tar -xz --strip-components=1 -f "$1" -C "$2"
}

install_native_binary() {
    local pkg_dir="$INSTALL_ROOT/pkg/$RELEASE_TAG"
    FIREBOLT_BIN="$INSTALL_ROOT/bin/firebolt"

    if [ -x "$pkg_dir/usr/bin/firebolt" ]; then
        echo "[🔥] Firebolt $RELEASE_TAG is already installed in '$pkg_dir' ✅"
    else
        local asset="packdb-linux-$NATIVE_ARCH.tar.gz"
        local url="https://github.com/$FIREBOLT_REPO/releases/download/$RELEASE_TAG/$asset"
        local tarball="$INSTALL_ROOT/pkg/.$asset.partial"
        local staging="$INSTALL_ROOT/pkg/.staging.$$"

        echo "[🔥] Downloading $url"
        mkdir -p "$INSTALL_ROOT/pkg"
        # Show a progress bar when we have a terminal for it; piped installs
        # ('curl | bash') stay quiet.
        if [ -t 2 ]; then
            curl -fL --progress-bar "${CURL_RETRY_ARGS[@]}" -o "$tarball" "$url"
        else
            curl -fsSL "${CURL_RETRY_ARGS[@]}" -o "$tarball" "$url"
        fi
        echo "[🔥] Download finished ✅"

        # Unpack into a staging directory and move it into place at the end,
        # so an interrupted install never leaves a half-populated version
        # directory that a re-run would treat as complete.
        echo -n "[🔥] Unpacking"
        mkdir -p "$staging"
        trap "rm -rf '$staging' '$tarball'" EXIT
        extract_tarball "$tarball" "$staging"
        rm -f "$tarball"
        # A leftover version directory (e.g. from an older installer) would
        # make mv nest staging inside it instead of replacing it.
        rm -rf "$pkg_dir"
        mv "$staging" "$pkg_dir"
        trap - EXIT
        echo " ✅"
    fi

    # The binary locates its bundled runtime files (e.g. opt/fb_udf_images)
    # relative to its real path, so symlink it instead of copying it out of
    # the package tree. -n: replace the symlink itself if it already points
    # to an older version's directory.
    mkdir -p "$INSTALL_ROOT/bin"
    ln -sfn "$pkg_dir/usr/bin/firebolt" "$FIREBOLT_BIN"
    echo "[🔥] Installed 'firebolt' to '$FIREBOLT_BIN' ✅"

    # Like DuckDB's installer: if ~/.local/bin (in the default PATH on most
    # modern distros) exists, is writable, and 'firebolt' there is absent or
    # already ours, link it in so 'firebolt' works right away. Never touch a
    # 'firebolt' that something else put there.
    local localbin="$HOME/.local/bin"
    if [ -d "$localbin" ] && [ -w "$localbin" ]; then
        if [ ! -e "$localbin/firebolt" ] || [ "$(readlink "$localbin/firebolt" 2>/dev/null)" = "$FIREBOLT_BIN" ]; then
            ln -sfn "$FIREBOLT_BIN" "$localbin/firebolt"
            echo "[🔥] Linked '$localbin/firebolt' -> '$FIREBOLT_BIN' ✅"
        fi
    fi

    # Point the user at whichever install location is already on their PATH;
    # if neither is, tell them how to fix that.
    FIREBOLT_CMD="$FIREBOLT_BIN"
    case ":$PATH:" in
        *":$INSTALL_ROOT/bin:"*)
            FIREBOLT_CMD="firebolt"
            ;;
        *":$localbin:"*)
            if [ "$(readlink "$localbin/firebolt" 2>/dev/null)" = "$FIREBOLT_BIN" ]; then
                FIREBOLT_CMD="firebolt"
            fi
            ;;
    esac
    if [ "$FIREBOLT_CMD" != "firebolt" ]; then
        echo "[💡] Add it to your PATH to run 'firebolt' from anywhere:"
        echo
        echo "    export PATH=\"$INSTALL_ROOT/bin:\$PATH\""
        echo
    fi
}

run_native_binary() {
    if [ "$AUTO_RUN" = true ]; then
        answer="y"
    else
        prompt="[🔥] Everything is set up and you are ready to go! Do you want to start Firebolt? (use --auto-run to skip this prompt) [y/N]: "
        if ! { printf "%s" "$prompt" > /dev/tty && read -r answer < /dev/tty; } 2>/dev/null; then
            answer=""
        fi
    fi

    case "$answer" in
        [yY])
            # stdin may be the script itself (e.g. 'curl | bash'), so attach
            # the REPL to the controlling terminal instead; without one there
            # is nothing interactive to do, so just print how to start it.
            if { : < /dev/tty; } 2>/dev/null; then
                echo "[🔥] Starting the Firebolt REPL (embedded server with a temporary database; pass --data-dir to keep data)"
                "$FIREBOLT_BIN" < /dev/tty
            else
                echo "[🔥] No terminal available. Start Firebolt with: $FIREBOLT_CMD"
            fi
            ;;
        *)
            echo "[🔥] Firebolt is ready. Start the interactive REPL with:"
            echo
            echo "$FIREBOLT_CMD"
            echo
            echo "Or run SQL directly:"
            echo
            echo "$FIREBOLT_CMD -c 'SELECT 42'"
            echo
            ;;
    esac
}

##############################################################################
# Docker-based installation (macOS, and Linux fallback)
##############################################################################

# Docker image to pull - allow specifying overrides via env variables
ENGINE_REPO="${ENGINE_REPO:-ghcr.io/firebolt-db/engine}"
ENGINE_TAG="${ENGINE_TAG:-dev}"
DOCKER_IMAGE="${ENGINE_REPO}:${ENGINE_TAG}"
EXTERNAL_PORT=3473
# Generated engine config, dropped into the data directory (used on macOS only,
# see below). It is auto-loaded by the engine as /var/lib/firebolt/config.yaml.
CONFIG_FILE="firebolt-data/config.yaml"
DOCKER_CONTAINER_NAME="firebolt"
DOCKER_RUN_ARGS=(
  -i
  --name $DOCKER_CONTAINER_NAME
  --rm
  --ulimit memlock=8589934592:8589934592
  --security-opt seccomp=unconfined
  -v "$(pwd)/firebolt-data:/var/lib/firebolt"
  -p "$EXTERNAL_PORT:3473"
)
# On macOS the Docker Desktop file-sharing backend cannot stat the engine's Unix
# domain socket when it lives on the bind-mounted data directory, so Firebolt
# fails to start. Keep the socket off the shared filesystem: put it on an
# in-memory tmpfs at /run/firebolt and relocate it there via the generated
# config file. Mirror the ownership/permissions the image ships /run/firebolt
# with (firebolt:root, mode 2770) so both root and the non-root firebolt user
# (uid 3473, group 0) can create the socket.
if [ $IS_MACOS -eq 1 ]; then
    DOCKER_RUN_ARGS+=( --tmpfs /run/firebolt:rw,mode=2770,uid=3473,gid=0 )
fi
DOCKER_RUN_ARGS+=( "$DOCKER_IMAGE" )

# Engine config that moves the query Unix socket onto the tmpfs while keeping the
# HTTP endpoint on TCP 3473. Used on macOS only.
read -r -d '' CONFIG_YAML <<'EOF' || true
schema_version: "1.0"
endpoints:
  http:
    listeners:
      - type: tcp
        port: 3473
      - type: unix
        path: /run/firebolt/query_endpoint
EOF

write_firebolt_config() {
    printf '%s\n' "$CONFIG_YAML" > "$CONFIG_FILE"
}

ensure_docker_is_installed() {
    if docker info >/dev/null 2>&1; then
        echo "[🐳] Docker is present and works ✅"
        return 0
    fi

    if [ $IS_MACOS -eq 1 ]; then
        echo "[🐳] Docker needs to be installed: https://docs.docker.com/desktop/setup/install/mac-install/ ❌"
    else
        echo "[🐳] Docker needs to be installed: https://docs.docker.com/desktop/setup/install/linux/ ❌"
    fi
    return 1
}

check_docker_version() {
    # Explicitly inform the user about the known io_uring issue in Docker Desktop for Mac
    # See also:
    # * https://github.com/docker/for-mac/issues/7707
    if [ $IS_MACOS -eq 1 ]; then
        version=$(docker version | sed -n 's/.*Docker Desktop \([0-9.]*\).*/\1/p')
        if [ "$version" = "4.42.1" ] || [ "$version" = "4.43.0" ] || [ "$version" = "4.43.1" ]; then
            echo "[❌] Firebolt cannot run with Docker Desktop version ${version} on Mac, as it contains a known io_uring issue; please use version 4.43.2+"
            return 1
        fi
    fi
}

pull_docker_image() {
    echo "[🐳] Pulling Firebolt Docker image '$DOCKER_IMAGE'"
    # Check the pull inline rather than through '$?': 'set -e' would abort the script on a
    # failed pull before any separate check could report it, leaving the user with docker's
    # raw error and none of the context below.
    if docker pull --quiet "$DOCKER_IMAGE"; then
        echo "[🐳] Docker image '$DOCKER_IMAGE' pulled successfully ✅"
    else
        echo "[🐳] Failed to pull Docker image '$DOCKER_IMAGE' ❌"
        return 1
    fi
}

DEFAULT_RUN_USER=""
detect_firebolt_user() {
    if [ $IS_MACOS -eq 1 ]; then
        DEFAULT_RUN_USER=root
    else
        DEFAULT_RUN_USER="firebolt"
    fi

    # set FIREBOLT_USER, unless already set by user
    FIREBOLT_USER="${FIREBOLT_USER:-$DEFAULT_RUN_USER}"
}

wait_for_firebolt_to_be_ready() {
    # If curl is not installed, we can't check if Firebolt is ready
    if ! command -v curl >/dev/null 2>&1; then
        return 0
    fi

    echo -n "[🔥] Wait for Firebolt to be ready"

    # Try for ~10 seconds to get a valid response from Firebolt
    timeout=10
    RESPONSE="Unknown error"
    while [ $timeout -gt 0 ]; do
        set +e
        RESPONSE=$(curl -s 'http://localhost:3473/?output_format=TabSeparatedWithNamesAndTypes' --data-binary "SELECT 42;")
        set -e

        if [ "$RESPONSE" = $'?column?\nint\n42' ]; then
            echo " ✅"
            return 0
        fi
        sleep 1
        timeout=$((timeout - 1))
        echo -n "."
    done

    echo " ❌"
    echo "[❌] Firebolt failed to start. This is unexpected, please submit a bug report on Github https://github.com/firebolt-db/firebolt-core/issues"
    echo "[❌] Error: $RESPONSE"
    return 1
}

run_docker_image() {
    echo "[⚠️] Note: a local 'firebolt-data directory' with permissions 0777 will be created."
    if [ $IS_MACOS -eq 1 ]; then
        echo "[⚠️] Note: on macOS a '$CONFIG_FILE' file will be created and the socket will run on an in-memory tmpfs."
    fi

    if [ "$AUTO_RUN" = true ]; then
        answer="y"
    else
        prompt="[🔥] Everything is set up and you are ready to go! Do you want to run the Firebolt image? (use --auto-run to skip this prompt) [y/N]: "
        if ! { printf "%s" "$prompt" > /dev/tty && read -r answer < /dev/tty; } 2>/dev/null; then
            answer=""
        fi
    fi

    case "$answer" in
        [yY])
            if [ ! -d firebolt-data ]; then
                mkdir -p -m 777 firebolt-data
            fi
            if [ $IS_MACOS -eq 1 ]; then
                write_firebolt_config
            fi
            echo -n "[🔥] Starting the Firebolt Docker container"
            CID="$(docker run --detach --user $FIREBOLT_USER "${DOCKER_RUN_ARGS[@]}")"
            trap "docker kill $CID" EXIT
            echo " ✅"

            wait_for_firebolt_to_be_ready

            # stdin may be the script itself (e.g. 'curl | bash'), so attach the
            # CLI to the controlling terminal instead; without one, leave the
            # container running in the background.
            #
            # The image ships no separate CLI binary: `firebolt` is both the server and the
            # client. The `client` subcommand is what connects to the server already running
            # in the container (localhost:3473, database "firebolt") — a bare `firebolt`
            # would start a second, embedded one instead.
            if { : < /dev/tty; } 2>/dev/null; then
                echo "[🔥] Running Firebolt CLI"
                docker exec -ti $CID firebolt client < /dev/tty
            else
                trap - EXIT
                echo "[🔥] No terminal available, leaving Firebolt running in the background."
                echo "[🔥] Connect to it with: docker exec -ti $DOCKER_CONTAINER_NAME firebolt client"
                echo "[🔥] Stop it with: docker kill $DOCKER_CONTAINER_NAME"
            fi
            ;;
        *)
            echo "[🔥] Firebolt is ready to be executed, you can do this by running the following commands:"
            echo
            echo "mkdir -m 777 firebolt-data"
            if [ $IS_MACOS -eq 1 ]; then
                echo "cat > $CONFIG_FILE <<'EOF'"
                printf '%s\n' "$CONFIG_YAML"
                echo "EOF"
            fi
            # Print the arguments one by one with '%q' so the line stays copy-pasteable: it
            # quotes whatever needs quoting, e.g. the bind-mount path when the current
            # directory contains spaces.
            printf 'docker run --user %q' "$FIREBOLT_USER"
            printf ' %q' "${DOCKER_RUN_ARGS[@]}"
            printf '\n'
            echo
            echo "And then in another terminal:"
            echo
            echo "docker exec -ti $DOCKER_CONTAINER_NAME firebolt client"
            echo
            ;;

    esac
}

# Main script execution
banner
if native_install_supported; then
    resolve_release_tag
    install_native_binary
    run_native_binary
else
    ensure_docker_is_installed
    check_docker_version
    pull_docker_image
    detect_firebolt_user
    run_docker_image
fi