Skip to content

Instantly share code, notes, and snippets.

@Marusyk
Created October 17, 2024 09:06
Show Gist options
  • Save Marusyk/560144364806648072c842d4068d6bc6 to your computer and use it in GitHub Desktop.
Save Marusyk/560144364806648072c842d4068d6bc6 to your computer and use it in GitHub Desktop.
elastic start local
#!/bin/sh
# --------------------------------------------------------
# Run Elasticsearch and Kibana for local testing
# Note: do not use this script in a production environment
# --------------------------------------------------------
#
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
# or more contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright
# ownership. Elasticsearch B.V. licenses this file to you under
# the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
set -eu
startup() {
echo
echo ' ______ _ _ _ '
echo ' | ____| | | | (_) '
echo ' | |__ | | __ _ ___| |_ _ ___ '
echo ' | __| | |/ _` / __| __| |/ __|'
echo ' | |____| | (_| \__ \ |_| | (__ '
echo ' |______|_|\__,_|___/\__|_|\___|'
echo '-------------------------------------------------'
echo 'πŸš€ Run Elasticsearch and Kibana for local testing'
echo '-------------------------------------------------'
echo
echo 'ℹ️ Do not use this script in a production environment'
echo
# Version
version="0.2.0"
# Folder name for the installation
installation_folder="elastic-start-local"
# API key name for Elasticsearch
api_key_name="elastic-start-local"
# Name of the error log
error_log="error-start-local.log"
# Minimum version for docker-compose
min_docker_compose="1.29.0"
# Elasticsearch container name
elasticsearch_container_name="es-local-dev"
# Kibana container name
kibana_container_name="kibana-local-dev"
}
# Get linux distribution
get_os_info() {
if [ -f /etc/os-release ]; then
# Most modern Linux distributions have this file
. /etc/os-release
echo "Distribution: $NAME"
echo "Version: $VERSION"
elif [ -f /etc/lsb-release ]; then
# For older distributions using LSB (Linux Standard Base)
. /etc/lsb-release
echo "Distribution: $DISTRIB_ID"
echo "Version: $DISTRIB_RELEASE"
elif [ -f /etc/debian_version ]; then
# For Debian-based distributions without os-release or lsb-release
echo "Distribution: Debian"
echo "Version: $(cat /etc/debian_version)"
elif [ -f /etc/redhat-release ]; then
# For Red Hat-based distributions
echo "Distribution: $(cat /etc/redhat-release)"
elif [[ "$OSTYPE" == "darwin"* ]]; then
# macOS detection
echo "Distribution: macOS"
echo "Version: $(sw_vers -productVersion)"
elif [[ "$OSTYPE" == "cygwin" || "$OSTYPE" == "msys" || "$OSTYPE" == "win32" ]]; then
# Windows detection in environments like Git Bash, Cygwin, or MinGW
echo "Distribution: Windows"
echo "Version: $(cmd.exe /c ver | tr -d '\r')"
elif [[ "$OSTYPE" == "linux-gnu" && "$(uname -r)" == *"Microsoft"* ]]; then
# Windows Subsystem for Linux (WSL) detection
echo "Distribution: Windows (WSL)"
echo "Version: $(uname -r)"
else
echo "Unknown operating system"
fi
}
# Check if a command exists
available() { command -v $1 >/dev/null; }
# Revert the status, removing containers, volumes, network and folder
cleanup() {
if [ -d "./../$folder_to_clean" ]; then
if [ -f "docker-compose.yml" ]; then
$docker_clean >/dev/null 2>&1
$docker_remove_volumes >/dev/null 2>&1
fi
cd ..
rm -rf ${folder_to_clean}
fi
}
# Generate the error log
# parameter 1: error message
# parameter 2: the container names to retrieve, separated by comma
generate_error_log() {
local msg=$1
local docker_services=$2
local error_file="$error_log"
if [ -d "./../$folder_to_clean" ]; then
error_file="./../$error_log"
fi
if [ -n "${msg}" ]; then
echo "${msg}" > "$error_file"
fi
echo "Docker engine: $(docker --version)" >> "$error_file"
echo "Docker compose: ${docker_version}" >> "$error_file"
echo $(get_os_info) >> "$error_file"
for service in $docker_services; do
echo "-- Logs of service ${service}:" >> "$error_file"
docker logs "${service}" >> "$error_file" 2> /dev/null
done
echo "An error log has been generated in ${error_log} file."
echo "If you need assistance, open an issue at https://github.com/elastic/start-local/issues"
}
# Compare versions
# parameter 1: version to compare
# parameter 2: version to compare
compare_versions() {
local v1=$1
local v2=$2
original_ifs="$IFS"
IFS='.'; set -- $v1; v1_major=$1; v1_minor=$2; v1_patch=$3
IFS='.'; set -- $v2; v2_major=$1; v2_minor=$2; v2_patch=$3
IFS="$original_ifs"
[ "$v1_major" -lt "$v2_major" ] && echo "lt" && return 0
[ "$v1_major" -gt "$v2_major" ] && echo "gt" && return 0
[ "$v1_minor" -lt "$v2_minor" ] && echo "lt" && return 0
[ "$v1_minor" -gt "$v2_minor" ] && echo "gt" && return 0
[ "$v1_patch" -lt "$v2_patch" ] && echo "lt" && return 0
[ "$v1_patch" -gt "$v2_patch" ] && echo "gt" && return 0
echo "eq"
}
# Wait for availability of Kibana
# parameter: timeout in seconds
wait_for_kibana() {
local timeout="${1:-60}"
echo "- Waiting for Kibana to be ready"
echo
local start_time="$(date +%s)"
until curl -s -I http://localhost:5601 | grep -q 'HTTP/1.1 302 Found'; do
elapsed_time="$(($(date +%s) - start_time))"
if [ "$elapsed_time" -ge "$timeout" ]; then
error_msg="Error: Kibana timeout of ${timeout} sec"
echo $error_msg
generate_error_log "${error_msg}" "${elasticsearch_container_name} ${kibana_container_name} kibana_settings"
cleanup
exit 1
fi
sleep 2
done
}
# Generates a random password with letters and numbers
# parameter: size of the password (default is 8 characters)
random_password() {
local LENGTH="${1:-8}"
echo $(LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | head -c ${LENGTH})
}
# Returns the latest Elasticsearch tag version
get_latest_version() {
local tags="$(curl -s "https://api.github.com/repos/elastic/elasticsearch/tags")"
local latest="$(echo "$tags" | grep -m 1 '"name"' | grep -Eo '[0-9.]+')"
echo $latest
}
# Create an API key for Elasticsearch
# parameter 1: the Elasticsearch password
# parameter 2: name of the API key to generate
create_api_key() {
local es_password=$1
local name=$2
local response="$(curl -s -u "elastic:${es_password}" -X POST http://localhost:9200/_security/api_key -d "{\"name\": \"${name}\"}" -H "Content-Type: application/json")"
if [ -z "$response" ]; then
echo ""
else
local api_key="$(echo "$response" | grep -Eo '"encoded":"[A-Za-z0-9+/=]+' | grep -Eo '[A-Za-z0-9+/=]+' | tail -n 1)"
echo $api_key
fi
}
# Check if a container is runnning
# parameter: the name of the container
check_container_running() {
local container_name=$1
local containers=$(docker ps --format '{{.Names}}')
if $(echo "$containers" | grep -q "^${container_name}$"); then
echo "The docker container '$container_name' is already running!"
echo "You can have only one running at time."
echo "To stop the container run the following command:"
echo
echo "docker stop $container_name"
exit 1
fi
}
check_requirements() {
# Check the requirements
if ! available "curl"; then
echo "Error: curl command is required"
echo "You can install it from https://curl.se/download.html."
exit 1
fi
if ! available "grep"; then
echo "Error: grep command is required"
echo "You can install it from https://www.gnu.org/software/grep/."
exit 1
fi
need_wait_for_kibana=true
# Check for "docker compose" or "docker-compose"
set +e
docker compose >/dev/null 2>&1
if [ $? -ne 0 ]; then
if ! available "docker-compose"; then
if ! available "docker"; then
echo "Error: docker command is required"
echo "You can install it from https://docs.docker.com/engine/install/."
exit 1
fi
echo "Error: docker compose is required"
echo "You can install it from https://docs.docker.com/compose/install/"
exit 1
fi
docker="docker-compose up -d"
docker_stop="docker-compose stop"
docker_clean="docker-compose rm -fsv"
docker_remove_volumes="docker-compose down -v"
docker_version=$(docker-compose --version | head -n 1 | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+')
if [ $(compare_versions "$docker_version" "$min_docker_compose") = "lt" ]; then
echo "Unfortunately we don't support docker compose ${docker_version}. The minimum required version is $min_docker_compose."
echo "You can migrate you docker compose from https://docs.docker.com/compose/migrate/"
cleanup
exit 1
fi
else
docker_stop="docker compose stop"
docker_clean="docker compose rm -fsv"
docker_remove_volumes="docker compose down -v"
docker_version=$(docker compose version | head -n 1 | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+')
# --wait option has been introduced in 2.1.1+
if [ "$(compare_versions "$docker_version" "2.1.0")" = "gt" ]; then
docker="docker compose up --wait"
need_wait_for_kibana=false
else
docker="docker compose up -d"
fi
fi
set -e
}
check_installation_folder() {
# Check if $installation_folder exists
folder=$installation_folder
if [ -d "$folder" ]; then
if [ -n "$(ls -A "$folder")" ]; then
echo "It seems you have already a start-local installation in '${folder}'."
if [ -f "$folder/uninstall.sh" ]; then
echo "I cannot proceed unless you uninstall it, using the following command:"
echo "cd $folder && ./uninstall.sh"
else
echo "I did not find the uninstall.sh file, you need to proceed manually."
if [ -f "$folder/docker-compose.yml" ] && [ -f "$folder/.env" ]; then
echo "Execute the following commands:"
echo "cd $folder"
echo $docker_clean
echo $docker_remove_volumes
echo "cd .."
echo "rm -rf $folder"
fi
echo "Finally, remove the folder '${folder}' and try again."
exit 1
fi
fi
fi
}
check_docker_services() {
# Check for docker containers running
check_container_running "$elasticsearch_container_name"
check_container_running "$kibana_container_name"
check_container_running "kibana_settings"
}
create_installation_folder() {
# If $folder already exists, it is empty, see above
if [ ! -d "$folder" ]; then
mkdir $folder
fi
cd $folder
folder_to_clean=$folder
}
generate_passwords_api_keys() {
# Generate random passwords
es_password="$(random_password)"
kibana_password="$(random_password)"
es_version="$(get_latest_version)"
kibana_encryption_key="$(random_password 32)"
}
create_env_file() {
# Create the .env file
cat > .env <<- EOM
ES_LOCAL_VERSION=$es_version
ES_LOCAL_CONTAINER_NAME=$elasticsearch_container_name
ES_LOCAL_PASSWORD=$es_password
ES_LOCAL_URL=http://localhost:9200
ES_LOCAL_PORT=9200
ES_LOCAL_HEAP_INIT=128m
ES_LOCAL_HEAP_MAX=2g
KIBANA_LOCAL_CONTAINER_NAME=$kibana_container_name
KIBANA_LOCAL_PORT=5601
KIBANA_LOCAL_PASSWORD=$kibana_password
KIBANA_ENCRYPTION_KEY=$kibana_encryption_key
EOM
}
# Create the start script (start.sh)
create_start_file() {
cat > start.sh <<-'EOM'
#!/bin/sh
# Start script for start-local
# More information: https://github.com/elastic/start-local
set -eu
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd ${SCRIPT_DIR}
EOM
if [ "$need_wait_for_kibana" = true ]; then
cat >> start.sh <<-'EOM'
wait_for_kibana() {
local timeout="${1:-60}"
echo "- Waiting for Kibana to be ready"
echo
local start_time="$(date +%s)"
until curl -s -I http://localhost:5601 | grep -q 'HTTP/1.1 302 Found'; do
elapsed_time="$(($(date +%s) - start_time))"
if [ "$elapsed_time" -ge "$timeout" ]; then
echo "Error: Kibana timeout of ${timeout} sec"
exit 1
fi
sleep 2
done
}
EOM
fi
cat >> start.sh <<- EOM
$docker
EOM
if [ "$need_wait_for_kibana" = true ]; then
cat >> start.sh <<-'EOM'
wait_for_kibana 120
EOM
fi
chmod +x start.sh
}
# Create the stop script (stop.sh)
create_stop_file() {
cat > stop.sh <<-'EOM'
#!/bin/sh
# Stop script for start-local
# More information: https://github.com/elastic/start-local
set -eu
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd ${SCRIPT_DIR}
EOM
cat >> stop.sh <<- EOM
$docker_stop
EOM
chmod +x stop.sh
}
# Create the uninstall script (uninstall.sh)
create_uninstall_file() {
cat > uninstall.sh <<-'EOM'
#!/bin/sh
# Uninstall script for start-local
# More information: https://github.com/elastic/start-local
set -eu
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ask_confirmation() {
echo "Do you want to continue? (yes/no)"
read answer
case "$answer" in
yes|y|Y|Yes|YES)
return 0 # true
;;
no|n|N|No|NO)
return 1 # false
;;
*)
echo "Please answer yes or no."
ask_confirmation # Ask again if the input is invalid
;;
esac
}
cd ${SCRIPT_DIR}
if [ ! -e "docker-compose.yml" ]; then
echo "Error: I cannot find the docker-compose.yml file"
echo "I cannot uninstall start-local.
fi
if [ ! -e ".env" ]; then
echo "Error: I cannot find the .env file"
echo "I cannot uninstall start-local.
fi
echo "This script will uninstall start-local."
echo "All data will be deleted and cannot be recovered."
if ask_confirmation; then
EOM
cat >> uninstall.sh <<- EOM
$docker_clean
$docker_remove_volumes
rm docker-compose.yml .env uninstall.sh start.sh stop.sh
echo "Start-local successfully removed"
fi
EOM
chmod +x uninstall.sh
}
create_docker_compose_file() {
# Create the docker-compose-yml file
cat > docker-compose.yml <<-'EOM'
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:${ES_LOCAL_VERSION}
container_name: ${ES_LOCAL_CONTAINER_NAME}
volumes:
- dev-elasticsearch:/usr/share/elasticsearch/data
ports:
- 127.0.0.1:${ES_LOCAL_PORT}:9200
environment:
- discovery.type=single-node
- ELASTIC_PASSWORD=${ES_LOCAL_PASSWORD}
- xpack.security.enabled=true
- xpack.security.http.ssl.enabled=false
- xpack.license.self_generated.type=trial
- xpack.ml.use_auto_machine_memory_percent=true
- ES_JAVA_OPTS=-Xms${ES_LOCAL_HEAP_INIT} -Xmx${ES_LOCAL_HEAP_MAX}
ulimits:
memlock:
soft: -1
hard: -1
healthcheck:
test:
[
"CMD-SHELL",
"curl --output /dev/null --silent --head --fail -u elastic:${ES_LOCAL_PASSWORD} http://elasticsearch:${ES_LOCAL_PORT}",
]
interval: 5s
timeout: 5s
retries: 10
kibana_settings:
depends_on:
elasticsearch:
condition: service_healthy
image: docker.elastic.co/elasticsearch/elasticsearch:${ES_LOCAL_VERSION}
container_name: kibana_settings
restart: 'no'
command: >
bash -c '
echo "Setup the kibana_system password";
until curl -s -u "elastic:${ES_LOCAL_PASSWORD}" -X POST http://elasticsearch:${ES_LOCAL_PORT}/_security/user/kibana_system/_password -d "{\"password\":\"'${KIBANA_LOCAL_PASSWORD}'\"}" -H "Content-Type: application/json" | grep -q "^{}"; do sleep 5; done;
'
kibana:
depends_on:
kibana_settings:
condition: service_completed_successfully
image: docker.elastic.co/kibana/kibana:${ES_LOCAL_VERSION}
container_name: ${KIBANA_LOCAL_CONTAINER_NAME}
volumes:
- dev-kibana:/usr/share/kibana/data
ports:
- 127.0.0.1:${KIBANA_LOCAL_PORT}:5601
environment:
- SERVER_NAME=kibana
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
- ELASTICSEARCH_USERNAME=kibana_system
- ELASTICSEARCH_PASSWORD=${KIBANA_LOCAL_PASSWORD}
- XPACK_ENCRYPTEDSAVEDOBJECTS_ENCRYPTIONKEY=${KIBANA_ENCRYPTION_KEY}
- ELASTICSEARCH_PUBLICBASEURL=http://localhost:${ES_LOCAL_PORT}
healthcheck:
test:
[
"CMD-SHELL",
"curl -s -I http://kibana:5601 | grep -q 'HTTP/1.1 302 Found'",
]
interval: 10s
timeout: 10s
retries: 20
volumes:
dev-elasticsearch:
dev-kibana:
EOM
}
print_steps() {
echo "βŒ›οΈ Setting up Elasticsearch and Kibana v${es_version}..."
echo
echo "- Generated random passwords"
echo "- Created the ${folder} folder containing the files:"
echo " - .env, with settings"
echo " - docker-compose.yml, for Docker services"
echo " - start/stop/uninstall commands"
}
running_docker_compose() {
# Execute docker compose
echo "- Running ${docker}"
echo
set +e
$docker
if [ $? -ne 0 ]; then
error_msg="Error: ${docker} command failed!"
echo $error_msg
generate_error_log "${error_msg}" "${elasticsearch_container_name} ${kibana_container_name} kibana_settings"
cleanup
exit 1
fi
set -e
}
api_key() {
# Create an API key for Elasticsearch
api_key=$(create_api_key $es_password $api_key_name)
if [ -n "$api_key" ]; then
echo "ES_LOCAL_API_KEY=${api_key}" >> .env
fi
}
kibana_wait() {
if [ "$need_wait_for_kibana" = true ]; then
wait_for_kibana 120
fi
}
success() {
echo
echo "πŸŽ‰ Congrats, Elasticsearch and Kibana are installed and running in Docker!"
echo
echo "🌐 Open your browser at http://localhost:5601"
echo
echo " Username: elastic"
echo " Password: ${es_password}"
echo
echo "πŸ”Œ Elasticsearch API endpoint: http://localhost:9200"
if [ -n "$api_key" ]; then
echo "πŸ”‘ API key: $api_key"
echo
else
echo "πŸ”‘ Use basic auth or create an API key"
echo "https://www.elastic.co/guide/en/kibana/current/api-keys.html"
echo
fi
echo
echo "Learn more at https://github.com/elastic/start-local"
echo
}
main() {
startup
check_requirements
check_installation_folder
check_docker_services
create_installation_folder
generate_passwords_api_keys
create_start_file
create_stop_file
create_uninstall_file
create_env_file
create_docker_compose_file
print_steps
running_docker_compose
api_key
kibana_wait
success
}
ctrl_c() {
cleanup
exit 1
}
# Trap ctrl-c
trap ctrl_c INT
# Execute the script
main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment