#!/bin/bash # Check if running as root if [[ $EUID -ne 0 ]]; then echo "Error: This script must be run as root. Use sudo." exit 1 fi # Initial settings set -euo pipefail # Log file LOG_FILE="/var/log/install_wg_easy.log" # Function to log messages log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" } # Function to check if a command exists check_command() { if ! command -v "$1" &> /dev/null; then log "Error: $1 not found. Please install it first." exit 1 fi } # Function to detect package manager get_package_manager() { if command -v yum &> /dev/null; then echo "yum" else log "Error: yum package manager not found." exit 1 fi } # Function to set up the swapfile with optimizations setup_swapfile() { local swap_size="4G" local swap_file="/swapfile" local swap_size_mb=4096 # 4GB in MB for dd fallback log "Setting up swapfile of $swap_size..." # Check if swapfile already exists if [[ -f "$swap_file" ]]; then if swapon --show | grep -q "$swap_file"; then log "Swapfile already exists and active at $swap_file." return else log "Swapfile exists at $swap_file but is not active. Please check configuration." exit 1 fi fi # Create and configure swapfile with progress indication log "Creating swapfile - this may take a few minutes for better performance..." if ! fallocate -l "$swap_size" "$swap_file" 2>> "$LOG_FILE"; then log "Warning: fallocate failed. Using dd with progress indication..." echo "Creating swapfile with dd (this will take several minutes)..." # Use dd with progress and reduced I/O priority if ! nice -n 10 ionice -c 3 dd if=/dev/zero of="$swap_file" bs=1M count="$swap_size_mb" status=progress 2>> "$LOG_FILE"; then log "Error creating swapfile with dd." exit 1 fi fi chmod 600 "$swap_file" 2>> "$LOG_FILE" || { log "Error setting swapfile permissions."; exit 1; } mkswap "$swap_file" >> "$LOG_FILE" 2>&1 || { log "Error formatting swapfile."; exit 1; } swapon "$swap_file" >> "$LOG_FILE" 2>&1 || { log "Error activating swapfile."; exit 1; } # Add to fstab if not already present if ! grep -q "$swap_file" /etc/fstab; then echo "$swap_file none swap sw 0 0" >> /etc/fstab log "Swapfile added to /etc/fstab." else log "Swapfile already present in /etc/fstab." fi log "Swapfile configured successfully!" } # Function to install Docker with optimizations for low-resource servers install_docker() { # Check if Docker is already installed if command -v docker &> /dev/null; then log "Docker is already installed. Checking if it's running..." if systemctl is-active --quiet docker; then log "Docker is already installed and running. Skipping installation." return 0 else log "Docker is installed but not running. Starting Docker..." systemctl start docker >> "$LOG_FILE" 2>&1 || { log "Error starting Docker."; exit 1; } systemctl enable docker >> "$LOG_FILE" 2>&1 || { log "Error enabling Docker."; exit 1; } log "Docker started successfully!" return 0 fi fi log "Installing Docker with CPU-friendly settings using yum..." # Set environment for non-interactive installation export DEBIAN_FRONTEND=noninteractive log "Adding Docker repository..." yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo >> "$LOG_FILE" 2>&1 || { log "Error adding Docker repository." exit 1 } log "Installing Docker CE (this may take several minutes, please be patient)..." echo "Note: The installation process is running with reduced CPU priority to prevent connection timeouts." # Use nice to reduce CPU priority nice -n 10 yum install -y docker-ce >> "$LOG_FILE" 2>&1 || { log "Error installing Docker." log "Retrying with more conservative settings..." sleep 5 nice -n 15 yum install -y docker-ce >> "$LOG_FILE" 2>&1 || { log "Error installing Docker after retry." exit 1 } } log "Starting and enabling Docker..." systemctl start docker >> "$LOG_FILE" 2>&1 || { log "Error starting Docker."; exit 1; } systemctl enable docker >> "$LOG_FILE" 2>&1 || { log "Error enabling Docker."; exit 1; } # Wait a moment for Docker to fully initialize sleep 3 # Verify Docker is working if docker --version >> "$LOG_FILE" 2>&1; then log "Docker installed and configured successfully!" else log "Warning: Docker installed but may not be fully ready. Waiting a bit longer..." sleep 5 if docker --version >> "$LOG_FILE" 2>&1; then log "Docker is now working correctly!" else log "Error: Docker installation may have issues. Check manually with 'docker --version'" exit 1 fi fi } # Function to set up the iptable_nat module - ENHANCED setup_iptables() { log "Configuring iptables/NAT functionality..." # First, explicitly load the critical modules log "Loading essential iptables modules..." if modprobe iptable_nat 2>>"$LOG_FILE"; then log "Successfully loaded iptable_nat module." else log "Warning: Could not load iptable_nat module (may be built-in)." fi if modprobe ip_tables 2>>"$LOG_FILE"; then log "Successfully loaded ip_tables module." else log "Warning: Could not load ip_tables module (may be built-in)." fi # On modern systems, modules are often built-in or loaded automatically # when needed. First, check if iptables works (most important test) if iptables -t nat -L >/dev/null 2>&1; then log "iptables NAT functionality is working correctly." log "Modules are either built-in or loaded automatically." return 0 fi log "iptables NAT not accessible, attempting to load additional modules..." # List of additional modules that might be needed modules=("nf_nat" "nf_conntrack" "iptable_filter") for module in "${modules[@]}"; do log "Trying to load module: $module" if modprobe "$module" 2>/dev/null; then log "Successfully loaded module: $module" else log "Module $module not loaded (may be built-in or not needed)" fi done # Wait a moment and test again sleep 2 # Final test: check if iptables works now if iptables -t nat -L >/dev/null 2>&1; then log "iptables NAT functionality verified successfully!" return 0 fi # If still not working, check if basic iptables is ok if iptables -L >/dev/null 2>&1; then log "Basic iptables working. NAT may be handled by Docker automatically." log "Proceeding with installation..." return 0 fi # Last resort: check kernel networking capabilities log "Checking kernel networking capabilities..." if [[ -f /proc/net/ip_tables_names ]] || [[ -d /proc/sys/net/netfilter ]] || [[ -f /proc/net/nf_conntrack ]]; then log "Kernel networking support detected. Proceeding..." return 0 fi # If we got here, something might be wrong, but let's try to continue log "Warning: Could not fully verify iptables/NAT support." log "This may be normal on some systems where Docker handles networking." log "Installation will continue - Docker will manage port forwarding." return 0 } # Function to validate port validate_port() { local port=$1 if [[ "$port" =~ ^[0-9]+$ ]] && [ "$port" -ge 1 ] && [ "$port" -le 65535 ]; then return 0 else return 1 fi } # Function to read input from terminal (works with pipes) read_from_terminal() { local prompt="$1" local default="$2" local response # Always try to read from /dev/tty for interactive input if [[ -r /dev/tty ]]; then echo -n "$prompt" >/dev/tty read response > "$LOG_FILE") || { log "Error generating password hash." exit 1 } # Extract just the hash from the output (removes PASSWORD_HASH=' and trailing ') password_hash=$(echo "$password_hash_output" | sed "s/PASSWORD_HASH='\(.*\)'/\1/") # Fallback: if sed didn't work, try alternative extraction methods if [[ -z "$password_hash" ]] || [[ "$password_hash" == "$password_hash_output" ]]; then # Try extracting everything between single quotes password_hash=$(echo "$password_hash_output" | grep -o "'[^']*'" | sed "s/'//g") fi # Final validation if [[ -z "$password_hash" ]] || [[ "$password_hash" == PASSWORD_HASH* ]]; then log "Error: Could not extract password hash from output: $password_hash_output" exit 1 fi log "Password hash generated and extracted successfully." panel_port=$(read_from_terminal "Enter port for wg-easy panel (commonly used: 80) [default: 80]: " "80") if ! validate_port "$panel_port"; then log "Error: Invalid port number. Using default port 80." panel_port=80 fi log "Using panel port: $panel_port" # Stop and remove existing container if it exists if docker ps -a --format 'table {{.Names}}' | grep -q "^$container_name$"; then log "Stopping and removing existing wg-easy container..." docker stop "$container_name" >> "$LOG_FILE" 2>&1 || true docker rm "$container_name" >> "$LOG_FILE" 2>&1 || true fi log "Starting wg-easy container..." docker run -d --name "$container_name" \ -e WG_HOST="$wg_host" \ -e PASSWORD_HASH="$password_hash" \ -p 51820:51820/udp \ -p "$panel_port":51821/tcp \ --cap-add=NET_ADMIN \ --cap-add=SYS_MODULE \ --sysctl net.ipv4.ip_forward=1 \ --restart unless-stopped \ -v ~/.wg-easy:/etc/wireguard \ ghcr.io/wg-easy/wg-easy:14 >> "$LOG_FILE" 2>&1 || { log "Error starting wg-easy container." exit 1 } # Wait a moment and check if container is running sleep 5 if ! docker ps --format 'table {{.Names}}' | grep -q "^$container_name$"; then log "Error: wg-easy container failed to start. Check logs with: docker logs $container_name" exit 1 fi log "wg-easy container started successfully!" # Store variables in memory for final output export WG_HOST="$wg_host" export PANEL_PORT="$panel_port" } # Main function main() { # Create log directory if it doesn't exist mkdir -p "$(dirname "$LOG_FILE")" # Display welcome message echo "Welcome to the wg-easy Installation Script!" echo "This script was created by Gustavo Toledo (@itxtoledo) to simplify setting up wg-easy on Oracle Linux." echo "Enjoy a seamless installation experience!" echo "Check out more tech content on Gustavo's YouTube channel: https://www.youtube.com/c/itxToledo" echo "----------------------------------------" log "Starting wg-easy installation script at $(date)..." # Check prerequisites (Docker check is optional here since we install it) check_command curl check_command systemctl check_command $(get_package_manager) # Prompt user for confirmation echo "This script will:" echo "1. Set up a 4GB swapfile" echo "2. Install Docker" echo "3. Configure iptables modules" echo "4. Run the wg-easy container" answer=$(read_from_terminal "Proceed with installation? (y/n): " "") case $answer in [Yy]* ) log "Starting installation...";; [Nn]* ) log "Installation canceled."; exit 0;; * ) log "Invalid response."; exit 1;; esac # Execute installation steps setup_swapfile install_docker setup_iptables run_wg_easy # Display final message echo "Installation completed successfully!" echo "Access the wg-easy panel at: http://$WG_HOST:$PANEL_PORT" echo "----------------------------------------" echo "Important:" echo "1. Ensure the web port ($PANEL_PORT/tcp) is open in your firewall." echo "2. Ensure the VPN port (51820/udp) is open in your firewall." echo " Example for firewalld (if installed):" echo " sudo firewall-cmd --add-port=$PANEL_PORT/tcp --permanent" echo " sudo firewall-cmd --add-port=51820/udp --permanent" echo " sudo firewall-cmd --reload" echo "3. If using Oracle Cloud, open ports $PANEL_PORT/tcp and 51820/udp in the OCI console (Virtual Cloud Network > Security List)." echo "4. Download the WireGuard client from: https://www.wireguard.com/install/" echo "5. Check container status with: docker logs wg-easy" echo "----------------------------------------" log "Installation completed successfully! Access wg-easy at http://$WG_HOST:$PANEL_PORT" } # Set trap for errors trap 'log "Error on line $LINENO in command: $BASH_COMMAND"; exit 1' ERR # Run the script main