Last active
July 14, 2025 02:18
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
To run:
curl -s RAW_URL | bash
Please copy the most recent raw URL from gist.