From 30d560f8049aab69146098d90d0a45bc7bfdd0b9 Mon Sep 17 00:00:00 2001 From: julien Date: Tue, 20 Jan 2026 09:06:51 +0100 Subject: [PATCH] WG helper script --- .../Screenshot 2025-04-12 122809.png | Bin 0 -> 54260 bytes Divers/wireguard_helper/readme.md | 53 + Divers/wireguard_helper/wg_helper.py | 986 ++++++++++++++++++ 3 files changed, 1039 insertions(+) create mode 100644 Divers/wireguard_helper/Screenshot 2025-04-12 122809.png create mode 100644 Divers/wireguard_helper/readme.md create mode 100644 Divers/wireguard_helper/wg_helper.py diff --git a/Divers/wireguard_helper/Screenshot 2025-04-12 122809.png b/Divers/wireguard_helper/Screenshot 2025-04-12 122809.png new file mode 100644 index 0000000000000000000000000000000000000000..9a68220466d144ba6a02ff3b8e90f8390e43bb49 GIT binary patch literal 54260 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

*X=REEDT=q2VqDy0xbK<+Fa%bR@Z~ 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 literal 0 HcmV?d00001 diff --git a/Divers/wireguard_helper/readme.md b/Divers/wireguard_helper/readme.md new file mode 100644 index 0000000..854879d --- /dev/null +++ b/Divers/wireguard_helper/readme.md @@ -0,0 +1,53 @@ +# SeaBee's WireGuard Helper Script + +This Python script simplifies the installation and management of a WireGuard VPN server, including client configuration. + +## 🔧 Features + +- Installs WireGuard and required dependencies +- Generates server and peer key pairs +- Edits and manages the WireGuard config file (`wg0.conf`) +- Stores configuration data in a JSON file for easy reuse +- Allows editing the server endpoint +- Starts a temporary web admin panel for WireGuard management +- Automatically opens port `51820` using UFW (if enabled) + +## 📦 Requirements + +- Python 3 +- Flask (`sudo apt install python3-flask` **or** `pip install flask`) +- A Linux-based system +- Internet connection + +> ⚠️ Note: This script has been tested on debain and may not work on your machine + +## 🚀 Installation & Usage + +1. Ensure you have **all** dependencies + +```bash +sudo apt install curl python3-flask -y +``` + +2. Download and run the script +```bash +curl -L -o wg_helper.py https://raw.githubusercontent.com/seabee33/wireguard_helper/main/wg_helper.py +``` + +3. **Set the admin password** on **line 10** of the file: `ADMIN_PASSWORD = "your_secure_password_here"` + +4. **Run the script with sudo or as root** `sudo python3 wg_helper.py` + +5. Access the web panel: Open your browser and go to: `http://:5050` and go through the easy setup steps: + + +## Setup once web panel loaded +1. Install and activate necessary software in **system status** (Install wireguard, autostart wireguard at boot, install iptables, Open Port 51820 *if ufw is installed*) +2. Fill in endpoint in options +3. generate keys +4. Add peers with a unique name +5. download peer config and connect on client device +6. Don't forget to port forward your server on your modem! (port 51820) + +Preview: +![image](https://github.com/user-attachments/assets/005ca4e2-0569-4605-acd4-0f0961fc2ba5) diff --git a/Divers/wireguard_helper/wg_helper.py b/Divers/wireguard_helper/wg_helper.py new file mode 100644 index 0000000..5045cfc --- /dev/null +++ b/Divers/wireguard_helper/wg_helper.py @@ -0,0 +1,986 @@ +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 = """ + + + + + + WireGuard Helper - Login + + + +
+ +
+ + + """ + + 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 = """ +
+ +
+ """ + + ufw_allow_port_button = """ +
+ +
+ """ + + 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 = """ +
+ +
+ """ + 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 = """ +
+ +
+ """ + 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 = """ +
+ +
+ """ + + 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 = """ +
+ + +
+ """ + else: + server_endpoint_form = f""" + {server_endpoint_data} +
+ + +
+ """ + + # Server public key + server_public_key = config_data.get("server", {}).get("PublicKey", "") + if server_public_key == "": + server_public_key_form = f""" + No Keys set +
+ +
+ """ + else: + server_public_key_form = f""" + {server_public_key} +
+ +
+ """ + + # 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""" +
+
ID: {peer_id}
+
Name: {peer_name}
+
Public Key: {peer_public_key}
+
+
+ + +
+
+ + +
+
+
""" + + html = f""" + + + + + + SeaBee's WG Helper + + + +
+
+

SeaBee's Wireguard Helper v0.2

+ +
+
+ +
+
+

System Status

+ +
+ Wireguard software:  + {wg_installed_status} + {install_wg_button} +
+ +
+ Wireguard autostart at boot:  + {wg_enabled_at_boot_status} + {wg_enabled_at_boot_button} +
+ +
+ UFW:  + {ufw_installed_status} + {'  &  ' + '' + ufw_enabled_status + '' if ufw_enabled_status else ''} +
+ + {f''' +
+ UFW Wireguard Port:  + {ufw_port_status} + {ufw_port_button} +
+ ''' if ufw_port_status else ''} + +
+ iptables:  + {iptables_installed_status} + {iptables_install_button} +
+ +
+ Server network interface:  + {server_network_interface} +
+ +
+ Wireguard status:  + {wg_running_status} + {wg_running_button} +
+
+ +
+

Server Configuration

+ +
+ Server endpoint: +
{server_endpoint_form}
+
+ +
+ Server Public key: +
{server_public_key_form}
+
+
+ +
+

Peer Management

+ +
+ Create new peer: +
+ + +
+
+ + {peers_list if peers_list else '

No peers configured yet. Create your first peer above.

'} +
+
+ + + """ + 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""" + + + + + + Installation Error + + + +
+
+

Installation Failed

+

Error: {e}

+ Back to Dashboard +
+
+ + + """ + 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""" + + + + + + Installation Error + + + +
+
+

Installation Failed

+

Error: {e}

+ Back to Dashboard +
+
+ + + """ + 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""" + + + + + + UFW Error + + + +
+
+

UFW Configuration Failed

+

Error: {e}

+ Back to Dashboard +
+
+ + + """ + 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)