Added all changes

main
root 2 months ago
parent 8c7bef73af
commit 3f98c368a3

326
app.py

@ -0,0 +1,326 @@
#!/usr/bin/env python3
"""
TorGuard WireGuard VPN Manager
A secure web interface for managing WireGuard VPN configurations
"""
import os
import sys
import subprocess
import json
import time
import secrets
from datetime import datetime
from pathlib import Path
from functools import wraps
from flask import Flask, request, render_template, redirect, url_for, session, flash, jsonify
from werkzeug.security import generate_password_hash, check_password_hash
from flask_wtf.csrf import CSRFProtect
import bcrypt
from cryptography.fernet import Fernet
import netifaces
import psutil
app = Flask(__name__)
app.secret_key = secrets.token_hex(32)
csrf = CSRFProtect(app)
CONF_DIR = Path('/etc/wireguard-manager')
WG_CONF_PATH = Path('/etc/wireguard/wg0.conf')
KEY_FILE = CONF_DIR / 'key.enc'
CREDS_FILE = CONF_DIR / 'credentials.enc'
def initialize_crypto():
"""Initialize encryption key for storing sensitive data"""
if not CONF_DIR.exists():
CONF_DIR.mkdir(mode=0o700)
if not KEY_FILE.exists():
key = Fernet.generate_key()
KEY_FILE.write_bytes(key)
KEY_FILE.chmod(0o600)
return Fernet(KEY_FILE.read_bytes())
crypto = initialize_crypto()
def requires_auth(f):
"""Decorator to require authentication for routes"""
@wraps(f)
def decorated(*args, **kwargs):
if not CREDS_FILE.exists():
return redirect(url_for('register'))
if 'authenticated' not in session:
return redirect(url_for('login'))
return f(*args, **kwargs)
return decorated
def check_system_config():
"""Verify system configuration for WireGuard"""
issues = []
# Check IP forwarding
try:
with open('/proc/sys/net/ipv4/ip_forward', 'r') as f:
if f.read().strip() != '1':
issues.append("IP forwarding is not enabled")
except Exception as e:
issues.append(f"Failed to check IP forwarding: {str(e)}")
# Check NAT rules
try:
output = subprocess.check_output(['iptables', '-t', 'nat', '-L', 'POSTROUTING', '-n'], text=True)
if "MASQUERADE" not in output:
issues.append("NAT masquerade rule is missing")
except Exception as e:
issues.append(f"Failed to check NAT rules: {str(e)}")
# Check WireGuard module
try:
subprocess.check_output(['lsmod'], text=True)
if 'wireguard' not in subprocess.check_output(['lsmod'], text=True):
issues.append("WireGuard kernel module is not loaded")
except Exception as e:
issues.append(f"Failed to check WireGuard module: {str(e)}")
return issues
def get_wg_status():
"""Get WireGuard interface status and statistics"""
try:
# Check system configuration
system_issues = check_system_config()
if system_issues:
return {
'status': 'error',
'issues': system_issues
}
# Check WireGuard interface
if not WG_CONF_PATH.exists():
return {'status': 'disconnected', 'message': 'No configuration file found'}
try:
output = subprocess.check_output(['wg', 'show', 'wg0'], text=True)
if 'peer' not in output:
return {'status': 'disconnected'}
# Parse WireGuard stats
stats = {'status': 'connected'}
for line in output.split('\n'):
if 'transfer:' in line:
tx, rx = line.split('transfer:')[1].split(',')
stats['tx'] = tx.strip()
stats['rx'] = rx.strip()
elif 'latest handshake:' in line:
time_str = line.split('latest handshake:')[1].strip()
stats['connected_since'] = time_str
# Add interface information
try:
interface_stats = psutil.net_io_counters(pernic=True).get('wg0', None)
if interface_stats:
stats['total_tx'] = f"{interface_stats.bytes_sent / (1024*1024):.2f} MB"
stats['total_rx'] = f"{interface_stats.bytes_recv / (1024*1024):.2f} MB"
except:
pass
return stats
except subprocess.CalledProcessError:
return {'status': 'disconnected'}
except Exception as e:
return {
'status': 'error',
'error': str(e)
}
def validate_wireguard_config(config):
"""Validate WireGuard configuration format"""
required_fields = {
'Interface': ['PrivateKey', 'Address'],
'Peer': ['PublicKey', 'AllowedIPs', 'Endpoint']
}
current_section = None
found_fields = {'Interface': set(), 'Peer': set()}
for line in config.splitlines():
line = line.strip()
if not line or line.startswith('#'):
continue
if line.startswith('[') and line.endswith(']'):
current_section = line[1:-1]
continue
if current_section and '=' in line:
key = line.split('=')[0].strip()
if current_section in found_fields and key in required_fields[current_section]:
found_fields[current_section].add(key)
# Check if all required fields are present
missing_fields = []
for section, fields in required_fields.items():
for field in fields:
if field not in found_fields[section]:
missing_fields.append(f"{section}/{field}")
return len(missing_fields) == 0, missing_fields
@app.route('/')
def index():
if not CREDS_FILE.exists():
return redirect(url_for('register'))
if 'authenticated' not in session:
return redirect(url_for('login'))
status = get_wg_status()
return render_template('index.html', status=status, WG_CONF_PATH=WG_CONF_PATH)
@app.route('/status')
@requires_auth
def get_status_route():
"""API endpoint for getting VPN status"""
return jsonify(get_wg_status())
@app.route('/login', methods=['GET', 'POST'])
def login():
if not CREDS_FILE.exists():
return redirect(url_for('register'))
if 'authenticated' in session:
return redirect(url_for('index'))
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
try:
stored_creds = json.loads(crypto.decrypt(CREDS_FILE.read_bytes()))
if username == stored_creds['username'] and \
check_password_hash(stored_creds['password'], password):
session['authenticated'] = True
session['username'] = username
return redirect(url_for('index'))
except Exception as e:
app.logger.error(f"Login error: {str(e)}")
flash('Error accessing credentials')
return render_template('login.html')
flash('Invalid credentials')
return render_template('login.html')
@app.route('/register', methods=['GET', 'POST'])
def register():
if CREDS_FILE.exists():
return redirect(url_for('login'))
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
# Validate input
if not username or not password:
flash('Username and password are required')
return render_template('register.html')
if len(password) < 8:
flash('Password must be at least 8 characters long')
return render_template('register.html')
try:
# Store encrypted credentials
creds = {
'username': username,
'password': generate_password_hash(password)
}
CREDS_FILE.write_bytes(crypto.encrypt(json.dumps(creds).encode()))
CREDS_FILE.chmod(0o600)
flash('Account created successfully. Please login.')
return redirect(url_for('login'))
except Exception as e:
app.logger.error(f"Registration error: {str(e)}")
flash('Error creating account')
return render_template('register.html')
@app.route('/logout')
def logout():
session.clear()
return redirect(url_for('login'))
@app.route('/config', methods=['POST'])
@requires_auth
def save_config():
try:
if 'config_file' in request.files:
config = request.files['config_file'].read().decode()
else:
config = request.form['config_text']
# Validate WireGuard config format
is_valid, missing_fields = validate_wireguard_config(config)
if not is_valid:
flash(f'Invalid WireGuard configuration. Missing fields: {", ".join(missing_fields)}')
return redirect(url_for('index'))
# Save config securely
WG_CONF_PATH.write_text(config)
WG_CONF_PATH.chmod(0o600)
flash('Configuration saved successfully')
except Exception as e:
app.logger.error(f"Config save error: {str(e)}")
flash('Error saving configuration')
return redirect(url_for('index'))
@app.route('/toggle', methods=['POST'])
@requires_auth
def toggle_vpn():
status = get_wg_status()
try:
if status['status'] == 'connected':
subprocess.run(['wg-quick', 'down', 'wg0'], check=True)
flash('VPN disconnected successfully')
else:
# Check system configuration before connecting
issues = check_system_config()
if issues:
flash(f'System configuration issues found: {", ".join(issues)}')
return redirect(url_for('index'))
subprocess.run(['wg-quick', 'up', 'wg0'], check=True)
flash('VPN connected successfully')
except subprocess.CalledProcessError as e:
app.logger.error(f"VPN toggle error: {e.stderr.decode() if e.stderr else str(e)}")
flash(f'Error toggling VPN: {e.stderr.decode() if e.stderr else str(e)}')
except Exception as e:
app.logger.error(f"VPN toggle error: {str(e)}")
flash(f'Error toggling VPN: {str(e)}')
return redirect(url_for('index'))
def main():
"""Main entry point"""
# Check if running as root
if os.geteuid() != 0:
print("This program must be run as root")
sys.exit(1)
# Initialize directories
CONF_DIR.mkdir(mode=0o700, exist_ok=True)
# Check system configuration
issues = check_system_config()
if issues:
print("Warning: System configuration issues found:")
for issue in issues:
print(f" - {issue}")
# Start Flask server
app.run(host='0.0.0.0', port=1337)
if __name__ == '__main__':
main()

