379 lines
12 KiB
Bash
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 "$@"
|