mirror of
https://github.com/farcasterxyz/hub-monorepo.git
synced 2026-01-27 14:07:58 -05:00
419 lines
13 KiB
Bash
Executable File
419 lines
13 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# The Hubble installation script. This script is used to install the latest version of Hubble.
|
|
# It can also be used to upgrade an existing installation of Hubble, also upgrading
|
|
# itself in the process.
|
|
|
|
# Define the version of this script
|
|
CURRENT_VERSION="1"
|
|
|
|
REPO="farcasterxyz/hub-monorepo"
|
|
RAWFILE_BASE="https://raw.githubusercontent.com/$REPO"
|
|
LATEST_TAG="@latest"
|
|
|
|
DOCKER_COMPOSE_FILE_PATH="apps/hubble/docker-compose.yml"
|
|
SCRIPT_FILE_PATH="scripts/hubble.sh"
|
|
GRAFANA_DASHBOARD_JSON_PATH="apps/hubble/grafana/grafana-dashboard.json"
|
|
GRAFANA_INI_PATH="apps/hubble/grafana/grafana.ini"
|
|
|
|
install_jq() {
|
|
if command -v jq >/dev/null 2>&1; then
|
|
echo "✅ Dependencies are installed."
|
|
return 0
|
|
fi
|
|
|
|
echo "Installing jq..."
|
|
|
|
# macOS
|
|
if [[ "$(uname)" == "Darwin" ]]; then
|
|
if command -v brew >/dev/null 2>&1; then
|
|
brew install jq
|
|
else
|
|
echo "Homebrew is not installed. Please install Homebrew first."
|
|
return 1
|
|
fi
|
|
|
|
# Ubuntu/Debian
|
|
elif [[ -f /etc/lsb-release ]] || [[ -f /etc/debian_version ]]; then
|
|
sudo apt-get update
|
|
sudo apt-get install -y jq
|
|
|
|
# RHEL/CentOS
|
|
elif [[ -f /etc/redhat-release ]]; then
|
|
sudo yum install -y jq
|
|
|
|
# Fedora
|
|
elif [[ -f /etc/fedora-release ]]; then
|
|
sudo dnf install -y jq
|
|
|
|
# openSUSE
|
|
elif [[ -f /etc/os-release ]] && grep -q "ID=openSUSE" /etc/os-release; then
|
|
sudo zypper install -y jq
|
|
|
|
# Arch Linux
|
|
elif [[ -f /etc/arch-release ]]; then
|
|
sudo pacman -S jq
|
|
|
|
else
|
|
echo "Unsupported operating system. Please install jq manually."
|
|
return 1
|
|
fi
|
|
|
|
echo "✅ jq installed successfully."
|
|
}
|
|
|
|
# Fetch file from repo at "@latest"
|
|
fetch_file_from_repo() {
|
|
local file_path="$1"
|
|
local local_filename="$2"
|
|
|
|
local download_url
|
|
download_url="$RAWFILE_BASE/$LATEST_TAG/$file_path?t=$(date +%s)"
|
|
|
|
# Download the file using curl, and save it to the local filename. If the download fails,
|
|
# exit with an error.
|
|
curl -sS -o "$local_filename" "$download_url" || { echo "Failed to fetch $download_url."; exit 1; }
|
|
}
|
|
|
|
# Upgrade the script
|
|
self_upgrade() {
|
|
local tmp_file
|
|
tmp_file=$(mktemp)
|
|
fetch_file_from_repo "$SCRIPT_FILE_PATH" "$tmp_file"
|
|
|
|
local latest_version
|
|
latest_version=$(awk -F'="' '/^CURRENT_VERSION=/ { print $2 }' "$tmp_file" | tr -d '"')
|
|
|
|
# Compare the versions
|
|
if [[ "$latest_version" > "$CURRENT_VERSION" ]]; then
|
|
echo "Newer version found ($latest_version). Upgrading..."
|
|
mv "$tmp_file" "$0" # Overwrite the current script
|
|
chmod +x "$0" # Ensure the script remains executable
|
|
echo "✅ Upgrade complete. Restarting with new version..."
|
|
echo ""
|
|
exec "$0" "$1" || echo "Exec failed with status: $?"
|
|
|
|
# Exit the script because we already "exec"ed the script above
|
|
exit 0
|
|
else
|
|
echo "✅ Latest Script Version: $CURRENT_VERSION."
|
|
rm -f "$tmp_file" # Clean up temporary file if no upgrade was needed
|
|
fi
|
|
}
|
|
|
|
# Fetch the docker-compose.yml and grafana-dashboard.json files
|
|
fetch_latest_docker_compose_and_dashboard() {
|
|
fetch_file_from_repo "$DOCKER_COMPOSE_FILE_PATH" "docker-compose.yml"
|
|
fetch_file_from_repo "$GRAFANA_DASHBOARD_JSON_PATH" "grafana-dashboard.json"
|
|
mkdir -p grafana
|
|
chmod 777 grafana
|
|
fetch_file_from_repo "$GRAFANA_INI_PATH" "grafana/grafana.ini"
|
|
}
|
|
|
|
validate_and_store() {
|
|
local rpc_name=$1
|
|
local expected_chain_id=$2
|
|
|
|
while true; do
|
|
read -p "> Enter your $rpc_name RPC URL: " RPC_URL
|
|
RESPONSE=$(curl -s -X POST --data '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}' "$RPC_URL")
|
|
|
|
# Convert both the response and expected chain ID to lowercase for comparison
|
|
local lower_response=$(echo "$RESPONSE" | tr '[:upper:]' '[:lower:]')
|
|
local lower_expected_chain_id=$(echo "$expected_chain_id" | tr '[:upper:]' '[:lower:]')
|
|
|
|
|
|
if [[ $lower_response == *'"result":"'$lower_expected_chain_id'"'* ]]; then
|
|
echo "$3=$RPC_URL" >> .env
|
|
break
|
|
else
|
|
echo "!!! Invalid !!!"
|
|
echo "$rpc_name Ethereum RPC URL or chainID is not $expected_chain_id. Please retry."
|
|
echo "You can signup for a free account at Alchemy or Infura if you need an RPC provider"
|
|
echo "Server returned \"$RESPONSE\""
|
|
fi
|
|
done
|
|
}
|
|
|
|
key_exists() {
|
|
local key=$1
|
|
grep -q "^$key=" .env
|
|
return $?
|
|
}
|
|
|
|
write_env_file() {
|
|
if [[ ! -f .env ]]; then
|
|
touch .env
|
|
fi
|
|
|
|
if ! key_exists "FC_NETWORK_ID"; then
|
|
echo "FC_NETWORK_ID=1" >> .env
|
|
fi
|
|
|
|
if ! key_exists "STATSD_METRICS_SERVER"; then
|
|
echo "STATSD_METRICS_SERVER=statsd:8125" >> .env
|
|
fi
|
|
|
|
if ! key_exists "ETH_MAINNET_RPC_URL"; then
|
|
validate_and_store "Ethereum Mainnet" "0x1" "ETH_MAINNET_RPC_URL"
|
|
fi
|
|
|
|
if ! key_exists "OPTIMISM_L2_RPC_URL"; then
|
|
validate_and_store "Optimism L2 Mainnet" "0xa" "OPTIMISM_L2_RPC_URL"
|
|
fi
|
|
|
|
echo "✅ .env file updated."
|
|
}
|
|
|
|
## Configure Grafana
|
|
setup_grafana() {
|
|
local grafana_url="http://127.0.0.1:3000"
|
|
local credentials="admin:admin"
|
|
local response dashboard_uid prefs
|
|
|
|
add_datasource() {
|
|
response=$(curl -s -o /dev/null -w "%{http_code}" -X "POST" "$grafana_url/api/datasources" \
|
|
-u "$credentials" \
|
|
-H "Content-Type: application/json" \
|
|
--data-binary '{
|
|
"name":"Graphite",
|
|
"type":"graphite",
|
|
"url":"http://statsd:80",
|
|
"access":"proxy"
|
|
}')
|
|
|
|
# Handle if the datasource already exists
|
|
if [[ "$response" == "409" ]]; then
|
|
echo "✅ Datasource 'Graphite' exists."
|
|
response="200"
|
|
fi
|
|
}
|
|
|
|
delete_dashboard() {
|
|
local dashboard_uid
|
|
|
|
dashboard_uid=$(curl -s "$grafana_url/api/search?query=Hub Dashboard" -u "$credentials" | \
|
|
jq -r '.[] | select(.title == "Hub Dashboard") | .uid')
|
|
|
|
if [[ "$dashboard_uid" ]]; then
|
|
curl -s -X "DELETE" "$grafana_url/api/dashboards/uid/$dashboard_uid" -u "$credentials"
|
|
fi
|
|
}
|
|
|
|
# Step 1: Restart statsd and grafana if they are running, otherwise start them
|
|
if $COMPOSE_CMD ps statsd 2>&1 >/dev/null; then
|
|
if $COMPOSE_CMD ps statsd | grep -q "Up"; then
|
|
$COMPOSE_CMD restart statsd grafana
|
|
else
|
|
$COMPOSE_CMD up -d statsd grafana
|
|
fi
|
|
else
|
|
echo "❌ Docker is not running or there's another issue with Docker. Please start Docker manually."
|
|
exit 1
|
|
fi
|
|
|
|
# Step 2: Wait for Grafana to be ready
|
|
echo "Waiting for Grafana to be ready..."
|
|
while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' $grafana_url/api/health)" != "200" ]]; do
|
|
sleep 2;
|
|
done
|
|
echo "Grafana is ready."
|
|
|
|
# Step 3: Add Graphite as a data source using Grafana's API
|
|
add_datasource
|
|
|
|
# Check if the default credentials failed
|
|
if [[ "$response" == "401" ]]; then
|
|
echo "Please enter your Grafana credentials."
|
|
read -p "Username: " username
|
|
read -sp "Password: " password
|
|
echo
|
|
credentials="$username:$password"
|
|
|
|
# Retry adding the data source with the new credentials
|
|
add_datasource
|
|
|
|
if [[ "$response" != "200" ]]; then
|
|
echo "Failed to add data source with provided credentials. Exiting."
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
# Step 4: Delete the dashboard if it exists
|
|
delete_dashboard
|
|
|
|
# Step 5: Import the dashboard. The API takes a slighly different format than the JSON import
|
|
# in the UI, so we need to convert the JSON file first.
|
|
jq 'if .dashboard then . else {dashboard: ., folderId: 0, overwrite: true} end' "grafana-dashboard.json" > "grafana-dashboard.api.json"
|
|
|
|
response=$(curl -s -X "POST" "$grafana_url/api/dashboards/db" \
|
|
-u "$credentials" \
|
|
-H "Content-Type: application/json" \
|
|
--data-binary @grafana-dashboard.api.json)
|
|
|
|
rm "grafana-dashboard.api.json"
|
|
|
|
if echo "$response" | jq -e '.status == "success"' >/dev/null; then
|
|
# Extract dashboard UID from the response
|
|
dashboard_uid=$(echo "$response" | jq -r '.uid')
|
|
|
|
# Set the default home dashboard for the organization
|
|
prefs=$(curl -s -X "PUT" "$grafana_url/api/org/preferences" \
|
|
-u "$credentials" \
|
|
-H "Content-Type: application/json" \
|
|
--data "{\"homeDashboardUID\":\"$dashboard_uid\"}")
|
|
|
|
echo "✅ Dashboard is installed."
|
|
else
|
|
echo "Failed to install dashboard. Exiting."
|
|
echo "$response"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
install_docker() {
|
|
# Check if Docker is already installed
|
|
if command -v docker &> /dev/null; then
|
|
echo "✅ Docker is installed."
|
|
return 0
|
|
fi
|
|
|
|
# Install using Docker's convenience script
|
|
curl -fsSL https://get.docker.com -o get-docker.sh
|
|
sh get-docker.sh
|
|
rm get-docker.sh
|
|
|
|
# Add current user to the docker group
|
|
sudo usermod -aG docker $(whoami)
|
|
|
|
echo "✅ Docker is installed"
|
|
return 0
|
|
}
|
|
|
|
start_hubble() {
|
|
# First, make sure to pull all the latest images in docker compose
|
|
$COMPOSE_CMD pull
|
|
|
|
# Make directory for hubble data called ".hub" and ".rocks". Make sure to set
|
|
# the permissions so that the current user owns the directory and it is writable
|
|
# by everyone
|
|
mkdir -p .hub .rocks
|
|
chmod 777 .hub .rocks
|
|
|
|
if [[ ! -f "./.hub/default_id.protobuf" ]]; then
|
|
$COMPOSE_CMD run hubble yarn identity create
|
|
echo "✅ Created Peer Identity"
|
|
else
|
|
echo "✅ Peer Identity exists"
|
|
fi
|
|
|
|
# Stop the "hubble" service if it is already running
|
|
$COMPOSE_CMD stop hubble
|
|
|
|
# Start the "hubble" service
|
|
$COMPOSE_CMD up -d hubble
|
|
}
|
|
|
|
set_compose_command() {
|
|
# Detect whether "docker-compose" or "docker compose" is available
|
|
if command -v docker-compose &> /dev/null; then
|
|
COMPOSE_CMD="docker-compose"
|
|
echo "✅ Using docker-compose"
|
|
elif docker compose version &> /dev/null; then
|
|
COMPOSE_CMD="docker compose"
|
|
echo "✅ Using docker compose"
|
|
else
|
|
echo "❌ Neither 'docker-compose' nor 'docker compose' is available on this system."
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
reexec_as_root_if_needed() {
|
|
# Check if on Linux
|
|
if [[ "$(uname)" == "Linux" ]]; then
|
|
# Check if not running as root, then re-exec as root
|
|
if [[ "$(id -u)" -ne 0 ]]; then
|
|
# Ensure the script runs in the ~/hubble directory
|
|
cd ~/hubble || { echo "Failed to switch to ~/hubble directory."; exit 1; }
|
|
exec sudo "$0" "$@"
|
|
else
|
|
# If the current directory is not named "hubble", change to "~/hubble"
|
|
if [[ "$(basename "$PWD")" != "hubble" ]]; then
|
|
cd ~/hubble
|
|
fi
|
|
echo "✅ Running on Linux."
|
|
fi
|
|
# Check if on macOS
|
|
elif [[ "$(uname)" == "Darwin" ]]; then
|
|
cd ~/hubble || { echo "Failed to switch to ~/hubble directory."; exit 1; }
|
|
echo "✅ Running on macOS."
|
|
fi
|
|
}
|
|
|
|
|
|
# Call the function at the beginning of your script
|
|
reexec_as_root_if_needed "$@"
|
|
|
|
# Check the command-line argument for 'upgrade'
|
|
if [ "$1" == "upgrade" ]; then
|
|
# Ensure the ~/hubble directory exists
|
|
if [ ! -d ~/hubble ]; then
|
|
mkdir -p ~/hubble || { echo "Failed to create ~/hubble directory."; exit 1; }
|
|
fi
|
|
|
|
# Install dependencies
|
|
install_jq
|
|
|
|
# Upgrade this script itself
|
|
self_upgrade "$@"
|
|
|
|
# Call the function to install docker
|
|
install_docker "$@"
|
|
|
|
# Call the function to set the COMPOSE_CMD variable
|
|
set_compose_command
|
|
|
|
# Update the env file if needed
|
|
write_env_file
|
|
|
|
# Fetch the latest docker-compose.yml
|
|
fetch_latest_docker_compose_and_dashboard
|
|
|
|
# Setup the Grafana dashboard
|
|
setup_grafana
|
|
|
|
# Start the hubble service
|
|
start_hubble
|
|
|
|
echo "✅ Upgrade complete."
|
|
echo ""
|
|
echo "Monitor your node at http://localhost:3000/"
|
|
|
|
# Sleep for 5 seconds
|
|
sleep 5
|
|
|
|
# Finally, start showing the logs
|
|
$COMPOSE_CMD logs --tail 100 -f hubble
|
|
|
|
exit 0
|
|
fi
|
|
|
|
# Show logs of the hubble service
|
|
if [ "$1" == "logs" ]; then
|
|
# Ensure the script runs in the ~/hubble directory
|
|
cd ~/hubble || { echo "Failed to switch to ~/hubble directory."; exit 1; }
|
|
|
|
$COMPOSE_CMD logs --tail 100 -f hubble
|
|
exit 0
|
|
fi
|
|
|
|
# If run without args, show a help
|
|
if [ $# -eq 0 ]; then
|
|
echo "hubble.sh - Install or upgrade Hubble"
|
|
echo "Usage: hubble.sh [command]"
|
|
echo " upgrade: Upgrade an existing installation of Hubble"
|
|
echo " logs: Show the logs of the Hubble service"
|
|
echo " help: Show this help"
|
|
exit 0
|
|
fi
|