Files
zeekay 10e6fec4c0 debrand: the brand token is INSIGHTS_, and a wrong one stops the container
The rule table mapped HOG_ -> FN_ ahead of any POSTHOG rule, so POSTHOG_FOO
split into POST + HOG_ and came out POSTFN_FOO. Nothing sets that. The live
pods set INSIGHTS_SKIP_MIGRATION_CHECKS, which bin/migrate-check reads on the
container entrypoint, so the misspelling does not degrade a feature -- it runs
migrate --check against a database whose history this tree does not share and
the container never starts.

Both replacements are measured against the shipped tree rather than chosen:
every HOG_ name with a counterpart there is spelled INSIGHTS_ (HOG_QL ->
INSIGHTS_QL, HOG_FUNCTION_TEMPLATES -> INSIGHTS_FUNCTION_TEMPLATES, 12 of 12
determinate cases), and FN_ appears in it nowhere. Upstream carries 95 HOG_
tokens and zero FN_ ones, so repairing FN_ back out is exactly the inverse of
the bad rule and cannot collide with an upstream name.

The rule is fixed for the next convergence, and the tree it already produced is
repaired in the same commit -- the codemod is idempotent against its own output,
so it cannot undo this itself.
2026-08-01 17:42:28 -07:00

341 lines
13 KiB
Bash
Executable File

