- Restructure repo into docker/ and native/ deployment types with a shared common/wallarm-lib.sh (logging, detection, validation, connectivity, env parsing) - Move Docker scripts + artifacts (binaries/, images/) under docker/ (git mv, history preserved) - Refactor Docker scripts to source the shared library; update artifact URLs - Add native/ scripts for no-Docker deployment using the Wallarm all-in-one installer (check, deploy, reconfigure, uninstall) with version pinning via WALLARM_VERSION - Add native/wallarm-native.sh unified node manager (Wallarm Native Node, connector mode) with preflight checks, parallel multi-node install, config/remove/status - Update setup.sh to download scripts per deployment type (DEPLOYMENT_TYPE=...) - Update README.md and changelog.md
530 lines
18 KiB
Bash
Executable file
530 lines
18 KiB
Bash
Executable file
#!/bin/bash
|
|
# ==============================================================================
|
|
# WALLARM COMMON LIBRARY - shared functions for docker/ and native/ deployment
|
|
# ==============================================================================
|
|
# Purpose: Single source of truth for functionality shared by both deployment
|
|
# types (Docker container vs native NGINX install).
|
|
# Usage: Scripts source this file AFTER setting `set -euo pipefail` and before
|
|
# defining their own functions:
|
|
# source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../common/wallarm-lib.sh"
|
|
# The library does NOT set the error trap itself; each script owns its
|
|
# error handling configuration.
|
|
# ==============================================================================
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# COLOR DEFINITIONS (for better UX)
|
|
# ------------------------------------------------------------------------------
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[1;34m'
|
|
CYAN='\033[0;36m'
|
|
MAGENTA='\033[0;35m'
|
|
BOLD='\033[1m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# SSL SECURITY SETTINGS
|
|
# WALLARM_INSECURE_SSL=1 disables SSL certificate validation (for self-signed
|
|
# certs). Kept as a default of 1 for backward compatibility with existing usage.
|
|
# ------------------------------------------------------------------------------
|
|
INSECURE_SSL="${WALLARM_INSECURE_SSL:-1}"
|
|
if [ "$INSECURE_SSL" = "1" ]; then
|
|
CURL_INSECURE_FLAG="-k"
|
|
else
|
|
CURL_INSECURE_FLAG=""
|
|
fi
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# EARLY ERROR HANDLER
|
|
# Use with: trap early_error_handler ERR
|
|
# Handles failures before log_message is available (or when logging is not set).
|
|
# ------------------------------------------------------------------------------
|
|
early_error_handler() {
|
|
echo -e "${RED}${BOLD}[ERROR]${NC} Script failed at line $LINENO. Command: $BASH_COMMAND" >&2
|
|
exit 1
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# LOGGING
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Log a message to stderr (colored) and to $LOG_FILE (if set).
|
|
log_message() {
|
|
local level="$1"
|
|
local message="$2"
|
|
local timestamp
|
|
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
|
|
|
local color="$NC"
|
|
case "$level" in
|
|
"INFO") color="${BLUE}" ;;
|
|
"SUCCESS") color="${GREEN}" ;;
|
|
"WARNING") color="${YELLOW}" ;;
|
|
"ERROR") color="${RED}" ;;
|
|
"DEBUG") color="${CYAN}" ;;
|
|
esac
|
|
|
|
echo -e "${color}[${timestamp}] ${level}: ${message}${NC}" >&2
|
|
if [ -n "${LOG_FILE:-}" ]; then
|
|
echo "[${timestamp}] ${level}: ${message}" >> "$LOG_FILE"
|
|
fi
|
|
}
|
|
|
|
# Log an ERROR, print a remediation banner, and exit non-zero.
|
|
fail_with_remediation() {
|
|
local error_msg="$1"
|
|
local remediation="$2"
|
|
|
|
log_message "ERROR" "$error_msg"
|
|
echo -e "\n${RED}${BOLD}╔══════════════════════════════════════════════════════════════╗${NC}"
|
|
echo -e "${RED}${BOLD}║ DEPLOYMENT FAILED ║${NC}"
|
|
echo -e "${RED}${BOLD}╚══════════════════════════════════════════════════════════════╝${NC}"
|
|
echo -e "\n${YELLOW}${BOLD}Root Cause:${NC} $error_msg"
|
|
echo -e "\n${YELLOW}${BOLD}How to Fix:${NC}"
|
|
echo -e "$remediation"
|
|
echo -e "\n${YELLOW}Check the full log for details:${NC} ${LOG_FILE:-stdout}"
|
|
exit 1
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# MISC HELPERS
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Extract hostname from a URL, stripping protocol and credentials for safe logging.
|
|
extract_hostname_from_url() {
|
|
local url="$1"
|
|
local hostpart="${url#*://}"
|
|
hostpart="${hostpart#*@}"
|
|
hostpart="${hostpart%%[:/]*}"
|
|
echo "$hostpart"
|
|
}
|
|
|
|
# Check whether a command exists (respects PATH + common system directories).
|
|
command_exists() {
|
|
local cmd="$1"
|
|
if command -v "$cmd" >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
local system_dirs=("/usr/sbin" "/sbin" "/usr/local/sbin" "/usr/bin" "/bin" "/usr/local/bin")
|
|
for dir in "${system_dirs[@]}"; do
|
|
if [ -x "$dir/$cmd" ]; then
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# Validate an IPv4 address (basic format + octet range check).
|
|
validate_ip_address() {
|
|
local ip="$1"
|
|
|
|
if [[ ! "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
|
|
return 1
|
|
fi
|
|
|
|
IFS='.' read -r i1 i2 i3 i4 <<< "$ip"
|
|
if [ "$i1" -gt 255 ] || [ "$i2" -gt 255 ] || [ "$i3" -gt 255 ] || [ "$i4" -gt 255 ]; then
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# Validate an IP or CIDR entry (IPv4 with optional /prefix). Returns 0 if valid.
|
|
validate_ip_or_cidr() {
|
|
local entry="$1"
|
|
|
|
if [[ ! "$entry" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}(/[0-9]{1,2})?$ ]]; then
|
|
return 1
|
|
fi
|
|
|
|
IFS='/' read -r ip cidr <<< "$entry"
|
|
IFS='.' read -r o1 o2 o3 o4 <<< "$ip"
|
|
if [ "$o1" -gt 255 ] || [ "$o2" -gt 255 ] || [ "$o3" -gt 255 ] || [ "$o4" -gt 255 ]; then
|
|
return 1
|
|
fi
|
|
if [ -n "$cidr" ] && { [ "$cidr" -lt 0 ] || [ "$cidr" -gt 32 ]; }; then
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# Check if a TCP/UDP port is currently in use. Returns 0 when available.
|
|
check_port_available() {
|
|
local port="$1"
|
|
local protocol="${2:-tcp}"
|
|
|
|
log_message "DEBUG" "Checking port $port/$protocol availability..."
|
|
|
|
if command -v ss >/dev/null 2>&1; then
|
|
if ss -"${protocol:0:1}"ln | grep -q ":$port "; then
|
|
return 1 # Port in use
|
|
fi
|
|
elif command -v netstat >/dev/null 2>&1; then
|
|
if netstat -tulpn 2>/dev/null | grep -E ":$port\s" >/dev/null 2>&1; then
|
|
return 1 # Port in use
|
|
fi
|
|
else
|
|
log_message "WARNING" "Neither ss nor netstat available, cannot check port $port"
|
|
fi
|
|
|
|
return 0 # Port available (or cannot check)
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# SYSTEM DETECTION
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Detect OS name and version. Prints "name:version".
|
|
detect_os_and_version() {
|
|
log_message "INFO" "Detecting OS and version..."
|
|
|
|
local os_name=""
|
|
local os_version=""
|
|
|
|
if [ -f /etc/os-release ]; then
|
|
. /etc/os-release
|
|
os_name="$ID"
|
|
os_version="$VERSION_ID"
|
|
elif [ -f /etc/redhat-release ]; then
|
|
os_name="rhel"
|
|
os_version=$(sed -e 's/.*release \([0-9]\+\)\..*/\1/' /etc/redhat-release)
|
|
elif [ -f /etc/alpine-release ]; then
|
|
os_name="alpine"
|
|
os_version=$(cat /etc/alpine-release)
|
|
else
|
|
os_name=$(uname -s | tr '[:upper:]' '[:lower:]')
|
|
os_version=$(uname -r)
|
|
fi
|
|
|
|
os_name="${os_name//[$'\t\r\n']/}"
|
|
os_version="${os_version//[$'\t\r\n']/}"
|
|
|
|
case "$os_name" in
|
|
"ubuntu"|"debian"|"centos"|"rhel"|"alpine"|"amzn"|"ol"|"rocky"|"almalinux")
|
|
log_message "SUCCESS" "OS detected: $os_name $os_version (supported)"
|
|
;;
|
|
*)
|
|
log_message "WARNING" "OS '$os_name' not explicitly tested but may work"
|
|
;;
|
|
esac
|
|
|
|
echo "$os_name:$os_version"
|
|
}
|
|
|
|
# Detect architecture. Prints a normalized value (x86_64/aarch64/armhf/unknown).
|
|
detect_architecture() {
|
|
log_message "INFO" "Detecting system architecture..."
|
|
|
|
local arch
|
|
arch=$(uname -m)
|
|
local docker_arch=""
|
|
|
|
case "$arch" in
|
|
x86_64|x64|amd64)
|
|
docker_arch="x86_64"
|
|
log_message "SUCCESS" "Architecture: x86_64 (Intel/AMD 64-bit)"
|
|
;;
|
|
aarch64|arm64)
|
|
docker_arch="aarch64"
|
|
log_message "SUCCESS" "Architecture: aarch64 (ARM 64-bit)"
|
|
;;
|
|
armv7l|armhf)
|
|
docker_arch="armhf"
|
|
log_message "SUCCESS" "Architecture: armhf (ARM 32-bit)"
|
|
;;
|
|
*)
|
|
log_message "ERROR" "Unsupported architecture: $arch"
|
|
docker_arch="unknown"
|
|
;;
|
|
esac
|
|
|
|
echo "$docker_arch"
|
|
}
|
|
|
|
# Detect the init system. Prints one of systemd/openrc/sysvinit/upstart/unknown.
|
|
detect_init_system() {
|
|
log_message "INFO" "Detecting init system..."
|
|
|
|
local init_system="unknown"
|
|
|
|
if [ "$(uname -s)" = "Darwin" ]; then
|
|
init_system="darwin"
|
|
log_message "SUCCESS" "Init system: darwin (macOS)"
|
|
elif command -v systemctl >/dev/null 2>&1 && systemctl --version >/dev/null 2>&1; then
|
|
init_system="systemd"
|
|
log_message "SUCCESS" "Init system: systemd"
|
|
elif { [ -d /etc/init.d ] && [ -x /sbin/initctl ]; } || [ -x /sbin/init ]; then
|
|
init_system="sysvinit"
|
|
log_message "SUCCESS" "Init system: sysvinit"
|
|
elif [ -d /etc/rc.d ] && [ -x /sbin/rc-service ]; then
|
|
init_system="openrc"
|
|
log_message "SUCCESS" "Init system: openrc (Alpine)"
|
|
elif [ -x /sbin/upstart ]; then
|
|
init_system="upstart"
|
|
log_message "SUCCESS" "Init system: upstart"
|
|
else
|
|
log_message "WARNING" "Could not determine init system"
|
|
fi
|
|
|
|
echo "$init_system"
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# NETWORK CONNECTIVITY
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Test connectivity to a host/URL. Returns 0 when reachable.
|
|
test_connectivity() {
|
|
local host="$1"
|
|
local description="$2"
|
|
local timeout="${3:-10}"
|
|
|
|
local display_host
|
|
display_host=$(extract_hostname_from_url "$host")
|
|
log_message "INFO" "Testing connectivity to $description ($display_host)..."
|
|
|
|
local url="$host"
|
|
if [[ ! "$host" =~ ^https?:// ]]; then
|
|
url="https://$host"
|
|
fi
|
|
|
|
if curl -sL $CURL_INSECURE_FLAG --connect-timeout "$timeout" "$url" >/dev/null 2>&1; then
|
|
log_message "SUCCESS" "$description is reachable"
|
|
return 0
|
|
else
|
|
log_message "ERROR" "$description is NOT reachable"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Test a set of cloud endpoints. Prints "true" if all reachable, else "false".
|
|
test_cloud_endpoints() {
|
|
local cloud_name="$1"
|
|
shift
|
|
local endpoints=("$@")
|
|
|
|
log_message "INFO" "Testing $cloud_name cloud endpoints..."
|
|
|
|
local all_reachable=true
|
|
local endpoint
|
|
for endpoint in "${endpoints[@]}"; do
|
|
if ! test_connectivity "$endpoint" "$cloud_name cloud endpoint $endpoint"; then
|
|
all_reachable=false
|
|
fi
|
|
done
|
|
|
|
if [ "$all_reachable" = "true" ]; then
|
|
log_message "SUCCESS" "All $cloud_name cloud endpoints reachable"
|
|
echo "true"
|
|
else
|
|
log_message "WARNING" "Some $cloud_name cloud endpoints unreachable"
|
|
echo "false"
|
|
fi
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# ENVIRONMENT FILE HANDLING
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Load a preflight .env file into global variables. Returns 1 if file missing.
|
|
load_env_file() {
|
|
local env_file="${1:-$ENV_FILE}"
|
|
|
|
if [ ! -f "$env_file" ]; then
|
|
log_message "ERROR" "Environment file not found: $env_file"
|
|
return 1
|
|
fi
|
|
|
|
local key value
|
|
while IFS='=' read -r key value; do
|
|
[[ "$key" =~ ^#.*$ ]] && continue
|
|
[[ -z "$key" ]] && continue
|
|
|
|
value="${value%\"}"
|
|
value="${value#\"}"
|
|
|
|
case "$key" in
|
|
result) CHECK_RESULT="$value" ;;
|
|
os_name) OS_NAME="$value" ;;
|
|
os_version) OS_VERSION="$value" ;;
|
|
architecture) ARCHITECTURE="$value" ;;
|
|
init_system) INIT_SYSTEM="$value" ;;
|
|
us_cloud_reachable) US_CLOUD_REACHABLE="$value" ;;
|
|
eu_cloud_reachable) EU_CLOUD_REACHABLE="$value" ;;
|
|
registry_reachable) REGISTRY_REACHABLE="$value" ;;
|
|
download_reachable) DOWNLOAD_REACHABLE="$value" ;;
|
|
git_reachable) GIT_REACHABLE="$value" ;;
|
|
installer_reachable) INSTALLER_REACHABLE="$value" ;;
|
|
esac
|
|
done < "$env_file"
|
|
|
|
log_message "SUCCESS" "Loaded preflight results from $env_file"
|
|
return 0
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# CLOUD REGION SELECTION
|
|
# Sets CLOUD_REGION and API_HOST based on reachability from the preflight check.
|
|
# ------------------------------------------------------------------------------
|
|
select_cloud_region() {
|
|
log_message "INFO" "Selecting Wallarm Cloud region..."
|
|
|
|
echo -e "\n${CYAN}${BOLD}Wallarm Cloud Region Selection:${NC}"
|
|
|
|
local available_options=()
|
|
|
|
if [ "${US_CLOUD_REACHABLE:-false}" = "true" ]; then
|
|
echo -e "1. ${YELLOW}US Cloud${NC} (us1.api.wallarm.com) - For US-based deployments"
|
|
available_options+=("1" "US")
|
|
fi
|
|
|
|
if [ "${EU_CLOUD_REACHABLE:-false}" = "true" ]; then
|
|
echo -e "2. ${YELLOW}EU Cloud${NC} (api.wallarm.com) - For EU-based deployments"
|
|
available_options+=("2" "EU")
|
|
fi
|
|
|
|
if [ ${#available_options[@]} -eq 0 ]; then
|
|
fail_with_remediation "No cloud regions available" \
|
|
"Preflight check showed no reachable cloud regions.
|
|
1. Check network connectivity to Wallarm endpoints
|
|
2. Run the preflight check again
|
|
3. Contact network administrator if behind firewall"
|
|
fi
|
|
|
|
local pattern
|
|
pattern="^($(IFS='|'; echo "${available_options[*]}"))$"
|
|
|
|
local cloud_choice=""
|
|
while [[ ! "$cloud_choice" =~ $pattern ]]; do
|
|
if [ ${#available_options[@]} -eq 2 ]; then
|
|
if [ "${US_CLOUD_REACHABLE:-false}" = "true" ]; then
|
|
cloud_choice="US"
|
|
break
|
|
else
|
|
cloud_choice="EU"
|
|
break
|
|
fi
|
|
fi
|
|
|
|
read -r -p "$(echo -e "${YELLOW}Enter choice [1/US or 2/EU]: ${NC}")" cloud_choice
|
|
cloud_choice=$(echo "$cloud_choice" | tr '[:lower:]' '[:upper:]')
|
|
|
|
case "$cloud_choice" in
|
|
1|"US")
|
|
if [ "${US_CLOUD_REACHABLE:-false}" = "true" ]; then
|
|
CLOUD_REGION="US"
|
|
API_HOST="us1.api.wallarm.com"
|
|
log_message "INFO" "Selected US Cloud"
|
|
else
|
|
echo -e "${RED}US Cloud is not reachable (per preflight check)${NC}"
|
|
cloud_choice=""
|
|
fi
|
|
;;
|
|
2|"EU")
|
|
if [ "${EU_CLOUD_REACHABLE:-false}" = "true" ]; then
|
|
CLOUD_REGION="EU"
|
|
API_HOST="api.wallarm.com"
|
|
log_message "INFO" "Selected EU Cloud"
|
|
else
|
|
echo -e "${RED}EU Cloud is not reachable (per preflight check)${NC}"
|
|
cloud_choice=""
|
|
fi
|
|
;;
|
|
*)
|
|
if [ -n "$cloud_choice" ]; then
|
|
echo -e "${RED}Invalid choice. Select from available options above.${NC}"
|
|
fi
|
|
;;
|
|
esac
|
|
done
|
|
|
|
log_message "SUCCESS" "Cloud region selected: $CLOUD_REGION ($API_HOST)"
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# ARTIFACT DOWNLOAD (Git Repositorys primary source)
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Download a file from Git Repositorys. Returns 0 on success.
|
|
download_from_git() {
|
|
local url="$1"
|
|
local output_path="$2"
|
|
local description="$3"
|
|
|
|
log_message "INFO" "Attempting to download $description from Git Repositorys..."
|
|
log_message "DEBUG" "URL: $url"
|
|
log_message "DEBUG" "Output path: $output_path"
|
|
|
|
if curl -fL "$CURL_INSECURE_FLAG" --connect-timeout 30 --max-time 300 --progress-bar "$url" -o "$output_path"; then
|
|
log_message "SUCCESS" "Downloaded $description to $output_path"
|
|
return 0
|
|
else
|
|
local curl_exit=$?
|
|
log_message "ERROR" "Failed to download $description from Git Repositorys (curl exit: $curl_exit)"
|
|
if [ -f "$output_path" ]; then
|
|
rm -f "$output_path"
|
|
log_message "DEBUG" "Removed partial download: $output_path"
|
|
fi
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Verify a file against a checksum file or URL. Returns 0 on success;
|
|
# skips verification (returns 0) when the checksum cannot be obtained.
|
|
verify_checksum() {
|
|
local file_path="$1"
|
|
local checksum_file_or_url="$2"
|
|
local description="$3"
|
|
|
|
log_message "INFO" "Verifying $description checksum..."
|
|
|
|
local checksum_file=""
|
|
if [[ "$checksum_file_or_url" =~ ^https?:// ]]; then
|
|
checksum_file="/tmp/$(basename "$checksum_file_or_url")"
|
|
log_message "DEBUG" "Downloading checksum from URL: $checksum_file_or_url"
|
|
if ! curl -fL "$CURL_INSECURE_FLAG" --connect-timeout 10 --max-time 30 -s "$checksum_file_or_url" -o "$checksum_file"; then
|
|
log_message "WARNING" "Could not download checksum file, skipping verification"
|
|
return 0
|
|
fi
|
|
else
|
|
checksum_file="$checksum_file_or_url"
|
|
fi
|
|
|
|
if [ ! -f "$checksum_file" ]; then
|
|
log_message "WARNING" "Checksum file not found: $checksum_file, skipping verification"
|
|
return 0
|
|
fi
|
|
|
|
local expected_checksum
|
|
expected_checksum=$(awk '{print $1}' "$checksum_file" 2>/dev/null)
|
|
if [ -z "$expected_checksum" ]; then
|
|
log_message "WARNING" "Could not read checksum from $checksum_file, skipping verification"
|
|
return 0
|
|
fi
|
|
|
|
log_message "DEBUG" "Computing SHA256 checksum of $file_path..."
|
|
local actual_checksum
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
actual_checksum=$(sha256sum "$file_path" | awk '{print $1}')
|
|
elif command -v shasum >/dev/null 2>&1; then
|
|
actual_checksum=$(shasum -a 256 "$file_path" | awk '{print $1}')
|
|
else
|
|
log_message "WARNING" "sha256sum or shasum not available, skipping checksum verification"
|
|
return 0
|
|
fi
|
|
|
|
if [ "$expected_checksum" = "$actual_checksum" ]; then
|
|
log_message "SUCCESS" "$description checksum verified successfully"
|
|
return 0
|
|
else
|
|
log_message "ERROR" "$description checksum verification FAILED"
|
|
log_message "DEBUG" "Expected: $expected_checksum"
|
|
log_message "DEBUG" "Actual: $actual_checksum"
|
|
rm -f "$file_path"
|
|
log_message "INFO" "Removed corrupted file: $file_path"
|
|
return 1
|
|
fi
|
|
}
|