Files
setec_wg_helper/wg_assist.py

2017 lines
81 KiB
Python
Raw Permalink Normal View History

2026-03-13 13:12:00 -07:00
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()