#!/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'
IP: -
Location: -