New branch

This commit is contained in:
nocci 2025-08-10 15:34:34 +02:00
commit 58d70409b5
31 changed files with 9093 additions and 0 deletions

22
.env.example Normal file
View file

@ -0,0 +1,22 @@
# VPN Gateway Configuration Example
# Copy to .env and adjust
# Network Configuration
LAN_INTERFACE=eth0
LAN_NETWORK=192.168.1.0/24
# Provider Configuration
VPN_PROVIDER=mullvad
MULLVAD_ACCOUNT=1234567890123456
# Custom Server (if using custom provider)
WG_ENDPOINT=your-server.com:51820
WG_SERVER_PUBKEY=your-server-public-key-here
WG_CLIENT_IP=10.0.0.2/32
# Security
ALERT_EMAIL=admin@example.com
CHECK_INTERVAL=10
# WebUI
WEBUI_PORT=5000

35
.github/workflows/test.yml vendored Normal file
View file

@ -0,0 +1,35 @@
name: Test Installation
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test-install:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Test installer syntax
run: |
bash -n install.sh
for script in scripts/*.sh; do
bash -n "$script"
done
- name: Test Python syntax
run: |
python3 -m py_compile backend/app.py
- name: Security scan
run: |
pip install bandit
bandit -r backend/ || true
- name: Shellcheck
run: |
sudo apt-get install -y shellcheck
shellcheck install.sh scripts/*.sh || true

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 VPN Gateway Project
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

42
Makefile Normal file
View file

@ -0,0 +1,42 @@
.PHONY: install uninstall update test health backup restore start stop status logs
install:
@echo "Installing VPN Gateway..."
@sudo bash install.sh
uninstall:
@echo "Uninstalling VPN Gateway..."
@sudo bash scripts/uninstall.sh
update:
@echo "Updating VPN Gateway..."
@sudo bash scripts/update.sh
test:
@echo "Testing installation..."
@bash -n install.sh
@for script in scripts/*.sh; do bash -n $$script; done
@python3 -m py_compile backend/app.py
@echo "All tests passed!"
health:
@sudo bash scripts/health-check.sh
backup:
@echo "Creating backup..."
@sudo tar czf vpn-gateway-backup-$$(date +%Y%m%d-%H%M%S).tar.gz \
/opt/vpn-gateway \
/etc/wireguard \
/etc/systemd/system/vpn-*.service
start:
@sudo systemctl start vpn-webui vpn-killswitch vpn-security-monitor
stop:
@sudo systemctl stop vpn-webui vpn-security-monitor
status:
@sudo systemctl status vpn-webui vpn-killswitch vpn-security-monitor
logs:
@sudo journalctl -u vpn-webui -u vpn-killswitch -u vpn-security-monitor -f

60
README.md Normal file
View file

@ -0,0 +1,60 @@
# 🔒 Mullvad VPN Gateway for LXC - Multi-Provider Edition
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Version](https://img.shields.io/badge/version-1.0.0-blue.svg)](https://github.com/yourusername/vpn-gateway)
[![Security](https://img.shields.io/badge/security-hardened-green.svg)](https://github.com/yourusername/vpn-gateway)
Secure VPN Gateway with **permanent killswitch** for LXC containers. Supports Mullvad, custom WireGuard servers, and imported configurations.
## ⚡ Quick Start
```bash
curl -sSL https://raw.githubusercontent.com/yourusername/vpn-gateway/main/install.sh | bash
```
## 🎯 Features
- ✅ **Multi-Provider Support** - Mullvad, Custom Servers, Import Configs
- ✅ **Permanent Killswitch** - No internet without VPN, ever
- 🌍 **Dynamic Server Selection** - Switch between countries/cities via WebUI
- 🛡️ **Zero-Leak Protection** - DNS leak protection, IPv6 blocking
- 🎨 **Modern WebUI** - Beautiful, responsive control panel
- 🔄 **Auto-Reconnect** - Automatic recovery on connection drops
- 📊 **Live Monitoring** - Real-time status and statistics
## 📋 Requirements
- LXC Container (Proxmox/LXD)
- Ubuntu 20.04+ or Debian 11+
- Root access
- Mullvad account OR own WireGuard server
## 🚀 Installation
```bash
# Clone repository
git clone https://github.com/yourusername/vpn-gateway.git
cd vpn-gateway
# Run installer
sudo ./install.sh
```
## 📖 Documentation
- [Quick Start Guide](docs/QUICKSTART.md)
- [Provider Configuration](docs/PROVIDERS.md)
- [Security Documentation](docs/SECURITY.md)
- [FAQ](docs/FAQ.md)
## 🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## 📄 License
MIT License - see [LICENSE](LICENSE) file
## ⚠️ Disclaimer
The permanent killswitch will block ALL internet traffic when VPN is not connected. This is by design and cannot be disabled.

648
backend/app.py Normal file
View file

@ -0,0 +1,648 @@
#!/usr/bin/env python3
"""
Multi-Provider VPN Gateway Backend
Supports: Mullvad, Custom WireGuard, Imported Configs
With permanent killswitch protection
"""
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 yaml
import base64
from datetime import datetime
import threading
import logging
from pathlib import Path
from typing import Dict, List, Optional
app = Flask(__name__)
CORS(app)
# Configuration
CONFIG_FILE = '/opt/vpn-gateway/config.json'
PROVIDERS_DIR = '/opt/vpn-gateway/providers'
WIREGUARD_DIR = '/etc/wireguard'
# Setup logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('/var/log/vpn-gateway.log'),
logging.StreamHandler()
]
)
class VPNProvider:
"""Base class for VPN providers"""
def __init__(self, name: str):
self.name = name
self.servers = {}
def get_servers(self) -> Dict:
raise NotImplementedError
def generate_config(self, server: str) -> str:
raise NotImplementedError
class MullvadProvider(VPNProvider):
"""Mullvad VPN provider"""
def __init__(self):
super().__init__("mullvad")
self.api_url = "https://api.mullvad.net/www/relays/all/"
self.public_key = "g+9JNZp3SvLPvBb+PzXHyOPHhqNiUdATrz1YdNEPvWo="
def get_servers(self) -> Dict:
try:
response = requests.get(self.api_url, 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'],
'ipv6': server.get('ipv6_addr_in'),
'type': 'WireGuard',
'provider': 'Mullvad'
})
self.servers = organized
return organized
except Exception as e:
logging.error(f"Failed to fetch Mullvad servers: {e}")
return {}
def generate_config(self, server_hostname: str) -> str:
server_info = self._find_server(server_hostname)
if not server_info:
raise ValueError(f"Server {server_hostname} not found")
private_key = self._get_or_generate_key()
return f"""# Mullvad WireGuard Configuration
# Server: {server_hostname}
# Provider: Mullvad
[Interface]
PrivateKey = {private_key}
Address = 10.64.0.2/32,fc00:bbbb:bbbb:bb01::2/128
DNS = 100.64.0.1
# PERMANENT KILLSWITCH - CANNOT BE DISABLED
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 eth0 -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 = {self.public_key}
AllowedIPs = 0.0.0.0/0,::/0
Endpoint = {server_info['ipv4']}:51820
PersistentKeepalive = 25
"""
def _find_server(self, hostname: str) -> Optional[Dict]:
for country in self.servers.values():
for city in country.values():
for server in city:
if server['hostname'] == hostname:
return server
return None
def _get_or_generate_key(self) -> str:
key_file = f"{WIREGUARD_DIR}/mullvad_private.key"
if os.path.exists(key_file):
with open(key_file, 'r') as f:
return f.read().strip()
else:
private_key = subprocess.check_output(['wg', 'genkey'], text=True).strip()
with open(key_file, 'w') as f:
f.write(private_key)
os.chmod(key_file, 0o600)
return private_key
class CustomWireGuardProvider(VPNProvider):
"""Custom WireGuard servers provider"""
def __init__(self):
super().__init__("custom")
self.config_file = f"{PROVIDERS_DIR}/custom_servers.json"
self.load_servers()
def load_servers(self):
"""Load custom servers from config file"""
if os.path.exists(self.config_file):
try:
with open(self.config_file, 'r') as f:
self.servers = json.load(f)
except Exception as e:
logging.error(f"Failed to load custom servers: {e}")
self.servers = {}
else:
self.servers = {}
def save_servers(self):
"""Save custom servers to config file"""
os.makedirs(PROVIDERS_DIR, exist_ok=True)
with open(self.config_file, 'w') as f:
json.dump(self.servers, f, indent=2)
def add_server(self, name: str, config: Dict) -> bool:
"""Add a custom WireGuard server"""
try:
location = config.get('location', 'Custom')
if location not in self.servers:
self.servers[location] = {}
if 'Custom' not in self.servers[location]:
self.servers[location]['Custom'] = []
self.servers[location]['Custom'].append({
'hostname': name,
'endpoint': config['endpoint'],
'public_key': config['public_key'],
'private_key': config.get('private_key'),
'address': config.get('address', '10.0.0.2/32'),
'dns': config.get('dns', '1.1.1.1,1.0.0.1'),
'allowed_ips': config.get('allowed_ips', '0.0.0.0/0,::/0'),
'keepalive': config.get('keepalive', 25),
'type': 'WireGuard',
'provider': 'Custom'
})
self.save_servers()
return True
except Exception as e:
logging.error(f"Failed to add custom server: {e}")
return False
def remove_server(self, name: str) -> bool:
"""Remove a custom server"""
for location in self.servers.values():
for city in location.values():
for i, server in enumerate(city):
if server['hostname'] == name:
city.pop(i)
self.save_servers()
return True
return False
def generate_config(self, server_name: str) -> str:
server_info = self._find_server(server_name)
if not server_info:
raise ValueError(f"Server {server_name} not found")
private_key = server_info.get('private_key')
if not private_key:
private_key = self._get_or_generate_key(server_name)
dns_servers = server_info.get('dns', '1.1.1.1,1.0.0.1')
return f"""# Custom WireGuard Configuration
# Server: {server_name}
# Provider: Custom
[Interface]
PrivateKey = {private_key}
Address = {server_info.get('address', '10.0.0.2/32')}
DNS = {dns_servers}
# PERMANENT KILLSWITCH - CANNOT BE DISABLED
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 eth0 -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 = {server_info['public_key']}
AllowedIPs = {server_info.get('allowed_ips', '0.0.0.0/0,::/0')}
Endpoint = {server_info['endpoint']}
PersistentKeepalive = {server_info.get('keepalive', 25)}
"""
def _find_server(self, name: str) -> Optional[Dict]:
for location in self.servers.values():
for city in location.values():
for server in city:
if server['hostname'] == name:
return server
return None
def _get_or_generate_key(self, name: str) -> str:
key_file = f"{WIREGUARD_DIR}/custom_{name}_private.key"
if os.path.exists(key_file):
with open(key_file, 'r') as f:
return f.read().strip()
else:
private_key = subprocess.check_output(['wg', 'genkey'], text=True).strip()
with open(key_file, 'w') as f:
f.write(private_key)
os.chmod(key_file, 0o600)
return private_key
class ImportedConfigProvider(VPNProvider):
"""Provider for imported WireGuard configs"""
def __init__(self):
super().__init__("imported")
self.configs_dir = f"{PROVIDERS_DIR}/imported"
os.makedirs(self.configs_dir, exist_ok=True)
self.load_configs()
def load_configs(self):
"""Load all imported configs"""
self.servers = {"Imported": {"Configs": []}}
for config_file in Path(self.configs_dir).glob("*.conf"):
name = config_file.stem
self.servers["Imported"]["Configs"].append({
'hostname': name,
'file': str(config_file),
'type': 'WireGuard',
'provider': 'Imported'
})
def import_config(self, name: str, config_content: str) -> bool:
"""Import a WireGuard config"""
try:
# Validate config
if '[Interface]' not in config_content or '[Peer]' not in config_content:
raise ValueError("Invalid WireGuard configuration")
# Add killswitch if not present
if 'PostUp' not in config_content:
config_content = self._add_killswitch(config_content)
# Save config
config_file = f"{self.configs_dir}/{name}.conf"
with open(config_file, 'w') as f:
f.write(config_content)
os.chmod(config_file, 0o600)
self.load_configs()
return True
except Exception as e:
logging.error(f"Failed to import config: {e}")
return False
def _add_killswitch(self, config: str) -> str:
"""Add killswitch rules to imported config"""
killswitch_rules = """
# PERMANENT KILLSWITCH - ADDED AUTOMATICALLY
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 eth0 -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
"""
# Insert after [Interface] section
lines = config.split('\n')
for i, line in enumerate(lines):
if line.strip() == '[Interface]':
# Find next section or end
for j in range(i+1, len(lines)):
if lines[j].strip().startswith('['):
lines.insert(j, killswitch_rules)
break
else:
lines.insert(len(lines), killswitch_rules)
break
return '\n'.join(lines)
def generate_config(self, name: str) -> str:
for server in self.servers["Imported"]["Configs"]:
if server['hostname'] == name:
with open(server['file'], 'r') as f:
return f.read()
raise ValueError(f"Config {name} not found")
# Global provider instances
PROVIDERS = {
'mullvad': MullvadProvider(),
'custom': CustomWireGuardProvider(),
'imported': ImportedConfigProvider()
}
# Current provider
CURRENT_PROVIDER = None
# VPN Status
VPN_STATUS = {
'connected': False,
'provider': None,
'server': None,
'ip': None,
'location': None,
'start_time': None
}
def load_config():
"""Load application configuration"""
global CURRENT_PROVIDER
if os.path.exists(CONFIG_FILE):
try:
with open(CONFIG_FILE, 'r') as f:
config = json.load(f)
provider_name = config.get('provider', 'mullvad')
CURRENT_PROVIDER = PROVIDERS.get(provider_name)
logging.info(f"Loaded provider: {provider_name}")
except Exception as e:
logging.error(f"Failed to load config: {e}")
CURRENT_PROVIDER = PROVIDERS['mullvad']
else:
CURRENT_PROVIDER = PROVIDERS['mullvad']
def save_config():
"""Save application configuration"""
try:
config = {
'provider': CURRENT_PROVIDER.name if CURRENT_PROVIDER else 'mullvad',
'timestamp': datetime.now().isoformat()
}
os.makedirs(os.path.dirname(CONFIG_FILE), exist_ok=True)
with open(CONFIG_FILE, 'w') as f:
json.dump(config, f, indent=2)
except Exception as e:
logging.error(f"Failed to save config: {e}")
def check_vpn_status():
"""Check current VPN status"""
global VPN_STATUS
try:
result = subprocess.run(['wg', 'show', 'wg0'], capture_output=True, text=True)
if result.returncode == 0 and 'interface:' in result.stdout.lower():
VPN_STATUS['connected'] = True
# Get public IP and location
try:
# Try Mullvad API first
response = requests.get('https://am.i.mullvad.net/json', timeout=5)
data = response.json()
VPN_STATUS['ip'] = data.get('ip')
if data.get('mullvad_exit_ip'):
VPN_STATUS['location'] = f"{data.get('city')}, {data.get('country')}"
else:
# Fallback to ipinfo
response = requests.get('https://ipinfo.io/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
VPN_STATUS['provider'] = None
except Exception as e:
logging.error(f"Status check error: {e}")
VPN_STATUS['connected'] = False
# Flask Routes
@app.route('/')
def index():
return send_from_directory('/opt/vpn-gateway/static', 'index.html')
@app.route('/api/providers')
def get_providers():
"""Get available providers"""
return jsonify({
'providers': list(PROVIDERS.keys()),
'current': CURRENT_PROVIDER.name if CURRENT_PROVIDER else None
})
@app.route('/api/provider/<provider_name>', methods=['POST'])
def set_provider(provider_name):
"""Switch provider"""
global CURRENT_PROVIDER
if provider_name not in PROVIDERS:
return jsonify({'success': False, 'error': 'Invalid provider'}), 400
# Disconnect if connected
if VPN_STATUS['connected']:
subprocess.run(['wg-quick', 'down', 'wg0'], capture_output=True)
CURRENT_PROVIDER = PROVIDERS[provider_name]
save_config()
return jsonify({'success': True, 'provider': provider_name})
@app.route('/api/servers')
def get_servers():
"""Get servers for current provider"""
if not CURRENT_PROVIDER:
return jsonify({'servers': {}})
servers = CURRENT_PROVIDER.get_servers()
return jsonify({
'servers': servers,
'provider': CURRENT_PROVIDER.name
})
@app.route('/api/custom/add', methods=['POST'])
def add_custom_server():
"""Add custom WireGuard server"""
if not isinstance(CURRENT_PROVIDER, CustomWireGuardProvider):
return jsonify({'success': False, 'error': 'Not in custom mode'}), 400
data = request.json
name = data.get('name')
config = {
'endpoint': data.get('endpoint'),
'public_key': data.get('public_key'),
'private_key': data.get('private_key'),
'address': data.get('address', '10.0.0.2/32'),
'dns': data.get('dns', '1.1.1.1,1.0.0.1'),
'allowed_ips': data.get('allowed_ips', '0.0.0.0/0,::/0'),
'location': data.get('location', 'Custom')
}
if CURRENT_PROVIDER.add_server(name, config):
return jsonify({'success': True})
else:
return jsonify({'success': False, 'error': 'Failed to add server'}), 500
@app.route('/api/custom/remove/<name>', methods=['DELETE'])
def remove_custom_server(name):
"""Remove custom server"""
if not isinstance(CURRENT_PROVIDER, CustomWireGuardProvider):
return jsonify({'success': False, 'error': 'Not in custom mode'}), 400
if CURRENT_PROVIDER.remove_server(name):
return jsonify({'success': True})
else:
return jsonify({'success': False, 'error': 'Server not found'}), 404
@app.route('/api/import', methods=['POST'])
def import_config():
"""Import WireGuard config"""
data = request.json
name = data.get('name')
config_content = data.get('config')
if not name or not config_content:
return jsonify({'success': False, 'error': 'Missing name or config'}), 400
provider = PROVIDERS['imported']
if provider.import_config(name, config_content):
return jsonify({'success': True})
else:
return jsonify({'success': False, 'error': 'Failed to import config'}), 500
@app.route('/api/status')
def get_status():
"""Get VPN 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'],
'provider': VPN_STATUS['provider'],
'server': VPN_STATUS['server'],
'ip': VPN_STATUS['ip'],
'location': VPN_STATUS['location'],
'uptime': uptime,
'killswitch_active': True # Always true
})
@app.route('/api/connect', methods=['POST'])
def connect_vpn():
"""Connect to VPN"""
data = request.json
server = data.get('server')
if not server or not CURRENT_PROVIDER:
return jsonify({'success': False, 'error': 'No server or provider selected'}), 400
try:
# Disconnect if connected
subprocess.run(['wg-quick', 'down', 'wg0'], capture_output=True)
time.sleep(1)
# Generate config
config = CURRENT_PROVIDER.generate_config(server)
# Write config
with open('/etc/wireguard/wg0.conf', 'w') as f:
f.write(config)
os.chmod('/etc/wireguard/wg0.conf', 0o600)
# Add firewall exception for endpoint
endpoint_match = re.search(r'Endpoint = ([\d.]+):', config)
if endpoint_match:
subprocess.run([
'iptables', '-I', 'OUTPUT', '1', '-p', 'udp',
'--dport', '51820', '-d', endpoint_match.group(1), '-j', 'ACCEPT'
])
# Connect
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
VPN_STATUS['provider'] = CURRENT_PROVIDER.name
logging.info(f"Connected to {server} via {CURRENT_PROVIDER.name}")
return jsonify({'success': True})
else:
logging.error(f"Connection failed: {result.stderr}")
return jsonify({'success': False, 'error': result.stderr}), 500
except Exception as e:
logging.error(f"Connect error: {e}")
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/disconnect', methods=['POST'])
def disconnect_vpn():
"""Disconnect VPN"""
try:
result = subprocess.run(['wg-quick', 'down', 'wg0'],
capture_output=True, text=True)
VPN_STATUS['start_time'] = None
VPN_STATUS['connected'] = False
VPN_STATUS['provider'] = None
return jsonify({
'success': result.returncode == 0,
'message': 'Disconnected - No internet (killswitch active)'
})
except Exception as e:
logging.error(f"Disconnect error: {e}")
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/keypair', methods=['GET'])
def generate_keypair():
"""Generate WireGuard keypair"""
try:
private_key = subprocess.check_output(['wg', 'genkey'], text=True).strip()
public_key = subprocess.check_output(['wg', 'pubkey'], input=private_key, text=True).strip()
return jsonify({
'private_key': private_key,
'public_key': public_key
})
except Exception as e:
return jsonify({'error': str(e)}), 500
if __name__ == '__main__':
# Load configuration
load_config()
# Create directories
os.makedirs('/opt/vpn-gateway/static', exist_ok=True)
os.makedirs(PROVIDERS_DIR, exist_ok=True)
# Start app
app.run(host='0.0.0.0', port=5000, debug=False)

5
backend/requirements.txt Normal file
View file

@ -0,0 +1,5 @@
flask==2.3.3
flask-cors==4.0.0
requests==2.31.0
gunicorn==21.2.0
pyyaml==6.0.1

View file

@ -0,0 +1,54 @@
# VPN Gateway IPTables Rules Template
# This is a template - actual rules are generated by killswitch.sh
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT DROP [0:0]
# Loopback
-A INPUT -i lo -j ACCEPT
-A OUTPUT -o lo -j ACCEPT
# Established connections
-A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
-A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
-A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# LAN (will be replaced with actual interface/network)
-A INPUT -i eth0 -s 192.168.1.0/24 -j ACCEPT
-A OUTPUT -o eth0 -d 192.168.1.0/24 -j ACCEPT
# DNS for root only (for initial VPN connection)
-A OUTPUT -p udp --dport 53 -m owner --uid-owner 0 -j ACCEPT
-A OUTPUT -p tcp --dport 53 -m owner --uid-owner 0 -j ACCEPT
# VPN Forward
-A FORWARD -i eth0 -s 192.168.1.0/24 -j ACCEPT
# Log dropped packets (optional)
# -A INPUT -j LOG --log-prefix "DROP-IN: " --log-level 4
# -A OUTPUT -j LOG --log-prefix "DROP-OUT: " --log-level 4
# -A FORWARD -j LOG --log-prefix "DROP-FWD: " --log-level 4
COMMIT
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
# NAT will be added dynamically when VPN connects
# -A POSTROUTING -o wg0 -j MASQUERADE
COMMIT
*mangle
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
COMMIT

30
configs/logrotate.conf Normal file
View file

@ -0,0 +1,30 @@
# VPN Gateway Log Rotation
# Place in: /etc/logrotate.d/vpn-gateway
/var/log/vpn-*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
create 640 root root
sharedscripts
postrotate
systemctl reload vpn-webui 2>/dev/null || true
endscript
}
/var/log/nginx/vpn-gateway*.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
create 640 www-data adm
sharedscripts
postrotate
systemctl reload nginx 2>/dev/null || true
endscript
}

135
configs/nginx.conf Normal file
View file

@ -0,0 +1,135 @@
# VPN Gateway Nginx Configuration
# Place in: /etc/nginx/sites-available/vpn-gateway
# Rate limiting zones
limit_req_zone $binary_remote_addr zone=vpn_general:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=vpn_api:10m rate=5r/s;
limit_conn_zone $binary_remote_addr zone=vpn_conn:10m;
# Upstream backend
upstream vpn_backend {
server 127.0.0.1:5000 fail_timeout=10s;
keepalive 32;
}
server {
listen 80;
listen [::]:80;
server_name _;
# Access and error logs
access_log /var/log/nginx/vpn-gateway.access.log;
error_log /var/log/nginx/vpn-gateway.error.log;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src 'self' data:; font-src 'self' data:;" always;
# Connection limits
limit_conn vpn_conn 10;
# Client body size (for config uploads)
client_max_body_size 1M;
client_body_buffer_size 128k;
# Timeouts
client_body_timeout 10s;
client_header_timeout 10s;
send_timeout 10s;
# Root location - WebUI
location / {
limit_req zone=vpn_general burst=20 nodelay;
proxy_pass http://vpn_backend;
proxy_http_version 1.1;
# Headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket support
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeouts for proxy
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Buffering
proxy_buffering off;
proxy_request_buffering off;
}
# API endpoints - stricter rate limiting
location /api/ {
limit_req zone=vpn_api burst=10 nodelay;
limit_conn vpn_conn 5;
proxy_pass http://vpn_backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# API specific timeouts
proxy_connect_timeout 30s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
}
# Static files (if any)
location /static/ {
alias /opt/vpn-gateway/static/;
expires 1h;
add_header Cache-Control "public, immutable";
}
# Health check endpoint
location /health {
access_log off;
add_header Content-Type text/plain;
return 200 "healthy\n";
}
# Block sensitive paths
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
# Block access to backup files
location ~ ~$ {
deny all;
}
}
# HTTPS configuration (optional - uncomment if using SSL)
# server {
# listen 443 ssl http2;
# listen [::]:443 ssl http2;
# server_name vpn.yourdomain.com;
#
# ssl_certificate /etc/letsencrypt/live/vpn.yourdomain.com/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/vpn.yourdomain.com/privkey.pem;
#
# # SSL configuration
# ssl_protocols TLSv1.2 TLSv1.3;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# ssl_session_cache shared:SSL:10m;
# ssl_session_timeout 10m;
#
# # HSTS
# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
#
# # Rest of configuration same as above...
# }

View file

@ -0,0 +1,12 @@
[Unit]
Description=VPN Gateway Auto-Update Check
Documentation=https://github.com/yourusername/vpn-gateway
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
User=root
ExecStart=/usr/local/bin/vpn-update.sh --check-only
StandardOutput=journal
StandardError=journal

View file

@ -0,0 +1,13 @@
[Unit]
Description=VPN Gateway Auto-Update Timer
Documentation=https://github.com/yourusername/vpn-gateway
[Timer]
# Run daily at 3 AM
OnCalendar=daily
OnCalendar=*-*-* 03:00:00
RandomizedDelaySec=1h
Persistent=true
[Install]
WantedBy=timers.target

View file

@ -0,0 +1,38 @@
[Unit]
Description=VPN Killswitch - Permanent Network Protection
Documentation=https://github.com/yourusername/vpn-gateway
DefaultDependencies=no
Before=network-pre.target
Wants=network-pre.target
# This service MUST start before networking
After=local-fs.target
[Service]
Type=oneshot
RemainAfterExit=yes
# Execute killswitch enable
ExecStart=/usr/local/bin/vpn-killswitch.sh enable
# On reload, restart the killswitch
ExecReload=/usr/local/bin/vpn-killswitch.sh restart
# On stop, we still keep killswitch active for security
ExecStop=/bin/echo "Killswitch remains active for security"
# Logging
StandardOutput=journal
StandardError=journal
# Security
User=root
Group=root
# We want this to always succeed
SuccessExitStatus=0 1
[Install]
# Critical: Start at earliest possible stage
WantedBy=sysinit.target
RequiredBy=network.target

View file

@ -0,0 +1,40 @@
[Unit]
Description=VPN Security Monitor - Continuous Protection Monitoring
Documentation=https://github.com/yourusername/vpn-gateway
After=vpn-killswitch.service network-online.target
Requires=vpn-killswitch.service
Wants=network-online.target
[Service]
Type=simple
User=root
Group=root
# Execute monitoring script
ExecStart=/usr/local/bin/vpn-security-monitor.sh
# Restart policy
Restart=always
RestartSec=30
StartLimitInterval=300
StartLimitBurst=5
# Logging
StandardOutput=journal
StandardError=journal
# Resource limits
CPUQuota=10%
MemoryLimit=100M
# Security
NoNewPrivileges=true
PrivateTmp=true
# Kill settings
KillMode=process
KillSignal=SIGTERM
TimeoutStopSec=10
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,60 @@
[Unit]
Description=VPN Gateway WebUI Service
Documentation=https://github.com/yourusername/vpn-gateway
After=network-online.target vpn-killswitch.service
Wants=network-online.target
Requires=vpn-killswitch.service
[Service]
Type=simple
User=root
Group=root
WorkingDirectory=/opt/vpn-gateway
# Environment
Environment="PATH=/opt/vpn-gateway/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
Environment="PYTHONPATH=/opt/vpn-gateway"
Environment="FLASK_APP=app.py"
Environment="FLASK_ENV=production"
# Pre-start delay to ensure network is ready
ExecStartPre=/bin/bash -c 'sleep 5'
# Start command with gunicorn
ExecStart=/opt/vpn-gateway/venv/bin/gunicorn \
--bind 0.0.0.0:5000 \
--workers 2 \
--threads 4 \
--worker-class sync \
--worker-connections 1000 \
--max-requests 1000 \
--max-requests-jitter 50 \
--timeout 120 \
--keepalive 5 \
--access-logfile /var/log/vpn-gateway-access.log \
--error-logfile /var/log/vpn-gateway-error.log \
--log-level info \
--capture-output \
app:app
# Restart policy
Restart=always
RestartSec=10
StartLimitInterval=60
StartLimitBurst=3
# Security settings
NoNewPrivileges=true
PrivateTmp=true
# Resource limits
LimitNOFILE=65536
LimitNPROC=4096
# Kill settings
KillMode=mixed
KillSignal=SIGTERM
TimeoutStopSec=30
[Install]
WantedBy=multi-user.target

2514
create-configs-docs.sh Executable file

File diff suppressed because it is too large Load diff

320
docs/API.md Normal file
View file

@ -0,0 +1,320 @@
# API Reference
## Overview
The VPN Gateway provides a RESTful API for managing VPN connections and configuration.
Base URL: `http://<gateway-ip>:5000`
## Authentication
Currently, the API does not require authentication for local network access. For production use, consider implementing API keys or JWT tokens.
## Endpoints
### System Status
#### GET /api/status
Get current VPN and system status.
**Response:**
```json
{
"connected": true,
"provider": "mullvad",
"server": "se-sto-wg-001",
"ip": "185.65.134.123",
"location": "Stockholm, Sweden",
"uptime": "2h 34m",
"killswitch_active": true
}
```
### Provider Management
#### GET /api/providers
List available providers.
**Response:**
```json
{
"providers": ["mullvad", "custom", "imported"],
"current": "mullvad"
}
```
#### POST /api/provider/{provider}
Switch to a different provider.
**Parameters:**
- `provider`: Provider name (mullvad|custom|imported)
**Response:**
```json
{
"success": true,
"provider": "custom"
}
```
### Server Management
#### GET /api/servers
Get available servers for current provider.
**Response:**
```json
{
"servers": {
"Sweden": {
"Stockholm": [
{
"hostname": "se-sto-wg-001",
"ipv4": "185.65.134.123",
"type": "WireGuard",
"provider": "Mullvad"
}
]
}
},
"provider": "mullvad"
}
```
### Connection Management
#### POST /api/connect
Connect to VPN server.
**Request Body:**
```json
{
"server": "se-sto-wg-001"
}
```
**Response:**
```json
{
"success": true
}
```
#### POST /api/disconnect
Disconnect from VPN.
**Response:**
```json
{
"success": true,
"message": "Disconnected - No internet (killswitch active)"
}
```
### Custom Server Management
#### POST /api/custom/add
Add a custom WireGuard server.
**Request Body:**
```json
{
"name": "my-vps",
"endpoint": "1.2.3.4:51820",
"public_key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=",
"private_key": "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy=",
"address": "10.0.0.2/32",
"dns": "1.1.1.1,1.0.0.1",
"allowed_ips": "0.0.0.0/0,::/0",
"location": "Germany"
}
```
**Response:**
```json
{
"success": true
}
```
#### DELETE /api/custom/remove/{name}
Remove a custom server.
**Parameters:**
- `name`: Server name
**Response:**
```json
{
"success": true
}
```
### Import Configuration
#### POST /api/import
Import a WireGuard configuration.
**Request Body:**
```json
{
"name": "imported-config",
"config": "[Interface]\nPrivateKey = xxx\n..."
}
```
**Response:**
```json
{
"success": true
}
```
### Utility
#### GET /api/keypair
Generate a new WireGuard keypair.
**Response:**
```json
{
"private_key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=",
"public_key": "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy="
}
```
#### GET /health
Health check endpoint.
**Response:**
```
healthy
```
## Error Responses
All endpoints may return error responses:
```json
{
"success": false,
"error": "Error message here"
}
```
Common HTTP status codes:
- `200`: Success
- `400`: Bad request
- `404`: Not found
- `500`: Internal server error
## WebSocket Events (Future)
Planned WebSocket support for real-time updates:
```javascript
const ws = new WebSocket('ws://gateway-ip:5000/ws');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Event:', data.type, data.payload);
};
```
Events:
- `status_change`: VPN connection status changed
- `server_update`: Server list updated
- `security_alert`: Security issue detected
## Example Usage
### cURL
```bash
# Get status
curl http://gateway-ip:5000/api/status
# Connect to server
curl -X POST http://gateway-ip:5000/api/connect \
-H "Content-Type: application/json" \
-d '{"server":"se-sto-wg-001"}'
# Add custom server
curl -X POST http://gateway-ip:5000/api/custom/add \
-H "Content-Type: application/json" \
-d '{
"name": "my-server",
"endpoint": "1.2.3.4:51820",
"public_key": "xxx..."
}'
```
### Python
```python
import requests
# API base URL
base_url = "http://gateway-ip:5000"
# Get status
response = requests.get(f"{base_url}/api/status")
status = response.json()
print(f"Connected: {status['connected']}")
# Connect to server
response = requests.post(
f"{base_url}/api/connect",
json={"server": "se-sto-wg-001"}
)
if response.json()["success"]:
print("Connected successfully")
```
### JavaScript
```javascript
// Get status
fetch('http://gateway-ip:5000/api/status')
.then(response => response.json())
.then(data => console.log('Status:', data));
// Connect to server
fetch('http://gateway-ip:5000/api/connect', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
server: 'se-sto-wg-001'
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
console.log('Connected');
}
});
```
## Rate Limiting
API endpoints are rate-limited:
- General endpoints: 10 requests/second
- Connection endpoints: 5 requests/second
Headers returned:
- `X-RateLimit-Limit`: Request limit
- `X-RateLimit-Remaining`: Remaining requests
- `X-RateLimit-Reset`: Reset timestamp
## Future Enhancements
Planned API features:
- JWT authentication
- GraphQL endpoint
- Metrics endpoint (Prometheus format)
- Bulk operations
- Configuration backup/restore
- Traffic statistics
- Connection history

241
docs/FAQ.md Normal file
View file

@ -0,0 +1,241 @@
# Frequently Asked Questions
## General Questions
### Q: What is the VPN Gateway?
**A:** It's a secure VPN gateway solution that routes all network traffic through a VPN connection with a permanent killswitch to prevent leaks.
### Q: Which VPN providers are supported?
**A:**
- Mullvad VPN (commercial service)
- Custom WireGuard servers (your own VPS)
- Any imported WireGuard configuration
### Q: Can I use this with OpenVPN?
**A:** No, this gateway only supports WireGuard protocol for better performance and security.
### Q: Is this free to use?
**A:** The software is free and open source. You need to provide your own VPN service (Mullvad account or custom server).
## Installation
### Q: What are the system requirements?
**A:**
- LXC container or Linux system
- Ubuntu 20.04+ or Debian 11+
- 512MB RAM minimum
- 1GB disk space
- Root access
### Q: Can I install on a Raspberry Pi?
**A:** Yes, as long as it runs a supported OS and has WireGuard kernel module support.
### Q: Does it work in Docker?
**A:** It requires privileged mode and NET_ADMIN capability. LXC is recommended over Docker.
### Q: Can I install on a VPS?
**A:** Yes, but be aware that the killswitch will block all traffic except through VPN, which might lock you out via SSH.
## Usage
### Q: No internet after disconnecting VPN?
**A:** This is correct behavior! The killswitch blocks all internet traffic when VPN is not connected. This prevents leaks.
### Q: Can I disable the killswitch?
**A:** No, the killswitch cannot be disabled through normal means. This is a security feature.
### Q: How do I access the WebUI?
**A:** Navigate to `http://<container-ip>` in your browser. The WebUI is always accessible from the local network.
### Q: Can I use multiple VPN connections simultaneously?
**A:** No, only one VPN connection is active at a time. You can switch between servers/providers via the WebUI.
## Security
### Q: Is this really secure?
**A:** Yes, when properly configured:
- Permanent killswitch prevents leaks
- DNS leak protection enabled
- IPv6 completely disabled
- Continuous security monitoring
### Q: What about WebRTC leaks?
**A:** WebRTC leaks are prevented at the firewall level. No direct peer connections are possible.
### Q: Can applications bypass the VPN?
**A:** No, all traffic is forced through the VPN tunnel or blocked by the killswitch.
### Q: Is my traffic logged?
**A:** The gateway itself doesn't log traffic. Logging depends on your VPN provider's policy.
## Troubleshooting
### Q: WebUI is not accessible
**A:**
```bash
# Check if service is running
sudo systemctl status vpn-webui
# Restart the service
sudo systemctl restart vpn-webui
# Check if port is open
sudo netstat -tlnp | grep 5000
```
### Q: VPN won't connect
**A:**
1. Check your credentials/keys are correct
2. Verify the server is reachable
3. Check firewall allows outbound UDP 51820
4. Review logs: `sudo journalctl -u vpn-webui -n 50`
### Q: DNS not working
**A:**
```bash
# Check DNS configuration
cat /etc/resolv.conf
# Test DNS resolution
nslookup google.com
# Restart VPN connection
sudo wg-quick down wg0
sudo wg-quick up wg0
```
### Q: High CPU usage
**A:**
- Check security monitor: `sudo systemctl status vpn-security-monitor`
- Reduce monitoring frequency if needed
- Check for packet loops in firewall rules
## Configuration
### Q: How do I add a custom DNS server?
**A:** Edit the WireGuard configuration:
```bash
sudo nano /etc/wireguard/wg0.conf
# Change DNS = line to your preferred servers
```
### Q: Can I change the WebUI port?
**A:** Yes, edit the systemd service:
```bash
sudo nano /etc/systemd/system/vpn-webui.service
# Change --bind 0.0.0.0:5000 to your desired port
sudo systemctl daemon-reload
sudo systemctl restart vpn-webui
```
### Q: How do I backup my configuration?
**A:**
```bash
sudo tar czf vpn-backup.tar.gz \
/opt/vpn-gateway \
/etc/wireguard \
/etc/systemd/system/vpn-*.service
```
### Q: How do I enable auto-reconnect?
**A:** Auto-reconnect is handled by the security monitor. Ensure it's running:
```bash
sudo systemctl enable vpn-security-monitor
sudo systemctl start vpn-security-monitor
```
## Advanced
### Q: Can I use split tunneling?
**A:** Yes, for custom servers. Modify the AllowedIPs in your WireGuard config:
```ini
# Only specific subnets through VPN
AllowedIPs = 10.0.0.0/8, 172.16.0.0/12
```
### Q: How do I set up failover?
**A:** Add multiple peers in the WireGuard configuration:
```ini
[Peer]
# Primary
PublicKey = xxx...
Endpoint = primary.example.com:51820
[Peer]
# Backup
PublicKey = yyy...
Endpoint = backup.example.com:51820
```
### Q: Can I monitor traffic statistics?
**A:**
```bash
# WireGuard statistics
wg show wg0 transfer
# Network statistics
vnstat -i wg0
# Real-time monitoring
iftop -i wg0
```
### Q: How do I integrate with existing infrastructure?
**A:**
- Use as default gateway for network segments
- Configure via DHCP options
- Set up policy-based routing for specific clients
## Updates
### Q: How do I update the VPN Gateway?
**A:**
```bash
sudo /usr/local/bin/vpn-update.sh
```
### Q: Will updates break my configuration?
**A:** No, updates preserve your configuration. Backups are created automatically.
### Q: How do I check for updates?
**A:**
```bash
# Check current version
cat /opt/vpn-gateway/version
# Check for updates
curl -s https://raw.githubusercontent.com/yourusername/vpn-gateway/main/version
```
## Support
### Q: Where can I get help?
**A:**
- GitHub Issues: https://github.com/yourusername/vpn-gateway/issues
- Documentation: https://github.com/yourusername/vpn-gateway/wiki
- Community Forum: [Link to forum]
### Q: How do I report a bug?
**A:** Open an issue on GitHub with:
- System information
- Error messages
- Steps to reproduce
- Relevant logs
### Q: Can I contribute?
**A:** Yes! Contributions are welcome:
- Submit pull requests
- Report bugs
- Improve documentation
- Share your setup
## Legal
### Q: Is this legal to use?
**A:** Yes, but check your local laws regarding VPN usage. Some countries restrict VPN use.
### Q: Can I use this commercially?
**A:** Yes, under the MIT license terms. See LICENSE file for details.
### Q: What about warranty?
**A:** This software is provided "as is" without warranty. Use at your own risk.

339
docs/PROVIDERS.md Normal file
View file

@ -0,0 +1,339 @@
# VPN Provider Configuration Guide
## Overview
The VPN Gateway supports three types of providers:
1. **Mullvad VPN** - Commercial VPN service
2. **Custom WireGuard** - Your own VPN server
3. **Import Config** - Existing WireGuard configurations
## Mullvad VPN
### Setup
1. Get a Mullvad account at https://mullvad.net
2. Note your 16-digit account number
3. During installation, select "Mullvad" and enter your account number
### Features
- Automatic server list updates
- 40+ countries available
- Built-in DNS leak protection
- No logging policy
### Server Selection
Servers are organized by:
- **Country** (Sweden, Germany, USA, etc.)
- **City** (Stockholm, Berlin, New York, etc.)
- **Server** (se-sto-wg-001, de-ber-wg-002, etc.)
### Configuration
The system automatically:
- Fetches current server list
- Generates WireGuard keys
- Configures DNS (100.64.0.1)
- Sets up kill switch
## Custom WireGuard Server
### Prerequisites
You need:
- A VPS or dedicated server
- WireGuard installed on the server
- Server public key
- Open port (usually 51820)
### Server Setup (VPS Side)
#### 1. Install WireGuard
```bash
# Ubuntu/Debian
sudo apt update
sudo apt install wireguard
# CentOS/RHEL
sudo yum install wireguard-tools
```
#### 2. Generate Keys
```bash
cd /etc/wireguard
wg genkey | tee server_private.key | wg pubkey > server_public.key
```
#### 3. Configure Server
```bash
cat > /etc/wireguard/wg0.conf << EOF
[Interface]
PrivateKey = $(cat server_private.key)
Address = 10.0.0.1/24
ListenPort = 51820
# Enable IP forwarding
PostUp = sysctl -w net.ipv4.ip_forward=1
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
# Peer (VPN Gateway)
[Peer]
PublicKey = <GATEWAY_PUBLIC_KEY>
AllowedIPs = 10.0.0.2/32
EOF
```
#### 4. Start WireGuard
```bash
sudo systemctl enable wg-quick@wg0
sudo systemctl start wg-quick@wg0
```
### Gateway Setup (Client Side)
During installation, provide:
- **Endpoint**: Your server's IP:Port (e.g., 1.2.3.4:51820)
- **Server Public Key**: From server_public.key
- **Client IP**: Usually 10.0.0.2/32
- **DNS**: 1.1.1.1,1.0.0.1 or your preferred DNS
### Adding Multiple Servers
Via WebUI:
1. Go to "Custom Server" tab
2. Click "Add New Server"
3. Fill in server details
4. Save configuration
Via API:
```bash
curl -X POST http://gateway-ip/api/custom/add \
-H "Content-Type: application/json" \
-d '{
"name": "my-vps-us",
"endpoint": "us.example.com:51820",
"public_key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=",
"location": "United States"
}'
```
## Import Existing Configuration
### Supported Formats
- Standard WireGuard .conf files
- Configs from any WireGuard provider
- Custom peer configurations
### Import Methods
#### Via WebUI
1. Select "Import Config" tab
2. Choose file or paste configuration
3. Provide a name for the config
4. Click "Import"
#### Via CLI
```bash
# Copy config to gateway
scp myconfig.conf root@gateway-ip:/tmp/
# Import via API
curl -X POST http://gateway-ip/api/import \
-H "Content-Type: application/json" \
-d '{
"name": "imported-config",
"config": "'"$(cat /tmp/myconfig.conf)"'"
}'
```
### Automatic Modifications
The system automatically:
- Adds killswitch rules if missing
- Preserves original settings
- Validates configuration syntax
### Example Configuration
```ini
[Interface]
PrivateKey = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
Address = 10.8.0.2/32
DNS = 1.1.1.1
[Peer]
PublicKey = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
AllowedIPs = 0.0.0.0/0
Endpoint = vpn.example.com:51820
PersistentKeepalive = 25
```
## Provider Switching
### Via WebUI
1. Click on provider tabs
2. System automatically switches backend
3. Previous provider settings are preserved
### Via API
```bash
# Switch to Mullvad
curl -X POST http://gateway-ip/api/provider/mullvad
# Switch to Custom
curl -X POST http://gateway-ip/api/provider/custom
# Switch to Imported
curl -X POST http://gateway-ip/api/provider/imported
```
## Advanced Configuration
### Split Tunneling
For custom servers, modify AllowedIPs:
```ini
# Route only specific subnets through VPN
AllowedIPs = 10.0.0.0/8, 192.168.0.0/16
# Route everything except local network
AllowedIPs = 0.0.0.0/1, 128.0.0.0/1
```
### Multiple Peers (Failover)
```ini
[Peer]
# Primary server
PublicKey = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
AllowedIPs = 0.0.0.0/0
Endpoint = primary.example.com:51820
[Peer]
# Backup server
PublicKey = yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy=
AllowedIPs = 0.0.0.0/0
Endpoint = backup.example.com:51820
```
### Custom DNS
Modify DNS in the configuration:
```ini
# CloudFlare
DNS = 1.1.1.1, 1.0.0.1
# Quad9
DNS = 9.9.9.9, 149.112.112.112
# Custom/Local
DNS = 192.168.1.1
```
## Performance Optimization
### MTU Settings
For optimal performance:
```ini
[Interface]
MTU = 1420 # Default, works for most connections
# MTU = 1380 # For problematic connections
# MTU = 1280 # Maximum compatibility
```
### Persistent Keepalive
Adjust based on your needs:
```ini
# For stable connections
PersistentKeepalive = 25
# For NAT/firewall traversal
PersistentKeepalive = 10
# Disable for on-demand
# PersistentKeepalive = 0
```
## Troubleshooting Providers
### Mullvad Issues
```bash
# Check account status
curl https://api.mullvad.net/www/accounts/<account-number>/
# Test server connectivity
ping -c 1 <server-ip>
# Verify WireGuard keys
wg show wg0 public-key
```
### Custom Server Issues
```bash
# Test connectivity
nc -zv <server-ip> 51820
# Check server logs (on VPS)
sudo journalctl -u wg-quick@wg0 -f
# Verify keys match
echo "<public-key>" | base64 -d | wc -c # Should be 32
```
### Import Issues
```bash
# Validate config syntax
wg-quick strip /path/to/config.conf
# Test config manually
sudo wg-quick up /tmp/test.conf
sudo wg-quick down /tmp/test.conf
```
## Security Considerations
### Key Management
- Never share private keys
- Rotate keys periodically
- Use unique keys per device/gateway
### Server Hardening
For custom servers:
```bash
# Firewall rules
ufw allow 51820/udp
ufw allow from 10.0.0.0/24
# Disable password auth
sed -i 's/PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
# Enable automatic updates
apt install unattended-upgrades
```
### Monitoring
```bash
# Connection status
wg show
# Traffic statistics
wg show wg0 transfer
# Active connections
netstat -tunlp | grep 51820
```

211
docs/QUICKSTART.md Normal file
View file

@ -0,0 +1,211 @@
# Quick Start Guide
## Prerequisites
- LXC Container with Ubuntu/Debian
- Root access
- Internet connection for initial setup
## Installation
### 1. One-Line Install
```bash
curl -sSL https://raw.githubusercontent.com/yourusername/vpn-gateway/main/install.sh | bash
```
### 2. Manual Install
```bash
# Clone repository
git clone https://github.com/yourusername/vpn-gateway.git
cd vpn-gateway
# Run installer
sudo ./install.sh
```
## Initial Setup
### Step 1: Network Detection
The installer will auto-detect your network configuration:
- Network interface (e.g., eth0)
- LAN subnet (e.g., 192.168.1.0/24)
- Container IP address
Confirm or modify as needed.
### Step 2: Choose Provider
Select your VPN provider:
#### Option 1: Mullvad VPN
```
Select provider [1-3]: 1
Enter your Mullvad account number: 1234567890123456
```
#### Option 2: Custom WireGuard Server
```
Select provider [1-3]: 2
Server endpoint (IP:Port): 1.2.3.4:51820
Server public key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
```
#### Option 3: Import Configuration
```
Select provider [1-3]: 3
Path to WireGuard config: /path/to/config.conf
```
### Step 3: Complete Installation
The installer will:
1. Install dependencies
2. Configure killswitch
3. Set up WebUI
4. Start services
## Using the WebUI
### Access the Interface
Open your browser and navigate to:
```
http://<container-ip>
```
### Connect to VPN
1. **Select Location** (Mullvad only)
- Choose country
- Choose city
- Choose server
2. **Click Connect**
- Connection established in ~2-5 seconds
- Status indicator turns green
3. **Verify Connection**
- Check public IP displayed
- Verify location shown
### Disconnect from VPN
1. Click **Disconnect** button
2. **WARNING**: No internet access after disconnect (killswitch active)
## Client Configuration
### Configure Your Devices
Set on each client device:
#### Windows
1. Network Settings → IPv4 Properties
2. Default Gateway: `<container-ip>`
3. DNS Server: `<container-ip>` or `1.1.1.1`
#### Linux
```bash
# Temporary
sudo ip route del default
sudo ip route add default via <container-ip>
echo "nameserver <container-ip>" | sudo tee /etc/resolv.conf
# Permanent (NetworkManager)
nmcli connection modify <connection-name> ipv4.gateway <container-ip>
nmcli connection modify <connection-name> ipv4.dns <container-ip>
```
#### macOS
1. System Preferences → Network
2. Advanced → TCP/IP
3. Router: `<container-ip>`
4. DNS: `<container-ip>`
## Quick Commands
### Check Status
```bash
# Service status
sudo systemctl status vpn-webui
# Connection status
curl http://localhost:5000/api/status
# Health check
sudo /usr/local/bin/vpn-health-check.sh
```
### View Logs
```bash
# All logs
sudo journalctl -u vpn-webui -u vpn-killswitch -f
# WebUI logs only
sudo journalctl -u vpn-webui -f
```
### Restart Services
```bash
sudo systemctl restart vpn-webui
sudo systemctl restart vpn-security-monitor
```
## Important Notes
⚠️ **Killswitch Always Active**
- No internet without VPN connection
- This is intentional for security
- Local network still accessible
⚠️ **After Disconnect**
- Internet blocked until reconnection
- WebUI remains accessible
- Connect to VPN to restore internet
## Troubleshooting
### WebUI Not Accessible
```bash
# Check if service is running
sudo systemctl status vpn-webui
# Check if port is listening
sudo netstat -tlnp | grep 5000
# Restart service
sudo systemctl restart vpn-webui
```
### No Internet After Connect
```bash
# Check VPN status
sudo wg show
# Check killswitch
sudo iptables -L -n -v
# Check DNS
nslookup google.com
```
### Can't Connect to VPN
```bash
# Check logs
sudo journalctl -u vpn-webui -n 50
# Test killswitch
sudo /usr/local/bin/vpn-killswitch.sh verify
# Manual connection test
sudo wg-quick up wg0
```
## Next Steps
- Read [Provider Configuration](PROVIDERS.md) for advanced setup
- Review [Security Documentation](SECURITY.md) for security features
- See [FAQ](FAQ.md) for common questions

77
docs/README.md Normal file
View file

@ -0,0 +1,77 @@
# VPN Gateway Documentation
## Overview
This documentation covers the VPN Gateway multi-provider system with permanent killswitch protection.
## Contents
- [Quick Start Guide](QUICKSTART.md) - Get up and running in minutes
- [Provider Configuration](PROVIDERS.md) - Detailed provider setup
- [Security Documentation](SECURITY.md) - Security features and best practices
- [API Reference](API.md) - WebUI API endpoints
- [Troubleshooting](TROUBLESHOOTING.md) - Common issues and solutions
- [FAQ](FAQ.md) - Frequently asked questions
## Architecture
```
┌─────────────────────────────────────┐
│ Client Devices │
└────────────┬────────────────────────┘
┌─────────────────────────────────────┐
│ VPN Gateway Container │
│ ┌─────────────────────────────┐ │
│ │ WebUI (Port 80/5000) │ │
│ └──────────┬──────────────────┘ │
│ │ │
│ ┌──────────▼──────────────────┐ │
│ │ Flask Backend (Python) │ │
│ └──────────┬──────────────────┘ │
│ │ │
│ ┌──────────▼──────────────────┐ │
│ │ WireGuard Interface (wg0) │ │
│ └──────────┬──────────────────┘ │
│ │ │
│ ┌──────────▼──────────────────┐ │
│ │ Killswitch (iptables) │ │
│ └──────────┬──────────────────┘ │
└─────────────┼───────────────────────┘
┌──────────────────┐
│ VPN Provider │
│ • Mullvad │
│ • Custom Server │
│ • Imported Config│
└──────────────────┘
```
## Key Components
### 1. Killswitch
- Permanent firewall rules
- Blocks all non-VPN traffic
- Cannot be disabled via UI
### 2. WebUI
- Modern responsive interface
- Real-time status monitoring
- Multi-provider support
### 3. Backend
- Flask-based API
- Provider management
- Connection handling
### 4. Security Monitor
- Continuous monitoring
- Leak detection
- Auto-recovery
## Support
- GitHub Issues: https://github.com/yourusername/vpn-gateway/issues
- Documentation: https://github.com/yourusername/vpn-gateway/wiki

404
docs/SECURITY.md Normal file
View file

@ -0,0 +1,404 @@
# Security Documentation
## Overview
The VPN Gateway implements multiple layers of security to ensure zero-leak protection and maintain privacy.
## Core Security Features
### 1. Permanent Killswitch
The killswitch is the primary security mechanism that prevents any traffic leaks.
#### Implementation
- **Firewall Rules**: Default DROP policy for all chains
- **Boot Protection**: Activates before network initialization
- **Cannot be Disabled**: No UI or API endpoint to disable
- **Continuous Monitoring**: Verified every 10 seconds
#### Technical Details
```bash
# Default policies
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT DROP
# Only allowed traffic:
# - Loopback (system operations)
# - LAN subnet (WebUI access)
# - Established connections
# - VPN tunnel (when active)
```
### 2. DNS Leak Protection
#### Mechanisms
1. **Forced VPN DNS**: All DNS queries routed through VPN
2. **System DNS Override**: /etc/resolv.conf locked
3. **IPv6 Disabled**: Prevents IPv6 DNS leaks
4. **DNS Filtering**: Only root can make DNS queries for VPN connection
#### Configuration
```bash
# DNS through VPN 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
# Block all other DNS
iptables -A OUTPUT -p udp --dport 53 -j DROP
iptables -A OUTPUT -p tcp --dport 53 -j DROP
```
### 3. IPv6 Protection
Complete IPv6 blocking to prevent leaks:
```bash
# IPv6 firewall
ip6tables -P INPUT DROP
ip6tables -P FORWARD DROP
ip6tables -P OUTPUT DROP
# Kernel level
sysctl -w net.ipv6.conf.all.disable_ipv6=1
sysctl -w net.ipv6.conf.default.disable_ipv6=1
```
### 4. Security Monitor
Continuous monitoring daemon that:
- Verifies killswitch every 10 seconds
- Detects potential leaks
- Auto-recovers from failures
- Logs security events
## Threat Model
### Protected Against
✅ **IP Leaks**
- Killswitch blocks all non-VPN traffic
- No traffic possible without active tunnel
✅ **DNS Leaks**
- All DNS through VPN
- System DNS locked
- IPv6 DNS blocked
✅ **WebRTC Leaks**
- Blocked at firewall level
- No direct peer connections
✅ **IPv6 Leaks**
- IPv6 completely disabled
- Both firewall and kernel level
✅ **Connection Drops**
- Killswitch remains active
- No traffic during reconnection
- Auto-recovery available
✅ **Malicious Applications**
- Cannot bypass firewall rules
- All traffic subject to killswitch
### Not Protected Against
❌ **Compromised Container**
- If attacker gains root access
- Can modify firewall rules
❌ **Host System Compromise**
- Container isolation breach
- Hypervisor vulnerabilities
❌ **Traffic Analysis**
- VPN traffic patterns visible
- Timing correlation attacks
❌ **VPN Provider Compromise**
- Malicious VPN server
- Provider logging (choose carefully)
## Security Best Practices
### 1. Installation Security
```bash
# Verify installer integrity
sha256sum install.sh
# Compare with published hash
# Review script before execution
less install.sh
# Run with specific version
curl -sSL https://raw.githubusercontent.com/yourusername/vpn-gateway/v1.0.0/install.sh | bash
```
### 2. Access Control
#### WebUI Protection
```nginx
# Restrict WebUI access to LAN only
location / {
allow 192.168.1.0/24;
deny all;
# ... proxy settings
}
```
#### SSH Hardening
```bash
# Disable password authentication
PasswordAuthentication no
# Key-only access
PubkeyAuthentication yes
# Restrict to specific IPs
AllowUsers root@192.168.1.0/24
```
### 3. Key Management
#### WireGuard Keys
```bash
# Generate new keys periodically
wg genkey | tee privatekey | wg pubkey > publickey
# Secure storage
chmod 600 /etc/wireguard/*.key
# Never share private keys
# Unique keys per gateway
```
#### Rotation Schedule
- **Private Keys**: Every 3-6 months
- **Preshared Keys**: Every 1-3 months
- **API Keys**: Every 30 days
### 4. Monitoring
#### Security Logs
```bash
# Monitor security events
journalctl -u vpn-security-monitor -f
# Check for failures
grep "ALERT\|ERROR" /var/log/vpn-security-monitor.log
# Audit firewall drops
iptables -L -n -v | grep DROP
```
#### Leak Testing
```bash
# Regular leak tests
curl https://ipleak.net/json/
curl https://am.i.mullvad.net/json
# DNS leak test
nslookup example.com
dig example.com
```
### 5. Updates
#### Security Updates
```bash
# System updates (through VPN)
apt update && apt upgrade
# VPN Gateway updates
/usr/local/bin/vpn-update.sh
# Check for security advisories
```
#### Automatic Updates
```bash
# Enable unattended upgrades
apt install unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades
```
## Incident Response
### 1. Leak Detected
If a leak is detected:
1. **Immediate Action**
```bash
# Re-enable killswitch
/usr/local/bin/vpn-killswitch.sh enable
# Disconnect VPN
wg-quick down wg0
```
2. **Investigation**
```bash
# Check logs
journalctl -u vpn-security-monitor -n 100
# Verify firewall rules
iptables -L -n -v
```
3. **Recovery**
```bash
# Restart security services
systemctl restart vpn-killswitch
systemctl restart vpn-security-monitor
```
### 2. Suspicious Activity
Signs of compromise:
- Unexpected firewall rule changes
- Unknown processes with network access
- Unusual CPU/memory usage
- Modified system files
Response:
```bash
# Check processes
netstat -tulpn
ps aux | grep -v grep | grep wg
# Check file integrity
debsums -c
find /etc -type f -mtime -1
# Review auth logs
grep "Failed\|Invalid" /var/log/auth.log
```
### 3. Emergency Shutdown
If immediate isolation needed:
```bash
# Block ALL network traffic
iptables -P INPUT DROP
iptables -P OUTPUT DROP
iptables -P FORWARD DROP
iptables -F
# Stop services
systemctl stop vpn-webui
systemctl stop wg-quick@wg0
# Preserve evidence
tar czf /tmp/evidence-$(date +%s).tar.gz \
/var/log \
/etc/wireguard \
/opt/vpn-gateway/logs
```
## Security Hardening
### 1. Container Hardening
```bash
# Limit capabilities
lxc config set <container> security.nesting false
lxc config set <container> security.privileged false
# Resource limits
lxc config set <container> limits.memory 512MB
lxc config set <container> limits.cpu 1
```
### 2. Network Hardening
```bash
# Rate limiting
iptables -A INPUT -p tcp --dport 5000 \
-m conntrack --ctstate NEW \
-m limit --limit 10/min --limit-burst 5 \
-j ACCEPT
# SYN flood protection
echo 1 > /proc/sys/net/ipv4/tcp_syncookies
echo 2048 > /proc/sys/net/ipv4/tcp_max_syn_backlog
```
### 3. Application Hardening
```python
# Flask security headers
from flask import Flask
from flask_talisman import Talisman
app = Flask(__name__)
Talisman(app,
force_https=False, # Handle at reverse proxy
strict_transport_security=True,
content_security_policy={
'default-src': "'self'"
}
)
```
## Compliance
### GDPR Compliance
- No personal data logging
- User control over data
- Right to deletion
- Transparent processing
### Security Standards
- CIS Benchmarks compliance
- NIST framework alignment
- Zero-trust architecture
- Defense in depth
## Security Checklist
### Daily
- [ ] Check service status
- [ ] Review security logs
- [ ] Verify killswitch active
### Weekly
- [ ] Run leak tests
- [ ] Check for updates
- [ ] Review firewall rules
### Monthly
- [ ] Rotate keys
- [ ] Audit access logs
- [ ] Update documentation
### Quarterly
- [ ] Security assessment
- [ ] Penetration testing
- [ ] Disaster recovery test
## Contact
For security issues:
- **Email**: security@yourdomain.com
- **PGP Key**: [Public key]
- **Response Time**: < 24 hours for critical issues
Please report security vulnerabilities responsibly.

425
docs/TROUBLESHOOTING.md Normal file
View file

@ -0,0 +1,425 @@
# Troubleshooting Guide
## Common Issues and Solutions
### Installation Issues
#### Problem: Installation fails with dependency errors
```bash
E: Unable to locate package wireguard
```
**Solution:**
```bash
# Update package lists
sudo apt update
# Enable backports (Debian)
echo "deb http://deb.debian.org/debian $(lsb_release -cs)-backports main" | \
sudo tee /etc/apt/sources.list.d/backports.list
sudo apt update
# Install kernel headers
sudo apt install linux-headers-$(uname -r)
# Retry installation
sudo ./install.sh
```
#### Problem: WireGuard module not loading
```bash
modprobe: FATAL: Module wireguard not found
```
**Solution:**
```bash
# Install WireGuard kernel module
sudo apt install wireguard-dkms
# Load module manually
sudo modprobe wireguard
# Verify module loaded
lsmod | grep wireguard
```
### Connection Issues
#### Problem: VPN won't connect
**Symptoms:** Connection timeout, no handshake
**Solutions:**
1. **Check server reachability:**
```bash
# Ping server (if ICMP allowed)
ping -c 3 <server-ip>
# Check port connectivity
nc -zv <server-ip> 51820
# Trace route
traceroute <server-ip>
```
2. **Verify credentials:**
```bash
# Check keys format
wg show wg0 private-key
wg show wg0 public-key
# Verify key length (should be 44 characters)
echo "<key>" | wc -c
```
3. **Check firewall:**
```bash
# Ensure UDP 51820 outbound is allowed
sudo iptables -L OUTPUT -n -v | grep 51820
# Temporarily allow all outbound (testing only!)
sudo iptables -I OUTPUT 1 -j ACCEPT
```
4. **Review logs:**
```bash
# Check WebUI logs
sudo journalctl -u vpn-webui -n 100
# Check WireGuard logs
sudo dmesg | grep wireguard
# Enable debug logging
echo 'module wireguard +p' | sudo tee /sys/kernel/debug/dynamic_debug/control
```
#### Problem: Connection drops frequently
**Symptoms:** Intermittent connectivity, handshake failures
**Solutions:**
1. **Adjust keepalive:**
```bash
# Edit WireGuard config
sudo nano /etc/wireguard/wg0.conf
# Reduce keepalive interval
PersistentKeepalive = 10 # Instead of 25
```
2. **Check MTU:**
```bash
# Test different MTU values
sudo ip link set dev wg0 mtu 1380
# Find optimal MTU
ping -M do -s 1372 <vpn-server-ip>
# Increase/decrease -s value until it works
```
3. **Monitor connection:**
```bash
# Watch handshakes
watch -n 1 'wg show wg0 latest-handshakes'
# Check for packet loss
mtr <vpn-server-ip>
```
### Network Issues
#### Problem: No internet after connecting
**Symptoms:** VPN connected but can't browse
**Solutions:**
1. **Check routing:**
```bash
# Show routing table
ip route show
# Verify default route through VPN
ip route | grep default
# Add route manually if missing
sudo ip route add default dev wg0
```
2. **Check DNS:**
```bash
# Test DNS resolution
nslookup google.com
dig google.com
# Check DNS config
cat /etc/resolv.conf
# Force DNS update
echo "nameserver 1.1.1.1" | sudo tee /etc/resolv.conf
```
3. **Check NAT:**
```bash
# Verify NAT rules
sudo iptables -t nat -L POSTROUTING -n -v
# Add NAT manually if missing
sudo iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE
```
#### Problem: Local network not accessible
**Symptoms:** Can't reach LAN devices when VPN connected
**Solution:**
```bash
# Add LAN route exception
sudo ip route add 192.168.1.0/24 dev eth0
# Or modify WireGuard AllowedIPs
# Change from: 0.0.0.0/0
# To: 0.0.0.0/1, 128.0.0.0/1
```
### WebUI Issues
#### Problem: WebUI not loading
**Symptoms:** Connection refused, timeout
**Solutions:**
1. **Check service status:**
```bash
# Service status
sudo systemctl status vpn-webui
# Restart service
sudo systemctl restart vpn-webui
# Check if running
ps aux | grep gunicorn
```
2. **Check port binding:**
```bash
# Verify port is listening
sudo netstat -tlnp | grep 5000
sudo ss -tlnp | grep 5000
# Check for port conflicts
sudo lsof -i :5000
```
3. **Check Nginx (if used):**
```bash
# Test Nginx config
sudo nginx -t
# Restart Nginx
sudo systemctl restart nginx
# Check Nginx logs
sudo tail -f /var/log/nginx/error.log
```
#### Problem: Can't change settings
**Symptoms:** Changes don't save, errors on submit
**Solutions:**
1. **Check permissions:**
```bash
# Fix ownership
sudo chown -R root:root /opt/vpn-gateway
# Fix permissions
sudo chmod 755 /opt/vpn-gateway
sudo chmod 644 /opt/vpn-gateway/app.py
```
2. **Check disk space:**
```bash
# Check available space
df -h /opt/vpn-gateway
# Clean up if needed
sudo journalctl --vacuum-time=7d
sudo apt clean
```
### Security Issues
#### Problem: Killswitch not working
**Symptoms:** Internet accessible without VPN
**CRITICAL - Fix immediately:**
1. **Re-enable killswitch:**
```bash
# Force enable
sudo /usr/local/bin/vpn-killswitch.sh enable
# Verify rules
sudo iptables -L -n -v | grep DROP
```
2. **Check for rule conflicts:**
```bash
# List all rules
sudo iptables-save
# Remove conflicting rules
sudo iptables -F OUTPUT
sudo iptables -P OUTPUT DROP
```
3. **Restart security monitor:**
```bash
sudo systemctl restart vpn-security-monitor
sudo systemctl status vpn-security-monitor
```
#### Problem: DNS leaks detected
**Symptoms:** DNS queries not going through VPN
**Solutions:**
1. **Force VPN DNS:**
```bash
# Lock resolv.conf
sudo chattr +i /etc/resolv.conf
# Disable systemd-resolved
sudo systemctl stop systemd-resolved
sudo systemctl disable systemd-resolved
```
2. **Check firewall rules:**
```bash
# Block DNS except through VPN
sudo iptables -A OUTPUT -p udp --dport 53 ! -o wg0 -j DROP
sudo iptables -A OUTPUT -p tcp --dport 53 ! -o wg0 -j DROP
```
### Performance Issues
#### Problem: Slow speeds
**Symptoms:** Poor throughput, high latency
**Solutions:**
1. **Optimize MTU:**
```bash
# Test optimal MTU
for mtu in 1420 1400 1380 1360; do
sudo ip link set dev wg0 mtu $mtu
echo "Testing MTU $mtu"
iperf3 -c <server-ip> -t 5
done
```
2. **Check CPU usage:**
```bash
# Monitor CPU
top -n 1 | grep -E "wireguard|python"
# Check interrupts
watch -n 1 'cat /proc/interrupts | grep -E "eth|wg"'
```
3. **Tune kernel parameters:**
```bash
# Optimize network stack
cat >> /etc/sysctl.conf << EOF
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.ipv4.tcp_rmem = 4096 87380 134217728
net.ipv4.tcp_wmem = 4096 65536 134217728
net.core.netdev_max_backlog = 30000
net.ipv4.tcp_congestion_control = bbr
EOF
sudo sysctl -p
```
### Diagnostic Commands
#### Complete System Check
```bash
#!/bin/bash
echo "=== VPN Gateway Diagnostics ==="
echo ""
echo "1. Services Status:"
systemctl status vpn-webui --no-pager | head -n 10
systemctl status vpn-killswitch --no-pager | head -n 10
systemctl status vpn-security-monitor --no-pager | head -n 10
echo ""
echo "2. VPN Status:"
wg show
echo ""
echo "3. Firewall Rules:"
iptables -L -n | head -n 20
echo ""
echo "4. Network Configuration:"
ip addr show
ip route show
echo ""
echo "5. DNS Configuration:"
cat /etc/resolv.conf
echo ""
echo "6. Recent Logs:"
journalctl -u vpn-webui -n 20 --no-pager
echo ""
echo "7. Disk Usage:"
df -h /opt/vpn-gateway
echo ""
echo "8. Memory Usage:"
free -h
```
#### Export Debug Info
```bash
# Create debug archive
sudo tar czf vpn-debug-$(date +%s).tar.gz \
/var/log/vpn-*.log \
/var/log/syslog \
/etc/wireguard/ \
/opt/vpn-gateway/logs/ \
<(iptables-save) \
<(wg show) \
<(ip addr) \
<(ip route) \
<(systemctl status vpn-*)
```
## Getting Help
If problems persist:
1. **Check logs thoroughly:**
```bash
sudo journalctl -xe
sudo dmesg | tail -50
```
2. **Run health check:**
```bash
sudo /usr/local/bin/vpn-health-check.sh
```
3. **Create issue on GitHub** with:
- System info: `uname -a`
- Service status: `systemctl status vpn-*`
- Error messages
- Debug archive
4. **Emergency recovery:**
```bash
# Disable killswitch (TEMPORARY!)
sudo iptables -P INPUT ACCEPT
sudo iptables -P OUTPUT ACCEPT
sudo iptables -P FORWARD ACCEPT
sudo iptables -F
# Reinstall
curl -sSL https://raw.githubusercontent.com/yourusername/vpn-gateway/main/install.sh | bash
```

975
frontend/index.html Normal file
View file

@ -0,0 +1,975 @@
<!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 - Multi Provider</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.5em;
margin-bottom: 10px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
}
.provider-selector {
background: white;
border-radius: 15px;
padding: 30px;
margin-bottom: 30px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
}
.provider-tabs {
display: flex;
gap: 10px;
margin-bottom: 30px;
flex-wrap: wrap;
}
.provider-tab {
flex: 1;
min-width: 150px;
padding: 15px 25px;
border: 2px solid #e0e0e0;
border-radius: 10px;
background: white;
cursor: pointer;
transition: all 0.3s;
text-align: center;
font-weight: 600;
}
.provider-tab:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}
.provider-tab.active {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-color: transparent;
}
.main-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
}
@media (max-width: 768px) {
.main-grid {
grid-template-columns: 1fr;
}
}
.card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 15px;
padding: 30px;
box-shadow: 0 10px 40px rgba(0,0,0,0.1);
}
.status-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.status-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
}
.status-indicator {
display: flex;
align-items: center;
gap: 10px;
font-size: 1.3em;
font-weight: bold;
}
.status-dot {
width: 15px;
height: 15px;
border-radius: 50%;
animation: pulse 2s infinite;
}
.status-dot.connected {
background: #4ade80;
box-shadow: 0 0 10px #4ade80;
}
.status-dot.disconnected {
background: #f87171;
box-shadow: 0 0 10px #f87171;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.2); }
}
.info-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
}
.info-item {
background: rgba(255, 255, 255, 0.1);
padding: 15px;
border-radius: 10px;
backdrop-filter: blur(5px);
}
.info-label {
font-size: 0.9em;
opacity: 0.9;
margin-bottom: 5px;
}
.info-value {
font-size: 1.2em;
font-weight: bold;
}
.security-notice {
background: linear-gradient(135deg, #4ade80 0%, #22c55e 100%);
color: white;
padding: 20px;
border-radius: 10px;
margin-bottom: 20px;
}
select, input, textarea {
width: 100%;
padding: 15px;
border: 2px solid #e0e0e0;
border-radius: 10px;
font-size: 1em;
margin-bottom: 15px;
transition: all 0.3s;
}
select:focus, input:focus, textarea:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
button {
padding: 15px 30px;
border: none;
border-radius: 10px;
font-size: 1.1em;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
width: 100%;
margin-bottom: 10px;
}
.btn-primary {
background: linear-gradient(135deg, #4ade80 0%, #22c55e 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(74, 222, 128, 0.3);
}
.btn-danger {
background: linear-gradient(135deg, #f87171 0%, #ef4444 100%);
color: white;
}
.btn-secondary {
background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%);
color: white;
}
.custom-server-form {
display: none;
background: #f8f9fa;
padding: 20px;
border-radius: 10px;
margin-top: 20px;
}
.custom-server-form.active {
display: block;
}
.server-list {
max-height: 300px;
overflow-y: auto;
margin-top: 20px;
}
.server-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
background: white;
border-radius: 10px;
margin-bottom: 10px;
transition: all 0.3s;
}
.server-item:hover {
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}
.import-section {
background: #f8f9fa;
padding: 20px;
border-radius: 10px;
margin-top: 20px;
}
.file-upload {
position: relative;
display: inline-block;
cursor: pointer;
width: 100%;
}
.file-upload input[type=file] {
position: absolute;
left: -9999px;
}
.file-upload label {
display: block;
padding: 15px;
background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%);
color: white;
border-radius: 10px;
text-align: center;
cursor: pointer;
transition: all 0.3s;
}
.file-upload label:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(96, 165, 250, 0.3);
}
.tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.tab {
padding: 10px 20px;
background: #e0e0e0;
border-radius: 10px;
cursor: pointer;
transition: all 0.3s;
}
.tab.active {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.spinner {
display: none;
width: 20px;
height: 20px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-top-color: white;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading .spinner {
display: block;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
align-items: center;
justify-content: center;
}
.modal.active {
display: flex;
}
.modal-content {
background: white;
padding: 30px;
border-radius: 15px;
max-width: 500px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
}
.keypair-display {
background: #f8f9fa;
padding: 15px;
border-radius: 10px;
margin: 10px 0;
font-family: monospace;
word-break: break-all;
font-size: 0.9em;
}
.copy-btn {
padding: 5px 10px;
background: #667eea;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 0.9em;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🔐 VPN Gateway Control Center</h1>
<p>Multi-Provider Support with Permanent Killswitch</p>
</div>
<div class="provider-selector">
<h2>Select VPN Provider</h2>
<div class="provider-tabs">
<div class="provider-tab active" data-provider="mullvad" onclick="switchProvider('mullvad')">
<div>🌍 Mullvad</div>
<small>Commercial VPN</small>
</div>
<div class="provider-tab" data-provider="custom" onclick="switchProvider('custom')">
<div>🔧 Custom Server</div>
<small>Own VPS/Server</small>
</div>
<div class="provider-tab" data-provider="imported" onclick="switchProvider('imported')">
<div>📁 Import Config</div>
<small>Existing WireGuard</small>
</div>
</div>
</div>
<div class="main-grid">
<div class="card status-card">
<div class="status-header">
<div class="status-indicator">
<span class="status-dot disconnected" id="statusDot"></span>
<span id="statusText">Disconnected</span>
</div>
<button class="btn-secondary" onclick="refreshStatus()" style="width: auto; padding: 10px 20px;">
🔄 Refresh
</button>
</div>
<div class="info-grid">
<div class="info-item">
<div class="info-label">Provider</div>
<div class="info-value" id="currentProvider">-</div>
</div>
<div class="info-item">
<div class="info-label">Server</div>
<div class="info-value" id="currentServer">-</div>
</div>
<div class="info-item">
<div class="info-label">Public IP</div>
<div class="info-value" id="publicIP">-</div>
</div>
<div class="info-item">
<div class="info-label">Location</div>
<div class="info-value" id="location">-</div>
</div>
</div>
<div class="security-notice" style="margin-top: 20px;">
<strong>🛡️ Security Status: PROTECTED</strong><br>
✓ Killswitch permanently active<br>
✓ No internet without VPN<br>
✓ DNS leak protection enabled
</div>
</div>
<div class="card">
<!-- Mullvad Provider Section -->
<div id="mullvad-section" class="provider-section">
<h3>Mullvad Server Selection</h3>
<select id="countrySelect" onchange="updateCities()">
<option value="">Loading countries...</option>
</select>
<select id="citySelect" onchange="updateServers()">
<option value="">Select a country first</option>
</select>
<select id="serverSelect">
<option value="">Select a city first</option>
</select>
</div>
<!-- Custom Provider Section -->
<div id="custom-section" class="provider-section" style="display: none;">
<h3>Custom WireGuard Servers</h3>
<button class="btn-secondary" onclick="toggleCustomForm()">
Add New Server
</button>
<div id="customServerForm" class="custom-server-form">
<h4>Add Custom Server</h4>
<input type="text" id="customName" placeholder="Server Name (e.g., my-vps)">
<input type="text" id="customEndpoint" placeholder="Endpoint (IP:Port or domain:port)">
<input type="text" id="customPublicKey" placeholder="Server Public Key">
<input type="text" id="customPrivateKey" placeholder="Client Private Key (optional)">
<input type="text" id="customAddress" placeholder="Client IP (default: 10.0.0.2/32)">
<input type="text" id="customDNS" placeholder="DNS Servers (default: 1.1.1.1,1.0.0.1)">
<input type="text" id="customLocation" placeholder="Location (e.g., Germany)">
<button class="btn-secondary" onclick="generateKeypair()">
🔑 Generate Keypair
</button>
<button class="btn-primary" onclick="addCustomServer()">
Add Server
</button>
</div>
<div class="server-list" id="customServerList">
<!-- Custom servers will be listed here -->
</div>
<select id="customServerSelect">
<option value="">Select a custom server</option>
</select>
</div>
<!-- Import Provider Section -->
<div id="import-section" class="provider-section" style="display: none;">
<h3>Import WireGuard Configuration</h3>
<div class="import-section">
<input type="text" id="importName" placeholder="Configuration Name">
<div class="file-upload">
<input type="file" id="configFile" accept=".conf" onchange="handleFileSelect(event)">
<label for="configFile">📁 Choose WireGuard Config File</label>
</div>
<textarea id="configContent" placeholder="Or paste your WireGuard config here..." rows="10"></textarea>
<button class="btn-primary" onclick="importConfig()">
Import Configuration
</button>
</div>
<div class="server-list" id="importedConfigList">
<!-- Imported configs will be listed here -->
</div>
<select id="importedServerSelect">
<option value="">Select an imported config</option>
</select>
</div>
<div style="margin-top: 30px;">
<button class="btn-primary" onclick="connectVPN()">
<span>🔗 Connect</span>
<span class="spinner"></span>
</button>
<button class="btn-danger" onclick="disconnectVPN()">
<span>⛔ Disconnect</span>
<span class="spinner"></span>
</button>
</div>
</div>
</div>
</div>
<!-- Keypair Modal -->
<div id="keypairModal" class="modal">
<div class="modal-content">
<h3>Generated WireGuard Keypair</h3>
<p style="margin: 15px 0;">Save these keys securely!</p>
<label>Private Key (Keep Secret!):</label>
<div class="keypair-display" id="generatedPrivateKey"></div>
<button class="copy-btn" onclick="copyToClipboard('generatedPrivateKey')">Copy</button>
<label style="margin-top: 15px; display: block;">Public Key (Share with Server):</label>
<div class="keypair-display" id="generatedPublicKey"></div>
<button class="copy-btn" onclick="copyToClipboard('generatedPublicKey')">Copy</button>
<button class="btn-secondary" style="margin-top: 20px;" onclick="closeModal()">
Close
</button>
</div>
</div>
<script>
let currentProvider = 'mullvad';
let servers = {};
let customServers = [];
let importedConfigs = [];
// Initialize
document.addEventListener('DOMContentLoaded', function() {
loadProviders();
refreshStatus();
setInterval(refreshStatus, 10000);
});
async function loadProviders() {
try {
const response = await fetch('/api/providers');
const data = await response.json();
if (data.current) {
currentProvider = data.current;
switchProvider(currentProvider);
} else {
loadServers();
}
} catch (error) {
console.error('Error loading providers:', error);
loadServers();
}
}
async function switchProvider(provider) {
// Update UI
document.querySelectorAll('.provider-tab').forEach(tab => {
tab.classList.remove('active');
});
document.querySelector(`[data-provider="${provider}"]`).classList.add('active');
// Hide all sections
document.querySelectorAll('.provider-section').forEach(section => {
section.style.display = 'none';
});
// Show selected section
document.getElementById(`${provider}-section`).style.display = 'block';
currentProvider = provider;
// Switch backend provider
try {
await fetch(`/api/provider/${provider}`, { method: 'POST' });
} catch (error) {
console.error('Error switching provider:', error);
}
// Load servers for new provider
loadServers();
}
async function loadServers() {
try {
const response = await fetch('/api/servers');
const data = await response.json();
servers = data.servers;
if (currentProvider === 'mullvad') {
updateMullvadSelects();
} else if (currentProvider === 'custom') {
updateCustomServers();
} else if (currentProvider === 'imported') {
updateImportedConfigs();
}
} catch (error) {
console.error('Error loading servers:', error);
}
}
function updateMullvadSelects() {
const countrySelect = document.getElementById('countrySelect');
countrySelect.innerHTML = '<option value="">Select a country</option>';
Object.keys(servers).forEach(country => {
const option = document.createElement('option');
option.value = country;
option.textContent = country;
countrySelect.appendChild(option);
});
}
function updateCities() {
const country = document.getElementById('countrySelect').value;
const citySelect = document.getElementById('citySelect');
const serverSelect = document.getElementById('serverSelect');
citySelect.innerHTML = '<option value="">Select a city</option>';
serverSelect.innerHTML = '<option value="">Select a city first</option>';
if (country && servers[country]) {
Object.keys(servers[country]).forEach(city => {
const option = document.createElement('option');
option.value = city;
option.textContent = city;
citySelect.appendChild(option);
});
}
}
function updateServers() {
const country = document.getElementById('countrySelect').value;
const city = document.getElementById('citySelect').value;
const serverSelect = document.getElementById('serverSelect');
serverSelect.innerHTML = '<option value="">Select a server</option>';
if (country && city && servers[country][city]) {
servers[country][city].forEach(server => {
const option = document.createElement('option');
option.value = server.hostname;
option.textContent = `${server.hostname} (${server.type || 'WireGuard'})`;
serverSelect.appendChild(option);
});
}
}
function updateCustomServers() {
const customSelect = document.getElementById('customServerSelect');
const serverList = document.getElementById('customServerList');
customSelect.innerHTML = '<option value="">Select a custom server</option>';
serverList.innerHTML = '';
// Flatten servers structure for custom
for (const location in servers) {
for (const city in servers[location]) {
servers[location][city].forEach(server => {
// Add to select
const option = document.createElement('option');
option.value = server.hostname;
option.textContent = `${server.hostname} (${location})`;
customSelect.appendChild(option);
// Add to list
const item = document.createElement('div');
item.className = 'server-item';
item.innerHTML = `
<div>
<strong>${server.hostname}</strong><br>
<small>${server.endpoint} - ${location}</small>
</div>
<button class="btn-danger" style="width: auto; padding: 5px 15px;"
onclick="removeCustomServer('${server.hostname}')">
Remove
</button>
`;
serverList.appendChild(item);
});
}
}
}
function updateImportedConfigs() {
const importedSelect = document.getElementById('importedServerSelect');
const configList = document.getElementById('importedConfigList');
importedSelect.innerHTML = '<option value="">Select an imported config</option>';
configList.innerHTML = '';
// Check for imported configs in servers
if (servers['Imported'] && servers['Imported']['Configs']) {
servers['Imported']['Configs'].forEach(config => {
// Add to select
const option = document.createElement('option');
option.value = config.hostname;
option.textContent = config.hostname;
importedSelect.appendChild(option);
// Add to list
const item = document.createElement('div');
item.className = 'server-item';
item.innerHTML = `
<div>
<strong>${config.hostname}</strong><br>
<small>Imported Configuration</small>
</div>
`;
configList.appendChild(item);
});
}
}
function toggleCustomForm() {
const form = document.getElementById('customServerForm');
form.classList.toggle('active');
}
async function generateKeypair() {
try {
const response = await fetch('/api/keypair');
const data = await response.json();
document.getElementById('customPrivateKey').value = data.private_key;
document.getElementById('generatedPrivateKey').textContent = data.private_key;
document.getElementById('generatedPublicKey').textContent = data.public_key;
document.getElementById('keypairModal').classList.add('active');
} catch (error) {
alert('Error generating keypair: ' + error);
}
}
async function addCustomServer() {
const name = document.getElementById('customName').value;
const endpoint = document.getElementById('customEndpoint').value;
const publicKey = document.getElementById('customPublicKey').value;
if (!name || !endpoint || !publicKey) {
alert('Please fill in required fields');
return;
}
const config = {
name: name,
endpoint: endpoint,
public_key: publicKey,
private_key: document.getElementById('customPrivateKey').value,
address: document.getElementById('customAddress').value,
dns: document.getElementById('customDNS').value,
location: document.getElementById('customLocation').value || 'Custom'
};
try {
const response = await fetch('/api/custom/add', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(config)
});
if (response.ok) {
alert('Server added successfully');
toggleCustomForm();
// Clear form
document.querySelectorAll('#customServerForm input').forEach(input => input.value = '');
loadServers();
} else {
alert('Failed to add server');
}
} catch (error) {
alert('Error adding server: ' + error);
}
}
async function removeCustomServer(name) {
if (!confirm(`Remove server ${name}?`)) return;
try {
const response = await fetch(`/api/custom/remove/${name}`, {
method: 'DELETE'
});
if (response.ok) {
loadServers();
} else {
alert('Failed to remove server');
}
} catch (error) {
alert('Error removing server: ' + error);
}
}
function handleFileSelect(event) {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
document.getElementById('configContent').value = e.target.result;
// Try to extract name from filename
const name = file.name.replace('.conf', '');
document.getElementById('importName').value = name;
};
reader.readAsText(file);
}
}
async function importConfig() {
const name = document.getElementById('importName').value;
const config = document.getElementById('configContent').value;
if (!name || !config) {
alert('Please provide a name and configuration');
return;
}
try {
const response = await fetch('/api/import', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({name: name, config: config})
});
if (response.ok) {
alert('Configuration imported successfully');
document.getElementById('importName').value = '';
document.getElementById('configContent').value = '';
loadServers();
} else {
alert('Failed to import configuration');
}
} catch (error) {
alert('Error importing config: ' + error);
}
}
async function refreshStatus() {
try {
const response = await fetch('/api/status');
const data = await response.json();
const statusDot = document.getElementById('statusDot');
const statusText = document.getElementById('statusText');
if (data.connected) {
statusDot.className = 'status-dot connected';
statusText.textContent = 'Connected';
document.getElementById('currentProvider').textContent = data.provider || '-';
document.getElementById('currentServer').textContent = data.server || '-';
document.getElementById('publicIP').textContent = data.ip || 'Checking...';
document.getElementById('location').textContent = data.location || '-';
} else {
statusDot.className = 'status-dot disconnected';
statusText.textContent = 'Disconnected';
document.getElementById('currentProvider').textContent = '-';
document.getElementById('currentServer').textContent = '-';
document.getElementById('publicIP').textContent = '-';
document.getElementById('location').textContent = '-';
}
} catch (error) {
console.error('Error refreshing status:', error);
}
}
async function connectVPN() {
let server = null;
if (currentProvider === 'mullvad') {
server = document.getElementById('serverSelect').value;
} else if (currentProvider === 'custom') {
server = document.getElementById('customServerSelect').value;
} else if (currentProvider === 'imported') {
server = document.getElementById('importedServerSelect').value;
}
if (!server) {
alert('Please select a server');
return;
}
const button = event.target.closest('button');
button.classList.add('loading');
try {
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 successfully!');
setTimeout(refreshStatus, 2000);
} else {
alert('Connection failed: ' + (data.error || 'Unknown error'));
}
} catch (error) {
alert('Connection error: ' + error);
} finally {
button.classList.remove('loading');
}
}
async function disconnectVPN() {
const button = event.target.closest('button');
button.classList.add('loading');
try {
const response = await fetch('/api/disconnect', {method: 'POST'});
const data = await response.json();
if (data.success) {
alert('Disconnected - WARNING: No internet access (killswitch active)');
setTimeout(refreshStatus, 1000);
} else {
alert('Disconnect failed: ' + (data.error || 'Unknown error'));
}
} catch (error) {
alert('Disconnect error: ' + error);
} finally {
button.classList.remove('loading');
}
}
function copyToClipboard(elementId) {
const text = document.getElementById(elementId).textContent;
navigator.clipboard.writeText(text).then(() => {
alert('Copied to clipboard!');
});
}
function closeModal() {
document.getElementById('keypairModal').classList.remove('active');
}
</script>
</body>
</html>

1408
install.sh Normal file

File diff suppressed because it is too large Load diff

162
scripts/health-check.sh Executable file
View file

@ -0,0 +1,162 @@
#!/bin/bash
# VPN Gateway Health Check Script
# Comprehensive system health monitoring
# Version: 1.0.0
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Scoring
TOTAL_SCORE=0
MAX_SCORE=100
ISSUES=()
check_mark() {
echo -e "${GREEN}${NC}"
}
x_mark() {
echo -e "${RED}${NC}"
}
warning_mark() {
echo -e "${YELLOW}${NC}"
}
add_score() {
TOTAL_SCORE=$((TOTAL_SCORE + $1))
}
add_issue() {
ISSUES+=("$1")
}
echo -e "${BLUE}=== VPN Gateway Health Check ===${NC}"
echo ""
# 1. Check Services
echo -n "Checking services... "
services_ok=true
for service in vpn-webui vpn-killswitch vpn-security-monitor; do
if systemctl is-active $service >/dev/null 2>&1; then
add_score 10
else
services_ok=false
add_issue "Service $service is not running"
fi
done
[ "$services_ok" = true ] && check_mark || x_mark
# 2. Check Killswitch
echo -n "Checking killswitch... "
if iptables -L OUTPUT -n | grep -q "policy DROP"; then
add_score 20
check_mark
else
add_issue "Killswitch not active!"
x_mark
fi
# 3. Check VPN Connection
echo -n "Checking VPN connection... "
if wg show wg0 >/dev/null 2>&1; then
add_score 15
check_mark
else
add_issue "VPN not connected"
warning_mark
fi
# 4. Check for leaks
echo -n "Checking for leaks... "
if ! ping -c 1 -W 1 8.8.8.8 >/dev/null 2>&1; then
if wg show wg0 >/dev/null 2>&1; then
add_issue "VPN connected but no internet"
warning_mark
else
add_score 15
check_mark
fi
else
if wg show wg0 >/dev/null 2>&1; then
add_score 15
check_mark
else
add_issue "CRITICAL: Internet accessible without VPN!"
x_mark
fi
fi
# 5. Check DNS
echo -n "Checking DNS configuration... "
dns_ok=true
while read -r dns; do
case "$dns" in
127.0.0.1|10.*|172.*|192.168.*|100.64.*)
;;
*)
dns_ok=false
add_issue "Public DNS detected: $dns"
;;
esac
done < <(grep "^nameserver" /etc/resolv.conf | awk '{print $2}')
if [ "$dns_ok" = true ]; then
add_score 10
check_mark
else
warning_mark
fi
# 6. Check disk space
echo -n "Checking disk space... "
disk_usage=$(df /opt/vpn-gateway | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$disk_usage" -lt 80 ]; then
add_score 5
check_mark
elif [ "$disk_usage" -lt 90 ]; then
add_issue "Disk usage high: ${disk_usage}%"
warning_mark
else
add_issue "Critical disk usage: ${disk_usage}%"
x_mark
fi
# 7. Check WebUI accessibility
echo -n "Checking WebUI... "
if curl -s http://localhost:5000/api/status >/dev/null 2>&1; then
add_score 10
check_mark
else
add_issue "WebUI not accessible"
x_mark
fi
# Results
echo ""
echo -e "${BLUE}=== Health Score: $TOTAL_SCORE/$MAX_SCORE ===${NC}"
echo ""
if [ $TOTAL_SCORE -ge 90 ]; then
echo -e "${GREEN}System Status: EXCELLENT${NC}"
elif [ $TOTAL_SCORE -ge 70 ]; then
echo -e "${GREEN}System Status: GOOD${NC}"
elif [ $TOTAL_SCORE -ge 50 ]; then
echo -e "${YELLOW}System Status: WARNING${NC}"
else
echo -e "${RED}System Status: CRITICAL${NC}"
fi
if [ ${#ISSUES[@]} -gt 0 ]; then
echo ""
echo "Issues found:"
for issue in "${ISSUES[@]}"; do
echo " - $issue"
done
fi
exit $((100 - TOTAL_SCORE))

241
scripts/killswitch.sh Executable file
View file

@ -0,0 +1,241 @@
#!/bin/bash
#############################################################
# scripts/killswitch.sh #
#############################################################
cat > scripts/killswitch.sh << 'EOFKILLSWITCH'
#!/bin/bash
# VPN Gateway Killswitch Script
# CRITICAL: This script ensures NO traffic leaks without VPN
# Version: 1.0.0
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Get network configuration from install
if [ -f /opt/vpn-gateway/network.conf ]; then
source /opt/vpn-gateway/network.conf
else
# Fallback to auto-detection
LAN_IF=$(ip route | grep default | awk '{print $5}' | head -n1)
LAN_NET=$(ip route | grep "$LAN_IF" | grep -v default | awk '{print $1}' | head -n1)
fi
# Logging
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> /var/log/vpn-killswitch.log
echo -e "${GREEN}[+]${NC} $1"
}
error() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1" >> /var/log/vpn-killswitch.log
echo -e "${RED}[!]${NC} $1"
}
warning() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] WARNING: $1" >> /var/log/vpn-killswitch.log
echo -e "${YELLOW}[*]${NC} $1"
}
enable_killswitch() {
log "ENABLING PERMANENT KILLSWITCH - NO INTERNET WITHOUT VPN"
# Backup current rules (just in case)
iptables-save > /tmp/iptables.backup.$(date +%s) 2>/dev/null || true
# Reset all rules
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X
iptables -t raw -F
iptables -t raw -X
# DEFAULT POLICIES: DROP EVERYTHING
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT DROP
log "Default policies set to DROP"
# CRITICAL: Allow loopback (required for system operation)
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT
# Allow LAN communication (for WebUI and local clients)
if [ -n "$LAN_IF" ] && [ -n "$LAN_NET" ]; then
iptables -A INPUT -i $LAN_IF -s $LAN_NET -j ACCEPT
iptables -A OUTPUT -o $LAN_IF -d $LAN_NET -j ACCEPT
log "LAN communication allowed: $LAN_IF ($LAN_NET)"
fi
# Allow DNS for initial VPN connection (root only, limited)
iptables -A OUTPUT -p udp --dport 53 -m owner --uid-owner root -m limit --limit 10/min -j ACCEPT
iptables -A OUTPUT -p tcp --dport 53 -m owner --uid-owner root -m limit --limit 10/min -j ACCEPT
# Allow DHCP client (if needed)
iptables -A OUTPUT -p udp --sport 68 --dport 67 -j ACCEPT
iptables -A INPUT -p udp --sport 67 --dport 68 -j ACCEPT
# Allow established/related 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 (will only work when VPN is up)
if [ -n "$LAN_IF" ] && [ -n "$LAN_NET" ]; then
iptables -A FORWARD -i $LAN_IF -s $LAN_NET -j ACCEPT
fi
# Log dropped packets (optional, can be verbose)
# iptables -A INPUT -j LOG --log-prefix "INPUT-DROP: " --log-level 4
# iptables -A OUTPUT -j LOG --log-prefix "OUTPUT-DROP: " --log-level 4
# IPv6: Complete blocking (unless you use IPv6 VPN)
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 for persistence
mkdir -p /etc/iptables
iptables-save > /etc/iptables/rules.v4
ip6tables-save > /etc/iptables/rules.v6
# Ensure persistence across reboots
if ! systemctl is-enabled netfilter-persistent >/dev/null 2>&1; then
systemctl enable netfilter-persistent 2>/dev/null || true
fi
log "KILLSWITCH ACTIVE - System is now protected"
log "Rules saved to /etc/iptables/"
# Verify killswitch is working
verify_killswitch
}
disable_killswitch() {
error "KILLSWITCH CANNOT BE DISABLED FOR SECURITY REASONS!"
warning "This is a security feature. The killswitch must remain active."
warning "If you really need to disable it, you must manually flush iptables rules."
warning "This would leave your system vulnerable to leaks!"
# Re-enable killswitch immediately
enable_killswitch
return 1
}
verify_killswitch() {
log "Verifying killswitch status..."
# Check DROP policies
if iptables -L -n | grep -q "Chain INPUT (policy DROP)" && \
iptables -L -n | grep -q "Chain OUTPUT (policy DROP)" && \
iptables -L -n | grep -q "Chain FORWARD (policy DROP)"; then
log "✓ Killswitch policies verified: DROP"
else
error "✗ Killswitch policies NOT active!"
enable_killswitch
return 1
fi
# Test that we cannot reach internet directly
if ping -c 1 -W 1 8.8.8.8 >/dev/null 2>&1; then
if wg show wg0 >/dev/null 2>&1; then
log "✓ Internet accessible (VPN is connected)"
else
error "✗ LEAK DETECTED: Internet accessible without VPN!"
enable_killswitch
return 1
fi
else
if wg show wg0 >/dev/null 2>&1; then
warning "VPN appears connected but internet not reachable"
else
log "✓ Internet blocked (VPN not connected)"
fi
fi
log "Killswitch verification complete"
}
status_killswitch() {
echo -e "${GREEN}=== VPN Killswitch Status ===${NC}"
echo ""
# Check policies
echo "Firewall Policies:"
iptables -L -n | grep "Chain.*policy" | while read line; do
if echo "$line" | grep -q "DROP"; then
echo -e " ${GREEN}${NC} $line"
else
echo -e " ${RED}${NC} $line"
fi
done
echo ""
echo "Active Rules Summary:"
echo " INPUT: $(iptables -L INPUT -n | grep -c ACCEPT) ACCEPT rules"
echo " OUTPUT: $(iptables -L OUTPUT -n | grep -c ACCEPT) ACCEPT rules"
echo " FORWARD: $(iptables -L FORWARD -n | grep -c ACCEPT) ACCEPT rules"
echo ""
echo "VPN Status:"
if wg show wg0 >/dev/null 2>&1; then
echo -e " ${GREEN}${NC} WireGuard interface active"
echo " Endpoint: $(wg show wg0 endpoints | awk '{print $2}')"
else
echo -e " ${YELLOW}${NC} WireGuard interface not active"
fi
echo ""
echo "Leak Test:"
if ping -c 1 -W 1 8.8.8.8 >/dev/null 2>&1; then
if wg show wg0 >/dev/null 2>&1; then
echo -e " ${GREEN}${NC} Internet accessible via VPN"
else
echo -e " ${RED}✗ LEAK: Internet accessible without VPN!${NC}"
fi
else
echo -e " ${GREEN}${NC} Internet blocked (killswitch working)"
fi
}
# Main logic
case "${1:-enable}" in
enable|start)
enable_killswitch
;;
disable|stop)
disable_killswitch
;;
verify|check)
verify_killswitch
;;
status)
status_killswitch
;;
restart|reload)
enable_killswitch
;;
*)
echo "Usage: $0 {enable|disable|verify|status|restart}"
echo " enable - Enable killswitch (default)"
echo " disable - Disable killswitch (blocked for security)"
echo " verify - Verify killswitch is working"
echo " status - Show detailed status"
echo " restart - Restart killswitch"
exit 1
;;
esac

258
scripts/security-monitor.sh Executable file
View file

@ -0,0 +1,258 @@
#############################################################
# scripts/security-monitor.sh #
#############################################################
cat > scripts/security-monitor.sh << 'EOFSECMON'
#!/bin/bash
# VPN Security Monitor
# Continuous monitoring of killswitch and VPN status
# Version: 1.0.0
# Configuration
CHECK_INTERVAL=10 # seconds
ALERT_EMAIL="" # Set email for alerts
LOG_FILE="/var/log/vpn-security-monitor.log"
STATE_FILE="/var/run/vpn-monitor.state"
# Source network config
if [ -f /opt/vpn-gateway/network.conf ]; then
source /opt/vpn-gateway/network.conf
fi
# Logging functions
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
}
alert() {
local message="$1"
log "ALERT: $message"
# Send email alert if configured
if [ -n "$ALERT_EMAIL" ]; then
echo "$message" | mail -s "VPN Security Alert" "$ALERT_EMAIL" 2>/dev/null || true
fi
# System notification (if available)
if command -v notify-send >/dev/null 2>&1; then
notify-send "VPN Security Alert" "$message" -u critical
fi
# Log to syslog
logger -t "vpn-security" -p auth.crit "$message"
}
# Check functions
check_killswitch() {
# Verify DROP policies are active
local policies_ok=true
for chain in INPUT OUTPUT FORWARD; do
if ! iptables -L $chain -n | grep -q "policy DROP"; then
alert "Killswitch policy missing for chain $chain!"
policies_ok=false
fi
done
if [ "$policies_ok" = false ]; then
log "Reactivating killswitch..."
/usr/local/bin/vpn-killswitch.sh enable
return 1
fi
return 0
}
check_vpn_connection() {
if wg show wg0 >/dev/null 2>&1; then
# VPN interface exists, check if it's actually working
local endpoint=$(wg show wg0 endpoints | awk '{print $2}')
if [ -z "$endpoint" ]; then
log "WARNING: VPN interface exists but no endpoint configured"
return 1
fi
# Check last handshake
local last_handshake=$(wg show wg0 latest-handshakes | awk '{print $2}')
local current_time=$(date +%s)
if [ -n "$last_handshake" ] && [ "$last_handshake" -ne 0 ]; then
local time_diff=$((current_time - last_handshake))
# If last handshake was more than 3 minutes ago, connection might be dead
if [ $time_diff -gt 180 ]; then
log "WARNING: Last WireGuard handshake was ${time_diff} seconds ago"
return 2
fi
fi
return 0
else
return 1
fi
}
check_dns_leaks() {
# Check if DNS is going through VPN
local dns_servers=$(cat /etc/resolv.conf | grep "^nameserver" | awk '{print $2}')
for dns in $dns_servers; do
# Check if DNS server is in private range or VPN provider's DNS
case "$dns" in
10.*|172.16.*|172.17.*|172.18.*|172.19.*|172.2*|172.30.*|172.31.*|192.168.*|100.64.*)
# Private IP, likely VPN DNS
;;
127.*)
# Localhost, check if it's a DNS proxy
if systemctl is-active systemd-resolved >/dev/null 2>&1; then
log "WARNING: systemd-resolved is active, checking for leaks"
# Additional checks for systemd-resolved
fi
;;
*)
alert "Potential DNS leak detected! Public DNS server: $dns"
return 1
;;
esac
done
return 0
}
check_leak_test() {
# Try to reach internet without VPN
local vpn_active=$(check_vpn_connection; echo $?)
if [ $vpn_active -eq 1 ]; then
# VPN not active, internet should be blocked
if ping -c 1 -W 1 8.8.8.8 >/dev/null 2>&1; then
alert "CRITICAL: Internet accessible without VPN! Leak detected!"
# Immediately re-enable killswitch
/usr/local/bin/vpn-killswitch.sh enable
return 1
fi
fi
return 0
}
check_processes() {
# Check for processes that might bypass VPN
local suspicious_processes=$(netstat -tunp 2>/dev/null | grep -v "127.0.0.1\|::1\|$LAN_NET" | grep ESTABLISHED)
if [ -n "$suspicious_processes" ]; then
log "WARNING: Detected network connections that might bypass VPN:"
echo "$suspicious_processes" >> "$LOG_FILE"
fi
}
monitor_bandwidth() {
# Monitor bandwidth to detect unusual activity
if command -v vnstat >/dev/null 2>&1; then
local current_tx=$(vnstat --oneline | cut -d';' -f9)
local current_rx=$(vnstat --oneline | cut -d';' -f10)
if [ -f "$STATE_FILE" ]; then
source "$STATE_FILE"
# Compare with previous values
# Alert if sudden spike in traffic when VPN is down
if ! check_vpn_connection && [ -n "$LAST_TX" ]; then
# Calculate difference
# If significant traffic when VPN is down, alert
log "Bandwidth check: TX=$current_tx RX=$current_rx"
fi
fi
# Save current state
echo "LAST_TX='$current_tx'" > "$STATE_FILE"
echo "LAST_RX='$current_rx'" >> "$STATE_FILE"
fi
}
auto_recovery() {
local vpn_status=$(check_vpn_connection; echo $?)
if [ $vpn_status -eq 2 ]; then
# Connection stale, try to reconnect
log "Attempting auto-recovery of VPN connection..."
# Get last used server from config
if [ -f /etc/wireguard/wg0.conf ]; then
systemctl restart wg-quick@wg0 2>/dev/null || \
wg-quick down wg0 2>/dev/null && wg-quick up wg0 2>/dev/null
sleep 5
if check_vpn_connection; then
log "Auto-recovery successful"
return 0
else
alert "Auto-recovery failed - manual intervention required"
return 1
fi
fi
fi
return 0
}
# Main monitoring loop
main() {
log "VPN Security Monitor started"
log "Check interval: ${CHECK_INTERVAL}s"
# Initial checks
check_killswitch
check_vpn_connection
check_dns_leaks
local error_count=0
while true; do
# Run all checks
local all_ok=true
if ! check_killswitch; then
all_ok=false
((error_count++))
fi
if ! check_leak_test; then
all_ok=false
((error_count++))
fi
if ! check_dns_leaks; then
all_ok=false
((error_count++))
fi
check_processes
monitor_bandwidth
# Auto-recovery if needed
if [ $error_count -gt 3 ]; then
auto_recovery
error_count=0
fi
# Status indicator
if [ "$all_ok" = true ]; then
echo -n "."
error_count=0
else
echo -n "!"
fi
sleep "$CHECK_INTERVAL"
done
}
# Signal handlers
trap 'log "Security monitor stopped"; exit 0' SIGTERM SIGINT
# Run main loop
main

156
scripts/uninstall.sh Executable file
View file

@ -0,0 +1,156 @@
#!/bin/bash
# VPN Gateway Uninstall Script
# Completely removes VPN Gateway
# Version: 1.0.0
set -e
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
INSTALL_DIR="/opt/vpn-gateway"
log() {
echo -e "${GREEN}[+]${NC} $1"
}
error() {
echo -e "${RED}[!]${NC} $1"
}
warning() {
echo -e "${YELLOW}[!]${NC} $1"
}
# Check root
if [[ $EUID -ne 0 ]]; then
error "This script must be run as root"
exit 1
fi
echo -e "${RED}=== VPN Gateway Uninstaller ===${NC}"
echo ""
warning "This will completely remove VPN Gateway and its components."
warning "The killswitch will be DISABLED, potentially exposing your traffic!"
echo ""
echo "The following will be removed:"
echo " - VPN Gateway application ($INSTALL_DIR)"
echo " - WireGuard configurations"
echo " - Systemd services"
echo " - Firewall rules (killswitch)"
echo " - Nginx configuration"
echo ""
read -p "Are you SURE you want to uninstall? Type 'YES' to confirm: " CONFIRM
if [ "$CONFIRM" != "YES" ]; then
log "Uninstall cancelled"
exit 0
fi
# Create backup just in case
BACKUP_DIR="/root/vpn-gateway-final-backup-$(date +%Y%m%d-%H%M%S)"
log "Creating final backup at $BACKUP_DIR..."
mkdir -p "$BACKUP_DIR"
# Backup configs
cp -r /etc/wireguard "$BACKUP_DIR/wireguard" 2>/dev/null || true
cp -r "$INSTALL_DIR" "$BACKUP_DIR/app" 2>/dev/null || true
iptables-save > "$BACKUP_DIR/iptables.rules" 2>/dev/null || true
# Stop and disable services
log "Stopping services..."
systemctl stop vpn-webui vpn-killswitch vpn-security-monitor 2>/dev/null || true
systemctl disable vpn-webui vpn-killswitch vpn-security-monitor 2>/dev/null || true
# Stop WireGuard
wg-quick down wg0 2>/dev/null || true
systemctl stop wg-quick@wg0 2>/dev/null || true
systemctl disable wg-quick@wg0 2>/dev/null || true
# Remove systemd services
log "Removing systemd services..."
rm -f /etc/systemd/system/vpn-*.service
systemctl daemon-reload
# Remove application files
log "Removing application files..."
rm -rf "$INSTALL_DIR"
# Remove scripts
log "Removing scripts..."
rm -f /usr/local/bin/vpn-*.sh
# Remove Nginx configuration
log "Removing Nginx configuration..."
rm -f /etc/nginx/sites-enabled/vpn-gateway
rm -f /etc/nginx/sites-available/vpn-gateway
systemctl reload nginx 2>/dev/null || true
# Remove WireGuard configs (optional)
read -p "Remove WireGuard configurations? (y/N): " -n 1 -r
echo ""
if [[ $REPLY =~ ^[Yy]$ ]]; then
rm -rf /etc/wireguard
log "WireGuard configurations removed"
fi
# CRITICAL: Remove killswitch
warning "Removing killswitch - your traffic will no longer be protected!"
read -p "Remove killswitch firewall rules? (y/N): " -n 1 -r
echo ""
if [[ $REPLY =~ ^[Yy]$ ]]; then
# Reset firewall to default ACCEPT policies
iptables -P INPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X
# IPv6
ip6tables -P INPUT ACCEPT
ip6tables -P FORWARD ACCEPT
ip6tables -P OUTPUT ACCEPT
ip6tables -F
ip6tables -X
# Save clean rules
iptables-save > /etc/iptables/rules.v4 2>/dev/null || true
ip6tables-save > /etc/iptables/rules.v6 2>/dev/null || true
warning "Firewall reset to ACCEPT all - System is no longer protected!"
else
warning "Killswitch still active - you may have no internet access!"
fi
# Remove log files
read -p "Remove log files? (y/N): " -n 1 -r
echo ""
if [[ $REPLY =~ ^[Yy]$ ]]; then
rm -f /var/log/vpn-*.log
log "Log files removed"
fi
# Final cleanup
log "Cleaning up..."
rm -f /tmp/vpn-gateway* 2>/dev/null || true
echo ""
echo -e "${GREEN}=== Uninstall Complete ===${NC}"
echo ""
echo "VPN Gateway has been removed."
echo "Backup saved at: $BACKUP_DIR"
echo ""
warning "IMPORTANT: Your system is no longer protected by the killswitch!"
warning "All traffic will now use your regular internet connection."
echo ""
echo "To reinstall, run:"
echo " curl -sSL https://your-domain/install.sh | bash"

146
scripts/update.sh Executable file
View file

@ -0,0 +1,146 @@
#############################################################
# scripts/update.sh #
#############################################################
cat > scripts/update.sh << 'EOFUPDATE'
#!/bin/bash
# VPN Gateway Update Script
# Updates the VPN Gateway installation
# Version: 1.0.0
set -e
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
INSTALL_DIR="/opt/vpn-gateway"
BACKUP_DIR="/opt/vpn-gateway-backup-$(date +%Y%m%d-%H%M%S)"
GITHUB_REPO="https://github.com/yourusername/vpn-gateway"
log() {
echo -e "${GREEN}[+]${NC} $1"
}
error() {
echo -e "${RED}[!]${NC} $1"
exit 1
}
warning() {
echo -e "${YELLOW}[*]${NC} $1"
}
# Check root
if [[ $EUID -ne 0 ]]; then
error "This script must be run as root"
fi
echo -e "${BLUE}=== VPN Gateway Update ===${NC}"
echo ""
# Check current version
if [ -f "$INSTALL_DIR/version" ]; then
CURRENT_VERSION=$(cat "$INSTALL_DIR/version")
log "Current version: $CURRENT_VERSION"
else
warning "Version file not found"
CURRENT_VERSION="unknown"
fi
# Check for updates
log "Checking for updates..."
LATEST_VERSION=$(curl -s "$GITHUB_REPO/raw/main/version" 2>/dev/null || echo "")
if [ -z "$LATEST_VERSION" ]; then
error "Could not fetch latest version"
fi
if [ "$CURRENT_VERSION" = "$LATEST_VERSION" ]; then
log "Already running latest version: $LATEST_VERSION"
exit 0
fi
log "New version available: $LATEST_VERSION"
echo ""
read -p "Update to version $LATEST_VERSION? (y/N): " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
log "Update cancelled"
exit 0
fi
# Backup current installation
log "Creating backup at $BACKUP_DIR..."
cp -r "$INSTALL_DIR" "$BACKUP_DIR"
# Backup WireGuard configs
cp -r /etc/wireguard "$BACKUP_DIR/wireguard-configs"
# Backup iptables rules
iptables-save > "$BACKUP_DIR/iptables.rules"
ip6tables-save > "$BACKUP_DIR/ip6tables.rules"
log "Backup complete"
# Stop services
log "Stopping services..."
systemctl stop vpn-webui vpn-security-monitor 2>/dev/null || true
# Download updates
log "Downloading updates..."
cd /tmp
rm -rf vpn-gateway-update
git clone "$GITHUB_REPO" vpn-gateway-update || \
error "Failed to download updates"
# Update backend
log "Updating backend..."
cp /tmp/vpn-gateway-update/backend/app.py "$INSTALL_DIR/app.py"
# Update frontend
log "Updating frontend..."
cp /tmp/vpn-gateway-update/frontend/index.html "$INSTALL_DIR/static/index.html"
# Update scripts
log "Updating scripts..."
cp /tmp/vpn-gateway-update/scripts/*.sh /usr/local/bin/
chmod +x /usr/local/bin/vpn-*.sh
# Update Python dependencies
log "Updating dependencies..."
source "$INSTALL_DIR/venv/bin/activate"
pip install --upgrade -r /tmp/vpn-gateway-update/backend/requirements.txt
# Update version file
echo "$LATEST_VERSION" > "$INSTALL_DIR/version"
# Restart services
log "Restarting services..."
systemctl daemon-reload
systemctl start vpn-webui vpn-security-monitor
# Cleanup
rm -rf /tmp/vpn-gateway-update
# Verify update
sleep 3
if systemctl is-active vpn-webui >/dev/null 2>&1; then
log "Update successful!"
log "Version $LATEST_VERSION is now running"
echo ""
echo -e "${GREEN}Update complete!${NC}"
echo "Backup saved at: $BACKUP_DIR"
else
error "Services failed to start after update!"
echo "Restore from backup with:"
echo " rm -rf $INSTALL_DIR"
echo " mv $BACKUP_DIR $INSTALL_DIR"
echo " systemctl restart vpn-webui"
fi

1
version Normal file
View file

@ -0,0 +1 @@
1.0.0