@ -0,0 +1,297 @@
#!/usr/bin/env python3
"""
TorGuard WireGuard Manager Installer
Installs and configures the WireGuard Manager web interface with complete system setup
"""
import os
import sys
import shutil
import subprocess
from pathlib import Path
import time
def print_step(emoji, message):
"""Print a step with emoji and message"""
print(f"\n{emoji} {message}")
def run_command(command, error_message, shell=False):
"""Run a shell command and handle errors"""
try:
if shell:
subprocess.run(command, check=True, shell=True)
else:
subprocess.run(command, check=True)
print("✅ Done!")
return True
except subprocess.CalledProcessError as e:
print(f"❌ Error: {error_message}")
if hasattr(e, 'stderr') and e.stderr:
print(f"Details: {e.stderr.decode()}")
return False
def get_local_ip():
"""Get the local IP address"""
try:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
ip = s.getsockname()[0]
s.close()
return ip
except:
return "0.0.0.0"
def setup_wireguard():
"""Set up WireGuard with complete system configuration"""
print_step("🔒", "Setting up WireGuard system configuration...")
# Ensure WireGuard kernel module is loaded
print("Loading WireGuard kernel module...")
run_command("sudo modprobe wireguard", "Failed to load WireGuard kernel module", shell=True)
# Enable IP forwarding
print("Enabling IP forwarding...")
run_command(
"sudo sed -i 's/#net.ipv4.ip_forward=1/net.ipv4.ip_forward=1/' /etc/sysctl.conf",
"Failed to update sysctl.conf",
shell=True
)
run_command(
"sudo sysctl -p",
"Failed to apply sysctl changes",
shell=True
)
# Apply NAT rules
print("Applying NAT rules...")
nat_rules = [
"sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE",
"sudo iptables -A FORWARD -i wg0 -j ACCEPT",
"sudo iptables -A FORWARD -o wg0 -j ACCEPT"
]
for rule in nat_rules:
run_command(rule, f"Failed to apply iptables rule: {rule}", shell=True)
# Update nameserver
print("Updating nameserver...")
run_command(
"sudo sed -i 's/nameserver .*/nameserver 1.1.1.1/' /etc/resolv.conf",
"Failed to update nameserver",
shell=True
)
# Install iptables-persistent and save rules
print("Making iptables rules persistent...")
run_command(
["apt-get", "install", "-y", "iptables-persistent"],
"Failed to install iptables-persistent"
)
run_command(
"sudo iptables-save | sudo tee /etc/iptables/rules.v4",
"Failed to save iptables rules",
shell=True
)
def main():
if os.geteuid() != 0:
print("❌ This script must be run as root (sudo)")
sys.exit(1)
print("""
🚀 TorGuard WireGuard Manager Installer
=======================================
This installer will set up WireGuard and the Manager web interface.
""")
# System update and upgrade
print_step("📦", "Updating system packages...")
run_command(["apt-get", "update"], "Failed to update package list")
run_command(["apt-get", "upgrade", "-y"], "Failed to upgrade packages")
# Install WireGuard
print_step("🔧", "Installing WireGuard...")
run_command(
["apt-get", "install", "-y", "wireguard", "wireguard-tools"],
"Failed to install WireGuard"
)
# Ensure WireGuard module is loaded at boot
run_command("echo 'wireguard' | sudo tee -a /etc/modules-load.d/wireguard.conf", "Failed to set WireGuard module to load at boot", shell=True)
install_dir = Path("/opt/wireguard-manager")
config_dir = Path("/etc/wireguard-manager")
service_file = Path("/etc/systemd/system/wireguard-manager.service")
# Create directories
print_step("📁", "Creating installation directories...")
install_dir.mkdir(parents=True, exist_ok=True)
config_dir.mkdir(parents=True, exist_ok=True)
# Install Python dependencies
print_step("🐍", "Setting up Python environment...")
run_command(
["apt-get", "install", "-y", "python3-pip", "python3-venv"],
"Failed to install Python tools"
)
# Create virtual environment
run_command(
["python3", "-m", "venv", str(install_dir / "venv")],
"Failed to create virtual environment"
)
# Install Python packages
pip = install_dir / "venv/bin/pip"
run_command(
[str(pip), "install", "flask", "flask-wtf", "cryptography", "bcrypt", "werkzeug", "netifaces", "psutil"],
"Failed to install Python packages"
)
# Upgrade pip and setuptools
run_command(
[str(pip), "install", "--upgrade", "pip", "setuptools"],
"Failed to upgrade pip and setuptools"
)
# Copy application files
print_step("📝", "Installing application files...")
script_dir = Path(__file__).parent.resolve()
files_to_copy = {
"app.py": install_dir / "app.py",
"templates/base.html": install_dir / "templates/base.html",
"templates/index.html": install_dir / "templates/index.html",
"templates/login.html": install_dir / "templates/login.html",
"templates/register.html": install_dir / "templates/register.html",
}
for src, dest in files_to_copy.items():
dest.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(script_dir / src, dest)
# Create static directories and copy files
static_dir = install_dir / "static"
css_dir = static_dir / "css"
js_dir = static_dir / "js"
# Ensure directories exist
css_dir.mkdir(parents=True, exist_ok=True)
js_dir.mkdir(parents=True, exist_ok=True)
# Copy CSS files
css_files = ["bootstrap.min.css", "bootstrap-icons.css"]
for css_file in css_files:
src = script_dir / "static/css" / css_file
dest = css_dir / css_file
if src.exists():
shutil.copy2(src, dest)
# Copy JS files
js_files = ["bootstrap.bundle.min.js"]
for js_file in js_files:
src = script_dir / "static/js" / js_file
dest = js_dir / js_file
if src.exists():
shutil.copy2(src, dest)
# Copy Bootstrap Icons fonts
fonts_dir = static_dir / "fonts"
fonts_dir.mkdir(parents=True, exist_ok=True)
font_files = ["bootstrap-icons.woff", "bootstrap-icons.woff2"]
for font_file in font_files:
src = script_dir / f"static/fonts/{font_file}"
dest = fonts_dir / font_file
if src.exists():
shutil.copy2(src, dest) # Fixed indentation here
# Copy logo.png
if (script_dir / "static/logo.png").exists():
shutil.copy2(script_dir / "static/logo.png", static_dir / "logo.png")
# Set permissions
print_step("🔒", "Setting secure permissions...")
run_command(["chown", "-R", "root:root", str(install_dir)], "Failed to set ownership")
run_command(["chmod", "-R", "755", str(install_dir)], "Failed to set permissions")
run_command(["chmod", "700", str(config_dir)], "Failed to set config directory permissions")
# Configure WireGuard system settings
setup_wireguard()
# Create systemd service for web interface
print_step("⚙️", "Creating web interface service...")
service_content = f"""[Unit]
Description=TorGuard WireGuard Manager
After=network.target
Wants=network-online.target
[Service]
Type=simple
User=root
WorkingDirectory={install_dir}
Environment=PATH={install_dir}/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ExecStart={install_dir}/venv/bin/python3 {install_dir}/app.py
Restart=always
RestartSec=3
TimeoutStartSec=0
# Hardening
ProtectSystem=full
ReadWritePaths={install_dir} /etc/wireguard /etc/wireguard-manager
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW
[Install]
WantedBy=multi-user.target
"""
service_file.write_text(service_content)
# Enable and start web interface service
print_step("🎯", "Starting web interface...")
run_command(["systemctl", "enable", "wireguard-manager"], "Failed to enable web interface service")
run_command(["systemctl", "restart", "wireguard-manager"], "Failed to start web interface service")
# Enable UFW and allow required ports
print_step("🛡️", "Configuring firewall...")
run_command(["apt-get", "install", "-y", "ufw"], "Failed to install UFW")
run_command(["ufw", "allow", "1337/tcp"], "Failed to allow port 1337")
run_command(["ufw", "--force", "enable"], "Failed to enable UFW")
# Final instructions
local_ip = get_local_ip()
print(f"""
Installation Complete!
==========================
WireGuard Manager has been installed successfully!
📱 Access the web interface at:
http://{local_ip}:1337
💡 On first access, you'll be prompted to create an admin account.
System Configuration:
- WireGuard is installed and ready
- IP forwarding is enabled
- NAT rules are configured
- DNS is set to 1.1.1.1
- UFW is enabled and port 1337 is open
- Web interface will start automatically on boot
📝 Important locations:
- WireGuard config: /etc/wireguard/wg0.conf
- Manager config: {config_dir}
- Web interface: {install_dir}
Security Notes:
- Make sure port 1337 is only accessible from trusted networks
- Use a strong password for your admin account
- Keep your system and packages updated
Need help? Visit https://torguard.net/support
""")
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