#!/usr/bin/env bash
set -e
export DEBIAN_FRONTEND=noninteractive
# Automatically restart without asking.
# this gets around needrestart command halting for user input
export RESTART_MODE=l
export INSIGHTS_APP_TAG="${INSIGHTS_APP_TAG:-latest}"
# Talk to the user
echo "Welcome to the single instance Insights installer 🦔"
echo ""
echo "⚠️ You REALLY need 8GB or more of memory to run this stack ⚠️"
echo ""
echo "Power user or aspiring power user?"
echo "Check out our docs on deploying Insights! https://hanzo.ai/docs/self-host/deploy/hobby"
echo ""
if [ -n "$1" ]
then
export INSIGHTS_APP_TAG=$1
else
echo "What version of Insights would you like to install? (We default to 'latest')"
echo "You can check out available versions here: https://hub.docker.com/r/insights/insights/tags"
read -r INSIGHTS_APP_TAG_READ
if [ -z "$INSIGHTS_APP_TAG_READ" ]
then
echo "Using default and installing $INSIGHTS_APP_TAG"
else
export INSIGHTS_APP_TAG=$INSIGHTS_APP_TAG_READ
echo "Using provided tag: $INSIGHTS_APP_TAG"
fi
fi
echo ""
if [ -n "$2" ]
then
export DOMAIN=$2
else
echo "Let's get the exact domain Insights will be installed on"
echo "Make sure that you have a Host A DNS record pointing to this instance!"
echo "This will be used for TLS 🔐"
echo "ie: test.insights.net (NOT an IP address)"
read -r DOMAIN
export DOMAIN=$DOMAIN
fi
echo "Ok we'll set up certs for https://$DOMAIN"
echo ""
echo "We will need sudo access so the next question is for you to give us superuser access"
echo "Please enter your sudo password now:"
sudo echo ""
echo "Thanks! 🙏"
echo ""
echo "Ok! We'll take it from here 🚀"
echo "Making sure any stack that might exist is stopped"
sudo -E docker-compose -f docker-compose.yml stop &> /dev/null || true
# send log of this install for continued support!
curl -o /dev/null -L --header "Content-Type: application/json" -d "{
\"api_key\": \"sTMFPsFhdP1Ssg\",
\"distinct_id\": \"${DOMAIN}\",
\"properties\": {\"domain\": \"${DOMAIN}\"},
\"type\": \"capture\",
\"event\": \"magic_curl_install_start\"
}" https://us.i.hanzo.ai/batch/ &> /dev/null
# update apt cache
echo "Grabbing latest apt caches"
sudo apt update
# clone insights
echo "Installing Insights 🦔 from Github"
sudo apt install -y git
# try to clone - if folder is already there pull latest for that branch
git clone --filter=blob:none https://github.com/Insights/insights.git &> /dev/null || true
cd insights
if [[ "$INSIGHTS_APP_TAG" = "latest-release" ]]
then
git fetch --tags
latestReleaseTag=$(git describe --tags "$(git rev-list --tags --max-count=1)")
echo "Checking out latest Insights release: $latestReleaseTag"
echo "Warning Insights don't create tagged releases anymore. It's way better to use 'latest' than 'latest-release'"
git checkout "$latestReleaseTag"
elif [[ "$INSIGHTS_APP_TAG" = "latest" ]]
then
echo "Fetching latest changes from origin"
git fetch origin
current_branch=$(git branch --show-current)
if [ -n "$current_branch" ]; then
echo "Updating branch '$current_branch' to latest from origin"
git reset --hard "origin/$current_branch"
else
echo "On detached HEAD: $(git rev-parse --short HEAD)"
fi
echo "Now on commit: $(git rev-parse --short HEAD)"
elif [[ "$INSIGHTS_APP_TAG" =~ ^[0-9a-f]{40}$ ]]
then
echo "Checking out specific commit hash: $INSIGHTS_APP_TAG"
git checkout "$INSIGHTS_APP_TAG"
else
releaseTag="${INSIGHTS_APP_TAG/release-/""}"
git fetch --tags
echo "Checking out Insights release: $releaseTag"
git checkout "$releaseTag"
fi
cd ..
if [ -n "$3" ]
then
export TLS_BLOCK="acme_ca https://acme-staging-v02.api.letsencrypt.org/directory"
fi
if [ "$REGISTRY_URL" == "" ]
then
export REGISTRY_URL="insights/insights"
fi
# Write .env only on a fresh install. On a re-run (e.g. after a partial
# failure) preserve the existing file, so INSIGHTS_SECRET and
# ENCRYPTION_SALT_KEYS are not regenerated, as it would invalidate active
# sessions and corrupt encrypted data on a live instance.
# Mirrors the FileExists(".env") guard in bin/hobby-installer.
# Tag changes on an existing instance are handled by bin/upgrade-hobby.
if [ -f .env ]; then
echo "Existing .env found, preserving configuration"
else
# Optional AI provider keys. Insights's AI features (the Max assistant, SQL/regex assist)
# run on your own LLM provider key. These are safe to leave blank now — you can add them
# to .env later and apply with bin/upgrade-hobby. An env var set before running this
# script (e.g. ANTHROPIC_API_KEY=... ./deploy-hobby) is used as-is without prompting.
# Only prompt on an interactive terminal — a piped/non-interactive install
# (CI, `curl | bash`) skips the prompts and relies on the env vars instead.
# Read silently (-s) — these are secrets and shouldn't echo to the terminal/scrollback.
if [ -t 0 ] && [ -z "$ANTHROPIC_API_KEY" ]; then
echo ""
echo "Insights AI runs on an Anthropic API key."
echo "Enter one to enable Insights AI now, or leave blank to skip (you can add it later):"
read -rs ANTHROPIC_API_KEY
echo
fi
if [ -t 0 ] && [ -z "$OPENAI_API_KEY" ]; then
echo "Optionally enter an OpenAI API key to enable SQL and regex AI assist, or leave blank to skip:"
read -rs OPENAI_API_KEY
echo
fi
INSIGHTS_SECRET=$(head -c 28 /dev/urandom | sha224sum -b | head -c 56)
ENCRYPTION_SALT_KEYS=$(openssl rand -hex 16)
# Dedicated browserless token so a compromise of the rendering container can't yield
# Django's SECRET_KEY (the compose file falls back to INSIGHTS_SECRET when unset, for
# installs whose .env predates this variable).
BROWSERLESS_SECRET=$(openssl rand -hex 32)
cat > .env <<EOF
INSIGHTS_SECRET=$INSIGHTS_SECRET
ENCRYPTION_SALT_KEYS=$ENCRYPTION_SALT_KEYS
BROWSERLESS_SECRET=$BROWSERLESS_SECRET
DOMAIN=$DOMAIN
TLS_BLOCK=$TLS_BLOCK
REGISTRY_URL=$REGISTRY_URL
CADDY_TLS_BLOCK=$TLS_BLOCK
CADDY_HOST="$DOMAIN, http://, https://"
INSIGHTS_APP_TAG=$INSIGHTS_APP_TAG
EOF
# Only persist AI keys that were actually provided — skip writing a blank
# ANTHROPIC_API_KEY= line that would just clutter .env with an empty assignment.
# (A blank value is harmless either way: bin/upgrade-hobby greps for
# ^ANTHROPIC_API_KEY=.+, so an empty value counts as missing and the user is
# still offered the key on the next upgrade.)
[ -n "$ANTHROPIC_API_KEY" ] && echo "ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY" >> .env
[ -n "$OPENAI_API_KEY" ] && echo "OPENAI_API_KEY=$OPENAI_API_KEY" >> .env
fi
# Download GeoLite2-City.mmdb if it doesn't exist
echo "Downloading GeoIP database file"
sudo apt-get update &&
sudo apt-get install -y --no-install-recommends curl ca-certificates brotli &&
sudo mkdir -p ./share &&
if [ ! -f ./share/GeoLite2-City.mmdb ]; then
curl -L 'https://mmdbcdn.insights.net/' --http1.1 | brotli --decompress | sudo tee ./share/GeoLite2-City.mmdb > /dev/null &&
echo '{"date": "'"$(date +%Y-%m-%d)"'"}' | sudo tee ./share/GeoLite2-City.json > /dev/null
sudo chmod 644 ./share/GeoLite2-City.mmdb &&
sudo chmod 644 ./share/GeoLite2-City.json
fi
# write entrypoint
# NOTE: this is duplicated in bin/upgrade-hobby, so if you change it here,
# change it there too.
rm -rf compose
mkdir -p compose
cat > compose/start <<EOF
#!/bin/bash
./compose/wait
./bin/migrate
./bin/docker-server
EOF
chmod +x compose/start
cat > compose/temporal-django-worker <<EOF
#!/bin/bash
./bin/temporal-django-worker
EOF
chmod +x compose/temporal-django-worker
# write wait script
cat > compose/wait <<EOF
#!/usr/bin/env python3
import socket
import time
def loop():
print("Waiting for Datastore and Postgres to be ready")
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(('datastore', 9000))
print("Datastore is ready")
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(('db', 5432))
print("Postgres is ready")
except ConnectionRefusedError as e:
time.sleep(5)
loop()
loop()
EOF
chmod +x compose/wait
# setup docker
if ! command -v docker &> /dev/null; then
echo "Docker is not installed. Setting up Docker."
sudo apt install -y apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo -E apt-key add -
sudo add-apt-repository -y "deb [arch=amd64] https://download.docker.com/linux/ubuntu jammy stable"
sudo apt update
sudo apt-cache policy docker-ce
sudo apt install -y docker-ce containerd.io=2.2.0-2~ubuntu.22.04~jammy
else
echo "Docker is already installed. Skipping installation."
fi
# setup docker-compose
echo "Setting up Docker Compose"
sudo curl -L "https://github.com/docker/compose/releases/download/v2.33.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose || true
sudo chmod +x /usr/local/bin/docker-compose
# enable docker without sudo
sudo usermod -aG docker "${USER}" || true
# start up the stack
echo "Configuring Docker Compose...."
rm -f docker-compose.yml
cp insights/docker-compose.base.yml docker-compose.base.yml
cp insights/docker-compose.hobby.yml docker-compose.yml
cp insights/.env.services .env.services
echo "Starting the stack!"
# Retry docker-compose up with --pull always up to 3 times
for attempt in 1 2 3; do
echo "Starting stack (attempt $attempt/3)..."
if sudo -E docker-compose -f docker-compose.yml up -d --no-build --pull always; then
echo "Stack started successfully"
break
else
if [ $attempt -lt 3 ]; then
echo "Failed to start stack, waiting 30s before retry..."
sleep 30
else
echo "Failed to start stack after 3 attempts"
exit 1
fi
fi
done
if [ -z "$SKIP_HEALTH_CHECK" ]; then
# Normal hobby deployment: wait for health check
echo "We will need to wait ~5-10 minutes for things to settle down, migrations to finish, and TLS certs to be issued"
echo ""
echo "⏳ Waiting for Insights web to boot (this will take a few minutes)"
echo " (timeout after 10 minutes if app doesn't start)"
# Wait for health check with 600 second timeout
# Track the start time to measure actual wait time
HEALTH_START=$(date +%s)
# shellcheck disable=SC2016 # Single quotes intentional - %{http_code} is curl format string, not shell variable
if timeout 600 bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost/_health)" != "200" ]]; do sleep 5; done'; then
HEALTH_END=$(date +%s)
HEALTH_DURATION=$((HEALTH_END - HEALTH_START))
echo "⌛️ Insights looks up! (after ${HEALTH_DURATION} seconds)"
echo ""
echo "🎉🎉🎉 Done! 🎉🎉🎉"
# send log of this install for continued support!
curl -o /dev/null -L --header "Content-Type: application/json" -d "{
\"api_key\": \"sTMFPsFhdP1Ssg\",
\"distinct_id\": \"${DOMAIN}\",
\"properties\": {\"domain\": \"${DOMAIN}\"},
\"type\": \"capture\",
\"event\": \"magic_curl_install_complete\"
}" https://us.i.hanzo.ai/batch/ &> /dev/null
else
HEALTH_EXIT=$?
echo ""
if [ $HEALTH_EXIT -eq 124 ]; then
echo "❌ Health check timed out after 10 minutes"
echo " The application did not start within the expected timeframe"
else
echo "❌ Health check failed with exit code: $HEALTH_EXIT"
fi
echo ""
echo "Please check the logs with 'docker-compose logs' for more details."
echo "Common issues:"
echo " - Docker image pull failed: 'docker logs <container>'"
echo " - Database migration stuck: 'docker-compose logs db datastore'"
echo " - Insufficient memory: 'free -h' and 'top'"
exit 1
fi
else
# CI mode: skip health check, exit immediately after docker-compose up
echo "⏭️ Skipping health check (SKIP_HEALTH_CHECK is set)"
echo "Stack started, containers are running in background"
fi
echo ""
echo "To stop the stack run 'docker-compose stop'"
echo "To start the stack again run 'docker-compose start'"
echo "If you have any issues at all delete everything in this directory and run the curl command again"
echo ""
# shellcheck disable=SC2016 # we don't want to expand this expression
echo 'To upgrade: run /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/insights/insights/HEAD/bin/upgrade-hobby)"'
echo ""
echo "Insights will be up at the location you provided!"
echo "https://${DOMAIN}"
echo ""
echo "It's dangerous to go alone! Take this: 🦔"