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:
137
lib/client_mgmt.sh
Normal file
137
lib/client_mgmt.sh
Normal 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
57
lib/network.sh
Normal 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
52
lib/os_detect.sh
Normal 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
54
lib/utils.sh
Normal 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
99
lib/wg_core.sh
Normal 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"
|
||||
}
|
||||
Reference in New Issue
Block a user