feat: add wireguard secure installer with modular architecture

This commit introduces a new WireGuard VPN installer with enterprise-grade security features. The installer includes:

- Zero-touch installation with automatic configuration
- Modular architecture for maintainability (separate lib files)
- Client management interface with bandwidth monitoring
- Support for multiple Linux distributions
- Secure defaults and hardened configurations

The implementation provides a complete solution for deploying WireGuard VPN servers with minimal user interaction while maintaining security best practices.
This commit is contained in:
2025-11-30 14:55:15 +07:00
commit 35ff83baca
12 changed files with 1322 additions and 0 deletions

137
lib/client_mgmt.sh Normal file
View File

@@ -0,0 +1,137 @@
#!/bin/bash
# WireGuard Secure Installer
# Copyright (c) 2025 Muhammad Fadhila Abiyyu Faris
# GitHub: [github.com/fadhila36/wireguard-secure-installer](https://github.com/fadhila36/wireguard-secure-installer)
new_client() {
echo -e "${BLUE}=== Add New Client ===${NC}"
echo -n "Enter Client Name (no spaces): "
read -r CLIENT_NAME
if [[ -z "$CLIENT_NAME" ]]; then
log_error "Client name cannot be empty."
return
fi
# Check if client already exists
if grep -q "### Client: $CLIENT_NAME" "$WG_CONFIG"; then
log_error "Client '$CLIENT_NAME' already exists."
return
fi
# Find next available IP octet
# We scan for 10.66.66.X and find the first missing number starting from 2
for i in {2..254}; do
if ! grep -q "10.66.66.$i" "$WG_CONFIG"; then
NEXT_IP="$i"
break
fi
done
if [[ -z "$NEXT_IP" ]]; then
fatal_error "No available IPs in the subnet."
fi
# Re-detect public IP in case it changed
detect_public_ip
# Use existing function from wg_core.sh
create_client_config "$CLIENT_NAME" "$NEXT_IP"
echo -e "${GREEN}Client $CLIENT_NAME added with IP 10.66.66.$NEXT_IP${NC}"
}
remove_client() {
echo -e "${BLUE}=== Remove Client ===${NC}"
# List clients
echo "Existing Clients:"
grep "### Client:" "$WG_CONFIG" | cut -d ' ' -f 3 | nl -s ') '
echo -n "Enter Client Name to remove: "
read -r CLIENT_TO_REMOVE
if [[ -z "$CLIENT_TO_REMOVE" ]]; then
log_error "Client name cannot be empty."
return
fi
if ! grep -q "### Client: $CLIENT_TO_REMOVE" "$WG_CONFIG"; then
log_error "Client '$CLIENT_TO_REMOVE' not found."
return
fi
# Backup config
cp "$WG_CONFIG" "${WG_CONFIG}.bak"
# Remove the block from config
# We use a more robust sed command that handles the block structure
# It deletes from the line containing "### Client: NAME" up to the next blank line
sed -i "/### Client: $CLIENT_TO_REMOVE/,/^$/d" "$WG_CONFIG"
# Also clean up any potential double empty lines left behind
sed -i '/^$/N;/^\n$/D' "$WG_CONFIG"
# Reload WireGuard
wg syncconf "$SERVER_WG_NIC" <(wg-quick strip "$SERVER_WG_NIC")
# Remove config file
rm -f "$INSTALL_DIR/clients/$CLIENT_TO_REMOVE.conf"
echo -e "${GREEN}Client $CLIENT_TO_REMOVE removed.${NC}"
}
view_usage_logs() {
echo -e "${BLUE}=== Bandwidth Usage ===${NC}"
# Header
printf "%-20s | %-15s | %-15s | %-20s\n" "Client Name" "Data Received" "Data Sent" "Last Handshake"
echo "--------------------------------------------------------------------------------"
# Get dump
DUMP=$(wg show "$SERVER_WG_NIC" dump)
# Iterate over clients in config to map names to keys
# Grep Client Names
CLIENT_NAMES=$(grep "### Client:" "$WG_CONFIG" | cut -d ' ' -f 3)
for NAME in $CLIENT_NAMES; do
# Get Public Key for this client from config
# We need to read the config file more smartly.
# Let's assume the structure:
# ### Client: NAME
# [Peer]
# PublicKey = KEY
# Extract Public Key for the specific client
PUB_KEY=$(sed -n "/### Client: $NAME/,/PublicKey/p" "$WG_CONFIG" | grep "PublicKey" | cut -d ' ' -f 3)
if [[ -n "$PUB_KEY" ]]; then
# Find stats in dump
# Dump format: public-key preshared-key endpoint allowed-ips latest-handshake transfer-rx transfer-tx persistent-keepalive
STATS=$(echo "$DUMP" | grep "$PUB_KEY")
if [[ -n "$STATS" ]]; then
RX_BYTES=$(echo "$STATS" | awk '{print $6}')
TX_BYTES=$(echo "$STATS" | awk '{print $7}')
LAST_HANDSHAKE=$(echo "$STATS" | awk '{print $5}')
# Convert Bytes
RX_HUMAN=$(numfmt --to=iec-i --suffix=B "$RX_BYTES")
TX_HUMAN=$(numfmt --to=iec-i --suffix=B "$TX_BYTES")
# Convert Handshake Time
if [[ "$LAST_HANDSHAKE" -eq 0 ]]; then
TIME_HUMAN="Never"
else
TIME_HUMAN=$(date -d @"$LAST_HANDSHAKE" '+%Y-%m-%d %H:%M')
fi
printf "%-20s | %-15s | %-15s | %-20s\n" "$NAME" "$RX_HUMAN" "$TX_HUMAN" "$TIME_HUMAN"
fi
fi
done
echo ""
echo -n "Press Enter to continue..."
read -r
}

57
lib/network.sh Normal file
View File

@@ -0,0 +1,57 @@
#!/bin/bash
# WireGuard Secure Installer
# Copyright (c) 2025 Muhammad Fadhila Abiyyu Faris
# GitHub: [github.com/fadhila36/wireguard-secure-installer](https://github.com/fadhila36/wireguard-secure-installer)
detect_public_ip() {
log_info "Detecting public IP..."
# Try multiple sources for redundancy
PUBLIC_IP=$(curl -s https://api.ipify.org || curl -s https://ifconfig.me || curl -s https://icanhazip.com)
if [[ -z "$PUBLIC_IP" ]]; then
log_warn "Failed to detect public IP. Falling back to local interface IP."
# Fallback to default route IP
PUBLIC_IP=$(ip route get 1.1.1.1 | grep -oP 'src \K\S+')
fi
if [[ -z "$PUBLIC_IP" ]]; then
fatal_error "Could not detect Public IP or Local IP. Network configuration failed."
fi
log_info "Public IP detected: $PUBLIC_IP"
}
detect_main_interface() {
MAIN_NIC=$(ip route get 1.1.1.1 | grep -oP 'dev \K\S+')
if [[ -z "$MAIN_NIC" ]]; then
fatal_error "Could not detect main network interface."
fi
log_info "Main Interface detected: $MAIN_NIC"
}
configure_firewall() {
log_info "Configuring Firewall..."
# Enable IP Forwarding (Idempotent: Overwrites the file)
cat > /etc/sysctl.d/99-wireguard.conf <<EOF
net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1
EOF
sysctl --system >> "$LOG_FILE" 2>&1
# Detect Firewall Type (UFW, Firewalld, or IPTables)
if command -v ufw >/dev/null; then
log_info "UFW detected. Adding rules..."
ufw allow "$SERVER_PORT"/udp
ufw allow OpenSSH
# UFW routing rules are complex to automate safely without breaking existing config,
# relying on PostUp/PostDown in wg0.conf for NAT is safer and standard for WG.
elif command -v firewall-cmd >/dev/null; then
log_info "Firewalld detected. Adding rules..."
firewall-cmd --zone=public --add-port="$SERVER_PORT"/udp --permanent
firewall-cmd --zone=public --add-masquerade --permanent
firewall-cmd --reload
else
log_info "No specific firewall manager found. Relying on WireGuard PostUp/PostDown for iptables."
fi
}

52
lib/os_detect.sh Normal file
View File

@@ -0,0 +1,52 @@
#!/bin/bash
# WireGuard Secure Installer
# Copyright (c) 2025 Muhammad Fadhila Abiyyu Faris
# GitHub: [github.com/fadhila36/wireguard-secure-installer](https://github.com/fadhila36/wireguard-secure-installer)
check_os() {
if [ -f /etc/os-release ]; then
. /etc/os-release
OS=$ID
VERSION_ID=$VERSION_ID
else
fatal_error "Cannot detect OS. /etc/os-release not found."
fi
log_info "Detected OS: $OS $VERSION_ID"
case "$OS" in
ubuntu|debian)
PKG_MANAGER="apt-get"
UPDATE_CMD="apt-get update"
INSTALL_CMD="apt-get install -y"
;;
centos|rhel|rocky|almalinux)
PKG_MANAGER="dnf"
UPDATE_CMD="dnf check-update" # dnf update is slow, check-update is enough to refresh metadata
INSTALL_CMD="dnf install -y"
;;
*)
fatal_error "Unsupported OS: $OS. Supported: Ubuntu, Debian, CentOS, Rocky, AlmaLinux."
;;
esac
}
install_dependencies() {
log_info "Installing dependencies..."
$UPDATE_CMD >> "$LOG_FILE" 2>&1
case "$OS" in
ubuntu|debian)
$INSTALL_CMD wireguard qrencode curl iptables >> "$LOG_FILE" 2>&1
;;
centos|rhel|rocky|almalinux)
$INSTALL_CMD epel-release >> "$LOG_FILE" 2>&1
$INSTALL_CMD wireguard-tools qrencode curl iptables-services >> "$LOG_FILE" 2>&1
;;
esac
if ! command -v wg >/dev/null; then
fatal_error "WireGuard installation failed."
fi
}

54
lib/utils.sh Normal file
View File

@@ -0,0 +1,54 @@
#!/bin/bash
# WireGuard Secure Installer
# Copyright (c) 2025 Muhammad Fadhila Abiyyu Faris
# GitHub: [github.com/fadhila36/wireguard-secure-installer](https://github.com/fadhila36/wireguard-secure-installer)
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
echo "[INFO] $1" >> "$LOG_FILE"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
echo "[WARN] $1" >> "$LOG_FILE"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
echo "[ERROR] $1" >> "$LOG_FILE"
}
fatal_error() {
log_error "$1"
exit 1
}
check_root() {
if [ "$EUID" -ne 0 ]; then
fatal_error "This script must be run as root."
fi
}
show_banner() {
clear
echo -e "${BLUE}"
echo "========================================================================="
echo " WireGuard Secure Installer Enterprise"
echo " (c) 2025 Muhammad Fadhila Abiyyu Faris"
echo " https://github.com/fadhila36/wireguard-secure-installer"
echo "========================================================================="
echo -e "${NC}"
}
cleanup() {
if [ $? -ne 0 ]; then
log_error "An error occurred. Check $LOG_FILE for details."
fi
}

99
lib/wg_core.sh Normal file
View File

@@ -0,0 +1,99 @@
#!/bin/bash
# WireGuard Secure Installer
# Copyright (c) 2025 Muhammad Fadhila Abiyyu Faris
# GitHub: [github.com/fadhila36/wireguard-secure-installer](https://github.com/fadhila36/wireguard-secure-installer)
generate_keys() {
if [ -f "$WG_CONFIG" ]; then
log_warn "WireGuard config already exists. Skipping key generation to prevent overwrite."
# Extract existing private key for context if needed, or just return
SERVER_PRIV_KEY=$(grep "PrivateKey" "$WG_CONFIG" | cut -d ' ' -f 3)
SERVER_PUB_KEY=$(echo "$SERVER_PRIV_KEY" | wg pubkey)
return
fi
log_info "Generating Server Keys..."
umask 077
SERVER_PRIV_KEY=$(wg genkey)
SERVER_PUB_KEY=$(echo "$SERVER_PRIV_KEY" | wg pubkey)
}
generate_server_config() {
if [ -f "$WG_CONFIG" ]; then
log_warn "WireGuard config already exists. Skipping config generation."
return
fi
log_info "Generating Server Config..."
cat > "$WG_CONFIG" <<EOF
[Interface]
Address = 10.66.66.1/24,fd42:42:42::1/64
ListenPort = $SERVER_PORT
PrivateKey = $SERVER_PRIV_KEY
PostUp = iptables -A FORWARD -i $SERVER_WG_NIC -j ACCEPT; iptables -t nat -A POSTROUTING -o $MAIN_NIC -j MASQUERADE; ip6tables -A FORWARD -i $SERVER_WG_NIC -j ACCEPT; ip6tables -t nat -A POSTROUTING -o $MAIN_NIC -j MASQUERADE
PostDown = iptables -D FORWARD -i $SERVER_WG_NIC -j ACCEPT; iptables -t nat -D POSTROUTING -o $MAIN_NIC -j MASQUERADE; ip6tables -D FORWARD -i $SERVER_WG_NIC -j ACCEPT; ip6tables -t nat -D POSTROUTING -o $MAIN_NIC -j MASQUERADE
EOF
chmod 600 "$WG_CONFIG"
}
start_wireguard() {
log_info "Starting WireGuard Service..."
systemctl enable "wg-quick@$SERVER_WG_NIC" >> "$LOG_FILE" 2>&1
systemctl start "wg-quick@$SERVER_WG_NIC" >> "$LOG_FILE" 2>&1
# Verify status
if systemctl is-active --quiet "wg-quick@$SERVER_WG_NIC"; then
log_info "WireGuard Service is RUNNING."
else
fatal_error "Failed to start WireGuard service."
fi
}
create_client_config() {
local CLIENT_NAME=$1
local CLIENT_IP_SUFFIX=$2 # e.g., 2 for 10.66.66.2
log_info "Creating Client: $CLIENT_NAME"
CLIENT_PRIV_KEY=$(wg genkey)
CLIENT_PUB_KEY=$(echo "$CLIENT_PRIV_KEY" | wg pubkey)
CLIENT_PRESHARED_KEY=$(wg genpsk)
# Add peer to server config
cat >> "$WG_CONFIG" <<EOF
### Client: $CLIENT_NAME
[Peer]
PublicKey = $CLIENT_PUB_KEY
PresharedKey = $CLIENT_PRESHARED_KEY
AllowedIPs = 10.66.66.$CLIENT_IP_SUFFIX/32,fd42:42:42::$CLIENT_IP_SUFFIX/128
EOF
# Update live interface
wg syncconf "$SERVER_WG_NIC" <(wg-quick strip "$SERVER_WG_NIC")
# Generate Client Config File
mkdir -p "$INSTALL_DIR/clients"
cat > "$INSTALL_DIR/clients/$CLIENT_NAME.conf" <<EOF
[Interface]
PrivateKey = $CLIENT_PRIV_KEY
Address = 10.66.66.$CLIENT_IP_SUFFIX/24,fd42:42:42::$CLIENT_IP_SUFFIX/64
DNS = $SERVER_DNS
[Peer]
PublicKey = $SERVER_PUB_KEY
PresharedKey = $CLIENT_PRESHARED_KEY
Endpoint = $PUBLIC_IP:$SERVER_PORT
AllowedIPs = $ALLOWED_IPS
PersistentKeepalive = 25
EOF
log_info "Client config saved to: $INSTALL_DIR/clients/$CLIENT_NAME.conf"
# Show QR Code
echo -e "${BLUE}Scan this QR Code to connect:${NC}"
qrencode -t ansiutf8 < "$INSTALL_DIR/clients/$CLIENT_NAME.conf"
}