@ -0,0 +1,243 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="robots" content="noindex, nofollow">
<meta name="referrer" content="no-referrer">
<!-- Security headers -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'self';
img-src 'self' data:;
style-src 'self' 'unsafe-inline';
script-src 'self' 'unsafe-inline';
font-src 'self' data:;
connect-src 'self';">
<meta http-equiv="X-Content-Type-Options" content="nosniff">
<meta http-equiv="X-Frame-Options" content="DENY">
<title>TorGuard WireGuard Manager</title>
<!-- Bootstrap CSS and Icons (Local) -->
<link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/bootstrap-icons.css') }}" rel="stylesheet">
<!-- Custom Styles -->
<style>
:root {
--primary-color: #0d6efd;
--danger-color: #dc3545;
--success-color: #198754;
}
body {
background-color: #f8f9fa;
min-height: 100vh;
display: flex;
flex-direction: column;
}
.container {
max-width: 800px;
flex: 1;
}
.logo {
max-width: 300px;
margin: 2rem 0;
height: auto;
}
.status-badge {
font-size: 1.1rem;
padding: 0.5rem 1rem;
transition: all 0.3s ease;
}
.form-container {
background: #ffffff;
padding: 2rem;
border-radius: 10px;
box-shadow: 0 0 20px rgba(0,0,0,0.1);
margin-bottom: 2rem;
}
.btn {
transition: all 0.2s ease;
}
.btn:hover {
transform: translateY(-1px);
}
.alert {
border-radius: 8px;
margin-bottom: 1rem;
}
.alert-info {
background-color: #cff4fc;
border-color: #b6effb;
}
.alert-danger {
background-color: #f8d7da;
border-color: #f5c2c7;
}
.alert-success {
background-color: #d1e7dd;
border-color: #badbcc;
}
/* Loading spinner */
.spinner-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 9999;
justify-content: center;
align-items: center;
}
.spinner-container {
background: white;
padding: 2rem;
border-radius: 10px;
text-align: center;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.form-container {
padding: 1.5rem;
}
.logo {
max-width: 250px;
}
}
@media (max-width: 576px) {
.form-container {
padding: 1rem;
}
.logo {
max-width: 200px;
}
}
/* Footer */
.footer {
margin-top: auto;
padding: 1rem 0;
text-align: center;
font-size: 0.875rem;
color: #6c757d;
}
</style>
</head>
<body>
<!-- Loading Spinner -->
<div class="spinner-overlay" id="loadingSpinner">
<div class="spinner-container">
<div class="spinner-border text-primary mb-2" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<div>Please wait...</div>
</div>
</div>
<div class="container py-4">
<!-- Header Section -->
{% if session.authenticated %}
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<span class="text-muted">Welcome, {{ session.username }}</span>
</div>
<div>
<a href="{{ url_for('logout') }}" class="btn btn-outline-danger">
<i class="bi bi-box-arrow-right"></i> Logout
</a>
</div>
</div>
{% endif %}
<!-- Logo Section -->
<div class="text-center">
<img src="{{ url_for('static', filename='logo.png') }}"
alt="TorGuard Logo"
class="logo"
onerror="this.onerror=null; this.src='data:image/svg+xml,<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 100 40\'><rect width=\'100\' height=\'40\' fill=\'%23f8f9fa\'/><text x=\'50\' y=\'20\' text-anchor=\'middle\' alignment-baseline=\'middle\' font-family=\'Arial\' font-size=\'16\'>TorGuard</text></svg>';">
</div>
<!-- Flash Messages -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category if category != 'message' else 'info' }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
<!-- Main Content -->
{% block content %}{% endblock %}
</div>
<!-- Footer -->
<footer class="footer">
<div class="container">
<p class="mb-0">TorGuard WireGuard Manager</p>
{% if session.authenticated %}
<small>Connected to: {{ request.host }}</small>
{% endif %}
</div>
</footer>
<!-- JavaScript Dependencies (Local) -->
<script src="{{ url_for('static', filename='js/bootstrap.bundle.min.js') }}"></script>
<!-- Common JavaScript -->
<script>
// Show loading spinner on form submissions
document.addEventListener('DOMContentLoaded', function() {
const forms = document.querySelectorAll('form');
const spinner = document.getElementById('loadingSpinner');
forms.forEach(form => {
form.addEventListener('submit', function() {
if (this.checkValidity()) {
spinner.style.display = 'flex';
}
});
});
// Hide alerts after 5 seconds
const alerts = document.querySelectorAll('.alert');
alerts.forEach(alert => {
setTimeout(() => {
const bsAlert = new bootstrap.Alert(alert);
bsAlert.close();
}, 5000);
});
});
// Prevent form resubmission on refresh
if (window.history.replaceState) {
window.history.replaceState(null, null, window.location.href);
}
</script>
</body>
</html>

