Files
bikinibottom/Divers/wireguard_helper/wg_helper.py
2026-01-20 09:06:51 +01:00

987 lines
28 KiB
Python

from flask import Flask, request, render_template_string, redirect, url_for, session, Response
import os, subprocess, shutil, sys, json
if os.geteuid() != 0:
print("This script needs to be run as root or sudo, try 'sudo python3 wg_helper.py'")
sys.exit(1)
# ===================== #
ADMIN_PASSWORD = ""
# ===================== #
app = Flask(__name__)
app.secret_key = os.urandom(24)
wireguard_dir = "/etc/wireguard"
config_file = "wg_helper.json"
full_config_file_path = os.path.join(wireguard_dir, config_file)
SYSCTL_CONF = "/etc/sysctl.conf"
default_config = {
"server": {
"server_network_interface": subprocess.run("ip -o -4 route show to default | awk '{print $5}'", shell=True, capture_output=True, text=True).stdout.strip(),
"Endpoint": "",
"ListenPort": 51820,
"PrivateKey": "",
"PublicKey": ""
},
"peers": [
]
}
# Common CSS for all pages
CSS = """
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f5f7fa;
padding: 0;
margin: 0;
}
.container {
width: 95%;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.header {
background-color: #2c3e50;
color: white;
padding: 15px 0;
margin-bottom: 20px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 {
margin: 0;
font-size: 24px;
}
.navbar {
text-align: right;
}
.navbar a {
color: white;
text-decoration: none;
padding: 8px 12px;
border-radius: 4px;
transition: background-color 0.3s;
}
.navbar a:hover {
background-color: rgba(255,255,255,0.1);
}
.card {
background-color: white;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.card h2 {
color: #2c3e50;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.card strong {
color: #34495e;
}
.divider {
height: 1px;
background-color: #e1e4e8;
margin: 20px 0;
}
input[type="text"],
input[type="password"] {
width: 100%;
padding: 10px;
margin: 8px 0;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}
.btn, input[type="submit"] {
background-color: #3498db;
color: white;
border: none;
padding: 8px 15px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
margin-right: 5px;
transition: background-color 0.3s;
}
.btn:hover, input[type="submit"]:hover {
background-color: #2980b9;
}
.btn-small {
padding: 5px 10px;
font-size: 12px;
}
.btn-danger {
background-color: #e74c3c;
}
.btn-danger:hover {
background-color: #c0392b;
}
.btn-success {
background-color: #2ecc71;
}
.btn-success:hover {
background-color: #27ae60;
}
.status-item {
margin-bottom: 10px;
display: flex;
align-items: center;
flex-wrap: wrap;
}
.status-item form {
margin-left: 10px;
}
.login-form {
max-width: 400px;
margin: 100px auto;
background-color: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
}
.login-form h2 {
text-align: center;
margin-bottom: 20px;
color: #2c3e50;
}
.login-form input[type="submit"] {
width: 100%;
padding: 10px;
margin-top: 15px;
}
.error-message {
color: #e74c3c;
margin-top: 10px;
text-align: center;
}
.peer-item {
background-color: #f8f9fa;
border-left: 4px solid #3498db;
padding: 15px;
margin-bottom: 15px;
border-radius: 4px;
}
.peer-item form {
margin-top: 10px;
}
.peer-actions {
margin-top: 10px;
display: flex;
gap: 5px;
}
.option-row {
display: flex;
align-items: center;
margin-bottom: 15px;
}
.option-row strong {
min-width: 150px;
}
.option-row form {
flex-grow: 1;
display: flex;
gap: 10px;
}
.option-row input[type="text"] {
flex-grow: 1;
}
.success {
color: #2ecc71;
}
.danger {
color: #e74c3c;
}
.warning {
color: #f39c12;
}
@media (max-width: 768px) {
.option-row {
flex-direction: column;
align-items: flex-start;
}
.option-row strong {
margin-bottom: 5px;
}
.option-row form {
width: 100%;
}
}
"""
@app.route("/", methods=["GET", "POST"])
def login():
LOGIN_HTML = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WireGuard Helper - Login</title>
<style>
""" + CSS + """
</style>
</head>
<body>
<div class="container">
<div class="login-form">
<h2>SeaBee's WireGuard Helper</h2>
<form method="POST">
<div>
<input type="password" name="password" placeholder="Enter admin password" required autofocus>
</div>
<div>
<input type="submit" value="Login" class="btn">
</div>
{% if error %}
<div class="error-message">{{ error }}</div>
{% endif %}
</form>
</div>
</div>
</body>
</html>
"""
if request.method == "POST":
if request.form.get("password") == ADMIN_PASSWORD:
session["logged_in"] = True
return redirect(url_for("dashboard"))
else:
return render_template_string(LOGIN_HTML, error="Incorrect password, please try again")
return render_template_string(LOGIN_HTML, error=None)
@app.route("/dashboard")
def dashboard():
ensure_logged_in()
config_data = load_config()
# One liners
is_wg_installed = shutil.which("wg") is not None
is_ufw_installed = ufw_get_path()
is_iptables_installed = iptables_get_path()
server_network_interface = config_data.get("server", {}).get("server_network_interface", "")
# Status indicators
wg_installed_status = "Installed" if is_wg_installed else "Not installed"
wg_installed_class = "success" if is_wg_installed else "danger"
install_wg_button = ""
if not is_wg_installed:
install_wg_button = """
<form action="/install_wireguard" method="POST" style='display:inline'>
<input type="submit" value="Install WireGuard" class="btn btn-success">
</form>
"""
ufw_allow_port_button = """
<form action='/ufw_open_port' method='POST' style='display:inline'>
<input type='submit' value='Open Port 51820' class="btn btn-success">
</form>
"""
ufw_installed_status = "Installed" if is_ufw_installed else "Not installed"
ufw_installed_class = "success" if is_ufw_installed else "warning"
if is_ufw_installed:
ufw_path = ufw_get_path()
ufw_enabled_status = subprocess.run(f"{ufw_path} status | awk '/Status:/ {{print $2}}'", shell=True, capture_output=True, text=True).stdout.strip()
if ufw_enabled_status == "active":
ufw_enabled_status = "active"
ufw_enabled_class = "success"
else:
ufw_enabled_status = "disabled"
ufw_enabled_class = "warning"
else:
ufw_enabled_status = ""
ufw_enabled_class = ""
iptables_installed_status = "Installed" if is_iptables_installed else "Not installed"
iptables_installed_class = "success" if is_iptables_installed else "danger"
if not is_iptables_installed:
iptables_install_button = """
<form action="/install_iptables" method="POST" style='display:inline'>
<input type="submit" value="Install iptables" class="btn btn-success">
</form>
"""
else:
iptables_install_button = ""
wg_running_status = ""
wg_running_class = ""
wg_running_button = ""
if is_wg_installed:
wg_running_check = subprocess.run("systemctl is-active wg-quick@wg0", shell=True, capture_output=True, text=True).stdout.strip()
config_data = load_config()
server_priv_key = config_data.get("server", {}).get("PrivateKey", "")
if server_priv_key == "":
wg_running_status = "Server keys need to be generated first"
wg_running_class = "warning"
else:
if wg_running_check == "failed":
wg_running_status = "Not running"
wg_running_class = "danger"
wg_running_button = """
<form action='/start_wg' method='POST' style='display:inline'>
<input type='submit' value='Start wireguard' class="btn btn-success">
</form>
"""
else:
wg_running_status = "Running"
wg_running_class = "success"
else:
wg_running_status = "Not running, waiting for install"
wg_running_class = "warning"
wg_enabled_at_boot_status = ""
wg_enabled_at_boot_class = ""
wg_enabled_at_boot_button = ""
wg_enabled_at_boot_check = subprocess.run(f"systemctl is-enabled wg-quick@wg0", shell=True, capture_output=True, text=True).stdout.strip()
if wg_enabled_at_boot_check == "enabled":
wg_enabled_at_boot_status = "Enabled"
wg_enabled_at_boot_class = "success"
else:
wg_enabled_at_boot_status = "DISABLED"
wg_enabled_at_boot_class = "danger"
wg_enabled_at_boot_button = """
<form action='/autostart_wg_on_boot' method='POST' style='display:inline'>
<input type='submit' value='Autostart Wireguard at boot' class="btn btn-success">
</form>
"""
ufw_port_status = ""
ufw_port_class = ""
if is_ufw_installed and ufw_enabled_status == "active":
ufw_path = ufw_get_path()
ufw_wg_port_check = subprocess.run(f"{ufw_path} status | awk '/51820/ {{print $2; exit}}'", shell=True, capture_output=True, text=True).stdout.strip()
if ufw_wg_port_check == "":
ufw_port_status = "Not added"
ufw_port_class = "danger"
ufw_port_button = ufw_allow_port_button
else:
ufw_port_status = "Allowed" if ufw_wg_port_check == "ALLOW" else "Denied"
ufw_port_class = "success" if ufw_wg_port_check == "ALLOW" else "danger"
ufw_port_button = "" if ufw_wg_port_check == "ALLOW" else ufw_allow_port_button
else:
ufw_port_status = "N/A"
ufw_port_class = "warning"
ufw_port_button = ""
# Endpoint data
server_endpoint_data = config_data.get("server", {}).get("Endpoint", "")
if server_endpoint_data == "":
server_endpoint_form = """
<form action="/update_server_endpoint" method="POST" class="option-form">
<input type="text" placeholder="Set server endpoint" name='endpoint' required>
<input type="submit" value="Set endpoint" class="btn btn-success">
</form>
"""
else:
server_endpoint_form = f"""
<span>{server_endpoint_data}</span>
<form action="/update_server_endpoint" method="POST" class="option-form">
<input type="text" placeholder='Enter new endpoint' name='endpoint' required>
<input type="submit" value="Update endpoint" class="btn">
</form>
"""
# Server public key
server_public_key = config_data.get("server", {}).get("PublicKey", "")
if server_public_key == "":
server_public_key_form = f"""
<span class="warning">No Keys set</span>
<form action="/regenerate_server_keys" method="POST" class="option-form">
<input type="submit" value="Generate server keys" class="btn btn-success">
</form>
"""
else:
server_public_key_form = f"""
<code class="success">{server_public_key}</code>
<form action="/regenerate_server_keys" method="POST" class="option-form">
<input type="submit" value="Regenerate server keys" class="btn">
</form>
"""
# Peers list
peers_list = ""
peer_config = config_data.get("peers", [])
for peer in peer_config:
peer_id = peer["id"]
peer_name = peer["name"]
peer_public_key = peer["PublicKey"]
peers_list += f"""
<div class="peer-item">
<div><strong>ID:</strong> {peer_id}</div>
<div><strong>Name:</strong> {peer_name}</div>
<div><strong>Public Key:</strong> <code>{peer_public_key}</code></div>
<div class="peer-actions">
<form action='/delete_peer' method='POST'>
<input type='hidden' name='peer_id_to_delete' value='{peer_id}'>
<input type='submit' value='Delete peer' class="btn btn-danger">
</form>
<form action='/download_peer_config' method='POST'>
<input type='hidden' name='peer_id' value='{peer_id}'>
<input type='submit' value='Download config file' class="btn">
</form>
</div>
</div>"""
html = f"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SeaBee's WG Helper</title>
<style>
{CSS}
</style>
</head>
<body>
<div class="header">
<div class="container header-content">
<h1>SeaBee's Wireguard Helper v0.2</h1>
<div class="navbar">
<a href="/logout">Logout</a>
</div>
</div>
</div>
<div class="container">
<div class="card">
<h2>System Status</h2>
<div class="status-item">
<strong>Wireguard software:</strong>&nbsp;
<span class="{wg_installed_class}">{wg_installed_status}</span>
{install_wg_button}
</div>
<div class="status-item">
<strong>Wireguard autostart at boot:</strong>&nbsp;
<span class="{wg_enabled_at_boot_class}">{wg_enabled_at_boot_status}</span>
{wg_enabled_at_boot_button}
</div>
<div class="status-item">
<strong>UFW:</strong>&nbsp;
<span class="{ufw_installed_class}">{ufw_installed_status}</span>
{' &nbsp;&&nbsp; ' + '<span class="' + ufw_enabled_class + '">' + ufw_enabled_status + '</span>' if ufw_enabled_status else ''}
</div>
{f'''
<div class="status-item">
<strong>UFW Wireguard Port:</strong>&nbsp;
<span class="{ufw_port_class}">{ufw_port_status}</span>
{ufw_port_button}
</div>
''' if ufw_port_status else ''}
<div class="status-item">
<strong>iptables:</strong>&nbsp;
<span class="{iptables_installed_class}">{iptables_installed_status}</span>
{iptables_install_button}
</div>
<div class="status-item">
<strong>Server network interface:</strong>&nbsp;
<span>{server_network_interface}</span>
</div>
<div class="status-item">
<strong>Wireguard status:</strong>&nbsp;
<span class="{wg_running_class}">{wg_running_status}</span>
{wg_running_button}
</div>
</div>
<div class="card">
<h2>Server Configuration</h2>
<div class="option-row">
<strong>Server endpoint:</strong>
<div>{server_endpoint_form}</div>
</div>
<div class="option-row">
<strong>Server Public key:</strong>
<div>{server_public_key_form}</div>
</div>
</div>
<div class="card">
<h2>Peer Management</h2>
<div class="status-item" style="margin-bottom: 20px;">
<strong>Create new peer:</strong>
<form action="/create_new_peer" method="POST" style="display:flex; gap:10px; margin-left:10px;">
<input type="text" placeholder="Peer name" name="peer_name" required>
<input type="submit" value="Create" class="btn btn-success">
</form>
</div>
{peers_list if peers_list else '<p>No peers configured yet. Create your first peer above.</p>'}
</div>
</div>
</body>
</html>
"""
return html
@app.route("/logout")
def logout():
session.clear()
return redirect(url_for("login"))
def ensure_logged_in():
if not session.get("logged_in"):
return redirect(url_for("login"))
@app.route("/install_wireguard", methods=["POST"])
def install_wireguard():
ensure_logged_in()
try:
subprocess.check_call(["sudo", "apt", "install", "-y", "wireguard"])
return redirect(url_for("dashboard"))
except subprocess.CalledProcessError as e:
error_html = f"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Installation Error</title>
<style>{CSS}</style>
</head>
<body>
<div class="container">
<div class="card">
<h2>Installation Failed</h2>
<p class="danger">Error: {e}</p>
<a href="/dashboard" class="btn">Back to Dashboard</a>
</div>
</div>
</body>
</html>
"""
return error_html
@app.route("/install_iptables", methods=["POST"])
def install_iptables():
ensure_logged_in()
try:
subprocess.check_call(["sudo", "apt", "install", "-y", "iptables"])
return redirect(url_for("dashboard"))
except subprocess.CalledProcessError as e:
error_html = f"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Installation Error</title>
<style>{CSS}</style>
</head>
<body>
<div class="container">
<div class="card">
<h2>Installation Failed</h2>
<p class="danger">Error: {e}</p>
<a href="/dashboard" class="btn">Back to Dashboard</a>
</div>
</div>
</body>
</html>
"""
return error_html
@app.route("/update_server_endpoint", methods=["POST"])
def update_server_endpoint():
ensure_logged_in()
new_endpoint = request.form.get("endpoint", "").strip()
config_data = load_config()
config_data.setdefault("server", {})["Endpoint"] = new_endpoint
update_config(config_data)
return redirect(url_for("dashboard"))
@app.route("/ufw_open_port", methods=["POST"])
def ufw_open_port():
ensure_logged_in()
ufw_path = ufw_get_path()
try:
subprocess.check_call([ufw_path, "allow", "51820", "comment", '"Wireguard"'])
except subprocess.CalledProcessError as e:
error_html = f"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>UFW Error</title>
<style>{CSS}</style>
</head>
<body>
<div class="container">
<div class="card">
<h2>UFW Configuration Failed</h2>
<p class="danger">Error: {e}</p>
<a href="/dashboard" class="btn">Back to Dashboard</a>
</div>
</div>
</body>
</html>
"""
return error_html
return redirect(url_for("dashboard"))
@app.route("/regenerate_server_keys", methods=["POST"])
def regenerate_server_keys():
private_key = subprocess.check_output(["wg", "genkey"], text=True).strip()
public_key = subprocess.check_output(["wg", "pubkey"], input=private_key, text=True).strip()
config_data = load_config()
config_data.setdefault("server", {})["PrivateKey"] = private_key
config_data.setdefault("server", {})["PublicKey"] = public_key
update_config(config_data)
return redirect(url_for("dashboard"))
@app.route("/create_new_peer", methods=["POST"])
def create_new_peer():
new_peer_name = request.form.get("peer_name", "").strip()
config_data = load_config()
peers = config_data.get("peers", [])
existing_ids = sorted(peer["id"] for peer in peers)
# Find available ID by checking gaps
existing_ids = sorted(peer["id"] for peer in peers) # Get all assigned IDs, sorted
# Start from 2, find the first missing number
new_id = 2
for id in existing_ids:
if id == new_id:
new_id += 1
else:
break # Found a gap, use it
# Generate new keys
private_key = subprocess.check_output(["wg", "genkey"], text=True).strip()
public_key = subprocess.check_output(["wg", "pubkey"], input=private_key, text=True).strip()
# Create new peer info
new_peer = {
"id": new_id,
"name": new_peer_name,
"PrivateKey": private_key,
"PublicKey": public_key
}
peers.append(new_peer)
peers = sorted(peers, key=lambda peer: peer.get("id", 0))
config_data["peers"] = peers
update_config(config_data)
return redirect(url_for("dashboard"))
@app.route("/delete_peer", methods=["POST"])
def delete_peer():
peer_id_to_delete = request.form.get("peer_id_to_delete", "").strip()
peer_id_to_delete = int(peer_id_to_delete)
config_data = load_config()
peer_config = config_data.get("peers", [])
# Literally just set the peers to itself minus the peer ID requested
new_peer_config = [peer for peer in peer_config if peer["id"] != peer_id_to_delete]
new_peer_config = sorted(new_peer_config, key=lambda peer: peer.get("id", 0))
config_data["peers"] = new_peer_config
update_config(config_data)
return redirect(url_for("dashboard"))
@app.route("/download_peer_config", methods=["POST"])
def download_peer_config():
peer_id_to_download = request.form.get("peer_id", "").strip()
peer_id_to_download = int(peer_id_to_download)
config_data = load_config()
peer_config = config_data.get("peers", [])
selected_peer = None
for peer in peer_config:
if peer.get("id") == peer_id_to_download:
selected_peer = peer
break
if not selected_peer:
return "Peer not found", 404
peer_name = selected_peer["name"]
peer_pub_key = selected_peer["PublicKey"]
peer_priv_key = selected_peer["PrivateKey"]
server_pub_key = config_data["server"]["PublicKey"]
endpoint = config_data["server"]["Endpoint"]
listen_port = config_data["server"]["ListenPort"]
interface = config_data["server"]["server_network_interface"]
# Compose the WireGuard peer config
config_text = f"""[Interface]
PrivateKey = {peer_priv_key}
Address = 10.0.0.{peer_id_to_download}/32
DNS = 1.1.1.1
[Peer]
PublicKey = {server_pub_key}
Endpoint = {endpoint}:{listen_port}
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
"""
# Create response with downloadable config file
filename = f"peer_{peer_name}.conf"
return Response(
config_text,
mimetype="text/plain",
headers={"Content-Disposition": f"attachment; filename={filename}"}
)
@app.route("/start_wg", methods=["POST"])
def start_wg():
subprocess.run(["wg-quick", "up", "wg0"], text=True)
return redirect(url_for("dashboard"))
@app.route("/autostart_wg_on_boot", methods=["POST"])
def enable_wg_at_boot():
subprocess.run(["systemctl", "enable", "wg-quick@wg0"], text=True)
return redirect(url_for("dashboard"))
def ufw_get_path():
ufw_dirs = ["/bin", "/sbin", "/usr/bin", "/usr/sbin", "/usr/local/bin", "/usr/local/sbin"]
for dir in ufw_dirs:
path = os.path.join(dir, "ufw")
if os.path.isfile(path) and os.access(path, os.X_OK):
return path
return None
def iptables_get_path():
ufw_dirs = ["/sbin", "/usr/sbin", "/bin", "/usr/bin", "/usr/local/sbin", "/usr/local/bin"]
for dir in ufw_dirs:
path = os.path.join(dir, "iptables")
if os.path.isfile(path) and os.access(path, os.X_OK):
return path
return None
def run_first_install_setup():
# Create /etc/wireguard folder
if not os.path.exists(wireguard_dir):
os.mkdir(wireguard_dir)
# Create config json
if not os.path.isfile(full_config_file_path):
with open(full_config_file_path, "w") as f:
json.dump(default_config, f, indent=4)
# Create wg0.conf
if not os.path.isfile("/etc/wireguard/wg0.conf"):
update_config(load_config())
# Port forward
update_sysctl()
def update_sysctl():
try:
with open(SYSCTL_CONF, "r") as file:
lines = file.readlines()
modified = False
found = False
for i in range(len(lines)):
if "net.ipv4.ip_forward=" in lines[i]:
found = True
if lines[i].strip().startswith("#"): # If commented, uncomment
lines[i] = lines[i].lstrip("#") # Remove leading #
modified = True
if lines[i].strip() != "net.ipv4.ip_forward=1": # Ensure it's set to 1
lines[i] = "net.ipv4.ip_forward=1\n"
modified = True
if not found: # If not found, append it
lines.append("\nnet.ipv4.ip_forward=1\n")
modified = True
if modified:
with open(SYSCTL_CONF, "w") as file:
file.writelines(lines)
os.system("sudo sysctl -p") # Apply changes
print("Updated sysctl.conf and applied changes.")
else:
print("No changes needed.")
except Exception as e:
print(f"Error: {e}")
def load_config():
try:
with open(full_config_file_path, "r") as f:
return json.load(f)
except:
return(0)
def update_config(config_data):
with open(full_config_file_path, "w") as f:
json.dump(config_data, f, indent=4)
peers = config_data.get("peers", [])
server_priv_key = config_data.get("server", {}).get("PrivateKey", "")
server_pub_key = config_data.get("server", {}).get("PublicKey", "")
server_endpoint = config_data.get("server", {}).get("Endpoint", "")
server_network_interface = config_data.get("server", {}).get("server_network_interface", "")
wg_config_content = f"""[Interface]
Address = 10.0.0.1/24
SaveConfig = true
PostUp = {iptables_get_path()} -A FORWARD -i %i -j ACCEPT; {iptables_get_path()} -t nat -A POSTROUTING -o {server_network_interface} -j MASQUERADE
PostDown = {iptables_get_path()} -D FORWARD -i %i -j ACCEPT; {iptables_get_path()} -t nat -D POSTROUTING -o {server_network_interface} -j MASQUERADE
ListenPort = 51820
PrivateKey = {server_priv_key}
"""
for peer in peers:
peer_id = peer["id"]
peer_name = peer["name"]
peer_pub_key = peer["PublicKey"]
peer_priv_key = peer["PrivateKey"]
wg_config_content += f"""
[Peer]
PublicKey = {peer_pub_key}
AllowedIPs = 10.0.0.{peer_id}/32
"""
subprocess.run(["systemctl", "stop", "wg-quick@wg0"], text=True)
with open("/etc/wireguard/wg0.conf", "w") as f:
f.write(wg_config_content)
print("Server config updated")
print("restarting wireguard service")
subprocess.run(["systemctl", "start", "wg-quick@wg0"], text=True)
if __name__ == "__main__":
run_first_install_setup()
app.run(host="0.0.0.0", port=5050)