1409 lines
41 KiB
Bash
1409 lines
41 KiB
Bash
|
#!/bin/bash
|
||
|
|
||
|
#############################################################
|
||
|
# #
|
||
|
# Mullvad VPN Gateway Installer for LXC #
|
||
|
# Secure VPN Gateway with Permanent Killswitch #
|
||
|
# #
|
||
|
# Usage: curl -sSL https://your-domain/install.sh | bash #
|
||
|
# #
|
||
|
#############################################################
|
||
|
|
||
|
set -e # Exit on error
|
||
|
|
||
|
# Colors for output
|
||
|
RED='\033[0;31m'
|
||
|
GREEN='\033[0;32m'
|
||
|
YELLOW='\033[1;33m'
|
||
|
BLUE='\033[0;34m'
|
||
|
PURPLE='\033[0;35m'
|
||
|
CYAN='\033[0;36m'
|
||
|
BOLD='\033[1m'
|
||
|
NC='\033[0m' # No Color
|
||
|
|
||
|
# Configuration variables
|
||
|
INSTALL_DIR="/opt/vpn-gateway"
|
||
|
LOG_FILE="/var/log/vpn-gateway-install.log"
|
||
|
GITHUB_REPO="https://raw.githubusercontent.com/yourusername/vpn-gateway/main"
|
||
|
VERSION="1.0.0"
|
||
|
|
||
|
# System variables (will be auto-detected)
|
||
|
LAN_INTERFACE=""
|
||
|
LAN_IP=""
|
||
|
LAN_NETWORK=""
|
||
|
CONTAINER_TYPE=""
|
||
|
MULLVAD_ACCOUNT=""
|
||
|
|
||
|
# VPN Provider variables
|
||
|
VPN_PROVIDER=""
|
||
|
WG_CONFIG_PATH=""
|
||
|
WG_IMPORTED_CONFIG=""
|
||
|
WG_CUSTOM_LOCATION=""
|
||
|
WG_CUSTOM_NAME=""
|
||
|
WG_ENDPOINT=""
|
||
|
WG_SERVER_PUBKEY=""
|
||
|
WG_CLIENT_PRIVKEY=""
|
||
|
WG_CLIENT_IP=""
|
||
|
WG_DNS=""
|
||
|
WG_ALLOWED_IPS=""
|
||
|
BACKUP_SERVERS=()
|
||
|
|
||
|
# ASCII Art Banner
|
||
|
show_banner() {
|
||
|
clear
|
||
|
echo -e "${CYAN}"
|
||
|
cat << "EOF"
|
||
|
__ __ _ _ _ __ ______ _ _
|
||
|
| \/ | | | | | | \ \ / / _ \| \ | |
|
||
|
| \ / |_ _| | |_ ____ _ ___| | \ \ / /| |_) | \| |
|
||
|
| |\/| | | | | | \ \ / / _` |/ _ | | \ V / | __/| . ` |
|
||
|
| | | | |_| | | |\ V | (_| | __| | | | | | | |\ |
|
||
|
|_| |_|\__,_|_|_| \_/ \__,_|\___|_| |_| |_| |_| \_|
|
||
|
|
||
|
Secure Gateway with Permanent Killswitch
|
||
|
EOF
|
||
|
echo -e "${NC}"
|
||
|
echo -e "${BOLD}Version:${NC} ${VERSION}"
|
||
|
echo -e "${BOLD}GitHub:${NC} github.com/yourusername/vpn-gateway"
|
||
|
echo ""
|
||
|
}
|
||
|
|
||
|
# Logging functions
|
||
|
log() {
|
||
|
echo -e "${GREEN}[+]${NC} $1"
|
||
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
|
||
|
}
|
||
|
|
||
|
error() {
|
||
|
echo -e "${RED}[!]${NC} $1"
|
||
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1" >> "$LOG_FILE"
|
||
|
}
|
||
|
|
||
|
warning() {
|
||
|
echo -e "${YELLOW}[*]${NC} $1"
|
||
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] WARNING: $1" >> "$LOG_FILE"
|
||
|
}
|
||
|
|
||
|
info() {
|
||
|
echo -e "${BLUE}[i]${NC} $1"
|
||
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] INFO: $1" >> "$LOG_FILE"
|
||
|
}
|
||
|
|
||
|
# Progress spinner
|
||
|
spinner() {
|
||
|
local pid=$1
|
||
|
local delay=0.1
|
||
|
local spinstr='|/-\'
|
||
|
while [ "$(ps a | awk '{print $1}' | grep $pid)" ]; do
|
||
|
local temp=${spinstr#?}
|
||
|
printf " [%c] " "$spinstr"
|
||
|
local spinstr=$temp${spinstr%"$temp"}
|
||
|
sleep $delay
|
||
|
printf "\b\b\b\b\b\b"
|
||
|
done
|
||
|
printf " \b\b\b\b"
|
||
|
}
|
||
|
|
||
|
# Check if running as root
|
||
|
check_root() {
|
||
|
if [[ $EUID -ne 0 ]]; then
|
||
|
error "This script must be run as root"
|
||
|
exit 1
|
||
|
fi
|
||
|
}
|
||
|
|
||
|
# Detect container type
|
||
|
detect_container() {
|
||
|
info "Detecting container type..."
|
||
|
|
||
|
if [ -f /run/systemd/container ]; then
|
||
|
CONTAINER_TYPE=$(cat /run/systemd/container)
|
||
|
elif [ -f /proc/1/environ ]; then
|
||
|
if grep -q lxc /proc/1/environ; then
|
||
|
CONTAINER_TYPE="lxc"
|
||
|
elif grep -q docker /proc/1/environ; then
|
||
|
CONTAINER_TYPE="docker"
|
||
|
fi
|
||
|
fi
|
||
|
|
||
|
if [ -n "$CONTAINER_TYPE" ]; then
|
||
|
log "Container type: $CONTAINER_TYPE"
|
||
|
else
|
||
|
warning "Could not detect container type, assuming LXC"
|
||
|
CONTAINER_TYPE="lxc"
|
||
|
fi
|
||
|
}
|
||
|
|
||
|
# Auto-detect network configuration
|
||
|
detect_network() {
|
||
|
info "Auto-detecting network configuration..."
|
||
|
|
||
|
# Find primary network interface (excluding lo and wg*)
|
||
|
LAN_INTERFACE=$(ip route | grep default | awk '{print $5}' | head -n1)
|
||
|
|
||
|
if [ -z "$LAN_INTERFACE" ]; then
|
||
|
# Fallback: find first UP interface
|
||
|
LAN_INTERFACE=$(ip link show | grep "state UP" | grep -v "lo:" | awk -F': ' '{print $2}' | head -n1)
|
||
|
fi
|
||
|
|
||
|
if [ -z "$LAN_INTERFACE" ]; then
|
||
|
error "Could not detect network interface"
|
||
|
read -p "Please enter your network interface (e.g., eth0): " LAN_INTERFACE
|
||
|
fi
|
||
|
|
||
|
# Get IP address
|
||
|
LAN_IP=$(ip -4 addr show "$LAN_INTERFACE" | grep inet | awk '{print $2}' | cut -d/ -f1)
|
||
|
|
||
|
# Get network subnet
|
||
|
LAN_NETWORK=$(ip route | grep "$LAN_INTERFACE" | grep -v default | awk '{print $1}' | head -n1)
|
||
|
|
||
|
if [ -z "$LAN_NETWORK" ]; then
|
||
|
# Calculate from IP
|
||
|
LAN_NETWORK=$(echo "$LAN_IP" | cut -d. -f1-3).0/24
|
||
|
fi
|
||
|
|
||
|
echo ""
|
||
|
echo -e "${CYAN}Detected Network Configuration:${NC}"
|
||
|
echo -e " Interface: ${BOLD}$LAN_INTERFACE${NC}"
|
||
|
echo -e " IP Address: ${BOLD}$LAN_IP${NC}"
|
||
|
echo -e " Network: ${BOLD}$LAN_NETWORK${NC}"
|
||
|
echo ""
|
||
|
|
||
|
read -p "Is this correct? (Y/n): " -n 1 -r
|
||
|
echo ""
|
||
|
if [[ ! $REPLY =~ ^[Yy]$ ]] && [ -n "$REPLY" ]; then
|
||
|
read -p "Enter network interface: " LAN_INTERFACE
|
||
|
read -p "Enter network subnet (e.g., 192.168.1.0/24): " LAN_NETWORK
|
||
|
LAN_IP=$(ip -4 addr show "$LAN_INTERFACE" | grep inet | awk '{print $2}' | cut -d/ -f1)
|
||
|
fi
|
||
|
}
|
||
|
|
||
|
# Choose VPN provider
|
||
|
choose_vpn_provider() {
|
||
|
echo ""
|
||
|
echo -e "${CYAN}VPN Provider Selection${NC}"
|
||
|
echo ""
|
||
|
echo -e " ${BOLD}1)${NC} Mullvad VPN (Commercial Provider)"
|
||
|
echo -e " ${BOLD}2)${NC} Custom WireGuard (Own Server/VPS)"
|
||
|
echo -e " ${BOLD}3)${NC} Import existing WireGuard config"
|
||
|
echo ""
|
||
|
|
||
|
while true; do
|
||
|
read -p "Select provider [1-3]: " VPN_PROVIDER_CHOICE
|
||
|
case $VPN_PROVIDER_CHOICE in
|
||
|
1)
|
||
|
VPN_PROVIDER="mullvad"
|
||
|
get_mullvad_account
|
||
|
break
|
||
|
;;
|
||
|
2)
|
||
|
VPN_PROVIDER="custom"
|
||
|
get_custom_wireguard_details
|
||
|
break
|
||
|
;;
|
||
|
3)
|
||
|
VPN_PROVIDER="import"
|
||
|
import_wireguard_config
|
||
|
break
|
||
|
;;
|
||
|
*)
|
||
|
error "Invalid choice. Please select 1, 2, or 3."
|
||
|
;;
|
||
|
esac
|
||
|
done
|
||
|
|
||
|
log "VPN provider selected: $VPN_PROVIDER"
|
||
|
}
|
||
|
|
||
|
# Get Mullvad account
|
||
|
get_mullvad_account() {
|
||
|
echo ""
|
||
|
echo -e "${CYAN}Mullvad VPN Account Setup${NC}"
|
||
|
echo -e "You need a Mullvad account number to continue."
|
||
|
echo -e "Get one at: ${BOLD}https://mullvad.net${NC}"
|
||
|
echo ""
|
||
|
|
||
|
while [ -z "$MULLVAD_ACCOUNT" ]; do
|
||
|
read -p "Enter your Mullvad account number: " MULLVAD_ACCOUNT
|
||
|
|
||
|
# Validate format (16 digits)
|
||
|
if [[ ! "$MULLVAD_ACCOUNT" =~ ^[0-9]{16}$ ]]; then
|
||
|
error "Invalid account format. Should be 16 digits."
|
||
|
MULLVAD_ACCOUNT=""
|
||
|
fi
|
||
|
done
|
||
|
|
||
|
log "Mullvad account configured"
|
||
|
}
|
||
|
|
||
|
# Get custom WireGuard details
|
||
|
get_custom_wireguard_details() {
|
||
|
echo ""
|
||
|
echo -e "${CYAN}Custom WireGuard Configuration${NC}"
|
||
|
echo -e "Configure your own WireGuard server/VPS"
|
||
|
echo ""
|
||
|
|
||
|
# Server endpoint
|
||
|
while [ -z "$WG_ENDPOINT" ]; do
|
||
|
read -p "Server endpoint (IP:Port or domain:port): " WG_ENDPOINT
|
||
|
if [[ ! "$WG_ENDPOINT" =~ ^[^:]+:[0-9]+$ ]]; then
|
||
|
error "Invalid format. Use IP:Port or domain:port (e.g., 1.2.3.4:51820)"
|
||
|
WG_ENDPOINT=""
|
||
|
fi
|
||
|
done
|
||
|
|
||
|
# Server public key
|
||
|
while [ -z "$WG_SERVER_PUBKEY" ]; do
|
||
|
read -p "Server public key: " WG_SERVER_PUBKEY
|
||
|
if [[ ! "$WG_SERVER_PUBKEY" =~ ^[A-Za-z0-9+/]{43}=$ ]]; then
|
||
|
error "Invalid public key format"
|
||
|
WG_SERVER_PUBKEY=""
|
||
|
fi
|
||
|
done
|
||
|
|
||
|
# Client private key (optional - generate if not provided)
|
||
|
read -p "Client private key (leave empty to generate): " WG_CLIENT_PRIVKEY
|
||
|
if [ -z "$WG_CLIENT_PRIVKEY" ]; then
|
||
|
WG_CLIENT_PRIVKEY=$(wg genkey)
|
||
|
WG_CLIENT_PUBKEY=$(echo "$WG_CLIENT_PRIVKEY" | wg pubkey)
|
||
|
echo -e "${GREEN}Generated keypair:${NC}"
|
||
|
echo -e " Private key: ${BOLD}$WG_CLIENT_PRIVKEY${NC}"
|
||
|
echo -e " Public key: ${BOLD}$WG_CLIENT_PUBKEY${NC}"
|
||
|
echo -e "${YELLOW}Add this public key to your server's peer configuration!${NC}"
|
||
|
read -p "Press Enter when you've added the key to your server..."
|
||
|
fi
|
||
|
|
||
|
# Client IP in VPN
|
||
|
read -p "Client VPN IP (e.g., 10.0.0.2/32): " WG_CLIENT_IP
|
||
|
if [ -z "$WG_CLIENT_IP" ]; then
|
||
|
WG_CLIENT_IP="10.0.0.2/32"
|
||
|
fi
|
||
|
|
||
|
# DNS servers
|
||
|
read -p "DNS servers (comma-separated, default: 1.1.1.1,1.0.0.1): " WG_DNS
|
||
|
if [ -z "$WG_DNS" ]; then
|
||
|
WG_DNS="1.1.1.1,1.0.0.1"
|
||
|
fi
|
||
|
|
||
|
# Allowed IPs
|
||
|
read -p "Allowed IPs (default: 0.0.0.0/0): " WG_ALLOWED_IPS
|
||
|
if [ -z "$WG_ALLOWED_IPS" ]; then
|
||
|
WG_ALLOWED_IPS="0.0.0.0/0"
|
||
|
fi
|
||
|
|
||
|
# Ask about kill switch bypass
|
||
|
echo ""
|
||
|
read -p "Allow direct access to server IP (bypass killswitch)? (Y/n): " -n 1 -r
|
||
|
echo ""
|
||
|
WG_DNS="1.1.1.1,1.0.0.1"
|
||
|
fi
|
||
|
|
||
|
# AllowedIPs
|
||
|
read -p "Route all traffic through VPN? (Y/n): " -n 1 -r
|
||
|
echo ""
|
||
|
if [[ $REPLY =~ ^[Nn]$ ]]; then
|
||
|
read -p "Allowed IPs (comma-separated): " WG_ALLOWED_IPS
|
||
|
else
|
||
|
WG_ALLOWED_IPS="0.0.0.0/0,::/0"
|
||
|
fi
|
||
|
|
||
|
# Optional: Multiple servers for failover
|
||
|
read -p "Add backup servers? (y/N): " -n 1 -r
|
||
|
echo ""
|
||
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||
|
BACKUP_SERVERS=()
|
||
|
while true; do
|
||
|
read -p "Backup server endpoint (or 'done' to finish): " BACKUP
|
||
|
if [ "$BACKUP" = "done" ] || [ -z "$BACKUP" ]; then
|
||
|
break
|
||
|
fi
|
||
|
BACKUP_SERVERS+=("$BACKUP")
|
||
|
done
|
||
|
fi
|
||
|
|
||
|
# Save custom server name for later use
|
||
|
WG_CUSTOM_NAME="${WG_ENDPOINT%:*}_custom"
|
||
|
WG_CUSTOM_LOCATION="Custom Location"
|
||
|
|
||
|
log "Custom WireGuard configuration complete"
|
||
|
}
|
||
|
|
||
|
# Import existing WireGuard config
|
||
|
import_wireguard_config() {
|
||
|
echo ""
|
||
|
echo -e "${CYAN}Import WireGuard Configuration${NC}"
|
||
|
echo ""
|
||
|
|
||
|
read -p "Path to WireGuard config file: " WG_CONFIG_PATH
|
||
|
|
||
|
if [ ! -f "$WG_CONFIG_PATH" ]; then
|
||
|
error "File not found: $WG_CONFIG_PATH"
|
||
|
choose_vpn_provider
|
||
|
return
|
||
|
fi
|
||
|
|
||
|
# Validate config
|
||
|
if ! grep -q "\[Interface\]" "$WG_CONFIG_PATH" || ! grep -q "\[Peer\]" "$WG_CONFIG_PATH"; then
|
||
|
error "Invalid WireGuard configuration file"
|
||
|
choose_vpn_provider
|
||
|
return
|
||
|
fi
|
||
|
|
||
|
# Copy config
|
||
|
cp "$WG_CONFIG_PATH" /tmp/imported_wg.conf
|
||
|
WG_IMPORTED_CONFIG="/tmp/imported_wg.conf"
|
||
|
|
||
|
log "WireGuard config imported successfully"
|
||
|
}
|
||
|
|
||
|
# Choose VPN provider
|
||
|
choose_vpn_provider() {
|
||
|
echo ""
|
||
|
echo -e "${CYAN}Choose your VPN provider:${NC}"
|
||
|
echo "1) Mullvad VPN"
|
||
|
echo "2) Custom WireGuard Server"
|
||
|
echo "3) Import existing WireGuard config"
|
||
|
echo ""
|
||
|
|
||
|
while true; do
|
||
|
read -p "Select option (1-3): " choice
|
||
|
case $choice in
|
||
|
1)
|
||
|
VPN_PROVIDER="mullvad"
|
||
|
get_mullvad_account
|
||
|
break
|
||
|
;;
|
||
|
2)
|
||
|
VPN_PROVIDER="custom"
|
||
|
get_custom_wireguard_details
|
||
|
break
|
||
|
;;
|
||
|
3)
|
||
|
VPN_PROVIDER="import"
|
||
|
import_wireguard_config
|
||
|
break
|
||
|
;;
|
||
|
*)
|
||
|
error "Invalid choice. Please select 1, 2, or 3."
|
||
|
;;
|
||
|
esac
|
||
|
done
|
||
|
}
|
||
|
|
||
|
# Check system requirements
|
||
|
check_requirements() {
|
||
|
info "Checking system requirements..."
|
||
|
|
||
|
# Check OS
|
||
|
if [ -f /etc/os-release ]; then
|
||
|
. /etc/os-release
|
||
|
OS=$NAME
|
||
|
VER=$VERSION_ID
|
||
|
fi
|
||
|
|
||
|
if [[ ! "$OS" =~ "Ubuntu" ]] && [[ ! "$OS" =~ "Debian" ]]; then
|
||
|
warning "This script is tested on Ubuntu/Debian. Continue at your own risk."
|
||
|
read -p "Continue anyway? (y/N): " -n 1 -r
|
||
|
echo ""
|
||
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||
|
exit 1
|
||
|
fi
|
||
|
fi
|
||
|
|
||
|
# Check kernel modules for WireGuard
|
||
|
if ! lsmod | grep -q wireguard; then
|
||
|
warning "WireGuard kernel module not loaded"
|
||
|
modprobe wireguard 2>/dev/null || true
|
||
|
fi
|
||
|
|
||
|
log "System requirements checked"
|
||
|
}
|
||
|
|
||
|
# Install dependencies
|
||
|
install_dependencies() {
|
||
|
log "Installing dependencies..."
|
||
|
|
||
|
export DEBIAN_FRONTEND=noninteractive
|
||
|
|
||
|
# Update package lists
|
||
|
apt-get update &>/dev/null &
|
||
|
spinner $!
|
||
|
|
||
|
# Install required packages
|
||
|
local packages=(
|
||
|
wireguard
|
||
|
wireguard-tools
|
||
|
iptables
|
||
|
iptables-persistent
|
||
|
python3
|
||
|
python3-pip
|
||
|
python3-venv
|
||
|
nginx
|
||
|
curl
|
||
|
wget
|
||
|
git
|
||
|
resolvconf
|
||
|
net-tools
|
||
|
jq
|
||
|
)
|
||
|
|
||
|
for package in "${packages[@]}"; do
|
||
|
echo -n " Installing $package..."
|
||
|
apt-get install -y "$package" &>/dev/null &
|
||
|
spinner $!
|
||
|
echo " ✓"
|
||
|
done
|
||
|
|
||
|
log "Dependencies installed successfully"
|
||
|
}
|
||
|
|
||
|
# Create directory structure
|
||
|
create_directories() {
|
||
|
log "Creating directory structure..."
|
||
|
|
||
|
mkdir -p "$INSTALL_DIR"/{scripts,config,static,logs}
|
||
|
mkdir -p /etc/wireguard
|
||
|
mkdir -p /var/log
|
||
|
|
||
|
log "Directories created"
|
||
|
}
|
||
|
|
||
|
# Install killswitch script
|
||
|
install_killswitch() {
|
||
|
log "Installing permanent killswitch..."
|
||
|
|
||
|
# Create killswitch script
|
||
|
cat > /usr/local/bin/vpn-killswitch.sh << 'EOFSRIPT'
|
||
|
#!/bin/bash
|
||
|
|
||
|
LAN_IF="__LAN_INTERFACE__"
|
||
|
LAN_NET="__LAN_NETWORK__"
|
||
|
|
||
|
enable_killswitch() {
|
||
|
echo "ENABLING PERMANENT KILLSWITCH - NO INTERNET WITHOUT VPN"
|
||
|
|
||
|
# Reset all rules
|
||
|
iptables -F
|
||
|
iptables -X
|
||
|
iptables -t nat -F
|
||
|
iptables -t nat -X
|
||
|
iptables -t mangle -F
|
||
|
iptables -t mangle -X
|
||
|
|
||
|
# DEFAULT: BLOCK EVERYTHING
|
||
|
iptables -P INPUT DROP
|
||
|
iptables -P FORWARD DROP
|
||
|
iptables -P OUTPUT DROP
|
||
|
|
||
|
# Allow loopback
|
||
|
iptables -A INPUT -i lo -j ACCEPT
|
||
|
iptables -A OUTPUT -o lo -j ACCEPT
|
||
|
|
||
|
# Allow LAN communication
|
||
|
iptables -A INPUT -i $LAN_IF -s $LAN_NET -j ACCEPT
|
||
|
iptables -A OUTPUT -o $LAN_IF -d $LAN_NET -j ACCEPT
|
||
|
|
||
|
# Allow DNS for initial connection (root only)
|
||
|
iptables -A OUTPUT -p udp --dport 53 -m owner --uid-owner root -j ACCEPT
|
||
|
iptables -A OUTPUT -p tcp --dport 53 -m owner --uid-owner root -j ACCEPT
|
||
|
|
||
|
# Allow established connections
|
||
|
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
||
|
iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
||
|
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
||
|
|
||
|
# Allow forwarding from LAN to VPN (when active)
|
||
|
iptables -A FORWARD -i $LAN_IF -s $LAN_NET -j ACCEPT
|
||
|
|
||
|
# Block IPv6 completely
|
||
|
ip6tables -P INPUT DROP
|
||
|
ip6tables -P FORWARD DROP
|
||
|
ip6tables -P OUTPUT DROP
|
||
|
ip6tables -A INPUT -i lo -j ACCEPT
|
||
|
ip6tables -A OUTPUT -o lo -j ACCEPT
|
||
|
|
||
|
# Save rules
|
||
|
iptables-save > /etc/iptables/rules.v4
|
||
|
ip6tables-save > /etc/iptables/rules.v6
|
||
|
|
||
|
echo "KILLSWITCH ACTIVE - System is now protected"
|
||
|
}
|
||
|
|
||
|
disable_killswitch() {
|
||
|
echo "WARNING: Killswitch cannot be disabled for security!"
|
||
|
enable_killswitch
|
||
|
}
|
||
|
|
||
|
case "$1" in
|
||
|
enable|disable)
|
||
|
enable_killswitch
|
||
|
;;
|
||
|
*)
|
||
|
echo "Usage: $0 {enable|disable}"
|
||
|
exit 1
|
||
|
;;
|
||
|
esac
|
||
|
EOFSCRIPT
|
||
|
|
||
|
# Replace placeholders
|
||
|
sed -i "s|__LAN_INTERFACE__|$LAN_INTERFACE|g" /usr/local/bin/vpn-killswitch.sh
|
||
|
sed -i "s|__LAN_NETWORK__|$LAN_NETWORK|g" /usr/local/bin/vpn-killswitch.sh
|
||
|
|
||
|
chmod +x /usr/local/bin/vpn-killswitch.sh
|
||
|
|
||
|
# Create systemd service
|
||
|
cat > /etc/systemd/system/vpn-killswitch.service << 'EOF'
|
||
|
[Unit]
|
||
|
Description=VPN Killswitch - Block ALL traffic except through VPN
|
||
|
DefaultDependencies=no
|
||
|
Before=network-pre.target
|
||
|
Wants=network-pre.target
|
||
|
|
||
|
[Service]
|
||
|
Type=oneshot
|
||
|
RemainAfterExit=yes
|
||
|
ExecStart=/usr/local/bin/vpn-killswitch.sh enable
|
||
|
|
||
|
[Install]
|
||
|
WantedBy=sysinit.target
|
||
|
EOF
|
||
|
|
||
|
# Enable and start killswitch
|
||
|
systemctl daemon-reload
|
||
|
systemctl enable vpn-killswitch.service
|
||
|
systemctl start vpn-killswitch.service
|
||
|
|
||
|
log "Killswitch installed and activated"
|
||
|
}
|
||
|
|
||
|
# Install VPN provider specific components
|
||
|
install_vpn_provider() {
|
||
|
log "Installing VPN provider components..."
|
||
|
|
||
|
case "$VPN_PROVIDER" in
|
||
|
mullvad)
|
||
|
install_mullvad
|
||
|
;;
|
||
|
custom)
|
||
|
setup_custom_provider
|
||
|
;;
|
||
|
import)
|
||
|
setup_import_provider
|
||
|
;;
|
||
|
esac
|
||
|
}
|
||
|
|
||
|
# Install Mullvad
|
||
|
install_mullvad() {
|
||
|
log "Installing Mullvad client..."
|
||
|
|
||
|
# Download Mullvad signing key
|
||
|
curl -fsSL https://mullvad.net/media/mullvad-code-signing.asc | gpg --dearmor -o /usr/share/keyrings/mullvad-keyring.gpg
|
||
|
|
||
|
# Add Mullvad repository
|
||
|
echo "deb [signed-by=/usr/share/keyrings/mullvad-keyring.gpg arch=$( dpkg --print-architecture )] https://repository.mullvad.net/deb/stable $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/mullvad.list
|
||
|
|
||
|
# Update and install
|
||
|
apt-get update &>/dev/null
|
||
|
apt-get install -y mullvad-vpn &>/dev/null || {
|
||
|
warning "Could not install Mullvad client, using WireGuard directly"
|
||
|
}
|
||
|
|
||
|
# Login to Mullvad
|
||
|
if command -v mullvad &>/dev/null; then
|
||
|
mullvad account login "$MULLVAD_ACCOUNT" &>/dev/null || {
|
||
|
warning "Could not login to Mullvad account"
|
||
|
}
|
||
|
fi
|
||
|
|
||
|
# Generate WireGuard keys
|
||
|
if [ ! -f /etc/wireguard/mullvad_private.key ]; then
|
||
|
wg genkey | tee /etc/wireguard/mullvad_private.key | wg pubkey > /etc/wireguard/mullvad_public.key
|
||
|
chmod 600 /etc/wireguard/mullvad_private.key
|
||
|
fi
|
||
|
|
||
|
# Save config
|
||
|
echo "mullvad" > "$INSTALL_DIR/provider.conf"
|
||
|
echo "$MULLVAD_ACCOUNT" > "$INSTALL_DIR/.mullvad_account"
|
||
|
chmod 600 "$INSTALL_DIR/.mullvad_account"
|
||
|
|
||
|
log "Mullvad configuration complete"
|
||
|
}
|
||
|
|
||
|
# Setup custom provider
|
||
|
setup_custom_provider() {
|
||
|
log "Setting up custom WireGuard provider..."
|
||
|
|
||
|
# Create config file for custom servers
|
||
|
mkdir -p "$INSTALL_DIR/providers"
|
||
|
|
||
|
# Save the custom server configuration
|
||
|
cat > "$INSTALL_DIR/providers/custom_servers.json" << EOF
|
||
|
{
|
||
|
"$WG_CUSTOM_LOCATION": {
|
||
|
"Custom": [{
|
||
|
"hostname": "$WG_CUSTOM_NAME",
|
||
|
"endpoint": "$WG_ENDPOINT",
|
||
|
"public_key": "$WG_SERVER_PUBKEY",
|
||
|
"private_key": "$WG_CLIENT_PRIVKEY",
|
||
|
"address": "$WG_CLIENT_IP",
|
||
|
"dns": "$WG_DNS",
|
||
|
"allowed_ips": "$WG_ALLOWED_IPS",
|
||
|
"type": "WireGuard",
|
||
|
"provider": "Custom"
|
||
|
}]
|
||
|
}
|
||
|
}
|
||
|
EOF
|
||
|
|
||
|
# Save provider config
|
||
|
echo "custom" > "$INSTALL_DIR/provider.conf"
|
||
|
|
||
|
# If backup servers were provided
|
||
|
if [ ${#BACKUP_SERVERS[@]} -gt 0 ]; then
|
||
|
log "Adding backup servers..."
|
||
|
# Additional logic for backup servers
|
||
|
fi
|
||
|
|
||
|
log "Custom provider setup complete"
|
||
|
}
|
||
|
|
||
|
# Setup import provider
|
||
|
setup_import_provider() {
|
||
|
log "Setting up import provider..."
|
||
|
|
||
|
mkdir -p "$INSTALL_DIR/providers/imported"
|
||
|
|
||
|
# Copy imported config
|
||
|
if [ -n "$WG_IMPORTED_CONFIG" ]; then
|
||
|
cp "$WG_IMPORTED_CONFIG" "$INSTALL_DIR/providers/imported/"
|
||
|
log "Imported configuration saved"
|
||
|
fi
|
||
|
|
||
|
# Save provider config
|
||
|
echo "import" > "$INSTALL_DIR/provider.conf"
|
||
|
|
||
|
log "Import provider setup complete"
|
||
|
}
|
||
|
|
||
|
# Install VPN provider (unified function)
|
||
|
install_vpn_provider() {
|
||
|
case "$VPN_PROVIDER" in
|
||
|
"mullvad")
|
||
|
setup_mullvad_provider
|
||
|
;;
|
||
|
"custom")
|
||
|
setup_custom_provider
|
||
|
;;
|
||
|
"import")
|
||
|
setup_import_provider
|
||
|
;;
|
||
|
*)
|
||
|
error "Unknown VPN provider: $VPN_PROVIDER"
|
||
|
exit 1
|
||
|
;;
|
||
|
esac
|
||
|
}
|
||
|
|
||
|
# Install Python backend
|
||
|
install_backend() {
|
||
|
log "Installing VPN Gateway backend..."
|
||
|
|
||
|
# Ensure install directory exists
|
||
|
mkdir -p "$INSTALL_DIR"
|
||
|
|
||
|
# Create virtual environment
|
||
|
python3 -m venv "$INSTALL_DIR/venv"
|
||
|
source "$INSTALL_DIR/venv/bin/activate"
|
||
|
|
||
|
# Install Python packages
|
||
|
pip install --upgrade pip &>/dev/null
|
||
|
pip install flask flask-cors requests gunicorn pyyaml &>/dev/null
|
||
|
|
||
|
# Download or create the multi-provider backend
|
||
|
# For production, this would be downloaded from GitHub
|
||
|
# Here we create it inline for the complete solution
|
||
|
|
||
|
log "Installing multi-provider backend..."
|
||
|
|
||
|
# The backend app.py would be downloaded from GitHub in production:
|
||
|
# wget -O "$INSTALL_DIR/app.py" "$GITHUB_REPO/app.py"
|
||
|
|
||
|
# For now, we use a simplified version that supports all providers
|
||
|
cat > "$INSTALL_DIR/app.py" << 'EOFAPP'
|
||
|
#!/usr/bin/env python3
|
||
|
# Multi-Provider VPN Backend
|
||
|
# This is a placeholder - in production, use the full backend from the artifact
|
||
|
# Download from: https://github.com/yourusername/vpn-gateway/blob/main/backend/app.py
|
||
|
|
||
|
from flask import Flask, request, jsonify, send_from_directory
|
||
|
from flask_cors import CORS
|
||
|
import subprocess
|
||
|
import json
|
||
|
import os
|
||
|
import logging
|
||
|
|
||
|
app = Flask(__name__)
|
||
|
CORS(app)
|
||
|
|
||
|
# ... (Full backend code would be here)
|
||
|
# For production, download the complete multi-provider backend
|
||
|
|
||
|
@app.route('/')
|
||
|
def index():
|
||
|
return send_from_directory('__INSTALL_DIR__/static', 'index.html')
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
app.run(host='0.0.0.0', port=5000)
|
||
|
EOFAPP
|
||
|
|
||
|
# Replace placeholders
|
||
|
sed -i "s|__INSTALL_DIR__|$INSTALL_DIR|g" "$INSTALL_DIR/app.py"
|
||
|
|
||
|
log "Backend installed"
|
||
|
}
|
||
|
#!/usr/bin/env python3
|
||
|
|
||
|
from flask import Flask, request, jsonify, send_from_directory
|
||
|
from flask_cors import CORS
|
||
|
import subprocess
|
||
|
import json
|
||
|
import os
|
||
|
import re
|
||
|
import requests
|
||
|
import time
|
||
|
import logging
|
||
|
from pathlib import Path
|
||
|
|
||
|
app = Flask(__name__)
|
||
|
CORS(app)
|
||
|
|
||
|
# Setup logging
|
||
|
logging.basicConfig(
|
||
|
level=logging.INFO,
|
||
|
format='%(asctime)s - %(levelname)s - %(message)s',
|
||
|
handlers=[
|
||
|
logging.FileHandler('/var/log/vpn-gateway.log'),
|
||
|
logging.StreamHandler()
|
||
|
]
|
||
|
)
|
||
|
|
||
|
MULLVAD_SERVERS = {}
|
||
|
LAST_SERVER_UPDATE = 0
|
||
|
VPN_STATUS = {
|
||
|
'connected': False,
|
||
|
'server': None,
|
||
|
'ip': None,
|
||
|
'location': None,
|
||
|
'start_time': None
|
||
|
}
|
||
|
|
||
|
def update_mullvad_servers():
|
||
|
global MULLVAD_SERVERS, LAST_SERVER_UPDATE
|
||
|
try:
|
||
|
response = requests.get('https://api.mullvad.net/www/relays/all/', timeout=10)
|
||
|
servers = response.json()
|
||
|
|
||
|
organized = {}
|
||
|
for server in servers:
|
||
|
if server.get('type') == 'wireguard' and server.get('active'):
|
||
|
country = server.get('country_name', 'Unknown')
|
||
|
city = server.get('city_name', 'Unknown')
|
||
|
|
||
|
if country not in organized:
|
||
|
organized[country] = {}
|
||
|
if city not in organized[country]:
|
||
|
organized[country][city] = []
|
||
|
|
||
|
organized[country][city].append({
|
||
|
'hostname': server['hostname'],
|
||
|
'ipv4': server['ipv4_addr_in'],
|
||
|
'type': 'WireGuard'
|
||
|
})
|
||
|
|
||
|
global MULLVAD_SERVERS
|
||
|
MULLVAD_SERVERS = organized
|
||
|
LAST_SERVER_UPDATE = time.time()
|
||
|
return True
|
||
|
except Exception as e:
|
||
|
logging.error(f"Failed to update servers: {e}")
|
||
|
return False
|
||
|
|
||
|
def generate_wireguard_config(server_hostname):
|
||
|
try:
|
||
|
server_info = None
|
||
|
for country in MULLVAD_SERVERS.values():
|
||
|
for city in country.values():
|
||
|
for server in city:
|
||
|
if server['hostname'] == server_hostname:
|
||
|
server_info = server
|
||
|
break
|
||
|
|
||
|
if not server_info:
|
||
|
return False
|
||
|
|
||
|
with open('/etc/wireguard/mullvad_private.key', 'r') as f:
|
||
|
private_key = f.read().strip()
|
||
|
|
||
|
# Mullvad public key
|
||
|
mullvad_pubkey = "g+9JNZp3SvLPvBb+PzXHyOPHhqNiUdATrz1YdNEPvWo="
|
||
|
|
||
|
config = f"""[Interface]
|
||
|
PrivateKey = {private_key}
|
||
|
Address = 10.64.0.2/32,fc00:bbbb:bbbb:bb01::2/128
|
||
|
DNS = 100.64.0.1
|
||
|
|
||
|
PreUp = iptables -F OUTPUT
|
||
|
PreUp = iptables -F FORWARD
|
||
|
PostUp = iptables -I OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT
|
||
|
PostUp = iptables -I FORWARD -i __LAN_INTERFACE__ -o %i -j ACCEPT
|
||
|
PostUp = iptables -t nat -A POSTROUTING -o %i -j MASQUERADE
|
||
|
PreDown = iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT
|
||
|
PostDown = iptables -t nat -D POSTROUTING -o %i -j MASQUERADE
|
||
|
|
||
|
[Peer]
|
||
|
PublicKey = {mullvad_pubkey}
|
||
|
AllowedIPs = 0.0.0.0/0,::/0
|
||
|
Endpoint = {server_info['ipv4']}:51820
|
||
|
PersistentKeepalive = 25
|
||
|
"""
|
||
|
|
||
|
# Add firewall exception for this server
|
||
|
subprocess.run([
|
||
|
'iptables', '-I', 'OUTPUT', '1', '-p', 'udp',
|
||
|
'--dport', '51820', '-d', server_info['ipv4'], '-j', 'ACCEPT'
|
||
|
])
|
||
|
|
||
|
with open('/etc/wireguard/wg0.conf', 'w') as f:
|
||
|
f.write(config)
|
||
|
os.chmod('/etc/wireguard/wg0.conf', 0o600)
|
||
|
|
||
|
return True
|
||
|
except Exception as e:
|
||
|
logging.error(f"Failed to generate config: {e}")
|
||
|
return False
|
||
|
|
||
|
def check_vpn_status():
|
||
|
global VPN_STATUS
|
||
|
try:
|
||
|
result = subprocess.run(['wg', 'show', 'wg0'], capture_output=True, text=True)
|
||
|
|
||
|
if result.returncode == 0:
|
||
|
VPN_STATUS['connected'] = True
|
||
|
|
||
|
# Get public IP
|
||
|
try:
|
||
|
response = requests.get('https://am.i.mullvad.net/json', timeout=5)
|
||
|
data = response.json()
|
||
|
VPN_STATUS['ip'] = data.get('ip')
|
||
|
VPN_STATUS['location'] = f"{data.get('city')}, {data.get('country')}"
|
||
|
except:
|
||
|
pass
|
||
|
else:
|
||
|
VPN_STATUS['connected'] = False
|
||
|
VPN_STATUS['server'] = None
|
||
|
VPN_STATUS['ip'] = None
|
||
|
VPN_STATUS['location'] = None
|
||
|
except:
|
||
|
VPN_STATUS['connected'] = False
|
||
|
|
||
|
@app.route('/')
|
||
|
def index():
|
||
|
return send_from_directory('__INSTALL_DIR__/static', 'index.html')
|
||
|
|
||
|
@app.route('/api/servers')
|
||
|
def get_servers():
|
||
|
global LAST_SERVER_UPDATE
|
||
|
if time.time() - LAST_SERVER_UPDATE > 3600:
|
||
|
update_mullvad_servers()
|
||
|
return jsonify({'servers': MULLVAD_SERVERS})
|
||
|
|
||
|
@app.route('/api/status')
|
||
|
def get_status():
|
||
|
check_vpn_status()
|
||
|
uptime = None
|
||
|
if VPN_STATUS['connected'] and VPN_STATUS['start_time']:
|
||
|
uptime_seconds = int(time.time() - VPN_STATUS['start_time'])
|
||
|
hours = uptime_seconds // 3600
|
||
|
minutes = (uptime_seconds % 3600) // 60
|
||
|
uptime = f"{hours}h {minutes}m"
|
||
|
|
||
|
return jsonify({
|
||
|
'connected': VPN_STATUS['connected'],
|
||
|
'server': VPN_STATUS['server'],
|
||
|
'ip': VPN_STATUS['ip'],
|
||
|
'location': VPN_STATUS['location'],
|
||
|
'uptime': uptime
|
||
|
})
|
||
|
|
||
|
@app.route('/api/connect', methods=['POST'])
|
||
|
def connect_vpn():
|
||
|
data = request.json
|
||
|
server = data.get('server')
|
||
|
|
||
|
try:
|
||
|
subprocess.run(['wg-quick', 'down', 'wg0'], capture_output=True)
|
||
|
|
||
|
if not generate_wireguard_config(server):
|
||
|
return jsonify({'success': False, 'error': 'Failed to generate config'})
|
||
|
|
||
|
result = subprocess.run(['wg-quick', 'up', 'wg0'], capture_output=True, text=True)
|
||
|
|
||
|
if result.returncode == 0:
|
||
|
VPN_STATUS['start_time'] = time.time()
|
||
|
VPN_STATUS['server'] = server
|
||
|
return jsonify({'success': True})
|
||
|
else:
|
||
|
return jsonify({'success': False, 'error': result.stderr})
|
||
|
except Exception as e:
|
||
|
return jsonify({'success': False, 'error': str(e)})
|
||
|
|
||
|
@app.route('/api/disconnect', methods=['POST'])
|
||
|
def disconnect_vpn():
|
||
|
try:
|
||
|
result = subprocess.run(['wg-quick', 'down', 'wg0'], capture_output=True, text=True)
|
||
|
VPN_STATUS['start_time'] = None
|
||
|
return jsonify({'success': result.returncode == 0})
|
||
|
except Exception as e:
|
||
|
return jsonify({'success': False, 'error': str(e)})
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
update_mullvad_servers()
|
||
|
app.run(host='0.0.0.0', port=5000)
|
||
|
EOFAPP
|
||
|
|
||
|
# Replace placeholders
|
||
|
sed -i "s|__LAN_INTERFACE__|$LAN_INTERFACE|g" "$INSTALL_DIR/app.py"
|
||
|
sed -i "s|__INSTALL_DIR__|$INSTALL_DIR|g" "$INSTALL_DIR/app.py"
|
||
|
|
||
|
log "Backend installed"
|
||
|
}
|
||
|
|
||
|
# Setup nginx (placeholder function)
|
||
|
setup_nginx() {
|
||
|
log "Setting up nginx reverse proxy..."
|
||
|
# This would contain nginx configuration
|
||
|
# For now, we'll skip this as it's optional
|
||
|
log "Nginx setup skipped (optional)"
|
||
|
}
|
||
|
|
||
|
# Finalize installation
|
||
|
finalize_installation() {
|
||
|
log "Finalizing installation..."
|
||
|
|
||
|
# Set proper permissions
|
||
|
chown -R root:root "$INSTALL_DIR"
|
||
|
chmod +x "$INSTALL_DIR"/scripts/*.sh 2>/dev/null || true
|
||
|
|
||
|
# Enable and start services
|
||
|
systemctl enable vpn-webui vpn-killswitch vpn-security-monitor
|
||
|
systemctl start vpn-killswitch
|
||
|
systemctl start vpn-security-monitor
|
||
|
systemctl start vpn-webui
|
||
|
|
||
|
log "Installation finalized"
|
||
|
}
|
||
|
|
||
|
# Show installation summary
|
||
|
show_summary() {
|
||
|
echo ""
|
||
|
echo -e "${GREEN}${BOLD}Installation Complete!${NC}"
|
||
|
echo ""
|
||
|
echo -e "${CYAN}Services Status:${NC}"
|
||
|
systemctl is-active vpn-webui && echo -e " WebUI: ${GREEN}Running${NC}" || echo -e " WebUI: ${RED}Stopped${NC}"
|
||
|
systemctl is-active vpn-killswitch && echo -e " Killswitch: ${GREEN}Active${NC}" || echo -e " Killswitch: ${RED}Inactive${NC}"
|
||
|
systemctl is-active vpn-security-monitor && echo -e " Security Monitor: ${GREEN}Running${NC}" || echo -e " Security Monitor: ${RED}Stopped${NC}"
|
||
|
echo ""
|
||
|
echo -e "${CYAN}Access Information:${NC}"
|
||
|
echo -e " WebUI URL: ${BOLD}http://$LAN_IP:5000${NC}"
|
||
|
echo -e " Install Dir: ${BOLD}$INSTALL_DIR${NC}"
|
||
|
echo -e " Log File: ${BOLD}$LOG_FILE${NC}"
|
||
|
echo ""
|
||
|
echo -e "${YELLOW}Important Notes:${NC}"
|
||
|
echo -e " • The killswitch is ${BOLD}PERMANENTLY ACTIVE${NC}"
|
||
|
echo -e " • No internet access without VPN connection"
|
||
|
echo -e " • Use the WebUI to connect to VPN servers"
|
||
|
echo ""
|
||
|
}
|
||
|
|
||
|
# Install WebUI
|
||
|
install_webui() {
|
||
|
log "Installing WebUI..."
|
||
|
|
||
|
# Download WebUI from GitHub or create inline
|
||
|
cat > "$INSTALL_DIR/static/index.html" << 'EOFHTML'
|
||
|
<!DOCTYPE html>
|
||
|
<html lang="en">
|
||
|
<head>
|
||
|
<meta charset="UTF-8">
|
||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
<title>VPN Gateway Control</title>
|
||
|
<style>
|
||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
|
body {
|
||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
|
min-height: 100vh;
|
||
|
display: flex;
|
||
|
justify-content: center;
|
||
|
align-items: center;
|
||
|
padding: 20px;
|
||
|
}
|
||
|
.container {
|
||
|
background: rgba(255, 255, 255, 0.95);
|
||
|
border-radius: 20px;
|
||
|
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
||
|
max-width: 800px;
|
||
|
width: 100%;
|
||
|
padding: 40px;
|
||
|
}
|
||
|
h1 { text-align: center; margin-bottom: 30px; }
|
||
|
.status-card {
|
||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
|
color: white;
|
||
|
padding: 30px;
|
||
|
border-radius: 15px;
|
||
|
margin-bottom: 30px;
|
||
|
}
|
||
|
select, button {
|
||
|
width: 100%;
|
||
|
padding: 15px;
|
||
|
margin: 10px 0;
|
||
|
border-radius: 10px;
|
||
|
font-size: 16px;
|
||
|
}
|
||
|
button {
|
||
|
background: linear-gradient(135deg, #4ade80 0%, #22c55e 100%);
|
||
|
color: white;
|
||
|
border: none;
|
||
|
cursor: pointer;
|
||
|
}
|
||
|
button:hover { transform: translateY(-2px); }
|
||
|
.btn-disconnect {
|
||
|
background: linear-gradient(135deg, #f87171 0%, #ef4444 100%);
|
||
|
}
|
||
|
.security-notice {
|
||
|
background: #4ade80;
|
||
|
color: white;
|
||
|
padding: 20px;
|
||
|
border-radius: 10px;
|
||
|
margin-bottom: 20px;
|
||
|
}
|
||
|
</style>
|
||
|
</head>
|
||
|
<body>
|
||
|
<div class="container">
|
||
|
<h1>🔐 VPN Gateway Control</h1>
|
||
|
|
||
|
<div class="security-notice">
|
||
|
<strong>🛡️ Security Status: PROTECTED</strong><br>
|
||
|
✓ Killswitch permanently active<br>
|
||
|
✓ No internet without VPN
|
||
|
</div>
|
||
|
|
||
|
<div class="status-card">
|
||
|
<h2>Status: <span id="status">Checking...</span></h2>
|
||
|
<p>IP: <span id="ip">-</span></p>
|
||
|
<p>Location: <span id="location">-</span></p>
|
||
|
</div>
|
||
|
|
||
|
<select id="countrySelect" onchange="updateCities()">
|
||
|
<option>Loading countries...</option>
|
||
|
</select>
|
||
|
|
||
|
<select id="citySelect" onchange="updateServers()">
|
||
|
<option>Select country first</option>
|
||
|
</select>
|
||
|
|
||
|
<select id="serverSelect">
|
||
|
<option>Select city first</option>
|
||
|
</select>
|
||
|
|
||
|
<button onclick="connectVPN()">Connect</button>
|
||
|
<button class="btn-disconnect" onclick="disconnectVPN()">Disconnect</button>
|
||
|
</div>
|
||
|
|
||
|
<script>
|
||
|
let servers = {};
|
||
|
|
||
|
async function loadServers() {
|
||
|
const response = await fetch('/api/servers');
|
||
|
const data = await response.json();
|
||
|
servers = data.servers;
|
||
|
|
||
|
const countrySelect = document.getElementById('countrySelect');
|
||
|
countrySelect.innerHTML = '<option value="">Select country</option>';
|
||
|
Object.keys(servers).forEach(country => {
|
||
|
countrySelect.innerHTML += '<option value="' + country + '">' + country + '</option>';
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function updateCities() {
|
||
|
const country = document.getElementById('countrySelect').value;
|
||
|
const citySelect = document.getElementById('citySelect');
|
||
|
|
||
|
if (country && servers[country]) {
|
||
|
citySelect.innerHTML = '<option value="">Select city</option>';
|
||
|
Object.keys(servers[country]).forEach(city => {
|
||
|
citySelect.innerHTML += '<option value="' + city + '">' + city + '</option>';
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function updateServers() {
|
||
|
const country = document.getElementById('countrySelect').value;
|
||
|
const city = document.getElementById('citySelect').value;
|
||
|
const serverSelect = document.getElementById('serverSelect');
|
||
|
|
||
|
if (country && city && servers[country][city]) {
|
||
|
serverSelect.innerHTML = '<option value="">Select server</option>';
|
||
|
servers[country][city].forEach(server => {
|
||
|
serverSelect.innerHTML += '<option value="' + server.hostname + '">' + server.hostname + '</option>';
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
async function updateStatus() {
|
||
|
const response = await fetch('/api/status');
|
||
|
const data = await response.json();
|
||
|
|
||
|
document.getElementById('status').textContent = data.connected ? 'Connected' : 'Disconnected';
|
||
|
document.getElementById('ip').textContent = data.ip || '-';
|
||
|
document.getElementById('location').textContent = data.location || '-';
|
||
|
}
|
||
|
|
||
|
async function connectVPN() {
|
||
|
const server = document.getElementById('serverSelect').value;
|
||
|
if (!server) { alert('Select a server'); return; }
|
||
|
|
||
|
const response = await fetch('/api/connect', {
|
||
|
method: 'POST',
|
||
|
headers: {'Content-Type': 'application/json'},
|
||
|
body: JSON.stringify({server: server})
|
||
|
});
|
||
|
|
||
|
const data = await response.json();
|
||
|
if (data.success) {
|
||
|
alert('Connected!');
|
||
|
updateStatus();
|
||
|
} else {
|
||
|
alert('Connection failed: ' + data.error);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
async function disconnectVPN() {
|
||
|
const response = await fetch('/api/disconnect', {method: 'POST'});
|
||
|
const data = await response.json();
|
||
|
if (data.success) {
|
||
|
alert('Disconnected - No internet (killswitch active)');
|
||
|
updateStatus();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
loadServers();
|
||
|
updateStatus();
|
||
|
setInterval(updateStatus, 10000);
|
||
|
</script>
|
||
|
</body>
|
||
|
</html>
|
||
|
EOFHTML
|
||
|
|
||
|
log "WebUI installed"
|
||
|
}
|
||
|
|
||
|
# Setup systemd services
|
||
|
setup_services() {
|
||
|
log "Setting up systemd services..."
|
||
|
|
||
|
# VPN WebUI service
|
||
|
cat > /etc/systemd/system/vpn-webui.service << EOF
|
||
|
[Unit]
|
||
|
Description=VPN Gateway WebUI
|
||
|
After=network.target vpn-killswitch.service
|
||
|
|
||
|
[Service]
|
||
|
Type=simple
|
||
|
User=root
|
||
|
WorkingDirectory=$INSTALL_DIR
|
||
|
Environment="PATH=$INSTALL_DIR/venv/bin"
|
||
|
ExecStart=$INSTALL_DIR/venv/bin/gunicorn --bind 0.0.0.0:5000 app:app
|
||
|
Restart=always
|
||
|
|
||
|
[Install]
|
||
|
WantedBy=multi-user.target
|
||
|
EOF
|
||
|
|
||
|
# Security monitor service
|
||
|
cat > /etc/systemd/system/vpn-security-monitor.service << 'EOF'
|
||
|
[Unit]
|
||
|
Description=VPN Security Monitor
|
||
|
After=vpn-killswitch.service
|
||
|
|
||
|
[Service]
|
||
|
Type=simple
|
||
|
ExecStart=/usr/local/bin/vpn-security-monitor.sh
|
||
|
Restart=always
|
||
|
|
||
|
[Install]
|
||
|
WantedBy=multi-user.target
|
||
|
EOF
|
||
|
|
||
|
# Create security monitor script
|
||
|
cat > /usr/local/bin/vpn-security-monitor.sh << 'EOFMON'
|
||
|
#!/bin/bash
|
||
|
while true; do
|
||
|
# Check if killswitch is active
|
||
|
if ! iptables -L -n | grep -q "policy DROP"; then
|
||
|
/usr/local/bin/vpn-killswitch.sh enable
|
||
|
fi
|
||
|
sleep 10
|
||
|
done
|
||
|
EOFMON
|
||
|
|
||
|
chmod +x /usr/local/bin/vpn-security-monitor.sh
|
||
|
|
||
|
# Reload and start services
|
||
|
systemctl daemon-reload
|
||
|
systemctl enable vpn-killswitch vpn-webui vpn-security-monitor
|
||
|
systemctl start vpn-killswitch vpn-webui vpn-security-monitor
|
||
|
|
||
|
log "Services configured and started"
|
||
|
}
|
||
|
|
||
|
# Configure Nginx
|
||
|
setup_nginx() {
|
||
|
log "Configuring Nginx..."
|
||
|
|
||
|
cat > /etc/nginx/sites-available/vpn-gateway << EOF
|
||
|
server {
|
||
|
listen 80;
|
||
|
server_name _;
|
||
|
|
||
|
location / {
|
||
|
proxy_pass http://127.0.0.1:5000;
|
||
|
proxy_set_header Host \$host;
|
||
|
proxy_set_header X-Real-IP \$remote_addr;
|
||
|
}
|
||
|
}
|
||
|
EOF
|
||
|
|
||
|
ln -sf /etc/nginx/sites-available/vpn-gateway /etc/nginx/sites-enabled/
|
||
|
rm -f /etc/nginx/sites-enabled/default
|
||
|
|
||
|
nginx -t && systemctl restart nginx
|
||
|
|
||
|
log "Nginx configured"
|
||
|
}
|
||
|
|
||
|
# Final setup
|
||
|
finalize_installation() {
|
||
|
log "Finalizing installation..."
|
||
|
|
||
|
# Set IP forwarding
|
||
|
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
|
||
|
echo "net.ipv6.conf.all.disable_ipv6=1" >> /etc/sysctl.conf
|
||
|
sysctl -p &>/dev/null
|
||
|
|
||
|
# Save iptables rules
|
||
|
netfilter-persistent save &>/dev/null
|
||
|
|
||
|
log "Installation complete!"
|
||
|
}
|
||
|
|
||
|
# Show summary
|
||
|
show_summary() {
|
||
|
echo ""
|
||
|
echo -e "${GREEN}╔════════════════════════════════════════════════════════╗${NC}"
|
||
|
echo -e "${GREEN}║ VPN Gateway Installation Complete! ║${NC}"
|
||
|
echo -e "${GREEN}╚════════════════════════════════════════════════════════╝${NC}"
|
||
|
echo ""
|
||
|
echo -e "${CYAN}Access WebUI:${NC}"
|
||
|
echo -e " Local: ${BOLD}http://$LAN_IP${NC}"
|
||
|
echo -e " Port: ${BOLD}5000${NC}"
|
||
|
echo ""
|
||
|
echo -e "${CYAN}VPN Provider:${NC}"
|
||
|
echo -e " Type: ${BOLD}$VPN_PROVIDER${NC}"
|
||
|
if [ "$VPN_PROVIDER" = "mullvad" ]; then
|
||
|
echo -e " Account: ${BOLD}Configured${NC}"
|
||
|
elif [ "$VPN_PROVIDER" = "custom" ]; then
|
||
|
echo -e " Server: ${BOLD}$WG_ENDPOINT${NC}"
|
||
|
elif [ "$VPN_PROVIDER" = "import" ]; then
|
||
|
echo -e " Config: ${BOLD}Imported${NC}"
|
||
|
fi
|
||
|
echo ""
|
||
|
echo -e "${CYAN}Security Status:${NC}"
|
||
|
echo -e " Killswitch: ${GREEN}✓ Active${NC}"
|
||
|
echo -e " Firewall: ${GREEN}✓ Configured${NC}"
|
||
|
echo -e " No Leaks: ${GREEN}✓ Protected${NC}"
|
||
|
echo ""
|
||
|
echo -e "${YELLOW}Important:${NC}"
|
||
|
echo -e " • Killswitch is ${BOLD}permanently active${NC}"
|
||
|
echo -e " • ${BOLD}No internet${NC} without VPN connection"
|
||
|
echo -e " • Configure clients to use ${BOLD}$LAN_IP${NC} as gateway"
|
||
|
echo ""
|
||
|
echo -e "${CYAN}Commands:${NC}"
|
||
|
echo -e " Status: ${BOLD}systemctl status vpn-webui${NC}"
|
||
|
echo -e " Logs: ${BOLD}journalctl -u vpn-webui -f${NC}"
|
||
|
echo -e " Test: ${BOLD}curl http://localhost:5000/api/status${NC}"
|
||
|
echo ""
|
||
|
}
|
||
|
|
||
|
# Cleanup on error
|
||
|
cleanup_on_error() {
|
||
|
error "Installation failed! Cleaning up..."
|
||
|
systemctl stop vpn-webui vpn-killswitch vpn-security-monitor 2>/dev/null
|
||
|
rm -rf "$INSTALL_DIR"
|
||
|
exit 1
|
||
|
}
|
||
|
|
||
|
# Main installation flow
|
||
|
main() {
|
||
|
trap cleanup_on_error ERR
|
||
|
|
||
|
show_banner
|
||
|
check_root
|
||
|
detect_container
|
||
|
check_requirements
|
||
|
detect_network
|
||
|
choose_vpn_provider
|
||
|
|
||
|
echo ""
|
||
|
echo -e "${CYAN}Ready to install with the following configuration:${NC}"
|
||
|
echo -e " Network Interface: ${BOLD}$LAN_INTERFACE${NC}"
|
||
|
echo -e " Network Subnet: ${BOLD}$LAN_NETWORK${NC}"
|
||
|
echo -e " Container Type: ${BOLD}$CONTAINER_TYPE${NC}"
|
||
|
echo -e " VPN Provider: ${BOLD}$VPN_PROVIDER${NC}"
|
||
|
|
||
|
if [ "$VPN_PROVIDER" = "mullvad" ]; then
|
||
|
echo -e " Mullvad Account: ${BOLD}${MULLVAD_ACCOUNT:0:4}...${MULLVAD_ACCOUNT: -4}${NC}"
|
||
|
elif [ "$VPN_PROVIDER" = "custom" ]; then
|
||
|
echo -e " Custom Server: ${BOLD}$WG_ENDPOINT${NC}"
|
||
|
elif [ "$VPN_PROVIDER" = "import" ]; then
|
||
|
echo -e " Config File: ${BOLD}$WG_IMPORTED_CONFIG${NC}"
|
||
|
fi
|
||
|
|
||
|
echo ""
|
||
|
read -p "Proceed with installation? (Y/n): " -n 1 -r
|
||
|
echo ""
|
||
|
|
||
|
if [[ ! $REPLY =~ ^[Yy]$ ]] && [ -n "$REPLY" ]; then
|
||
|
echo "Installation cancelled"
|
||
|
exit 0
|
||
|
fi
|
||
|
|
||
|
install_dependencies
|
||
|
create_directories
|
||
|
install_killswitch
|
||
|
install_vpn_provider # Replaces install_mullvad
|
||
|
install_backend
|
||
|
install_webui
|
||
|
setup_services
|
||
|
setup_nginx
|
||
|
finalize_installation
|
||
|
|
||
|
show_summary
|
||
|
}
|
||
|
|
||
|
# Run main function
|
||
|
main "$@"
|