@ -0,0 +1,264 @@
{% extends "base.html" %}
{% block content %}
<div class="form-container">
<!-- Status Display -->
<div class="text-center mb-4">
<div class="status-container">
<span class="badge status-badge {% if status.status == 'connected' %}bg-success{% elif status.status == 'error' %}bg-warning{% else %}bg-danger{% endif %}">
{{ status.status|title }}
</span>
{% if status.status == 'connected' %}
<div class="mt-2">
<div class="text-success mb-2">
<i class="bi bi-shield-check me-1"></i>VPN Connection Active
</div>
<div class="text-muted">
<small>
<i class="bi bi-clock me-1"></i>Connected since: {{ status.connected_since }}<br>
<i class="bi bi-arrow-up me-1"></i>Upload: {{ status.tx }}
<i class="bi bi-arrow-down ms-2 me-1"></i>Download: {{ status.rx }}<br>
{% if status.total_tx and status.total_rx %}
<i class="bi bi-graph-up me-1"></i>Total Transfer: ↑{{ status.total_tx }} ↓{{ status.total_rx }}
{% endif %}
</small>
</div>
</div>
{% elif status.status == 'error' %}
<div class="alert alert-warning mt-3">
<i class="bi bi-exclamation-triangle me-2"></i>System Issues Detected:
<ul class="mb-0 mt-2 text-start">
{% for issue in status.issues %}
<li>{{ issue }}</li>
{% endfor %}
</ul>
</div>
{% else %}
<div class="text-muted mt-2">
<small>
<i class="bi bi-info-circle me-1"></i>
{% if status.message %}
{{ status.message }}
{% else %}
VPN is currently disconnected
{% endif %}
</small>
</div>
{% endif %}
</div>
</div>
<!-- Connection Toggle -->
<form method="POST" action="{{ url_for('toggle_vpn') }}" class="mb-4" id="vpnToggleForm">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="d-grid">
<button type="submit"
class="btn {% if status.status == 'connected' %}btn-danger{% else %}btn-success{% endif %} btn-lg"
{% if status.status == 'error' or not WG_CONF_PATH.exists() %}disabled{% endif %}>
{% if status.status == 'connected' %}
<i class="bi bi-power"></i> Disconnect VPN
{% else %}
<i class="bi bi-power"></i> Connect VPN
{% endif %}
</button>
</div>
{% if not WG_CONF_PATH.exists() %}
<div class="text-center mt-2">
<small class="text-muted">
<i class="bi bi-info-circle me-1"></i>Please add a WireGuard configuration first
</small>
</div>
{% endif %}
</form>
<!-- Configuration Section -->
<div class="card mb-4">
<div class="card-header bg-primary text-white d-flex align-items-center">
<i class="bi bi-gear-fill me-2"></i>WireGuard Configuration
{% if WG_CONF_PATH.exists() %}
<span class="badge bg-light text-primary ms-auto">
<i class="bi bi-check-circle me-1"></i>Config Present
</span>
{% endif %}
</div>
<div class="card-body">
<form method="POST" action="{{ url_for('save_config') }}" enctype="multipart/form-data" id="configForm">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="mb-3">
<label for="config_text" class="form-label">
<i class="bi bi-file-text me-2"></i>Paste Configuration
</label>
<textarea
class="form-control font-monospace"
id="config_text"
name="config_text"
rows="10"
placeholder="[Interface]&#10;PrivateKey = ...&#10;Address = ...&#10;&#10;[Peer]&#10;PublicKey = ...&#10;AllowedIPs = ...&#10;Endpoint = ..."
spellcheck="false"
></textarea>
<div class="form-text">
<i class="bi bi-info-circle me-1"></i>Paste your TorGuard WireGuard configuration here
</div>
</div>
<div class="mb-4">
<label for="config_file" class="form-label">
<i class="bi bi-upload me-2"></i>Or Upload Configuration File
</label>
<input
type="file"
class="form-control"
id="config_file"
name="config_file"
accept=".conf,.txt"
>
<div class="form-text">
<i class="bi bi-shield-lock me-1"></i>File will be stored securely with restricted permissions
</div>
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary" id="saveConfigBtn">
<i class="bi bi-save me-2"></i>Save Configuration
</button>
</div>
</form>
</div>
</div>
<!-- Quick Links -->
<div class="text-center">
<a href="https://torguard.net" target="_blank" class="btn btn-outline-secondary">
<i class="bi bi-box-arrow-up-right me-2"></i>Login to TorGuard Portal
</a>
</div>
<!-- Version Info -->
<div class="text-center mt-4">
<small class="text-muted">
<i class="bi bi-info-circle me-1"></i>TorGuard WireGuard Manager v1.0
</small>
</div>
</div>
<!-- Status Update Script -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Function to update status via API
function updateStatus() {
fetch('{{ url_for("get_status_route") }}')
.then(response => response.json())
.then(data => {
const statusContainer = document.querySelector('.status-container');
const statusBadge = statusContainer.querySelector('.status-badge');
// Update badge class
statusBadge.className = 'badge status-badge';
if (data.status === 'connected') {
statusBadge.classList.add('bg-success');
} else if (data.status === 'error') {
statusBadge.classList.add('bg-warning');
} else {
statusBadge.classList.add('bg-danger');
}
// Update badge text
statusBadge.textContent = data.status.charAt(0).toUpperCase() + data.status.slice(1);
// Update status details
const details = document.createElement('div');
if (data.status === 'connected') {
details.innerHTML = `
<div class="mt-2">
<div class="text-success mb-2">
<i class="bi bi-shield-check me-1"></i>VPN Connection Active
</div>
<div class="text-muted">
<small>
<i class="bi bi-clock me-1"></i>Connected since: ${data.connected_since}<br>
<i class="bi bi-arrow-up me-1"></i>Upload: ${data.tx}
<i class="bi bi-arrow-down ms-2 me-1"></i>Download: ${data.rx}<br>
${data.total_tx && data.total_rx ? `<i class="bi bi-graph-up me-1"></i>Total Transfer: ↑${data.total_tx} ↓${data.total_rx}` : ''}
</small>
</div>
</div>`;
} else if (data.status === 'error') {
const issues = data.issues.map(issue => `<li>${issue}</li>`).join('');
details.innerHTML = `
<div class="alert alert-warning mt-3">
<i class="bi bi-exclamation-triangle me-2"></i>System Issues Detected:
<ul class="mb-0 mt-2 text-start">${issues}</ul>
</div>`;
} else {
details.innerHTML = `
<div class="text-muted mt-2">
<small>
<i class="bi bi-info-circle me-1"></i>
${data.message || 'VPN is currently disconnected'}
</small>
</div>`;
}
// Replace existing content
while (statusContainer.childNodes.length > 1) {
statusContainer.removeChild(statusContainer.lastChild);
}
statusContainer.appendChild(details);
// Update toggle button
const toggleBtn = document.querySelector('#vpnToggleForm button');
toggleBtn.className = `btn ${data.status === 'connected' ? 'btn-danger' : 'btn-success'} btn-lg`;
toggleBtn.disabled = data.status === 'error' || !data.config_exists;
toggleBtn.innerHTML = `
<i class="bi bi-power"></i> ${data.status === 'connected' ? 'Disconnect' : 'Connect'} VPN`;
})
.catch(error => console.error('Error updating status:', error));
}
// Update status every 30 seconds if page is visible
setInterval(() => {
if (!document.hidden) {
updateStatus();
}
}, 30000);
// Update when page becomes visible
document.addEventListener('visibilitychange', () => {
if (!document.hidden) {
updateStatus();
}
});
// File upload handling
const configFile = document.getElementById('config_file');
const configText = document.getElementById('config_text');
configFile.addEventListener('change', function() {
if (this.files.length > 0) {
const file = this.files[0];
const reader = new FileReader();
reader.onload = function(e) {
configText.value = e.target.result;
};
reader.readAsText(file);
}
});
// Form submission loading state
document.querySelectorAll('form').forEach(form => {
form.addEventListener('submit', function() {
const btn = this.querySelector('button[type="submit"]');
if (btn) {
btn.disabled = true;
btn.innerHTML = `
<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
${btn.textContent}`;
}
});
});
});
</script>
{% endblock %}

