2017 lines
81 KiB
Python
2017 lines
81 KiB
Python
|
|
def log(self, message):
|
||
|
|
"""Add message to log display and terminal output"""
|
||
|
|
# Check if log_text exists and write to it
|
||
|
|
if hasattr(self, 'log_text'):
|
||
|
|
self.log_text.insert(tk.END, f"{message}\n")
|
||
|
|
self.log_text.see(tk.END)
|
||
|
|
|
||
|
|
# Also write to terminal output if it exists
|
||
|
|
if hasattr(self, 'terminal_output'):
|
||
|
|
timestamp = datetime.now().strftime("%H:%M:%S")
|
||
|
|
self.terminal_output.insert(tk.END, f"[{timestamp}] {message}\n")
|
||
|
|
self.terminal_output.see(tk.END)
|
||
|
|
self.root.update()
|
||
|
|
|
||
|
|
print(f"[LOG] {message}") # Also print to console for debugging
|
||
|
|
|
||
|
|
|
||
|
|
def terminal_write(self, message, color="green"):
|
||
|
|
"""Write directly to terminal output with optional color"""
|
||
|
|
if hasattr(self, 'terminal_output'):
|
||
|
|
timestamp = datetime.now().strftime("%H:%M:%S")
|
||
|
|
# Configure color tag
|
||
|
|
self.terminal_output.tag_config(color, foreground=color)
|
||
|
|
start = self.terminal_output.index(tk.END)
|
||
|
|
self.terminal_output.insert(tk.END, f"[{timestamp}] {message}\n", color)
|
||
|
|
self.terminal_output.see(tk.END)
|
||
|
|
self.root.update() # !/usr/bin/env python3
|
||
|
|
|
||
|
|
|
||
|
|
"""
|
||
|
|
WireGuard Server Setup GUI for Windows 11
|
||
|
|
Requires WireGuard to be installed from https://www.wireguard.com/install/
|
||
|
|
Uses only Python standard library - no additional packages needed
|
||
|
|
|
||
|
|
Features:
|
||
|
|
- Auto-detect network settings and WireGuard installation
|
||
|
|
- Generate server and client configurations
|
||
|
|
- Export client packages with OS-specific installation scripts
|
||
|
|
- Support for Windows, Linux, macOS, Android, and iOS clients
|
||
|
|
"""
|
||
|
|
|
||
|
|
import tkinter as tk
|
||
|
|
from tkinter import ttk, scrolledtext, messagebox, filedialog
|
||
|
|
import subprocess
|
||
|
|
import os
|
||
|
|
import random
|
||
|
|
import ipaddress
|
||
|
|
import json
|
||
|
|
import winreg
|
||
|
|
import socket
|
||
|
|
import urllib.request
|
||
|
|
import zipfile
|
||
|
|
import shutil
|
||
|
|
from datetime import datetime
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
|
||
|
|
class WireGuardServerGUI:
|
||
|
|
def __init__(self, root):
|
||
|
|
self.root = root
|
||
|
|
self.root.title("WireGuard Server Setup - Windows 11")
|
||
|
|
self.root.geometry("1200x800") # Increased size to show all options
|
||
|
|
self.root.minsize(1000, 700) # Set minimum size
|
||
|
|
|
||
|
|
# Center the window on screen
|
||
|
|
self.center_window()
|
||
|
|
|
||
|
|
# Check if running as admin
|
||
|
|
self.check_admin()
|
||
|
|
|
||
|
|
# Server configuration storage
|
||
|
|
self.server_config = {}
|
||
|
|
self.clients = []
|
||
|
|
|
||
|
|
# WireGuard path - try to auto-detect
|
||
|
|
self.wireguard_path = self.find_wireguard_installation()
|
||
|
|
|
||
|
|
# Check and setup WireGuard PATH
|
||
|
|
self.check_wireguard_path()
|
||
|
|
|
||
|
|
# Create UI
|
||
|
|
self.create_widgets()
|
||
|
|
|
||
|
|
# Initialize server status after widgets are created
|
||
|
|
self.update_server_status("not_configured")
|
||
|
|
|
||
|
|
# Welcome message in terminal (now that widgets exist)
|
||
|
|
self.terminal_write("WireGuard Server Setup GUI - Ready", "green")
|
||
|
|
self.terminal_write(f"WireGuard Path: {self.wireguard_path}", "cyan")
|
||
|
|
self.terminal_write("Run 'Auto-Detect Network' to configure network settings", "yellow")
|
||
|
|
|
||
|
|
# Auto-detect network settings after UI is ready
|
||
|
|
self.root.after(1000, self.auto_detect_network)
|
||
|
|
|
||
|
|
def check_admin(self):
|
||
|
|
"""Check if script is running with administrator privileges"""
|
||
|
|
try:
|
||
|
|
import ctypes
|
||
|
|
is_admin = ctypes.windll.shell32.IsUserAnAdmin()
|
||
|
|
if not is_admin:
|
||
|
|
messagebox.showwarning("Admin Required",
|
||
|
|
"This script requires administrator privileges.\n"
|
||
|
|
"Please run as administrator.")
|
||
|
|
except:
|
||
|
|
pass
|
||
|
|
|
||
|
|
def center_window(self):
|
||
|
|
"""Center the window on the screen"""
|
||
|
|
self.root.update_idletasks()
|
||
|
|
width = self.root.winfo_width()
|
||
|
|
height = self.root.winfo_height()
|
||
|
|
x = (self.root.winfo_screenwidth() // 2) - (width // 2)
|
||
|
|
y = (self.root.winfo_screenheight() // 2) - (height // 2)
|
||
|
|
self.root.geometry(f'{width}x{height}+{x}+{y}')
|
||
|
|
|
||
|
|
def find_wireguard_installation(self):
|
||
|
|
"""Try to find WireGuard installation path"""
|
||
|
|
possible_paths = [
|
||
|
|
r"C:\Program Files\WireGuard",
|
||
|
|
r"C:\Program Files (x86)\WireGuard",
|
||
|
|
r"D:\Program Files\WireGuard",
|
||
|
|
r"D:\Program Files (x86)\WireGuard",
|
||
|
|
]
|
||
|
|
|
||
|
|
# Check common installation paths
|
||
|
|
for path in possible_paths:
|
||
|
|
if os.path.exists(os.path.join(path, "wg.exe")):
|
||
|
|
print(f"[INFO] Found WireGuard at: {path}")
|
||
|
|
return path
|
||
|
|
|
||
|
|
# Check if wg is in PATH already
|
||
|
|
try:
|
||
|
|
result = subprocess.run("where wg", capture_output=True, text=True, shell=True)
|
||
|
|
if result.returncode == 0:
|
||
|
|
wg_location = result.stdout.strip().split('\n')[0]
|
||
|
|
if wg_location:
|
||
|
|
path = os.path.dirname(wg_location)
|
||
|
|
print(f"[INFO] Found WireGuard in PATH at: {path}")
|
||
|
|
return path
|
||
|
|
except:
|
||
|
|
pass
|
||
|
|
|
||
|
|
# Default to standard location
|
||
|
|
return r"C:\Program Files\WireGuard"
|
||
|
|
|
||
|
|
def check_wireguard_path(self):
|
||
|
|
"""Check if WireGuard is in PATH and add it if not"""
|
||
|
|
# Try to run wg command
|
||
|
|
try:
|
||
|
|
result = subprocess.run("wg version", shell=True, capture_output=True, text=True, timeout=2)
|
||
|
|
if result.returncode == 0:
|
||
|
|
return # WireGuard is already in PATH
|
||
|
|
except:
|
||
|
|
pass
|
||
|
|
|
||
|
|
# Check if WireGuard exists in default location
|
||
|
|
wireguard_path = self.wireguard_path
|
||
|
|
wg_exe = os.path.join(wireguard_path, "wg.exe")
|
||
|
|
wireguard_exe = os.path.join(wireguard_path, "wireguard.exe")
|
||
|
|
|
||
|
|
if not os.path.exists(wg_exe) or not os.path.exists(wireguard_exe):
|
||
|
|
messagebox.showwarning("WireGuard Not Found",
|
||
|
|
f"WireGuard not found in {wireguard_path}\n"
|
||
|
|
"Please install WireGuard from https://www.wireguard.com/install/\n"
|
||
|
|
"or set the correct path in the GUI.")
|
||
|
|
return
|
||
|
|
|
||
|
|
# Add to PATH for current session
|
||
|
|
current_path = os.environ.get('PATH', '')
|
||
|
|
if wireguard_path not in current_path:
|
||
|
|
os.environ['PATH'] = f"{wireguard_path};{current_path}"
|
||
|
|
|
||
|
|
# Also try to add to system PATH permanently (requires admin)
|
||
|
|
try:
|
||
|
|
import winreg
|
||
|
|
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,
|
||
|
|
r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment",
|
||
|
|
0, winreg.KEY_READ | winreg.KEY_WRITE) as key:
|
||
|
|
|
||
|
|
current_system_path, _ = winreg.QueryValueEx(key, "Path")
|
||
|
|
|
||
|
|
if wireguard_path not in current_system_path:
|
||
|
|
new_path = f"{current_system_path};{wireguard_path}"
|
||
|
|
winreg.SetValueEx(key, "Path", 0, winreg.REG_EXPAND_SZ, new_path)
|
||
|
|
|
||
|
|
# Broadcast WM_SETTINGCHANGE to notify other processes
|
||
|
|
import ctypes
|
||
|
|
HWND_BROADCAST = 0xFFFF
|
||
|
|
WM_SETTINGCHANGE = 0x001A
|
||
|
|
ctypes.windll.user32.SendMessageW(HWND_BROADCAST, WM_SETTINGCHANGE, 0, "Environment")
|
||
|
|
|
||
|
|
messagebox.showinfo("PATH Updated",
|
||
|
|
f"WireGuard has been added to system PATH:\n{wireguard_path}")
|
||
|
|
except Exception as e:
|
||
|
|
# If we can't update system PATH, at least we have it for current session
|
||
|
|
print(f"Could not update system PATH: {e}")
|
||
|
|
|
||
|
|
def browse_wireguard_path(self):
|
||
|
|
"""Browse for WireGuard installation directory"""
|
||
|
|
directory = filedialog.askdirectory(
|
||
|
|
title="Select WireGuard Installation Directory",
|
||
|
|
initialdir=self.wg_path_var.get()
|
||
|
|
)
|
||
|
|
if directory:
|
||
|
|
self.wg_path_var.set(directory)
|
||
|
|
self.wireguard_path = directory
|
||
|
|
self.log(f"WireGuard path set to: {directory}")
|
||
|
|
# Update PATH with new location
|
||
|
|
current_path = os.environ.get('PATH', '')
|
||
|
|
if directory not in current_path:
|
||
|
|
os.environ['PATH'] = f"{directory};{current_path}"
|
||
|
|
self.verify_wireguard_installation()
|
||
|
|
|
||
|
|
def verify_wireguard_installation(self):
|
||
|
|
"""Verify WireGuard is properly installed at the specified path"""
|
||
|
|
wg_path = self.wg_path_var.get()
|
||
|
|
wg_exe = os.path.join(wg_path, "wg.exe")
|
||
|
|
wireguard_exe = os.path.join(wg_path, "wireguard.exe")
|
||
|
|
|
||
|
|
self.terminal_write("Verifying WireGuard installation...", "cyan")
|
||
|
|
self.terminal_write(f" Path: {wg_path}", "cyan")
|
||
|
|
|
||
|
|
if os.path.exists(wg_exe) and os.path.exists(wireguard_exe):
|
||
|
|
# Try to get version
|
||
|
|
try:
|
||
|
|
result = subprocess.run(f'"{wg_exe}" version', capture_output=True, text=True, shell=True)
|
||
|
|
if result.returncode == 0:
|
||
|
|
version = result.stdout.strip()
|
||
|
|
self.log(f"WireGuard verified: {version}")
|
||
|
|
self.terminal_write(f" ✓ WireGuard found: {version}", "green")
|
||
|
|
self.terminal_write(f" ✓ wg.exe: {wg_exe}", "green")
|
||
|
|
self.terminal_write(f" ✓ wireguard.exe: {wireguard_exe}", "green")
|
||
|
|
messagebox.showinfo("Verification Successful",
|
||
|
|
f"WireGuard found and working!\n{version}")
|
||
|
|
return True
|
||
|
|
except Exception as e:
|
||
|
|
self.log(f"Error verifying WireGuard: {e}")
|
||
|
|
self.terminal_write(f" ✗ Error: {e}", "red")
|
||
|
|
|
||
|
|
self.log(f"WireGuard not found at: {wg_path}")
|
||
|
|
self.terminal_write(f" ✗ WireGuard not found at: {wg_path}", "red")
|
||
|
|
self.terminal_write(" Please ensure WireGuard is installed correctly", "yellow")
|
||
|
|
messagebox.showerror("Verification Failed",
|
||
|
|
f"WireGuard not found at:\n{wg_path}\n\n"
|
||
|
|
"Please ensure:\n"
|
||
|
|
"1. WireGuard is installed\n"
|
||
|
|
"2. The path is correct\n"
|
||
|
|
"3. wg.exe and wireguard.exe exist in the directory")
|
||
|
|
return False
|
||
|
|
|
||
|
|
def get_local_ip(self):
|
||
|
|
"""Get the local IP address of the machine"""
|
||
|
|
try:
|
||
|
|
# Create a socket to external address (doesn't actually connect)
|
||
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||
|
|
s.connect(("8.8.8.8", 80))
|
||
|
|
local_ip = s.getsockname()[0]
|
||
|
|
s.close()
|
||
|
|
return local_ip
|
||
|
|
except:
|
||
|
|
return None
|
||
|
|
|
||
|
|
def get_public_ip(self):
|
||
|
|
"""Get the public IP address"""
|
||
|
|
try:
|
||
|
|
# Try multiple services for redundancy
|
||
|
|
services = [
|
||
|
|
'https://api.ipify.org',
|
||
|
|
'https://ipinfo.io/ip',
|
||
|
|
'https://icanhazip.com',
|
||
|
|
'https://ident.me'
|
||
|
|
]
|
||
|
|
|
||
|
|
for service in services:
|
||
|
|
try:
|
||
|
|
with urllib.request.urlopen(service, timeout=5) as response:
|
||
|
|
public_ip = response.read().decode('utf8').strip()
|
||
|
|
# Validate it's an IP
|
||
|
|
socket.inet_aton(public_ip)
|
||
|
|
return public_ip
|
||
|
|
except:
|
||
|
|
continue
|
||
|
|
return None
|
||
|
|
except:
|
||
|
|
return None
|
||
|
|
|
||
|
|
def get_default_gateway(self):
|
||
|
|
"""Get the default gateway address"""
|
||
|
|
try:
|
||
|
|
# Method 1: Use PowerShell (most reliable on Windows)
|
||
|
|
ps_cmd = "(Get-NetRoute -DestinationPrefix '0.0.0.0/0' | Select-Object -First 1).NextHop"
|
||
|
|
result = subprocess.run(["powershell", "-Command", ps_cmd],
|
||
|
|
capture_output=True, text=True)
|
||
|
|
if result.returncode == 0:
|
||
|
|
gateway = result.stdout.strip()
|
||
|
|
if gateway and gateway != '0.0.0.0':
|
||
|
|
try:
|
||
|
|
socket.inet_aton(gateway)
|
||
|
|
return gateway
|
||
|
|
except:
|
||
|
|
pass
|
||
|
|
|
||
|
|
# Method 2: Parse ipconfig output
|
||
|
|
result = subprocess.run("ipconfig", capture_output=True, text=True, shell=True)
|
||
|
|
if result.returncode == 0:
|
||
|
|
lines = result.stdout.split('\n')
|
||
|
|
for line in lines:
|
||
|
|
if 'Default Gateway' in line:
|
||
|
|
# Extract IP address from the line
|
||
|
|
# Format is usually "Default Gateway . . . . . . : 192.168.1.1"
|
||
|
|
parts = line.split(':')
|
||
|
|
if len(parts) > 1:
|
||
|
|
gateway = parts[1].strip()
|
||
|
|
if gateway and gateway != '':
|
||
|
|
# Validate it's an IP
|
||
|
|
try:
|
||
|
|
socket.inet_aton(gateway)
|
||
|
|
return gateway
|
||
|
|
except:
|
||
|
|
pass
|
||
|
|
|
||
|
|
# Method 3: Use route print
|
||
|
|
result = subprocess.run("route print 0.0.0.0", capture_output=True, text=True, shell=True)
|
||
|
|
if result.returncode == 0:
|
||
|
|
lines = result.stdout.split('\n')
|
||
|
|
for line in lines:
|
||
|
|
if '0.0.0.0' in line and 'On-link' not in line:
|
||
|
|
parts = line.split()
|
||
|
|
for part in parts:
|
||
|
|
# Look for IP-like strings
|
||
|
|
if '.' in part and part.count('.') == 3:
|
||
|
|
try:
|
||
|
|
socket.inet_aton(part)
|
||
|
|
if part != '0.0.0.0' and not part.startswith('127.'):
|
||
|
|
return part
|
||
|
|
except:
|
||
|
|
pass
|
||
|
|
return None
|
||
|
|
except Exception as e:
|
||
|
|
self.log(f"Error detecting gateway: {str(e)}")
|
||
|
|
return None
|
||
|
|
|
||
|
|
def get_active_dns_servers(self):
|
||
|
|
"""Get currently active DNS servers"""
|
||
|
|
try:
|
||
|
|
# Get DNS servers from netsh
|
||
|
|
result = subprocess.run("netsh interface ip show dnsservers",
|
||
|
|
capture_output=True, text=True, shell=True)
|
||
|
|
if result.returncode == 0:
|
||
|
|
dns_servers = []
|
||
|
|
lines = result.stdout.split('\n')
|
||
|
|
for line in lines:
|
||
|
|
line = line.strip()
|
||
|
|
# Look for lines that contain IP addresses
|
||
|
|
parts = line.split()
|
||
|
|
for part in parts:
|
||
|
|
try:
|
||
|
|
socket.inet_aton(part)
|
||
|
|
if part not in dns_servers:
|
||
|
|
dns_servers.append(part)
|
||
|
|
except:
|
||
|
|
pass
|
||
|
|
|
||
|
|
if dns_servers:
|
||
|
|
return ', '.join(dns_servers[:2]) # Return first 2 DNS servers
|
||
|
|
|
||
|
|
# Default to common DNS if can't detect
|
||
|
|
return "8.8.8.8, 8.8.4.4"
|
||
|
|
except:
|
||
|
|
return "8.8.8.8, 8.8.4.4"
|
||
|
|
|
||
|
|
def check_port_availability(self):
|
||
|
|
"""Check if WireGuard default port is available"""
|
||
|
|
port = 51820
|
||
|
|
try:
|
||
|
|
# Try to bind to the port
|
||
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||
|
|
sock.bind(('', port))
|
||
|
|
sock.close()
|
||
|
|
return "51820"
|
||
|
|
except:
|
||
|
|
# Port in use, try alternatives
|
||
|
|
for alt_port in [51821, 51822, 51823, 51824, 51825]:
|
||
|
|
try:
|
||
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||
|
|
sock.bind(('', alt_port))
|
||
|
|
sock.close()
|
||
|
|
return str(alt_port)
|
||
|
|
except:
|
||
|
|
continue
|
||
|
|
return "51820" # Return default anyway
|
||
|
|
|
||
|
|
def auto_detect_network(self):
|
||
|
|
"""Auto-detect network settings"""
|
||
|
|
# Check if all required widgets exist
|
||
|
|
if not hasattr(self, 'local_ip_display'):
|
||
|
|
print("[DEBUG] Widgets not ready yet, postponing auto-detect")
|
||
|
|
self.root.after(1000, self.auto_detect_network)
|
||
|
|
return
|
||
|
|
|
||
|
|
self.update_status("Detecting network settings...")
|
||
|
|
self.log("Starting network auto-detection...")
|
||
|
|
self.terminal_write("=" * 50, "cyan")
|
||
|
|
self.terminal_write("Starting network auto-detection...", "cyan")
|
||
|
|
|
||
|
|
# Detect local IP
|
||
|
|
self.terminal_write("Detecting local IP address...", "cyan")
|
||
|
|
local_ip = self.get_local_ip()
|
||
|
|
if local_ip:
|
||
|
|
self.local_ip_display.config(state="normal")
|
||
|
|
self.local_ip_display.delete(0, tk.END)
|
||
|
|
self.local_ip_display.insert(0, local_ip)
|
||
|
|
self.local_ip_display.config(state="readonly")
|
||
|
|
self.log(f"Local IP detected: {local_ip}")
|
||
|
|
self.terminal_write(f" ✓ Local IP: {local_ip}", "green")
|
||
|
|
|
||
|
|
# Suggest VPN subnet based on local network
|
||
|
|
if local_ip.startswith("192.168."):
|
||
|
|
suggested_vpn = "10.0.0.1/24"
|
||
|
|
elif local_ip.startswith("10."):
|
||
|
|
suggested_vpn = "172.16.0.1/24"
|
||
|
|
else:
|
||
|
|
suggested_vpn = "10.0.0.1/24"
|
||
|
|
|
||
|
|
self.server_ip.delete(0, tk.END)
|
||
|
|
self.server_ip.insert(0, suggested_vpn)
|
||
|
|
self.log(f"Suggested VPN subnet: {suggested_vpn}")
|
||
|
|
self.terminal_write(f" ✓ Suggested VPN subnet: {suggested_vpn}", "green")
|
||
|
|
else:
|
||
|
|
self.log("Could not detect local IP")
|
||
|
|
self.terminal_write(" ✗ Could not detect local IP", "red")
|
||
|
|
|
||
|
|
# Detect public IP
|
||
|
|
self.log("Detecting public IP (this may take a moment)...")
|
||
|
|
self.terminal_write("Detecting public IP address...", "cyan")
|
||
|
|
public_ip = self.get_public_ip()
|
||
|
|
if public_ip:
|
||
|
|
self.public_ip_display.config(state="normal")
|
||
|
|
self.public_ip_display.delete(0, tk.END)
|
||
|
|
self.public_ip_display.insert(0, public_ip)
|
||
|
|
self.public_ip_display.config(state="readonly")
|
||
|
|
|
||
|
|
# Update the public endpoint field
|
||
|
|
self.public_endpoint.delete(0, tk.END)
|
||
|
|
self.public_endpoint.insert(0, public_ip)
|
||
|
|
|
||
|
|
self.log(f"Public IP detected: {public_ip}")
|
||
|
|
self.terminal_write(f" ✓ Public IP: {public_ip}", "green")
|
||
|
|
self.server_config['public_ip'] = public_ip
|
||
|
|
else:
|
||
|
|
self.log("Could not detect public IP - you may be offline or behind strict firewall")
|
||
|
|
self.terminal_write(" ✗ Could not detect public IP", "red")
|
||
|
|
self.terminal_write(" (You may be offline or behind strict firewall)", "yellow")
|
||
|
|
self.public_endpoint.delete(0, tk.END)
|
||
|
|
self.public_endpoint.insert(0, "MANUAL_ENTRY_REQUIRED")
|
||
|
|
|
||
|
|
# Detect default gateway
|
||
|
|
self.terminal_write("Detecting default gateway...", "cyan")
|
||
|
|
gateway = self.get_default_gateway()
|
||
|
|
if gateway:
|
||
|
|
self.gateway_display.config(state="normal")
|
||
|
|
self.gateway_display.delete(0, tk.END)
|
||
|
|
self.gateway_display.insert(0, gateway)
|
||
|
|
self.gateway_display.config(state="readonly")
|
||
|
|
self.log(f"Default gateway detected: {gateway}")
|
||
|
|
self.terminal_write(f" ✓ Gateway: {gateway}", "green")
|
||
|
|
else:
|
||
|
|
self.log("Could not detect default gateway")
|
||
|
|
self.terminal_write(" ✗ Could not detect default gateway", "red")
|
||
|
|
|
||
|
|
# Detect DNS servers
|
||
|
|
self.terminal_write("Detecting DNS servers...", "cyan")
|
||
|
|
dns_servers = self.get_active_dns_servers()
|
||
|
|
self.dns_servers.delete(0, tk.END)
|
||
|
|
self.dns_servers.insert(0, dns_servers)
|
||
|
|
self.log(f"DNS servers detected: {dns_servers}")
|
||
|
|
self.terminal_write(f" ✓ DNS: {dns_servers}", "green")
|
||
|
|
|
||
|
|
# Detect available port (check if default 51820 is free)
|
||
|
|
self.terminal_write("Checking port availability...", "cyan")
|
||
|
|
port = self.check_port_availability()
|
||
|
|
if port != "51820":
|
||
|
|
self.listen_port.delete(0, tk.END)
|
||
|
|
self.listen_port.insert(0, port)
|
||
|
|
self.log(f"Port {port} selected (51820 was in use)")
|
||
|
|
self.terminal_write(f" ✓ Port {port} selected (51820 was in use)", "yellow")
|
||
|
|
else:
|
||
|
|
self.log(f"Default port 51820 is available")
|
||
|
|
self.terminal_write(f" ✓ Port 51820 is available", "green")
|
||
|
|
|
||
|
|
self.terminal_write("=" * 50, "cyan")
|
||
|
|
self.terminal_write("Network auto-detection completed successfully", "green")
|
||
|
|
self.update_status("Network detection complete")
|
||
|
|
self.log("Network auto-detection completed successfully")
|
||
|
|
|
||
|
|
messagebox.showinfo("Network Detection Complete",
|
||
|
|
f"Detected Settings:\n"
|
||
|
|
f"Local IP: {local_ip or 'Not detected'}\n"
|
||
|
|
f"Public IP: {public_ip or 'Not detected'}\n"
|
||
|
|
f"Gateway: {gateway or 'Not detected'}\n"
|
||
|
|
f"DNS: {dns_servers}\n"
|
||
|
|
f"Port: {port}")
|
||
|
|
|
||
|
|
def create_widgets(self):
|
||
|
|
"""Create the GUI elements"""
|
||
|
|
|
||
|
|
# Main notebook for tabs
|
||
|
|
notebook = ttk.Notebook(self.root)
|
||
|
|
notebook.pack(fill="both", expand=True, padx=10, pady=10)
|
||
|
|
|
||
|
|
# Server Setup Tab
|
||
|
|
server_frame = ttk.Frame(notebook)
|
||
|
|
notebook.add(server_frame, text="Server Setup")
|
||
|
|
|
||
|
|
# Create main container with left and right sections
|
||
|
|
main_container = ttk.Frame(server_frame)
|
||
|
|
main_container.pack(fill="both", expand=True, padx=10, pady=10)
|
||
|
|
|
||
|
|
# Left side - Configuration
|
||
|
|
left_frame = ttk.Frame(main_container)
|
||
|
|
left_frame.pack(side="left", fill="both", expand=True, padx=(0, 10))
|
||
|
|
|
||
|
|
# Right side - Controls and Status
|
||
|
|
right_frame = ttk.Frame(main_container)
|
||
|
|
right_frame.pack(side="right", fill="y", padx=(0, 10))
|
||
|
|
|
||
|
|
# WireGuard Path Configuration (Left side)
|
||
|
|
path_frame = ttk.LabelFrame(left_frame, text="WireGuard Installation", padding=10)
|
||
|
|
path_frame.pack(fill="x", pady=(0, 10))
|
||
|
|
|
||
|
|
ttk.Label(path_frame, text="WireGuard Path:").grid(row=0, column=0, sticky="w", pady=5)
|
||
|
|
self.wg_path_var = tk.StringVar(value=self.wireguard_path)
|
||
|
|
self.wg_path_entry = ttk.Entry(path_frame, textvariable=self.wg_path_var, width=40)
|
||
|
|
self.wg_path_entry.grid(row=0, column=1, pady=5, padx=5)
|
||
|
|
|
||
|
|
self.browse_btn = ttk.Button(path_frame, text="Browse", command=self.browse_wireguard_path)
|
||
|
|
self.browse_btn.grid(row=0, column=2, pady=5, padx=5)
|
||
|
|
|
||
|
|
self.verify_btn = ttk.Button(path_frame, text="Verify Installation", command=self.verify_wireguard_installation)
|
||
|
|
self.verify_btn.grid(row=1, column=1, pady=5)
|
||
|
|
|
||
|
|
# Server Configuration (Left side)
|
||
|
|
config_frame = ttk.LabelFrame(left_frame, text="Server Configuration", padding=10)
|
||
|
|
config_frame.pack(fill="x", pady=(0, 10))
|
||
|
|
|
||
|
|
# Interface Name
|
||
|
|
ttk.Label(config_frame, text="Interface Name:").grid(row=0, column=0, sticky="w", pady=5)
|
||
|
|
self.interface_name = ttk.Entry(config_frame, width=30)
|
||
|
|
self.interface_name.insert(0, "wg_server")
|
||
|
|
self.interface_name.grid(row=0, column=1, pady=5)
|
||
|
|
|
||
|
|
# Server IP Address
|
||
|
|
ttk.Label(config_frame, text="VPN Network (CIDR):").grid(row=1, column=0, sticky="w", pady=5)
|
||
|
|
self.server_ip = ttk.Entry(config_frame, width=30)
|
||
|
|
self.server_ip.insert(0, "10.0.0.1/24")
|
||
|
|
self.server_ip.grid(row=1, column=1, pady=5)
|
||
|
|
|
||
|
|
# Add help text for VPN network field
|
||
|
|
vpn_help = ttk.Label(config_frame, text="Internal VPN subnet (e.g., 10.0.0.0/24), not your public IP",
|
||
|
|
font=('TkDefaultFont', 8), foreground='gray')
|
||
|
|
vpn_help.grid(row=2, column=1, sticky="w")
|
||
|
|
|
||
|
|
# Listen Port
|
||
|
|
ttk.Label(config_frame, text="Listen Port:").grid(row=3, column=0, sticky="w", pady=5)
|
||
|
|
self.listen_port = ttk.Entry(config_frame, width=30)
|
||
|
|
self.listen_port.insert(0, "51820")
|
||
|
|
self.listen_port.grid(row=3, column=1, pady=5)
|
||
|
|
|
||
|
|
# DNS Servers
|
||
|
|
ttk.Label(config_frame, text="DNS Servers:").grid(row=4, column=0, sticky="w", pady=5)
|
||
|
|
self.dns_servers = ttk.Entry(config_frame, width=30)
|
||
|
|
self.dns_servers.insert(0, "8.8.8.8, 8.8.4.4")
|
||
|
|
self.dns_servers.grid(row=4, column=1, pady=5)
|
||
|
|
|
||
|
|
# Public Endpoint (for clients)
|
||
|
|
ttk.Label(config_frame, text="Public IP/Domain:").grid(row=5, column=0, sticky="w", pady=5)
|
||
|
|
self.public_endpoint = ttk.Entry(config_frame, width=30)
|
||
|
|
self.public_endpoint.insert(0, "Auto-detect required")
|
||
|
|
self.public_endpoint.grid(row=5, column=1, pady=5)
|
||
|
|
|
||
|
|
# Keys Display (Left side)
|
||
|
|
keys_frame = ttk.LabelFrame(left_frame, text="Server Keys", padding=10)
|
||
|
|
keys_frame.pack(fill="x", pady=(0, 10))
|
||
|
|
|
||
|
|
ttk.Label(keys_frame, text="Private Key:").grid(row=0, column=0, sticky="w")
|
||
|
|
self.private_key_display = ttk.Entry(keys_frame, width=50, state="readonly")
|
||
|
|
self.private_key_display.grid(row=0, column=1, padx=5)
|
||
|
|
|
||
|
|
ttk.Label(keys_frame, text="Public Key:").grid(row=1, column=0, sticky="w")
|
||
|
|
self.public_key_display = ttk.Entry(keys_frame, width=50, state="readonly")
|
||
|
|
self.public_key_display.grid(row=1, column=1, padx=5)
|
||
|
|
|
||
|
|
# Network Info Display (Left side)
|
||
|
|
network_frame = ttk.LabelFrame(left_frame, text="Detected Network Information", padding=10)
|
||
|
|
network_frame.pack(fill="x", pady=(0, 10))
|
||
|
|
|
||
|
|
ttk.Label(network_frame, text="Local LAN IP:").grid(row=0, column=0, sticky="w")
|
||
|
|
self.local_ip_display = ttk.Entry(network_frame, width=20, state="readonly")
|
||
|
|
self.local_ip_display.grid(row=0, column=1, padx=5)
|
||
|
|
local_help = ttk.Label(network_frame, text="Your computer's IP on local network",
|
||
|
|
font=('TkDefaultFont', 8), foreground='gray')
|
||
|
|
local_help.grid(row=0, column=2, sticky="w", padx=5)
|
||
|
|
|
||
|
|
ttk.Label(network_frame, text="Public Internet IP:").grid(row=1, column=0, sticky="w")
|
||
|
|
self.public_ip_display = ttk.Entry(network_frame, width=20, state="readonly")
|
||
|
|
self.public_ip_display.grid(row=1, column=1, padx=5)
|
||
|
|
public_help = ttk.Label(network_frame, text="Your internet-facing IP (for clients)",
|
||
|
|
font=('TkDefaultFont', 8), foreground='gray')
|
||
|
|
public_help.grid(row=1, column=2, sticky="w", padx=5)
|
||
|
|
|
||
|
|
ttk.Label(network_frame, text="Default Gateway:").grid(row=2, column=0, sticky="w")
|
||
|
|
self.gateway_display = ttk.Entry(network_frame, width=20, state="readonly")
|
||
|
|
self.gateway_display.grid(row=2, column=1, padx=5)
|
||
|
|
gateway_help = ttk.Label(network_frame, text="Your router's IP address",
|
||
|
|
font=('TkDefaultFont', 8), foreground='gray')
|
||
|
|
gateway_help.grid(row=2, column=2, sticky="w", padx=5)
|
||
|
|
|
||
|
|
# Server Controls (Right side)
|
||
|
|
control_frame = ttk.LabelFrame(right_frame, text="Server Controls", padding=10)
|
||
|
|
control_frame.pack(fill="x", pady=(0, 10))
|
||
|
|
|
||
|
|
self.gen_keys_btn = ttk.Button(control_frame, text="Generate Server Keys",
|
||
|
|
command=self.generate_server_keys, width=20)
|
||
|
|
self.gen_keys_btn.pack(pady=5)
|
||
|
|
|
||
|
|
self.detect_btn = ttk.Button(control_frame, text="Auto-Detect Network",
|
||
|
|
command=self.auto_detect_network, width=20)
|
||
|
|
self.detect_btn.pack(pady=5)
|
||
|
|
|
||
|
|
self.setup_btn = ttk.Button(control_frame, text="Setup WireGuard Server",
|
||
|
|
command=self.setup_server, state="disabled", width=20)
|
||
|
|
self.setup_btn.pack(pady=5)
|
||
|
|
|
||
|
|
self.start_btn = ttk.Button(control_frame, text="▶ Start Server",
|
||
|
|
command=self.start_server, state="disabled", width=20)
|
||
|
|
self.start_btn.pack(pady=5)
|
||
|
|
|
||
|
|
self.stop_btn = ttk.Button(control_frame, text="■ Stop Server",
|
||
|
|
command=self.stop_server, state="disabled", width=20)
|
||
|
|
self.stop_btn.pack(pady=5)
|
||
|
|
|
||
|
|
# Server Status (Right side)
|
||
|
|
status_frame = ttk.LabelFrame(right_frame, text="Server Status", padding=10)
|
||
|
|
status_frame.pack(fill="x", pady=(0, 10))
|
||
|
|
|
||
|
|
# Status indicator
|
||
|
|
self.status_indicator = ttk.Label(status_frame, text="⬤", font=("Arial", 20))
|
||
|
|
self.status_indicator.pack()
|
||
|
|
|
||
|
|
self.status_text = ttk.Label(status_frame, text="NOT CONFIGURED",
|
||
|
|
font=("Arial", 10, "bold"))
|
||
|
|
self.status_text.pack()
|
||
|
|
|
||
|
|
self.status_details = ttk.Label(status_frame, text="", font=("Arial", 8))
|
||
|
|
self.status_details.pack()
|
||
|
|
|
||
|
|
# Refresh status button
|
||
|
|
self.refresh_status_btn = ttk.Button(status_frame, text="↻ Refresh Status",
|
||
|
|
command=self.refresh_server_status, width=15)
|
||
|
|
self.refresh_status_btn.pack(pady=5)
|
||
|
|
|
||
|
|
# Terminal Output (Bottom)
|
||
|
|
terminal_frame = ttk.LabelFrame(server_frame, text="Terminal Output", padding=10)
|
||
|
|
terminal_frame.pack(fill="both", expand=True, padx=10, pady=(0, 10))
|
||
|
|
|
||
|
|
# Terminal output with black background
|
||
|
|
self.terminal_output = tk.Text(terminal_frame, height=8, width=80,
|
||
|
|
bg="black", fg="green",
|
||
|
|
font=("Consolas", 9),
|
||
|
|
insertbackground="green")
|
||
|
|
self.terminal_output.pack(fill="both", expand=True, side="left")
|
||
|
|
|
||
|
|
# Configure color tags for terminal
|
||
|
|
self.terminal_output.tag_config("green", foreground="#00ff00")
|
||
|
|
self.terminal_output.tag_config("red", foreground="#ff4444")
|
||
|
|
self.terminal_output.tag_config("yellow", foreground="#ffff00")
|
||
|
|
self.terminal_output.tag_config("cyan", foreground="#00ffff")
|
||
|
|
self.terminal_output.tag_config("white", foreground="#ffffff")
|
||
|
|
self.terminal_output.tag_config("orange", foreground="#ff9900")
|
||
|
|
|
||
|
|
# Scrollbar for terminal
|
||
|
|
terminal_scroll = ttk.Scrollbar(terminal_frame, command=self.terminal_output.yview)
|
||
|
|
terminal_scroll.pack(side="right", fill="y")
|
||
|
|
self.terminal_output.config(yscrollcommand=terminal_scroll.set)
|
||
|
|
|
||
|
|
# Client Management Tab
|
||
|
|
client_frame = ttk.Frame(notebook)
|
||
|
|
notebook.add(client_frame, text="Client Management")
|
||
|
|
|
||
|
|
# Add Client Section
|
||
|
|
add_client_frame = ttk.LabelFrame(client_frame, text="Add New Client", padding=10)
|
||
|
|
add_client_frame.pack(fill="x", padx=10, pady=10)
|
||
|
|
|
||
|
|
ttk.Label(add_client_frame, text="Client Name:").grid(row=0, column=0, sticky="w", pady=5)
|
||
|
|
self.client_name = ttk.Entry(add_client_frame, width=30)
|
||
|
|
self.client_name.grid(row=0, column=1, pady=5)
|
||
|
|
|
||
|
|
ttk.Label(add_client_frame, text="VPN IP (Internal):").grid(row=1, column=0, sticky="w", pady=5)
|
||
|
|
self.client_ip = ttk.Entry(add_client_frame, width=30)
|
||
|
|
self.client_ip.insert(0, "10.0.0.2/32")
|
||
|
|
self.client_ip.grid(row=1, column=1, pady=5)
|
||
|
|
|
||
|
|
# Add help text for IP field
|
||
|
|
help_text = ttk.Label(add_client_frame,
|
||
|
|
text="This is the client's internal VPN IP, not their physical network IP",
|
||
|
|
font=('TkDefaultFont', 8), foreground='gray')
|
||
|
|
help_text.grid(row=2, column=1, sticky="w")
|
||
|
|
|
||
|
|
self.add_client_btn = ttk.Button(add_client_frame, text="Generate Client Config",
|
||
|
|
command=self.generate_client_config, state="disabled")
|
||
|
|
self.add_client_btn.grid(row=3, column=0, columnspan=2, pady=10)
|
||
|
|
|
||
|
|
# Client List
|
||
|
|
list_frame = ttk.LabelFrame(client_frame, text="Client Configurations", padding=10)
|
||
|
|
list_frame.pack(fill="both", expand=True, padx=10, pady=10)
|
||
|
|
|
||
|
|
self.client_list = scrolledtext.ScrolledText(list_frame, height=8, width=80)
|
||
|
|
self.client_list.pack(fill="both", expand=True)
|
||
|
|
|
||
|
|
# Export Client Section
|
||
|
|
export_frame = ttk.LabelFrame(client_frame, text="Export Client Setup", padding=10)
|
||
|
|
export_frame.pack(fill="x", padx=10, pady=10)
|
||
|
|
|
||
|
|
ttk.Label(export_frame, text="Select Client:").grid(row=0, column=0, sticky="w", pady=5)
|
||
|
|
self.export_client_combo = ttk.Combobox(export_frame, width=30, state="readonly")
|
||
|
|
self.export_client_combo.grid(row=0, column=1, pady=5, padx=5)
|
||
|
|
|
||
|
|
ttk.Label(export_frame, text="Client OS:").grid(row=1, column=0, sticky="w", pady=5)
|
||
|
|
self.client_os = ttk.Combobox(export_frame, width=30, state="readonly")
|
||
|
|
self.client_os['values'] = ('Windows', 'Ubuntu/Debian', 'Arch Linux', 'macOS', 'Android', 'iOS')
|
||
|
|
self.client_os.set('Windows')
|
||
|
|
self.client_os.grid(row=1, column=1, pady=5, padx=5)
|
||
|
|
|
||
|
|
self.export_client_btn = ttk.Button(export_frame, text="Export Client Package",
|
||
|
|
command=self.export_client_package, state="disabled")
|
||
|
|
self.export_client_btn.grid(row=2, column=0, columnspan=2, pady=10)
|
||
|
|
|
||
|
|
# Logs Tab
|
||
|
|
log_frame = ttk.Frame(notebook)
|
||
|
|
notebook.add(log_frame, text="Logs")
|
||
|
|
|
||
|
|
self.log_text = scrolledtext.ScrolledText(log_frame, height=20, width=80)
|
||
|
|
self.log_text.pack(fill="both", expand=True, padx=10, pady=10)
|
||
|
|
|
||
|
|
# Status Bar
|
||
|
|
self.status_bar = ttk.Label(self.root, text="Ready", relief=tk.SUNKEN, anchor=tk.W)
|
||
|
|
self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
|
||
|
|
|
||
|
|
def log(self, message):
|
||
|
|
"""Add message to log display"""
|
||
|
|
# Check if log_text exists before trying to use it
|
||
|
|
if hasattr(self, 'log_text'):
|
||
|
|
self.log_text.insert(tk.END, f"{message}\n")
|
||
|
|
self.log_text.see(tk.END)
|
||
|
|
self.root.update()
|
||
|
|
print(f"[LOG] {message}") # Also print to console for debugging
|
||
|
|
|
||
|
|
def terminal_write(self, message, color="green"):
|
||
|
|
"""Write directly to terminal output with optional color"""
|
||
|
|
try:
|
||
|
|
timestamp = datetime.now().strftime("%H:%M:%S")
|
||
|
|
if hasattr(self, 'terminal_output'):
|
||
|
|
# Ensure the color tag exists
|
||
|
|
if color not in self.terminal_output.tag_names():
|
||
|
|
self.terminal_output.tag_config(color, foreground=color)
|
||
|
|
self.terminal_output.insert(tk.END, f"[{timestamp}] {message}\n", color)
|
||
|
|
self.terminal_output.see(tk.END)
|
||
|
|
if hasattr(self, 'root'):
|
||
|
|
self.root.update()
|
||
|
|
else:
|
||
|
|
# Fallback to console if terminal widget isn't ready yet
|
||
|
|
print(f"[{timestamp}] {message}")
|
||
|
|
except Exception as e:
|
||
|
|
print(f"[terminal_write error] {e}; msg={message}")
|
||
|
|
|
||
|
|
def update_status(self, message):
|
||
|
|
"""Update status bar"""
|
||
|
|
if hasattr(self, 'status_bar'):
|
||
|
|
self.status_bar.config(text=message)
|
||
|
|
self.root.update()
|
||
|
|
print(f"[STATUS] {message}") # Also print to console
|
||
|
|
|
||
|
|
def update_server_status(self, status="stopped", details=""):
|
||
|
|
"""Update the server status indicator"""
|
||
|
|
if not hasattr(self, 'status_indicator'):
|
||
|
|
return
|
||
|
|
|
||
|
|
if status == "running":
|
||
|
|
self.status_indicator.config(text="⬤", foreground="green")
|
||
|
|
self.status_text.config(text="RUNNING", foreground="green")
|
||
|
|
self.status_details.config(text=details or f"Interface: {self.server_config.get('interface', 'wg_server')}")
|
||
|
|
elif status == "stopped":
|
||
|
|
self.status_indicator.config(text="⬤", foreground="red")
|
||
|
|
self.status_text.config(text="STOPPED", foreground="red")
|
||
|
|
self.status_details.config(text=details or "Server is not running")
|
||
|
|
elif status == "configured":
|
||
|
|
self.status_indicator.config(text="⬤", foreground="orange")
|
||
|
|
self.status_text.config(text="CONFIGURED", foreground="orange")
|
||
|
|
self.status_details.config(text=details or "Ready to start")
|
||
|
|
elif status == "error":
|
||
|
|
self.status_indicator.config(text="⬤", foreground="red")
|
||
|
|
self.status_text.config(text="ERROR", foreground="red")
|
||
|
|
self.status_details.config(text=details or "Check terminal output")
|
||
|
|
else:
|
||
|
|
self.status_indicator.config(text="⬤", foreground="gray")
|
||
|
|
self.status_text.config(text="NOT CONFIGURED", foreground="gray")
|
||
|
|
self.status_details.config(text=details or "Setup required")
|
||
|
|
|
||
|
|
def refresh_server_status(self):
|
||
|
|
"""Refresh the server status by checking if WireGuard service is running"""
|
||
|
|
interface = self.server_config.get('interface')
|
||
|
|
|
||
|
|
if not interface:
|
||
|
|
self.update_server_status("not_configured", "No server configured")
|
||
|
|
self.terminal_write("No server configuration found", "yellow")
|
||
|
|
return
|
||
|
|
|
||
|
|
self.terminal_write(f"Checking status of interface {interface}...", "cyan")
|
||
|
|
|
||
|
|
# Check if the WireGuard service is running
|
||
|
|
wg_path = os.path.join(self.wg_path_var.get(), "wg.exe")
|
||
|
|
if os.path.exists(wg_path):
|
||
|
|
cmd = f'"{wg_path}" show {interface}'
|
||
|
|
success, output = self.run_command(cmd)
|
||
|
|
|
||
|
|
if success and output.strip():
|
||
|
|
# Parse output to get connection details
|
||
|
|
lines = output.strip().split('\n')
|
||
|
|
peer_count = len([l for l in lines if l.startswith('peer:')])
|
||
|
|
self.update_server_status("running", f"Active | {peer_count} peer(s) configured")
|
||
|
|
self.terminal_write(f"Server is running with {peer_count} configured peer(s)", "green")
|
||
|
|
|
||
|
|
# Show more details in terminal
|
||
|
|
for line in lines[:5]: # Show first 5 lines of output
|
||
|
|
if line.strip():
|
||
|
|
self.terminal_write(f" {line.strip()}", "cyan")
|
||
|
|
else:
|
||
|
|
self.update_server_status("stopped", "Service not active")
|
||
|
|
self.terminal_write("Server is not running", "yellow")
|
||
|
|
else:
|
||
|
|
self.update_server_status("error", "WireGuard not found")
|
||
|
|
self.terminal_write("Error: WireGuard executable not found", "red")
|
||
|
|
|
||
|
|
def run_command(self, command, shell=True):
|
||
|
|
"""Run a system command and return output"""
|
||
|
|
try:
|
||
|
|
self.log(f"Running command: {command}")
|
||
|
|
self.terminal_write(f"$ {command}", "yellow")
|
||
|
|
result = subprocess.run(command, shell=shell, capture_output=True, text=True)
|
||
|
|
if result.returncode == 0:
|
||
|
|
if result.stdout.strip():
|
||
|
|
self.terminal_write(f" Output: {result.stdout[:100]}", "cyan")
|
||
|
|
return True, result.stdout
|
||
|
|
else:
|
||
|
|
self.log(f"Command failed with error: {result.stderr}")
|
||
|
|
self.terminal_write(f" Error: {result.stderr}", "red")
|
||
|
|
return False, result.stderr
|
||
|
|
except Exception as e:
|
||
|
|
self.log(f"Exception running command: {str(e)}")
|
||
|
|
self.terminal_write(f" Exception: {str(e)}", "red")
|
||
|
|
return False, str(e)
|
||
|
|
|
||
|
|
def generate_server_keys(self):
|
||
|
|
"""Generate WireGuard server keys"""
|
||
|
|
self.update_status("Generating server keys...")
|
||
|
|
self.log("Starting server key generation...")
|
||
|
|
self.terminal_write("Generating WireGuard server keys...", "cyan")
|
||
|
|
|
||
|
|
# Get the WireGuard path from the field
|
||
|
|
wg_path = os.path.join(self.wg_path_var.get(), "wg.exe")
|
||
|
|
if not os.path.exists(wg_path):
|
||
|
|
messagebox.showerror("Error",
|
||
|
|
f"WireGuard not found at: {wg_path}\n"
|
||
|
|
"Please set the correct path and verify installation.")
|
||
|
|
self.terminal_write(f"Error: wg.exe not found at {wg_path}", "red")
|
||
|
|
return
|
||
|
|
|
||
|
|
# Generate private key
|
||
|
|
try:
|
||
|
|
self.terminal_write(" Generating private key...", "cyan")
|
||
|
|
result = subprocess.run(f'"{wg_path}" genkey', capture_output=True, text=True, shell=True)
|
||
|
|
if result.returncode != 0:
|
||
|
|
self.log(f"Error generating private key: {result.stderr}")
|
||
|
|
self.terminal_write(f" ✗ Failed: {result.stderr}", "red")
|
||
|
|
messagebox.showerror("Error", f"Failed to generate private key: {result.stderr}")
|
||
|
|
return
|
||
|
|
private_key = result.stdout.strip()
|
||
|
|
self.terminal_write(" ✓ Private key generated", "green")
|
||
|
|
except Exception as e:
|
||
|
|
self.log(f"Exception generating private key: {str(e)}")
|
||
|
|
self.terminal_write(f" ✗ Exception: {str(e)}", "red")
|
||
|
|
messagebox.showerror("Error", f"Failed to generate private key: {str(e)}")
|
||
|
|
return
|
||
|
|
|
||
|
|
# Generate public key from private key
|
||
|
|
try:
|
||
|
|
self.terminal_write(" Generating public key...", "cyan")
|
||
|
|
# Use echo with pipe to pass private key to wg pubkey
|
||
|
|
cmd = f'echo {private_key} | "{wg_path}" pubkey'
|
||
|
|
result = subprocess.run(cmd, capture_output=True, text=True, shell=True)
|
||
|
|
if result.returncode != 0:
|
||
|
|
self.log(f"Error generating public key: {result.stderr}")
|
||
|
|
self.terminal_write(f" ✗ Failed: {result.stderr}", "red")
|
||
|
|
messagebox.showerror("Error", f"Failed to generate public key: {result.stderr}")
|
||
|
|
return
|
||
|
|
public_key = result.stdout.strip()
|
||
|
|
self.terminal_write(" ✓ Public key generated", "green")
|
||
|
|
except Exception as e:
|
||
|
|
self.log(f"Exception generating public key: {str(e)}")
|
||
|
|
self.terminal_write(f" ✗ Exception: {str(e)}", "red")
|
||
|
|
messagebox.showerror("Error", f"Failed to generate public key: {str(e)}")
|
||
|
|
return
|
||
|
|
|
||
|
|
# Store and display keys
|
||
|
|
self.server_config['private_key'] = private_key
|
||
|
|
self.server_config['public_key'] = public_key
|
||
|
|
|
||
|
|
self.private_key_display.config(state="normal")
|
||
|
|
self.private_key_display.delete(0, tk.END)
|
||
|
|
self.private_key_display.insert(0, private_key)
|
||
|
|
self.private_key_display.config(state="readonly")
|
||
|
|
|
||
|
|
self.public_key_display.config(state="normal")
|
||
|
|
self.public_key_display.delete(0, tk.END)
|
||
|
|
self.public_key_display.insert(0, public_key)
|
||
|
|
self.public_key_display.config(state="readonly")
|
||
|
|
|
||
|
|
self.log("Server keys generated successfully")
|
||
|
|
self.log(f"Private key: {private_key[:20]}...")
|
||
|
|
self.log(f"Public key: {public_key[:20]}...")
|
||
|
|
|
||
|
|
self.terminal_write("✓ Server keys generated successfully", "green")
|
||
|
|
self.terminal_write(f" Private key: {private_key[:20]}...", "cyan")
|
||
|
|
self.terminal_write(f" Public key: {public_key[:20]}...", "cyan")
|
||
|
|
|
||
|
|
self.setup_btn.config(state="normal")
|
||
|
|
self.update_status("Server keys generated")
|
||
|
|
|
||
|
|
def setup_server(self):
|
||
|
|
"""Setup WireGuard server configuration"""
|
||
|
|
self.update_status("Setting up WireGuard server...")
|
||
|
|
self.terminal_write("Starting server setup...", "cyan")
|
||
|
|
|
||
|
|
# Verify WireGuard is available at the set path
|
||
|
|
if not self.verify_wireguard_installation():
|
||
|
|
self.update_server_status("error", "WireGuard not installed")
|
||
|
|
return
|
||
|
|
|
||
|
|
# Get configuration values
|
||
|
|
interface = self.interface_name.get()
|
||
|
|
server_ip = self.server_ip.get()
|
||
|
|
port = self.listen_port.get()
|
||
|
|
private_key = self.server_config.get('private_key')
|
||
|
|
|
||
|
|
if not private_key:
|
||
|
|
messagebox.showerror("Error", "Please generate server keys first")
|
||
|
|
self.terminal_write("Error: Server keys not generated", "red")
|
||
|
|
return
|
||
|
|
|
||
|
|
# Create configuration directory (use WireGuard's Data directory if it exists)
|
||
|
|
wg_data_dir = os.path.join(self.wg_path_var.get(), "Data", "Configurations")
|
||
|
|
if os.path.exists(os.path.dirname(wg_data_dir)):
|
||
|
|
config_dir = Path(wg_data_dir)
|
||
|
|
else:
|
||
|
|
# Fallback to local directory
|
||
|
|
config_dir = Path("wireguard_configs")
|
||
|
|
|
||
|
|
config_dir.mkdir(parents=True, exist_ok=True)
|
||
|
|
|
||
|
|
# Create server configuration file
|
||
|
|
config_file = config_dir / f"{interface}.conf"
|
||
|
|
|
||
|
|
config_content = f"""# WireGuard Server Configuration
|
||
|
|
# Generated by WireGuard Setup GUI
|
||
|
|
|
||
|
|
[Interface]
|
||
|
|
# The server's private key for encryption
|
||
|
|
PrivateKey = {private_key}
|
||
|
|
|
||
|
|
# The server's IP address in the VPN network (not the public IP)
|
||
|
|
# This creates a virtual network separate from your LAN
|
||
|
|
Address = {server_ip}
|
||
|
|
|
||
|
|
# UDP port WireGuard listens on
|
||
|
|
ListenPort = {port}
|
||
|
|
|
||
|
|
# PostUp and PostDown rules can be added here for NAT/firewall
|
||
|
|
# PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
|
||
|
|
# PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
|
||
|
|
"""
|
||
|
|
|
||
|
|
try:
|
||
|
|
with open(config_file, 'w') as f:
|
||
|
|
f.write(config_content)
|
||
|
|
self.log(f"Configuration saved to {config_file}")
|
||
|
|
self.terminal_write(f"✓ Configuration saved: {config_file}", "green")
|
||
|
|
except Exception as e:
|
||
|
|
self.log(f"Error saving configuration: {e}")
|
||
|
|
self.terminal_write(f"✗ Error saving configuration: {e}", "red")
|
||
|
|
self.update_server_status("error", "Failed to save config")
|
||
|
|
return
|
||
|
|
|
||
|
|
# Setup Windows firewall rules
|
||
|
|
self.terminal_write("Configuring Windows Firewall...", "cyan")
|
||
|
|
self.setup_firewall_rules(port)
|
||
|
|
|
||
|
|
# Enable IP forwarding
|
||
|
|
self.terminal_write("Enabling IP forwarding...", "cyan")
|
||
|
|
self.enable_ip_forwarding()
|
||
|
|
|
||
|
|
self.server_config['interface'] = interface
|
||
|
|
self.server_config['config_file'] = str(config_file)
|
||
|
|
|
||
|
|
self.start_btn.config(state="normal")
|
||
|
|
self.add_client_btn.config(state="normal")
|
||
|
|
self.update_status("Server setup complete")
|
||
|
|
self.update_server_status("configured", "Ready to start")
|
||
|
|
self.terminal_write("✓ Server setup complete - Ready to start", "green")
|
||
|
|
|
||
|
|
def setup_firewall_rules(self, port):
|
||
|
|
"""Configure Windows firewall rules for WireGuard"""
|
||
|
|
self.log("Setting up firewall rules...")
|
||
|
|
self.terminal_write("Configuring Windows Firewall rules...", "cyan")
|
||
|
|
|
||
|
|
# Add inbound rule
|
||
|
|
cmd = f'netsh advfirewall firewall add rule name="WireGuard-In" dir=in action=allow protocol=UDP localport={port}'
|
||
|
|
success, output = self.run_command(cmd)
|
||
|
|
if success:
|
||
|
|
self.log("Inbound firewall rule added")
|
||
|
|
self.terminal_write(" ✓ Inbound firewall rule added", "green")
|
||
|
|
else:
|
||
|
|
self.log(f"Warning: Could not add inbound rule: {output}")
|
||
|
|
self.terminal_write(f" ⚠ Warning: Could not add inbound rule", "yellow")
|
||
|
|
|
||
|
|
# Add outbound rule
|
||
|
|
cmd = f'netsh advfirewall firewall add rule name="WireGuard-Out" dir=out action=allow protocol=UDP localport={port}'
|
||
|
|
success, output = self.run_command(cmd)
|
||
|
|
if success:
|
||
|
|
self.log("Outbound firewall rule added")
|
||
|
|
self.terminal_write(" ✓ Outbound firewall rule added", "green")
|
||
|
|
else:
|
||
|
|
self.log(f"Warning: Could not add outbound rule: {output}")
|
||
|
|
self.terminal_write(f" ⚠ Warning: Could not add outbound rule", "yellow")
|
||
|
|
|
||
|
|
def enable_ip_forwarding(self):
|
||
|
|
"""Enable IP forwarding on Windows"""
|
||
|
|
self.log("Enabling IP forwarding...")
|
||
|
|
self.terminal_write("Enabling IP forwarding...", "cyan")
|
||
|
|
|
||
|
|
cmd = 'reg add HKLM\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters /v IPEnableRouter /t REG_DWORD /d 1 /f'
|
||
|
|
success, output = self.run_command(cmd)
|
||
|
|
if success:
|
||
|
|
self.log("IP forwarding enabled")
|
||
|
|
self.terminal_write(" ✓ IP forwarding enabled in registry", "green")
|
||
|
|
else:
|
||
|
|
self.log(f"Warning: Could not enable IP forwarding: {output}")
|
||
|
|
self.terminal_write(f" ⚠ Warning: Could not enable IP forwarding", "yellow")
|
||
|
|
|
||
|
|
# Restart routing service
|
||
|
|
self.terminal_write(" Restarting routing service...", "cyan")
|
||
|
|
cmd = 'sc stop RemoteAccess & sc start RemoteAccess'
|
||
|
|
success, output = self.run_command(cmd)
|
||
|
|
if success:
|
||
|
|
self.terminal_write(" ✓ Routing service restarted", "green")
|
||
|
|
else:
|
||
|
|
self.terminal_write(" ⚠ Routing service may need manual restart", "yellow")
|
||
|
|
|
||
|
|
def start_server(self):
|
||
|
|
"""Start WireGuard server"""
|
||
|
|
interface = self.server_config.get('interface')
|
||
|
|
config_file = self.server_config.get('config_file')
|
||
|
|
|
||
|
|
if not interface or not config_file:
|
||
|
|
messagebox.showerror("Error", "Server not configured")
|
||
|
|
self.terminal_write("Error: Server not configured", "red")
|
||
|
|
return
|
||
|
|
|
||
|
|
self.update_status(f"Starting WireGuard server on {interface}...")
|
||
|
|
self.terminal_write(f"Starting WireGuard server on interface {interface}...", "cyan")
|
||
|
|
|
||
|
|
# Get WireGuard.exe path
|
||
|
|
wireguard_exe = os.path.join(self.wg_path_var.get(), "wireguard.exe")
|
||
|
|
if not os.path.exists(wireguard_exe):
|
||
|
|
messagebox.showerror("Error",
|
||
|
|
f"wireguard.exe not found at: {wireguard_exe}\n"
|
||
|
|
"Please set the correct path and verify installation.")
|
||
|
|
self.terminal_write(f"Error: wireguard.exe not found at {wireguard_exe}", "red")
|
||
|
|
self.update_server_status("error", "WireGuard not found")
|
||
|
|
return
|
||
|
|
|
||
|
|
# Install and start WireGuard tunnel
|
||
|
|
cmd = f'"{wireguard_exe}" /installtunnelservice "{config_file}"'
|
||
|
|
success, output = self.run_command(cmd)
|
||
|
|
|
||
|
|
if success:
|
||
|
|
self.log(f"WireGuard server started on interface {interface}")
|
||
|
|
self.terminal_write(f"✓ Server started successfully on {interface}", "green")
|
||
|
|
self.stop_btn.config(state="normal")
|
||
|
|
self.start_btn.config(state="disabled")
|
||
|
|
self.update_status("Server running")
|
||
|
|
self.update_server_status("running", f"Interface: {interface}")
|
||
|
|
|
||
|
|
# Show additional info
|
||
|
|
self.terminal_write(f" Port: {self.listen_port.get()}", "cyan")
|
||
|
|
self.terminal_write(f" VPN Network: {self.server_ip.get()}", "cyan")
|
||
|
|
self.terminal_write(f" Config: {config_file}", "cyan")
|
||
|
|
else:
|
||
|
|
self.log(f"Error starting server: {output}")
|
||
|
|
self.terminal_write(f"✗ Failed to start server: {output}", "red")
|
||
|
|
self.update_server_status("error", "Failed to start")
|
||
|
|
|
||
|
|
def stop_server(self):
|
||
|
|
"""Stop WireGuard server"""
|
||
|
|
interface = self.server_config.get('interface')
|
||
|
|
|
||
|
|
if not interface:
|
||
|
|
return
|
||
|
|
|
||
|
|
self.update_status(f"Stopping WireGuard server on {interface}...")
|
||
|
|
self.terminal_write(f"Stopping WireGuard server on {interface}...", "cyan")
|
||
|
|
|
||
|
|
# Get WireGuard.exe path
|
||
|
|
wireguard_exe = os.path.join(self.wg_path_var.get(), "wireguard.exe")
|
||
|
|
if not os.path.exists(wireguard_exe):
|
||
|
|
messagebox.showerror("Error",
|
||
|
|
f"wireguard.exe not found at: {wireguard_exe}\n"
|
||
|
|
"Please set the correct path and verify installation.")
|
||
|
|
self.terminal_write(f"Error: wireguard.exe not found", "red")
|
||
|
|
return
|
||
|
|
|
||
|
|
cmd = f'"{wireguard_exe}" /uninstalltunnelservice {interface}'
|
||
|
|
success, output = self.run_command(cmd)
|
||
|
|
|
||
|
|
if success:
|
||
|
|
self.log(f"WireGuard server stopped")
|
||
|
|
self.terminal_write(f"✓ Server stopped successfully", "green")
|
||
|
|
self.stop_btn.config(state="disabled")
|
||
|
|
self.start_btn.config(state="normal")
|
||
|
|
self.update_status("Server stopped")
|
||
|
|
self.update_server_status("stopped", "Service inactive")
|
||
|
|
else:
|
||
|
|
self.log(f"Error stopping server: {output}")
|
||
|
|
self.terminal_write(f"✗ Error stopping server: {output}", "red")
|
||
|
|
self.update_server_status("error", "Failed to stop")
|
||
|
|
|
||
|
|
def generate_client_config(self):
|
||
|
|
"""Generate client configuration"""
|
||
|
|
client_name = self.client_name.get()
|
||
|
|
client_ip = self.client_ip.get()
|
||
|
|
|
||
|
|
if not client_name:
|
||
|
|
messagebox.showerror("Error", "Please enter a client name")
|
||
|
|
return
|
||
|
|
|
||
|
|
self.update_status(f"Generating configuration for {client_name}...")
|
||
|
|
|
||
|
|
# Get the WireGuard path from the field
|
||
|
|
wg_path = os.path.join(self.wg_path_var.get(), "wg.exe")
|
||
|
|
if not os.path.exists(wg_path):
|
||
|
|
messagebox.showerror("Error",
|
||
|
|
f"WireGuard not found at: {wg_path}\n"
|
||
|
|
"Please set the correct path and verify installation.")
|
||
|
|
return
|
||
|
|
|
||
|
|
# Generate client keys
|
||
|
|
try:
|
||
|
|
result = subprocess.run(f'"{wg_path}" genkey', capture_output=True, text=True, shell=True)
|
||
|
|
if result.returncode != 0:
|
||
|
|
self.log(f"Error generating client private key: {result.stderr}")
|
||
|
|
return
|
||
|
|
client_private_key = result.stdout.strip()
|
||
|
|
except Exception as e:
|
||
|
|
self.log(f"Exception generating client private key: {str(e)}")
|
||
|
|
return
|
||
|
|
|
||
|
|
try:
|
||
|
|
cmd = f'echo {client_private_key} | "{wg_path}" pubkey'
|
||
|
|
result = subprocess.run(cmd, capture_output=True, text=True, shell=True)
|
||
|
|
if result.returncode != 0:
|
||
|
|
self.log(f"Error generating client public key: {result.stderr}")
|
||
|
|
return
|
||
|
|
client_public_key = result.stdout.strip()
|
||
|
|
except Exception as e:
|
||
|
|
self.log(f"Exception generating client public key: {str(e)}")
|
||
|
|
return
|
||
|
|
|
||
|
|
# Generate preshared key
|
||
|
|
try:
|
||
|
|
result = subprocess.run(f'"{wg_path}" genpsk', capture_output=True, text=True, shell=True)
|
||
|
|
psk = result.stdout.strip() if result.returncode == 0 else ""
|
||
|
|
except:
|
||
|
|
psk = ""
|
||
|
|
|
||
|
|
# Get server public IP from the field or config
|
||
|
|
server_public_ip = self.public_endpoint.get()
|
||
|
|
if not server_public_ip or server_public_ip == "Auto-detect required" or server_public_ip == "MANUAL_ENTRY_REQUIRED":
|
||
|
|
messagebox.showerror("Error",
|
||
|
|
"Please run 'Auto-Detect Network Settings' first or manually enter the public IP/domain")
|
||
|
|
return
|
||
|
|
|
||
|
|
# Create client configuration
|
||
|
|
client_config = f"""[Interface]
|
||
|
|
# This is the VPN tunnel IP address for this client, not their physical network IP
|
||
|
|
# The client can connect from any network (home, office, mobile) and will always
|
||
|
|
# get this same internal VPN IP address
|
||
|
|
PrivateKey = {client_private_key}
|
||
|
|
Address = {client_ip}
|
||
|
|
DNS = {self.dns_servers.get()}
|
||
|
|
|
||
|
|
[Peer]
|
||
|
|
# Server configuration
|
||
|
|
PublicKey = {self.server_config.get('public_key')}
|
||
|
|
PresharedKey = {psk}
|
||
|
|
|
||
|
|
# AllowedIPs determines what traffic goes through the VPN tunnel:
|
||
|
|
# 0.0.0.0/0 = Route ALL internet traffic through VPN (full tunnel)
|
||
|
|
# To route only specific traffic through VPN, you could use:
|
||
|
|
# - 10.0.0.0/24 = Only traffic to VPN network
|
||
|
|
# - 10.0.0.0/24, 192.168.1.0/24 = VPN network + specific LAN
|
||
|
|
AllowedIPs = 0.0.0.0/0
|
||
|
|
|
||
|
|
# Server's public endpoint - this is where the client connects to
|
||
|
|
# The client can be behind any NAT/firewall and connect to this public IP
|
||
|
|
Endpoint = {server_public_ip}:{self.listen_port.get()}
|
||
|
|
|
||
|
|
# Keep connection alive through NAT/firewalls (ping every 25 seconds)
|
||
|
|
PersistentKeepalive = 25
|
||
|
|
"""
|
||
|
|
|
||
|
|
# Save client configuration
|
||
|
|
config_dir = Path("wireguard_clients")
|
||
|
|
config_dir.mkdir(exist_ok=True)
|
||
|
|
|
||
|
|
client_file = config_dir / f"{client_name}.conf"
|
||
|
|
with open(client_file, 'w') as f:
|
||
|
|
f.write(client_config)
|
||
|
|
|
||
|
|
# Store client info for export
|
||
|
|
client_info = {
|
||
|
|
'name': client_name,
|
||
|
|
'config_file': str(client_file),
|
||
|
|
'config_content': client_config,
|
||
|
|
'public_key': client_public_key,
|
||
|
|
'ip': client_ip
|
||
|
|
}
|
||
|
|
self.clients.append(client_info)
|
||
|
|
|
||
|
|
# Update export client dropdown
|
||
|
|
client_names = [c['name'] for c in self.clients]
|
||
|
|
self.export_client_combo['values'] = client_names
|
||
|
|
if len(client_names) == 1:
|
||
|
|
self.export_client_combo.set(client_names[0])
|
||
|
|
self.export_client_btn.config(state="normal")
|
||
|
|
|
||
|
|
# Update server configuration to add client as peer
|
||
|
|
self.add_client_peer(client_name, client_public_key, client_ip, psk)
|
||
|
|
|
||
|
|
# Display client info
|
||
|
|
self.client_list.insert(tk.END, f"\n--- {client_name} ---\n")
|
||
|
|
self.client_list.insert(tk.END, f"Public Key: {client_public_key}\n")
|
||
|
|
self.client_list.insert(tk.END, f"IP: {client_ip}\n")
|
||
|
|
self.client_list.insert(tk.END, f"Config saved to: {client_file}\n")
|
||
|
|
|
||
|
|
self.log(f"Client configuration generated for {client_name}")
|
||
|
|
self.update_status(f"Client {client_name} added")
|
||
|
|
|
||
|
|
# Clear input fields
|
||
|
|
self.client_name.delete(0, tk.END)
|
||
|
|
|
||
|
|
# Increment IP for next client
|
||
|
|
try:
|
||
|
|
ip_obj = ipaddress.ip_interface(client_ip)
|
||
|
|
next_ip = ip_obj.ip + 1
|
||
|
|
self.client_ip.delete(0, tk.END)
|
||
|
|
self.client_ip.insert(0, f"{next_ip}/32")
|
||
|
|
except:
|
||
|
|
pass
|
||
|
|
|
||
|
|
def add_client_peer(self, name, public_key, allowed_ips, psk):
|
||
|
|
"""Add client as peer to server configuration"""
|
||
|
|
config_file = self.server_config.get('config_file')
|
||
|
|
if not config_file:
|
||
|
|
return
|
||
|
|
|
||
|
|
peer_config = f"""
|
||
|
|
[Peer]
|
||
|
|
# {name}
|
||
|
|
PublicKey = {public_key}
|
||
|
|
PresharedKey = {psk}
|
||
|
|
AllowedIPs = {allowed_ips}
|
||
|
|
"""
|
||
|
|
|
||
|
|
try:
|
||
|
|
with open(config_file, 'a') as f:
|
||
|
|
f.write(peer_config)
|
||
|
|
self.log(f"Added {name} to server configuration")
|
||
|
|
|
||
|
|
# If server is running, reload configuration
|
||
|
|
if self.stop_btn['state'] == 'normal':
|
||
|
|
interface = self.server_config.get('interface')
|
||
|
|
wg_path = os.path.join(self.wg_path_var.get(), "wg.exe")
|
||
|
|
if os.path.exists(wg_path):
|
||
|
|
self.run_command(f'"{wg_path}" syncconf {interface} "{config_file}"')
|
||
|
|
self.log(f"Reloaded server configuration")
|
||
|
|
except Exception as e:
|
||
|
|
self.log(f"Error updating server configuration: {e}")
|
||
|
|
|
||
|
|
def export_client_package(self):
|
||
|
|
"""Export client configuration with OS-specific installation scripts"""
|
||
|
|
selected_client = self.export_client_combo.get()
|
||
|
|
selected_os = self.client_os.get()
|
||
|
|
|
||
|
|
if not selected_client:
|
||
|
|
messagebox.showerror("Error", "Please select a client to export")
|
||
|
|
return
|
||
|
|
|
||
|
|
# Find the client info
|
||
|
|
client_info = None
|
||
|
|
for client in self.clients:
|
||
|
|
if client['name'] == selected_client:
|
||
|
|
client_info = client
|
||
|
|
break
|
||
|
|
|
||
|
|
if not client_info:
|
||
|
|
messagebox.showerror("Error", "Client configuration not found")
|
||
|
|
return
|
||
|
|
|
||
|
|
# Ask where to save the export package
|
||
|
|
export_dir = filedialog.askdirectory(title=f"Select Export Location for {selected_client}")
|
||
|
|
if not export_dir:
|
||
|
|
return
|
||
|
|
|
||
|
|
self.log(f"Exporting client package for {selected_client} ({selected_os})...")
|
||
|
|
|
||
|
|
# Create package directory
|
||
|
|
package_name = f"{selected_client}_{selected_os.replace('/', '_').replace(' ', '_')}_WireGuard_Setup"
|
||
|
|
package_dir = Path(export_dir) / package_name
|
||
|
|
package_dir.mkdir(exist_ok=True)
|
||
|
|
|
||
|
|
# Copy configuration file
|
||
|
|
config_file = package_dir / f"{selected_client}.conf"
|
||
|
|
with open(config_file, 'w') as f:
|
||
|
|
f.write(client_info['config_content'])
|
||
|
|
|
||
|
|
# Create OS-specific installation script
|
||
|
|
if selected_os == 'Windows':
|
||
|
|
self.create_windows_setup(package_dir, selected_client)
|
||
|
|
self.log("Created Windows setup batch script")
|
||
|
|
elif selected_os == 'Ubuntu/Debian':
|
||
|
|
self.create_ubuntu_setup(package_dir, selected_client)
|
||
|
|
self.log("Created Ubuntu/Debian setup script")
|
||
|
|
elif selected_os == 'Arch Linux':
|
||
|
|
self.create_arch_setup(package_dir, selected_client)
|
||
|
|
self.log("Created Arch Linux setup script")
|
||
|
|
elif selected_os == 'macOS':
|
||
|
|
self.create_macos_setup(package_dir, selected_client)
|
||
|
|
self.log("Created macOS setup script")
|
||
|
|
elif selected_os == 'Android':
|
||
|
|
self.create_android_instructions(package_dir, selected_client)
|
||
|
|
self.log("Created Android setup instructions")
|
||
|
|
elif selected_os == 'iOS':
|
||
|
|
self.create_ios_instructions(package_dir, selected_client)
|
||
|
|
self.log("Created iOS setup instructions")
|
||
|
|
|
||
|
|
# Create README
|
||
|
|
self.create_readme(package_dir, selected_client, selected_os, client_info)
|
||
|
|
self.log("Created README file")
|
||
|
|
|
||
|
|
# List all files in package directory for debugging
|
||
|
|
package_files = list(package_dir.glob('*'))
|
||
|
|
self.log(f"Package contains {len(package_files)} files:")
|
||
|
|
for file in package_files:
|
||
|
|
self.log(f" - {file.name}")
|
||
|
|
|
||
|
|
# Create ZIP archive
|
||
|
|
zip_path = Path(export_dir) / f"{package_name}.zip"
|
||
|
|
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
||
|
|
for file in package_dir.rglob('*'):
|
||
|
|
if file.is_file():
|
||
|
|
zipf.write(file, file.relative_to(package_dir))
|
||
|
|
|
||
|
|
self.log(f"Client package exported to: {zip_path}")
|
||
|
|
messagebox.showinfo("Export Complete",
|
||
|
|
f"Client setup package exported successfully!\n\n"
|
||
|
|
f"Location: {zip_path}\n\n"
|
||
|
|
f"The package contains:\n"
|
||
|
|
f"• WireGuard configuration file\n"
|
||
|
|
f"• Installation script for {selected_os}\n"
|
||
|
|
f"• Setup instructions")
|
||
|
|
|
||
|
|
# Open the export directory
|
||
|
|
try:
|
||
|
|
os.startfile(export_dir)
|
||
|
|
except:
|
||
|
|
pass # startfile only works on Windows
|
||
|
|
|
||
|
|
def create_windows_setup(self, package_dir, client_name):
|
||
|
|
"""Create Windows setup batch script"""
|
||
|
|
setup_script = package_dir / "setup_wireguard.bat"
|
||
|
|
content = f"""@echo off
|
||
|
|
title WireGuard Client Setup - {client_name}
|
||
|
|
color 0A
|
||
|
|
echo ===============================================
|
||
|
|
echo WireGuard Client Setup for Windows
|
||
|
|
echo Client: {client_name}
|
||
|
|
echo ===============================================
|
||
|
|
echo.
|
||
|
|
|
||
|
|
:: Check for admin privileges
|
||
|
|
net session >nul 2>&1
|
||
|
|
if %errorLevel% neq 0 (
|
||
|
|
echo This script requires administrator privileges!
|
||
|
|
echo Please run as Administrator.
|
||
|
|
pause
|
||
|
|
exit /b 1
|
||
|
|
)
|
||
|
|
|
||
|
|
echo [1] Checking if WireGuard is installed...
|
||
|
|
where wireguard >nul 2>&1
|
||
|
|
if %errorLevel% equ 0 (
|
||
|
|
echo WireGuard is already installed!
|
||
|
|
goto :import_config
|
||
|
|
)
|
||
|
|
|
||
|
|
echo [2] WireGuard not found. Installing WireGuard...
|
||
|
|
echo.
|
||
|
|
|
||
|
|
:: Check if Chocolatey is installed
|
||
|
|
where choco >nul 2>&1
|
||
|
|
if %errorLevel% equ 0 (
|
||
|
|
echo Using Chocolatey to install WireGuard...
|
||
|
|
choco install wireguard -y
|
||
|
|
goto :check_install
|
||
|
|
)
|
||
|
|
|
||
|
|
:: Download WireGuard installer directly
|
||
|
|
echo Chocolatey not found. Downloading WireGuard installer...
|
||
|
|
echo.
|
||
|
|
set "DOWNLOAD_URL=https://download.wireguard.com/windows-client/wireguard-installer.exe"
|
||
|
|
set "INSTALLER=wireguard-installer.exe"
|
||
|
|
|
||
|
|
powershell -Command "Invoke-WebRequest -Uri '%DOWNLOAD_URL%' -OutFile '%INSTALLER%'"
|
||
|
|
|
||
|
|
if exist %INSTALLER% (
|
||
|
|
echo Installing WireGuard...
|
||
|
|
%INSTALLER% /silent
|
||
|
|
timeout /t 10 /nobreak >nul
|
||
|
|
del %INSTALLER%
|
||
|
|
) else (
|
||
|
|
echo Failed to download WireGuard installer!
|
||
|
|
echo Please install WireGuard manually from: https://www.wireguard.com/install/
|
||
|
|
pause
|
||
|
|
exit /b 1
|
||
|
|
)
|
||
|
|
|
||
|
|
:check_install
|
||
|
|
where wireguard >nul 2>&1
|
||
|
|
if %errorLevel% neq 0 (
|
||
|
|
echo WireGuard installation failed!
|
||
|
|
echo Please install manually from: https://www.wireguard.com/install/
|
||
|
|
pause
|
||
|
|
exit /b 1
|
||
|
|
)
|
||
|
|
|
||
|
|
:import_config
|
||
|
|
echo.
|
||
|
|
echo [3] Importing WireGuard configuration...
|
||
|
|
set "CONFIG_FILE={client_name}.conf"
|
||
|
|
|
||
|
|
if not exist "%CONFIG_FILE%" (
|
||
|
|
echo Configuration file not found: %CONFIG_FILE%
|
||
|
|
echo Please ensure the .conf file is in the same directory as this script.
|
||
|
|
pause
|
||
|
|
exit /b 1
|
||
|
|
)
|
||
|
|
|
||
|
|
:: Import the configuration
|
||
|
|
echo Importing configuration: %CONFIG_FILE%
|
||
|
|
set "WG_PATH=C:\\Program Files\\WireGuard\\wireguard.exe"
|
||
|
|
if exist "%WG_PATH%" (
|
||
|
|
"%WG_PATH%" /installtunnelservice "%CD%\\%CONFIG_FILE%"
|
||
|
|
) else (
|
||
|
|
echo WireGuard executable not found at expected location.
|
||
|
|
echo Trying to import manually...
|
||
|
|
wireguard /installtunnelservice "%CD%\\%CONFIG_FILE%"
|
||
|
|
)
|
||
|
|
|
||
|
|
echo.
|
||
|
|
echo ===============================================
|
||
|
|
echo Setup Complete!
|
||
|
|
echo ===============================================
|
||
|
|
echo.
|
||
|
|
echo WireGuard has been installed and configured.
|
||
|
|
echo.
|
||
|
|
echo To manage your VPN connection:
|
||
|
|
echo 1. Open WireGuard from the Start Menu or System Tray
|
||
|
|
echo 2. Your tunnel "{client_name}" should appear in the list
|
||
|
|
echo 3. Click "Activate" to connect to the VPN
|
||
|
|
echo.
|
||
|
|
echo To start automatically with Windows:
|
||
|
|
echo - Right-click the tunnel in WireGuard
|
||
|
|
echo - Select "Properties"
|
||
|
|
echo - Check "Start on boot"
|
||
|
|
echo.
|
||
|
|
pause
|
||
|
|
"""
|
||
|
|
with open(setup_script, 'w') as f:
|
||
|
|
f.write(content)
|
||
|
|
|
||
|
|
def create_ubuntu_setup(self, package_dir, client_name):
|
||
|
|
"""Create Ubuntu/Debian setup script"""
|
||
|
|
setup_script = package_dir / "setup_wireguard_debian.sh" # Clear name for Debian/Ubuntu
|
||
|
|
content = f"""#!/bin/bash
|
||
|
|
# WireGuard Client Setup Script for Ubuntu/Debian
|
||
|
|
# Client: {client_name}
|
||
|
|
|
||
|
|
set -e
|
||
|
|
|
||
|
|
echo "======================================="
|
||
|
|
echo " WireGuard Client Setup for Ubuntu/Debian"
|
||
|
|
echo " Client: {client_name}"
|
||
|
|
echo "======================================="
|
||
|
|
echo ""
|
||
|
|
|
||
|
|
# Check if running as root
|
||
|
|
if [[ $EUID -ne 0 ]]; then
|
||
|
|
echo "This script must be run as root (use sudo)"
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
echo "[1] Updating package list..."
|
||
|
|
apt update
|
||
|
|
|
||
|
|
echo ""
|
||
|
|
echo "[2] Installing WireGuard..."
|
||
|
|
apt install -y wireguard wireguard-tools
|
||
|
|
|
||
|
|
echo ""
|
||
|
|
echo "[3] Setting up configuration..."
|
||
|
|
CONFIG_FILE="{client_name}.conf"
|
||
|
|
DEST_CONFIG="/etc/wireguard/{client_name}.conf"
|
||
|
|
|
||
|
|
if [ ! -f "$CONFIG_FILE" ]; then
|
||
|
|
echo "Configuration file not found: $CONFIG_FILE"
|
||
|
|
echo "Please ensure the .conf file is in the same directory as this script."
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Copy configuration to WireGuard directory
|
||
|
|
cp "$CONFIG_FILE" "$DEST_CONFIG"
|
||
|
|
chmod 600 "$DEST_CONFIG"
|
||
|
|
chown root:root "$DEST_CONFIG"
|
||
|
|
|
||
|
|
echo ""
|
||
|
|
echo "[4] Enabling IP forwarding..."
|
||
|
|
sysctl -w net.ipv4.ip_forward=1
|
||
|
|
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
|
||
|
|
|
||
|
|
echo ""
|
||
|
|
echo "======================================="
|
||
|
|
echo " Setup Complete!"
|
||
|
|
echo "======================================="
|
||
|
|
echo ""
|
||
|
|
echo "WireGuard has been installed and configured."
|
||
|
|
echo ""
|
||
|
|
echo "Available commands:"
|
||
|
|
echo " Start VPN: sudo wg-quick up {client_name}"
|
||
|
|
echo " Stop VPN: sudo wg-quick down {client_name}"
|
||
|
|
echo " Show status: sudo wg show"
|
||
|
|
echo ""
|
||
|
|
echo "To start VPN automatically on boot:"
|
||
|
|
echo " sudo systemctl enable wg-quick@{client_name}"
|
||
|
|
echo ""
|
||
|
|
echo "Would you like to start the VPN now? (y/n)"
|
||
|
|
read -r response
|
||
|
|
if [[ "$response" =~ ^[Yy]$ ]]; then
|
||
|
|
wg-quick up {client_name}
|
||
|
|
echo ""
|
||
|
|
echo "VPN is now connected!"
|
||
|
|
echo ""
|
||
|
|
wg show
|
||
|
|
fi
|
||
|
|
"""
|
||
|
|
with open(setup_script, 'w', newline='\n') as f: # Force Unix line endings
|
||
|
|
f.write(content)
|
||
|
|
|
||
|
|
# Make script executable (for when extracted on Linux)
|
||
|
|
os.chmod(setup_script, 0o755)
|
||
|
|
self.log(f"Created Ubuntu/Debian setup script: setup_wireguard_debian.sh")
|
||
|
|
|
||
|
|
def create_arch_setup(self, package_dir, client_name):
|
||
|
|
"""Create Arch Linux setup script"""
|
||
|
|
setup_script = package_dir / "setup_wireguard_arch.sh" # Unique name for Arch
|
||
|
|
content = f"""#!/bin/bash
|
||
|
|
# WireGuard Client Setup Script for Arch Linux
|
||
|
|
# Client: {client_name}
|
||
|
|
|
||
|
|
set -e
|
||
|
|
|
||
|
|
echo "======================================="
|
||
|
|
echo " WireGuard Client Setup for Arch Linux"
|
||
|
|
echo " Client: {client_name}"
|
||
|
|
echo "======================================="
|
||
|
|
echo ""
|
||
|
|
|
||
|
|
# Check if running as root
|
||
|
|
if [[ $EUID -ne 0 ]]; then
|
||
|
|
echo "This script must be run as root (use sudo)"
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
echo "[1] Updating package database..."
|
||
|
|
pacman -Sy
|
||
|
|
|
||
|
|
echo ""
|
||
|
|
echo "[2] Installing WireGuard..."
|
||
|
|
pacman -S --noconfirm wireguard-tools
|
||
|
|
|
||
|
|
echo ""
|
||
|
|
echo "[3] Setting up configuration..."
|
||
|
|
CONFIG_FILE="{client_name}.conf"
|
||
|
|
DEST_CONFIG="/etc/wireguard/{client_name}.conf"
|
||
|
|
|
||
|
|
if [ ! -f "$CONFIG_FILE" ]; then
|
||
|
|
echo "Configuration file not found: $CONFIG_FILE"
|
||
|
|
echo "Please ensure the .conf file is in the same directory as this script."
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Create WireGuard directory if it doesn't exist
|
||
|
|
mkdir -p /etc/wireguard
|
||
|
|
|
||
|
|
# Copy configuration to WireGuard directory
|
||
|
|
cp "$CONFIG_FILE" "$DEST_CONFIG"
|
||
|
|
chmod 600 "$DEST_CONFIG"
|
||
|
|
chown root:root "$DEST_CONFIG"
|
||
|
|
|
||
|
|
echo ""
|
||
|
|
echo "[4] Enabling IP forwarding..."
|
||
|
|
sysctl -w net.ipv4.ip_forward=1
|
||
|
|
echo "net.ipv4.ip_forward=1" > /etc/sysctl.d/99-wireguard.conf
|
||
|
|
|
||
|
|
echo ""
|
||
|
|
echo "[5] Loading WireGuard kernel module..."
|
||
|
|
modprobe wireguard || echo "Note: WireGuard module may be built-in to kernel"
|
||
|
|
|
||
|
|
echo ""
|
||
|
|
echo "======================================="
|
||
|
|
echo " Setup Complete!"
|
||
|
|
echo "======================================="
|
||
|
|
echo ""
|
||
|
|
echo "WireGuard has been installed and configured."
|
||
|
|
echo ""
|
||
|
|
echo "Available commands:"
|
||
|
|
echo " Start VPN: sudo wg-quick up {client_name}"
|
||
|
|
echo " Stop VPN: sudo wg-quick down {client_name}"
|
||
|
|
echo " Show status: sudo wg show"
|
||
|
|
echo ""
|
||
|
|
echo "To start VPN automatically on boot:"
|
||
|
|
echo " sudo systemctl enable wg-quick@{client_name}"
|
||
|
|
echo ""
|
||
|
|
echo "Would you like to start the VPN now? (y/n)"
|
||
|
|
read -r response
|
||
|
|
if [[ "$response" =~ ^[Yy]$ ]]; then
|
||
|
|
wg-quick up {client_name}
|
||
|
|
echo ""
|
||
|
|
echo "VPN is now connected!"
|
||
|
|
echo ""
|
||
|
|
wg show
|
||
|
|
fi
|
||
|
|
"""
|
||
|
|
with open(setup_script, 'w', newline='\n') as f: # Force Unix line endings
|
||
|
|
f.write(content)
|
||
|
|
|
||
|
|
# Make script executable (sets permission bits for when extracted on Linux)
|
||
|
|
os.chmod(setup_script, 0o755)
|
||
|
|
self.log(f"Created Arch Linux setup script: setup_wireguard_arch.sh")
|
||
|
|
|
||
|
|
def create_macos_setup(self, package_dir, client_name):
|
||
|
|
"""Create macOS setup script"""
|
||
|
|
setup_script = package_dir / "setup_wireguard_macos.sh" # Clear name for macOS
|
||
|
|
content = f"""#!/bin/bash
|
||
|
|
# WireGuard Client Setup Script for macOS
|
||
|
|
# Client: {client_name}
|
||
|
|
|
||
|
|
echo "======================================="
|
||
|
|
echo " WireGuard Client Setup for macOS"
|
||
|
|
echo " Client: {client_name}"
|
||
|
|
echo "======================================="
|
||
|
|
echo ""
|
||
|
|
|
||
|
|
# Function to check if command exists
|
||
|
|
command_exists() {{
|
||
|
|
command -v "$1" >/dev/null 2>&1
|
||
|
|
}}
|
||
|
|
|
||
|
|
echo "[1] Checking installation method..."
|
||
|
|
|
||
|
|
# Check if Homebrew is installed
|
||
|
|
if command_exists brew; then
|
||
|
|
echo "Homebrew detected. Installing WireGuard via Homebrew..."
|
||
|
|
brew install wireguard-tools
|
||
|
|
|
||
|
|
echo ""
|
||
|
|
echo "[2] Setting up configuration..."
|
||
|
|
CONFIG_FILE="{client_name}.conf"
|
||
|
|
DEST_CONFIG="/usr/local/etc/wireguard/{client_name}.conf"
|
||
|
|
|
||
|
|
if [ ! -f "$CONFIG_FILE" ]; then
|
||
|
|
echo "Configuration file not found: $CONFIG_FILE"
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Create WireGuard directory if it doesn't exist
|
||
|
|
sudo mkdir -p /usr/local/etc/wireguard
|
||
|
|
|
||
|
|
# Copy configuration
|
||
|
|
sudo cp "$CONFIG_FILE" "$DEST_CONFIG"
|
||
|
|
sudo chmod 600 "$DEST_CONFIG"
|
||
|
|
|
||
|
|
echo ""
|
||
|
|
echo "======================================="
|
||
|
|
echo " Setup Complete!"
|
||
|
|
echo "======================================="
|
||
|
|
echo ""
|
||
|
|
echo "WireGuard has been installed via Homebrew."
|
||
|
|
echo ""
|
||
|
|
echo "To use WireGuard from command line:"
|
||
|
|
echo " Start VPN: sudo wg-quick up {client_name}"
|
||
|
|
echo " Stop VPN: sudo wg-quick down {client_name}"
|
||
|
|
echo " Show status: sudo wg show"
|
||
|
|
else
|
||
|
|
echo "Homebrew not found."
|
||
|
|
echo ""
|
||
|
|
echo "RECOMMENDED: Install WireGuard from the Mac App Store"
|
||
|
|
echo ""
|
||
|
|
echo "Instructions:"
|
||
|
|
echo "1. Open the Mac App Store"
|
||
|
|
echo "2. Search for 'WireGuard'"
|
||
|
|
echo "3. Install the WireGuard app (by WireGuard Development Team)"
|
||
|
|
echo "4. Open WireGuard from Applications"
|
||
|
|
echo "5. Click 'Import tunnel(s) from file'"
|
||
|
|
echo "6. Select the {client_name}.conf file"
|
||
|
|
echo "7. Click 'Activate' to connect"
|
||
|
|
echo ""
|
||
|
|
echo "Alternative: Install Homebrew first, then run this script again"
|
||
|
|
echo "Install Homebrew from: https://brew.sh"
|
||
|
|
fi
|
||
|
|
|
||
|
|
echo ""
|
||
|
|
echo "Configuration file location: {client_name}.conf"
|
||
|
|
echo ""
|
||
|
|
echo "For GUI application (recommended):"
|
||
|
|
echo "1. Install WireGuard from Mac App Store"
|
||
|
|
echo "2. Import the {client_name}.conf file"
|
||
|
|
echo ""
|
||
|
|
"""
|
||
|
|
with open(setup_script, 'w', newline='\n') as f: # Force Unix line endings
|
||
|
|
f.write(content)
|
||
|
|
|
||
|
|
os.chmod(setup_script, 0o755)
|
||
|
|
self.log(f"Created macOS setup script: setup_wireguard_macos.sh")
|
||
|
|
|
||
|
|
def create_android_instructions(self, package_dir, client_name):
|
||
|
|
"""Create Android setup instructions"""
|
||
|
|
instructions = package_dir / "ANDROID_SETUP.txt"
|
||
|
|
content = f"""WireGuard Setup Instructions for Android
|
||
|
|
Client: {client_name}
|
||
|
|
=========================================
|
||
|
|
|
||
|
|
INSTALLATION STEPS:
|
||
|
|
|
||
|
|
1. Install WireGuard App:
|
||
|
|
- Open Google Play Store
|
||
|
|
- Search for "WireGuard"
|
||
|
|
- Install the app by "WireGuard Development Team"
|
||
|
|
- Or visit: https://play.google.com/store/apps/details?id=com.wireguard.android
|
||
|
|
|
||
|
|
2. Import Configuration:
|
||
|
|
|
||
|
|
Method A - QR Code (Easiest):
|
||
|
|
- Open WireGuard app
|
||
|
|
- Tap the "+" button
|
||
|
|
- Select "Create from QR code"
|
||
|
|
- Use the QR code provided (if available)
|
||
|
|
|
||
|
|
Method B - File Import:
|
||
|
|
- Transfer the {client_name}.conf file to your Android device
|
||
|
|
- Open WireGuard app
|
||
|
|
- Tap the "+" button
|
||
|
|
- Select "Import from file"
|
||
|
|
- Navigate to and select {client_name}.conf
|
||
|
|
- Give the tunnel a name (or keep default)
|
||
|
|
|
||
|
|
3. Connect to VPN:
|
||
|
|
- Toggle the switch next to your tunnel name to ON
|
||
|
|
- Accept the VPN connection request (first time only)
|
||
|
|
- You should see "Active" status
|
||
|
|
|
||
|
|
4. Optional Settings:
|
||
|
|
- Long press on the tunnel name for options
|
||
|
|
- You can enable "Auto-start on boot"
|
||
|
|
- You can exclude certain apps from VPN
|
||
|
|
|
||
|
|
TROUBLESHOOTING:
|
||
|
|
|
||
|
|
- If connection fails, check your internet connection
|
||
|
|
- Ensure the server is running and accessible
|
||
|
|
- Try toggling airplane mode and reconnecting
|
||
|
|
- Check if your mobile carrier blocks VPN connections
|
||
|
|
|
||
|
|
SECURITY NOTE:
|
||
|
|
Keep your configuration file secure. Anyone with this file
|
||
|
|
can connect to your VPN server using your credentials.
|
||
|
|
"""
|
||
|
|
with open(instructions, 'w') as f:
|
||
|
|
f.write(content)
|
||
|
|
|
||
|
|
# Try to generate QR code if qrcode library is available
|
||
|
|
try:
|
||
|
|
import qrcode
|
||
|
|
qr = qrcode.QRCode(version=None, box_size=10, border=5)
|
||
|
|
with open(package_dir / f"{client_name}.conf", 'r') as f:
|
||
|
|
config_content = f.read()
|
||
|
|
qr.add_data(config_content)
|
||
|
|
qr.make(fit=True)
|
||
|
|
img = qr.make_image(fill_color="black", back_color="white")
|
||
|
|
img.save(package_dir / f"{client_name}_QR.png")
|
||
|
|
|
||
|
|
with open(instructions, 'a') as f:
|
||
|
|
f.write(f"\n\nQR CODE:\nA QR code has been generated: {client_name}_QR.png\n"
|
||
|
|
"You can scan this directly with the WireGuard Android app.\n")
|
||
|
|
self.log("QR code generated for mobile setup")
|
||
|
|
except ImportError:
|
||
|
|
self.log("Note: Install 'qrcode' and 'pillow' packages for QR code generation")
|
||
|
|
with open(instructions, 'a') as f:
|
||
|
|
f.write("\n\nNOTE: QR code generation requires 'pip install qrcode pillow'\n")
|
||
|
|
|
||
|
|
def create_ios_instructions(self, package_dir, client_name):
|
||
|
|
"""Create iOS setup instructions"""
|
||
|
|
instructions = package_dir / "iOS_SETUP.txt"
|
||
|
|
content = f"""WireGuard Setup Instructions for iOS (iPhone/iPad)
|
||
|
|
Client: {client_name}
|
||
|
|
==================================================
|
||
|
|
|
||
|
|
INSTALLATION STEPS:
|
||
|
|
|
||
|
|
1. Install WireGuard App:
|
||
|
|
- Open the App Store
|
||
|
|
- Search for "WireGuard"
|
||
|
|
- Install the app by "WireGuard Development Team"
|
||
|
|
- Or visit: https://apps.apple.com/us/app/wireguard/id1441195209
|
||
|
|
|
||
|
|
2. Import Configuration:
|
||
|
|
|
||
|
|
Method A - QR Code (Easiest):
|
||
|
|
- Open WireGuard app
|
||
|
|
- Tap "+" button
|
||
|
|
- Select "Create from QR code"
|
||
|
|
- Allow camera access
|
||
|
|
- Scan the QR code provided (if available)
|
||
|
|
- Name your tunnel and tap "Save"
|
||
|
|
|
||
|
|
Method B - File Import:
|
||
|
|
- Email yourself the {client_name}.conf file
|
||
|
|
- Open the email on your iOS device
|
||
|
|
- Tap and hold the .conf attachment
|
||
|
|
- Select "Share" and choose "WireGuard"
|
||
|
|
- Name your tunnel and tap "Save"
|
||
|
|
|
||
|
|
Method C - Manual Entry:
|
||
|
|
- Open WireGuard app
|
||
|
|
- Tap "+" then "Add a tunnel manually"
|
||
|
|
- Enter the configuration details from {client_name}.conf
|
||
|
|
|
||
|
|
3. Connect to VPN:
|
||
|
|
- Toggle the switch next to your tunnel name to ON
|
||
|
|
- Accept the VPN configuration request (first time only)
|
||
|
|
- You should see "Active" status
|
||
|
|
|
||
|
|
4. Optional Settings:
|
||
|
|
- Tap on the tunnel name for details
|
||
|
|
- You can enable "Connect on Demand" for automatic connection
|
||
|
|
- Set up rules for Wi-Fi or cellular connections
|
||
|
|
|
||
|
|
TROUBLESHOOTING:
|
||
|
|
|
||
|
|
- If connection fails, check your internet connection
|
||
|
|
- Ensure the server is running and accessible
|
||
|
|
- Try toggling airplane mode and reconnecting
|
||
|
|
- Check Settings > VPN to ensure configuration is present
|
||
|
|
|
||
|
|
SECURITY NOTE:
|
||
|
|
Keep your configuration file secure. Anyone with this file
|
||
|
|
can connect to your VPN server using your credentials.
|
||
|
|
|
||
|
|
For Face ID/Touch ID protection:
|
||
|
|
Settings > WireGuard > Use Face ID / Touch ID
|
||
|
|
"""
|
||
|
|
with open(instructions, 'w') as f:
|
||
|
|
f.write(content)
|
||
|
|
|
||
|
|
# Try to generate QR code if qrcode library is available
|
||
|
|
try:
|
||
|
|
import qrcode
|
||
|
|
qr = qrcode.QRCode(version=None, box_size=10, border=5)
|
||
|
|
with open(package_dir / f"{client_name}.conf", 'r') as f:
|
||
|
|
config_content = f.read()
|
||
|
|
qr.add_data(config_content)
|
||
|
|
qr.make(fit=True)
|
||
|
|
img = qr.make_image(fill_color="black", back_color="white")
|
||
|
|
img.save(package_dir / f"{client_name}_QR.png")
|
||
|
|
|
||
|
|
with open(instructions, 'a') as f:
|
||
|
|
f.write(f"\n\nQR CODE:\nA QR code has been generated: {client_name}_QR.png\n"
|
||
|
|
"You can scan this directly with the WireGuard iOS app.\n")
|
||
|
|
self.log("QR code generated for mobile setup")
|
||
|
|
except ImportError:
|
||
|
|
self.log("Note: Install 'qrcode' and 'pillow' packages for QR code generation")
|
||
|
|
with open(instructions, 'a') as f:
|
||
|
|
f.write("\n\nNOTE: QR code generation requires 'pip install qrcode pillow'\n")
|
||
|
|
|
||
|
|
def create_readme(self, package_dir, client_name, os_type, client_info):
|
||
|
|
"""Create a general README file"""
|
||
|
|
current_date = datetime.now().strftime("%Y-%m-%d %H:%M")
|
||
|
|
|
||
|
|
# Determine script name based on OS
|
||
|
|
script_name = "setup_wireguard"
|
||
|
|
if os_type == "Windows":
|
||
|
|
script_name = "setup_wireguard.bat"
|
||
|
|
elif os_type == "Ubuntu/Debian":
|
||
|
|
script_name = "setup_wireguard_debian.sh"
|
||
|
|
elif os_type == "Arch Linux":
|
||
|
|
script_name = "setup_wireguard_arch.sh"
|
||
|
|
elif os_type == "macOS":
|
||
|
|
script_name = "setup_wireguard_macos.sh"
|
||
|
|
elif os_type in ["Android", "iOS"]:
|
||
|
|
script_name = f"{os_type.replace(' ', '_')}_SETUP.txt"
|
||
|
|
|
||
|
|
client_vpn_ip = client_info.get('ip', '10.0.0.x/32')
|
||
|
|
|
||
|
|
readme = package_dir / "README.txt"
|
||
|
|
content = f"""WireGuard VPN Client Setup Package
|
||
|
|
===================================
|
||
|
|
Client Name: {client_name}
|
||
|
|
Target OS: {os_type}
|
||
|
|
Generated: {current_date}
|
||
|
|
|
||
|
|
PACKAGE CONTENTS:
|
||
|
|
-----------------
|
||
|
|
• {client_name}.conf - WireGuard configuration file (your VPN credentials)
|
||
|
|
• {script_name} - Automated installation script for {os_type}
|
||
|
|
• README.txt - This file
|
||
|
|
• OS-specific setup instructions (if applicable)
|
||
|
|
|
||
|
|
IMPORTANT - Understanding the IP Addresses:
|
||
|
|
--------------------------------------------
|
||
|
|
The configuration file contains "Address = {client_vpn_ip}"
|
||
|
|
This is your VPN TUNNEL IP, not your physical network IP.
|
||
|
|
|
||
|
|
• Your device's network IP (changes based on WiFi/network): Handled by DHCP
|
||
|
|
• Your VPN tunnel IP (always the same): {client_vpn_ip}
|
||
|
|
|
||
|
|
You can connect from ANY network (home, office, mobile data) and will always
|
||
|
|
get the same VPN tunnel IP for routing within the VPN.
|
||
|
|
|
||
|
|
QUICK START:
|
||
|
|
------------
|
||
|
|
1. Extract all files to the same directory
|
||
|
|
2. Run the setup script for your operating system:
|
||
|
|
- Windows: Right-click {script_name} and "Run as Administrator"
|
||
|
|
- Linux/Mac: chmod +x {script_name} && sudo ./{script_name}
|
||
|
|
- Mobile: Follow the instructions in the setup guide
|
||
|
|
3. The script will install WireGuard and import your configuration
|
||
|
|
4. Connect to the VPN using the WireGuard application
|
||
|
|
|
||
|
|
MANUAL SETUP:
|
||
|
|
-------------
|
||
|
|
If the automated script doesn't work:
|
||
|
|
1. Install WireGuard from: https://www.wireguard.com/install/
|
||
|
|
2. Import the {client_name}.conf file into WireGuard
|
||
|
|
3. Activate the connection
|
||
|
|
|
||
|
|
SECURITY NOTES:
|
||
|
|
---------------
|
||
|
|
• Keep your .conf file secure - it contains your private keys
|
||
|
|
• Do not share this file with others
|
||
|
|
• Delete this package after successful setup
|
||
|
|
• Each client should have its own unique configuration
|
||
|
|
|
||
|
|
SUPPORT:
|
||
|
|
--------
|
||
|
|
• WireGuard Documentation: https://www.wireguard.com/
|
||
|
|
• Platform-specific guides are included in this package
|
||
|
|
|
||
|
|
CONNECTION DETAILS:
|
||
|
|
-------------------
|
||
|
|
Once connected, you can verify your VPN connection by:
|
||
|
|
• Checking your IP address at: https://whatismyipaddress.com
|
||
|
|
• Running 'wg show' command (on Linux/Mac with admin privileges)
|
||
|
|
• Checking the WireGuard app status
|
||
|
|
|
||
|
|
Remember to disconnect from the VPN when not needed to conserve bandwidth.
|
||
|
|
"""
|
||
|
|
with open(readme, 'w') as f:
|
||
|
|
f.write(content)
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
root = tk.Tk()
|
||
|
|
app = WireGuardServerGUI(root)
|
||
|
|
root.mainloop()
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|