wallarm/wallarm-deploy-ct.sh
2026-03-18 20:33:00 +00:00

985 lines
No EOL
38 KiB
Bash

#!/bin/bash
# ==============================================================================
# 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)
# ==============================================================================
# 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 <<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
# Create docker socket
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
# Reload systemd and enable service
log_message "INFO" "Starting Docker daemon..."
sudo systemctl daemon-reload
sudo systemctl enable docker.service docker.socket
sudo systemctl start docker
# Wait for Docker to start
local max_attempts=30
local attempt=1
while [ $attempt -le $max_attempts ]; do
if sudo docker info >/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 <<EOF
server {
listen 80;
server_name _;
wallarm_mode monitoring;
location / {
proxy_pass http://$UPSTREAM_IP:$UPSTREAM_PORT;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# Health check endpoint
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}
server {
listen 90;
server_name _;
location /wallarm-status {
wallarm_status on;
allow all;
access_log off;
}
}
EOF
# Create start.sh script for persistence across reboots
log_message "INFO" "Creating start.sh script for persistence..."
sudo tee "$INSTANCE_DIR/start.sh" > /dev/null <<EOF
#!/bin/bash
# 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"
# 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 <<EOF
[Unit]
Description=Wallarm Filtering Node $INSTANCE_NAME
After=docker.service
Requires=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=$INSTANCE_DIR
ExecStart=$INSTANCE_DIR/start.sh
TimeoutStartSec=0
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable "wallarm-$INSTANCE_NAME.service" 2>/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}"