@ -0,0 +1,167 @@
{% extends "base.html" %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-6">
<div class="form-container">
<div class="text-center mb-4">
<h2 class="h3 mb-3">Welcome Back</h2>
<p class="text-muted">Please login to manage your VPN connection</p>
</div>
<form method="POST" class="needs-validation" novalidate autocomplete="off">
<!-- CSRF Protection -->
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<!-- Username Field -->
<div class="mb-3">
<label for="username" class="form-label">
<i class="bi bi-person-fill me-2"></i>Username
</label>
<div class="input-group has-validation">
<span class="input-group-text">
<i class="bi bi-person"></i>
</span>
<input type="text"
class="form-control"
id="username"
name="username"
required
autofocus
autocomplete="username"
pattern="[a-zA-Z0-9_-]{3,20}"
title="Username must be 3-20 characters, using only letters, numbers, underscore, or hyphen"
maxlength="20">
<div class="invalid-feedback">
Please enter a valid username (3-20 characters, letters, numbers, _ or -)
</div>
</div>
</div>
<!-- Password Field -->
<div class="mb-4">
<label for="password" class="form-label">
<i class="bi bi-key-fill me-2"></i>Password
</label>
<div class="input-group has-validation">
<span class="input-group-text">
<i class="bi bi-key"></i>
</span>
<input type="password"
class="form-control"
id="password"
name="password"
required
autocomplete="current-password"
minlength="8"
maxlength="128">
<button class="btn btn-outline-secondary"
type="button"
id="togglePassword"
aria-label="Toggle password visibility">
<i class="bi bi-eye-fill"></i>
</button>
<div class="invalid-feedback">
Password must be at least 8 characters
</div>
</div>
<div class="form-text">
<i class="bi bi-shield-lock me-1"></i>Password is securely encrypted
</div>
</div>
<!-- Rate Limiting Notice -->
{% if attempts_remaining is defined %}
<div class="alert alert-warning" role="alert">
<i class="bi bi-exclamation-triangle me-2"></i>
{{ attempts_remaining }} login attempts remaining
</div>
{% endif %}
<!-- Submit Button -->
<div class="d-grid mb-4">
<button type="submit"
class="btn btn-primary btn-lg"
id="loginButton">
<i class="bi bi-box-arrow-in-right me-2"></i>Login
</button>
</div>
</form>
<!-- TorGuard Portal Link -->
<div class="text-center">
<hr class="my-4">
<a href="https://torguard.net"
target="_blank"
class="btn btn-outline-secondary btn-sm">
<i class="bi bi-box-arrow-up-right me-2"></i>Login to TorGuard Portal
</a>
</div>
</div>
<!-- Security Notice -->
<div class="text-center mt-3">
<small class="text-muted">
<i class="bi bi-shield-lock me-1"></i>
Secure, encrypted connection
</small>
</div>
</div>
</div>
<!-- Login Scripts -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.querySelector('form');
const loginButton = document.getElementById('loginButton');
const togglePassword = document.getElementById('togglePassword');
const password = document.getElementById('password');
const username = document.getElementById('username');
// Password visibility toggle
togglePassword.addEventListener('click', function() {
const icon = this.querySelector('i');
if (password.type === 'password') {
password.type = 'text';
icon.classList.remove('bi-eye-fill');
icon.classList.add('bi-eye-slash-fill');
} else {
password.type = 'password';
icon.classList.remove('bi-eye-slash-fill');
icon.classList.add('bi-eye-fill');
}
});
// Form validation
form.addEventListener('submit', function(event) {
if (!form.checkValidity()) {
event.preventDefault();
event.stopPropagation();
} else {
loginButton.disabled = true;
loginButton.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Logging in...';
}
form.classList.add('was-validated');
});
// Reset validation on input
username.addEventListener('input', function() {
form.classList.remove('was-validated');
});
password.addEventListener('input', function() {
form.classList.remove('was-validated');
});
// Prevent pasting into username field
username.addEventListener('paste', function(e) {
e.preventDefault();
});
// Prevent form resubmission
if (window.history.replaceState) {
window.history.replaceState(null, null, window.location.href);
}
});
</script>
{% endblock %}

