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 0000000..9a68220 Binary files /dev/null and b/Divers/wireguard_helper/Screenshot 2025-04-12 122809.png differ 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)