#!/bin/bash # ============================================================================== # WALLARM BULLETPROOF DEPLOYMENT SCRIPT - V1.0 # ============================================================================== # Features: # - OS-agnostic deployment (Ubuntu, Debian, CentOS, RHEL, Alpine, etc.) # - Docker/containerd installation from static binaries (no package managers) # - Comprehensive pre-flight checks (sudo, OS, architecture, resources) # - Resource verification (Wallarm cloud, Docker registry, application server) # - DAU-friendly error handling with remediation instructions # - Multiple-run detection and handling # - Deployment logging with overwrite protection # - Handshake verification with application server # - Persistence across reboots (start.sh) # ============================================================================== # Color definitions for better UX RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' MAGENTA='\033[0;35m' BOLD='\033[1m' NC='\033[0m' # No Color # Logging configuration LOG_FILE="/var/log/wallarm-deployment.log" DEPLOYMENT_TIMESTAMP=$(date '+%Y-%m-%d_%H-%M-%S') # Cloud endpoints (from Wallarm documentation) EU_DATA_NODES=("api.wallarm.com" "node-data0.eu1.wallarm.com" "node-data1.eu1.wallarm.com") US_DATA_NODES=("us1.api.wallarm.com" "node-data0.us1.wallarm.com" "node-data1.us1.wallarm.com") # Docker configuration DOCKER_VERSION="27.0.0" # Current stable version DOCKER_STATIC_BASE_URL="https://download.docker.com/linux/static/stable" # Resource reachability flags (set during verification) REGISTRY_REACHABLE=false DOWNLOAD_REACHABLE=false CLOUD_REGION="" API_HOST="" # ============================================================================== # LOGGING & ERROR HANDLING FUNCTIONS # ============================================================================== log_message() { local level="$1" local message="$2" local 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}" 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 } validate_sudo_access() { log_message "INFO" "Validating sudo access..." # Check if user can run sudo if ! command -v sudo >/dev/null 2>&1; then fail_with_remediation "sudo command not found" \ "1. Install sudo package for your OS: - Ubuntu/Debian: apt-get update && apt-get install -y sudo - CentOS/RHEL: yum install -y sudo - Alpine: apk add sudo 2. Add your user to sudoers: usermod -aG sudo \$(whoami) # Ubuntu/Debian usermod -aG wheel \$(whoami) # CentOS/RHEL 3. Log out and log back in for changes to take effect." fi # Test sudo with password prompt if needed if ! sudo -v; then fail_with_remediation "sudo authentication failed" \ "1. Ensure you have sudo privileges: - Run 'sudo -l' to check your sudo permissions - Contact your system administrator 2. If password is required: - Make sure you know the correct password - The script will prompt for password when needed 3. For passwordless sudo: - Add '\$(whoami) ALL=(ALL) NOPASSWD:ALL' to /etc/sudoers - Use 'visudo' to edit safely - WARNING: Only do this in secure environments" fi log_message "SUCCESS" "Sudo access validated" } detect_os_and_version() { log_message "INFO" "Detecting OS and version..." local os_name="" local os_version="" # Check for /etc/os-release first (modern systems) if [ -f /etc/os-release ]; then . /etc/os-release os_name="$ID" os_version="$VERSION_ID" # Check for older RedHat/CentOS elif [ -f /etc/redhat-release ]; then os_name="rhel" os_version=$(cat /etc/redhat-release | sed -e 's/.*release \([0-9]\+\)\..*/\1/') # Check for Alpine 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 # Normalize OS names case "$os_name" in "ubuntu"|"debian"|"centos"|"rhel"|"alpine"|"amzn"|"ol"|"rocky"|"almalinux") # Valid supported OS ;; *) log_message "WARNING" "OS '$os_name' not explicitly tested but may work" ;; esac echo "$os_name:$os_version" } detect_architecture() { log_message "INFO" "Detecting system architecture..." local 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)" ;; *) fail_with_remediation "Unsupported architecture: $arch" \ "This script supports: 1. x86_64 (Intel/AMD 64-bit) - Most common 2. aarch64 (ARM 64-bit) - AWS Graviton, Raspberry Pi 4 3. armhf (ARM 32-bit) - Older ARM devices Your architecture '$arch' is not supported for Docker static binaries. Consider: - Using a different machine with supported architecture - Manual Docker installation from package manager - Contact Wallarm support for alternative deployment options" ;; esac echo "$docker_arch" } verify_resources() { log_message "INFO" "=== PHASE 2: RESOURCE VERIFICATION ===" # Get cloud selection from user log_message "INFO" "Select Wallarm Cloud region" echo -e "\n${CYAN}${BOLD}Wallarm Cloud Region Selection:${NC}" echo -e "1. ${YELLOW}US Cloud${NC} (us1.api.wallarm.com) - For US-based deployments" echo -e "2. ${YELLOW}EU Cloud${NC} (api.wallarm.com) - For EU-based deployments" local cloud_choice="" while [[ ! "$cloud_choice" =~ ^(1|2|US|EU)$ ]]; do read -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") CLOUD_REGION="US" API_HOST="us1.api.wallarm.com" NODES_TO_TEST=("${US_DATA_NODES[@]}") log_message "INFO" "Selected US Cloud" ;; 2|"EU") CLOUD_REGION="EU" API_HOST="api.wallarm.com" NODES_TO_TEST=("${EU_DATA_NODES[@]}") log_message "INFO" "Selected EU Cloud" ;; *) echo -e "${RED}Invalid choice. Enter 1/US for US Cloud or 2/EU for EU Cloud${NC}" ;; esac done # Test Wallarm Cloud endpoints log_message "INFO" "Testing connectivity to Wallarm Cloud endpoints..." local all_endpoints_reachable=true for endpoint in "${NODES_TO_TEST[@]}"; do if curl -skI --connect-timeout 10 "https://$endpoint" >/dev/null 2>&1; then log_message "SUCCESS" "Wallarm endpoint $endpoint is reachable" else log_message "ERROR" "Wallarm endpoint $endpoint is NOT reachable" all_endpoints_reachable=false fi done if [ "$all_endpoints_reachable" = false ]; then fail_with_remediation "Some Wallarm Cloud endpoints are not reachable" \ "1. Check firewall rules: - Allow outbound HTTPS (port 443) to Wallarm IPs - US Cloud IPs: 34.96.64.17, 34.110.183.149, 35.235.66.155, 34.102.90.100, 34.94.156.115, 35.235.115.105 - EU Cloud IPs: 34.160.38.183, 34.144.227.90, 34.90.110.226 2. Check network connectivity: - Run: ping 8.8.8.8 (test general internet) - Run: nslookup $API_HOST (test DNS) - Run: curl -v https://$API_HOST (verbose test) 3. Check proxy settings: - If behind proxy, set http_proxy/https_proxy environment variables - Or configure Docker to use proxy 4. Contact your network administrator if issues persist" fi # Test Docker Registry (hub.docker.com) log_message "INFO" "Testing connectivity to Docker Registry (hub.docker.com)..." local registry_status=$(curl -skI --connect-timeout 10 -o /dev/null -w "%{http_code}" "https://hub.docker.com/v2/") if [[ "$registry_status" == "200" || "$registry_status" == "401" || "$registry_status" == "302" ]]; then log_message "SUCCESS" "Docker Registry is reachable (Status: $registry_status)" REGISTRY_REACHABLE=true else log_message "WARNING" "Docker Registry may be blocked (Status: $registry_status)" REGISTRY_REACHABLE=false fi # Test Docker Download Server (download.docker.com) log_message "INFO" "Testing connectivity to Docker Download Server..." if curl -skI --connect-timeout 10 "https://download.docker.com" >/dev/null 2>&1; then log_message "SUCCESS" "Docker Download Server is reachable" DOWNLOAD_REACHABLE=true else log_message "WARNING" "Docker Download Server may be blocked" DOWNLOAD_REACHABLE=false fi # Check for local Docker binaries as fallback if [ "$DOWNLOAD_REACHABLE" = false ]; then log_message "INFO" "Checking for local Docker binary fallback..." if ls docker-*.tgz 2>/dev/null | grep -q .; then log_message "SUCCESS" "Found local Docker binary: $(ls docker-*.tgz | head -1)" else log_message "WARNING" "No local Docker binaries found. Manual download may be required." fi fi # Check for local Wallarm image as fallback if [ "$REGISTRY_REACHABLE" = false ]; then log_message "INFO" "Checking for local Wallarm image fallback..." if ls wallarm-node-*.tar 2>/dev/null | grep -q .; then log_message "SUCCESS" "Found local Wallarm image: $(ls wallarm-node-*.tar | head -1)" else log_message "WARNING" "No local Wallarm images found. Manual download may be required." fi fi # Final resource assessment if [ "$REGISTRY_REACHABLE" = false ] && [ "$DOWNLOAD_REACHABLE" = false ]; then log_message "ERROR" "Critical: Neither Docker Registry nor Download Server reachable" echo -e "\n${YELLOW}${BOLD}Possible workarounds:${NC}" echo -e "1. Download Docker binary manually:" echo -e " curl -L '$DOCKER_STATIC_BASE_URL/$architecture/docker-$DOCKER_VERSION.tgz' -o docker.tgz" echo -e "2. Download Wallarm image manually:" echo -e " docker pull wallarm/node:latest" echo -e " docker save wallarm/node:latest -o wallarm-node-latest.tar" echo -e "3. Place files in current directory and re-run script" echo -e "\n${RED}Without these resources, deployment cannot proceed.${NC}" read -p "$(echo -e "${YELLOW}Do you want to continue anyway? (y/N): ${NC}")" -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then fail_with_remediation "Insufficient resources for deployment" \ "Please ensure at least one of these is available: 1. Internet access to Docker Registry (hub.docker.com) 2. Internet access to Docker Download Server (download.docker.com) 3. Local Docker binary (docker-*.tgz) in current directory 4. Local Wallarm image (wallarm-node-*.tar) in current directory" fi fi log_message "SUCCESS" "Resource verification completed" echo "$CLOUD_REGION:$API_HOST:$REGISTRY_REACHABLE:$DOWNLOAD_REACHABLE" } setup_docker_engine() { local architecture="$1" log_message "INFO" "Setting up Docker Engine..." # 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 | cut -d' ' -f3 | tr -d ',') log_message "SUCCESS" "Docker is already installed and running (version $docker_version)" return 0 fi log_message "INFO" "Docker not found or not running. Proceeding with installation..." # Determine binary source local binary_file="docker-$DOCKER_VERSION.tgz" local binary_path="" if [ "$DOWNLOAD_REACHABLE" = true ]; then # Download Docker static binary log_message "INFO" "Downloading Docker static binary for $architecture..." local download_url="$DOCKER_STATIC_BASE_URL/$architecture/docker-$DOCKER_VERSION.tgz" if curl -fL --connect-timeout 30 "$download_url" -o "$binary_file"; then log_message "SUCCESS" "Downloaded Docker binary: $binary_file" binary_path="$binary_file" else log_message "ERROR" "Failed to download Docker binary from $download_url" binary_path="" fi fi # Fallback: Check for local Docker binary if [ -z "$binary_path" ]; then log_message "INFO" "Checking for local Docker binary..." local 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" else fail_with_remediation "No Docker binary available" \ "Please provide a Docker static binary: 1. Download manually: curl -L '$DOCKER_STATIC_BASE_URL/$architecture/docker-$DOCKER_VERSION.tgz' -o docker.tgz 2. Or place an existing docker-*.tgz file in current directory 3. Re-run the script after downloading" fi fi # Extract and install log_message "INFO" "Extracting Docker binary..." if ! tar xzvf "$binary_path" >/dev/null 2>&1; then fail_with_remediation "Failed to extract Docker binary" \ "The Docker binary file may be corrupted: 1. Delete the corrupted file: rm -f docker-*.tgz 2. Download a fresh copy manually 3. Verify the file integrity: tar -tzf docker-*.tgz should list files" 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/" } # Cleanup extracted directory rm -rf docker # Configure systemd service log_message "INFO" "Configuring Docker systemd service..." # Check if docker group exists, create if not if ! getent group docker >/dev/null; then sudo groupadd docker 2>/dev/null || log_message "WARNING" "Failed to create docker group (may already exist)" fi # Create systemd service file sudo tee /etc/systemd/system/docker.service > /dev/null < /dev/null </dev/null 2>&1; then log_message "SUCCESS" "Docker daemon started successfully (attempt $attempt/$max_attempts)" break fi log_message "DEBUG" "Waiting for Docker daemon... ($attempt/$max_attempts)" sleep 2 attempt=$((attempt + 1)) done if [ $attempt -gt $max_attempts ]; then fail_with_remediation "Docker daemon failed to start" \ "Troubleshooting steps: 1. Check Docker logs: sudo journalctl -u docker.service 2. Verify systemd configuration: sudo systemctl status docker 3. Check for port conflicts: sudo netstat -tulpn | grep :2375 4. Manual start attempt: sudo dockerd --debug 5. Check kernel requirements: uname -r (should be 3.10+) 6. Ensure cgroups are mounted: mount | grep cgroup" fi # Test Docker installation log_message "INFO" "Testing Docker installation with hello-world..." if sudo docker run --rm hello-world >/dev/null 2>&1; then log_message "SUCCESS" "Docker installation verified successfully" else log_message "WARNING" "Docker hello-world test failed (network may be restricted)" fi # Add current user to docker group (optional) log_message "INFO" "Adding current user to docker group..." if ! groups $(whoami) | grep -q docker; then sudo usermod -aG docker $(whoami) 2>/dev/null && \ log_message "SUCCESS" "Added $(whoami) to docker group. Log out and back in for changes to take effect." fi log_message "SUCCESS" "Docker Engine setup completed" } 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 -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}" fi done # Calculate monitoring port (ingress + 10) local monitoring_port=$((ingress_port + 10)) log_message "INFO" "Monitoring port will be: $monitoring_port" # Check for port conflicts log_message "INFO" "Checking for port conflicts..." if sudo netstat -tulpn 2>/dev/null | grep -E ":$ingress_port\s" >/dev/null 2>&1; then fail_with_remediation "Port $ingress_port is already in use" \ "1. Free up port $ingress_port: - Stop the service using it: sudo lsof -ti:$ingress_port | xargs kill -9 - Change your application to use a different port 2. Choose a different inbound port - Common alternatives: 8080, 8888, 3000 - Avoid well-known ports (80, 443) if already used 3. Check what's using the port: sudo netstat -tulpn | grep :$ingress_port sudo lsof -i :$ingress_port" fi if sudo netstat -tulpn 2>/dev/null | grep -E ":$monitoring_port\s" >/dev/null 2>&1; then log_message "WARNING" "Port $monitoring_port is in use, choosing alternative..." monitoring_port=$((ingress_port + 100)) log_message "INFO" "New monitoring port: $monitoring_port" fi # 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 -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 ! [[ "$upstream_ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] && \ ! [[ "$upstream_ip" =~ ^[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 -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 -p "$(echo -e "${YELLOW}Continue anyway? (y/N): ${NC}")" -n 1 -r 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 netstat -tulpn | 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 "US Cloud: https://us1.my.wallarm.com/nodes" echo -e "EU Cloud: https://my.wallarm.com/nodes" echo -e "Create a new 'Wallarm node' and copy the token" while [[ -z "$wallarm_token" ]]; do read -p "$(echo -e "${YELLOW}Paste Wallarm Node Token: ${NC}")" wallarm_token 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${NC}" wallarm_token="" fi done # Generate instance names and paths local instance_name="wallarm-node-$ingress_port" local instance_dir="/opt/wallarm/$instance_name" # Check for existing container with same name if sudo docker ps -a --format "{{.Names}}" | grep -q "^$instance_name$"; then log_message "WARNING" "Container '$instance_name' already exists" echo -e "${YELLOW}A container named '$instance_name' already exists.${NC}" echo -e "Options:" echo -e "1. Remove existing container and deploy new" echo -e "2. Use different port to avoid conflict" echo -e "3. Exit and manage manually" read -p "$(echo -e "${YELLOW}Choose option (1/2/3) [1]: ${NC}")" option option="${option:-1}" case "$option" in 1) log_message "INFO" "Removing existing container '$instance_name'..." sudo docker rm -f "$instance_name" 2>/dev/null || \ log_message "WARNING" "Failed to remove container (may not exist)" ;; 2) log_message "INFO" "Please restart script with different port" exit 0 ;; 3) log_message "INFO" "Exiting for manual management" exit 0 ;; *) log_message "ERROR" "Invalid option" ;; esac fi # Create instance directory log_message "INFO" "Creating instance directory: $instance_dir" sudo mkdir -p "$instance_dir" # Output configuration summary echo -e "\n${GREEN}${BOLD}✓ Configuration Complete${NC}" echo -e "${CYAN}Summary:${NC}" echo -e " Inbound Port: ${YELLOW}$ingress_port${NC}" echo -e " Monitoring Port: ${YELLOW}$monitoring_port${NC}" echo -e " Application Server: ${YELLOW}$upstream_ip:$upstream_port${NC}" echo -e " Wallarm Cloud: ${YELLOW}$CLOUD_REGION ($API_HOST)${NC}" echo -e " Instance Directory: ${YELLOW}$instance_dir${NC}" # Return configuration as colon-separated values echo "$ingress_port:$monitoring_port:$upstream_ip:$upstream_port:$wallarm_token:$instance_name:$instance_dir" } deploy_wallarm_node() { log_message "INFO" "Deploying Wallarm filtering node..." # Pull or load Wallarm image if [ "$REGISTRY_REACHABLE" = true ]; then log_message "INFO" "Pulling Wallarm Docker image..." if ! sudo docker pull wallarm/node:latest; then fail_with_remediation "Failed to pull Wallarm image" \ "Docker pull failed. Possible reasons: 1. Network connectivity to Docker Hub 2. Docker Hub rate limiting (login required) 3. Insufficient disk space Solutions: 1. Check network: docker pull hello-world 2. Login to Docker Hub: docker login 3. Use local image: docker save/load 4. Check disk: df -h /var/lib/docker" fi log_message "SUCCESS" "Wallarm image pulled successfully" else log_message "INFO" "Registry unreachable, checking for local image..." local image_files=$(ls wallarm-node-*.tar 2>/dev/null | head -1) if [ -n "$image_files" ]; then log_message "INFO" "Loading local image: $image_files" if ! sudo docker load < "$image_files"; then fail_with_remediation "Failed to load local Wallarm image" \ "The local image file may be corrupted: 1. Verify file integrity: tar -tzf $image_files 2. Download fresh image on another machine: docker pull wallarm/node:latest docker save wallarm/node:latest -o wallarm-node-latest.tar 3. Transfer file to this machine" fi log_message "SUCCESS" "Local Wallarm image loaded" else fail_with_remediation "No Wallarm image available" \ "Please provide a Wallarm Docker image: 1. Download on another machine with internet: docker pull wallarm/node:latest docker save wallarm/node:latest -o wallarm-node-latest.tar 2. Transfer file to this machine 3. Place in current directory and re-run script" fi fi # Create nginx configuration log_message "INFO" "Creating nginx configuration..." sudo tee "$INSTANCE_DIR/nginx.conf" > /dev/null < /dev/null <> "\$LOG_FILE" # Stop and remove 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 \\ --network host \\ -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/node:latest 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 "$INSTANCE_DIR/start.sh" # Deploy container using start.sh log_message "INFO" "Deploying Wallarm container..." if ! sudo "$INSTANCE_DIR/start.sh"; then log_message "ERROR" "Container deployment failed, checking logs..." sudo docker logs "$INSTANCE_NAME" 2>&1 | tail -20 | while read line; do log_message "DEBUG" "Container log: $line" done fail_with_remediation "Wallarm container deployment failed" \ "Common issues: 1. Invalid Wallarm token - Verify token in Wallarm Console - Ensure token matches selected cloud region (US/EU) 2. Port conflicts - Check ports $INGRESS_PORT and $MONITORING_PORT are free - sudo netstat -tulpn | grep -E ':$INGRESS_PORT|:$MONITORING_PORT' 3. Docker permissions - Ensure Docker daemon is running: sudo systemctl status docker - Check user in docker group: groups 4. Resource constraints - Check memory: free -h - Check disk: df -h 5. View full logs: sudo docker logs $INSTANCE_NAME" fi log_message "SUCCESS" "Wallarm node deployed successfully" log_message "INFO" "Container Name: $INSTANCE_NAME" log_message "INFO" "Instance Directory: $INSTANCE_DIR" log_message "INFO" "Start Script: $INSTANCE_DIR/start.sh" # Create systemd service for auto-start on boot log_message "INFO" "Creating systemd service for automatic startup..." sudo tee "/etc/systemd/system/wallarm-$INSTANCE_NAME.service" > /dev/null </dev/null || \ log_message "WARNING" "Failed to enable systemd service (may already exist)" log_message "SUCCESS" "Systemd service created: wallarm-$INSTANCE_NAME.service" } verify_deployment() { log_message "INFO" "Verifying deployment..." # Wait for container to be fully started log_message "INFO" "Waiting for Wallarm node to initialize (30 seconds)..." sleep 30 # Check container status log_message "INFO" "Checking container status..." if ! sudo docker ps --format "table {{.Names}}\t{{.Status}}" | grep -q "$INSTANCE_NAME"; then fail_with_remediation "Container is not running" \ "The Wallarm container failed to start: 1. Check container logs: sudo docker logs $INSTANCE_NAME 2. Verify Docker service: sudo systemctl status docker 3. Check resource constraints: sudo docker stats $INSTANCE_NAME 4. Review start script: $INSTANCE_DIR/start.sh" fi local container_status=$(sudo docker ps --format "table {{.Names}}\t{{.Status}}" | grep "$INSTANCE_NAME") log_message "SUCCESS" "Container status: $container_status" # Test monitoring endpoint log_message "INFO" "Testing monitoring endpoint (port $MONITORING_PORT)..." local max_attempts=10 local attempt=1 local monitoring_ok=false while [ $attempt -le $max_attempts ]; do if curl -s "http://localhost:$MONITORING_PORT/wallarm-status" 2>/dev/null | grep -q "requests"; then monitoring_ok=true log_message "SUCCESS" "Monitoring endpoint responding (attempt $attempt/$max_attempts)" break fi log_message "DEBUG" "Monitoring endpoint not ready yet ($attempt/$max_attempts)" sleep 5 attempt=$((attempt + 1)) done if [ "$monitoring_ok" = false ]; then log_message "WARNING" "Monitoring endpoint not responding as expected" log_message "INFO" "Checking alternative..." # Try direct container exec if sudo docker exec "$INSTANCE_NAME" curl -s http://localhost:90/wallarm-status 2>/dev/null | grep -q "requests"; then log_message "SUCCESS" "Monitoring endpoint works inside container" else log_message "ERROR" "Monitoring endpoint completely unavailable" fi fi # Test handshake through filtering node to application server log_message "INFO" "Testing handshake through filtering node to application server..." local handshake_ok=false for i in {1..5}; do local response_code=$(curl -s -o /dev/null -w "%{http_code}" -H "Host: test.local" "http://localhost:$INGRESS_PORT/health" --connect-timeout 10) if [[ "$response_code" =~ ^(200|301|302|401|403)$ ]]; then handshake_ok=true log_message "SUCCESS" "Handshake successful: HTTP $response_code from application server (attempt $i/5)" break else log_message "DEBUG" "Handshake attempt $i failed: HTTP $response_code" sleep 3 fi done if [ "$handshake_ok" = false ]; then log_message "WARNING" "Handshake to application server failed" echo -e "${YELLOW}${BOLD}Warning:${NC} Cannot reach application server through filtering node" echo -e "${YELLOW}Possible issues:${NC}" echo -e "1. Application server not responding on $UPSTREAM_IP:$UPSTREAM_PORT" echo -e "2. Network routing/firewall between container and application server" echo -e "3. NGINX configuration issue" echo -e "4. Application server requires specific Host header" # Test direct connection to application server log_message "INFO" "Testing direct connection to application server..." if timeout 5 bash -c "cat < /dev/null > /dev/tcp/$UPSTREAM_IP/$UPSTREAM_PORT" 2>/dev/null; then log_message "SUCCESS" "Application server is directly reachable" else log_message "ERROR" "Application server is not reachable even directly" fi else log_message "SUCCESS" "Handshake verification passed" fi # Perform attack simulation test (safe) log_message "INFO" "Performing attack simulation test (safe SQL injection probe)..." local attack_test_url="http://localhost:$INGRESS_PORT/?id=%27OR+1%3D1--" local attack_response=$(curl -s -o /dev/null -w "%{http_code}" -H "Host: test.local" "$attack_test_url" --connect-timeout 10) if [[ "$attack_response" =~ ^(200|301|302|401|403|404)$ ]]; then log_message "SUCCESS" "Attack simulation test passed: HTTP $attack_response" log_message "INFO" "Note: Wallarm in monitoring mode logs attacks but doesn't block" else log_message "WARNING" "Attack simulation test returned unexpected code: $attack_response" fi # Check Wallarm synchronization status log_message "INFO" "Checking Wallarm cloud synchronization..." local sync_attempts=0 local sync_ok=false while [ $sync_attempts -lt 5 ]; do if sudo docker logs "$INSTANCE_NAME" 2>&1 | tail -20 | grep -i "sync\|connected\|success" | grep -v "error\|fail" >/dev/null; then sync_ok=true log_message "SUCCESS" "Wallarm cloud synchronization detected" break fi sync_attempts=$((sync_attempts + 1)) sleep 10 done if [ "$sync_ok" = false ]; then log_message "WARNING" "Wallarm cloud synchronization not confirmed in logs" log_message "INFO" "Check synchronization manually in Wallarm Console" fi # Final deployment summary echo -e "\n${GREEN}${BOLD}╔══════════════════════════════════════════════════════════════╗${NC}" echo -e "${GREEN}${BOLD}║ DEPLOYMENT VERIFICATION COMPLETE ║${NC}" echo -e "${GREEN}${BOLD}╚══════════════════════════════════════════════════════════════╝${NC}" echo -e "\n${CYAN}${BOLD}Deployment Summary:${NC}" echo -e " ${YELLOW}✓${NC} Container: ${GREEN}$INSTANCE_NAME${NC} (running)" echo -e " ${YELLOW}✓${NC} Inbound Port: ${GREEN}$INGRESS_PORT${NC}" echo -e " ${YELLOW}✓${NC} Monitoring Port: ${GREEN}$MONITORING_PORT${NC} (http://localhost:$MONITORING_PORT/wallarm-status)" echo -e " ${YELLOW}✓${NC} Application Server: ${GREEN}$UPSTREAM_IP:$UPSTREAM_PORT${NC}" echo -e " ${YELLOW}✓${NC} Wallarm Cloud: ${GREEN}$CLOUD_REGION${NC}" echo -e " ${YELLOW}✓${NC} Instance Directory: ${GREEN}$INSTANCE_DIR${NC}" echo -e " ${YELLOW}✓${NC} Start Script: ${GREEN}$INSTANCE_DIR/start.sh${NC}" echo -e " ${YELLOW}✓${NC} Systemd Service: ${GREEN}wallarm-$INSTANCE_NAME.service${NC}" echo -e "\n${CYAN}${BOLD}Next Steps:${NC}" echo -e " 1. Monitor attacks in ${YELLOW}Wallarm Console${NC}" echo -e " 2. Switch to ${YELLOW}blocking mode${NC} when ready:" echo -e " Edit ${GREEN}$INSTANCE_DIR/nginx.conf${NC} and change 'monitoring' to 'block'" echo -e " 3. Restart container: ${GREEN}sudo $INSTANCE_DIR/start.sh${NC}" echo -e " 4. Check logs: ${GREEN}sudo docker logs -f $INSTANCE_NAME${NC}" echo -e "\n${YELLOW}Deployment log: $LOG_FILE${NC}" # Write success marker sudo tee "$INSTANCE_DIR/DEPLOYMENT_SUCCESS" > /dev/null </dev/null || { 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" truncate -s 0 "$LOG_FILE" 2>/dev/null || > "$LOG_FILE" } sudo chmod 644 "$LOG_FILE" 2>/dev/null || chmod 644 "$LOG_FILE" exec > >(tee "$LOG_FILE") 2>&1 log_message "INFO" "=== Wallarm Bulletproof Deployment Started ===" # Phase 1: Pre-flight validation log_message "INFO" "=== PHASE 1: PRE-FLIGHT VALIDATION ===" validate_sudo_access local os_info=$(detect_os_and_version) local os_name=$(echo "$os_info" | cut -d: -f1) local os_version=$(echo "$os_info" | cut -d: -f2) log_message "SUCCESS" "OS detected: $os_name $os_version" local architecture=$(detect_architecture) log_message "SUCCESS" "Architecture: $architecture" log_message "INFO" "Pre-flight validation completed successfully" # Phase 2: Resource verification local resource_info=$(verify_resources) CLOUD_REGION=$(echo "$resource_info" | cut -d: -f1) API_HOST=$(echo "$resource_info" | cut -d: -f2) REGISTRY_REACHABLE=$(echo "$resource_info" | cut -d: -f3) DOWNLOAD_REACHABLE=$(echo "$resource_info" | cut -d: -f4) log_message "SUCCESS" "Resource verification completed:" log_message "SUCCESS" " Cloud Region: $CLOUD_REGION" log_message "SUCCESS" " API Host: $API_HOST" log_message "SUCCESS" " Registry Reachable: $REGISTRY_REACHABLE" log_message "SUCCESS" " Download Reachable: $DOWNLOAD_REACHABLE" # Phase 3: Docker engine setup log_message "INFO" "=== PHASE 3: DOCKER ENGINE SETUP ===" setup_docker_engine "$architecture" # Phase 4: Configuration collection log_message "INFO" "=== PHASE 4: CONFIGURATION COLLECTION ===" local config_info=$(collect_configuration) INGRESS_PORT=$(echo "$config_info" | cut -d: -f1) MONITORING_PORT=$(echo "$config_info" | cut -d: -f2) UPSTREAM_IP=$(echo "$config_info" | cut -d: -f3) UPSTREAM_PORT=$(echo "$config_info" | cut -d: -f4) WALLARM_TOKEN=$(echo "$config_info" | cut -d: -f5) INSTANCE_NAME=$(echo "$config_info" | cut -d: -f6) INSTANCE_DIR=$(echo "$config_info" | cut -d: -f7) 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" log_message "SUCCESS" " Instance: $INSTANCE_NAME" log_message "SUCCESS" " Directory: $INSTANCE_DIR" # Phase 5: Deployment log_message "INFO" "=== PHASE 5: DEPLOYMENT ===" deploy_wallarm_node # Phase 6: Verification log_message "INFO" "=== PHASE 6: VERIFICATION ===" verify_deployment 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}Thank you for using Wallarm!${NC}" } # Execute main function main "$@"