@ -0,0 +1,243 @@
{% extends "base.html" %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-6">
<div class="form-container">
<div class="text-center mb-4">
<h2 class="h3 mb-3">Create Administrator Account</h2>
<p class="text-muted">Set up your secure access to TorGuard WireGuard Manager</p>
</div>
<form method="POST" class="needs-validation" novalidate autocomplete="off">
<!-- CSRF Protection -->
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<!-- Username Field -->
<div class="mb-3">
<label for="username" class="form-label">
<i class="bi bi-person-fill me-2"></i>Username
</label>
<div class="input-group has-validation">
<span class="input-group-text">
<i class="bi bi-person"></i>
</span>
<input type="text"
class="form-control"
id="username"
name="username"
required
autofocus
autocomplete="username"
pattern="[a-zA-Z0-9_-]{3,20}"
title="Username must be 3-20 characters, using only letters, numbers, underscore, or hyphen"
maxlength="20">
<div class="valid-feedback">
<i class="bi bi-check-circle me-1"></i>Username available
</div>
<div class="invalid-feedback">
Username must be 3-20 characters, using only letters, numbers, underscore, or hyphen
</div>
</div>
</div>
<!-- Password Field -->
<div class="mb-3">
<label for="password" class="form-label">
<i class="bi bi-key-fill me-2"></i>Password
</label>
<div class="input-group has-validation">
<span class="input-group-text">
<i class="bi bi-key"></i>
</span>
<input type="password"
class="form-control"
id="password"
name="password"
required
autocomplete="new-password"
minlength="8"
maxlength="128"
pattern="^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$"
title="Password must be at least 8 characters and include letters, numbers, and special characters">
<button class="btn btn-outline-secondary"
type="button"
id="togglePassword"
aria-label="Toggle password visibility">
<i class="bi bi-eye-fill"></i>
</button>
</div>
<!-- Password Strength Meter -->
<div class="progress mt-2" style="height: 5px;">
<div class="progress-bar" id="passwordStrength" role="progressbar" style="width: 0%"></div>
</div>
<div class="form-text mt-2">
<div class="password-requirements">
<div id="req-length" class="requirement">
<i class="bi bi-x-circle text-danger"></i> At least 8 characters
</div>
<div id="req-letter" class="requirement">
<i class="bi bi-x-circle text-danger"></i> Contains letters
</div>
<div id="req-number" class="requirement">
<i class="bi bi-x-circle text-danger"></i> Contains numbers
</div>
<div id="req-special" class="requirement">
<i class="bi bi-x-circle text-danger"></i> Contains special characters
</div>
</div>
</div>
</div>
<!-- Confirm Password Field -->
<div class="mb-4">
<label for="confirm_password" class="form-label">
<i class="bi bi-key-fill me-2"></i>Confirm Password
</label>
<div class="input-group has-validation">
<span class="input-group-text">
<i class="bi bi-key"></i>
</span>
<input type="password"
class="form-control"
id="confirm_password"
required
autocomplete="new-password"
maxlength="128">
<div class="valid-feedback">
<i class="bi bi-check-circle me-1"></i>Passwords match
</div>
<div class="invalid-feedback">
Passwords do not match
</div>
</div>
</div>
<!-- Submit Button -->
<div class="d-grid mb-4">
<button type="submit" class="btn btn-primary btn-lg" id="submitBtn">
<i class="bi bi-person-plus-fill me-2"></i>Create Administrator Account
</button>
</div>
</form>
<!-- Security Notices -->
<div class="text-center">
<small class="text-muted d-block mb-2">
<i class="bi bi-shield-lock me-1"></i>
Your credentials will be stored securely using industry-standard encryption
</small>
<small class="text-muted d-block">
<i class="bi bi-info-circle me-1"></i>
This account will have full administrative access to the WireGuard Manager
</small>
</div>
</div>
</div>
</div>
<!-- Registration Scripts -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.querySelector('form');
const password = document.getElementById('password');
const confirmPassword = document.getElementById('confirm_password');
const togglePassword = document.getElementById('togglePassword');
const submitBtn = document.getElementById('submitBtn');
const passwordStrength = document.getElementById('passwordStrength');
// Password visibility toggle
togglePassword.addEventListener('click', function() {
const icon = this.querySelector('i');
if (password.type === 'password') {
password.type = 'text';
icon.classList.remove('bi-eye-fill');
icon.classList.add('bi-eye-slash-fill');
} else {
password.type = 'password';
icon.classList.remove('bi-eye-slash-fill');
icon.classList.add('bi-eye-fill');
}
});
// Password strength checker
function checkPasswordStrength(value) {
let strength = 0;
const requirements = {
length: value.length >= 8,
letter: /[A-Za-z]/.test(value),
number: /\d/.test(value),
special: /[@$!%*#?&]/.test(value)
};
// Update requirement indicators
document.getElementById('req-length').innerHTML = `
<i class="bi bi-${requirements.length ? 'check-circle text-success' : 'x-circle text-danger'}"></i>
At least 8 characters
`;
document.getElementById('req-letter').innerHTML = `
<i class="bi bi-${requirements.letter ? 'check-circle text-success' : 'x-circle text-danger'}"></i>
Contains letters
`;
document.getElementById('req-number').innerHTML = `
<i class="bi bi-${requirements.number ? 'check-circle text-success' : 'x-circle text-danger'}"></i>
Contains numbers
`;
document.getElementById('req-special').innerHTML = `
<i class="bi bi-${requirements.special ? 'check-circle text-success' : 'x-circle text-danger'}"></i>
Contains special characters
`;
// Calculate strength
strength += requirements.length ? 25 : 0;
strength += requirements.letter ? 25 : 0;
strength += requirements.number ? 25 : 0;
strength += requirements.special ? 25 : 0;
// Update strength bar
passwordStrength.style.width = strength + '%';
if (strength < 50) {
passwordStrength.className = 'progress-bar bg-danger';
} else if (strength < 75) {
passwordStrength.className = 'progress-bar bg-warning';
} else {
passwordStrength.className = 'progress-bar bg-success';
}
}
// Password input handler
password.addEventListener('input', function() {
checkPasswordStrength(this.value);
if (confirmPassword.value) {
confirmPassword.dispatchEvent(new Event('input'));
}
});
// Confirm password validation
confirmPassword.addEventListener('input', function() {
if (this.value !== password.value) {
this.setCustomValidity('Passwords do not match');
} else {
this.setCustomValidity('');
}
});
// Form submission
form.addEventListener('submit', function(event) {
if (!form.checkValidity()) {
event.preventDefault();
event.stopPropagation();
} else {
submitBtn.disabled = true;
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Creating Account...';
}
form.classList.add('was-validated');
});
// Prevent form resubmission
if (window.history.replaceState) {
window.history.replaceState(null, null, window.location.href);
}
});
</script>
{% endblock %}

@ -0,0 +1,153 @@
#!/usr/bin/env python3
"""
TorGuard WireGuard Manager Uninstaller
Completely removes the WireGuard Manager, its configurations, services, and dependencies.
"""
import os
import sys
import shutil
import subprocess
from pathlib import Path
def print_step(emoji, message):
"""Print a step with emoji and message"""
print(f"\n{emoji} {message}")
def run_command(command, error_message, shell=False):
"""Run a shell command and handle errors"""
try:
if shell:
subprocess.run(command, check=True, shell=True)
else:
subprocess.run(command, check=True)
print("✅ Done!")
return True
except subprocess.CalledProcessError as e:
print(f"❌ Error: {error_message}")
if hasattr(e, 'stderr') and e.stderr:
print(f"Details: {e.stderr.decode()}")
return False
def remove_firewall_rules():
"""Remove firewall rules related to WireGuard Manager"""
print_step("🛑", "Removing firewall rules...")
rules = [
"ufw delete allow 1337/tcp",
"ufw --force disable"
]
for rule in rules:
run_command(rule, "Failed to remove firewall rule", shell=True)
def stop_and_disable_services():
"""Stops and removes the WireGuard Manager system service"""
print_step("🛑", "Stopping and disabling WireGuard Manager service...")
service_name = "wireguard-manager"
run_command(f"systemctl stop {service_name}", "Failed to stop WireGuard Manager service", shell=True)
run_command(f"systemctl disable {service_name}", "Failed to disable WireGuard Manager service", shell=True)
run_command(f"rm -f /etc/systemd/system/{service_name}.service", "Failed to remove service file", shell=True)
run_command("systemctl daemon-reload", "Failed to reload systemd", shell=True)
def remove_wireguard():
"""Removes WireGuard and its configurations"""
print_step("🗑️", "Removing WireGuard and its configurations...")
# Remove WireGuard kernel module
run_command("modprobe -r wireguard", "Failed to remove WireGuard kernel module", shell=True)
# Uninstall WireGuard packages
run_command("apt-get remove --purge -y wireguard wireguard-tools", "Failed to uninstall WireGuard", shell=True)
# Remove WireGuard configurations
run_command("rm -rf /etc/wireguard", "Failed to remove WireGuard configurations", shell=True)
def reset_network_config():
"""Resets networking configurations made by the installer"""
print_step("🔄", "Resetting network configurations...")
# Reset IP forwarding
run_command("sed -i 's/net.ipv4.ip_forward=1/#net.ipv4.ip_forward=1/' /etc/sysctl.conf", "Failed to reset sysctl.conf", shell=True)
run_command("sysctl -p", "Failed to apply sysctl changes", shell=True)
# Reset NAT rules
run_command("iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE", "Failed to remove NAT rule", shell=True)
run_command("iptables -D FORWARD -i wg0 -j ACCEPT", "Failed to remove iptables FORWARD rule", shell=True)
run_command("iptables -D FORWARD -o wg0 -j ACCEPT", "Failed to remove iptables FORWARD rule", shell=True)
# Reset iptables rules to default
run_command("iptables -F", "Failed to flush iptables rules", shell=True)
run_command("iptables -X", "Failed to delete custom chains", shell=True)
run_command("iptables -t nat -F", "Failed to flush NAT rules", shell=True)
run_command("iptables -t nat -X", "Failed to delete custom NAT chains", shell=True)
# Remove iptables-persistent package
run_command("apt-get remove --purge -y iptables-persistent", "Failed to remove iptables-persistent", shell=True)
def remove_wireguard_manager():
"""Deletes all files and directories related to the WireGuard Manager"""
print_step("🗑️", "Removing WireGuard Manager files...")
install_dir = Path("/opt/wireguard-manager")
config_dir = Path("/etc/wireguard-manager")
# Delete directories
if install_dir.exists():
shutil.rmtree(install_dir, ignore_errors=True)
if config_dir.exists():
shutil.rmtree(config_dir, ignore_errors=True)
# Remove WireGuard kernel module auto-load setting
run_command("rm -f /etc/modules-load.d/wireguard.conf", "Failed to remove WireGuard module auto-load config", shell=True)
def remove_python_env():
"""Removes the Python virtual environment"""
print_step("🐍", "Removing Python virtual environment...")
venv_path = Path("/opt/wireguard-manager/venv")
if venv_path.exists():
shutil.rmtree(venv_path, ignore_errors=True)
# Remove Python dependencies
run_command("apt-get remove --purge -y python3-pip python3-venv", "Failed to remove Python dependencies", shell=True)
def final_cleanup():
"""Performs final cleanup and system reset"""
print_step("🧹", "Performing final cleanup...")
# Clear APT cache
run_command("apt-get autoremove -y", "Failed to remove unnecessary packages", shell=True)
run_command("apt-get clean", "Failed to clean package cache", shell=True)
def main():
if os.geteuid() != 0:
print("❌ This script must be run as root (sudo)")
sys.exit(1)
print("""
🛑 TorGuard WireGuard Manager Uninstaller
========================================
This will completely remove WireGuard Manager, its configurations, services, and dependencies.
""")
confirmation = input("⚠️ Are you sure you want to proceed? This action is irreversible! (yes/no): ").strip().lower()
if confirmation != "yes":
print("❌ Uninstallation aborted.")
sys.exit(1)
stop_and_disable_services()
remove_firewall_rules()
remove_wireguard()
reset_network_config()
remove_wireguard_manager()
remove_python_env()
final_cleanup()
print("""
Uninstallation Complete!
==========================
TorGuard WireGuard Manager and all associated configurations have been successfully removed.
If you need to reinstall, run the installer script again.
""")
if __name__ == "__main__":
main()
Loading…
Cancel
Save