Create automated scripts to provision WireGuard clients, manage configurations, and monitor VPN connections. This tutorial builds comprehensive management tools for production WireGuard deployments.
Prerequisites
- WireGuard server already installed and configured
- Root or sudo access
- Basic knowledge of WireGuard configuration
- Understanding of JSON and bash scripting
What this solves
Managing WireGuard clients manually becomes cumbersome as your VPN infrastructure grows. This tutorial creates automated scripts for client provisioning, configuration management, and monitoring to streamline WireGuard operations. You'll build tools that generate client configurations, distribute keys securely, and track connection health automatically.
Step-by-step configuration
Verify WireGuard server installation
Ensure your WireGuard server is properly configured before setting up automation scripts. This confirms the base infrastructure is working correctly.
sudo systemctl status wg-quick@wg0
sudo wg show
Install required dependencies
Install tools needed for automated client management including QR code generation and JSON processing utilities.
sudo apt update
sudo apt install -y qrencode jq curl wireguard-tools
Create management directory structure
Set up organized directories for scripts, client configurations, and logs with proper permissions.
sudo mkdir -p /opt/wireguard-manager/{scripts,clients,logs,templates}
sudo mkdir -p /opt/wireguard-manager/clients/{active,revoked,configs}
sudo chown -R root:root /opt/wireguard-manager
sudo chmod -R 755 /opt/wireguard-manager
sudo chmod 700 /opt/wireguard-manager/clients
Create client database file
Initialize a JSON database to track client information, IP assignments, and connection status.
{
"clients": [],
"next_ip": 2,
"subnet": "10.0.0.0/24",
"server_public_key": "",
"server_endpoint": "",
"dns_servers": ["1.1.1.1", "8.8.8.8"]
}
sudo chmod 600 /opt/wireguard-manager/clients/database.json
Create client configuration template
Define a reusable template for generating client configurations with proper security settings.
[Interface]
PrivateKey = CLIENT_PRIVATE_KEY
Address = CLIENT_IP/32
DNS = DNS_SERVERS
[Peer]
PublicKey = SERVER_PUBLIC_KEY
AllowedIPs = 0.0.0.0/0
Endpoint = SERVER_ENDPOINT
PersistentKeepalive = 25
Create client provisioning script
Build the main script that provisions new clients, generates keys, and updates server configuration automatically.
#!/bin/bash
WireGuard Client Provisioning Script
Usage: ./provision-client.sh [server-endpoint] [dns-servers]
set -euo pipefail
Configuration
WG_DIR="/opt/wireguard-manager"
CLIENT_DB="$WG_DIR/clients/database.json"
SERVER_CONFIG="/etc/wireguard/wg0.conf"
TEMPLATE="$WG_DIR/templates/client.conf"
LOG_FILE="$WG_DIR/logs/provisioning.log"
Logging function
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
Validate input
if [[ $# -lt 1 ]]; then
echo "Usage: $0 [server-endpoint] [dns-servers]"
echo "Example: $0 john-laptop vpn.example.com:51820 '1.1.1.1,8.8.8.8'"
exit 1
fi
CLIENT_NAME="$1"
SERVER_ENDPOINT="${2:-$(jq -r '.server_endpoint' "$CLIENT_DB")}"
DNS_SERVERS="${3:-$(jq -r '.dns_servers | join(",")' "$CLIENT_DB")}"
Validate client name
if [[ ! "$CLIENT_NAME" =~ ^[a-zA-Z0-9_-]+$ ]]; then
log "ERROR: Client name must contain only alphanumeric characters, hyphens, and underscores"
exit 1
fi
Check if client already exists
if jq -e ".clients[] | select(.name == \"$CLIENT_NAME\")" "$CLIENT_DB" > /dev/null; then
log "ERROR: Client '$CLIENT_NAME' already exists"
exit 1
fi
Generate client keys
CLIENT_PRIVATE_KEY=$(wg genkey)
CLIENT_PUBLIC_KEY=$(echo "$CLIENT_PRIVATE_KEY" | wg pubkey)
Get next available IP
NEXT_IP=$(jq -r '.next_ip' "$CLIENT_DB")
SUBNET_BASE=$(jq -r '.subnet' "$CLIENT_DB" | cut -d'/' -f1 | cut -d'.' -f1-3)
CLIENT_IP="$SUBNET_BASE.$NEXT_IP"
Get server public key
SERVER_PUBLIC_KEY=$(jq -r '.server_public_key' "$CLIENT_DB")
if [[ "$SERVER_PUBLIC_KEY" == "" || "$SERVER_PUBLIC_KEY" == "null" ]]; then
SERVER_PUBLIC_KEY=$(sudo wg show wg0 public-key 2>/dev/null || echo "")
if [[ "$SERVER_PUBLIC_KEY" == "" ]]; then
log "ERROR: Cannot retrieve server public key"
exit 1
fi
fi
Create client configuration
CLIENT_CONFIG="$WG_DIR/clients/configs/$CLIENT_NAME.conf"
sed -e "s/CLIENT_PRIVATE_KEY/$CLIENT_PRIVATE_KEY/g" \
-e "s/CLIENT_IP/$CLIENT_IP/g" \
-e "s/SERVER_PUBLIC_KEY/$SERVER_PUBLIC_KEY/g" \
-e "s/SERVER_ENDPOINT/$SERVER_ENDPOINT/g" \
-e "s/DNS_SERVERS/$DNS_SERVERS/g" \
"$TEMPLATE" > "$CLIENT_CONFIG"
chmod 600 "$CLIENT_CONFIG"
Add peer to server configuration
echo "" >> "$SERVER_CONFIG"
echo "# Client: $CLIENT_NAME" >> "$SERVER_CONFIG"
echo "[Peer]" >> "$SERVER_CONFIG"
echo "PublicKey = $CLIENT_PUBLIC_KEY" >> "$SERVER_CONFIG"
echo "AllowedIPs = $CLIENT_IP/32" >> "$SERVER_CONFIG"
Update client database
TMP_DB=$(mktemp)
jq --arg name "$CLIENT_NAME" \
--arg pubkey "$CLIENT_PUBLIC_KEY" \
--arg ip "$CLIENT_IP" \
--arg created "$(date -Iseconds)" \
--arg status "active" \
'.clients += [{"name": $name, "public_key": $pubkey, "ip": $ip, "created": $created, "status": $status, "last_seen": null}] | .next_ip += 1 | .server_public_key = $ARGS.named.server_pubkey | .server_endpoint = $ARGS.named.endpoint' \
--arg server_pubkey "$SERVER_PUBLIC_KEY" \
--arg endpoint "$SERVER_ENDPOINT" \
"$CLIENT_DB" > "$TMP_DB"
mv "$TMP_DB" "$CLIENT_DB"
Generate QR code
QR_FILE="$WG_DIR/clients/configs/$CLIENT_NAME.png"
qrencode -t png -o "$QR_FILE" < "$CLIENT_CONFIG"
chmod 600 "$QR_FILE"
Restart WireGuard to apply changes
sudo systemctl reload-or-restart wg-quick@wg0
log "SUCCESS: Client '$CLIENT_NAME' provisioned successfully"
log "Config: $CLIENT_CONFIG"
log "QR Code: $QR_FILE"
log "Client IP: $CLIENT_IP"
echo "Client '$CLIENT_NAME' has been provisioned successfully!"
echo "Configuration file: $CLIENT_CONFIG"
echo "QR code: $QR_FILE"
sudo chmod 755 /opt/wireguard-manager/scripts/provision-client.sh
Create client revocation script
Build a script to safely revoke client access by removing their configuration and updating the server.
#!/bin/bash
WireGuard Client Revocation Script
Usage: ./revoke-client.sh
set -euo pipefail
Configuration
WG_DIR="/opt/wireguard-manager"
CLIENT_DB="$WG_DIR/clients/database.json"
SERVER_CONFIG="/etc/wireguard/wg0.conf"
LOG_FILE="$WG_DIR/logs/provisioning.log"
Logging function
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
Validate input
if [[ $# -ne 1 ]]; then
echo "Usage: $0 "
exit 1
fi
CLIENT_NAME="$1"
Check if client exists
if ! jq -e ".clients[] | select(.name == \"$CLIENT_NAME\")" "$CLIENT_DB" > /dev/null; then
log "ERROR: Client '$CLIENT_NAME' not found"
exit 1
fi
Get client public key for removal
CLIENT_PUBLIC_KEY=$(jq -r ".clients[] | select(.name == \"$CLIENT_NAME\") | .public_key" "$CLIENT_DB")
Remove client from server config
TMP_CONFIG=$(mktemp)
awk -v client="$CLIENT_NAME" -v pubkey="$CLIENT_PUBLIC_KEY" '
/^# Client:/ { if ($3 == client) skip=1; else skip=0 }
/^\[Peer\]/ && skip { skip_peer=1; next }
/^PublicKey/ && skip_peer && $3 == pubkey { next }
/^AllowedIPs/ && skip_peer { skip_peer=0; skip=0; next }
!skip { print }
' "$SERVER_CONFIG" > "$TMP_CONFIG"
sudo mv "$TMP_CONFIG" "$SERVER_CONFIG"
Move client files to revoked directory
mkdir -p "$WG_DIR/clients/revoked"
if [[ -f "$WG_DIR/clients/configs/$CLIENT_NAME.conf" ]]; then
mv "$WG_DIR/clients/configs/$CLIENT_NAME.conf" "$WG_DIR/clients/revoked/"
fi
if [[ -f "$WG_DIR/clients/configs/$CLIENT_NAME.png" ]]; then
mv "$WG_DIR/clients/configs/$CLIENT_NAME.png" "$WG_DIR/clients/revoked/"
fi
Update client database
TMP_DB=$(mktemp)
jq --arg name "$CLIENT_NAME" \
--arg revoked "$(date -Iseconds)" \
'(.clients[] | select(.name == $name)) |= (.status = "revoked" | .revoked = $revoked)' \
"$CLIENT_DB" > "$TMP_DB"
mv "$TMP_DB" "$CLIENT_DB"
Restart WireGuard
sudo systemctl reload-or-restart wg-quick@wg0
log "SUCCESS: Client '$CLIENT_NAME' revoked successfully"
echo "Client '$CLIENT_NAME' has been revoked successfully!"
sudo chmod 755 /opt/wireguard-manager/scripts/revoke-client.sh
Create client monitoring script
Build a monitoring script that checks client connection status and updates the database with connection information.
#!/bin/bash
WireGuard Client Monitoring Script
Updates client connection status and last seen timestamps
set -euo pipefail
Configuration
WG_DIR="/opt/wireguard-manager"
CLIENT_DB="$WG_DIR/clients/database.json"
LOG_FILE="$WG_DIR/logs/monitoring.log"
STATS_FILE="$WG_DIR/logs/connection-stats.json"
Logging function
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
Get current WireGuard status
WG_STATUS=$(sudo wg show wg0 dump 2>/dev/null || echo "")
if [[ -z "$WG_STATUS" ]]; then
log "WARNING: Cannot retrieve WireGuard status"
exit 1
fi
Initialize stats
STATS=$(cat <Process each client
TMP_DB=$(mktemp)
cp "$CLIENT_DB" "$TMP_DB"
TOTAL_CLIENTS=0
ACTIVE_CONNECTIONS=0
ONLINE_CLIENTS="[]"
OFFLINE_CLIENTS="[]"
while IFS= read -r client; do
CLIENT_NAME=$(echo "$client" | jq -r '.name')
CLIENT_PUBKEY=$(echo "$client" | jq -r '.public_key')
CLIENT_STATUS=$(echo "$client" | jq -r '.status')
if [[ "$CLIENT_STATUS" == "revoked" ]]; then
continue
fi
((TOTAL_CLIENTS++))
# Check if client is connected
PEER_INFO=$(echo "$WG_STATUS" | grep "$CLIENT_PUBKEY" || echo "")
if [[ -n "$PEER_INFO" ]]; then
LAST_HANDSHAKE=$(echo "$PEER_INFO" | cut -f5)
RX_BYTES=$(echo "$PEER_INFO" | cut -f6)
TX_BYTES=$(echo "$PEER_INFO" | cut -f7)
# Update last seen if handshake is recent (within 3 minutes)
CURRENT_TIME=$(date +%s)
if [[ "$LAST_HANDSHAKE" != "0" ]] && [[ $((CURRENT_TIME - LAST_HANDSHAKE)) -lt 180 ]]; then
LAST_SEEN=$(date -Iseconds -d "@$LAST_HANDSHAKE")
((ACTIVE_CONNECTIONS++))
ONLINE_CLIENTS=$(echo "$ONLINE_CLIENTS" | jq --arg name "$CLIENT_NAME" '. += [$name]')
# Update database
jq --arg name "$CLIENT_NAME" \
--arg last_seen "$LAST_SEEN" \
--arg rx "$RX_BYTES" \
--arg tx "$TX_BYTES" \
'(.clients[] | select(.name == $name)) |= (.last_seen = $last_seen | .rx_bytes = ($rx | tonumber) | .tx_bytes = ($tx | tonumber))' \
"$TMP_DB" > "${TMP_DB}.tmp" && mv "${TMP_DB}.tmp" "$TMP_DB"
else
OFFLINE_CLIENTS=$(echo "$OFFLINE_CLIENTS" | jq --arg name "$CLIENT_NAME" '. += [$name]')
fi
else
OFFLINE_CLIENTS=$(echo "$OFFLINE_CLIENTS" | jq --arg name "$CLIENT_NAME" '. += [$name]')
fi
done < <(jq -c '.clients[]' "$CLIENT_DB")
Update main database
mv "$TMP_DB" "$CLIENT_DB"
Generate statistics
STATS=$(echo "$STATS" | jq \
--arg total "$TOTAL_CLIENTS" \
--arg active "$ACTIVE_CONNECTIONS" \
--argjson online "$ONLINE_CLIENTS" \
--argjson offline "$OFFLINE_CLIENTS" \
'.total_clients = ($total | tonumber) | .active_connections = ($active | tonumber) | .clients_online = $online | .clients_offline = $offline')
echo "$STATS" > "$STATS_FILE"
log "Monitoring complete: $ACTIVE_CONNECTIONS/$TOTAL_CLIENTS clients online"
sudo chmod 755 /opt/wireguard-manager/scripts/monitor-clients.sh
Create client listing script
Build a utility script to display client information in a readable format for administrative purposes.
#!/bin/bash
WireGuard Client Listing Script
Displays client information in various formats
set -euo pipefail
Configuration
WG_DIR="/opt/wireguard-manager"
CLIENT_DB="$WG_DIR/clients/database.json"
Parse arguments
SHOW_ALL=false
SHOW_ACTIVE=false
SHOW_REVOKED=false
OUTPUT_FORMAT="table"
while [[ $# -gt 0 ]]; do
case $1 in
--all)
SHOW_ALL=true
shift
;;
--active)
SHOW_ACTIVE=true
shift
;;
--revoked)
SHOW_REVOKED=true
shift
;;
--json)
OUTPUT_FORMAT="json"
shift
;;
--help)
echo "Usage: $0 [--all|--active|--revoked] [--json]"
echo " --all Show all clients (default)"
echo " --active Show only active clients"
echo " --revoked Show only revoked clients"
echo " --json Output in JSON format"
exit 0
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
done
Default to show all if no filter specified
if [[ "$SHOW_ACTIVE" == "false" && "$SHOW_REVOKED" == "false" ]]; then
SHOW_ALL=true
fi
Build jq filter
JQ_FILTER=".clients[]"
if [[ "$SHOW_ACTIVE" == "true" ]]; then
JQ_FILTER="$JQ_FILTER | select(.status == \"active\")"
elif [[ "$SHOW_REVOKED" == "true" ]]; then
JQ_FILTER="$JQ_FILTER | select(.status == \"revoked\")"
fi
if [[ "$OUTPUT_FORMAT" == "json" ]]; then
jq "[$JQ_FILTER]" "$CLIENT_DB"
else
echo "WireGuard Client Status Report"
echo "Generated: $(date)"
echo ""
printf "%-20s %-15s %-12s %-20s %-20s\n" "Name" "IP Address" "Status" "Created" "Last Seen"
printf "%-20s %-15s %-12s %-20s %-20s\n" "----" "----------" "------" "-------" "---------"
while IFS= read -r client; do
if [[ "$client" == "null" ]]; then
continue
fi
NAME=$(echo "$client" | jq -r '.name')
IP=$(echo "$client" | jq -r '.ip')
STATUS=$(echo "$client" | jq -r '.status')
CREATED=$(echo "$client" | jq -r '.created' | cut -d'T' -f1)
LAST_SEEN=$(echo "$client" | jq -r '.last_seen // "Never"' | cut -d'T' -f1)
printf "%-20s %-15s %-12s %-20s %-20s\n" "$NAME" "$IP" "$STATUS" "$CREATED" "$LAST_SEEN"
done < <(jq -c "$JQ_FILTER" "$CLIENT_DB")
fi
sudo chmod 755 /opt/wireguard-manager/scripts/list-clients.sh
Set up automated monitoring with cron
Configure cron to run client monitoring automatically and rotate logs to maintain system health.
sudo crontab -e
Add these lines to run monitoring every 5 minutes and daily log rotation:
# WireGuard client monitoring
/5 * /opt/wireguard-manager/scripts/monitor-clients.sh
Daily log rotation
0 0 find /opt/wireguard-manager/logs -name ".log" -mtime +30 -delete
Create management wrapper script
Build a main management script that provides a unified interface for all WireGuard client operations.
#!/bin/bash
WireGuard Manager - Unified client management interface
set -euo pipefail
SCRIPT_DIR="/opt/wireguard-manager/scripts"
show_help() {
cat << EOF
WireGuard Manager - Client Management Tool
Usage: wg-manager [options]
Commands:
provision [endpoint] [dns] Provision new client
revoke Revoke client access
list [--active|--revoked] [--json] List clients
monitor Run monitoring check
status Show connection statistics
Examples:
wg-manager provision john-laptop vpn.example.com:51820
wg-manager list --active
wg-manager revoke old-device
wg-manager status
For more details, see: /opt/wireguard-manager/README.md
EOF
}
if [[ $# -lt 1 ]]; then
show_help
exit 1
fi
COMMAND="$1"
shift
case "$COMMAND" in
provision)
"$SCRIPT_DIR/provision-client.sh" "$@"
;;
revoke)
"$SCRIPT_DIR/revoke-client.sh" "$@"
;;
list)
"$SCRIPT_DIR/list-clients.sh" "$@"
;;
monitor)
"$SCRIPT_DIR/monitor-clients.sh" "$@"
;;
status)
if [[ -f "/opt/wireguard-manager/logs/connection-stats.json" ]]; then
jq -r '"Last Updated: " + .timestamp + "\nTotal Clients: " + (.total_clients | tostring) + "\nActive Connections: " + (.active_connections | tostring) + "\nOnline: " + (.clients_online | join(", ")) + "\nOffline: " + (.clients_offline | join(", "))' /opt/wireguard-manager/logs/connection-stats.json
else
echo "No statistics available. Run 'wg-manager monitor' first."
fi
;;
help|--help|-h)
show_help
;;
*)
echo "Unknown command: $COMMAND"
echo "Run 'wg-manager help' for usage information."
exit 1
;;
esac
sudo chmod 755 /opt/wireguard-manager/wg-manager
sudo ln -sf /opt/wireguard-manager/wg-manager /usr/local/bin/wg-manager
Initialize the management system
Set up the initial configuration with your server details and run the first monitoring check.
# Update database with server information
sudo jq --arg endpoint "vpn.example.com:51820" \
--arg dns "1.1.1.1,8.8.8.8" \
'.server_endpoint = $endpoint | .dns_servers = ($dns | split(","))' \
/opt/wireguard-manager/clients/database.json > /tmp/db.json
sudo mv /tmp/db.json /opt/wireguard-manager/clients/database.json
Run initial monitoring
sudo /opt/wireguard-manager/scripts/monitor-clients.sh
Verify your setup
Test the automation scripts to ensure they work correctly and integrate with your WireGuard server.
# Test client provisioning
sudo wg-manager provision test-client
Verify client was created
sudo wg-manager list --active
Check WireGuard server configuration
sudo wg show
Test monitoring
sudo wg-manager monitor
sudo wg-manager status
Clean up test client
sudo wg-manager revoke test-client
Your client management system should show the test client creation, configuration, and revocation process. The status command should display current connection statistics.
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Permission denied accessing database | Incorrect file ownership | sudo chown root:root /opt/wireguard-manager/clients/database.json |
| QR code generation fails | qrencode not installed | Install qrencode package for your distribution |
| Cannot retrieve server public key | WireGuard interface not active | sudo systemctl status wg-quick@wg0 and restart if needed |
| Monitoring shows no clients online | Clock synchronization issues | sudo systemctl status systemd-timesyncd and sync time |
| Client configuration not working | Firewall blocking UDP port | Ensure UDP port 51820 is open on server firewall |
| Database corruption errors | Invalid JSON in database file | jq . /opt/wireguard-manager/clients/database.json to validate JSON |
Next steps
- Set up NGINX reverse proxy with SSL to secure your WireGuard management interface
- Monitor WireGuard with Prometheus and Grafana for comprehensive VPN analytics
- Configure DNS filtering and ad blocking for enhanced client protection
- Implement multi-site mesh networking for complex VPN topologies
- Integrate LDAP authentication for enterprise user management
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# WireGuard Client Management Automation Install Script
# Production-grade installation with multi-distro support
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Configuration
WG_MANAGER_DIR="/opt/wireguard-manager"
WG_CONFIG_DIR="/etc/wireguard"
SERVER_ENDPOINT=""
VPN_SUBNET="10.0.0.0/24"
# Progress counter
STEP=0
TOTAL_STEPS=8
# Functions
log() {
echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1"
}
success() {
echo -e "${GREEN}✓${NC} $1"
}
error() {
echo -e "${RED}✗${NC} $1" >&2
}
warning() {
echo -e "${YELLOW}⚠${NC} $1"
}
progress() {
STEP=$((STEP + 1))
echo -e "\n${BLUE}[$STEP/$TOTAL_STEPS]${NC} $1"
}
usage() {
echo "Usage: $0 [SERVER_ENDPOINT] [VPN_SUBNET]"
echo "Example: $0 vpn.example.com:51820 10.0.0.0/24"
echo "If not provided, you'll be prompted to configure later"
exit 1
}
cleanup() {
if [[ $? -ne 0 ]]; then
error "Installation failed. Cleaning up..."
rm -rf "$WG_MANAGER_DIR" 2>/dev/null || true
fi
}
trap cleanup ERR EXIT
# Parse arguments
if [[ $# -gt 2 ]]; then
usage
fi
SERVER_ENDPOINT="${1:-}"
VPN_SUBNET="${2:-10.0.0.0/24}"
# Check root privileges
if [[ $EUID -ne 0 ]]; then
error "This script must be run as root"
exit 1
fi
# Detect distribution
progress "Detecting Linux distribution"
if [[ ! -f /etc/os-release ]]; then
error "Cannot detect Linux distribution"
exit 1
fi
source /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update"
PKG_INSTALL="apt install -y"
EPEL_INSTALL=""
;;
almalinux|rocky|centos|rhel|ol)
PKG_MGR="dnf"
PKG_UPDATE="dnf check-update || true"
PKG_INSTALL="dnf install -y"
EPEL_INSTALL="dnf install -y epel-release"
;;
fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf check-update || true"
PKG_INSTALL="dnf install -y"
EPEL_INSTALL=""
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum check-update || true"
PKG_INSTALL="yum install -y"
EPEL_INSTALL="yum install -y epel-release"
;;
*)
error "Unsupported distribution: $ID"
exit 1
;;
esac
success "Detected $PRETTY_NAME"
# Verify WireGuard server installation
progress "Verifying WireGuard server installation"
if ! command -v wg &> /dev/null; then
error "WireGuard is not installed. Please install WireGuard server first."
exit 1
fi
if [[ ! -f "$WG_CONFIG_DIR/wg0.conf" ]]; then
warning "WireGuard server configuration not found at $WG_CONFIG_DIR/wg0.conf"
warning "Please ensure WireGuard server is properly configured"
fi
success "WireGuard installation verified"
# Install dependencies
progress "Installing required dependencies"
log "Updating package repositories..."
$PKG_UPDATE
if [[ -n "$EPEL_INSTALL" ]]; then
log "Installing EPEL repository..."
$EPEL_INSTALL
fi
log "Installing packages..."
$PKG_INSTALL qrencode jq curl wireguard-tools
success "Dependencies installed successfully"
# Create directory structure
progress "Creating management directory structure"
mkdir -p "$WG_MANAGER_DIR"/{scripts,clients,logs,templates}
mkdir -p "$WG_MANAGER_DIR/clients"/{active,revoked,configs}
# Set proper permissions
chown -R root:root "$WG_MANAGER_DIR"
chmod -R 755 "$WG_MANAGER_DIR"
chmod 700 "$WG_MANAGER_DIR/clients"
chmod 700 "$WG_MANAGER_DIR/logs"
success "Directory structure created"
# Initialize client database
progress "Creating client database"
cat > "$WG_MANAGER_DIR/clients/database.json" << 'EOF'
{
"clients": [],
"next_ip": 2,
"subnet": "10.0.0.0/24",
"server_public_key": "",
"server_endpoint": "",
"dns_servers": ["1.1.1.1", "8.8.8.8"]
}
EOF
chmod 600 "$WG_MANAGER_DIR/clients/database.json"
success "Client database initialized"
# Create configuration template
progress "Creating client configuration template"
cat > "$WG_MANAGER_DIR/templates/client.conf" << 'EOF'
[Interface]
PrivateKey = CLIENT_PRIVATE_KEY
Address = CLIENT_IP/32
DNS = DNS_SERVERS
[Peer]
PublicKey = SERVER_PUBLIC_KEY
AllowedIPs = 0.0.0.0/0
Endpoint = SERVER_ENDPOINT
PersistentKeepalive = 25
EOF
chmod 644 "$WG_MANAGER_DIR/templates/client.conf"
success "Configuration template created"
# Create provisioning script
progress "Creating client provisioning script"
cat > "$WG_MANAGER_DIR/scripts/provision-client.sh" << 'EOF'
#!/usr/bin/env bash
set -euo pipefail
# WireGuard Client Provisioning Script
WG_DIR="/opt/wireguard-manager"
CLIENT_DB="$WG_DIR/clients/database.json"
SERVER_CONFIG="/etc/wireguard/wg0.conf"
TEMPLATE="$WG_DIR/templates/client.conf"
LOG_FILE="$WG_DIR/logs/provisioning.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
if [[ $# -lt 1 ]]; then
echo "Usage: $0 <client_name> [server_endpoint] [dns_servers]"
echo "Example: $0 john-laptop vpn.example.com:51820 '1.1.1.1,8.8.8.8'"
exit 1
fi
CLIENT_NAME="$1"
SERVER_ENDPOINT="${2:-$(jq -r '.server_endpoint' "$CLIENT_DB")}"
DNS_SERVERS="${3:-$(jq -r '.dns_servers | join(",")' "$CLIENT_DB")}"
# Validate client name
if [[ ! "$CLIENT_NAME" =~ ^[a-zA-Z0-9_-]+$ ]]; then
log "ERROR: Client name must contain only alphanumeric characters, hyphens, and underscores"
exit 1
fi
# Check if client exists
if jq -e ".clients[] | select(.name == \"$CLIENT_NAME\")" "$CLIENT_DB" > /dev/null 2>&1; then
log "ERROR: Client '$CLIENT_NAME' already exists"
exit 1
fi
# Generate keys
CLIENT_PRIVATE_KEY=$(wg genkey)
CLIENT_PUBLIC_KEY=$(echo "$CLIENT_PRIVATE_KEY" | wg pubkey)
# Get next IP
NEXT_IP=$(jq -r '.next_ip' "$CLIENT_DB")
SUBNET_BASE=$(jq -r '.subnet' "$CLIENT_DB" | cut -d'/' -f1 | cut -d'.' -f1-3)
CLIENT_IP="$SUBNET_BASE.$NEXT_IP"
# Get server public key
SERVER_PUBLIC_KEY=$(jq -r '.server_public_key' "$CLIENT_DB")
if [[ "$SERVER_PUBLIC_KEY" == "" || "$SERVER_PUBLIC_KEY" == "null" ]]; then
SERVER_PUBLIC_KEY=$(wg show wg0 public-key 2>/dev/null || echo "")
fi
# Create client config
CLIENT_CONFIG="$WG_DIR/clients/configs/$CLIENT_NAME.conf"
sed -e "s/CLIENT_PRIVATE_KEY/$CLIENT_PRIVATE_KEY/g" \
-e "s/CLIENT_IP/$CLIENT_IP/g" \
-e "s/DNS_SERVERS/$DNS_SERVERS/g" \
-e "s/SERVER_PUBLIC_KEY/$SERVER_PUBLIC_KEY/g" \
-e "s/SERVER_ENDPOINT/$SERVER_ENDPOINT/g" \
"$TEMPLATE" > "$CLIENT_CONFIG"
chmod 600 "$CLIENT_CONFIG"
# Update database
jq --arg name "$CLIENT_NAME" \
--arg ip "$CLIENT_IP" \
--arg pubkey "$CLIENT_PUBLIC_KEY" \
--arg privkey "$CLIENT_PRIVATE_KEY" \
--argjson next_ip "$((NEXT_IP + 1))" \
'.clients += [{
"name": $name,
"ip": $ip,
"public_key": $pubkey,
"private_key": $privkey,
"created": now,
"active": true
}] | .next_ip = $next_ip' "$CLIENT_DB" > "${CLIENT_DB}.tmp"
mv "${CLIENT_DB}.tmp" "$CLIENT_DB"
# Generate QR code
qrencode -t ansiutf8 -r "$CLIENT_CONFIG"
log "Client '$CLIENT_NAME' provisioned successfully"
echo "Configuration saved to: $CLIENT_CONFIG"
EOF
chmod 755 "$WG_MANAGER_DIR/scripts/provision-client.sh"
success "Provisioning script created"
# Create management script
progress "Creating management utilities"
cat > "$WG_MANAGER_DIR/scripts/manage-clients.sh" << 'EOF'
#!/usr/bin/env bash
set -euo pipefail
WG_DIR="/opt/wireguard-manager"
CLIENT_DB="$WG_DIR/clients/database.json"
case "${1:-}" in
list)
echo "Active WireGuard Clients:"
jq -r '.clients[] | select(.active == true) | "\(.name) - \(.ip) - Created: \(.created | strftime("%Y-%m-%d %H:%M"))"' "$CLIENT_DB"
;;
revoke)
if [[ $# -ne 2 ]]; then
echo "Usage: $0 revoke <client_name>"
exit 1
fi
CLIENT_NAME="$2"
jq --arg name "$CLIENT_NAME" '(.clients[] | select(.name == $name) | .active) = false' "$CLIENT_DB" > "${CLIENT_DB}.tmp"
mv "${CLIENT_DB}.tmp" "$CLIENT_DB"
mv "$WG_DIR/clients/configs/$CLIENT_NAME.conf" "$WG_DIR/clients/revoked/" 2>/dev/null || true
echo "Client '$CLIENT_NAME' revoked"
;;
show)
if [[ $# -ne 2 ]]; then
echo "Usage: $0 show <client_name>"
exit 1
fi
CLIENT_NAME="$2"
if [[ -f "$WG_DIR/clients/configs/$CLIENT_NAME.conf" ]]; then
cat "$WG_DIR/clients/configs/$CLIENT_NAME.conf"
echo -e "\nQR Code:"
qrencode -t ansiutf8 -r "$WG_DIR/clients/configs/$CLIENT_NAME.conf"
else
echo "Client configuration not found"
exit 1
fi
;;
*)
echo "Usage: $0 {list|revoke|show} [client_name]"
echo " list - Show all active clients"
echo " revoke <name> - Revoke client access"
echo " show <name> - Show client config and QR code"
;;
esac
EOF
chmod 755 "$WG_MANAGER_DIR/scripts/manage-clients.sh"
# Create symlinks for easy access
ln -sf "$WG_MANAGER_DIR/scripts/provision-client.sh" /usr/local/bin/wg-provision
ln -sf "$WG_MANAGER_DIR/scripts/manage-clients.sh" /usr/local/bin/wg-manage
success "Management utilities created"
# Final verification
progress "Verifying installation"
if [[ -d "$WG_MANAGER_DIR" ]] && [[ -f "$WG_MANAGER_DIR/clients/database.json" ]] && [[ -x "$WG_MANAGER_DIR/scripts/provision-client.sh" ]]; then
success "Installation verification passed"
else
error "Installation verification failed"
exit 1
fi
# Show completion message
echo -e "\n${GREEN}WireGuard Client Management System installed successfully!${NC}"
echo
echo "Next steps:"
echo "1. Configure your server endpoint and subnet in the database:"
echo " $WG_MANAGER_DIR/clients/database.json"
echo
echo "2. Provision a new client:"
echo " wg-provision client-name [server-endpoint] [dns-servers]"
echo
echo "3. Manage existing clients:"
echo " wg-manage list|revoke|show [client-name]"
echo
echo "Configuration directory: $WG_MANAGER_DIR"
echo "Logs directory: $WG_MANAGER_DIR/logs"
Review the script before running. Execute with: bash install.sh