#!/bin/bash #
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