Last active
October 20, 2025 12:47
-
-
Save pipethedev/2b1fd8781e22664fc3eb0b1734c20f7e to your computer and use it in GitHub Desktop.
Script to setup consul in client or server mode with auto server discovery in client mode
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| set -e | |
| CONSUL_VERSION="1.21.5" | |
| CONSUL_URL="https://releases.hashicorp.com/consul/${CONSUL_VERSION}/consul_${CONSUL_VERSION}_linux_amd64.zip" | |
| INSTALL_DIR="/usr/local/bin" | |
| CONFIG_DIR="/etc/consul.d" | |
| DATA_DIR="/opt/consul" | |
| TEMP_DIR="/tmp/consul-install" | |
| MODE="" | |
| BIND_ADDR="" | |
| BOOTSTRAP_EXPECT=3 | |
| RETRY_JOIN="consul-servers.internal" | |
| DATACENTER="dc1" | |
| ENABLE_UI="true" | |
| NUKE="false" | |
| GREEN='\033[0;32m' | |
| YELLOW='\033[1;33m' | |
| RED='\033[0;31m' | |
| NC='\033[0m' | |
| log_info() { echo -e "${GREEN}[INFO]${NC} $1"; } | |
| log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } | |
| log_error() { echo -e "${RED}[ERROR]${NC} $1"; } | |
| detect_bind_address() { | |
| local addr="" | |
| if ip addr show tailscale0 &>/dev/null; then | |
| addr=$(ip addr show tailscale0 | grep "inet " | awk '{print $2}' | cut -d/ -f1 | head -n1) | |
| if [ -n "$addr" ]; then | |
| echo "$addr" | |
| return | |
| fi | |
| fi | |
| addr=$(ip route get 8.8.8.8 | grep -oP 'src \K\S+') | |
| if [ -n "$addr" ]; then | |
| echo "$addr" | |
| return | |
| fi | |
| addr=$(hostname -I | awk '{print $1}') | |
| if [ -n "$addr" ]; then | |
| echo "$addr" | |
| return | |
| fi | |
| >&2 echo -e "${RED}[ERROR]${NC} Could not detect IP address" | |
| exit 1 | |
| } | |
| show_usage() { | |
| cat << EOF | |
| Usage: $0 --mode=<server|client> [OPTIONS] | |
| Required: | |
| --mode=<server|client> Installation mode | |
| Optional: | |
| --bind-addr=<IP> IP address to bind to (auto-detects if not provided) | |
| --data-dir=<PATH> Data directory (default: /opt/consul) | |
| --bootstrap-expect=<N> Expected servers (default: 3, server mode only) | |
| --retry-join=<DNS> DNS name for retry-join (default: consul-servers.internal) | |
| --datacenter=<NAME> Datacenter name (default: dc1) | |
| --enable-ui=<true|false> Enable UI (default: true for server, false for client) | |
| --nuke=<true|false> Delete existing data directory (default: false) | |
| Examples: | |
| $0 --mode=server | |
| $0 --mode=server --nuke=true | |
| $0 --mode=client --bind-addr=10.0.1.10 | |
| EOF | |
| exit 1 | |
| } | |
| for arg in "$@"; do | |
| case $arg in | |
| --mode=*) | |
| MODE="${arg#*=}" | |
| ;; | |
| --bind-addr=*) | |
| BIND_ADDR="${arg#*=}" | |
| ;; | |
| --data-dir=*) | |
| DATA_DIR="${arg#*=}" | |
| ;; | |
| --bootstrap-expect=*) | |
| BOOTSTRAP_EXPECT="${arg#*=}" | |
| ;; | |
| --retry-join=*) | |
| RETRY_JOIN="${arg#*=}" | |
| ;; | |
| --datacenter=*) | |
| DATACENTER="${arg#*=}" | |
| ;; | |
| --enable-ui=*) | |
| ENABLE_UI="${arg#*=}" | |
| ;; | |
| --nuke=*) | |
| NUKE="${arg#*=}" | |
| ;; | |
| --help|-h) | |
| show_usage | |
| ;; | |
| *) | |
| log_error "Unknown argument: $arg" | |
| show_usage | |
| ;; | |
| esac | |
| done | |
| if [ -z "$MODE" ]; then | |
| log_error "Missing required argument: --mode" | |
| show_usage | |
| fi | |
| if [ "$MODE" != "server" ] && [ "$MODE" != "client" ]; then | |
| log_error "Mode must be 'server' or 'client'" | |
| show_usage | |
| fi | |
| if [ "$MODE" = "client" ] && [ "$ENABLE_UI" = "true" ]; then | |
| ENABLE_UI="false" | |
| fi | |
| if [ -z "$BIND_ADDR" ]; then | |
| log_info "No bind address provided, auto-detecting..." | |
| BIND_ADDR=$(detect_bind_address) | |
| fi | |
| if [ "$EUID" -ne 0 ]; then | |
| log_error "Please run as root (use sudo)" | |
| exit 1 | |
| fi | |
| echo "======================================" | |
| echo "Consul Installation Script" | |
| echo "======================================" | |
| echo "Mode: $MODE" | |
| echo "Bind Address: $BIND_ADDR" | |
| echo "Data Directory: $DATA_DIR" | |
| echo "UI Enabled: $ENABLE_UI" | |
| echo "Nuke Data: $NUKE" | |
| [ "$MODE" = "server" ] && echo "Bootstrap Expect: $BOOTSTRAP_EXPECT" | |
| echo "Retry Join: $RETRY_JOIN" | |
| echo "Datacenter: $DATACENTER" | |
| echo "======================================" | |
| echo "" | |
| log_info "Stopping any existing Consul services..." | |
| systemctl stop consul 2>/dev/null || true | |
| log_info "Killing any remaining Consul processes..." | |
| pkill -9 consul 2>/dev/null || true | |
| sleep 2 | |
| log_info "Cleaning old config files..." | |
| rm -f ${CONFIG_DIR}/*.hcl ${CONFIG_DIR}/*.json | |
| if [ "$NUKE" = "true" ]; then | |
| log_warn "Nuking existing data directory: ${DATA_DIR}" | |
| rm -rf ${DATA_DIR}/* | |
| fi | |
| log_info "Removing any existing Consul containers..." | |
| docker ps -q --filter "ancestor=hashicorp/consul" | xargs -r docker stop 2>/dev/null || true | |
| docker ps -aq --filter "ancestor=hashicorp/consul" | xargs -r docker rm 2>/dev/null || true | |
| if ! command -v unzip &> /dev/null; then | |
| log_info "Installing unzip..." | |
| apt-get update -qq | |
| apt-get install -y unzip | |
| fi | |
| log_info "Downloading Consul ${CONSUL_VERSION}..." | |
| mkdir -p ${TEMP_DIR} | |
| cd ${TEMP_DIR} | |
| wget -q ${CONSUL_URL} | |
| log_info "Installing Consul..." | |
| unzip -oq consul_${CONSUL_VERSION}_linux_amd64.zip | |
| if [ -f "${INSTALL_DIR}/consul" ]; then | |
| log_warn "Backing up existing Consul binary..." | |
| cp ${INSTALL_DIR}/consul ${INSTALL_DIR}/consul.backup | |
| fi | |
| mv consul ${INSTALL_DIR}/consul | |
| chmod +x ${INSTALL_DIR}/consul | |
| INSTALLED_VERSION=$(consul version | head -n1) | |
| log_info "Installed: ${INSTALLED_VERSION}" | |
| log_info "Creating directories..." | |
| mkdir -p ${CONFIG_DIR} ${DATA_DIR} | |
| chmod 755 ${CONFIG_DIR} ${DATA_DIR} | |
| log_info "Creating Consul configuration..." | |
| if [ "$MODE" = "server" ]; then | |
| cat > ${CONFIG_DIR}/consul.hcl <<'EOF' | |
| datacenter = "dc1" | |
| node_name = "HOSTNAME_PLACEHOLDER" | |
| data_dir = "DATA_DIR_PLACEHOLDER" | |
| log_level = "INFO" | |
| server = true | |
| bootstrap_expect = BOOTSTRAP_EXPECT_PLACEHOLDER | |
| bind_addr = "BIND_ADDR_PLACEHOLDER" | |
| client_addr = "0.0.0.0" | |
| advertise_addr = "BIND_ADDR_PLACEHOLDER" | |
| retry_join = ["RETRY_JOIN_PLACEHOLDER"] | |
| connect { | |
| enabled = true | |
| } | |
| ui_config { | |
| enabled = ENABLE_UI_PLACEHOLDER | |
| } | |
| ports { | |
| grpc = 8502 | |
| grpc_tls = 8503 | |
| http = 8500 | |
| dns = 8600 | |
| https = 8501 | |
| serf_lan = 8301 | |
| serf_wan = 8302 | |
| server = 8300 | |
| } | |
| performance { | |
| raft_multiplier = 1 | |
| } | |
| autopilot { | |
| cleanup_dead_servers = true | |
| last_contact_threshold = "200ms" | |
| max_trailing_logs = 250 | |
| server_stabilization_time = "10s" | |
| } | |
| raft_protocol = 3 | |
| EOF | |
| sed -i "s|HOSTNAME_PLACEHOLDER|$(hostname)|g" ${CONFIG_DIR}/consul.hcl | |
| sed -i "s|DATA_DIR_PLACEHOLDER|${DATA_DIR}|g" ${CONFIG_DIR}/consul.hcl | |
| sed -i "s|BOOTSTRAP_EXPECT_PLACEHOLDER|${BOOTSTRAP_EXPECT}|g" ${CONFIG_DIR}/consul.hcl | |
| sed -i "s|BIND_ADDR_PLACEHOLDER|${BIND_ADDR}|g" ${CONFIG_DIR}/consul.hcl | |
| sed -i "s|RETRY_JOIN_PLACEHOLDER|${RETRY_JOIN}|g" ${CONFIG_DIR}/consul.hcl | |
| sed -i "s|ENABLE_UI_PLACEHOLDER|${ENABLE_UI}|g" ${CONFIG_DIR}/consul.hcl | |
| else | |
| cat > ${CONFIG_DIR}/consul.hcl <<'EOF' | |
| datacenter = "dc1" | |
| node_name = "HOSTNAME_PLACEHOLDER" | |
| data_dir = "DATA_DIR_PLACEHOLDER" | |
| log_level = "INFO" | |
| server = false | |
| bind_addr = "BIND_ADDR_PLACEHOLDER" | |
| client_addr = "0.0.0.0" | |
| advertise_addr = "BIND_ADDR_PLACEHOLDER" | |
| retry_join = ["RETRY_JOIN_PLACEHOLDER"] | |
| connect { | |
| enabled = true | |
| } | |
| ui_config { | |
| enabled = ENABLE_UI_PLACEHOLDER | |
| } | |
| ports { | |
| grpc = 8502 | |
| http = 8500 | |
| dns = 8600 | |
| https = 8501 | |
| } | |
| performance { | |
| raft_multiplier = 1 | |
| } | |
| EOF | |
| sed -i "s|HOSTNAME_PLACEHOLDER|$(hostname)|g" ${CONFIG_DIR}/consul.hcl | |
| sed -i "s|DATA_DIR_PLACEHOLDER|${DATA_DIR}|g" ${CONFIG_DIR}/consul.hcl | |
| sed -i "s|BIND_ADDR_PLACEHOLDER|${BIND_ADDR}|g" ${CONFIG_DIR}/consul.hcl | |
| sed -i "s|RETRY_JOIN_PLACEHOLDER|${RETRY_JOIN}|g" ${CONFIG_DIR}/consul.hcl | |
| sed -i "s|ENABLE_UI_PLACEHOLDER|${ENABLE_UI}|g" ${CONFIG_DIR}/consul.hcl | |
| fi | |
| log_info "Validating Consul configuration..." | |
| if ! consul validate ${CONFIG_DIR}/consul.hcl; then | |
| log_error "Config validation failed!" | |
| log_error "Config file content:" | |
| cat ${CONFIG_DIR}/consul.hcl | |
| exit 1 | |
| fi | |
| log_info "Configuration validated successfully" | |
| log_info "Creating systemd service..." | |
| cat > /etc/systemd/system/consul.service <<EOF | |
| [Unit] | |
| Description=Consul Agent | |
| After=network-online.target | |
| Wants=network-online.target | |
| [Service] | |
| Type=simple | |
| User=root | |
| ExecStart=${INSTALL_DIR}/consul agent -config-dir=${CONFIG_DIR} | |
| ExecReload=/bin/kill -HUP \$MAINPID | |
| KillMode=process | |
| Restart=on-failure | |
| RestartSec=5 | |
| LimitNOFILE=65536 | |
| [Install] | |
| WantedBy=multi-user.target | |
| EOF | |
| log_info "Enabling and starting Consul service..." | |
| systemctl daemon-reload | |
| systemctl enable consul | |
| systemctl start consul | |
| log_info "Waiting for Consul to start..." | |
| sleep 5 | |
| if systemctl is-active --quiet consul; then | |
| log_info "Consul is running successfully!" | |
| echo "" | |
| log_info "Consul Status:" | |
| consul members 2>/dev/null || log_warn "Cluster not yet formed (normal on first server)" | |
| echo "" | |
| log_info "Consul is accessible at:" | |
| if [ "$ENABLE_UI" = "true" ]; then | |
| echo " - UI: http://${BIND_ADDR}:8500" | |
| fi | |
| echo " - HTTP API: http://${BIND_ADDR}:8500" | |
| echo " - DNS: ${BIND_ADDR}:8600" | |
| else | |
| log_error "Consul failed to start!" | |
| echo "" | |
| log_error "Check logs with: journalctl -u consul -n 50" | |
| exit 1 | |
| fi | |
| log_info "Cleaning up..." | |
| cd / | |
| rm -rf ${TEMP_DIR} | |
| echo "" | |
| echo "======================================" | |
| log_info "Installation Complete!" | |
| echo "======================================" | |
| echo "" | |
| echo "Config file: ${CONFIG_DIR}/consul.hcl" | |
| echo "Data directory: ${DATA_DIR}" | |
| echo "" | |
| echo "Useful commands:" | |
| echo " - Check status: systemctl status consul" | |
| echo " - View logs: journalctl -u consul -f" | |
| echo " - Restart: systemctl restart consul" | |
| echo " - Stop: systemctl stop consul" | |
| echo " - Members: consul members" | |
| echo " - Services: consul catalog services" | |
| echo "" | |
| if [ "$MODE" = "server" ]; then | |
| echo "NOTE: Make sure DNS is configured for '${RETRY_JOIN}' pointing to all server IPs" | |
| fi |
Author
Thanks, this will come in very handy!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Consul Bare Metal Setup Guide
Prerequisites
Step 1: Configure DNS Resolution
On ALL nodes, add server IPs to
/etc/hosts:Replace IPs with your actual server IPs.
Step 2: Download Installation Script
Step 3: Install on Server Nodes
Run on each of your 3 server nodes:
Options:
Step 4: Install on Client Nodes
Run on each client node:
# Client with UI enabled sudo ./install-consul.sh --mode=client --enable-ui=trueStep 5: Add More Servers (Optional)
To add server 4, 5, etc after initial cluster is running:
# Just run the same command, they'll join automatically sudo ./install-consul.sh --mode=server# Server without UI sudo ./install-consul.sh --mode=server --enable-ui=falseNote:
bootstrap_expectstays at 3 forever. New servers join viaretry_join.Verification
Check cluster:
Access UI:
Common Commands
Data Location
Default:
/opt/consulCustom:
--data-dir=/your/pathTroubleshooting
Cluster not forming?
/etc/hostson all nodesjournalctl -u consul -n 100Service won't start?
netstat -tlnp | grep 8500