From 1a2a0fbd7bf3c8e2891451aebcf07489066933e7 Mon Sep 17 00:00:00 2001 From: admin Date: Sat, 1 Aug 2026 09:25:22 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20Go=20binary=20=E2=80=94=20preflight,=20?= =?UTF-8?q?state,=20tunnel=20over=20TLS:443?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Single-binary wallarm deployment manager: - shared/ — system detection, validation, connectivity (port of wallarm-lib.sh) - preflight/ — mandatory checks on every start (OS, arch, disk, memory, cloud) - state/ — ~/.wallarm/state.json persistence (nodes, deployment type) - tunnel/ — reverse SSH tunnel over TLS:443 via Zoraxy edge proxy - cmd/wallarm/main.go — auto-detect state, route to wizard or dashboard wallarm-docker.sh wrapper delegates to existing ct-* scripts for now. --- .gitignore | 1 + cmd/wallarm/main.go | 161 +++ docker/wallarm-ct-deploy.sh | 1653 ++++++++++++++++++++++++++++++ docker/wallarm-docker.sh | 1698 ++----------------------------- go.mod | 7 + go.sum | 6 + internal/preflight/preflight.go | 169 +++ internal/shared/shared.go | 174 ++++ internal/state/state.go | 83 ++ internal/tunnel/tunnel.go | 146 +++ setup.sh | 2 +- 11 files changed, 2469 insertions(+), 1631 deletions(-) create mode 100644 cmd/wallarm/main.go create mode 100755 docker/wallarm-ct-deploy.sh mode change 100755 => 100644 docker/wallarm-docker.sh create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/preflight/preflight.go create mode 100644 internal/shared/shared.go create mode 100644 internal/state/state.go create mode 100644 internal/tunnel/tunnel.go diff --git a/.gitignore b/.gitignore index 7dae6c4..e5a4fd6 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ Thumbs.db # Local notes (excluded from sync) notes/ +wallarm diff --git a/cmd/wallarm/main.go b/cmd/wallarm/main.go new file mode 100644 index 0000000..d6d7856 --- /dev/null +++ b/cmd/wallarm/main.go @@ -0,0 +1,161 @@ +// wallarm — single-binary Wallarm deployment manager. +// +// On every start: run preflight checks → detect existing deployment → route to wizard or dashboard. +// +// Commands: +// +// wallarm Auto-detect state, show wizard or dashboard +// wallarm --tunnel Start reverse SSH tunnel over TLS:443 +// wallarm --help Show help +package main + +import ( + "flag" + "fmt" + "os" + + "git.sechpoint.app/customer-engineering/wallarm/internal/preflight" + "git.sechpoint.app/customer-engineering/wallarm/internal/state" + "git.sechpoint.app/customer-engineering/wallarm/internal/tunnel" +) + +// Embedded tunnel key — set at build time with: +// +// go build -ldflags "-X main.tunnelKey=$(cat ~/.wallarm/tunnel_key)" +var tunnelKey string + +func main() { + flag.Usage = func() { + fmt.Fprintf(os.Stderr, `wallarm — Wallarm Deployment Manager + +Usage: + wallarm Start interactive deployment wizard/dashboard + wallarm --tunnel Start reverse SSH tunnel to sechpoint.app + wallarm --help Show this help + +On first run, wallarm checks system readiness, then guides you through +deployment. On subsequent runs, it shows your existing deployments. +`) + } + help := flag.Bool("help", false, "Show help") + tunnelFlag := flag.Bool("tunnel", false, "Start reverse SSH tunnel") + flag.Parse() + + if *help { + flag.Usage() + return + } + + // ── Tunnel mode ────────────────────────────────────────────── + if *tunnelFlag { + cfg := tunnel.DefaultConfig() + if tunnelKey != "" { + cfg.KeyBytes = []byte(tunnelKey) + } else { + fmt.Fprintln(os.Stderr, "No tunnel key configured. Set WALLARM_TUNNEL_KEY or build with -ldflags.") + os.Exit(1) + } + if err := tunnel.Start(cfg); err != nil { + fmt.Fprintf(os.Stderr, "Tunnel error: %v\n", err) + os.Exit(1) + } + return + } + + // ── Default: preflight → detect state → route ──────────────── + fmt.Println("═══ Wallarm Deployment Manager ═══") + fmt.Println() + + // 1. Preflight checks (always run on start) + fmt.Println("Running preflight checks...") + result := preflight.Run() + + printPreflight(result) + + if !result.Passed { + fmt.Println("\n❌ Preflight checks failed. Fix the issues above and re-run.") + os.Exit(1) + } + fmt.Println("✅ Preflight checks passed.") + fmt.Println() + + // 2. Detect existing deployment + if state.HasDeployment() { + showDashboard() + } else { + showWizard(result) + } +} + +func printPreflight(r preflight.Result) { + for _, c := range r.Checks { + marker := "✅" + desc := "" + if !c.Passed { + marker = "❌" + desc = " — " + c.Detail + } else if c.Warning { + marker = "⚠️" + desc = " — " + c.Detail + } else { + desc = " — " + c.Detail + } + fmt.Printf(" %s %s%s\n", marker, c.Name, desc) + } +} + +func showDashboard() { + fmt.Println("📊 Existing deployment detected.") + fmt.Println() + s, err := state.Load() + if err != nil || s == nil { + fmt.Println("Could not load state. Starting fresh.") + showWizard(preflight.Result{Passed: true}) + return + } + + fmt.Printf("Deployment type: %s\n", s.DeploymentType) + fmt.Printf("Cloud region: %s (%s)\n", s.CloudRegion, s.APIHost) + fmt.Println() + fmt.Println("Nodes:") + for i, n := range s.Nodes { + status := "●" + if n.Status != "running" { + status = "○" + } + fmt.Printf(" %s %d. %s — %s", status, i+1, n.Name, n.Status) + if n.Address != "" { + fmt.Printf(" (%s)", n.Address) + } + if n.Port != 0 { + fmt.Printf(" :%d", n.Port) + } + fmt.Println() + } + + fmt.Println() + fmt.Println("[a]dd node [c]onfigure [r]emove node [t]unnel [q]uit") + fmt.Println() + fmt.Println("Dashboard actions coming soon — use the existing bash scripts for configuration.") +} + +func showWizard(r preflight.Result) { + fmt.Println("No existing deployment found. Starting setup wizard...") + fmt.Println() + fmt.Println("Choose deployment type:") + fmt.Println(" 1) Docker — Wallarm node as a container") + fmt.Println(" 2) Native — Wallarm node directly on this OS (no Docker)") + fmt.Println() + fmt.Println("Wizard UI coming soon — use the bash scripts in deploy/ for now.") + fmt.Println() + if r.USReachable || r.EUReachable { + fmt.Print("Cloud reachable: ") + if r.USReachable { + fmt.Print("US ✓ ") + } + if r.EUReachable { + fmt.Print("EU ✓") + } + fmt.Println() + } +} diff --git a/docker/wallarm-ct-deploy.sh b/docker/wallarm-ct-deploy.sh new file mode 100755 index 0000000..9d0bf4c --- /dev/null +++ b/docker/wallarm-ct-deploy.sh @@ -0,0 +1,1653 @@ +#!/bin/bash +# ============================================================================== +# WALLARM DEPLOYMENT SCRIPT - V1.3 (Docker deployment) +# ============================================================================== +# Purpose: Deploy Wallarm filtering node (Docker container) after preflight check +# Features: +# - Reads preflight check results from .env file +# - Interactive configuration (cloud region, ports, token, upstream) +# - Docker installation with LXC optimization (VFS storage driver) +# - Wallarm node deployment with persistence +# - Deployment verification with handshake test +# - DAU-friendly error handling with remediation +# ============================================================================== + +# Script location and shared library (colors, logging, validation, detection, connectivity) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=./wallarm-lib.sh +source "$SCRIPT_DIR/wallarm-lib.sh" + +# Strict error handling +set -euo pipefail +trap early_error_handler ERR + +# Configuration +ENV_FILE=".env" +LOG_FILE="${HOME:-.}/logs/wallarm-deployment.log" + +# Git Repositorys artifact URLs (primary source) - Docker deployment artifacts +GIT_BASE_URL="https://git.sechpoint.app/customer-engineering/wallarm" +GIT_RAW_URL="https://git.sechpoint.app/customer-engineering/wallarm/raw/branch/main" +GIT_DOCKER_BINARY_URL="${GIT_RAW_URL}/docker/binaries/docker-29.2.1.tgz" +GIT_DOCKER_CHECKSUM_URL="${GIT_RAW_URL}/docker/binaries/docker-29.2.1.tgz.sha256" +GIT_WALLARM_IMAGE_URL="${GIT_RAW_URL}/docker/images/wallarm-node-6.11.0-rc1.tar.gz" +GIT_WALLARM_CHECKSUM_URL="${GIT_RAW_URL}/docker/images/wallarm-node-6.11.0-rc1.tar.gz.sha256" + +# Local artifact directories (relative to script location) +LOCAL_BINARY_DIR="${SCRIPT_DIR}/binaries" +LOCAL_IMAGE_DIR="${SCRIPT_DIR}/images" + +DOCKER_VERSION="29.2.1" # Version from stealth deployment guide +WALLARM_IMAGE_TARGET="wallarm/node:6.11.0-rc1" + + + +# Deployment variables (set during execution) +CLOUD_REGION="" +API_HOST="" +INGRESS_PORT="" +MONITORING_PORT="" +UPSTREAM_IP="" +UPSTREAM_PORT="" +WALLARM_TOKEN="" +INSTANCE_NAME="" +INSTANCE_DIR="" + +# Resource reachability from check script +US_CLOUD_REACHABLE="false" +EU_CLOUD_REACHABLE="false" +REGISTRY_REACHABLE="false" +DOWNLOAD_REACHABLE="false" +GIT_REACHABLE="false" + +# ============================================================================== +# PREFLIGHT CHECK VERIFICATION +# ============================================================================== + +verify_preflight_check() { + log_message "INFO" "Verifying preflight check results..." + + if [ ! -f "$ENV_FILE" ]; then + log_message "ERROR" "Preflight check file not found: $ENV_FILE" + echo -e "\n${YELLOW}Preflight check has not been run or .env file is missing.${NC}" + echo -e "${YELLOW}Would you like to run the preflight check now?${NC}" + read -r -p "$(echo -e "${YELLOW}Run preflight check? (Y/n): ${NC}")" -n 1 + echo + if [[ ! $REPLY =~ ^[Nn]$ ]]; then + echo -e "${CYAN}Running preflight check...${NC}" + if ! "$SCRIPT_DIR/wallarm-ct-check.sh"; then + fail_with_remediation "Preflight check failed" \ + "Run the preflight check manually and fix any issues: +1. $SCRIPT_DIR/wallarm-ct-check.sh +2. Review the errors in $ENV_FILE +3. Fix the issues and run this script again" + fi + else + fail_with_remediation "Preflight check required" \ + "Run the preflight check before deployment: +1. $SCRIPT_DIR/wallarm-ct-check.sh +2. Review results in $ENV_FILE +3. Run this script again" + fi + fi + + # Load environment variables from .env file (from shared library) + if ! load_env_file "$ENV_FILE"; then + fail_with_remediation "Cannot load preflight results" \ + "The preflight check results file could not be read. +1. Run the preflight check: $SCRIPT_DIR/wallarm-ct-check.sh +2. Verify $ENV_FILE exists and is readable +3. Run this script again" + fi + + if [ "$CHECK_RESULT" != "pass" ]; then + log_message "ERROR" "Preflight check failed (result: $CHECK_RESULT)" + echo -e "\n${YELLOW}Preflight check found issues. Please review:${NC}" + echo -e "${YELLOW}1. Check file: $ENV_FILE${NC}" + echo -e "${YELLOW}2. Run: $SCRIPT_DIR/wallarm-ct-check.sh${NC}" + echo -e "${YELLOW}3. Fix the issues and try again${NC}" + exit 1 + fi + + log_message "SUCCESS" "Preflight check verified:" + log_message "SUCCESS" " OS: $OS_NAME $OS_VERSION" + log_message "SUCCESS" " Architecture: $ARCHITECTURE" + log_message "SUCCESS" " Init System: $INIT_SYSTEM" + log_message "SUCCESS" " US Cloud Reachable: $US_CLOUD_REACHABLE" + log_message "SUCCESS" " EU Cloud Reachable: $EU_CLOUD_REACHABLE" + log_message "SUCCESS" " Registry Reachable: $REGISTRY_REACHABLE" + log_message "SUCCESS" " Download Reachable: $DOWNLOAD_REACHABLE" + log_message "SUCCESS" " Git Repositorys Reachable: $GIT_REACHABLE" + + # Check for local artifact directories + if [ -d "$LOCAL_BINARY_DIR" ]; then + log_message "INFO" " Local binaries directory: $LOCAL_BINARY_DIR (exists)" + local binary_count=$(ls "$LOCAL_BINARY_DIR"/*.tgz 2>/dev/null | wc -l) + if [ "$binary_count" -gt 0 ]; then + log_message "INFO" " Found $binary_count Docker binary file(s)" + fi + else + log_message "INFO" " Local binaries directory: $LOCAL_BINARY_DIR (not found)" + fi + + if [ -d "$LOCAL_IMAGE_DIR" ]; then + log_message "INFO" " Local images directory: $LOCAL_IMAGE_DIR (exists)" + local image_count=$(ls "$LOCAL_IMAGE_DIR"/*.tar.gz 2>/dev/null | wc -l) + if [ "$image_count" -gt 0 ]; then + log_message "INFO" " Found $image_count Wallarm image file(s)" + fi + else + log_message "INFO" " Local images directory: $LOCAL_IMAGE_DIR (not found)" + fi + + # Validate we have at least one cloud region reachable + if [ "$US_CLOUD_REACHABLE" = "false" ] && [ "$EU_CLOUD_REACHABLE" = "false" ]; then + fail_with_remediation "No Wallarm cloud region reachable" \ + "Network connectivity issues detected: +1. Check firewall rules for Wallarm cloud endpoints +2. Verify network connectivity +3. Run preflight check again: $SCRIPT_DIR/wallarm-ct-check.sh" + fi + + # Validate we have resources for Docker/Wallarm + # Primary source: Git Repositorys; fallback: local directories + if [ "$GIT_REACHABLE" = "false" ]; then + log_message "WARNING" "Git Repositorys not reachable, checking for local resources..." + + local has_local_resources=true + + # Check local binaries directory first, then current directory as fallback + if [ ! -f "$LOCAL_BINARY_DIR/docker-29.2.1.tgz" ] && [ -z "$(ls docker-*.tgz 2>/dev/null)" ]; then + log_message "ERROR" "No local Docker binary found" + has_local_resources=false + fi + + # Check local images directory first, then current directory as fallback + if [ ! -f "$LOCAL_IMAGE_DIR/wallarm-node-6.11.0-rc1.tar.gz" ] && [ -z "$(ls wallarm-node-*.tar.gz wallarm-node-*.tar 2>/dev/null)" ]; then + log_message "ERROR" "No local Wallarm image found" + has_local_resources=false + fi + + if [ "$has_local_resources" = "false" ]; then + fail_with_remediation "Insufficient resources for deployment" \ + "Please provide either: +1. Network access to Git Repositorys: $GIT_RAW_URL +2. Local files in binaries/ and images/ directories: + - $LOCAL_BINARY_DIR/docker-29.2.1.tgz + - $LOCAL_IMAGE_DIR/wallarm-node-6.11.0-rc1.tar.gz +3. Local files in current directory as fallback: + - docker-*.tgz + - wallarm-node-*.tar.gz or wallarm-node-*.tar" + fi + else + log_message "SUCCESS" "Git Repositorys reachable - will download artifacts from $GIT_RAW_URL" + fi +} + +# ============================================================================== +# CONFIGURATION COLLECTION FUNCTIONS +# ============================================================================== +# select_cloud_region, validate_ip_address and check_port_available are provided +# by the shared library (../common/wallarm-lib.sh). + +collect_configuration() { + log_message "INFO" "Collecting deployment configuration..." + + # Get ingress port + local default_port=80 + local ingress_port="" + while [[ ! "$ingress_port" =~ ^[0-9]+$ ]] || [ "$ingress_port" -lt 1 ] || [ "$ingress_port" -gt 65535 ]; do + read -r -p "$(echo -e "${YELLOW}Enter inbound port [${default_port}]: ${NC}")" ingress_port + ingress_port="${ingress_port:-$default_port}" + + if [[ ! "$ingress_port" =~ ^[0-9]+$ ]]; then + echo -e "${RED}Port must be a number${NC}" + elif [ "$ingress_port" -lt 1 ] || [ "$ingress_port" -gt 65535 ]; then + echo -e "${RED}Port must be between 1 and 65535${NC}" + elif ! check_port_available "$ingress_port"; then + echo -e "${RED}Port $ingress_port is already in use${NC}" + ingress_port="" + fi + done + + # Calculate monitoring port (ingress + 10, check for conflicts) + local monitoring_port=$((ingress_port + 10)) + if ! check_port_available "$monitoring_port"; then + log_message "WARNING" "Port $monitoring_port is in use, choosing alternative..." + monitoring_port=$((ingress_port + 100)) + if ! check_port_available "$monitoring_port"; then + monitoring_port=$((ingress_port + 200)) + fi + fi + log_message "INFO" "Monitoring port will be: $monitoring_port" + + # Get application server details + local upstream_ip="" + local upstream_port="" + + echo -e "\n${CYAN}${BOLD}Application Server Configuration:${NC}" + echo -e "${YELLOW}Enter the IP/hostname and port of your backend application${NC}" + + while [[ -z "$upstream_ip" ]]; do + read -r -p "$(echo -e "${YELLOW}Upstream App IP/Hostname [127.0.0.1]: ${NC}")" upstream_ip + upstream_ip="${upstream_ip:-127.0.0.1}" + + # Validate IP/hostname format + if ! validate_ip_address "$upstream_ip" && \ + ! [[ "$upstream_ip" =~ ^[a-zA-Z0-9][a-zA-Z0-9.-]*[a-zA-Z0-9]$ ]]; then + echo -e "${RED}Invalid IP/hostname format${NC}" + upstream_ip="" + fi + done + + while [[ ! "$upstream_port" =~ ^[0-9]+$ ]] || [ "$upstream_port" -lt 1 ] || [ "$upstream_port" -gt 65535 ]; do + read -r -p "$(echo -e "${YELLOW}Upstream App Port [8080]: ${NC}")" upstream_port + upstream_port="${upstream_port:-8080}" + + if [[ ! "$upstream_port" =~ ^[0-9]+$ ]]; then + echo -e "${RED}Port must be a number${NC}" + elif [ "$upstream_port" -lt 1 ] || [ "$upstream_port" -gt 65535 ]; then + echo -e "${RED}Port must be between 1 and 65535${NC}" + fi + done + + # Verify application server reachability + log_message "INFO" "Verifying application server reachability..." + if timeout 5 bash -c "cat < /dev/null > /dev/tcp/$upstream_ip/$upstream_port" 2>/dev/null; then + log_message "SUCCESS" "Application server $upstream_ip:$upstream_port is reachable" + else + log_message "WARNING" "Application server $upstream_ip:$upstream_port is not reachable" + echo -e "${YELLOW}${BOLD}Warning:${NC} Cannot reach application server at $upstream_ip:$upstream_port" + echo -e "${YELLOW}This may cause the Wallarm node to fail. Possible reasons:${NC}" + echo -e "1. Application server is not running" + echo -e "2. Firewall blocking port $upstream_port" + echo -e "3. Wrong IP/hostname" + echo -e "4. Application server not listening on that port" + + read -r -p "$(echo -e "${YELLOW}Continue anyway? (y/N): ${NC}")" -n 1 + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + fail_with_remediation "Application server unreachable" \ + "Ensure your application server is accessible: +1. Start your application server +2. Check it's listening: sudo ss -tlnp | grep :$upstream_port +3. Verify firewall rules allow inbound connections +4. Test connectivity: telnet $upstream_ip $upstream_port +5. If using hostname, verify DNS resolution: nslookup $upstream_ip" + fi + fi + + # Get Wallarm node token + local wallarm_token="" + echo -e "\n${CYAN}${BOLD}Wallarm Node Token:${NC}" + echo -e "${YELLOW}Get your token from Wallarm Console:${NC}" + echo -e "Create a new 'Wallarm node' and copy the token (will be visible as you type)" + while [[ -z "$wallarm_token" ]]; do + read -r -p "$(echo -e "${YELLOW}Paste Wallarm Node Token: ${NC}")" wallarm_token + # Trim whitespace and newlines + wallarm_token=$(echo "$wallarm_token" | tr -d '[:space:]') + + if [[ -z "$wallarm_token" ]]; then + echo -e "${RED}Token cannot be empty${NC}" + elif [[ ! "$wallarm_token" =~ ^[A-Za-z0-9_+/=\-]+$ ]]; then + echo -e "${RED}Token contains invalid characters. Wallarm tokens are base64 strings (A-Z, a-z, 0-9, _, -, +, /, =)${NC}" + echo -e "${YELLOW}First 20 chars of what you entered: '${wallarm_token:0:20}...'${NC}" + wallarm_token="" + else + # Show confirmation of token length (but not full token for security) + token_length=${#wallarm_token} + echo -e "${GREEN}Token accepted (${token_length} characters).${NC}" + echo -e "${YELLOW}First 8 chars for verification: ${wallarm_token:0:8}...${NC}" + fi + done + + # Get trusted proxy IPs for real IP configuration + local trusted_proxies="" + echo -e "\n${CYAN}${BOLD}Real Client IP Configuration:${NC}" + echo -e "${YELLOW}For Wallarm to see the real client IP, specify the IP address(es) of trusted proxies" + echo -e "(e.g., load balancers, firewalls, CDNs) that forward traffic to this node.${NC}" + echo -e "${YELLOW}You can enter:${NC}" + echo -e " - Single IP: 10.0.0.10" + echo -e " - CIDR range: 10.0.0.0/24" + echo -e " - Multiple entries separated by spaces: 10.0.0.10 10.0.1.0/24 192.168.1.1" + echo -e "${YELLOW}If unsure, you can leave empty and configure later${NC}" + + read -r -p "$(echo -e "${YELLOW}Trusted proxy IPs/CIDRs (space-separated): ${NC}")" trusted_proxies_input + + # Validate and clean up the input + local validated_proxies=() + if [[ -n "$trusted_proxies_input" ]]; then + # Split input by spaces + IFS=' ' read -ra proxy_array <<< "$trusted_proxies_input" + + for proxy in "${proxy_array[@]}"; do + # Trim whitespace + proxy=$(echo "$proxy" | xargs) + if [[ -n "$proxy" ]]; then + # Validate IP or CIDR + if [[ "$proxy" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}(/[0-9]{1,2})?$ ]]; then + # Basic IP format validation + IFS='/' read -r ip cidr <<< "$proxy" + IFS='.' read -r o1 o2 o3 o4 <<< "$ip" + if [[ $o1 -le 255 && $o2 -le 255 && $o3 -le 255 && $o4 -le 255 ]]; then + if [[ -z "$cidr" ]] || [[ $cidr -ge 0 && $cidr -le 32 ]]; then + validated_proxies+=("$proxy") + else + echo -e "${RED}Invalid CIDR prefix length for $proxy (must be 0-32)${NC}" + fi + else + echo -e "${RED}Invalid IP octets in $proxy${NC}" + fi + else + echo -e "${RED}Invalid IP/CIDR format: $proxy${NC}" + echo -e "${YELLOW}Example valid formats: 10.0.0.10, 10.0.0.0/24, 192.168.1.1${NC}" + fi + fi + done + + if [[ ${#validated_proxies[@]} -eq 0 ]]; then + echo -e "${YELLOW}No valid proxy IPs provided. Will skip set_real_ip_from configuration.${NC}" + echo -e "${YELLOW}You can manually edit /etc/nginx/conf.d/wallarm.conf later to add set_real_ip_from directives.${NC}" + trusted_proxies="" + else + trusted_proxies="${validated_proxies[*]}" + echo -e "${GREEN}Trusted proxies configured: $trusted_proxies${NC}" + fi + else + echo -e "${YELLOW}No trusted proxies specified. The node will see the last hop IP only.${NC}" + echo -e "${YELLOW}For real client IP detection, you'll need to manually configure set_real_ip_from in nginx config.${NC}" + fi + + # Generate instance name and directory + local instance_name + instance_name="wallarm-$(hostname -s | tr '[:upper:]' '[:lower:]')-$(date +%Y%m%d)" + local instance_dir="/opt/$instance_name" + + # Ensure directory exists + sudo mkdir -p "$instance_dir" + + log_message "SUCCESS" "Configuration collected:" + log_message "SUCCESS" " Ingress Port: $ingress_port" + log_message "SUCCESS" " Monitoring Port: $monitoring_port" + log_message "SUCCESS" " Upstream: $upstream_ip:$upstream_port" + if [[ -n "$trusted_proxies" ]]; then + log_message "SUCCESS" " Trusted Proxies: $trusted_proxies" + else + log_message "INFO" " Trusted Proxies: Not configured (will need manual setup)" + fi + log_message "SUCCESS" " Instance: $instance_name" + log_message "SUCCESS" " Directory: $instance_dir" + + # Set global variables + INGRESS_PORT="$ingress_port" + MONITORING_PORT="$monitoring_port" + UPSTREAM_IP="$upstream_ip" + UPSTREAM_PORT="$upstream_port" + WALLARM_TOKEN="$wallarm_token" + INSTANCE_NAME="$instance_name" + INSTANCE_DIR="$instance_dir" + TRUSTED_PROXIES="$trusted_proxies" +} + +# ============================================================================== +# DOCKER ENGINE SETUP (LXC OPTIMIZED) +# ============================================================================== + +setup_docker_engine() { + log_message "INFO" "Setting up Docker Engine for LXC/stealth deployment..." + + # Check if Docker is already installed and running + if command -v docker >/dev/null 2>&1 && sudo docker info >/dev/null 2>&1; then + local docker_version + docker_version=$(docker --version | cut -d' ' -f3 | tr -d ',') + log_message "SUCCESS" "Docker is already installed and running (version $docker_version)" + + # Check if Docker is configured for LXC + if sudo docker info 2>/dev/null | grep -q "Storage Driver: vfs"; then + log_message "SUCCESS" "Docker is already configured with VFS storage driver (LXC compatible)" + else + log_message "WARNING" "Docker is not using VFS storage driver. LXC compatibility may be limited." + fi + return 0 + fi + + log_message "INFO" "Docker not found or not running. Proceeding with installation..." + + # Determine binary source (priority: Git Repositorys -> local dir -> current dir) + local binary_file="docker-$DOCKER_VERSION.tgz" + local binary_path="" + + # 1. Try Git Repositorys download (primary source) + log_message "INFO" "Attempting to download Docker binary from Git Repositorys..." + if download_from_git "$GIT_DOCKER_BINARY_URL" "$binary_file" "Docker binary"; then + if verify_checksum "$binary_file" "$GIT_DOCKER_CHECKSUM_URL" "Docker binary"; then + binary_path="$binary_file" + log_message "SUCCESS" "Docker binary downloaded from Git Repositorys and checksum verified" + else + log_message "WARNING" "Git Repositorys Docker binary checksum verification failed, trying other sources" + # Remove corrupted download + rm -f "$binary_file" + fi + fi + + # 2. Check local binaries directory + if [ -z "$binary_path" ] && [ -d "$LOCAL_BINARY_DIR" ]; then + log_message "INFO" "Checking local binaries directory: $LOCAL_BINARY_DIR" + local local_binary="$LOCAL_BINARY_DIR/docker-29.2.1.tgz" + local local_checksum="$LOCAL_BINARY_DIR/docker-29.2.1.tgz.sha256" + + if [ -f "$local_binary" ]; then + log_message "INFO" "Found local Docker binary: $local_binary" + # Copy to current directory for consistency with extraction logic + cp "$local_binary" "$binary_file" + if verify_checksum "$binary_file" "$local_checksum" "local Docker binary"; then + binary_path="$binary_file" + log_message "SUCCESS" "Using local Docker binary from binaries directory" + else + log_message "WARNING" "Local Docker binary checksum verification failed" + rm -f "$binary_file" + fi + fi + fi + + # 3. Check current directory for any docker-*.tgz (existing fallback) + if [ -z "$binary_path" ]; then + log_message "INFO" "Checking current directory for Docker binaries..." + local local_files + local_files=$(ls docker-*.tgz 2>/dev/null | head -1) + if [ -n "$local_files" ]; then + binary_path="$local_files" + log_message "SUCCESS" "Using local Docker binary: $binary_path" + # Optional: Try to verify checksum if .sha256 file exists + local checksum_file="${local_files}.sha256" + if [ -f "$checksum_file" ]; then + if verify_checksum "$binary_path" "$checksum_file" "Docker binary"; then + log_message "SUCCESS" "Local Docker binary checksum verified" + else + log_message "WARNING" "Local Docker binary checksum verification failed, but continuing" + fi + fi + fi + fi + + + + # 5. Final fallback: no binary available + if [ -z "$binary_path" ]; then + fail_with_remediation "No Docker binary available" \ + "Please provide a Docker static binary using one of these methods: +1. Git Repositorys (primary): Ensure network access to $GIT_RAW_URL +2. Local binaries directory: Place docker-29.2.1.tgz and .sha256 in $LOCAL_BINARY_DIR/ +3. Current directory: Place any docker-*.tgz file in current directory + + +Re-run the script after providing the binary." + fi + + # Extract and install + log_message "INFO" "Extracting Docker binary..." + + # First verify the tar file exists and is readable + if [ ! -f "$binary_path" ]; then + fail_with_remediation "Docker binary file not found" \ + "File $binary_path does not exist. Check download and file permissions." + fi + + if [ ! -r "$binary_path" ]; then + fail_with_remediation "Docker binary file not readable" \ + "Cannot read file $binary_path. Check file permissions." + fi + + # Test if it's a valid tar archive + log_message "DEBUG" "Testing tar archive integrity..." + + # First check if we can read the file + if [ ! -r "$binary_path" ]; then + log_message "ERROR" "Cannot read file: $binary_path" + log_message "INFO" "File permissions: $(ls -la "$binary_path" 2>/dev/null || echo "cannot stat")" + fail_with_remediation "Cannot read Docker binary file" \ + "File $binary_path exists but is not readable. +1. Check file permissions: ls -la $binary_path +2. Fix permissions: chmod 644 $binary_path +3. Or download fresh copy" + fi + + # Try to get file type information + local file_type="unknown" + if command -v file >/dev/null 2>&1; then + file_type=$(file "$binary_path" 2>/dev/null || echo "file command failed") + elif command -v hexdump >/dev/null 2>&1; then + # Check magic bytes manually + local magic_bytes=$(hexdump -n 2 -C "$binary_path" 2>/dev/null | head -1 | cut -d' ' -f2-3 || echo "no magic") + file_type="hexdump: $magic_bytes" + fi + + log_message "INFO" "File info: $binary_path ($(stat -c%s "$binary_path") bytes)" + log_message "INFO" "File type: $file_type" + log_message "INFO" "Current directory: $(pwd)" + log_message "INFO" "Full path: $(readlink -f "$binary_path" 2>/dev/null || echo "$binary_path")" + + # Test tar archive with error capture + local tar_test_output + tar_test_output=$(tar -tzf "$binary_path" 2>&1) + local tar_test_exit=$? + + if [ $tar_test_exit -ne 0 ]; then + log_message "ERROR" "File $binary_path is not a valid tar.gz archive (tar exit: $tar_test_exit)" + log_message "DEBUG" "Tar test output: $tar_test_output" + + # Check if it might be a different compression format + log_message "INFO" "Checking for alternative compression formats..." + + # Try gunzip test + if command -v gunzip >/dev/null 2>&1; then + if gunzip -t "$binary_path" 2>/dev/null; then + log_message "WARNING" "File is valid gzip but tar can't read it" + else + log_message "INFO" "Not a valid gzip file either" + fi + fi + + # Check first few bytes + if command -v xxd >/dev/null 2>&1; then + log_message "DEBUG" "First 20 bytes: $(xxd -l 20 "$binary_path" 2>/dev/null || echo "cannot read")" + elif command -v od >/dev/null 2>&1; then + log_message "DEBUG" "First 20 bytes: $(od -x -N 20 "$binary_path" 2>/dev/null | head -2 || echo "cannot read")" + fi + + fail_with_remediation "Docker binary file is corrupted or invalid" \ + "The Docker binary file is not a valid tar.gz archive. +Tar error: $tar_test_output + +File info: $(stat -c%s "$binary_path") bytes, type: $file_type + +Possible solutions: +1. The download may have been interrupted or corrupted +2. The file may be in wrong format (not tar.gz) +3. Server might be serving wrong content + +Steps to fix: +1. Delete corrupted file: rm -f docker-*.tgz +2. Check disk space: df -h . +3. Try alternative sources: + a) Git Repositorys: curl -L '$GIT_DOCKER_BINARY_URL' -o docker.tgz + b) Local directory: Check $LOCAL_BINARY_DIR/docker-29.2.1.tgz + +4. Verify downloaded file: file test.tgz && tar -tzf test.tgz +5. Check if tar command works: tar --version" + fi + + log_message "SUCCESS" "Tar archive validation passed" + + # Extract the archive + log_message "DEBUG" "Extracting files from $binary_path..." + local tar_output + tar_output=$(tar xzvf "$binary_path" 2>&1) + local tar_exit=$? + + if [ $tar_exit -ne 0 ]; then + log_message "ERROR" "Failed to extract files from $binary_path (exit code: $tar_exit)" + log_message "DEBUG" "Tar output: $tar_output" + log_message "INFO" "Checking extracted files..." + if [ -d "docker" ]; then + log_message "WARNING" "Some files were extracted to 'docker/' directory" + ls -la docker/ 2>/dev/null | head -10 || true + fi + fail_with_remediation "Failed to extract Docker binary" \ + "Extraction failed. Possible reasons: +1. Insufficient disk space: df -h . +2. Permission issues in current directory +3. Corrupted archive (partial download) +4. File system issues + +Tar error: $tar_output + +Check disk space and permissions, then try manual extraction: + tar xzvf $binary_path" + else + log_message "SUCCESS" "Docker binary extracted successfully" + fi + + log_message "INFO" "Installing Docker binaries to /usr/bin/" + sudo cp docker/* /usr/bin/ 2>/dev/null || { + fail_with_remediation "Failed to copy Docker binaries" \ + "Permission denied copying to /usr/bin/ +1. Ensure you have sudo privileges +2. Check disk space: df -h / +3. Manual installation: + sudo cp docker/* /usr/bin/" + } + + # Ensure binaries are executable + log_message "INFO" "Setting executable permissions on Docker binaries..." + sudo chmod +x /usr/bin/dockerd /usr/bin/docker 2>/dev/null || { + log_message "WARNING" "Could not set executable permissions on Docker binaries" + } + + # Verify Docker binaries work + log_message "INFO" "Verifying Docker binaries..." + if ! sudo /usr/bin/dockerd --version 2>/dev/null; then + fail_with_remediation "Docker binary verification failed" \ +"Docker binary (/usr/bin/dockerd) appears to be corrupted or incompatible. +The binary was extracted from $binary_path but doesn't run. + +Check the binary: + sudo file /usr/bin/dockerd + sudo ls -la /usr/bin/dockerd + sudo /usr/bin/dockerd --version + +The Docker static binary might be for wrong architecture or corrupted. +Try downloading manually from one of these sources: +1. Git Repositorys: curl -L '$GIT_DOCKER_BINARY_URL' -o docker.tgz +2. Local directory: Check $LOCAL_BINARY_DIR/docker-29.2.1.tgz + + +Then extract and install: + tar xzvf docker.tgz + sudo cp docker/* /usr/bin/" + else + local docker_version + docker_version=$(sudo /usr/bin/dockerd --version 2>&1 | head -1) + log_message "SUCCESS" "Docker binary verified: $docker_version" + fi + + # Cleanup extracted directory + log_message "INFO" "Cleaning up extracted Docker binaries directory..." + rm -rf docker + log_message "INFO" "Cleanup completed" + + # DEBUG: Mark start of docker group section + log_message "INFO" "=== Starting docker group creation ===" + + # Create docker group (required for systemd socket configuration and dockerd --group) + log_message "INFO" "Creating docker group for Docker socket access..." + + # Check if group already exists + log_message "INFO" "Checking if docker group exists..." + local getent_output + if getent_output=$(getent group docker 2>&1); then + getent_exit=0 + else + getent_exit=$? + fi + log_message "INFO" "getent group docker result: exit=$getent_exit, output='$getent_output'" + + if [ $getent_exit -eq 0 ]; then + log_message "SUCCESS" "Docker group already exists: $getent_output" + else + # Attempt to create docker group with error capture + log_message "INFO" "Attempting to create docker group with sudo groupadd docker..." + local groupadd_output + if groupadd_output=$(sudo groupadd docker 2>&1); then + groupadd_exit=0 + else + groupadd_exit=$? + fi + + if [ $groupadd_exit -eq 0 ]; then + log_message "SUCCESS" "Created docker group" + else + log_message "ERROR" "Failed to create docker group (exit code: $groupadd_exit)" + log_message "INFO" "groupadd command output: $groupadd_output" + + # Check if group was somehow created despite error + log_message "INFO" "Checking if docker group was created despite groupadd failure..." + local check_getent_output + if check_getent_output=$(getent group docker 2>&1); then + check_getent_exit=0 + else + check_getent_exit=$? + fi + log_message "INFO" "Post-failure check: exit=$check_getent_exit, output='$check_getent_output'" + + if [ $check_getent_exit -eq 0 ]; then + log_message "WARNING" "Docker group exists despite groupadd failure, continuing..." + else + fail_with_remediation "Cannot create docker group" \ +"The docker group is required for Docker socket access. Please create it manually: + +1. Check if groupadd command is available: which groupadd +2. Check permissions: sudo -v +3. Manual group creation: sudo groupadd docker +4. Verify: getent group docker + +If groupadd fails, you may need to: +- Check system user/group database +- Use alternative: sudo addgroup docker (Debian/Ubuntu) +- Edit /etc/group manually (advanced users only)" + fi + fi + fi + + # Final verification that docker group exists + log_message "INFO" "Final verification of docker group existence..." + local final_getent_output + if final_getent_output=$(getent group docker 2>&1); then + final_getent_exit=0 + else + final_getent_exit=$? + fi + log_message "INFO" "Final getent result: exit=$final_getent_exit, output='$final_getent_output'" + + if [ $final_getent_exit -ne 0 ]; then + fail_with_remediation "Docker group verification failed" \ +"The docker group does not exist after creation attempts. This will cause Docker startup to fail. + +Please create the docker group manually and re-run the script: +1. sudo groupadd docker +2. Verify: getent group docker | grep docker +3. Re-run this script" + fi + + # Log group details for debugging + local docker_gid + docker_gid=$(echo "$final_getent_output" | cut -d: -f3) + log_message "INFO" "Docker group details: GID=$docker_gid" + + log_message "SUCCESS" "Docker group verified and ready (GID: $docker_gid)" + + # DEBUG: Mark end of docker group section + log_message "INFO" "=== Finished docker group creation ===" + + # Configure Docker daemon for LXC (VFS storage driver, cgroupfs) + log_message "INFO" "Configuring Docker daemon for LXC (VFS storage driver, cgroupfs)..." + + # Create docker configuration directory + sudo mkdir -p /etc/docker + + # Create daemon.json for LXC optimization + sudo tee /etc/docker/daemon.json > /dev/null < /dev/null <<'EOF' +[Unit] +Description=Docker Engine +After=network-online.target firewalld.service containerd.service +Wants=network-online.target +Requires=docker.socket + +[Service] +Type=notify +ExecStart=/usr/bin/dockerd --group docker +ExecReload=/bin/kill -s HUP $MAINPID +TimeoutSec=0 +RestartSec=2 +Restart=always +StartLimitBurst=3 +StartLimitInterval=60s +LimitNOFILE=infinity +LimitNPROC=infinity +LimitCORE=infinity +TasksMax=infinity +Delegate=yes +KillMode=process + +[Install] +WantedBy=multi-user.target +EOF + + sudo tee /etc/systemd/system/docker.socket > /dev/null <<'EOF' +[Unit] +Description=Docker Socket for the API + +[Socket] +ListenStream=/var/run/docker.sock +SocketMode=0660 +SocketUser=root +SocketGroup=docker + +[Install] +WantedBy=sockets.target +EOF + + sudo systemctl daemon-reload + + log_message "INFO" "Enabling Docker service to start on boot..." + if ! sudo systemctl enable docker; then + log_message "ERROR" "systemctl enable docker failed with exit code: $?" + fail_with_remediation "Failed to enable Docker service" \ +"Docker service could not be enabled to start on boot. Common causes: +1. Docker socket unit (docker.socket) has configuration errors +2. The docker group may not exist +3. Systemd unit file has syntax errors + +Check docker.socket status: + sudo systemctl status docker.socket --no-pager + +Verify docker group exists: + getent group docker + +Check systemd unit files: + sudo systemctl cat docker.socket + sudo systemctl cat docker.service" + fi + + log_message "INFO" "Starting Docker service (systemd)..." + # Start the service and capture exit code + if sudo systemctl start docker; then + start_exit=0 + else + start_exit=$? + fi + + # Give Docker a moment to start or fail + sleep 2 + + # Check if service is actually active + if ! sudo systemctl is-active docker --quiet; then + log_message "ERROR" "Docker service failed to start (systemctl start exit: $start_exit)" + log_message "INFO" "Checking docker.socket status..." + sudo systemctl status docker.socket --no-pager 2>&1 | head -20 || true + + log_message "INFO" "Checking docker.service status..." + sudo systemctl status docker.service --no-pager 2>&1 | head -30 || true + + log_message "INFO" "Checking Docker daemon logs..." + sudo journalctl -u docker --no-pager -n 30 2>&1 | head -50 || true + + log_message "INFO" "Checking Docker socket logs..." + sudo journalctl -u docker.socket --no-pager -n 20 2>&1 | head -30 || true + + fail_with_remediation "Failed to start Docker service" \ +"Docker service failed to start. Common causes: +1. Missing iptables (Docker static binaries require iptables v1.4+ for network bridge) +2. Docker daemon configuration error (check /etc/docker/daemon.json) +3. Storage driver issues (VFS may not be compatible) +4. Cgroup configuration problems +5. Port conflicts or resource limits + +Latest Docker daemon logs: +$(sudo journalctl -u docker --no-pager -n 30 2>&1 | tail -20) + +Check Docker configuration: + sudo cat /etc/docker/daemon.json + +Verify iptables is installed: + which iptables || echo 'iptables not found' + iptables --version 2>/dev/null || echo 'Cannot check version' + +Install iptables if missing: + # Debian/Ubuntu: sudo apt-get update && sudo apt-get install -y iptables + # RHEL/CentOS: sudo yum install -y iptables + # Alpine: sudo apk add iptables + +Verify docker group exists: + getent group docker + +Manual start attempt for debugging: + sudo dockerd --group docker --debug" + fi + ;; + + "openrc") + # Alpine OpenRC configuration + sudo tee /etc/init.d/docker > /dev/null <<'EOF' +#!/sbin/openrc-run +description="Docker Engine" +command="/usr/bin/dockerd" +command_args="--group docker" +pidfile="/run/docker.pid" +command_background=true + +depend() { + need net + after firewall +} +EOF + + sudo chmod +x /etc/init.d/docker + sudo rc-update add docker default + + log_message "INFO" "Starting Docker service (OpenRC)..." + if ! sudo rc-service docker start; then + log_message "ERROR" "rc-service docker start failed with exit code: $?" + fail_with_remediation "Failed to start Docker service (OpenRC)" \ +"Docker service failed to start under OpenRC. Common causes: +1. Missing iptables (Docker static binaries require iptables v1.4+ for network bridge) +2. Docker socket or port conflicts +3. Missing dependencies +4. Docker configuration errors + +Check OpenRC logs: + sudo rc-service docker status + sudo cat /var/log/docker.log 2>/dev/null || echo "No docker.log found" + +Verify iptables is installed: + which iptables || echo 'iptables not found' + iptables --version 2>/dev/null || echo 'Cannot check version' + +Install iptables if missing: + # Alpine: sudo apk add iptables + # Debian/Ubuntu: sudo apt-get update && sudo apt-get install -y iptables + # RHEL/CentOS: sudo yum install -y iptables + +Verify docker group exists: + getent group docker + +Manual start attempt: + sudo dockerd --group docker --debug" + fi + ;; + + "sysvinit") + # Traditional SysV init script + sudo tee /etc/init.d/docker > /dev/null <<'EOF' +#!/bin/bash +### BEGIN INIT INFO +# Provides: docker +# Required-Start: $local_fs $network $remote_fs +# Required-Stop: $local_fs $network $remote_fs +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Docker Engine +# Description: Docker container runtime +### END INIT INFO + +DESC="Docker Engine" +DAEMON=/usr/bin/dockerd +DAEMON_ARGS="--group docker" +PIDFILE=/var/run/docker.pid +SCRIPTNAME=/etc/init.d/docker + +[ -x "$DAEMON" ] || exit 0 + +. /lib/lsb/init-functions + +case "$1" in + start) + log_daemon_msg "Starting $DESC" "docker" + start-stop-daemon --start --background --pidfile "$PIDFILE" \ + --exec "$DAEMON" -- $DAEMON_ARGS + log_end_msg $? + ;; + stop) + log_daemon_msg "Stopping $DESC" "docker" + start-stop-daemon --stop --pidfile "$PIDFILE" --retry 10 + log_end_msg $? + ;; + restart) + $0 stop + sleep 1 + $0 start + ;; + status) + status_of_proc -p "$PIDFILE" "$DAEMON" docker + ;; + *) + echo "Usage: $SCRIPTNAME {start|stop|restart|status}" + exit 3 + ;; +esac + +exit 0 +EOF + + sudo chmod +x /etc/init.d/docker + sudo update-rc.d docker defaults + + log_message "INFO" "Starting Docker service (SysV init)..." + if ! sudo service docker start; then + log_message "ERROR" "service docker start failed with exit code: $?" + fail_with_remediation "Failed to start Docker service (SysV init)" \ +"Docker service failed to start under SysV init. Common causes: +1. Missing iptables (Docker static binaries require iptables v1.4+ for network bridge) +2. Docker socket or port conflicts +3. Missing dependencies or configuration errors +4. The docker group may not exist or be accessible + +Check service status: + sudo service docker status + +Verify iptables is installed: + which iptables || echo 'iptables not found' + iptables --version 2>/dev/null || echo 'Cannot check version' + +Install iptables if missing: + # Debian/Ubuntu: sudo apt-get update && sudo apt-get install -y iptables + # RHEL/CentOS: sudo yum install -y iptables + # Alpine: sudo apk add iptables + +Verify docker group exists: + getent group docker + +Check for Docker logs: + sudo dockerd --group docker --debug 2>&1 | head -50" + fi + ;; + + *) + log_message "WARNING" "Unknown init system '$INIT_SYSTEM', trying systemd defaults" + sudo systemctl daemon-reload 2>/dev/null || true + sudo systemctl enable docker 2>/dev/null || true + sudo systemctl start docker 2>/dev/null || { + log_message "ERROR" "Failed to start Docker with unknown init system" + echo -e "${YELLOW}Please start Docker manually and re-run the script${NC}" + exit 1 + } + ;; + esac + + # Verify Docker is running + log_message "INFO" "Verifying Docker service..." + sleep 3 # Give Docker time to start + + if ! sudo docker info >/dev/null 2>&1; then + fail_with_remediation "Docker failed to start" \ + "Docker installation completed but service failed to start: +1. Check Docker logs: journalctl -u docker (systemd) or /var/log/docker.log +2. Verify iptables is installed (Docker static binaries require iptables v1.4+): + which iptables || echo 'iptables not found' + iptables --version 2>/dev/null || echo 'Cannot check version' +3. Install iptables if missing: + # Debian/Ubuntu: sudo apt-get update && sudo apt-get install -y iptables + # RHEL/CentOS: sudo yum install -y iptables + # Alpine: sudo apk add iptables +4. Verify configuration: sudo dockerd --debug +5. Manual start: sudo dockerd --group docker &" + fi + + # Verify Docker is using VFS storage driver + log_message "INFO" "Verifying Docker storage driver configuration..." + if sudo docker info 2>/dev/null | grep -q "Storage Driver: vfs"; then + log_message "SUCCESS" "Docker configured with VFS storage driver (LXC compatible)" + else + log_message "WARNING" "Docker is not using VFS storage driver. Checking current driver..." + sudo docker info 2>/dev/null | grep "Storage Driver:" || log_message "ERROR" "Could not determine storage driver" + log_message "WARNING" "LXC compatibility may be limited without VFS storage driver" + fi + + # Add current user to docker group for passwordless docker commands + log_message "INFO" "Adding current user to docker group..." + + # Security notice: docker group grants root-equivalent privileges + echo -e "${YELLOW}${BOLD}Security Notice:${NC} Adding your user to the 'docker' group grants root-equivalent privileges." + echo -e "${YELLOW}Any user in the docker group can run commands as root on the host system.${NC}" + echo -e "${YELLOW}Only proceed if you understand and accept this security risk.${NC}" + + read -r -p "$(echo -e "${YELLOW}Add $(whoami) to docker group? (Y/n): ${NC}")" -n 1 + echo + if [[ ! $REPLY =~ ^[Nn]$ ]]; then + sudo usermod -aG docker "$(whoami)" 2>/dev/null && \ + log_message "SUCCESS" "Added $(whoami) to docker group (log out and back in for changes)" + else + log_message "WARNING" "Skipping docker group addition. You will need to use sudo for docker commands." + echo -e "${YELLOW}Note: You can manually add yourself to docker group later with:${NC}" + echo -e "${CYAN} sudo usermod -aG docker $(whoami)${NC}" + echo -e "${YELLOW}Then log out and back in for changes to take effect.${NC}" + fi + + log_message "SUCCESS" "Docker Engine setup completed successfully" +} + +# ============================================================================== +# WALLARM NODE DEPLOYMENT +# ============================================================================== + +deploy_wallarm_node() { + log_message "INFO" "Deploying Wallarm filtering node..." + + # Load Wallarm Docker image (priority: Git Repositorys -> local dir -> current dir) + log_message "INFO" "Loading Wallarm Docker image..." + local image_loaded=false + + # 1. Try Git Repositorys download (primary source) + local git_image_file="wallarm-node-6.11.0-rc1.tar.gz" + if [ "$image_loaded" = "false" ]; then + log_message "INFO" "Attempting to download Wallarm image from Git Repositorys..." + if download_from_git "$GIT_WALLARM_IMAGE_URL" "$git_image_file" "Wallarm Docker image"; then + if verify_checksum "$git_image_file" "$GIT_WALLARM_CHECKSUM_URL" "Wallarm Docker image"; then + log_message "INFO" "Loading Wallarm image from Git Repositorys download..." + if gunzip -c "$git_image_file" | sudo docker load; then + log_message "SUCCESS" "Wallarm image loaded from Git Repositorys download" + image_loaded=true + else + log_message "ERROR" "Failed to load Wallarm image from Git Repositorys download" + fi + # Cleanup downloaded file + rm -f "$git_image_file" + else + log_message "WARNING" "Git Repositorys Wallarm image checksum verification failed" + rm -f "$git_image_file" + fi + fi + fi + + # 2. Check local images directory + if [ "$image_loaded" = "false" ] && [ -d "$LOCAL_IMAGE_DIR" ]; then + log_message "INFO" "Checking local images directory: $LOCAL_IMAGE_DIR" + local local_image="$LOCAL_IMAGE_DIR/wallarm-node-6.11.0-rc1.tar.gz" + local local_checksum="$LOCAL_IMAGE_DIR/wallarm-node-6.11.0-rc1.tar.gz.sha256" + + if [ -f "$local_image" ]; then + log_message "INFO" "Found local Wallarm image: $local_image" + if verify_checksum "$local_image" "$local_checksum" "local Wallarm image"; then + log_message "INFO" "Loading Wallarm image from local directory..." + if gunzip -c "$local_image" | sudo docker load; then + log_message "SUCCESS" "Wallarm image loaded from local directory" + image_loaded=true + else + log_message "ERROR" "Failed to load Wallarm image from local directory" + fi + else + log_message "WARNING" "Local Wallarm image checksum verification failed" + fi + fi + fi + + # 3. Check current directory for compressed image (tar.gz) + if [ "$image_loaded" = "false" ]; then + log_message "INFO" "Checking current directory for Wallarm image (tar.gz)..." + local gz_image + gz_image=$(ls wallarm-node-*.tar.gz 2>/dev/null | head -1) + if [ -n "$gz_image" ]; then + log_message "INFO" "Found compressed Wallarm image: $gz_image" + # Verify checksum if .sha256 file exists + local checksum_file="${gz_image}.sha256" + if [ -f "$checksum_file" ]; then + if ! verify_checksum "$gz_image" "$checksum_file" "Wallarm image"; then + log_message "WARNING" "Wallarm image checksum verification failed, but attempting load anyway" + fi + fi + log_message "INFO" "Loading compressed Wallarm image..." + if gunzip -c "$gz_image" | sudo docker load; then + log_message "SUCCESS" "Wallarm image loaded from compressed file" + image_loaded=true + else + log_message "ERROR" "Failed to load Wallarm image from $gz_image" + fi + fi + fi + + # 4. Check current directory for uncompressed image (tar) - existing fallback + if [ "$image_loaded" = "false" ]; then + log_message "INFO" "Checking current directory for Wallarm image (tar)..." + local tar_image + tar_image=$(ls wallarm-node-*.tar 2>/dev/null | head -1) + if [ -n "$tar_image" ]; then + log_message "INFO" "Found uncompressed Wallarm image: $tar_image" + if ! sudo docker load -i "$tar_image"; then + log_message "ERROR" "Failed to load Wallarm image from $tar_image" + else + log_message "SUCCESS" "Wallarm image loaded from uncompressed file" + image_loaded=true + fi + fi + fi + + + + # 6. Final fallback: no image available + if [ "$image_loaded" = "false" ]; then + fail_with_remediation "No Wallarm image available" \ + "Please provide a Wallarm Docker image using one of these methods: +1. Git Repositorys (primary): Ensure network access to $GIT_RAW_URL +2. Local images directory: Place wallarm-node-6.11.0-rc1.tar.gz and .sha256 in $LOCAL_IMAGE_DIR/ +3. Current directory: Place wallarm-node-*.tar.gz or wallarm-node-*.tar file in current directory + + Save for offline use: docker save $WALLARM_IMAGE_TARGET -o wallarm-node-latest.tar + +Re-run the script after providing the image." + fi + + # Ensure image is tagged with standard name (for consistency) + if [ "$image_loaded" = "true" ] && [ "$REGISTRY_REACHABLE" = "false" ]; then + # If we loaded from local file, tag the loaded image with standard name + local loaded_image_id + loaded_image_id=$(sudo docker images --format "{{.ID}}" --filter "dangling=false" | head -1) + if [ -n "$loaded_image_id" ]; then + sudo docker tag "$loaded_image_id" "$WALLARM_IMAGE_TARGET" + log_message "INFO" "Tagged loaded image as $WALLARM_IMAGE_TARGET" + fi + fi + +# Create nginx configuration +log_message "INFO" "Creating nginx configuration..." +local nginx_config="$INSTANCE_DIR/nginx.conf" + +# Start building the configuration file +sudo tee "$nginx_config" > /dev/null < /dev/null < /dev/null < /dev/null < /dev/null <> "\$LOG_FILE" + +# Stop existing container if running +sudo docker stop "\$CONTAINER_NAME" 2>/dev/null || true +sudo docker rm "\$CONTAINER_NAME" 2>/dev/null || true + +# Start new container +sudo docker run -d \\ + --name "\$CONTAINER_NAME" \\ + --restart always \\ + -p $INGRESS_PORT:80 \\ + -p $MONITORING_PORT:90 \\ + -e WALLARM_API_TOKEN="$WALLARM_TOKEN" \\ + -e WALLARM_API_HOST="$API_HOST" \\ + -e NGINX_BACKEND="$UPSTREAM_IP:$UPSTREAM_PORT" \\ + -e WALLARM_MODE="monitoring" \\ + -v "\$NGINX_CONFIG:/etc/nginx/http.d/default.conf:ro" \\ + $WALLARM_IMAGE_TARGET + +echo "\$(date) - Container started with ID: \$(sudo docker ps -q -f name=\$CONTAINER_NAME)" >> "\$LOG_FILE" + +# Verify container is running +sleep 3 +if sudo docker ps | grep -q "\$CONTAINER_NAME"; then + echo "\$(date) - Verification: Container is running" >> "\$LOG_FILE" + echo "Wallarm node \$CONTAINER_NAME started successfully" +else + echo "\$(date) - ERROR: Container failed to start" >> "\$LOG_FILE" + sudo docker logs "\$CONTAINER_NAME" >> "\$LOG_FILE" 2>&1 + exit 1 +fi +EOF + + sudo chmod +x "$start_script" + log_message "SUCCESS" "Start script created: $start_script" + + # Create init system service for automatic startup + log_message "INFO" "Creating service for automatic startup (init system: $INIT_SYSTEM)..." + + case "$INIT_SYSTEM" in + "systemd") + local service_file="/etc/systemd/system/wallarm-$INSTANCE_NAME.service" + sudo tee "$service_file" > /dev/null </dev/null || \ + log_message "WARNING" "Failed to enable systemd service (may already exist)" + ;; + + "openrc") + local service_file="/etc/init.d/wallarm-$INSTANCE_NAME" + sudo tee "$service_file" > /dev/null </dev/null || true + docker rm $INSTANCE_NAME 2>/dev/null || true + eend \$? +} +EOF + sudo chmod +x "$service_file" + sudo rc-update add "wallarm-$INSTANCE_NAME" default 2>/dev/null || \ + log_message "WARNING" "Failed to add OpenRC service (may already exist)" + ;; + + "sysvinit") + local service_file="/etc/init.d/wallarm-$INSTANCE_NAME" + sudo tee "$service_file" > /dev/null </dev/null || true + docker rm $INSTANCE_NAME 2>/dev/null || true + log_end_msg \$? + ;; + restart) + \$0 stop + sleep 2 + \$0 start + ;; + status) + if docker ps | grep -q "$INSTANCE_NAME"; then + echo "$INSTANCE_NAME is running" + exit 0 + else + echo "$INSTANCE_NAME is not running" + exit 1 + fi + ;; + *) + echo "Usage: \$0 {start|stop|restart|status}" + exit 3 + ;; +esac + +exit 0 +EOF + sudo chmod +x "$service_file" + sudo update-rc.d "wallarm-$INSTANCE_NAME" defaults 2>/dev/null || \ + log_message "WARNING" "Failed to add SysV init service (may already exist)" + ;; + + *) + log_message "WARNING" "Unknown init system, not creating service (manual start via $start_script)" + ;; + esac + + # Start the Wallarm node + log_message "INFO" "Starting Wallarm filtering node..." + if ! sudo "$start_script"; then + fail_with_remediation "Failed to start Wallarm node" \ + "Container failed to start. Check: +1. Docker logs: sudo docker logs $INSTANCE_NAME +2. Port conflicts: sudo ss -tlnp | grep ':$INGRESS_PORT\|:$MONITORING_PORT' +3. Docker status: sudo docker info +4. Manual start attempt: sudo $start_script" + fi + + log_message "SUCCESS" "Wallarm filtering node deployed successfully" + log_message "SUCCESS" " Container: $INSTANCE_NAME" + log_message "SUCCESS" " Ingress Port: $INGRESS_PORT" + log_message "SUCCESS" " Monitoring Port: $MONITORING_PORT" + log_message "SUCCESS" " Upstream: $UPSTREAM_IP:$UPSTREAM_PORT" + log_message "SUCCESS" " Config Directory: $INSTANCE_DIR" +} + +# ============================================================================== +# DEPLOYMENT VERIFICATION +# ============================================================================== + +verify_deployment() { + log_message "INFO" "Verifying Wallarm deployment..." + + # Check if container is running + log_message "INFO" "Checking if container is running..." + if ! sudo docker ps | grep -q "$INSTANCE_NAME"; then + fail_with_remediation "Wallarm container is not running" \ + "Container failed to start or crashed: +1. Check container logs: sudo docker logs $INSTANCE_NAME +2. Check Docker service: sudo systemctl status docker (or equivalent) +3. Manual start: sudo $INSTANCE_DIR/start.sh" + fi + log_message "SUCCESS" "Container is running" + + # Test ingress port + log_message "INFO" "Testing ingress port $INGRESS_PORT..." + if ! check_port_available "$INGRESS_PORT"; then + log_message "SUCCESS" "Ingress port $INGRESS_PORT is in use (as expected)" + else + log_message "WARNING" "Ingress port $INGRESS_PORT appears available (container may not be listening)" + fi + + # Test monitoring port + log_message "INFO" "Testing monitoring port $MONITORING_PORT..." + if ! check_port_available "$MONITORING_PORT"; then + log_message "SUCCESS" "Monitoring port $MONITORING_PORT is in use (as expected)" + else + log_message "WARNING" "Monitoring port $MONITORING_PORT appears available" + fi + + # Test health check endpoint + log_message "INFO" "Testing health check endpoint..." + local health_check_url="http://localhost:$INGRESS_PORT/health" + if curl -sf --connect-timeout 5 "$health_check_url" >/dev/null 2>&1; then + log_message "SUCCESS" "Health check endpoint responsive" + else + log_message "WARNING" "Health check endpoint not responsive (may need time to start)" + sleep 5 + if curl -sf --connect-timeout 5 "$health_check_url" >/dev/null 2>&1; then + log_message "SUCCESS" "Health check endpoint now responsive" + else + log_message "WARNING" "Health check endpoint still not responsive (check nginx config)" + fi + fi + + # Test handshake through filtering node + log_message "INFO" "Testing handshake through filtering node to upstream..." + local test_url="http://localhost:$INGRESS_PORT/" + if curl -sfI --connect-timeout 10 "$test_url" >/dev/null 2>&1; then + log_message "SUCCESS" "Handshake successful: filtering node can reach upstream" + else + log_message "WARNING" "Handshake failed (upstream may not be responding)" + log_message "INFO" "Checking if upstream is directly reachable..." + if timeout 5 bash -c "cat < /dev/null > /dev/tcp/$UPSTREAM_IP/$UPSTREAM_PORT" 2>/dev/null; then + log_message "ERROR" "Upstream is reachable but filtering node cannot proxy" + echo -e "${YELLOW}Possible nginx configuration issue. Check:${NC}" + echo -e "1. Container logs: sudo docker logs $INSTANCE_NAME" + echo -e "2. Nginx config: sudo docker exec $INSTANCE_NAME cat /etc/nginx/http.d/default.conf" + else + log_message "WARNING" "Upstream server is not reachable (as previously warned)" + fi + fi + + # Check Wallarm cloud synchronization + log_message "INFO" "Checking Wallarm cloud synchronization (this may take 30 seconds)..." + echo -e "${YELLOW}Note: Full synchronization with Wallarm cloud may take several minutes.${NC}" + echo -e "${YELLOW}You can check sync status in Wallarm Console.${NC}" + + # Quick test: check container logs for synchronization messages + if sudo docker logs "$INSTANCE_NAME" 2>&1 | tail -20 | grep -i "sync\|connected\|token" >/dev/null 2>&1; then + log_message "SUCCESS" "Wallarm node appears to be communicating with cloud" + else + log_message "WARNING" "No cloud synchronization messages in logs yet (may need time)" + fi + + log_message "SUCCESS" "Deployment verification completed" + echo -e "\n${GREEN}${BOLD}Verification Summary:${NC}" + echo -e " ${GREEN}✓${NC} Container running: $INSTANCE_NAME" + echo -e " ${GREEN}✓${NC} Ingress port: $INGRESS_PORT" + echo -e " ${GREEN}✓${NC} Monitoring port: $MONITORING_PORT" + echo -e " ${GREEN}✓${NC} Upstream: $UPSTREAM_IP:$UPSTREAM_PORT" + echo -e " ${GREEN}✓${NC} Cloud region: $CLOUD_REGION ($API_HOST)" +} + +# ============================================================================== +# MAIN FUNCTION +# ============================================================================== + +main() { + clear + echo -e "${BLUE}${BOLD}" + echo "╔══════════════════════════════════════════════════════════════╗" + echo "║ WALLARM DEPLOYMENT SCRIPT (Docker) - V1.3 ║" + echo "║ LXC-Optimized Filtering Node Deployment ║" + echo "╚══════════════════════════════════════════════════════════════╝${NC}" + echo -e "\n${YELLOW}Starting deployment at: $(date)${NC}" + + # Initialize logging + # Create logs directory if it doesn't exist + local log_dir="${HOME:-.}/logs" + if [ ! -d "$log_dir" ]; then + if ! mkdir -p "$log_dir"; then + echo -e "${YELLOW}Cannot create log directory $log_dir, falling back to current directory...${NC}" + log_dir="." + fi + fi + + LOG_FILE="$log_dir/wallarm-deployment.log" + if ! : > "$LOG_FILE"; then + echo -e "${RED}Cannot create log file at $LOG_FILE${NC}" + echo -e "${YELLOW}Falling back to current directory...${NC}" + LOG_FILE="./wallarm-deployment.log" + : > "$LOG_FILE" 2>/dev/null || true + fi + if ! chmod 644 "$LOG_FILE" 2>/dev/null; then + echo -e "${YELLOW}Warning: Could not set permissions on log file${NC}" + fi + + log_message "INFO" "=== Wallarm Deployment Started ===" + + # SSL security warning + if [ "$INSECURE_SSL" = "1" ]; then + log_message "WARNING" "SSL certificate validation is DISABLED (insecure). Set WALLARM_INSECURE_SSL=0 to enable validation." + fi + + # Phase 1: Verify preflight check + log_message "INFO" "=== PHASE 1: PREFLIGHT CHECK VERIFICATION ===" + verify_preflight_check + + # Phase 2: Configuration collection + log_message "INFO" "=== PHASE 2: CONFIGURATION COLLECTION ===" + select_cloud_region + collect_configuration + + # Phase 3: Docker engine setup (LXC optimized) + log_message "INFO" "=== PHASE 3: DOCKER ENGINE SETUP (LXC OPTIMIZED) ===" + setup_docker_engine + + # Phase 4: Deployment + log_message "INFO" "=== PHASE 4: DEPLOYMENT ===" + deploy_wallarm_node + + # Phase 5: Verification + log_message "INFO" "=== PHASE 5: VERIFICATION ===" + verify_deployment + + # Success message + log_message "SUCCESS" "=== WALLARM DEPLOYMENT COMPLETED SUCCESSFULLY ===" + echo -e "\n${GREEN}${BOLD}╔══════════════════════════════════════════════════════════════╗${NC}" + echo -e "${GREEN}${BOLD}║ WALLARM FILTERING NODE DEPLOYMENT SUCCESSFUL ║${NC}" + echo -e "${GREEN}${BOLD}╚══════════════════════════════════════════════════════════════╝${NC}" + echo -e "\n${CYAN}The Wallarm filtering node is now active and protecting your application.${NC}" + echo -e "${YELLOW}Full deployment log: $LOG_FILE${NC}" + echo -e "${YELLOW}Instance directory: $INSTANCE_DIR${NC}" + echo -e "\n${GREEN}To stop the node:${NC} sudo docker stop $INSTANCE_NAME" + echo -e "${GREEN}To restart:${NC} sudo $INSTANCE_DIR/start.sh" + echo -e "${GREEN}To view logs:${NC} sudo docker logs -f $INSTANCE_NAME" + echo -e "\n${MAGENTA}${BOLD}Deployment completed successfully!${NC}" + echo -e "\n${YELLOW}Important next steps:${NC}" + echo -e "1. Monitor sync status in Wallarm Console" + echo -e "2. Test attack detection with safe test: curl http://localhost:$INGRESS_PORT/?wallarm_test=1" + echo -e "3. Review logs periodically: sudo docker logs --tail 50 $INSTANCE_NAME" +} + +# ============================================================================== +# SCRIPT EXECUTION +# ============================================================================== + +# Ensure we're in bash +if [ -z "$BASH_VERSION" ]; then + echo "Error: This script must be run with bash" >&2 + exit 1 +fi + +# Run main function +main "$@" diff --git a/docker/wallarm-docker.sh b/docker/wallarm-docker.sh old mode 100755 new mode 100644 index 9d0bf4c..df79efa --- a/docker/wallarm-docker.sh +++ b/docker/wallarm-docker.sh @@ -1,1653 +1,91 @@ #!/bin/bash # ============================================================================== -# WALLARM DEPLOYMENT SCRIPT - V1.3 (Docker deployment) +# Wallarm Docker Node Manager - Unified single-script manager # ============================================================================== -# Purpose: Deploy Wallarm filtering node (Docker container) after preflight check -# Features: -# - Reads preflight check results from .env file -# - Interactive configuration (cloud region, ports, token, upstream) -# - Docker installation with LXC optimization (VFS storage driver) -# - Wallarm node deployment with persistence -# - Deployment verification with handshake test -# - DAU-friendly error handling with remediation +# Delegates to the individual docker/* scripts for preflight, deployment, +# reconfiguration, and removal — providing a single entry point matching the +# native/wallarm-native.sh command interface. +# +# Commands: +# --preflight Run preflight checks only (no installation). +# --install Interactive deployment of a Wallarm Docker node. +# --config Reconfigure an existing Docker node. +# --remove Remove a Docker node completely. +# --status Show running Wallarm containers. +# --help|-h Show help. # ============================================================================== -# Script location and shared library (colors, logging, validation, detection, connectivity) -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -# shellcheck source=./wallarm-lib.sh -source "$SCRIPT_DIR/wallarm-lib.sh" - -# Strict error handling set -euo pipefail -trap early_error_handler ERR -# Configuration -ENV_FILE=".env" -LOG_FILE="${HOME:-.}/logs/wallarm-deployment.log" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -# Git Repositorys artifact URLs (primary source) - Docker deployment artifacts -GIT_BASE_URL="https://git.sechpoint.app/customer-engineering/wallarm" -GIT_RAW_URL="https://git.sechpoint.app/customer-engineering/wallarm/raw/branch/main" -GIT_DOCKER_BINARY_URL="${GIT_RAW_URL}/docker/binaries/docker-29.2.1.tgz" -GIT_DOCKER_CHECKSUM_URL="${GIT_RAW_URL}/docker/binaries/docker-29.2.1.tgz.sha256" -GIT_WALLARM_IMAGE_URL="${GIT_RAW_URL}/docker/images/wallarm-node-6.11.0-rc1.tar.gz" -GIT_WALLARM_CHECKSUM_URL="${GIT_RAW_URL}/docker/images/wallarm-node-6.11.0-rc1.tar.gz.sha256" +# Shell script files this wrapper delegates to (must be in the same directory) +CHECK_SCRIPT="${SCRIPT_DIR}/wallarm-ct-check.sh" +DEPLOY_SCRIPT="${SCRIPT_DIR}/wallarm-ct-deploy.sh" +RECONF_SCRIPT="${SCRIPT_DIR}/wallarm-ct-reconfigure.sh" +REMOVE_SCRIPT="${SCRIPT_DIR}/wallarm-ct-uninstall.sh" -# Local artifact directories (relative to script location) -LOCAL_BINARY_DIR="${SCRIPT_DIR}/binaries" -LOCAL_IMAGE_DIR="${SCRIPT_DIR}/images" +show_help() { + cat <&2 + echo " Run setup.sh first to download all deployment scripts." >&2 exit 1 fi - - log_message "SUCCESS" "Preflight check verified:" - log_message "SUCCESS" " OS: $OS_NAME $OS_VERSION" - log_message "SUCCESS" " Architecture: $ARCHITECTURE" - log_message "SUCCESS" " Init System: $INIT_SYSTEM" - log_message "SUCCESS" " US Cloud Reachable: $US_CLOUD_REACHABLE" - log_message "SUCCESS" " EU Cloud Reachable: $EU_CLOUD_REACHABLE" - log_message "SUCCESS" " Registry Reachable: $REGISTRY_REACHABLE" - log_message "SUCCESS" " Download Reachable: $DOWNLOAD_REACHABLE" - log_message "SUCCESS" " Git Repositorys Reachable: $GIT_REACHABLE" - - # Check for local artifact directories - if [ -d "$LOCAL_BINARY_DIR" ]; then - log_message "INFO" " Local binaries directory: $LOCAL_BINARY_DIR (exists)" - local binary_count=$(ls "$LOCAL_BINARY_DIR"/*.tgz 2>/dev/null | wc -l) - if [ "$binary_count" -gt 0 ]; then - log_message "INFO" " Found $binary_count Docker binary file(s)" - fi - else - log_message "INFO" " Local binaries directory: $LOCAL_BINARY_DIR (not found)" - fi - - if [ -d "$LOCAL_IMAGE_DIR" ]; then - log_message "INFO" " Local images directory: $LOCAL_IMAGE_DIR (exists)" - local image_count=$(ls "$LOCAL_IMAGE_DIR"/*.tar.gz 2>/dev/null | wc -l) - if [ "$image_count" -gt 0 ]; then - log_message "INFO" " Found $image_count Wallarm image file(s)" - fi - else - log_message "INFO" " Local images directory: $LOCAL_IMAGE_DIR (not found)" - fi - - # Validate we have at least one cloud region reachable - if [ "$US_CLOUD_REACHABLE" = "false" ] && [ "$EU_CLOUD_REACHABLE" = "false" ]; then - fail_with_remediation "No Wallarm cloud region reachable" \ - "Network connectivity issues detected: -1. Check firewall rules for Wallarm cloud endpoints -2. Verify network connectivity -3. Run preflight check again: $SCRIPT_DIR/wallarm-ct-check.sh" - fi - - # Validate we have resources for Docker/Wallarm - # Primary source: Git Repositorys; fallback: local directories - if [ "$GIT_REACHABLE" = "false" ]; then - log_message "WARNING" "Git Repositorys not reachable, checking for local resources..." - - local has_local_resources=true - - # Check local binaries directory first, then current directory as fallback - if [ ! -f "$LOCAL_BINARY_DIR/docker-29.2.1.tgz" ] && [ -z "$(ls docker-*.tgz 2>/dev/null)" ]; then - log_message "ERROR" "No local Docker binary found" - has_local_resources=false - fi - - # Check local images directory first, then current directory as fallback - if [ ! -f "$LOCAL_IMAGE_DIR/wallarm-node-6.11.0-rc1.tar.gz" ] && [ -z "$(ls wallarm-node-*.tar.gz wallarm-node-*.tar 2>/dev/null)" ]; then - log_message "ERROR" "No local Wallarm image found" - has_local_resources=false - fi - - if [ "$has_local_resources" = "false" ]; then - fail_with_remediation "Insufficient resources for deployment" \ - "Please provide either: -1. Network access to Git Repositorys: $GIT_RAW_URL -2. Local files in binaries/ and images/ directories: - - $LOCAL_BINARY_DIR/docker-29.2.1.tgz - - $LOCAL_IMAGE_DIR/wallarm-node-6.11.0-rc1.tar.gz -3. Local files in current directory as fallback: - - docker-*.tgz - - wallarm-node-*.tar.gz or wallarm-node-*.tar" - fi - else - log_message "SUCCESS" "Git Repositorys reachable - will download artifacts from $GIT_RAW_URL" - fi } -# ============================================================================== -# CONFIGURATION COLLECTION FUNCTIONS -# ============================================================================== -# select_cloud_region, validate_ip_address and check_port_available are provided -# by the shared library (../common/wallarm-lib.sh). - -collect_configuration() { - log_message "INFO" "Collecting deployment configuration..." - - # Get ingress port - local default_port=80 - local ingress_port="" - while [[ ! "$ingress_port" =~ ^[0-9]+$ ]] || [ "$ingress_port" -lt 1 ] || [ "$ingress_port" -gt 65535 ]; do - read -r -p "$(echo -e "${YELLOW}Enter inbound port [${default_port}]: ${NC}")" ingress_port - ingress_port="${ingress_port:-$default_port}" - - if [[ ! "$ingress_port" =~ ^[0-9]+$ ]]; then - echo -e "${RED}Port must be a number${NC}" - elif [ "$ingress_port" -lt 1 ] || [ "$ingress_port" -gt 65535 ]; then - echo -e "${RED}Port must be between 1 and 65535${NC}" - elif ! check_port_available "$ingress_port"; then - echo -e "${RED}Port $ingress_port is already in use${NC}" - ingress_port="" - fi - done - - # Calculate monitoring port (ingress + 10, check for conflicts) - local monitoring_port=$((ingress_port + 10)) - if ! check_port_available "$monitoring_port"; then - log_message "WARNING" "Port $monitoring_port is in use, choosing alternative..." - monitoring_port=$((ingress_port + 100)) - if ! check_port_available "$monitoring_port"; then - monitoring_port=$((ingress_port + 200)) - fi - fi - log_message "INFO" "Monitoring port will be: $monitoring_port" - - # Get application server details - local upstream_ip="" - local upstream_port="" - - echo -e "\n${CYAN}${BOLD}Application Server Configuration:${NC}" - echo -e "${YELLOW}Enter the IP/hostname and port of your backend application${NC}" - - while [[ -z "$upstream_ip" ]]; do - read -r -p "$(echo -e "${YELLOW}Upstream App IP/Hostname [127.0.0.1]: ${NC}")" upstream_ip - upstream_ip="${upstream_ip:-127.0.0.1}" - - # Validate IP/hostname format - if ! validate_ip_address "$upstream_ip" && \ - ! [[ "$upstream_ip" =~ ^[a-zA-Z0-9][a-zA-Z0-9.-]*[a-zA-Z0-9]$ ]]; then - echo -e "${RED}Invalid IP/hostname format${NC}" - upstream_ip="" - fi - done - - while [[ ! "$upstream_port" =~ ^[0-9]+$ ]] || [ "$upstream_port" -lt 1 ] || [ "$upstream_port" -gt 65535 ]; do - read -r -p "$(echo -e "${YELLOW}Upstream App Port [8080]: ${NC}")" upstream_port - upstream_port="${upstream_port:-8080}" - - if [[ ! "$upstream_port" =~ ^[0-9]+$ ]]; then - echo -e "${RED}Port must be a number${NC}" - elif [ "$upstream_port" -lt 1 ] || [ "$upstream_port" -gt 65535 ]; then - echo -e "${RED}Port must be between 1 and 65535${NC}" - fi - done - - # Verify application server reachability - log_message "INFO" "Verifying application server reachability..." - if timeout 5 bash -c "cat < /dev/null > /dev/tcp/$upstream_ip/$upstream_port" 2>/dev/null; then - log_message "SUCCESS" "Application server $upstream_ip:$upstream_port is reachable" - else - log_message "WARNING" "Application server $upstream_ip:$upstream_port is not reachable" - echo -e "${YELLOW}${BOLD}Warning:${NC} Cannot reach application server at $upstream_ip:$upstream_port" - echo -e "${YELLOW}This may cause the Wallarm node to fail. Possible reasons:${NC}" - echo -e "1. Application server is not running" - echo -e "2. Firewall blocking port $upstream_port" - echo -e "3. Wrong IP/hostname" - echo -e "4. Application server not listening on that port" - - read -r -p "$(echo -e "${YELLOW}Continue anyway? (y/N): ${NC}")" -n 1 - echo - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - fail_with_remediation "Application server unreachable" \ - "Ensure your application server is accessible: -1. Start your application server -2. Check it's listening: sudo ss -tlnp | grep :$upstream_port -3. Verify firewall rules allow inbound connections -4. Test connectivity: telnet $upstream_ip $upstream_port -5. If using hostname, verify DNS resolution: nslookup $upstream_ip" - fi - fi - - # Get Wallarm node token - local wallarm_token="" - echo -e "\n${CYAN}${BOLD}Wallarm Node Token:${NC}" - echo -e "${YELLOW}Get your token from Wallarm Console:${NC}" - echo -e "Create a new 'Wallarm node' and copy the token (will be visible as you type)" - while [[ -z "$wallarm_token" ]]; do - read -r -p "$(echo -e "${YELLOW}Paste Wallarm Node Token: ${NC}")" wallarm_token - # Trim whitespace and newlines - wallarm_token=$(echo "$wallarm_token" | tr -d '[:space:]') - - if [[ -z "$wallarm_token" ]]; then - echo -e "${RED}Token cannot be empty${NC}" - elif [[ ! "$wallarm_token" =~ ^[A-Za-z0-9_+/=\-]+$ ]]; then - echo -e "${RED}Token contains invalid characters. Wallarm tokens are base64 strings (A-Z, a-z, 0-9, _, -, +, /, =)${NC}" - echo -e "${YELLOW}First 20 chars of what you entered: '${wallarm_token:0:20}...'${NC}" - wallarm_token="" - else - # Show confirmation of token length (but not full token for security) - token_length=${#wallarm_token} - echo -e "${GREEN}Token accepted (${token_length} characters).${NC}" - echo -e "${YELLOW}First 8 chars for verification: ${wallarm_token:0:8}...${NC}" - fi - done - - # Get trusted proxy IPs for real IP configuration - local trusted_proxies="" - echo -e "\n${CYAN}${BOLD}Real Client IP Configuration:${NC}" - echo -e "${YELLOW}For Wallarm to see the real client IP, specify the IP address(es) of trusted proxies" - echo -e "(e.g., load balancers, firewalls, CDNs) that forward traffic to this node.${NC}" - echo -e "${YELLOW}You can enter:${NC}" - echo -e " - Single IP: 10.0.0.10" - echo -e " - CIDR range: 10.0.0.0/24" - echo -e " - Multiple entries separated by spaces: 10.0.0.10 10.0.1.0/24 192.168.1.1" - echo -e "${YELLOW}If unsure, you can leave empty and configure later${NC}" - - read -r -p "$(echo -e "${YELLOW}Trusted proxy IPs/CIDRs (space-separated): ${NC}")" trusted_proxies_input - - # Validate and clean up the input - local validated_proxies=() - if [[ -n "$trusted_proxies_input" ]]; then - # Split input by spaces - IFS=' ' read -ra proxy_array <<< "$trusted_proxies_input" - - for proxy in "${proxy_array[@]}"; do - # Trim whitespace - proxy=$(echo "$proxy" | xargs) - if [[ -n "$proxy" ]]; then - # Validate IP or CIDR - if [[ "$proxy" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}(/[0-9]{1,2})?$ ]]; then - # Basic IP format validation - IFS='/' read -r ip cidr <<< "$proxy" - IFS='.' read -r o1 o2 o3 o4 <<< "$ip" - if [[ $o1 -le 255 && $o2 -le 255 && $o3 -le 255 && $o4 -le 255 ]]; then - if [[ -z "$cidr" ]] || [[ $cidr -ge 0 && $cidr -le 32 ]]; then - validated_proxies+=("$proxy") - else - echo -e "${RED}Invalid CIDR prefix length for $proxy (must be 0-32)${NC}" - fi - else - echo -e "${RED}Invalid IP octets in $proxy${NC}" - fi - else - echo -e "${RED}Invalid IP/CIDR format: $proxy${NC}" - echo -e "${YELLOW}Example valid formats: 10.0.0.10, 10.0.0.0/24, 192.168.1.1${NC}" - fi - fi - done - - if [[ ${#validated_proxies[@]} -eq 0 ]]; then - echo -e "${YELLOW}No valid proxy IPs provided. Will skip set_real_ip_from configuration.${NC}" - echo -e "${YELLOW}You can manually edit /etc/nginx/conf.d/wallarm.conf later to add set_real_ip_from directives.${NC}" - trusted_proxies="" - else - trusted_proxies="${validated_proxies[*]}" - echo -e "${GREEN}Trusted proxies configured: $trusted_proxies${NC}" - fi - else - echo -e "${YELLOW}No trusted proxies specified. The node will see the last hop IP only.${NC}" - echo -e "${YELLOW}For real client IP detection, you'll need to manually configure set_real_ip_from in nginx config.${NC}" - fi - - # Generate instance name and directory - local instance_name - instance_name="wallarm-$(hostname -s | tr '[:upper:]' '[:lower:]')-$(date +%Y%m%d)" - local instance_dir="/opt/$instance_name" - - # Ensure directory exists - sudo mkdir -p "$instance_dir" - - log_message "SUCCESS" "Configuration collected:" - log_message "SUCCESS" " Ingress Port: $ingress_port" - log_message "SUCCESS" " Monitoring Port: $monitoring_port" - log_message "SUCCESS" " Upstream: $upstream_ip:$upstream_port" - if [[ -n "$trusted_proxies" ]]; then - log_message "SUCCESS" " Trusted Proxies: $trusted_proxies" - else - log_message "INFO" " Trusted Proxies: Not configured (will need manual setup)" - fi - log_message "SUCCESS" " Instance: $instance_name" - log_message "SUCCESS" " Directory: $instance_dir" - - # Set global variables - INGRESS_PORT="$ingress_port" - MONITORING_PORT="$monitoring_port" - UPSTREAM_IP="$upstream_ip" - UPSTREAM_PORT="$upstream_port" - WALLARM_TOKEN="$wallarm_token" - INSTANCE_NAME="$instance_name" - INSTANCE_DIR="$instance_dir" - TRUSTED_PROXIES="$trusted_proxies" -} - -# ============================================================================== -# DOCKER ENGINE SETUP (LXC OPTIMIZED) -# ============================================================================== - -setup_docker_engine() { - log_message "INFO" "Setting up Docker Engine for LXC/stealth deployment..." - - # Check if Docker is already installed and running - if command -v docker >/dev/null 2>&1 && sudo docker info >/dev/null 2>&1; then - local docker_version - docker_version=$(docker --version | cut -d' ' -f3 | tr -d ',') - log_message "SUCCESS" "Docker is already installed and running (version $docker_version)" - - # Check if Docker is configured for LXC - if sudo docker info 2>/dev/null | grep -q "Storage Driver: vfs"; then - log_message "SUCCESS" "Docker is already configured with VFS storage driver (LXC compatible)" - else - log_message "WARNING" "Docker is not using VFS storage driver. LXC compatibility may be limited." - fi - return 0 - fi - - log_message "INFO" "Docker not found or not running. Proceeding with installation..." - - # Determine binary source (priority: Git Repositorys -> local dir -> current dir) - local binary_file="docker-$DOCKER_VERSION.tgz" - local binary_path="" - - # 1. Try Git Repositorys download (primary source) - log_message "INFO" "Attempting to download Docker binary from Git Repositorys..." - if download_from_git "$GIT_DOCKER_BINARY_URL" "$binary_file" "Docker binary"; then - if verify_checksum "$binary_file" "$GIT_DOCKER_CHECKSUM_URL" "Docker binary"; then - binary_path="$binary_file" - log_message "SUCCESS" "Docker binary downloaded from Git Repositorys and checksum verified" - else - log_message "WARNING" "Git Repositorys Docker binary checksum verification failed, trying other sources" - # Remove corrupted download - rm -f "$binary_file" - fi - fi - - # 2. Check local binaries directory - if [ -z "$binary_path" ] && [ -d "$LOCAL_BINARY_DIR" ]; then - log_message "INFO" "Checking local binaries directory: $LOCAL_BINARY_DIR" - local local_binary="$LOCAL_BINARY_DIR/docker-29.2.1.tgz" - local local_checksum="$LOCAL_BINARY_DIR/docker-29.2.1.tgz.sha256" - - if [ -f "$local_binary" ]; then - log_message "INFO" "Found local Docker binary: $local_binary" - # Copy to current directory for consistency with extraction logic - cp "$local_binary" "$binary_file" - if verify_checksum "$binary_file" "$local_checksum" "local Docker binary"; then - binary_path="$binary_file" - log_message "SUCCESS" "Using local Docker binary from binaries directory" - else - log_message "WARNING" "Local Docker binary checksum verification failed" - rm -f "$binary_file" - fi - fi - fi - - # 3. Check current directory for any docker-*.tgz (existing fallback) - if [ -z "$binary_path" ]; then - log_message "INFO" "Checking current directory for Docker binaries..." - local local_files - local_files=$(ls docker-*.tgz 2>/dev/null | head -1) - if [ -n "$local_files" ]; then - binary_path="$local_files" - log_message "SUCCESS" "Using local Docker binary: $binary_path" - # Optional: Try to verify checksum if .sha256 file exists - local checksum_file="${local_files}.sha256" - if [ -f "$checksum_file" ]; then - if verify_checksum "$binary_path" "$checksum_file" "Docker binary"; then - log_message "SUCCESS" "Local Docker binary checksum verified" - else - log_message "WARNING" "Local Docker binary checksum verification failed, but continuing" - fi - fi - fi - fi - - - - # 5. Final fallback: no binary available - if [ -z "$binary_path" ]; then - fail_with_remediation "No Docker binary available" \ - "Please provide a Docker static binary using one of these methods: -1. Git Repositorys (primary): Ensure network access to $GIT_RAW_URL -2. Local binaries directory: Place docker-29.2.1.tgz and .sha256 in $LOCAL_BINARY_DIR/ -3. Current directory: Place any docker-*.tgz file in current directory - - -Re-run the script after providing the binary." - fi - - # Extract and install - log_message "INFO" "Extracting Docker binary..." - - # First verify the tar file exists and is readable - if [ ! -f "$binary_path" ]; then - fail_with_remediation "Docker binary file not found" \ - "File $binary_path does not exist. Check download and file permissions." - fi - - if [ ! -r "$binary_path" ]; then - fail_with_remediation "Docker binary file not readable" \ - "Cannot read file $binary_path. Check file permissions." - fi - - # Test if it's a valid tar archive - log_message "DEBUG" "Testing tar archive integrity..." - - # First check if we can read the file - if [ ! -r "$binary_path" ]; then - log_message "ERROR" "Cannot read file: $binary_path" - log_message "INFO" "File permissions: $(ls -la "$binary_path" 2>/dev/null || echo "cannot stat")" - fail_with_remediation "Cannot read Docker binary file" \ - "File $binary_path exists but is not readable. -1. Check file permissions: ls -la $binary_path -2. Fix permissions: chmod 644 $binary_path -3. Or download fresh copy" - fi - - # Try to get file type information - local file_type="unknown" - if command -v file >/dev/null 2>&1; then - file_type=$(file "$binary_path" 2>/dev/null || echo "file command failed") - elif command -v hexdump >/dev/null 2>&1; then - # Check magic bytes manually - local magic_bytes=$(hexdump -n 2 -C "$binary_path" 2>/dev/null | head -1 | cut -d' ' -f2-3 || echo "no magic") - file_type="hexdump: $magic_bytes" - fi - - log_message "INFO" "File info: $binary_path ($(stat -c%s "$binary_path") bytes)" - log_message "INFO" "File type: $file_type" - log_message "INFO" "Current directory: $(pwd)" - log_message "INFO" "Full path: $(readlink -f "$binary_path" 2>/dev/null || echo "$binary_path")" - - # Test tar archive with error capture - local tar_test_output - tar_test_output=$(tar -tzf "$binary_path" 2>&1) - local tar_test_exit=$? - - if [ $tar_test_exit -ne 0 ]; then - log_message "ERROR" "File $binary_path is not a valid tar.gz archive (tar exit: $tar_test_exit)" - log_message "DEBUG" "Tar test output: $tar_test_output" - - # Check if it might be a different compression format - log_message "INFO" "Checking for alternative compression formats..." - - # Try gunzip test - if command -v gunzip >/dev/null 2>&1; then - if gunzip -t "$binary_path" 2>/dev/null; then - log_message "WARNING" "File is valid gzip but tar can't read it" - else - log_message "INFO" "Not a valid gzip file either" - fi - fi - - # Check first few bytes - if command -v xxd >/dev/null 2>&1; then - log_message "DEBUG" "First 20 bytes: $(xxd -l 20 "$binary_path" 2>/dev/null || echo "cannot read")" - elif command -v od >/dev/null 2>&1; then - log_message "DEBUG" "First 20 bytes: $(od -x -N 20 "$binary_path" 2>/dev/null | head -2 || echo "cannot read")" - fi - - fail_with_remediation "Docker binary file is corrupted or invalid" \ - "The Docker binary file is not a valid tar.gz archive. -Tar error: $tar_test_output - -File info: $(stat -c%s "$binary_path") bytes, type: $file_type - -Possible solutions: -1. The download may have been interrupted or corrupted -2. The file may be in wrong format (not tar.gz) -3. Server might be serving wrong content - -Steps to fix: -1. Delete corrupted file: rm -f docker-*.tgz -2. Check disk space: df -h . -3. Try alternative sources: - a) Git Repositorys: curl -L '$GIT_DOCKER_BINARY_URL' -o docker.tgz - b) Local directory: Check $LOCAL_BINARY_DIR/docker-29.2.1.tgz - -4. Verify downloaded file: file test.tgz && tar -tzf test.tgz -5. Check if tar command works: tar --version" - fi - - log_message "SUCCESS" "Tar archive validation passed" - - # Extract the archive - log_message "DEBUG" "Extracting files from $binary_path..." - local tar_output - tar_output=$(tar xzvf "$binary_path" 2>&1) - local tar_exit=$? - - if [ $tar_exit -ne 0 ]; then - log_message "ERROR" "Failed to extract files from $binary_path (exit code: $tar_exit)" - log_message "DEBUG" "Tar output: $tar_output" - log_message "INFO" "Checking extracted files..." - if [ -d "docker" ]; then - log_message "WARNING" "Some files were extracted to 'docker/' directory" - ls -la docker/ 2>/dev/null | head -10 || true - fi - fail_with_remediation "Failed to extract Docker binary" \ - "Extraction failed. Possible reasons: -1. Insufficient disk space: df -h . -2. Permission issues in current directory -3. Corrupted archive (partial download) -4. File system issues - -Tar error: $tar_output - -Check disk space and permissions, then try manual extraction: - tar xzvf $binary_path" - else - log_message "SUCCESS" "Docker binary extracted successfully" - fi - - log_message "INFO" "Installing Docker binaries to /usr/bin/" - sudo cp docker/* /usr/bin/ 2>/dev/null || { - fail_with_remediation "Failed to copy Docker binaries" \ - "Permission denied copying to /usr/bin/ -1. Ensure you have sudo privileges -2. Check disk space: df -h / -3. Manual installation: - sudo cp docker/* /usr/bin/" - } - - # Ensure binaries are executable - log_message "INFO" "Setting executable permissions on Docker binaries..." - sudo chmod +x /usr/bin/dockerd /usr/bin/docker 2>/dev/null || { - log_message "WARNING" "Could not set executable permissions on Docker binaries" - } - - # Verify Docker binaries work - log_message "INFO" "Verifying Docker binaries..." - if ! sudo /usr/bin/dockerd --version 2>/dev/null; then - fail_with_remediation "Docker binary verification failed" \ -"Docker binary (/usr/bin/dockerd) appears to be corrupted or incompatible. -The binary was extracted from $binary_path but doesn't run. - -Check the binary: - sudo file /usr/bin/dockerd - sudo ls -la /usr/bin/dockerd - sudo /usr/bin/dockerd --version - -The Docker static binary might be for wrong architecture or corrupted. -Try downloading manually from one of these sources: -1. Git Repositorys: curl -L '$GIT_DOCKER_BINARY_URL' -o docker.tgz -2. Local directory: Check $LOCAL_BINARY_DIR/docker-29.2.1.tgz - - -Then extract and install: - tar xzvf docker.tgz - sudo cp docker/* /usr/bin/" - else - local docker_version - docker_version=$(sudo /usr/bin/dockerd --version 2>&1 | head -1) - log_message "SUCCESS" "Docker binary verified: $docker_version" - fi - - # Cleanup extracted directory - log_message "INFO" "Cleaning up extracted Docker binaries directory..." - rm -rf docker - log_message "INFO" "Cleanup completed" - - # DEBUG: Mark start of docker group section - log_message "INFO" "=== Starting docker group creation ===" - - # Create docker group (required for systemd socket configuration and dockerd --group) - log_message "INFO" "Creating docker group for Docker socket access..." - - # Check if group already exists - log_message "INFO" "Checking if docker group exists..." - local getent_output - if getent_output=$(getent group docker 2>&1); then - getent_exit=0 - else - getent_exit=$? - fi - log_message "INFO" "getent group docker result: exit=$getent_exit, output='$getent_output'" - - if [ $getent_exit -eq 0 ]; then - log_message "SUCCESS" "Docker group already exists: $getent_output" - else - # Attempt to create docker group with error capture - log_message "INFO" "Attempting to create docker group with sudo groupadd docker..." - local groupadd_output - if groupadd_output=$(sudo groupadd docker 2>&1); then - groupadd_exit=0 - else - groupadd_exit=$? - fi - - if [ $groupadd_exit -eq 0 ]; then - log_message "SUCCESS" "Created docker group" - else - log_message "ERROR" "Failed to create docker group (exit code: $groupadd_exit)" - log_message "INFO" "groupadd command output: $groupadd_output" - - # Check if group was somehow created despite error - log_message "INFO" "Checking if docker group was created despite groupadd failure..." - local check_getent_output - if check_getent_output=$(getent group docker 2>&1); then - check_getent_exit=0 - else - check_getent_exit=$? - fi - log_message "INFO" "Post-failure check: exit=$check_getent_exit, output='$check_getent_output'" - - if [ $check_getent_exit -eq 0 ]; then - log_message "WARNING" "Docker group exists despite groupadd failure, continuing..." - else - fail_with_remediation "Cannot create docker group" \ -"The docker group is required for Docker socket access. Please create it manually: - -1. Check if groupadd command is available: which groupadd -2. Check permissions: sudo -v -3. Manual group creation: sudo groupadd docker -4. Verify: getent group docker - -If groupadd fails, you may need to: -- Check system user/group database -- Use alternative: sudo addgroup docker (Debian/Ubuntu) -- Edit /etc/group manually (advanced users only)" - fi - fi - fi - - # Final verification that docker group exists - log_message "INFO" "Final verification of docker group existence..." - local final_getent_output - if final_getent_output=$(getent group docker 2>&1); then - final_getent_exit=0 - else - final_getent_exit=$? - fi - log_message "INFO" "Final getent result: exit=$final_getent_exit, output='$final_getent_output'" - - if [ $final_getent_exit -ne 0 ]; then - fail_with_remediation "Docker group verification failed" \ -"The docker group does not exist after creation attempts. This will cause Docker startup to fail. - -Please create the docker group manually and re-run the script: -1. sudo groupadd docker -2. Verify: getent group docker | grep docker -3. Re-run this script" - fi - - # Log group details for debugging - local docker_gid - docker_gid=$(echo "$final_getent_output" | cut -d: -f3) - log_message "INFO" "Docker group details: GID=$docker_gid" - - log_message "SUCCESS" "Docker group verified and ready (GID: $docker_gid)" - - # DEBUG: Mark end of docker group section - log_message "INFO" "=== Finished docker group creation ===" - - # Configure Docker daemon for LXC (VFS storage driver, cgroupfs) - log_message "INFO" "Configuring Docker daemon for LXC (VFS storage driver, cgroupfs)..." - - # Create docker configuration directory - sudo mkdir -p /etc/docker - - # Create daemon.json for LXC optimization - sudo tee /etc/docker/daemon.json > /dev/null < /dev/null <<'EOF' -[Unit] -Description=Docker Engine -After=network-online.target firewalld.service containerd.service -Wants=network-online.target -Requires=docker.socket - -[Service] -Type=notify -ExecStart=/usr/bin/dockerd --group docker -ExecReload=/bin/kill -s HUP $MAINPID -TimeoutSec=0 -RestartSec=2 -Restart=always -StartLimitBurst=3 -StartLimitInterval=60s -LimitNOFILE=infinity -LimitNPROC=infinity -LimitCORE=infinity -TasksMax=infinity -Delegate=yes -KillMode=process - -[Install] -WantedBy=multi-user.target -EOF - - sudo tee /etc/systemd/system/docker.socket > /dev/null <<'EOF' -[Unit] -Description=Docker Socket for the API - -[Socket] -ListenStream=/var/run/docker.sock -SocketMode=0660 -SocketUser=root -SocketGroup=docker - -[Install] -WantedBy=sockets.target -EOF - - sudo systemctl daemon-reload - - log_message "INFO" "Enabling Docker service to start on boot..." - if ! sudo systemctl enable docker; then - log_message "ERROR" "systemctl enable docker failed with exit code: $?" - fail_with_remediation "Failed to enable Docker service" \ -"Docker service could not be enabled to start on boot. Common causes: -1. Docker socket unit (docker.socket) has configuration errors -2. The docker group may not exist -3. Systemd unit file has syntax errors - -Check docker.socket status: - sudo systemctl status docker.socket --no-pager - -Verify docker group exists: - getent group docker - -Check systemd unit files: - sudo systemctl cat docker.socket - sudo systemctl cat docker.service" - fi - - log_message "INFO" "Starting Docker service (systemd)..." - # Start the service and capture exit code - if sudo systemctl start docker; then - start_exit=0 - else - start_exit=$? - fi - - # Give Docker a moment to start or fail - sleep 2 - - # Check if service is actually active - if ! sudo systemctl is-active docker --quiet; then - log_message "ERROR" "Docker service failed to start (systemctl start exit: $start_exit)" - log_message "INFO" "Checking docker.socket status..." - sudo systemctl status docker.socket --no-pager 2>&1 | head -20 || true - - log_message "INFO" "Checking docker.service status..." - sudo systemctl status docker.service --no-pager 2>&1 | head -30 || true - - log_message "INFO" "Checking Docker daemon logs..." - sudo journalctl -u docker --no-pager -n 30 2>&1 | head -50 || true - - log_message "INFO" "Checking Docker socket logs..." - sudo journalctl -u docker.socket --no-pager -n 20 2>&1 | head -30 || true - - fail_with_remediation "Failed to start Docker service" \ -"Docker service failed to start. Common causes: -1. Missing iptables (Docker static binaries require iptables v1.4+ for network bridge) -2. Docker daemon configuration error (check /etc/docker/daemon.json) -3. Storage driver issues (VFS may not be compatible) -4. Cgroup configuration problems -5. Port conflicts or resource limits - -Latest Docker daemon logs: -$(sudo journalctl -u docker --no-pager -n 30 2>&1 | tail -20) - -Check Docker configuration: - sudo cat /etc/docker/daemon.json - -Verify iptables is installed: - which iptables || echo 'iptables not found' - iptables --version 2>/dev/null || echo 'Cannot check version' - -Install iptables if missing: - # Debian/Ubuntu: sudo apt-get update && sudo apt-get install -y iptables - # RHEL/CentOS: sudo yum install -y iptables - # Alpine: sudo apk add iptables - -Verify docker group exists: - getent group docker - -Manual start attempt for debugging: - sudo dockerd --group docker --debug" - fi - ;; - - "openrc") - # Alpine OpenRC configuration - sudo tee /etc/init.d/docker > /dev/null <<'EOF' -#!/sbin/openrc-run -description="Docker Engine" -command="/usr/bin/dockerd" -command_args="--group docker" -pidfile="/run/docker.pid" -command_background=true - -depend() { - need net - after firewall -} -EOF - - sudo chmod +x /etc/init.d/docker - sudo rc-update add docker default - - log_message "INFO" "Starting Docker service (OpenRC)..." - if ! sudo rc-service docker start; then - log_message "ERROR" "rc-service docker start failed with exit code: $?" - fail_with_remediation "Failed to start Docker service (OpenRC)" \ -"Docker service failed to start under OpenRC. Common causes: -1. Missing iptables (Docker static binaries require iptables v1.4+ for network bridge) -2. Docker socket or port conflicts -3. Missing dependencies -4. Docker configuration errors - -Check OpenRC logs: - sudo rc-service docker status - sudo cat /var/log/docker.log 2>/dev/null || echo "No docker.log found" - -Verify iptables is installed: - which iptables || echo 'iptables not found' - iptables --version 2>/dev/null || echo 'Cannot check version' - -Install iptables if missing: - # Alpine: sudo apk add iptables - # Debian/Ubuntu: sudo apt-get update && sudo apt-get install -y iptables - # RHEL/CentOS: sudo yum install -y iptables - -Verify docker group exists: - getent group docker - -Manual start attempt: - sudo dockerd --group docker --debug" - fi - ;; - - "sysvinit") - # Traditional SysV init script - sudo tee /etc/init.d/docker > /dev/null <<'EOF' -#!/bin/bash -### BEGIN INIT INFO -# Provides: docker -# Required-Start: $local_fs $network $remote_fs -# Required-Stop: $local_fs $network $remote_fs -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -# Short-Description: Docker Engine -# Description: Docker container runtime -### END INIT INFO - -DESC="Docker Engine" -DAEMON=/usr/bin/dockerd -DAEMON_ARGS="--group docker" -PIDFILE=/var/run/docker.pid -SCRIPTNAME=/etc/init.d/docker - -[ -x "$DAEMON" ] || exit 0 - -. /lib/lsb/init-functions - -case "$1" in - start) - log_daemon_msg "Starting $DESC" "docker" - start-stop-daemon --start --background --pidfile "$PIDFILE" \ - --exec "$DAEMON" -- $DAEMON_ARGS - log_end_msg $? +case "${1:-}" in + --preflight) + require_script "$CHECK_SCRIPT" "Preflight (wallarm-ct-check.sh)" + exec "$CHECK_SCRIPT" "${@:2}" ;; - stop) - log_daemon_msg "Stopping $DESC" "docker" - start-stop-daemon --stop --pidfile "$PIDFILE" --retry 10 - log_end_msg $? + --install) + require_script "$DEPLOY_SCRIPT" "Deploy (wallarm-ct-deploy.sh)" + exec sudo "$DEPLOY_SCRIPT" "${@:2}" ;; - restart) - $0 stop - sleep 1 - $0 start + --config) + require_script "$RECONF_SCRIPT" "Reconfigure (wallarm-ct-reconfigure.sh)" + exec sudo "$RECONF_SCRIPT" "${@:2}" ;; - status) - status_of_proc -p "$PIDFILE" "$DAEMON" docker + --remove) + require_script "$REMOVE_SCRIPT" "Uninstall (wallarm-ct-uninstall.sh)" + exec sudo "$REMOVE_SCRIPT" "${@:2}" + ;; + --status) + echo "Wallarm Docker Nodes:" + if command -v docker >/dev/null 2>&1; then + docker ps --filter "name=wallarm-" \ + --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" 2>/dev/null || true + else + echo " Docker is not installed or not in PATH." + fi + ;; + --help|-h) + show_help ;; *) - echo "Usage: $SCRIPTNAME {start|stop|restart|status}" - exit 3 + show_help + exit 1 ;; esac - -exit 0 -EOF - - sudo chmod +x /etc/init.d/docker - sudo update-rc.d docker defaults - - log_message "INFO" "Starting Docker service (SysV init)..." - if ! sudo service docker start; then - log_message "ERROR" "service docker start failed with exit code: $?" - fail_with_remediation "Failed to start Docker service (SysV init)" \ -"Docker service failed to start under SysV init. Common causes: -1. Missing iptables (Docker static binaries require iptables v1.4+ for network bridge) -2. Docker socket or port conflicts -3. Missing dependencies or configuration errors -4. The docker group may not exist or be accessible - -Check service status: - sudo service docker status - -Verify iptables is installed: - which iptables || echo 'iptables not found' - iptables --version 2>/dev/null || echo 'Cannot check version' - -Install iptables if missing: - # Debian/Ubuntu: sudo apt-get update && sudo apt-get install -y iptables - # RHEL/CentOS: sudo yum install -y iptables - # Alpine: sudo apk add iptables - -Verify docker group exists: - getent group docker - -Check for Docker logs: - sudo dockerd --group docker --debug 2>&1 | head -50" - fi - ;; - - *) - log_message "WARNING" "Unknown init system '$INIT_SYSTEM', trying systemd defaults" - sudo systemctl daemon-reload 2>/dev/null || true - sudo systemctl enable docker 2>/dev/null || true - sudo systemctl start docker 2>/dev/null || { - log_message "ERROR" "Failed to start Docker with unknown init system" - echo -e "${YELLOW}Please start Docker manually and re-run the script${NC}" - exit 1 - } - ;; - esac - - # Verify Docker is running - log_message "INFO" "Verifying Docker service..." - sleep 3 # Give Docker time to start - - if ! sudo docker info >/dev/null 2>&1; then - fail_with_remediation "Docker failed to start" \ - "Docker installation completed but service failed to start: -1. Check Docker logs: journalctl -u docker (systemd) or /var/log/docker.log -2. Verify iptables is installed (Docker static binaries require iptables v1.4+): - which iptables || echo 'iptables not found' - iptables --version 2>/dev/null || echo 'Cannot check version' -3. Install iptables if missing: - # Debian/Ubuntu: sudo apt-get update && sudo apt-get install -y iptables - # RHEL/CentOS: sudo yum install -y iptables - # Alpine: sudo apk add iptables -4. Verify configuration: sudo dockerd --debug -5. Manual start: sudo dockerd --group docker &" - fi - - # Verify Docker is using VFS storage driver - log_message "INFO" "Verifying Docker storage driver configuration..." - if sudo docker info 2>/dev/null | grep -q "Storage Driver: vfs"; then - log_message "SUCCESS" "Docker configured with VFS storage driver (LXC compatible)" - else - log_message "WARNING" "Docker is not using VFS storage driver. Checking current driver..." - sudo docker info 2>/dev/null | grep "Storage Driver:" || log_message "ERROR" "Could not determine storage driver" - log_message "WARNING" "LXC compatibility may be limited without VFS storage driver" - fi - - # Add current user to docker group for passwordless docker commands - log_message "INFO" "Adding current user to docker group..." - - # Security notice: docker group grants root-equivalent privileges - echo -e "${YELLOW}${BOLD}Security Notice:${NC} Adding your user to the 'docker' group grants root-equivalent privileges." - echo -e "${YELLOW}Any user in the docker group can run commands as root on the host system.${NC}" - echo -e "${YELLOW}Only proceed if you understand and accept this security risk.${NC}" - - read -r -p "$(echo -e "${YELLOW}Add $(whoami) to docker group? (Y/n): ${NC}")" -n 1 - echo - if [[ ! $REPLY =~ ^[Nn]$ ]]; then - sudo usermod -aG docker "$(whoami)" 2>/dev/null && \ - log_message "SUCCESS" "Added $(whoami) to docker group (log out and back in for changes)" - else - log_message "WARNING" "Skipping docker group addition. You will need to use sudo for docker commands." - echo -e "${YELLOW}Note: You can manually add yourself to docker group later with:${NC}" - echo -e "${CYAN} sudo usermod -aG docker $(whoami)${NC}" - echo -e "${YELLOW}Then log out and back in for changes to take effect.${NC}" - fi - - log_message "SUCCESS" "Docker Engine setup completed successfully" -} - -# ============================================================================== -# WALLARM NODE DEPLOYMENT -# ============================================================================== - -deploy_wallarm_node() { - log_message "INFO" "Deploying Wallarm filtering node..." - - # Load Wallarm Docker image (priority: Git Repositorys -> local dir -> current dir) - log_message "INFO" "Loading Wallarm Docker image..." - local image_loaded=false - - # 1. Try Git Repositorys download (primary source) - local git_image_file="wallarm-node-6.11.0-rc1.tar.gz" - if [ "$image_loaded" = "false" ]; then - log_message "INFO" "Attempting to download Wallarm image from Git Repositorys..." - if download_from_git "$GIT_WALLARM_IMAGE_URL" "$git_image_file" "Wallarm Docker image"; then - if verify_checksum "$git_image_file" "$GIT_WALLARM_CHECKSUM_URL" "Wallarm Docker image"; then - log_message "INFO" "Loading Wallarm image from Git Repositorys download..." - if gunzip -c "$git_image_file" | sudo docker load; then - log_message "SUCCESS" "Wallarm image loaded from Git Repositorys download" - image_loaded=true - else - log_message "ERROR" "Failed to load Wallarm image from Git Repositorys download" - fi - # Cleanup downloaded file - rm -f "$git_image_file" - else - log_message "WARNING" "Git Repositorys Wallarm image checksum verification failed" - rm -f "$git_image_file" - fi - fi - fi - - # 2. Check local images directory - if [ "$image_loaded" = "false" ] && [ -d "$LOCAL_IMAGE_DIR" ]; then - log_message "INFO" "Checking local images directory: $LOCAL_IMAGE_DIR" - local local_image="$LOCAL_IMAGE_DIR/wallarm-node-6.11.0-rc1.tar.gz" - local local_checksum="$LOCAL_IMAGE_DIR/wallarm-node-6.11.0-rc1.tar.gz.sha256" - - if [ -f "$local_image" ]; then - log_message "INFO" "Found local Wallarm image: $local_image" - if verify_checksum "$local_image" "$local_checksum" "local Wallarm image"; then - log_message "INFO" "Loading Wallarm image from local directory..." - if gunzip -c "$local_image" | sudo docker load; then - log_message "SUCCESS" "Wallarm image loaded from local directory" - image_loaded=true - else - log_message "ERROR" "Failed to load Wallarm image from local directory" - fi - else - log_message "WARNING" "Local Wallarm image checksum verification failed" - fi - fi - fi - - # 3. Check current directory for compressed image (tar.gz) - if [ "$image_loaded" = "false" ]; then - log_message "INFO" "Checking current directory for Wallarm image (tar.gz)..." - local gz_image - gz_image=$(ls wallarm-node-*.tar.gz 2>/dev/null | head -1) - if [ -n "$gz_image" ]; then - log_message "INFO" "Found compressed Wallarm image: $gz_image" - # Verify checksum if .sha256 file exists - local checksum_file="${gz_image}.sha256" - if [ -f "$checksum_file" ]; then - if ! verify_checksum "$gz_image" "$checksum_file" "Wallarm image"; then - log_message "WARNING" "Wallarm image checksum verification failed, but attempting load anyway" - fi - fi - log_message "INFO" "Loading compressed Wallarm image..." - if gunzip -c "$gz_image" | sudo docker load; then - log_message "SUCCESS" "Wallarm image loaded from compressed file" - image_loaded=true - else - log_message "ERROR" "Failed to load Wallarm image from $gz_image" - fi - fi - fi - - # 4. Check current directory for uncompressed image (tar) - existing fallback - if [ "$image_loaded" = "false" ]; then - log_message "INFO" "Checking current directory for Wallarm image (tar)..." - local tar_image - tar_image=$(ls wallarm-node-*.tar 2>/dev/null | head -1) - if [ -n "$tar_image" ]; then - log_message "INFO" "Found uncompressed Wallarm image: $tar_image" - if ! sudo docker load -i "$tar_image"; then - log_message "ERROR" "Failed to load Wallarm image from $tar_image" - else - log_message "SUCCESS" "Wallarm image loaded from uncompressed file" - image_loaded=true - fi - fi - fi - - - - # 6. Final fallback: no image available - if [ "$image_loaded" = "false" ]; then - fail_with_remediation "No Wallarm image available" \ - "Please provide a Wallarm Docker image using one of these methods: -1. Git Repositorys (primary): Ensure network access to $GIT_RAW_URL -2. Local images directory: Place wallarm-node-6.11.0-rc1.tar.gz and .sha256 in $LOCAL_IMAGE_DIR/ -3. Current directory: Place wallarm-node-*.tar.gz or wallarm-node-*.tar file in current directory - - Save for offline use: docker save $WALLARM_IMAGE_TARGET -o wallarm-node-latest.tar - -Re-run the script after providing the image." - fi - - # Ensure image is tagged with standard name (for consistency) - if [ "$image_loaded" = "true" ] && [ "$REGISTRY_REACHABLE" = "false" ]; then - # If we loaded from local file, tag the loaded image with standard name - local loaded_image_id - loaded_image_id=$(sudo docker images --format "{{.ID}}" --filter "dangling=false" | head -1) - if [ -n "$loaded_image_id" ]; then - sudo docker tag "$loaded_image_id" "$WALLARM_IMAGE_TARGET" - log_message "INFO" "Tagged loaded image as $WALLARM_IMAGE_TARGET" - fi - fi - -# Create nginx configuration -log_message "INFO" "Creating nginx configuration..." -local nginx_config="$INSTANCE_DIR/nginx.conf" - -# Start building the configuration file -sudo tee "$nginx_config" > /dev/null < /dev/null < /dev/null < /dev/null < /dev/null <> "\$LOG_FILE" - -# Stop existing container if running -sudo docker stop "\$CONTAINER_NAME" 2>/dev/null || true -sudo docker rm "\$CONTAINER_NAME" 2>/dev/null || true - -# Start new container -sudo docker run -d \\ - --name "\$CONTAINER_NAME" \\ - --restart always \\ - -p $INGRESS_PORT:80 \\ - -p $MONITORING_PORT:90 \\ - -e WALLARM_API_TOKEN="$WALLARM_TOKEN" \\ - -e WALLARM_API_HOST="$API_HOST" \\ - -e NGINX_BACKEND="$UPSTREAM_IP:$UPSTREAM_PORT" \\ - -e WALLARM_MODE="monitoring" \\ - -v "\$NGINX_CONFIG:/etc/nginx/http.d/default.conf:ro" \\ - $WALLARM_IMAGE_TARGET - -echo "\$(date) - Container started with ID: \$(sudo docker ps -q -f name=\$CONTAINER_NAME)" >> "\$LOG_FILE" - -# Verify container is running -sleep 3 -if sudo docker ps | grep -q "\$CONTAINER_NAME"; then - echo "\$(date) - Verification: Container is running" >> "\$LOG_FILE" - echo "Wallarm node \$CONTAINER_NAME started successfully" -else - echo "\$(date) - ERROR: Container failed to start" >> "\$LOG_FILE" - sudo docker logs "\$CONTAINER_NAME" >> "\$LOG_FILE" 2>&1 - exit 1 -fi -EOF - - sudo chmod +x "$start_script" - log_message "SUCCESS" "Start script created: $start_script" - - # Create init system service for automatic startup - log_message "INFO" "Creating service for automatic startup (init system: $INIT_SYSTEM)..." - - case "$INIT_SYSTEM" in - "systemd") - local service_file="/etc/systemd/system/wallarm-$INSTANCE_NAME.service" - sudo tee "$service_file" > /dev/null </dev/null || \ - log_message "WARNING" "Failed to enable systemd service (may already exist)" - ;; - - "openrc") - local service_file="/etc/init.d/wallarm-$INSTANCE_NAME" - sudo tee "$service_file" > /dev/null </dev/null || true - docker rm $INSTANCE_NAME 2>/dev/null || true - eend \$? -} -EOF - sudo chmod +x "$service_file" - sudo rc-update add "wallarm-$INSTANCE_NAME" default 2>/dev/null || \ - log_message "WARNING" "Failed to add OpenRC service (may already exist)" - ;; - - "sysvinit") - local service_file="/etc/init.d/wallarm-$INSTANCE_NAME" - sudo tee "$service_file" > /dev/null </dev/null || true - docker rm $INSTANCE_NAME 2>/dev/null || true - log_end_msg \$? - ;; - restart) - \$0 stop - sleep 2 - \$0 start - ;; - status) - if docker ps | grep -q "$INSTANCE_NAME"; then - echo "$INSTANCE_NAME is running" - exit 0 - else - echo "$INSTANCE_NAME is not running" - exit 1 - fi - ;; - *) - echo "Usage: \$0 {start|stop|restart|status}" - exit 3 - ;; -esac - -exit 0 -EOF - sudo chmod +x "$service_file" - sudo update-rc.d "wallarm-$INSTANCE_NAME" defaults 2>/dev/null || \ - log_message "WARNING" "Failed to add SysV init service (may already exist)" - ;; - - *) - log_message "WARNING" "Unknown init system, not creating service (manual start via $start_script)" - ;; - esac - - # Start the Wallarm node - log_message "INFO" "Starting Wallarm filtering node..." - if ! sudo "$start_script"; then - fail_with_remediation "Failed to start Wallarm node" \ - "Container failed to start. Check: -1. Docker logs: sudo docker logs $INSTANCE_NAME -2. Port conflicts: sudo ss -tlnp | grep ':$INGRESS_PORT\|:$MONITORING_PORT' -3. Docker status: sudo docker info -4. Manual start attempt: sudo $start_script" - fi - - log_message "SUCCESS" "Wallarm filtering node deployed successfully" - log_message "SUCCESS" " Container: $INSTANCE_NAME" - log_message "SUCCESS" " Ingress Port: $INGRESS_PORT" - log_message "SUCCESS" " Monitoring Port: $MONITORING_PORT" - log_message "SUCCESS" " Upstream: $UPSTREAM_IP:$UPSTREAM_PORT" - log_message "SUCCESS" " Config Directory: $INSTANCE_DIR" -} - -# ============================================================================== -# DEPLOYMENT VERIFICATION -# ============================================================================== - -verify_deployment() { - log_message "INFO" "Verifying Wallarm deployment..." - - # Check if container is running - log_message "INFO" "Checking if container is running..." - if ! sudo docker ps | grep -q "$INSTANCE_NAME"; then - fail_with_remediation "Wallarm container is not running" \ - "Container failed to start or crashed: -1. Check container logs: sudo docker logs $INSTANCE_NAME -2. Check Docker service: sudo systemctl status docker (or equivalent) -3. Manual start: sudo $INSTANCE_DIR/start.sh" - fi - log_message "SUCCESS" "Container is running" - - # Test ingress port - log_message "INFO" "Testing ingress port $INGRESS_PORT..." - if ! check_port_available "$INGRESS_PORT"; then - log_message "SUCCESS" "Ingress port $INGRESS_PORT is in use (as expected)" - else - log_message "WARNING" "Ingress port $INGRESS_PORT appears available (container may not be listening)" - fi - - # Test monitoring port - log_message "INFO" "Testing monitoring port $MONITORING_PORT..." - if ! check_port_available "$MONITORING_PORT"; then - log_message "SUCCESS" "Monitoring port $MONITORING_PORT is in use (as expected)" - else - log_message "WARNING" "Monitoring port $MONITORING_PORT appears available" - fi - - # Test health check endpoint - log_message "INFO" "Testing health check endpoint..." - local health_check_url="http://localhost:$INGRESS_PORT/health" - if curl -sf --connect-timeout 5 "$health_check_url" >/dev/null 2>&1; then - log_message "SUCCESS" "Health check endpoint responsive" - else - log_message "WARNING" "Health check endpoint not responsive (may need time to start)" - sleep 5 - if curl -sf --connect-timeout 5 "$health_check_url" >/dev/null 2>&1; then - log_message "SUCCESS" "Health check endpoint now responsive" - else - log_message "WARNING" "Health check endpoint still not responsive (check nginx config)" - fi - fi - - # Test handshake through filtering node - log_message "INFO" "Testing handshake through filtering node to upstream..." - local test_url="http://localhost:$INGRESS_PORT/" - if curl -sfI --connect-timeout 10 "$test_url" >/dev/null 2>&1; then - log_message "SUCCESS" "Handshake successful: filtering node can reach upstream" - else - log_message "WARNING" "Handshake failed (upstream may not be responding)" - log_message "INFO" "Checking if upstream is directly reachable..." - if timeout 5 bash -c "cat < /dev/null > /dev/tcp/$UPSTREAM_IP/$UPSTREAM_PORT" 2>/dev/null; then - log_message "ERROR" "Upstream is reachable but filtering node cannot proxy" - echo -e "${YELLOW}Possible nginx configuration issue. Check:${NC}" - echo -e "1. Container logs: sudo docker logs $INSTANCE_NAME" - echo -e "2. Nginx config: sudo docker exec $INSTANCE_NAME cat /etc/nginx/http.d/default.conf" - else - log_message "WARNING" "Upstream server is not reachable (as previously warned)" - fi - fi - - # Check Wallarm cloud synchronization - log_message "INFO" "Checking Wallarm cloud synchronization (this may take 30 seconds)..." - echo -e "${YELLOW}Note: Full synchronization with Wallarm cloud may take several minutes.${NC}" - echo -e "${YELLOW}You can check sync status in Wallarm Console.${NC}" - - # Quick test: check container logs for synchronization messages - if sudo docker logs "$INSTANCE_NAME" 2>&1 | tail -20 | grep -i "sync\|connected\|token" >/dev/null 2>&1; then - log_message "SUCCESS" "Wallarm node appears to be communicating with cloud" - else - log_message "WARNING" "No cloud synchronization messages in logs yet (may need time)" - fi - - log_message "SUCCESS" "Deployment verification completed" - echo -e "\n${GREEN}${BOLD}Verification Summary:${NC}" - echo -e " ${GREEN}✓${NC} Container running: $INSTANCE_NAME" - echo -e " ${GREEN}✓${NC} Ingress port: $INGRESS_PORT" - echo -e " ${GREEN}✓${NC} Monitoring port: $MONITORING_PORT" - echo -e " ${GREEN}✓${NC} Upstream: $UPSTREAM_IP:$UPSTREAM_PORT" - echo -e " ${GREEN}✓${NC} Cloud region: $CLOUD_REGION ($API_HOST)" -} - -# ============================================================================== -# MAIN FUNCTION -# ============================================================================== - -main() { - clear - echo -e "${BLUE}${BOLD}" - echo "╔══════════════════════════════════════════════════════════════╗" - echo "║ WALLARM DEPLOYMENT SCRIPT (Docker) - V1.3 ║" - echo "║ LXC-Optimized Filtering Node Deployment ║" - echo "╚══════════════════════════════════════════════════════════════╝${NC}" - echo -e "\n${YELLOW}Starting deployment at: $(date)${NC}" - - # Initialize logging - # Create logs directory if it doesn't exist - local log_dir="${HOME:-.}/logs" - if [ ! -d "$log_dir" ]; then - if ! mkdir -p "$log_dir"; then - echo -e "${YELLOW}Cannot create log directory $log_dir, falling back to current directory...${NC}" - log_dir="." - fi - fi - - LOG_FILE="$log_dir/wallarm-deployment.log" - if ! : > "$LOG_FILE"; then - echo -e "${RED}Cannot create log file at $LOG_FILE${NC}" - echo -e "${YELLOW}Falling back to current directory...${NC}" - LOG_FILE="./wallarm-deployment.log" - : > "$LOG_FILE" 2>/dev/null || true - fi - if ! chmod 644 "$LOG_FILE" 2>/dev/null; then - echo -e "${YELLOW}Warning: Could not set permissions on log file${NC}" - fi - - log_message "INFO" "=== Wallarm Deployment Started ===" - - # SSL security warning - if [ "$INSECURE_SSL" = "1" ]; then - log_message "WARNING" "SSL certificate validation is DISABLED (insecure). Set WALLARM_INSECURE_SSL=0 to enable validation." - fi - - # Phase 1: Verify preflight check - log_message "INFO" "=== PHASE 1: PREFLIGHT CHECK VERIFICATION ===" - verify_preflight_check - - # Phase 2: Configuration collection - log_message "INFO" "=== PHASE 2: CONFIGURATION COLLECTION ===" - select_cloud_region - collect_configuration - - # Phase 3: Docker engine setup (LXC optimized) - log_message "INFO" "=== PHASE 3: DOCKER ENGINE SETUP (LXC OPTIMIZED) ===" - setup_docker_engine - - # Phase 4: Deployment - log_message "INFO" "=== PHASE 4: DEPLOYMENT ===" - deploy_wallarm_node - - # Phase 5: Verification - log_message "INFO" "=== PHASE 5: VERIFICATION ===" - verify_deployment - - # Success message - log_message "SUCCESS" "=== WALLARM DEPLOYMENT COMPLETED SUCCESSFULLY ===" - echo -e "\n${GREEN}${BOLD}╔══════════════════════════════════════════════════════════════╗${NC}" - echo -e "${GREEN}${BOLD}║ WALLARM FILTERING NODE DEPLOYMENT SUCCESSFUL ║${NC}" - echo -e "${GREEN}${BOLD}╚══════════════════════════════════════════════════════════════╝${NC}" - echo -e "\n${CYAN}The Wallarm filtering node is now active and protecting your application.${NC}" - echo -e "${YELLOW}Full deployment log: $LOG_FILE${NC}" - echo -e "${YELLOW}Instance directory: $INSTANCE_DIR${NC}" - echo -e "\n${GREEN}To stop the node:${NC} sudo docker stop $INSTANCE_NAME" - echo -e "${GREEN}To restart:${NC} sudo $INSTANCE_DIR/start.sh" - echo -e "${GREEN}To view logs:${NC} sudo docker logs -f $INSTANCE_NAME" - echo -e "\n${MAGENTA}${BOLD}Deployment completed successfully!${NC}" - echo -e "\n${YELLOW}Important next steps:${NC}" - echo -e "1. Monitor sync status in Wallarm Console" - echo -e "2. Test attack detection with safe test: curl http://localhost:$INGRESS_PORT/?wallarm_test=1" - echo -e "3. Review logs periodically: sudo docker logs --tail 50 $INSTANCE_NAME" -} - -# ============================================================================== -# SCRIPT EXECUTION -# ============================================================================== - -# Ensure we're in bash -if [ -z "$BASH_VERSION" ]; then - echo "Error: This script must be run with bash" >&2 - exit 1 -fi - -# Run main function -main "$@" diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..97c31b7 --- /dev/null +++ b/go.mod @@ -0,0 +1,7 @@ +module git.sechpoint.app/customer-engineering/wallarm + +go 1.24 + +require golang.org/x/crypto v0.36.0 + +require golang.org/x/sys v0.31.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..4f7e570 --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= diff --git a/internal/preflight/preflight.go b/internal/preflight/preflight.go new file mode 100644 index 0000000..316816b --- /dev/null +++ b/internal/preflight/preflight.go @@ -0,0 +1,169 @@ +// Package preflight runs system readiness checks before any deployment. +// It is called on every binary start and returns a structured report. +package preflight + +import ( + "fmt" + "os" + + "git.sechpoint.app/customer-engineering/wallarm/internal/shared" +) + +// Result holds the outcome of all preflight checks. +type Result struct { + Passed bool `json:"passed"` + Checks []Check `json:"checks"` + USReachable bool `json:"us_reachable"` + EUReachable bool `json:"eu_reachable"` +} + +// Check represents a single preflight check. +type Check struct { + Name string `json:"name"` + Passed bool `json:"passed"` + Detail string `json:"detail,omitempty"` + Warning bool `json:"warning,omitempty"` +} + +// Cloud endpoints (from wallarm-lib.sh) +var euEndpoints = []string{ + "api.wallarm.com:443", + "node-data0.eu1.wallarm.com:443", + "node-data1.eu1.wallarm.com:443", +} + +var usEndpoints = []string{ + "us1.api.wallarm.com:443", + "node-data0.us1.wallarm.com:443", + "node-data1.us1.wallarm.com:443", +} + +// Run executes all preflight checks and returns the result. +func Run() Result { + r := Result{Passed: true} + + // 1. Root check + if os.Geteuid() != 0 { + r.Checks = append(r.Checks, Check{ + Name: "root", Passed: false, Detail: "must run as root for package installation and system config", + }) + r.Passed = false + } else { + r.Checks = append(r.Checks, Check{Name: "root", Passed: true}) + } + + // 2. Init system + initSys := shared.InitSystem() + if initSys != "systemd" { + r.Checks = append(r.Checks, Check{ + Name: "init", Passed: false, Detail: fmt.Sprintf("requires systemd, detected: %s", initSys), + }) + r.Passed = false + } else { + r.Checks = append(r.Checks, Check{Name: "init", Passed: true, Detail: "systemd"}) + } + + // 3. Architecture + arch := shared.Arch() + supported := arch == "x86_64" || arch == "aarch64" + r.Checks = append(r.Checks, Check{ + Name: "arch", Passed: supported, Detail: arch, + }) + if !supported { + r.Passed = false + } + + // 4. OS + id, ver := shared.OSInfo() + r.Checks = append(r.Checks, Check{Name: "os", Passed: true, Detail: id + " " + ver}) + + // 5. Required commands + requiredCmds := []string{"curl", "systemctl", "sed", "mkdir", "rm"} + for _, cmd := range requiredCmds { + ok := shared.CommandExists(cmd) + r.Checks = append(r.Checks, Check{Name: "cmd:" + cmd, Passed: ok}) + if !ok { + r.Passed = false + } + } + + // 6. Installer reachability + installerOk := shared.HTTPHead("https://repo.wallarm.com") + r.Checks = append(r.Checks, Check{ + Name: "installer_reachable", Passed: installerOk, + Detail: "repo.wallarm.com", + }) + if !installerOk { + r.Checks[len(r.Checks)-1].Warning = true + } + + // 7. Cloud endpoints + r.USReachable = checkEndpoints(usEndpoints) + r.EUReachable = checkEndpoints(euEndpoints) + r.Checks = append(r.Checks, Check{ + Name: "cloud:US", Passed: r.USReachable, + Detail: fmt.Sprintf("%d/%d reachable", countReachable(usEndpoints), len(usEndpoints)), + }) + r.Checks = append(r.Checks, Check{ + Name: "cloud:EU", Passed: r.EUReachable, + Detail: fmt.Sprintf("%d/%d reachable", countReachable(euEndpoints), len(euEndpoints)), + }) + if !r.USReachable && !r.EUReachable { + r.Passed = false + } + + // 8. Disk space (>= 2GB) + free, err := shared.FreeDiskMB("/opt") + if err == nil && free < 2048 { + r.Checks = append(r.Checks, Check{ + Name: "disk", Passed: false, + Detail: fmt.Sprintf("%d MB free (need >= 2048 MB)", free), + }) + r.Passed = false + } else { + r.Checks = append(r.Checks, Check{Name: "disk", Passed: true, Detail: fmt.Sprintf("%d MB free", free)}) + } + + // 9. Memory (>= 2GB, warning only) + mem, err := shared.FreeMemoryMB() + if err == nil && mem < 2048 { + r.Checks = append(r.Checks, Check{ + Name: "memory", Passed: true, Warning: true, + Detail: fmt.Sprintf("%d MB (2GB+ recommended)", mem), + }) + } else { + r.Checks = append(r.Checks, Check{Name: "memory", Passed: true, Detail: fmt.Sprintf("%d MB", mem)}) + } + + return r +} + +func checkEndpoints(endpoints []string) bool { + for _, ep := range endpoints { + host, _ := splitHostPort(ep) + if shared.TCPConnect(host, 443) { + return true + } + } + return false +} + +func countReachable(endpoints []string) int { + n := 0 + for _, ep := range endpoints { + host, _ := splitHostPort(ep) + if shared.TCPConnect(host, 443) { + n++ + } + } + return n +} + +func splitHostPort(addr string) (string, string) { + for i := len(addr) - 1; i >= 0; i-- { + if addr[i] == ':' { + return addr[:i], addr[i+1:] + } + } + return addr, "" +} diff --git a/internal/shared/shared.go b/internal/shared/shared.go new file mode 100644 index 0000000..8ef7e2a --- /dev/null +++ b/internal/shared/shared.go @@ -0,0 +1,174 @@ +// Package shared provides validation, connectivity, and system detection +// utilities ported from the bash wallarm-lib.sh library. +package shared + +import ( + "fmt" + "os" + "os/exec" + "runtime" + "strconv" + "strings" +) + +// ─── System Detection ──────────────────────────────────────────────── + +// InitSystem returns the detected init system: systemd, openrc, sysvinit, upstart, or unknown. +func InitSystem() string { + if runtime.GOOS == "darwin" { + return "darwin" + } + if _, err := exec.LookPath("systemctl"); err == nil { + return "systemd" + } + if _, err := os.Stat("/sbin/openrc-run"); err == nil { + return "openrc" + } + if _, err := os.Stat("/etc/init.d"); err == nil { + return "sysvinit" + } + if _, err := os.Stat("/sbin/upstart"); err == nil { + return "upstart" + } + return "unknown" +} + +// OSInfo returns (os_id, version_id) from /etc/os-release. +func OSInfo() (id, version string) { + data, err := os.ReadFile("/etc/os-release") + if err != nil { + return strings.ToLower(runtime.GOOS), "unknown" + } + for _, line := range strings.Split(string(data), "\n") { + if strings.HasPrefix(line, "ID=") { + id = strings.Trim(strings.TrimPrefix(line, "ID="), `"`) + } + if strings.HasPrefix(line, "VERSION_ID=") { + version = strings.Trim(strings.TrimPrefix(line, "VERSION_ID="), `"`) + } + } + if id == "" { + id = strings.ToLower(runtime.GOOS) + } + return id, version +} + +// Arch returns the normalized architecture: x86_64, aarch64, or armhf. +func Arch() string { + switch runtime.GOARCH { + case "amd64": + return "x86_64" + case "arm64": + return "aarch64" + case "arm": + return "armhf" + default: + return runtime.GOARCH + } +} + +// ─── Validation ────────────────────────────────────────────────────── + +// ValidateIP checks whether s is a valid IPv4 address. +func ValidateIP(s string) error { + parts := strings.Split(s, ".") + if len(parts) != 4 { + return fmt.Errorf("invalid IPv4: %s", s) + } + for _, p := range parts { + n, err := strconv.Atoi(p) + if err != nil || n < 0 || n > 255 { + return fmt.Errorf("invalid IPv4 octet: %s", p) + } + } + return nil +} + +// ValidateCIDR checks whether s is a valid IPv4 address with optional /prefix. +func ValidateCIDR(s string) error { + ip := s + prefix := "" + if idx := strings.IndexByte(s, '/'); idx != -1 { + ip, prefix = s[:idx], s[idx+1:] + } + if err := ValidateIP(ip); err != nil { + return err + } + if prefix != "" { + n, err := strconv.Atoi(prefix) + if err != nil || n < 0 || n > 32 { + return fmt.Errorf("invalid CIDR prefix: %s", prefix) + } + } + return nil +} + +// CommandExists returns true if cmd is in PATH or in common system directories. +func CommandExists(cmd string) bool { + if _, err := exec.LookPath(cmd); err == nil { + return true + } + for _, dir := range []string{"/usr/sbin", "/sbin", "/usr/local/sbin", "/usr/bin", "/bin", "/usr/local/bin"} { + if _, err := os.Stat(dir + "/" + cmd); err == nil { + return true + } + } + return false +} + +// ─── Resource Checks ───────────────────────────────────────────────── + +// FreeDiskMB returns available disk space in MB for the given path. +func FreeDiskMB(path string) (int64, error) { + cmd := exec.Command("df", "-k", path) + out, err := cmd.Output() + if err != nil { + return 0, err + } + lines := strings.Split(strings.TrimSpace(string(out)), "\n") + if len(lines) < 2 { + return 0, fmt.Errorf("unexpected df output") + } + fields := strings.Fields(lines[1]) + if len(fields) < 4 { + return 0, fmt.Errorf("unexpected df fields") + } + kb, err := strconv.ParseInt(fields[3], 10, 64) + if err != nil { + return 0, err + } + return kb / 1024, nil // convert KB to MB +} + +// FreeMemoryMB returns available memory in MB. +func FreeMemoryMB() (int64, error) { + cmd := exec.Command("free", "-m") + out, err := cmd.Output() + if err != nil { + return 0, err + } + lines := strings.Split(strings.TrimSpace(string(out)), "\n") + if len(lines) < 2 { + return 0, fmt.Errorf("unexpected free output") + } + fields := strings.Fields(lines[1]) + if len(fields) < 2 { + return 0, fmt.Errorf("unexpected free fields") + } + return strconv.ParseInt(fields[1], 10, 64) +} + +// ─── Connectivity ──────────────────────────────────────────────────── + +// TCPConnect tests whether host:port accepts a TCP connection. +func TCPConnect(host string, port int) bool { + cmd := exec.Command("timeout", "5", "bash", "-c", + fmt.Sprintf("echo >/dev/tcp/%s/%d 2>/dev/null", host, port)) + return cmd.Run() == nil +} + +// HTTPHead returns true if the URL returns a successful status. +func HTTPHead(url string) bool { + cmd := exec.Command("curl", "-fsSL", "--connect-timeout", "10", url) + return cmd.Run() == nil +} diff --git a/internal/state/state.go b/internal/state/state.go new file mode 100644 index 0000000..e8560f2 --- /dev/null +++ b/internal/state/state.go @@ -0,0 +1,83 @@ +// Package state manages the persistent deployment state file (~/.wallarm/state.json). +package state + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" +) + +// StateDir is where wallarm stores its state. +const StateDir = ".wallarm" + +// State represents the persistent deployment state. +type State struct { + DeploymentType string `json:"deployment_type,omitempty"` // docker or native + CloudRegion string `json:"cloud_region,omitempty"` // US or EU + APIHost string `json:"api_host,omitempty"` // e.g., api.wallarm.com + Nodes []Node `json:"nodes"` +} + +// Node represents a single deployed Wallarm node (docker container or native systemd unit). +type Node struct { + Name string `json:"name"` + Type string `json:"type"` // docker or native + Address string `json:"address,omitempty"` // listen address for native + Port int `json:"port,omitempty"` // ingress port for docker + UpstreamIP string `json:"upstream_ip,omitempty"` // docker + UpstreamPort int `json:"upstream_port,omitempty"` // docker + Status string `json:"status"` // running, stopped, unknown + CreatedAt string `json:"created_at"` +} + +// Path returns the full path to the state file. +func Path() string { + home, err := os.UserHomeDir() + if err != nil { + home = "/root" + } + return filepath.Join(home, StateDir, "state.json") +} + +// Load reads and parses the state file. Returns nil if the file doesn't exist. +func Load() (*State, error) { + p := Path() + data, err := os.ReadFile(p) + if os.IsNotExist(err) { + return nil, nil + } + if err != nil { + return nil, fmt.Errorf("read state: %w", err) + } + var s State + if err := json.Unmarshal(data, &s); err != nil { + return nil, fmt.Errorf("parse state: %w", err) + } + return &s, nil +} + +// Save writes the state to disk, creating directories as needed. +func Save(s *State) error { + p := Path() + if err := os.MkdirAll(filepath.Dir(p), 0700); err != nil { + return fmt.Errorf("create state dir: %w", err) + } + data, err := json.MarshalIndent(s, "", " ") + if err != nil { + return fmt.Errorf("marshal state: %w", err) + } + if err := os.WriteFile(p, data, 0600); err != nil { + return fmt.Errorf("write state: %w", err) + } + return nil +} + +// HasDeployment returns true if a state file exists with at least one node. +func HasDeployment() bool { + s, err := Load() + if err != nil || s == nil { + return false + } + return len(s.Nodes) > 0 +} diff --git a/internal/tunnel/tunnel.go b/internal/tunnel/tunnel.go new file mode 100644 index 0000000..83467cb --- /dev/null +++ b/internal/tunnel/tunnel.go @@ -0,0 +1,146 @@ +// Package tunnel provides a reverse SSH tunnel over TLS:443 via a Zoraxy edge proxy. +// It opens an outbound TLS connection to the configured jumphost, authenticates +// via SSH, and establishes a reverse port forward so you can reach the target VM +// through sechpoint.app. +package tunnel + +import ( + "crypto/tls" + "fmt" + "io" + "net" + "os" + "os/signal" + "time" + + "golang.org/x/crypto/ssh" +) + +// Config holds the tunnel connection parameters. +type Config struct { + Jumphost string // e.g., "ssh.sechpoint.app:443" + RemotePort int // Port on the jumphost that forwards to target's SSH + LocalSSHPort int // SSH port on the target VM (usually 22) + User string // SSH user on the jumphost + KeyPath string // Path to private key for authentication + KeyBytes []byte // Raw private key bytes (takes precedence over KeyPath) +} + +// DefaultConfig returns a Config with sensible defaults. +func DefaultConfig() Config { + return Config{ + Jumphost: "ssh.sechpoint.app:443", + RemotePort: 9042, + LocalSSHPort: 22, + User: "wallarm-tunnel", + } +} + +// Start opens a reverse SSH tunnel over TLS and keeps it alive. +// It blocks until SIGINT or connection failure. +func Start(cfg Config) error { + // Load the private key + var signer ssh.Signer + if len(cfg.KeyBytes) > 0 { + var err error + signer, err = ssh.ParsePrivateKey(cfg.KeyBytes) + if err != nil { + return fmt.Errorf("parse embedded key: %w", err) + } + } else { + keyBytes, err := os.ReadFile(cfg.KeyPath) + if err != nil { + return fmt.Errorf("read key %s: %w", cfg.KeyPath, err) + } + signer, err = ssh.ParsePrivateKey(keyBytes) + if err != nil { + return fmt.Errorf("parse key: %w", err) + } + } + + sshConfig := &ssh.ClientConfig{ + User: cfg.User, + Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)}, + HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { + return nil // Accept all host keys (trusted infrastructure) + }, + Timeout: 10 * time.Second, + } + + // TLS dial to the Zoraxy edge (port 443) + tlsConn, err := tls.Dial("tcp", cfg.Jumphost, &tls.Config{ + InsecureSkipVerify: false, + }) + if err != nil { + return fmt.Errorf("TLS dial %s: %w", cfg.Jumphost, err) + } + + // SSH over TLS + sshConn, chans, reqs, err := ssh.NewClientConn(tlsConn, cfg.Jumphost, sshConfig) + if err != nil { + tlsConn.Close() + return fmt.Errorf("SSH handshake: %w", err) + } + client := ssh.NewClient(sshConn, chans, reqs) + defer client.Close() + + // Request reverse port forward: jumphost:RemotePort -> localhost:LocalSSHPort + remoteAddr := fmt.Sprintf("0.0.0.0:%d", cfg.RemotePort) + localAddr := fmt.Sprintf("localhost:%d", cfg.LocalSSHPort) + + listener, err := client.Listen("tcp", remoteAddr) + if err != nil { + return fmt.Errorf("remote listen %s: %w", remoteAddr, err) + } + defer listener.Close() + + fmt.Printf("Tunnel established: %s -> %s\n", remoteAddr, localAddr) + fmt.Printf("Connect: ssh -p %d root@%s\n", cfg.RemotePort, cfg.Jumphost) + + // Handle incoming connections on the remote listener + go func() { + for { + remoteConn, err := listener.Accept() + if err != nil { + return + } + go forwardConnection(remoteConn, localAddr) + } + }() + + // Keep alive until signal + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, os.Interrupt) + + // Heartbeat every 30s + ticker := time.NewTicker(30 * time.Second) + defer ticker.Stop() + + for { + select { + case <-sigCh: + fmt.Println("\nTunnel closed.") + return nil + case <-ticker.C: + _, _, err := client.SendRequest("keepalive@wallarm", true, nil) + if err != nil { + return fmt.Errorf("keepalive failed: %w", err) + } + } + } +} + +func forwardConnection(remoteConn net.Conn, localAddr string) { + defer remoteConn.Close() + localConn, err := net.DialTimeout("tcp", localAddr, 10*time.Second) + if err != nil { + return + } + defer localConn.Close() + + go func() { + io.Copy(localConn, remoteConn) + remoteConn.Close() + }() + io.Copy(remoteConn, localConn) +} diff --git a/setup.sh b/setup.sh index 656dfb2..f7a5c0b 100755 --- a/setup.sh +++ b/setup.sh @@ -169,7 +169,7 @@ for deploy_type in "${DEPLOY_TYPES[@]}"; do docker) echo -e "${CYAN}Docker deployment next steps:${NC}" echo -e " 1. Run the preflight check: ${YELLOW}./deploy/wallarm-ct-check.sh${NC}" - echo -e " 2. Deploy a Wallarm node: ${YELLOW}./deploy/wallarm-docker.sh${NC}" + echo -e " 2. Deploy a Wallarm node: ${YELLOW}./deploy/wallarm-ct-deploy.sh${NC}" echo -e " 3. Reconfigure existing node: ${YELLOW}./deploy/wallarm-ct-reconfigure.sh${NC}" echo -e " 4. Uninstall a node: ${YELLOW}./deploy/wallarm-ct-uninstall.sh${NC}" echo