Files
proxmox-lxc-shell-commands/proxmox-lxc-wireguard.sh

379 lines
12 KiB
Bash

#!/usr/bin/env bash
# WireGuard VPN LXC Installation Script
# Description: WireGuard VPN Server with wg-easy Web UI
# OS: Debian 12 (Bookworm)
# Ports: VPN: 51820 (UDP), Web UI: 51821
# Last Updated: 2026-01-05
set -euo pipefail
#################################################################
# Configuration Variables
#################################################################
# Container Configuration
CT_ID=${CT_ID:-10001}
CT_HOSTNAME=${CT_HOSTNAME:-"eunha-wireguard"}
CT_CORES=${CT_CORES:-1}
CT_MEMORY=${CT_MEMORY:-512}
CT_SWAP=${CT_SWAP:-256}
CT_DISK_SIZE=${CT_DISK_SIZE:-4}
# Network Configuration
CT_IP=${CT_IP:-"dhcp"}
CT_GATEWAY=${CT_GATEWAY:-""}
CT_BRIDGE=${CT_BRIDGE:-"vmbr0"}
CT_NAMESERVER=${CT_NAMESERVER:-"8.8.8.8"}
# Storage Configuration
CT_STORAGE=${CT_STORAGE:-"local-lvm"}
TEMPLATE_STORAGE=${TEMPLATE_STORAGE:-"local"}
# Debian Template
DEBIAN_VERSION="12"
TEMPLATE_NAME=""
# WireGuard Configuration
WG_HOST=${WG_HOST:-"vpn.example.com"}
WG_PORT=${WG_PORT:-51820}
WG_UI_PORT=${WG_UI_PORT:-51821}
WG_PASSWORD=${WG_PASSWORD:-"Fkslrhenfk1@"}
WG_DEFAULT_DNS=${WG_DEFAULT_DNS:-"1.1.1.1"}
WG_DEFAULT_ADDRESS=${WG_DEFAULT_ADDRESS:-"10.8.0.x"}
WG_ALLOWED_IPS=${WG_ALLOWED_IPS:-"0.0.0.0/0, ::/0"}
WG_PERSISTENT_KEEPALIVE=${WG_PERSISTENT_KEEPALIVE:-25}
# Container Options
CT_ONBOOT=${CT_ONBOOT:-1}
CT_UNPRIVILEGED=${CT_UNPRIVILEGED:-0}
CT_FEATURES=${CT_FEATURES:-"nesting=1,keyctl=1"}
#################################################################
# Color Output Functions
#################################################################
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
info() { echo -e "${BLUE}[INFO]${NC} $1"; }
success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
error() { echo -e "${RED}[ERROR]${NC} $1"; }
#################################################################
# Validation Functions
#################################################################
check_root() {
if [[ $EUID -ne 0 ]]; then
error "This script must be run as root"
exit 1
fi
}
check_proxmox() {
if ! command -v pct &> /dev/null; then
error "This script must be run on a Proxmox VE host"
exit 1
fi
}
check_container_exists() {
if pct status "$CT_ID" &> /dev/null; then
error "Container ID $CT_ID already exists"
exit 1
fi
}
detect_and_download_template() {
info "Updating template database..."
pveam update 2>&1 || true
info "Detecting available Debian ${DEBIAN_VERSION} template..."
local available_template
available_template=$(pveam available --section system 2>/dev/null | grep "debian-${DEBIAN_VERSION}" | grep "standard" | tail -1 | awk '{print $2}')
if [[ -z "$available_template" ]]; then
error "No Debian ${DEBIAN_VERSION} template found"
exit 1
fi
TEMPLATE_NAME="$available_template"
info "Found template: $TEMPLATE_NAME"
if pveam list "$TEMPLATE_STORAGE" 2>/dev/null | grep -q "$TEMPLATE_NAME"; then
success "Template already downloaded"
return 0
fi
warn "Downloading Debian template..."
pveam download "$TEMPLATE_STORAGE" "$TEMPLATE_NAME" || {
error "Failed to download template"
exit 1
}
success "Template downloaded"
}
#################################################################
# Container Creation Functions
#################################################################
create_container() {
info "Creating LXC container $CT_ID ($CT_HOSTNAME)..."
local net_config="name=eth0,bridge=${CT_BRIDGE},ip=${CT_IP}"
if [[ "$CT_IP" != "dhcp" ]] && [[ -n "$CT_GATEWAY" ]]; then
net_config="${net_config},gw=${CT_GATEWAY}"
fi
pct create "$CT_ID" \
"${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE_NAME}" \
--hostname "$CT_HOSTNAME" \
--cores "$CT_CORES" \
--memory "$CT_MEMORY" \
--swap "$CT_SWAP" \
--rootfs "${CT_STORAGE}:${CT_DISK_SIZE}" \
--net0 "$net_config" \
--nameserver "$CT_NAMESERVER" \
--onboot "$CT_ONBOOT" \
--unprivileged "$CT_UNPRIVILEGED" \
--features "$CT_FEATURES" \
--ostype debian || {
error "Failed to create container"
exit 1
}
success "Container $CT_ID created"
# Add WireGuard-specific LXC configuration
info "Configuring LXC for WireGuard..."
cat >> /etc/pve/lxc/${CT_ID}.conf << 'LXCEOF'
lxc.cgroup2.devices.allow: c 10:200 rwm
lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file
LXCEOF
success "LXC configuration added"
}
start_container() {
# Ensure WireGuard kernel module is loaded on host
info "Loading WireGuard kernel module on host..."
modprobe wireguard 2>/dev/null || warn "WireGuard module may already be loaded"
info "Starting container $CT_ID..."
pct start "$CT_ID" || {
error "Failed to start container"
exit 1
}
info "Waiting for container to boot..."
sleep 5
success "Container started"
}
configure_autologin() {
info "Configuring automatic console login..."
pct exec "$CT_ID" -- bash -c "mkdir -p /etc/systemd/system/container-getty@1.service.d"
pct exec "$CT_ID" -- bash -c 'cat > /etc/systemd/system/container-getty@1.service.d/override.conf << EOF
[Service]
ExecStart=
ExecStart=-/sbin/agetty --autologin root --noclear --keep-baud tty%I 115200,38400,9600 \$TERM
EOF'
pct exec "$CT_ID" -- bash -c "systemctl daemon-reload"
success "Autologin configured"
}
#################################################################
# WireGuard Installation
#################################################################
install_docker() {
info "Installing Docker..."
pct exec "$CT_ID" -- bash -c "apt-get update -qq"
pct exec "$CT_ID" -- bash -c "DEBIAN_FRONTEND=noninteractive apt-get install -y -qq curl ca-certificates gnupg"
# Install Docker
pct exec "$CT_ID" -- bash -c "curl -fsSL https://get.docker.com | sh"
# Enable Docker
pct exec "$CT_ID" -- bash -c "systemctl enable docker"
pct exec "$CT_ID" -- bash -c "systemctl start docker"
success "Docker installed"
}
install_wireguard() {
info "Setting up WireGuard with wg-easy..."
# Create directories
pct exec "$CT_ID" -- bash -c "mkdir -p /opt/wireguard"
# Get container IP for WG_HOST if not set
local container_ip
container_ip=$(pct exec "$CT_ID" -- hostname -I 2>/dev/null | awk '{print $1}')
# Use container IP as default if WG_HOST is example domain
local wg_host_final="$WG_HOST"
if [[ "$WG_HOST" == "vpn.example.com" ]]; then
wg_host_final="$container_ip"
fi
# Generate bcrypt password hash (required for wg-easy v14+)
info "Generating bcrypt password hash..."
local password_hash
password_hash=$(pct exec "$CT_ID" -- bash -c "docker run --rm ghcr.io/wg-easy/wg-easy wgpw '${WG_PASSWORD}' 2>/dev/null" | grep -o '\$2[aby]\?\$[0-9]\+\$[./A-Za-z0-9]\+' | head -1)
if [[ -z "$password_hash" ]]; then
error "Failed to generate password hash"
exit 1
fi
info "Password hash generated successfully"
# Create docker-compose.yml using printf to avoid heredoc variable expansion issues
pct exec "$CT_ID" -- bash -c "cat > /opt/wireguard/docker-compose.yml << 'EOFYAML'
services:
wg-easy:
image: ghcr.io/wg-easy/wg-easy
container_name: wg-easy
restart: unless-stopped
cap_add:
- NET_ADMIN
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1
environment:
- LANG=en
- WG_HOST=__WG_HOST__
- PASSWORD_HASH=__PASSWORD_HASH__
- WG_PORT=__WG_PORT__
- WG_DEFAULT_DNS=__WG_DEFAULT_DNS__
- WG_DEFAULT_ADDRESS=__WG_DEFAULT_ADDRESS__
- WG_ALLOWED_IPS=__WG_ALLOWED_IPS__
- WG_PERSISTENT_KEEPALIVE=__WG_PERSISTENT_KEEPALIVE__
- UI_TRAFFIC_STATS=true
- UI_CHART_TYPE=1
volumes:
- ./data:/etc/wireguard
ports:
- \"__WG_PORT__:51820/udp\"
- \"__WG_UI_PORT__:51821/tcp\"
EOFYAML"
# Replace placeholders with actual values
pct exec "$CT_ID" -- sed -i "s|__WG_HOST__|${wg_host_final}|g" /opt/wireguard/docker-compose.yml
pct exec "$CT_ID" -- sed -i "s|__PASSWORD_HASH__|${password_hash}|g" /opt/wireguard/docker-compose.yml
pct exec "$CT_ID" -- sed -i "s|__WG_PORT__|${WG_PORT}|g" /opt/wireguard/docker-compose.yml
pct exec "$CT_ID" -- sed -i "s|__WG_UI_PORT__|${WG_UI_PORT}|g" /opt/wireguard/docker-compose.yml
pct exec "$CT_ID" -- sed -i "s|__WG_DEFAULT_DNS__|${WG_DEFAULT_DNS}|g" /opt/wireguard/docker-compose.yml
pct exec "$CT_ID" -- sed -i "s|__WG_DEFAULT_ADDRESS__|${WG_DEFAULT_ADDRESS}|g" /opt/wireguard/docker-compose.yml
pct exec "$CT_ID" -- sed -i "s|__WG_ALLOWED_IPS__|${WG_ALLOWED_IPS}|g" /opt/wireguard/docker-compose.yml
pct exec "$CT_ID" -- sed -i "s|__WG_PERSISTENT_KEEPALIVE__|${WG_PERSISTENT_KEEPALIVE}|g" /opt/wireguard/docker-compose.yml
# Create data directory
pct exec "$CT_ID" -- bash -c "mkdir -p /opt/wireguard/data"
# Start WireGuard
info "Starting WireGuard..."
pct exec "$CT_ID" -- bash -c "cd /opt/wireguard && docker compose up -d"
sleep 5
success "WireGuard installed and running"
}
add_container_notes() {
info "Adding container notes..."
local container_ip
container_ip=$(pct exec "$CT_ID" -- hostname -I 2>/dev/null | awk '{print $1}')
local notes="WireGuard VPN Server
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Container ID: ${CT_ID}
Hostname: ${CT_HOSTNAME}
IP Address: ${container_ip}
CPU Cores: ${CT_CORES}
Memory: ${CT_MEMORY}MB
ACCESS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Web UI: http://${container_ip}:${WG_UI_PORT}
Password: ${WG_PASSWORD}
VPN Port: ${container_ip}:${WG_PORT} (UDP)
VPN Subnet: ${WG_DEFAULT_ADDRESS}
MANAGEMENT
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Logs: pct exec ${CT_ID} -- docker logs wg-easy -f
Restart: pct exec ${CT_ID} -- docker restart wg-easy
Config: /opt/wireguard/
Clients: /opt/wireguard/data/
CONTAINER
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Enter: pct enter ${CT_ID}
Stop: pct stop ${CT_ID}
Start: pct start ${CT_ID}"
pct set "$CT_ID" -description "$notes" 2>/dev/null || true
success "Notes added"
}
display_info() {
local container_ip
container_ip=$(pct exec "$CT_ID" -- hostname -I | awk '{print $1}')
echo ""
echo "================================================================="
success "WireGuard VPN LXC Container Setup Complete!"
echo "================================================================="
echo ""
echo "Container Details:"
echo " Container ID: $CT_ID"
echo " Hostname: $CT_HOSTNAME"
echo " IP Address: $container_ip"
echo ""
echo "WireGuard Access:"
echo " Web UI: http://${container_ip}:${WG_UI_PORT}"
echo " Password: $WG_PASSWORD"
echo " VPN Port: ${container_ip}:${WG_PORT} (UDP)"
echo " VPN Subnet: $WG_DEFAULT_ADDRESS"
echo ""
echo "Management:"
echo " Logs: pct exec $CT_ID -- docker logs wg-easy -f"
echo " Clients: /opt/wireguard/data/"
echo " Enter: pct enter $CT_ID"
echo ""
echo "================================================================="
}
#################################################################
# Main Execution
#################################################################
main() {
info "Starting WireGuard VPN LXC container creation..."
echo ""
check_root
check_proxmox
check_container_exists
detect_and_download_template
create_container
start_container
configure_autologin
install_docker
install_wireguard
add_container_notes
display_info
}
main "$@"