Skip to content

Instantly share code, notes, and snippets.

@itxtoledo
Last active July 14, 2025 02:18
Show Gist options
  • Save itxtoledo/27ca1e73e08c9ee4433a54480d4f57d3 to your computer and use it in GitHub Desktop.
Save itxtoledo/27ca1e73e08c9ee4433a54480d4f57d3 to your computer and use it in GitHub Desktop.
Optimized script to install wg-easy on Oracle Linux (Oracle Cloud) in a VPS with 1 GB of RAM
#!/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 </dev/tty
else
# Fallback to regular read if /dev/tty not available
read -p "$prompt" response
fi
# Use default if response is empty
echo "${response:-$default}"
}
# Function to run wg-easy container
run_wg_easy() {
local wg_host
local panel_port
local password
local password_hash
local container_name="wg-easy"
# Check if Docker is running
if ! systemctl is-active --quiet docker; then
log "Error: Docker is not running. Please check Docker installation."
exit 1
fi
log "Fetching public IP address..."
wg_host=$(curl -s --connect-timeout 10 ifconfig.me || echo "")
if [[ -z "$wg_host" ]]; then
log "Warning: Could not fetch public IP automatically."
wg_host=$(read_from_terminal "Enter public IP or hostname: " "")
if [[ -z "$wg_host" ]]; then
log "Error: No IP or hostname provided."
exit 1
fi
fi
log "Using WG_HOST=$wg_host"
password=$(read_from_terminal "Enter password for wg-easy [default: admin123]: " "admin123")
log "Generating password hash..."
password_hash_output=$(docker run ghcr.io/wg-easy/wg-easy:14 wgpw "$password" 2>> "$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
@itxtoledo
Copy link
Author

itxtoledo commented Jun 9, 2025

To run: curl -s RAW_URL | bash

Please copy the most recent raw URL from gist.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment