#!/bin/bash # ============================================================================== # WALLARM DEPLOYMENT SCRIPT - V1.2 # ============================================================================== # Purpose: Deploy Wallarm filtering node after preflight check # Features: # - Reads preflight check results from .env file # - Interactive configuration (cloud region, ports, token, upstream) # - Docker installation with LXC optimization (VFS storage driver) # - Wallarm node deployment with persistence # - Deployment verification with handshake test # - DAU-friendly error handling with remediation # ============================================================================== # 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 # Strict error handling set -euo pipefail # Simple error handler for early failures (before log_message is defined) early_error_handler() { echo -e "${RED}${BOLD}[ERROR]${NC} Script failed at line $LINENO. Command: $BASH_COMMAND" >&2 exit 1 } trap early_error_handler ERR # Extract hostname from URL (strip protocol and credentials for safe logging) extract_hostname_from_url() { local url="$1" # Remove protocol local hostpart="${url#*://}" # Remove credentials if present (username:password@) hostpart="${hostpart#*@}" # Remove port and path hostpart="${hostpart%%[:/]*}" echo "$hostpart" } # Configuration ENV_FILE=".env" LOG_FILE="${HOME:-.}/logs/wallarm-deployment.log" # SSL security settings # WALLARM_INSECURE_SSL=1 to disable SSL certificate validation (insecure, for self-signed certs) INSECURE_SSL="${WALLARM_INSECURE_SSL:-1}" # Default to insecure for backward compatibility if [ "$INSECURE_SSL" = "1" ]; then CURL_INSECURE_FLAG="-k" # Warning will be logged later when log_message is available else CURL_INSECURE_FLAG="" fi # Git Repositorys artifact URLs (primary source) GIT_BASE_URL="https://git.sechpoint.app/customer-engineering/wallarm" GIT_RAW_URL="https://git.sechpoint.app/customer-engineering/wallarm/src/branch/main" GIT_DOCKER_BINARY_URL="${GIT_RAW_URL}/binaries/docker-29.2.1.tgz" GIT_DOCKER_CHECKSUM_URL="${GIT_RAW_URL}/binaries/docker-29.2.1.tgz.sha256" GIT_WALLARM_IMAGE_URL="${GIT_RAW_URL}/images/wallarm-node-6.11.0-rc1.tar.gz" GIT_WALLARM_CHECKSUM_URL="${GIT_RAW_URL}/images/wallarm-node-6.11.0-rc1.tar.gz.sha256" # Local artifact directories (relative to script location) SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" LOCAL_BINARY_DIR="${SCRIPT_DIR}/binaries" LOCAL_IMAGE_DIR="${SCRIPT_DIR}/images" DOCKER_VERSION="29.2.1" # Version from stealth deployment guide WALLARM_IMAGE_TARGET="wallarm/node:6.11.0-rc1" # Deployment variables (set during execution) CLOUD_REGION="" API_HOST="" INGRESS_PORT="" MONITORING_PORT="" UPSTREAM_IP="" UPSTREAM_PORT="" WALLARM_TOKEN="" INSTANCE_NAME="" INSTANCE_DIR="" # Resource reachability from check script US_CLOUD_REACHABLE="false" EU_CLOUD_REACHABLE="false" REGISTRY_REACHABLE="false" DOWNLOAD_REACHABLE="false" # ============================================================================== # LOGGING & ERROR HANDLING FUNCTIONS # ============================================================================== log_message() { local level="$1" local message="$2" local timestamp timestamp=$(date '+%Y-%m-%d %H:%M:%S') case "$level" in "INFO") color="${BLUE}" ;; "SUCCESS") color="${GREEN}" ;; "WARNING") color="${YELLOW}" ;; "ERROR") color="${RED}" ;; "DEBUG") color="${CYAN}" ;; *) color="${NC}" ;; esac echo -e "${color}[${timestamp}] ${level}: ${message}${NC}" >&2 echo "[${timestamp}] ${level}: ${message}" >> "$LOG_FILE" } 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" exit 1 } # ============================================================================== # GIT ARTIFACT FUNCTIONS # ============================================================================== 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" # Use curl with follow redirects, fail on HTTP error, timeout settings 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)" # Clean up partial download if it exists if [ -f "$output_path" ]; then rm -f "$output_path" log_message "DEBUG" "Removed partial download: $output_path" fi return 1 fi } 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 is a URL, download it first 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 # Skip verification if checksum can't be downloaded fi else checksum_file="$checksum_file_or_url" fi # Verify checksum file exists if [ ! -f "$checksum_file" ]; then log_message "WARNING" "Checksum file not found: $checksum_file, skipping verification" return 0 fi # Get expected checksum (first field from checksum file) 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 # Compute actual checksum 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 # Compare checksums 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" # Clean up corrupted file rm -f "$file_path" log_message "INFO" "Removed corrupted file: $file_path" return 1 fi } # ============================================================================== # PREFLIGHT CHECK VERIFICATION # ============================================================================== verify_preflight_check() { log_message "INFO" "Verifying preflight check results..." if [ ! -f "$ENV_FILE" ]; then log_message "ERROR" "Preflight check file not found: $ENV_FILE" echo -e "\n${YELLOW}Preflight check has not been run or .env file is missing.${NC}" echo -e "${YELLOW}Would you like to run the preflight check now?${NC}" read -r -p "$(echo -e "${YELLOW}Run preflight check? (Y/n): ${NC}")" -n 1 echo if [[ ! $REPLY =~ ^[Nn]$ ]]; then echo -e "${CYAN}Running preflight check...${NC}" if ! ./wallarm-ct-check.sh; then fail_with_remediation "Preflight check failed" \ "Run the preflight check manually and fix any issues: 1. ./wallarm-ct-check.sh 2. Review the errors in $ENV_FILE 3. Fix the issues and run this script again" fi else fail_with_remediation "Preflight check required" \ "Run the preflight check before deployment: 1. ./wallarm-ct-check.sh 2. Review results in $ENV_FILE 3. Run this script again" fi fi # Load environment variables from .env file # Use a safer approach than sourcing (avoid code injection) while IFS='=' read -r key value; do # Remove comments and empty lines [[ "$key" =~ ^#.*$ ]] && continue [[ -z "$key" ]] && continue # Remove quotes from value value="${value%\"}" value="${value#\"}" # Export variable 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" ;; esac done < "$ENV_FILE" if [ "$CHECK_RESULT" != "pass" ]; then log_message "ERROR" "Preflight check failed (result: $CHECK_RESULT)" echo -e "\n${YELLOW}Preflight check found issues. Please review:${NC}" echo -e "${YELLOW}1. Check file: $ENV_FILE${NC}" echo -e "${YELLOW}2. Run: ./wallarm-ct-check.sh${NC}" echo -e "${YELLOW}3. Fix the issues and try again${NC}" exit 1 fi log_message "SUCCESS" "Preflight check verified:" log_message "SUCCESS" " OS: $OS_NAME $OS_VERSION" log_message "SUCCESS" " Architecture: $ARCHITECTURE" log_message "SUCCESS" " Init System: $INIT_SYSTEM" log_message "SUCCESS" " US Cloud Reachable: $US_CLOUD_REACHABLE" log_message "SUCCESS" " EU Cloud Reachable: $EU_CLOUD_REACHABLE" log_message "SUCCESS" " Registry Reachable: $REGISTRY_REACHABLE" log_message "SUCCESS" " Download Reachable: $DOWNLOAD_REACHABLE" # Check for local artifact directories if [ -d "$LOCAL_BINARY_DIR" ]; then log_message "INFO" " Local binaries directory: $LOCAL_BINARY_DIR (exists)" local binary_count=$(ls "$LOCAL_BINARY_DIR"/*.tgz 2>/dev/null | wc -l) if [ "$binary_count" -gt 0 ]; then log_message "INFO" " Found $binary_count Docker binary file(s)" fi else log_message "INFO" " Local binaries directory: $LOCAL_BINARY_DIR (not found)" fi if [ -d "$LOCAL_IMAGE_DIR" ]; then log_message "INFO" " Local images directory: $LOCAL_IMAGE_DIR (exists)" local image_count=$(ls "$LOCAL_IMAGE_DIR"/*.tar.gz 2>/dev/null | wc -l) if [ "$image_count" -gt 0 ]; then log_message "INFO" " Found $image_count Wallarm image file(s)" fi else log_message "INFO" " Local images directory: $LOCAL_IMAGE_DIR (not found)" fi # Validate we have at least one cloud region reachable if [ "$US_CLOUD_REACHABLE" = "false" ] && [ "$EU_CLOUD_REACHABLE" = "false" ]; then fail_with_remediation "No Wallarm cloud region reachable" \ "Network connectivity issues detected: 1. Check firewall rules for Wallarm cloud endpoints 2. Verify network connectivity 3. Run preflight check again: ./wallarm-ct-check.sh" fi # Validate we have resources for Docker/Wallarm if [ "$REGISTRY_REACHABLE" = "false" ] && [ "$DOWNLOAD_REACHABLE" = "false" ]; then log_message "WARNING" "Neither registry nor download server reachable" log_message "INFO" "Checking for local resources..." local has_local_resources=true if [ -z "$(ls docker-*.tgz 2>/dev/null)" ]; then log_message "ERROR" "No local Docker binary found" has_local_resources=false fi if [ -z "$(ls wallarm-node-*.tar 2>/dev/null)" ]; then log_message "ERROR" "No local Wallarm image found" has_local_resources=false fi if [ "$has_local_resources" = "false" ]; then fail_with_remediation "Insufficient resources for deployment" \ "Please provide either: 1. Network access to $DOCKER_REGISTRY_HOST 2. Network access to $DOCKER_DOWNLOAD_HOST 3. Local files: docker-*.tgz and wallarm-node-*.tar in current directory" fi fi } # ============================================================================== # CONFIGURATION COLLECTION FUNCTIONS # ============================================================================== select_cloud_region() { log_message "INFO" "Selecting Wallarm Cloud region..." echo -e "\n${CYAN}${BOLD}Wallarm Cloud Region Selection:${NC}" # Show available regions based on preflight check local available_options=() if [ "$US_CLOUD_REACHABLE" = "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" = "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 preflight check again: ./wallarm-ct-check.sh 3. Contact network administrator if behind firewall" fi # Build regex pattern for validation local pattern pattern="^($(IFS='|'; echo "${available_options[*]}"))$" local cloud_choice="" while [[ ! "$cloud_choice" =~ $pattern ]]; do if [ ${#available_options[@]} -eq 2 ]; then # Only one region available if [ "$US_CLOUD_REACHABLE" = "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" = "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" = "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)" } # Critical fix from review: Proper IP validation validate_ip_address() { local ip="$1" # Check basic format if [[ ! "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then return 1 fi # Check each octet is 0-255 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 } # Critical fix from review: Port conflict detection with fallback check_port_available() { local port="$1" local protocol="${2:-tcp}" log_message "DEBUG" "Checking port $port/$protocol availability..." # Try ss first (modern, usually available) 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 # Fallback to netstat 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) } collect_configuration() { log_message "INFO" "Collecting deployment configuration..." # Get ingress port local default_port=80 local ingress_port="" while [[ ! "$ingress_port" =~ ^[0-9]+$ ]] || [ "$ingress_port" -lt 1 ] || [ "$ingress_port" -gt 65535 ]; do read -r -p "$(echo -e "${YELLOW}Enter inbound port [${default_port}]: ${NC}")" ingress_port ingress_port="${ingress_port:-$default_port}" if [[ ! "$ingress_port" =~ ^[0-9]+$ ]]; then echo -e "${RED}Port must be a number${NC}" elif [ "$ingress_port" -lt 1 ] || [ "$ingress_port" -gt 65535 ]; then echo -e "${RED}Port must be between 1 and 65535${NC}" elif ! check_port_available "$ingress_port"; then echo -e "${RED}Port $ingress_port is already in use${NC}" ingress_port="" fi done # Calculate monitoring port (ingress + 10, check for conflicts) local monitoring_port=$((ingress_port + 10)) if ! check_port_available "$monitoring_port"; then log_message "WARNING" "Port $monitoring_port is in use, choosing alternative..." monitoring_port=$((ingress_port + 100)) if ! check_port_available "$monitoring_port"; then monitoring_port=$((ingress_port + 200)) fi fi log_message "INFO" "Monitoring port will be: $monitoring_port" # Get application server details local upstream_ip="" local upstream_port="" echo -e "\n${CYAN}${BOLD}Application Server Configuration:${NC}" echo -e "${YELLOW}Enter the IP/hostname and port of your backend application${NC}" while [[ -z "$upstream_ip" ]]; do read -r -p "$(echo -e "${YELLOW}Upstream App IP/Hostname [127.0.0.1]: ${NC}")" upstream_ip upstream_ip="${upstream_ip:-127.0.0.1}" # Validate IP/hostname format if ! validate_ip_address "$upstream_ip" && \ ! [[ "$upstream_ip" =~ ^[a-zA-Z0-9][a-zA-Z0-9.-]*[a-zA-Z0-9]$ ]]; then echo -e "${RED}Invalid IP/hostname format${NC}" upstream_ip="" fi done while [[ ! "$upstream_port" =~ ^[0-9]+$ ]] || [ "$upstream_port" -lt 1 ] || [ "$upstream_port" -gt 65535 ]; do read -r -p "$(echo -e "${YELLOW}Upstream App Port [8080]: ${NC}")" upstream_port upstream_port="${upstream_port:-8080}" if [[ ! "$upstream_port" =~ ^[0-9]+$ ]]; then echo -e "${RED}Port must be a number${NC}" elif [ "$upstream_port" -lt 1 ] || [ "$upstream_port" -gt 65535 ]; then echo -e "${RED}Port must be between 1 and 65535${NC}" fi done # Verify application server reachability log_message "INFO" "Verifying application server reachability..." if timeout 5 bash -c "cat < /dev/null > /dev/tcp/$upstream_ip/$upstream_port" 2>/dev/null; then log_message "SUCCESS" "Application server $upstream_ip:$upstream_port is reachable" else log_message "WARNING" "Application server $upstream_ip:$upstream_port is not reachable" echo -e "${YELLOW}${BOLD}Warning:${NC} Cannot reach application server at $upstream_ip:$upstream_port" echo -e "${YELLOW}This may cause the Wallarm node to fail. Possible reasons:${NC}" echo -e "1. Application server is not running" echo -e "2. Firewall blocking port $upstream_port" echo -e "3. Wrong IP/hostname" echo -e "4. Application server not listening on that port" read -r -p "$(echo -e "${YELLOW}Continue anyway? (y/N): ${NC}")" -n 1 echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then fail_with_remediation "Application server unreachable" \ "Ensure your application server is accessible: 1. Start your application server 2. Check it's listening: sudo ss -tlnp | grep :$upstream_port 3. Verify firewall rules allow inbound connections 4. Test connectivity: telnet $upstream_ip $upstream_port 5. If using hostname, verify DNS resolution: nslookup $upstream_ip" fi fi # Get Wallarm node token local wallarm_token="" echo -e "\n${CYAN}${BOLD}Wallarm Node Token:${NC}" echo -e "${YELLOW}Get your token from Wallarm Console:${NC}" echo -e "Create a new 'Wallarm node' and copy the token (will be visible as you type)" while [[ -z "$wallarm_token" ]]; do read -r -p "$(echo -e "${YELLOW}Paste Wallarm Node Token: ${NC}")" wallarm_token # Trim whitespace and newlines wallarm_token=$(echo "$wallarm_token" | tr -d '[:space:]') if [[ -z "$wallarm_token" ]]; then echo -e "${RED}Token cannot be empty${NC}" elif [[ ! "$wallarm_token" =~ ^[A-Za-z0-9_+/=\-]+$ ]]; then echo -e "${RED}Token contains invalid characters. Wallarm tokens are base64 strings (A-Z, a-z, 0-9, _, -, +, /, =)${NC}" echo -e "${YELLOW}First 20 chars of what you entered: '${wallarm_token:0:20}...'${NC}" wallarm_token="" else # Show confirmation of token length (but not full token for security) token_length=${#wallarm_token} echo -e "${GREEN}Token accepted (${token_length} characters).${NC}" echo -e "${YELLOW}First 8 chars for verification: ${wallarm_token:0:8}...${NC}" fi done # Get trusted proxy IPs for real IP configuration local trusted_proxies="" echo -e "\n${CYAN}${BOLD}Real Client IP Configuration:${NC}" echo -e "${YELLOW}For Wallarm to see the real client IP, specify the IP address(es) of trusted proxies" echo -e "(e.g., load balancers, firewalls, CDNs) that forward traffic to this node.${NC}" echo -e "${YELLOW}You can enter:${NC}" echo -e " - Single IP: 10.0.0.10" echo -e " - CIDR range: 10.0.0.0/24" echo -e " - Multiple entries separated by spaces: 10.0.0.10 10.0.1.0/24 192.168.1.1" echo -e "${YELLOW}If unsure, you can leave empty and configure later${NC}" read -r -p "$(echo -e "${YELLOW}Trusted proxy IPs/CIDRs (space-separated): ${NC}")" trusted_proxies_input # Validate and clean up the input local validated_proxies=() if [[ -n "$trusted_proxies_input" ]]; then # Split input by spaces IFS=' ' read -ra proxy_array <<< "$trusted_proxies_input" for proxy in "${proxy_array[@]}"; do # Trim whitespace proxy=$(echo "$proxy" | xargs) if [[ -n "$proxy" ]]; then # Validate IP or CIDR if [[ "$proxy" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}(/[0-9]{1,2})?$ ]]; then # Basic IP format validation IFS='/' read -r ip cidr <<< "$proxy" IFS='.' read -r o1 o2 o3 o4 <<< "$ip" if [[ $o1 -le 255 && $o2 -le 255 && $o3 -le 255 && $o4 -le 255 ]]; then if [[ -z "$cidr" ]] || [[ $cidr -ge 0 && $cidr -le 32 ]]; then validated_proxies+=("$proxy") else echo -e "${RED}Invalid CIDR prefix length for $proxy (must be 0-32)${NC}" fi else echo -e "${RED}Invalid IP octets in $proxy${NC}" fi else echo -e "${RED}Invalid IP/CIDR format: $proxy${NC}" echo -e "${YELLOW}Example valid formats: 10.0.0.10, 10.0.0.0/24, 192.168.1.1${NC}" fi fi done if [[ ${#validated_proxies[@]} -eq 0 ]]; then echo -e "${YELLOW}No valid proxy IPs provided. Will skip set_real_ip_from configuration.${NC}" echo -e "${YELLOW}You can manually edit /etc/nginx/conf.d/wallarm.conf later to add set_real_ip_from directives.${NC}" trusted_proxies="" else trusted_proxies="${validated_proxies[*]}" echo -e "${GREEN}Trusted proxies configured: $trusted_proxies${NC}" fi else echo -e "${YELLOW}No trusted proxies specified. The node will see the last hop IP only.${NC}" echo -e "${YELLOW}For real client IP detection, you'll need to manually configure set_real_ip_from in nginx config.${NC}" fi # Generate instance name and directory local instance_name instance_name="wallarm-$(hostname -s | tr '[:upper:]' '[:lower:]')-$(date +%Y%m%d)" local instance_dir="/opt/$instance_name" # Ensure directory exists sudo mkdir -p "$instance_dir" log_message "SUCCESS" "Configuration collected:" log_message "SUCCESS" " Ingress Port: $ingress_port" log_message "SUCCESS" " Monitoring Port: $monitoring_port" log_message "SUCCESS" " Upstream: $upstream_ip:$upstream_port" if [[ -n "$trusted_proxies" ]]; then log_message "SUCCESS" " Trusted Proxies: $trusted_proxies" else log_message "INFO" " Trusted Proxies: Not configured (will need manual setup)" fi log_message "SUCCESS" " Instance: $instance_name" log_message "SUCCESS" " Directory: $instance_dir" # Set global variables INGRESS_PORT="$ingress_port" MONITORING_PORT="$monitoring_port" UPSTREAM_IP="$upstream_ip" UPSTREAM_PORT="$upstream_port" WALLARM_TOKEN="$wallarm_token" INSTANCE_NAME="$instance_name" INSTANCE_DIR="$instance_dir" TRUSTED_PROXIES="$trusted_proxies" } # ============================================================================== # DOCKER ENGINE SETUP (LXC OPTIMIZED) # ============================================================================== setup_docker_engine() { log_message "INFO" "Setting up Docker Engine for LXC/stealth deployment..." # Check if Docker is already installed and running if command -v docker >/dev/null 2>&1 && sudo docker info >/dev/null 2>&1; then local docker_version docker_version=$(docker --version | cut -d' ' -f3 | tr -d ',') log_message "SUCCESS" "Docker is already installed and running (version $docker_version)" # Check if Docker is configured for LXC if sudo docker info 2>/dev/null | grep -q "Storage Driver: vfs"; then log_message "SUCCESS" "Docker is already configured with VFS storage driver (LXC compatible)" else log_message "WARNING" "Docker is not using VFS storage driver. LXC compatibility may be limited." fi return 0 fi log_message "INFO" "Docker not found or not running. Proceeding with installation..." # Determine binary source (priority: Git Repositorys -> local dir -> current dir) local binary_file="docker-$DOCKER_VERSION.tgz" local binary_path="" # 1. Try Git Repositorys download (primary source) log_message "INFO" "Attempting to download Docker binary from Git Repositorys..." if download_from_git "$GIT_DOCKER_BINARY_URL" "$binary_file" "Docker binary"; then if verify_checksum "$binary_file" "$GIT_DOCKER_CHECKSUM_URL" "Docker binary"; then binary_path="$binary_file" log_message "SUCCESS" "Docker binary downloaded from Git Repositorys and checksum verified" else log_message "WARNING" "Git Repositorys Docker binary checksum verification failed, trying other sources" # Remove corrupted download rm -f "$binary_file" fi fi # 2. Check local binaries directory if [ -z "$binary_path" ] && [ -d "$LOCAL_BINARY_DIR" ]; then log_message "INFO" "Checking local binaries directory: $LOCAL_BINARY_DIR" local local_binary="$LOCAL_BINARY_DIR/docker-29.2.1.tgz" local local_checksum="$LOCAL_BINARY_DIR/docker-29.2.1.tgz.sha256" if [ -f "$local_binary" ]; then log_message "INFO" "Found local Docker binary: $local_binary" # Copy to current directory for consistency with extraction logic cp "$local_binary" "$binary_file" if verify_checksum "$binary_file" "$local_checksum" "local Docker binary"; then binary_path="$binary_file" log_message "SUCCESS" "Using local Docker binary from binaries directory" else log_message "WARNING" "Local Docker binary checksum verification failed" rm -f "$binary_file" fi fi fi # 3. Check current directory for any docker-*.tgz (existing fallback) if [ -z "$binary_path" ]; then log_message "INFO" "Checking current directory for Docker binaries..." local local_files local_files=$(ls docker-*.tgz 2>/dev/null | head -1) if [ -n "$local_files" ]; then binary_path="$local_files" log_message "SUCCESS" "Using local Docker binary: $binary_path" # Optional: Try to verify checksum if .sha256 file exists local checksum_file="${local_files}.sha256" if [ -f "$checksum_file" ]; then if verify_checksum "$binary_path" "$checksum_file" "Docker binary"; then log_message "SUCCESS" "Local Docker binary checksum verified" else log_message "WARNING" "Local Docker binary checksum verification failed, but continuing" fi fi fi fi # 5. Final fallback: no binary available if [ -z "$binary_path" ]; then fail_with_remediation "No Docker binary available" \ "Please provide a Docker static binary using one of these methods: 1. Git Repositorys (primary): Ensure network access to $GIT_BASE_URL 2. Local binaries directory: Place docker-29.2.1.tgz and .sha256 in $LOCAL_BINARY_DIR/ 3. Current directory: Place any docker-*.tgz file in current directory Re-run the script after providing the binary." fi # Extract and install log_message "INFO" "Extracting Docker binary..." # First verify the tar file exists and is readable if [ ! -f "$binary_path" ]; then fail_with_remediation "Docker binary file not found" \ "File $binary_path does not exist. Check download and file permissions." fi if [ ! -r "$binary_path" ]; then fail_with_remediation "Docker binary file not readable" \ "Cannot read file $binary_path. Check file permissions." fi # Test if it's a valid tar archive log_message "DEBUG" "Testing tar archive integrity..." # First check if we can read the file if [ ! -r "$binary_path" ]; then log_message "ERROR" "Cannot read file: $binary_path" log_message "INFO" "File permissions: $(ls -la "$binary_path" 2>/dev/null || echo "cannot stat")" fail_with_remediation "Cannot read Docker binary file" \ "File $binary_path exists but is not readable. 1. Check file permissions: ls -la $binary_path 2. Fix permissions: chmod 644 $binary_path 3. Or download fresh copy" fi # Try to get file type information local file_type="unknown" if command -v file >/dev/null 2>&1; then file_type=$(file "$binary_path" 2>/dev/null || echo "file command failed") elif command -v hexdump >/dev/null 2>&1; then # Check magic bytes manually local magic_bytes=$(hexdump -n 2 -C "$binary_path" 2>/dev/null | head -1 | cut -d' ' -f2-3 || echo "no magic") file_type="hexdump: $magic_bytes" fi log_message "INFO" "File info: $binary_path ($(stat -c%s "$binary_path") bytes)" log_message "INFO" "File type: $file_type" log_message "INFO" "Current directory: $(pwd)" log_message "INFO" "Full path: $(readlink -f "$binary_path" 2>/dev/null || echo "$binary_path")" # Test tar archive with error capture local tar_test_output tar_test_output=$(tar -tzf "$binary_path" 2>&1) local tar_test_exit=$? if [ $tar_test_exit -ne 0 ]; then log_message "ERROR" "File $binary_path is not a valid tar.gz archive (tar exit: $tar_test_exit)" log_message "DEBUG" "Tar test output: $tar_test_output" # Check if it might be a different compression format log_message "INFO" "Checking for alternative compression formats..." # Try gunzip test if command -v gunzip >/dev/null 2>&1; then if gunzip -t "$binary_path" 2>/dev/null; then log_message "WARNING" "File is valid gzip but tar can't read it" else log_message "INFO" "Not a valid gzip file either" fi fi # Check first few bytes if command -v xxd >/dev/null 2>&1; then log_message "DEBUG" "First 20 bytes: $(xxd -l 20 "$binary_path" 2>/dev/null || echo "cannot read")" elif command -v od >/dev/null 2>&1; then log_message "DEBUG" "First 20 bytes: $(od -x -N 20 "$binary_path" 2>/dev/null | head -2 || echo "cannot read")" fi fail_with_remediation "Docker binary file is corrupted or invalid" \ "The Docker binary file is not a valid tar.gz archive. Tar error: $tar_test_output File info: $(stat -c%s "$binary_path") bytes, type: $file_type Possible solutions: 1. The download may have been interrupted or corrupted 2. The file may be in wrong format (not tar.gz) 3. Server might be serving wrong content Steps to fix: 1. Delete corrupted file: rm -f docker-*.tgz 2. Check disk space: df -h . 3. Try alternative sources: a) Git Repositorys: curl -L '$GIT_DOCKER_BINARY_URL' -o docker.tgz b) Local directory: Check $LOCAL_BINARY_DIR/docker-29.2.1.tgz 4. Verify downloaded file: file test.tgz && tar -tzf test.tgz 5. Check if tar command works: tar --version" fi log_message "SUCCESS" "Tar archive validation passed" # Extract the archive log_message "DEBUG" "Extracting files from $binary_path..." local tar_output tar_output=$(tar xzvf "$binary_path" 2>&1) local tar_exit=$? if [ $tar_exit -ne 0 ]; then log_message "ERROR" "Failed to extract files from $binary_path (exit code: $tar_exit)" log_message "DEBUG" "Tar output: $tar_output" log_message "INFO" "Checking extracted files..." if [ -d "docker" ]; then log_message "WARNING" "Some files were extracted to 'docker/' directory" ls -la docker/ 2>/dev/null | head -10 || true fi fail_with_remediation "Failed to extract Docker binary" \ "Extraction failed. Possible reasons: 1. Insufficient disk space: df -h . 2. Permission issues in current directory 3. Corrupted archive (partial download) 4. File system issues Tar error: $tar_output Check disk space and permissions, then try manual extraction: tar xzvf $binary_path" else log_message "SUCCESS" "Docker binary extracted successfully" fi log_message "INFO" "Installing Docker binaries to /usr/bin/" sudo cp docker/* /usr/bin/ 2>/dev/null || { fail_with_remediation "Failed to copy Docker binaries" \ "Permission denied copying to /usr/bin/ 1. Ensure you have sudo privileges 2. Check disk space: df -h / 3. Manual installation: sudo cp docker/* /usr/bin/" } # Ensure binaries are executable log_message "INFO" "Setting executable permissions on Docker binaries..." sudo chmod +x /usr/bin/dockerd /usr/bin/docker 2>/dev/null || { log_message "WARNING" "Could not set executable permissions on Docker binaries" } # Verify Docker binaries work log_message "INFO" "Verifying Docker binaries..." if ! sudo /usr/bin/dockerd --version 2>/dev/null; then fail_with_remediation "Docker binary verification failed" \ "Docker binary (/usr/bin/dockerd) appears to be corrupted or incompatible. The binary was extracted from $binary_path but doesn't run. Check the binary: sudo file /usr/bin/dockerd sudo ls -la /usr/bin/dockerd sudo /usr/bin/dockerd --version The Docker static binary might be for wrong architecture or corrupted. Try downloading manually from one of these sources: 1. Git Repositorys: curl -L '$GIT_DOCKER_BINARY_URL' -o docker.tgz 2. Local directory: Check $LOCAL_BINARY_DIR/docker-29.2.1.tgz Then extract and install: tar xzvf docker.tgz sudo cp docker/* /usr/bin/" else local docker_version docker_version=$(sudo /usr/bin/dockerd --version 2>&1 | head -1) log_message "SUCCESS" "Docker binary verified: $docker_version" fi # Cleanup extracted directory log_message "INFO" "Cleaning up extracted Docker binaries directory..." rm -rf docker log_message "INFO" "Cleanup completed" # DEBUG: Mark start of docker group section log_message "INFO" "=== Starting docker group creation ===" # Create docker group (required for systemd socket configuration and dockerd --group) log_message "INFO" "Creating docker group for Docker socket access..." # Check if group already exists log_message "INFO" "Checking if docker group exists..." local getent_output if getent_output=$(getent group docker 2>&1); then getent_exit=0 else getent_exit=$? fi log_message "INFO" "getent group docker result: exit=$getent_exit, output='$getent_output'" if [ $getent_exit -eq 0 ]; then log_message "SUCCESS" "Docker group already exists: $getent_output" else # Attempt to create docker group with error capture log_message "INFO" "Attempting to create docker group with sudo groupadd docker..." local groupadd_output if groupadd_output=$(sudo groupadd docker 2>&1); then groupadd_exit=0 else groupadd_exit=$? fi if [ $groupadd_exit -eq 0 ]; then log_message "SUCCESS" "Created docker group" else log_message "ERROR" "Failed to create docker group (exit code: $groupadd_exit)" log_message "INFO" "groupadd command output: $groupadd_output" # Check if group was somehow created despite error log_message "INFO" "Checking if docker group was created despite groupadd failure..." local check_getent_output if check_getent_output=$(getent group docker 2>&1); then check_getent_exit=0 else check_getent_exit=$? fi log_message "INFO" "Post-failure check: exit=$check_getent_exit, output='$check_getent_output'" if [ $check_getent_exit -eq 0 ]; then log_message "WARNING" "Docker group exists despite groupadd failure, continuing..." else fail_with_remediation "Cannot create docker group" \ "The docker group is required for Docker socket access. Please create it manually: 1. Check if groupadd command is available: which groupadd 2. Check permissions: sudo -v 3. Manual group creation: sudo groupadd docker 4. Verify: getent group docker If groupadd fails, you may need to: - Check system user/group database - Use alternative: sudo addgroup docker (Debian/Ubuntu) - Edit /etc/group manually (advanced users only)" fi fi fi # Final verification that docker group exists log_message "INFO" "Final verification of docker group existence..." local final_getent_output if final_getent_output=$(getent group docker 2>&1); then final_getent_exit=0 else final_getent_exit=$? fi log_message "INFO" "Final getent result: exit=$final_getent_exit, output='$final_getent_output'" if [ $final_getent_exit -ne 0 ]; then fail_with_remediation "Docker group verification failed" \ "The docker group does not exist after creation attempts. This will cause Docker startup to fail. Please create the docker group manually and re-run the script: 1. sudo groupadd docker 2. Verify: getent group docker | grep docker 3. Re-run this script" fi # Log group details for debugging local docker_gid docker_gid=$(echo "$final_getent_output" | cut -d: -f3) log_message "INFO" "Docker group details: GID=$docker_gid" log_message "SUCCESS" "Docker group verified and ready (GID: $docker_gid)" # DEBUG: Mark end of docker group section log_message "INFO" "=== Finished docker group creation ===" # Configure Docker daemon for LXC (VFS storage driver, cgroupfs) log_message "INFO" "Configuring Docker daemon for LXC (VFS storage driver, cgroupfs)..." # Create docker configuration directory sudo mkdir -p /etc/docker # Create daemon.json for LXC optimization sudo tee /etc/docker/daemon.json > /dev/null < /dev/null <<'EOF' [Unit] Description=Docker Engine After=network-online.target firewalld.service containerd.service Wants=network-online.target Requires=docker.socket [Service] Type=notify ExecStart=/usr/bin/dockerd --group docker ExecReload=/bin/kill -s HUP $MAINPID TimeoutSec=0 RestartSec=2 Restart=always StartLimitBurst=3 StartLimitInterval=60s LimitNOFILE=infinity LimitNPROC=infinity LimitCORE=infinity TasksMax=infinity Delegate=yes KillMode=process [Install] WantedBy=multi-user.target EOF sudo tee /etc/systemd/system/docker.socket > /dev/null <<'EOF' [Unit] Description=Docker Socket for the API [Socket] ListenStream=/var/run/docker.sock SocketMode=0660 SocketUser=root SocketGroup=docker [Install] WantedBy=sockets.target EOF sudo systemctl daemon-reload log_message "INFO" "Enabling Docker service to start on boot..." if ! sudo systemctl enable docker; then log_message "ERROR" "systemctl enable docker failed with exit code: $?" fail_with_remediation "Failed to enable Docker service" \ "Docker service could not be enabled to start on boot. Common causes: 1. Docker socket unit (docker.socket) has configuration errors 2. The docker group may not exist 3. Systemd unit file has syntax errors Check docker.socket status: sudo systemctl status docker.socket --no-pager Verify docker group exists: getent group docker Check systemd unit files: sudo systemctl cat docker.socket sudo systemctl cat docker.service" fi log_message "INFO" "Starting Docker service (systemd)..." # Start the service and capture exit code if sudo systemctl start docker; then start_exit=0 else start_exit=$? fi # Give Docker a moment to start or fail sleep 2 # Check if service is actually active if ! sudo systemctl is-active docker --quiet; then log_message "ERROR" "Docker service failed to start (systemctl start exit: $start_exit)" log_message "INFO" "Checking docker.socket status..." sudo systemctl status docker.socket --no-pager 2>&1 | head -20 || true log_message "INFO" "Checking docker.service status..." sudo systemctl status docker.service --no-pager 2>&1 | head -30 || true log_message "INFO" "Checking Docker daemon logs..." sudo journalctl -u docker --no-pager -n 30 2>&1 | head -50 || true log_message "INFO" "Checking Docker socket logs..." sudo journalctl -u docker.socket --no-pager -n 20 2>&1 | head -30 || true fail_with_remediation "Failed to start Docker service" \ "Docker service failed to start. Common causes: 1. Missing iptables (Docker static binaries require iptables v1.4+ for network bridge) 2. Docker daemon configuration error (check /etc/docker/daemon.json) 3. Storage driver issues (VFS may not be compatible) 4. Cgroup configuration problems 5. Port conflicts or resource limits Latest Docker daemon logs: $(sudo journalctl -u docker --no-pager -n 30 2>&1 | tail -20) Check Docker configuration: sudo cat /etc/docker/daemon.json Verify iptables is installed: which iptables || echo 'iptables not found' iptables --version 2>/dev/null || echo 'Cannot check version' Install iptables if missing: # Debian/Ubuntu: sudo apt-get update && sudo apt-get install -y iptables # RHEL/CentOS: sudo yum install -y iptables # Alpine: sudo apk add iptables Verify docker group exists: getent group docker Manual start attempt for debugging: sudo dockerd --group docker --debug" fi ;; "openrc") # Alpine OpenRC configuration sudo tee /etc/init.d/docker > /dev/null <<'EOF' #!/sbin/openrc-run description="Docker Engine" command="/usr/bin/dockerd" command_args="--group docker" pidfile="/run/docker.pid" command_background=true depend() { need net after firewall } EOF sudo chmod +x /etc/init.d/docker sudo rc-update add docker default log_message "INFO" "Starting Docker service (OpenRC)..." if ! sudo rc-service docker start; then log_message "ERROR" "rc-service docker start failed with exit code: $?" fail_with_remediation "Failed to start Docker service (OpenRC)" \ "Docker service failed to start under OpenRC. Common causes: 1. Missing iptables (Docker static binaries require iptables v1.4+ for network bridge) 2. Docker socket or port conflicts 3. Missing dependencies 4. Docker configuration errors Check OpenRC logs: sudo rc-service docker status sudo cat /var/log/docker.log 2>/dev/null || echo "No docker.log found" Verify iptables is installed: which iptables || echo 'iptables not found' iptables --version 2>/dev/null || echo 'Cannot check version' Install iptables if missing: # Alpine: sudo apk add iptables # Debian/Ubuntu: sudo apt-get update && sudo apt-get install -y iptables # RHEL/CentOS: sudo yum install -y iptables Verify docker group exists: getent group docker Manual start attempt: sudo dockerd --group docker --debug" fi ;; "sysvinit") # Traditional SysV init script sudo tee /etc/init.d/docker > /dev/null <<'EOF' #!/bin/bash ### BEGIN INIT INFO # Provides: docker # Required-Start: $local_fs $network $remote_fs # Required-Stop: $local_fs $network $remote_fs # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Docker Engine # Description: Docker container runtime ### END INIT INFO DESC="Docker Engine" DAEMON=/usr/bin/dockerd DAEMON_ARGS="--group docker" PIDFILE=/var/run/docker.pid SCRIPTNAME=/etc/init.d/docker [ -x "$DAEMON" ] || exit 0 . /lib/lsb/init-functions case "$1" in start) log_daemon_msg "Starting $DESC" "docker" start-stop-daemon --start --background --pidfile "$PIDFILE" \ --exec "$DAEMON" -- $DAEMON_ARGS log_end_msg $? ;; stop) log_daemon_msg "Stopping $DESC" "docker" start-stop-daemon --stop --pidfile "$PIDFILE" --retry 10 log_end_msg $? ;; restart) $0 stop sleep 1 $0 start ;; status) status_of_proc -p "$PIDFILE" "$DAEMON" docker ;; *) echo "Usage: $SCRIPTNAME {start|stop|restart|status}" exit 3 ;; esac exit 0 EOF sudo chmod +x /etc/init.d/docker sudo update-rc.d docker defaults log_message "INFO" "Starting Docker service (SysV init)..." if ! sudo service docker start; then log_message "ERROR" "service docker start failed with exit code: $?" fail_with_remediation "Failed to start Docker service (SysV init)" \ "Docker service failed to start under SysV init. Common causes: 1. Missing iptables (Docker static binaries require iptables v1.4+ for network bridge) 2. Docker socket or port conflicts 3. Missing dependencies or configuration errors 4. The docker group may not exist or be accessible Check service status: sudo service docker status Verify iptables is installed: which iptables || echo 'iptables not found' iptables --version 2>/dev/null || echo 'Cannot check version' Install iptables if missing: # Debian/Ubuntu: sudo apt-get update && sudo apt-get install -y iptables # RHEL/CentOS: sudo yum install -y iptables # Alpine: sudo apk add iptables Verify docker group exists: getent group docker Check for Docker logs: sudo dockerd --group docker --debug 2>&1 | head -50" fi ;; *) log_message "WARNING" "Unknown init system '$INIT_SYSTEM', trying systemd defaults" sudo systemctl daemon-reload 2>/dev/null || true sudo systemctl enable docker 2>/dev/null || true sudo systemctl start docker 2>/dev/null || { log_message "ERROR" "Failed to start Docker with unknown init system" echo -e "${YELLOW}Please start Docker manually and re-run the script${NC}" exit 1 } ;; esac # Verify Docker is running log_message "INFO" "Verifying Docker service..." sleep 3 # Give Docker time to start if ! sudo docker info >/dev/null 2>&1; then fail_with_remediation "Docker failed to start" \ "Docker installation completed but service failed to start: 1. Check Docker logs: journalctl -u docker (systemd) or /var/log/docker.log 2. Verify iptables is installed (Docker static binaries require iptables v1.4+): which iptables || echo 'iptables not found' iptables --version 2>/dev/null || echo 'Cannot check version' 3. Install iptables if missing: # Debian/Ubuntu: sudo apt-get update && sudo apt-get install -y iptables # RHEL/CentOS: sudo yum install -y iptables # Alpine: sudo apk add iptables 4. Verify configuration: sudo dockerd --debug 5. Manual start: sudo dockerd --group docker &" fi # Verify Docker is using VFS storage driver log_message "INFO" "Verifying Docker storage driver configuration..." if sudo docker info 2>/dev/null | grep -q "Storage Driver: vfs"; then log_message "SUCCESS" "Docker configured with VFS storage driver (LXC compatible)" else log_message "WARNING" "Docker is not using VFS storage driver. Checking current driver..." sudo docker info 2>/dev/null | grep "Storage Driver:" || log_message "ERROR" "Could not determine storage driver" log_message "WARNING" "LXC compatibility may be limited without VFS storage driver" fi # Add current user to docker group for passwordless docker commands log_message "INFO" "Adding current user to docker group..." # Security notice: docker group grants root-equivalent privileges echo -e "${YELLOW}${BOLD}Security Notice:${NC} Adding your user to the 'docker' group grants root-equivalent privileges." echo -e "${YELLOW}Any user in the docker group can run commands as root on the host system.${NC}" echo -e "${YELLOW}Only proceed if you understand and accept this security risk.${NC}" read -r -p "$(echo -e "${YELLOW}Add $(whoami) to docker group? (Y/n): ${NC}")" -n 1 echo if [[ ! $REPLY =~ ^[Nn]$ ]]; then sudo usermod -aG docker "$(whoami)" 2>/dev/null && \ log_message "SUCCESS" "Added $(whoami) to docker group (log out and back in for changes)" else log_message "WARNING" "Skipping docker group addition. You will need to use sudo for docker commands." echo -e "${YELLOW}Note: You can manually add yourself to docker group later with:${NC}" echo -e "${CYAN} sudo usermod -aG docker $(whoami)${NC}" echo -e "${YELLOW}Then log out and back in for changes to take effect.${NC}" fi log_message "SUCCESS" "Docker Engine setup completed successfully" } # ============================================================================== # WALLARM NODE DEPLOYMENT # ============================================================================== deploy_wallarm_node() { log_message "INFO" "Deploying Wallarm filtering node..." # Load Wallarm Docker image (priority: Git Repositorys -> local dir -> current dir) log_message "INFO" "Loading Wallarm Docker image..." local image_loaded=false # 1. Try Git Repositorys download (primary source) local git_image_file="wallarm-node-6.11.0-rc1.tar.gz" if [ "$image_loaded" = "false" ]; then log_message "INFO" "Attempting to download Wallarm image from Git Repositorys..." if download_from_git "$GIT_WALLARM_IMAGE_URL" "$git_image_file" "Wallarm Docker image"; then if verify_checksum "$git_image_file" "$GIT_WALLARM_CHECKSUM_URL" "Wallarm Docker image"; then log_message "INFO" "Loading Wallarm image from Git Repositorys download..." if gunzip -c "$git_image_file" | sudo docker load; then log_message "SUCCESS" "Wallarm image loaded from Git Repositorys download" image_loaded=true else log_message "ERROR" "Failed to load Wallarm image from Git Repositorys download" fi # Cleanup downloaded file rm -f "$git_image_file" else log_message "WARNING" "Git Repositorys Wallarm image checksum verification failed" rm -f "$git_image_file" fi fi fi # 2. Check local images directory if [ "$image_loaded" = "false" ] && [ -d "$LOCAL_IMAGE_DIR" ]; then log_message "INFO" "Checking local images directory: $LOCAL_IMAGE_DIR" local local_image="$LOCAL_IMAGE_DIR/wallarm-node-6.11.0-rc1.tar.gz" local local_checksum="$LOCAL_IMAGE_DIR/wallarm-node-6.11.0-rc1.tar.gz.sha256" if [ -f "$local_image" ]; then log_message "INFO" "Found local Wallarm image: $local_image" if verify_checksum "$local_image" "$local_checksum" "local Wallarm image"; then log_message "INFO" "Loading Wallarm image from local directory..." if gunzip -c "$local_image" | sudo docker load; then log_message "SUCCESS" "Wallarm image loaded from local directory" image_loaded=true else log_message "ERROR" "Failed to load Wallarm image from local directory" fi else log_message "WARNING" "Local Wallarm image checksum verification failed" fi fi fi # 3. Check current directory for compressed image (tar.gz) if [ "$image_loaded" = "false" ]; then log_message "INFO" "Checking current directory for Wallarm image (tar.gz)..." local gz_image gz_image=$(ls wallarm-node-*.tar.gz 2>/dev/null | head -1) if [ -n "$gz_image" ]; then log_message "INFO" "Found compressed Wallarm image: $gz_image" # Verify checksum if .sha256 file exists local checksum_file="${gz_image}.sha256" if [ -f "$checksum_file" ]; then if ! verify_checksum "$gz_image" "$checksum_file" "Wallarm image"; then log_message "WARNING" "Wallarm image checksum verification failed, but attempting load anyway" fi fi log_message "INFO" "Loading compressed Wallarm image..." if gunzip -c "$gz_image" | sudo docker load; then log_message "SUCCESS" "Wallarm image loaded from compressed file" image_loaded=true else log_message "ERROR" "Failed to load Wallarm image from $gz_image" fi fi fi # 4. Check current directory for uncompressed image (tar) - existing fallback if [ "$image_loaded" = "false" ]; then log_message "INFO" "Checking current directory for Wallarm image (tar)..." local tar_image tar_image=$(ls wallarm-node-*.tar 2>/dev/null | head -1) if [ -n "$tar_image" ]; then log_message "INFO" "Found uncompressed Wallarm image: $tar_image" if ! sudo docker load -i "$tar_image"; then log_message "ERROR" "Failed to load Wallarm image from $tar_image" else log_message "SUCCESS" "Wallarm image loaded from uncompressed file" image_loaded=true fi fi fi # 6. Final fallback: no image available if [ "$image_loaded" = "false" ]; then fail_with_remediation "No Wallarm image available" \ "Please provide a Wallarm Docker image using one of these methods: 1. Git Repositorys (primary): Ensure network access to $GIT_BASE_URL 2. Local images directory: Place wallarm-node-6.11.0-rc1.tar.gz and .sha256 in $LOCAL_IMAGE_DIR/ 3. Current directory: Place wallarm-node-*.tar.gz or wallarm-node-*.tar file in current directory Save for offline use: docker save $WALLARM_IMAGE_TARGET -o wallarm-node-latest.tar Re-run the script after providing the image." fi # Ensure image is tagged with standard name (for consistency) if [ "$image_loaded" = "true" ] && [ "$REGISTRY_REACHABLE" = "false" ]; then # If we loaded from local file, tag the loaded image with standard name local loaded_image_id loaded_image_id=$(sudo docker images --format "{{.ID}}" --filter "dangling=false" | head -1) if [ -n "$loaded_image_id" ]; then sudo docker tag "$loaded_image_id" "$WALLARM_IMAGE_TARGET" log_message "INFO" "Tagged loaded image as $WALLARM_IMAGE_TARGET" fi fi # Create nginx configuration log_message "INFO" "Creating nginx configuration..." local nginx_config="$INSTANCE_DIR/nginx.conf" # Start building the configuration file sudo tee "$nginx_config" > /dev/null < /dev/null < /dev/null < /dev/null < /dev/null <> "\$LOG_FILE" # Stop existing container if running sudo docker stop "\$CONTAINER_NAME" 2>/dev/null || true sudo docker rm "\$CONTAINER_NAME" 2>/dev/null || true # Start new container sudo docker run -d \\ --name "\$CONTAINER_NAME" \\ --restart always \\ -p $INGRESS_PORT:80 \\ -p $MONITORING_PORT:90 \\ -e WALLARM_API_TOKEN="$WALLARM_TOKEN" \\ -e WALLARM_API_HOST="$API_HOST" \\ -e NGINX_BACKEND="$UPSTREAM_IP:$UPSTREAM_PORT" \\ -e WALLARM_MODE="monitoring" \\ -v "\$NGINX_CONFIG:/etc/nginx/http.d/default.conf:ro" \\ $WALLARM_IMAGE_TARGET echo "\$(date) - Container started with ID: \$(sudo docker ps -q -f name=\$CONTAINER_NAME)" >> "\$LOG_FILE" # Verify container is running sleep 3 if sudo docker ps | grep -q "\$CONTAINER_NAME"; then echo "\$(date) - Verification: Container is running" >> "\$LOG_FILE" echo "Wallarm node \$CONTAINER_NAME started successfully" else echo "\$(date) - ERROR: Container failed to start" >> "\$LOG_FILE" sudo docker logs "\$CONTAINER_NAME" >> "\$LOG_FILE" 2>&1 exit 1 fi EOF sudo chmod +x "$start_script" log_message "SUCCESS" "Start script created: $start_script" # Create init system service for automatic startup log_message "INFO" "Creating service for automatic startup (init system: $INIT_SYSTEM)..." case "$INIT_SYSTEM" in "systemd") local service_file="/etc/systemd/system/wallarm-$INSTANCE_NAME.service" sudo tee "$service_file" > /dev/null </dev/null || \ log_message "WARNING" "Failed to enable systemd service (may already exist)" ;; "openrc") local service_file="/etc/init.d/wallarm-$INSTANCE_NAME" sudo tee "$service_file" > /dev/null </dev/null || true docker rm $INSTANCE_NAME 2>/dev/null || true eend \$? } EOF sudo chmod +x "$service_file" sudo rc-update add "wallarm-$INSTANCE_NAME" default 2>/dev/null || \ log_message "WARNING" "Failed to add OpenRC service (may already exist)" ;; "sysvinit") local service_file="/etc/init.d/wallarm-$INSTANCE_NAME" sudo tee "$service_file" > /dev/null </dev/null || true docker rm $INSTANCE_NAME 2>/dev/null || true log_end_msg \$? ;; restart) \$0 stop sleep 2 \$0 start ;; status) if docker ps | grep -q "$INSTANCE_NAME"; then echo "$INSTANCE_NAME is running" exit 0 else echo "$INSTANCE_NAME is not running" exit 1 fi ;; *) echo "Usage: \$0 {start|stop|restart|status}" exit 3 ;; esac exit 0 EOF sudo chmod +x "$service_file" sudo update-rc.d "wallarm-$INSTANCE_NAME" defaults 2>/dev/null || \ log_message "WARNING" "Failed to add SysV init service (may already exist)" ;; *) log_message "WARNING" "Unknown init system, not creating service (manual start via $start_script)" ;; esac # Start the Wallarm node log_message "INFO" "Starting Wallarm filtering node..." if ! sudo "$start_script"; then fail_with_remediation "Failed to start Wallarm node" \ "Container failed to start. Check: 1. Docker logs: sudo docker logs $INSTANCE_NAME 2. Port conflicts: sudo ss -tlnp | grep ':$INGRESS_PORT\|:$MONITORING_PORT' 3. Docker status: sudo docker info 4. Manual start attempt: sudo $start_script" fi log_message "SUCCESS" "Wallarm filtering node deployed successfully" log_message "SUCCESS" " Container: $INSTANCE_NAME" log_message "SUCCESS" " Ingress Port: $INGRESS_PORT" log_message "SUCCESS" " Monitoring Port: $MONITORING_PORT" log_message "SUCCESS" " Upstream: $UPSTREAM_IP:$UPSTREAM_PORT" log_message "SUCCESS" " Config Directory: $INSTANCE_DIR" } # ============================================================================== # DEPLOYMENT VERIFICATION # ============================================================================== verify_deployment() { log_message "INFO" "Verifying Wallarm deployment..." # Check if container is running log_message "INFO" "Checking if container is running..." if ! sudo docker ps | grep -q "$INSTANCE_NAME"; then fail_with_remediation "Wallarm container is not running" \ "Container failed to start or crashed: 1. Check container logs: sudo docker logs $INSTANCE_NAME 2. Check Docker service: sudo systemctl status docker (or equivalent) 3. Manual start: sudo $INSTANCE_DIR/start.sh" fi log_message "SUCCESS" "Container is running" # Test ingress port log_message "INFO" "Testing ingress port $INGRESS_PORT..." if ! check_port_available "$INGRESS_PORT"; then log_message "SUCCESS" "Ingress port $INGRESS_PORT is in use (as expected)" else log_message "WARNING" "Ingress port $INGRESS_PORT appears available (container may not be listening)" fi # Test monitoring port log_message "INFO" "Testing monitoring port $MONITORING_PORT..." if ! check_port_available "$MONITORING_PORT"; then log_message "SUCCESS" "Monitoring port $MONITORING_PORT is in use (as expected)" else log_message "WARNING" "Monitoring port $MONITORING_PORT appears available" fi # Test health check endpoint log_message "INFO" "Testing health check endpoint..." local health_check_url="http://localhost:$INGRESS_PORT/health" if curl -sf --connect-timeout 5 "$health_check_url" >/dev/null 2>&1; then log_message "SUCCESS" "Health check endpoint responsive" else log_message "WARNING" "Health check endpoint not responsive (may need time to start)" sleep 5 if curl -sf --connect-timeout 5 "$health_check_url" >/dev/null 2>&1; then log_message "SUCCESS" "Health check endpoint now responsive" else log_message "WARNING" "Health check endpoint still not responsive (check nginx config)" fi fi # Test handshake through filtering node log_message "INFO" "Testing handshake through filtering node to upstream..." local test_url="http://localhost:$INGRESS_PORT/" if curl -sfI --connect-timeout 10 "$test_url" >/dev/null 2>&1; then log_message "SUCCESS" "Handshake successful: filtering node can reach upstream" else log_message "WARNING" "Handshake failed (upstream may not be responding)" log_message "INFO" "Checking if upstream is directly reachable..." if timeout 5 bash -c "cat < /dev/null > /dev/tcp/$UPSTREAM_IP/$UPSTREAM_PORT" 2>/dev/null; then log_message "ERROR" "Upstream is reachable but filtering node cannot proxy" echo -e "${YELLOW}Possible nginx configuration issue. Check:${NC}" echo -e "1. Container logs: sudo docker logs $INSTANCE_NAME" echo -e "2. Nginx config: sudo docker exec $INSTANCE_NAME cat /etc/nginx/http.d/default.conf" else log_message "WARNING" "Upstream server is not reachable (as previously warned)" fi fi # Check Wallarm cloud synchronization log_message "INFO" "Checking Wallarm cloud synchronization (this may take 30 seconds)..." echo -e "${YELLOW}Note: Full synchronization with Wallarm cloud may take several minutes.${NC}" echo -e "${YELLOW}You can check sync status in Wallarm Console.${NC}" # Quick test: check container logs for synchronization messages if sudo docker logs "$INSTANCE_NAME" 2>&1 | tail -20 | grep -i "sync\|connected\|token" >/dev/null 2>&1; then log_message "SUCCESS" "Wallarm node appears to be communicating with cloud" else log_message "WARNING" "No cloud synchronization messages in logs yet (may need time)" fi log_message "SUCCESS" "Deployment verification completed" echo -e "\n${GREEN}${BOLD}Verification Summary:${NC}" echo -e " ${GREEN}✓${NC} Container running: $INSTANCE_NAME" echo -e " ${GREEN}✓${NC} Ingress port: $INGRESS_PORT" echo -e " ${GREEN}✓${NC} Monitoring port: $MONITORING_PORT" echo -e " ${GREEN}✓${NC} Upstream: $UPSTREAM_IP:$UPSTREAM_PORT" echo -e " ${GREEN}✓${NC} Cloud region: $CLOUD_REGION ($API_HOST)" } # ============================================================================== # MAIN FUNCTION # ============================================================================== main() { clear echo -e "${BLUE}${BOLD}" echo "╔══════════════════════════════════════════════════════════════╗" echo "║ WALLARM DEPLOYMENT SCRIPT - V1.2 ║" echo "║ LXC-Optimized Filtering Node Deployment ║" echo "╚══════════════════════════════════════════════════════════════╝${NC}" echo -e "\n${YELLOW}Starting deployment at: $(date)${NC}" # Initialize logging # Create logs directory if it doesn't exist local log_dir="${HOME:-.}/logs" if [ ! -d "$log_dir" ]; then if ! mkdir -p "$log_dir"; then echo -e "${YELLOW}Cannot create log directory $log_dir, falling back to current directory...${NC}" log_dir="." fi fi LOG_FILE="$log_dir/wallarm-deployment.log" if ! : > "$LOG_FILE"; then echo -e "${RED}Cannot create log file at $LOG_FILE${NC}" echo -e "${YELLOW}Falling back to current directory...${NC}" LOG_FILE="./wallarm-deployment.log" : > "$LOG_FILE" 2>/dev/null || true fi if ! chmod 644 "$LOG_FILE" 2>/dev/null; then echo -e "${YELLOW}Warning: Could not set permissions on log file${NC}" fi log_message "INFO" "=== Wallarm Deployment Started ===" # SSL security warning if [ "$INSECURE_SSL" = "1" ]; then log_message "WARNING" "SSL certificate validation is DISABLED (insecure). Set WALLARM_INSECURE_SSL=0 to enable validation." fi # Phase 1: Verify preflight check log_message "INFO" "=== PHASE 1: PREFLIGHT CHECK VERIFICATION ===" verify_preflight_check # Phase 2: Configuration collection log_message "INFO" "=== PHASE 2: CONFIGURATION COLLECTION ===" select_cloud_region collect_configuration # Phase 3: Docker engine setup (LXC optimized) log_message "INFO" "=== PHASE 3: DOCKER ENGINE SETUP (LXC OPTIMIZED) ===" setup_docker_engine # Phase 4: Deployment log_message "INFO" "=== PHASE 4: DEPLOYMENT ===" deploy_wallarm_node # Phase 5: Verification log_message "INFO" "=== PHASE 5: VERIFICATION ===" verify_deployment # Success message log_message "SUCCESS" "=== WALLARM DEPLOYMENT COMPLETED SUCCESSFULLY ===" echo -e "\n${GREEN}${BOLD}╔══════════════════════════════════════════════════════════════╗${NC}" echo -e "${GREEN}${BOLD}║ WALLARM FILTERING NODE DEPLOYMENT SUCCESSFUL ║${NC}" echo -e "${GREEN}${BOLD}╚══════════════════════════════════════════════════════════════╝${NC}" echo -e "\n${CYAN}The Wallarm filtering node is now active and protecting your application.${NC}" echo -e "${YELLOW}Full deployment log: $LOG_FILE${NC}" echo -e "${YELLOW}Instance directory: $INSTANCE_DIR${NC}" echo -e "\n${GREEN}To stop the node:${NC} sudo docker stop $INSTANCE_NAME" echo -e "${GREEN}To restart:${NC} sudo $INSTANCE_DIR/start.sh" echo -e "${GREEN}To view logs:${NC} sudo docker logs -f $INSTANCE_NAME" echo -e "\n${MAGENTA}${BOLD}Deployment completed successfully!${NC}" echo -e "\n${YELLOW}Important next steps:${NC}" echo -e "1. Monitor sync status in Wallarm Console" echo -e "2. Test attack detection with safe test: curl http://localhost:$INGRESS_PORT/?wallarm_test=1" echo -e "3. Review logs periodically: sudo docker logs --tail 50 $INSTANCE_NAME" } # ============================================================================== # SCRIPT EXECUTION # ============================================================================== # Ensure we're in bash if [ -z "$BASH_VERSION" ]; then echo "Error: This script must be run with bash" >&2 exit 1 fi # Run main function main "$@"