1#!/bin/bash 2# SOURCE: GRUNTWORKS 3# This script can be used to install Consul and its dependencies. This script has been tested with the following 4# operating systems: 5# 6# 1. Ubuntu 16.04 7# 1. Ubuntu 18.04 8# 1. Amazon Linux 2 9 10set -e 11 12readonly DEFAULT_INSTALL_PATH="/opt/consul" 13readonly DEFAULT_CONSUL_USER="consul" 14readonly DOWNLOAD_PACKAGE_PATH="/tmp/consul.zip" 15 16readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 17readonly SYSTEM_BIN_DIR="/usr/local/bin" 18 19readonly SCRIPT_NAME="$(basename "$0")" 20 21function print_usage { 22 echo 23 echo "Usage: install-consul [OPTIONS]" 24 echo 25 echo "This script can be used to install Consul and its dependencies. This script has been tested with Ubuntu 16.04 and Amazon Linux 2." 26 echo 27 echo "Options:" 28 echo 29 echo -e " --version\t\tThe version of Consul to install. Optional if download-url is provided." 30 echo -e " --download-url\t\tUrl to exact Consul package to be installed. Optional if version is provided." 31 echo -e " --path\t\tThe path where Consul should be installed. Optional. Default: $DEFAULT_INSTALL_PATH." 32 echo -e " --user\t\tThe user who will own the Consul install directories. Optional. Default: $DEFAULT_CONSUL_USER." 33 echo -e " --ca-file-path\t\tPath to a PEM-encoded certificate authority used to encrypt and verify authenticity of client and server connections. Will be installed under <install-path>/tls/ca." 34 echo -e " --cert-file-path\t\tPath to a PEM-encoded certificate, which will be provided to clients or servers to verify the agent's authenticity. Will be installed under <install-path>/tls. Must be provided along with --key-file-path." 35 echo -e " --key-file-path\t\tPath to a PEM-encoded private key, used with the certificate to verify the agent's authenticity. Will be installed under <install-path>/tls. Must be provided along with --cert-file-path" 36 echo 37 echo "Example:" 38 echo 39 echo " install-consul --version 1.2.2" 40} 41 42function log { 43 local -r level="$1" 44 local -r message="$2" 45 local -r timestamp=$(date +"%Y-%m-%d %H:%M:%S") 46 >&2 echo -e "${timestamp} [${level}] [$SCRIPT_NAME] ${message}" 47} 48 49function log_info { 50 local -r message="$1" 51 log "INFO" "$message" 52} 53 54function log_warn { 55 local -r message="$1" 56 log "WARN" "$message" 57} 58 59function log_error { 60 local -r message="$1" 61 log "ERROR" "$message" 62} 63 64function assert_not_empty { 65 local -r arg_name="$1" 66 local -r arg_value="$2" 67 68 if [[ -z "$arg_value" ]]; then 69 log_error "The value for '$arg_name' cannot be empty" 70 print_usage 71 exit 1 72 fi 73} 74 75function assert_either_or { 76 local -r arg1_name="$1" 77 local -r arg1_value="$2" 78 local -r arg2_name="$3" 79 local -r arg2_value="$4" 80 81 if [[ -z "$arg1_value" && -z "$arg2_value" ]]; then 82 log_error "Either the value for '$arg1_name' or '$arg2_name' must be passed, both cannot be empty" 83 print_usage 84 exit 1 85 fi 86} 87 88# A retry function that attempts to run a command a number of times and returns the output 89function retry { 90 local -r cmd="$1" 91 local -r description="$2" 92 93 for i in $(seq 1 5); do 94 log_info "$description" 95 96 # The boolean operations with the exit status are there to temporarily circumvent the "set -e" at the 97 # beginning of this script which exits the script immediatelly for error status while not losing the exit status code 98 output=$(eval "$cmd") && exit_status=0 || exit_status=$? 99 log_info "$output" 100 if [[ $exit_status -eq 0 ]]; then 101 echo "$output" 102 return 103 fi 104 log_warn "$description failed. Will sleep for 10 seconds and try again." 105 sleep 10 106 done; 107 108 log_error "$description failed after 5 attempts." 109 exit $exit_status 110} 111 112function has_yum { 113 [ -n "$(command -v yum)" ] 114} 115 116function has_apt_get { 117 [ -n "$(command -v apt-get)" ] 118} 119 120function install_dependencies { 121 log_info "Installing dependencies" 122 123 if $(has_apt_get); then 124 sudo apt-get update -y 125 sudo apt-get install -y awscli curl unzip jq 126 elif $(has_yum); then 127 sudo yum update -y 128 sudo yum install -y aws curl unzip jq 129 else 130 log_error "Could not find apt-get or yum. Cannot install dependencies on this OS." 131 exit 1 132 fi 133} 134 135function user_exists { 136 local -r username="$1" 137 id "$username" >/dev/null 2>&1 138} 139 140function create_consul_user { 141 local -r username="$1" 142 143 if $(user_exists "$username"); then 144 echo "User $username already exists. Will not create again." 145 else 146 log_info "Creating user named $username" 147 sudo useradd "$username" 148 fi 149} 150 151function create_consul_install_paths { 152 local -r path="$1" 153 local -r username="$2" 154 155 log_info "Creating install dirs for Consul at $path" 156 sudo mkdir -p "$path" 157 sudo mkdir -p "$path/bin" 158 sudo mkdir -p "$path/config" 159 sudo mkdir -p "$path/data" 160 sudo mkdir -p "$path/tls/ca" 161 162 log_info "Changing ownership of $path to $username" 163 sudo chown -R "$username:$username" "$path" 164} 165 166function fetch_binary { 167 local -r version="$1" 168 local download_url="$2" 169 170 if [[ -z "$download_url" && -n "$version" ]]; then 171 download_url="https://releases.hashicorp.com/consul/${version}/consul_${version}_linux_amd64.zip" 172 fi 173 174 retry \ 175 "curl -o '$DOWNLOAD_PACKAGE_PATH' '$download_url' --location --silent --fail --show-error" \ 176 "Downloading Consul to $DOWNLOAD_PACKAGE_PATH" 177} 178 179function install_binary { 180 local -r install_path="$1" 181 local -r username="$2" 182 183 local -r bin_dir="$install_path/bin" 184 local -r consul_dest_path="$bin_dir/consul" 185 local -r run_consul_dest_path="$bin_dir/run-consul" 186 187 unzip -d /tmp "$DOWNLOAD_PACKAGE_PATH" 188 189 log_info "Moving Consul binary to $consul_dest_path" 190 sudo mv "/tmp/consul" "$consul_dest_path" 191 sudo chown "$username:$username" "$consul_dest_path" 192 sudo chmod a+x "$consul_dest_path" 193 194 local -r symlink_path="$SYSTEM_BIN_DIR/consul" 195 if [[ -f "$symlink_path" ]]; then 196 log_info "Symlink $symlink_path already exists. Will not add again." 197 else 198 log_info "Adding symlink to $consul_dest_path in $symlink_path" 199 sudo ln -s "$consul_dest_path" "$symlink_path" 200 fi 201 202 log_info "Copying Consul run script to $run_consul_dest_path" 203 sudo cp "$SCRIPT_DIR/run-consul" "$run_consul_dest_path" 204 sudo chown "$username:$username" "$run_consul_dest_path" 205 sudo chmod a+x "$run_consul_dest_path" 206} 207 208function install_tls_certificates { 209 local -r path="$1" 210 local -r user="$2" 211 local -r ca_file_path="$3" 212 local -r cert_file_path="$4" 213 local -r key_file_path="$5" 214 215 local -r consul_tls_certs_path="$path/tls" 216 local -r ca_certs_path="$consul_tls_certs_path/ca" 217 218 log_info "Moving TLS certs to $consul_tls_certs_path and $ca_certs_path" 219 220 sudo mkdir -p "$ca_certs_path" 221 sudo mv "$ca_file_path" "$ca_certs_path/" 222 sudo mv "$cert_file_path" "$consul_tls_certs_path/" 223 sudo mv "$key_file_path" "$consul_tls_certs_path/" 224 225 sudo chown -R "$user:$user" "$consul_tls_certs_path/" 226 sudo find "$consul_tls_certs_path/" -type f -exec chmod u=r,g=,o= {} \; 227} 228 229function install { 230 local version="" 231 local download_url="" 232 local path="$DEFAULT_INSTALL_PATH" 233 local user="$DEFAULT_CONSUL_USER" 234 local ca_file_path="" 235 local cert_file_path="" 236 local key_file_path="" 237 238 while [[ $# > 0 ]]; do 239 local key="$1" 240 241 case "$key" in 242 --version) 243 version="$2" 244 shift 245 ;; 246 --download-url) 247 download_url="$2" 248 shift 249 ;; 250 --path) 251 path="$2" 252 shift 253 ;; 254 --user) 255 user="$2" 256 shift 257 ;; 258 --ca-file-path) 259 assert_not_empty "$key" "$2" 260 ca_file_path="$2" 261 shift 262 ;; 263 --cert-file-path) 264 assert_not_empty "$key" "$2" 265 cert_file_path="$2" 266 shift 267 ;; 268 --key-file-path) 269 assert_not_empty "$key" "$2" 270 key_file_path="$2" 271 shift 272 ;; 273 --help) 274 print_usage 275 exit 276 ;; 277 *) 278 log_error "Unrecognized argument: $key" 279 print_usage 280 exit 1 281 ;; 282 esac 283 284 shift 285 done 286 287 assert_either_or "--version" "$version" "--download-url" "$download_url" 288 assert_not_empty "--path" "$path" 289 assert_not_empty "--user" "$user" 290 291 log_info "Starting Consul install" 292 293 install_dependencies 294 create_consul_user "$user" 295 create_consul_install_paths "$path" "$user" 296 297 fetch_binary "$version" "$download_url" 298 install_binary "$path" "$user" 299 300 if [[ -n "$ca_file_path" || -n "$cert_file_path" || -n "$key_file_path" ]]; then 301 install_tls_certificates "$path" "$user" "$ca_file_path" "$cert_file_path" "$key_file_path" 302 fi 303 304 if command -v consul; then 305 log_info "Consul install complete!"; 306 else 307 log_info "Could not find consul command. Aborting."; 308 exit 1; 309 fi 310} 311 312install "$@" 313