From 4ceedb8d7a8bf132521ef69cf8f075c1e6b856bf Mon Sep 17 00:00:00 2001 From: cclohmar Date: Wed, 18 Mar 2026 20:33:00 +0000 Subject: [PATCH] chore: auto-commit 2026-03-18 20:33 --- wallarm-deploy-ct.sh | 1048 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 925 insertions(+), 123 deletions(-) diff --git a/wallarm-deploy-ct.sh b/wallarm-deploy-ct.sh index 9290327..520894b 100644 --- a/wallarm-deploy-ct.sh +++ b/wallarm-deploy-ct.sh @@ -1,183 +1,985 @@ #!/bin/bash # ============================================================================== -# SECHPOINT WALLARM SMART DEPLOYER - V7 (TRIPLE-VERIFIED CONNECTIVITY) +# WALLARM NODE 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) # ============================================================================== -YELLOW='\033[1;33m'; GREEN='\033[0;32m'; RED='\033[0;31m'; BLUE='\033[0;34m'; NC='\033[0m'; BOLD='\033[1m' -LOG_FILE="/var/log/wallarm-deploy.log" +# 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 -# Cloud Endpoints -EU_DATA_NODES=("api.wallarm.com" "node-data0.eu1.wallarm.com") -US_DATA_NODES=("us1.api.wallarm.com" "node-data0.us1.wallarm.com") +# Logging configuration +LOG_FILE="/var/log/wallarm-deployment.log" +DEPLOYMENT_TIMESTAMP=$(date '+%Y-%m-%d_%H-%M-%S') -# --- Initialization --- -sudo touch "$LOG_FILE" && sudo chmod 644 "$LOG_FILE" -exec > >(tee -a "$LOG_FILE") 2>&1 +# 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") -clear -echo -e "${BLUE}${BOLD}==========================================================${NC}" -echo -e "${BLUE}${BOLD} Wallarm Full Binary & Container Deployer V7 ${NC}" -echo -e "${BLUE}${BOLD}==========================================================${NC}" +# Docker configuration +DOCKER_VERSION="27.0.0" # Current stable version +DOCKER_STATIC_BASE_URL="https://download.docker.com/linux/static/stable" -# --- 1. PRE-FLIGHT & TRIPLE CONNECTIVITY CHECK --- +# Resource reachability flags (set during verification) +REGISTRY_REACHABLE=false +DOWNLOAD_REACHABLE=false +CLOUD_REGION="" +API_HOST="" -check_connectivity() { - echo -e "\n${YELLOW}[1/5] Testing Connectivity Matrix...${NC}" - - # A. Cloud Selection & Test - read -p " Wallarm Cloud (US/EU) [US]: " CLOUD_SEL - CLOUD_SEL=${CLOUD_SEL^^}; CLOUD_SEL=${CLOUD_SEL:-US} - local nodes_to_test=("${US_DATA_NODES[@]}") - [[ "$CLOUD_SEL" == "EU" ]] && nodes_to_test=("${EU_DATA_NODES[@]}") +# ============================================================================== +# LOGGING & ERROR HANDLING FUNCTIONS +# ============================================================================== - for node in "${nodes_to_test[@]}"; do - if curl -skI --connect-timeout 5 "https://$node" > /dev/null 2>&1; then - echo -e " ${GREEN}[PASS]${NC} Wallarm Cloud: $node" - else - echo -e " ${RED}[FAIL]${NC} Wallarm Cloud: $node (Check Firewall/Proxy)" - fi - done - API_HOST=$([[ "$CLOUD_SEL" == "EU" ]] && echo "api.wallarm.com" || echo "us1.api.wallarm.com") +log_message() { + local level="$1" + local message="$2" + local timestamp=$(date '+%Y-%m-%d %H:%M:%S') - # B. Test Docker Registry (For Images) - REGISTRY_STATUS=$(curl -skI --connect-timeout 5 -o /dev/null -w "%{http_code}" "https://registry-1.docker.io/v2/") - if [[ "$REGISTRY_STATUS" == "200" || "$REGISTRY_STATUS" == "401" ]]; then - REGISTRY_REACHABLE=true - echo -e " ${GREEN}[PASS]${NC} Docker Registry (Status: $REGISTRY_STATUS)" - else - REGISTRY_REACHABLE=false - echo -e " ${RED}[FAIL]${NC} Docker Registry (Status: $REGISTRY_STATUS)" - fi + case "$level" in + "INFO") color="${BLUE}" ;; + "SUCCESS") color="${GREEN}" ;; + "WARNING") color="${YELLOW}" ;; + "ERROR") color="${RED}" ;; + "DEBUG") color="${CYAN}" ;; + *) color="${NC}" ;; + esac - # C. Test Docker Download Server (For Engine Binaries) - DOWNLOAD_REACHABLE=true - if ! curl -skI --connect-timeout 5 "https://download.docker.com" > /dev/null 2>&1; then - DOWNLOAD_REACHABLE=false - echo -e " ${RED}[WARN]${NC} Docker Download Server (Blocked)" - else - echo -e " ${GREEN}[PASS]${NC} Docker Download Server" - fi - - # Final Gatekeeper - if [ "$REGISTRY_REACHABLE" = false ] && ! ls *.tar >/dev/null 2>&1; then - echo -e "${RED}✗ FATAL: No registry access and no local .tar image found.${NC}"; exit 1 - fi + echo -e "${color}[${timestamp}] ${level}: ${message}${NC}" + echo "[${timestamp}] ${level}: ${message}" >> "$LOG_FILE" } -# --- 2. ENGINE SETUP (Smart Binary Install) --- +fail_with_remediation() { + local error_msg="$1" + local remediation="$2" -setup_manual_engine() { - echo -e "\n${YELLOW}[2/5] Setting up Docker Engine...${NC}" - - if command -v docker > /dev/null 2>&1 && sudo docker info > /dev/null 2>&1; then - echo -e " ${GREEN}✓${NC} Docker is already active." + 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 - ARCH=$(uname -m) - BINARY_FILE="docker-static.tgz" - - if [ "$DOWNLOAD_REACHABLE" = true ]; then - echo " Downloading Docker Binaries ($ARCH)..." - curl -L "https://download.docker.com/linux/static/stable/$ARCH/docker-24.0.7.tgz" -o $BINARY_FILE - elif ls docker-static.tgz >/dev/null 2>&1; then - echo " Download blocked. Using local $BINARY_FILE..." + 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 - echo -e " ${RED}✗ FATAL: Download server blocked and no local $BINARY_FILE found.${NC}"; exit 1 + log_message "ERROR" "Wallarm endpoint $endpoint is NOT reachable" + all_endpoints_reachable=false fi - - tar xzvf $BINARY_FILE > /dev/null - sudo cp docker/* /usr/bin/ - rm -rf docker $BINARY_FILE - - sudo tee /etc/systemd/system/docker.service > /dev/null </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" } -# --- 3. INPUT & WORKSPACE --- +collect_configuration() { + log_message "INFO" "Collecting deployment configuration..." -get_user_input() { - echo -e "\n${YELLOW}[3/5] Instance Configuration...${NC}" - read -p " Enter Inbound Port [80]: " IN_PORT; IN_PORT=${IN_PORT:-80} - MON_PORT=$((IN_PORT + 10)) - echo -e " ${YELLOW}i${NC} Monitoring Port: ${BOLD}$MON_PORT${NC}" + # 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}" - INSTANCE_DIR="/opt/wallarm/node_$IN_PORT" - NODE_NAME="wallarm-node-$IN_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 - read -p " Upstream App IP [127.0.0.1]: " UPSTREAM_IP; UPSTREAM_IP=${UPSTREAM_IP:-127.0.0.1} - read -p " Upstream App Port [8080]: " UPSTREAM_PORT; UPSTREAM_PORT=${UPSTREAM_PORT:-8080} - read -p " Paste Wallarm Token: " TOKEN + # Calculate monitoring port (ingress + 10) + local monitoring_port=$((ingress_port + 10)) + log_message "INFO" "Monitoring port will be: $monitoring_port" - if sudo netstat -tulpn | grep -E ":$IN_PORT |:$MON_PORT " > /dev/null 2>&1; then - echo -e " ${RED}✗ FATAL: Port conflict detected.${NC}"; exit 1 + # 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" } -# --- 4. DEPLOYMENT & PERSISTENCE --- +deploy_wallarm_node() { + log_message "INFO" "Deploying Wallarm filtering node..." -execute_deployment() { - echo -e "\n${YELLOW}[4/5] Launching Container...${NC}" - sudo mkdir -p "$INSTANCE_DIR" + # 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 </dev/null -sudo docker run -d --name $NODE_NAME --restart always \\ - -p $IN_PORT:80 -p $MON_PORT:90 \\ - -e WALLARM_API_TOKEN=$TOKEN -e WALLARM_API_HOST=$API_HOST \\ - -v "$INSTANCE_DIR/nginx.conf:/etc/nginx/http.d/default.conf:ro" \\ +# Wallarm Node start script - Auto-generated +# This script ensures the Wallarm node starts on system boot + +set -e + +CONTAINER_NAME="$INSTANCE_NAME" +NGINX_CONFIG="$INSTANCE_DIR/nginx.conf" +LOG_FILE="$INSTANCE_DIR/container.log" + +echo "\$(date) - Starting Wallarm node \$CONTAINER_NAME" >> "\$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" - sudo "$INSTANCE_DIR/start.sh" -} -# --- 5. VERIFY --- + # 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 -verify() { - echo -e "\n${YELLOW}[5/5] Final Verification...${NC}" - sleep 15 - if curl -s "http://localhost:$MON_PORT/wallarm-status" | grep -q "requests"; then - echo -e "\n${GREEN}${BOLD}✅ SUCCESS: Wallarm Active on Port $IN_PORT${NC}" - echo -e " Monitor: http://localhost:$MON_PORT/wallarm-status" - curl -s -o /dev/null -w " Attack Test: HTTP %{http_code}\n" "http://localhost:$IN_PORT/?id='OR+1=1--" - else - echo -e "\n${RED}❌ FAILED: Node not responding.${NC}" - sudo docker logs $NODE_NAME | tail -n 5 + 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" } -check_connectivity; setup_manual_engine; get_user_input; execute_deployment; verify \ No newline at end of file +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}" \ No newline at end of file