wallarm/common/wallarm-lib.sh
Sechpoint Admin aa3d716f61 feat: add native deployment and separate docker|native structure
- 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
2026-08-01 08:36:37 +01:00

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
}