From 895db3a3b9ae7a9a0601512ca436abcc232cf784 Mon Sep 17 00:00:00 2001 From: Kiritow <1362050620@qq.com> Date: Mon, 26 Jul 2021 18:01:56 +0800 Subject: [PATCH] Major Update Fix: generated stop.sh cannot be executed. Add: restart script. Add: use getpass for password input. Add: Paste & Go: Exchange config with ease --- .gitignore | 3 + tool_common.py | 67 +++++++++++++++++++++ tool_create.py | 149 +++++++++++++++++++++++++++++------------------ tool_generate.py | 40 ++++++++----- 4 files changed, 189 insertions(+), 70 deletions(-) create mode 100644 tool_common.py diff --git a/.gitignore b/.gitignore index 549bbf6..4e24f46 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ bin/ local/ +.idea/ +__pycache__/ + *.json *.conf start.sh diff --git a/tool_common.py b/tool_common.py new file mode 100644 index 0000000..0c33515 --- /dev/null +++ b/tool_common.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +import logging +import json +import traceback +import base64 + + +class SimpleLogger(object): + def __init__(self, name=None, filename=None, fileonly=False, + level=logging.INFO, + default_encoding='utf-8', + log_format="%(asctime)s @%(module)s [%(levelname)s] %(funcName)s: %(message)s"): + if name is None: + name = __name__ + + if not filename and fileonly: + raise Exception("FileOnly=True but no filename provided.") + + self.logger = logging.getLogger(name) + if not getattr(self.logger, "_is_configured", None): + formatter = logging.Formatter(log_format) + if not fileonly: + console_handler = logging.StreamHandler() + console_handler.setFormatter(formatter) + self.logger.addHandler(console_handler) + if filename is not None: + file_handler = logging.FileHandler(filename, encoding=default_encoding) + file_handler.setFormatter(formatter) + self.logger.addHandler(file_handler) + self.logger.setLevel(level) + setattr(self.logger, "_is_configured", True) + + # Just acts as a logger + def __getattr__(self, name): + return getattr(self.logger, name) + + +logger = SimpleLogger() + + +def load_config(filename=None): + config_filename = filename or "local/config.json" + try: + with open(config_filename) as f: + return json.loads(f.read()) + except Exception: + logger.error("Unable to load config: {}".format(traceback.format_exc())) + return {} + + +def save_config(config, filename=None): + config_filename = filename or "local/config.json" + content = json.dumps(config, ensure_ascii=False, default=str, indent=2) + try: + with open(config_filename, "w", encoding='utf-8') as f: + f.write(content) + except Exception: + logger.error("Unable to save config: {}".format(traceback.format_exc())) + logger.info("Config: {}".format(content)) + + +def json_to_base64(content): + return base64.b64encode(json.dumps(content, ensure_ascii=False).encode('utf-8')).decode('utf-8') + + +def base64_to_json(content): + return json.loads(base64.b64decode(content).decode('utf-8')) diff --git a/tool_create.py b/tool_create.py index 2e41151..e03fa88 100755 --- a/tool_create.py +++ b/tool_create.py @@ -1,16 +1,19 @@ # -*- coding: utf-8 -*- import os -import json -import traceback +import getpass +from tool_common import load_config, save_config, SimpleLogger, json_to_base64 -try: - with open("local/config.json") as f: - content = f.read() - config = json.loads(content) - print("[WARN] Found a valid config. Creation of server is skipped.") + +logger = SimpleLogger() + +config = load_config() +if config: + logger.warn("Valid config found. Creation of server is skipped.") exit(0) -except Exception: - print(traceback.format_exc()) +else: + logger.info("No config found. Start creating interactively.") + +print("===== Choose Role =====") op_mode = input("What will this node act as? (C)lient [S]erver [M]ixed: ").strip().lower() if not op_mode: @@ -40,12 +43,17 @@ if op_mode in ("s", "m"): break while True: - udp_server_password = input("Please input udp2raw tunnel password: ").strip() + udp_server_password = getpass.getpass('Tunnel Password: ').strip() if not udp_server_password: - print("A udp2raw tunnel password is required. Try again.") + print("For security reasons, a udp2raw tunnel password is required. Try again.") continue + + if udp_server_password != getpass.getpass('Confirm Tunnel Password: ').strip(): + print("Password mismatch. Try again.") + continue + break - + is_enable_speeder = input("Enable UDP Speeder for this tunnel? [Y/n]: ").strip() if not is_enable_speeder or is_enable_speeder.lower() in ('y', 'yes'): speeder_ratio = input("Enter UDP Speeder Ratio (default to 20:10. Use 2:4 for gaming usage): ").strip() @@ -65,7 +73,7 @@ if op_mode in ("s", "m"): "speeder": speeder_info }) - if not input("Add more udp2raw server? (Keep empty to finish)").strip(): + if not input("Add more udp2raw server? (Keep empty to finish): ").strip(): break @@ -83,10 +91,15 @@ if op_mode in ("c", "m"): break while True: - udp_server_password = input("Please input udp2raw tunnel password: ").strip() + udp_server_password = getpass.getpass('Tunnel Password: ').strip() if not udp_server_password: print("A udp2raw tunnel password is required. Try again.") continue + + if udp_server_password != getpass.getpass('Confirm Tunnel Password: ').strip(): + print("Password mismatch. Try again.") + continue + break is_enable_speeder = input("Enable UDP Speeder for this tunnel? [Y/n]: ").strip() @@ -135,51 +148,14 @@ print(''' ifname = input("Input new wireguard interface name (wg0):").strip() or "wg0" listen_port = input("Input new wireguard listen port (51820): ").strip() or "51820" while True: - ifip = input("Input wireguard interface ip (Example: 10.0.0.1): ").strip() + ifip = input("Input wireguard interface ip (Example: 10.0.0.1)\n> ").strip() if not ifip: print("You MUST set a valid wireguard interface IP. Try Again.") continue break -peers = [] - -while True: - print("====== Adding Peer {} ======".format(len(peers) + 1)) - while True: - peer_pubk = input("Enter Wireguard Peer Public Key: ").strip() - if not peer_pubk: - print("A public key is required. Try Again.") - continue - break - while True: - peer_allowed = input("Enter Wireguard Peer AllowedIPs (CIDR, Example: 10.0.0.0/24): ").strip() - if not peer_allowed: - print("Peer allowed ips required. Try Again.") - continue - break - - print(">>> Choose from following udp2raw clients <<<") - for index, client_info in enumerate(udp2raw_config["client"]): - print("[{}] UDP2Raw Tunnel to Remote {}".format(index + 1, client_info["remote"])) - - peer_endpoint = input("Enter Wireguard Peer Endpoint (ID from tunnel list, keep empty on server side): ").strip() - if peer_endpoint: - peer_keepalive = input("Enter Wireguard Peer Keep Alive seconds: ").strip() - else: - peer_keepalive = "" - - peers.append({ - "pubkey": peer_pubk, - "allowed": peer_allowed, - "endpoint": peer_endpoint, - "keepalive": peer_keepalive - }) - - if not input("Add more peers? (Keep empty to finish)").strip(): - break - -print("Saving to local config...") +print("Saving config...") config = { "version": 1, "mode": op_mode, @@ -189,9 +165,70 @@ config = { "interface": ifname, "ip": ifip, "listen": listen_port, - "peers": peers, + "peers": [], "udp2raw": udp2raw_config } +save_config(config) -with open("local/config.json", "w") as f: - f.write(json.dumps(config, ensure_ascii=False)) + +if op_mode in ("s", "m"): + print("===== Quick Import =====") + for info in udp2raw_config["server"]: + target_config = { + "udp2raw": { + "client": [{ + "remote": "{}:{}".format(wg_public_ip, info["port"]), + "password": info["password"], + "port": "", + "speeder": info["speeder"] + }] + }, + "pubkey": wg_pubk + } + + print("Connect to this server via tunnel at port {}: (credential included) \n".format(info["port"])) + print(" {}\n".format(json_to_base64(target_config))) + + +# Configure Peer + +while True: + print("====== Adding Peer {} ======".format(len(config["peers"]) + 1)) + while True: + peer_pubk = input("Enter Wireguard Peer Public Key: ").strip() + if not peer_pubk: + print("A public key is required. Try Again.") + continue + break + while True: + peer_allowed = input("Enter Wireguard Peer AllowedIPs (CIDR, Example: 10.0.0.0/24)\n> ").strip() + if not peer_allowed: + print("Peer allowed ips required. Try Again.") + continue + break + + print(">>> Choose from following udp2raw clients <<<") + if config["udp2raw"]["client"]: + for index, client_info in enumerate(config["udp2raw"]["client"]): + print("[{}] UDP2Raw Tunnel to Remote {}".format(index + 1, client_info["remote"])) + else: + print(" no client ") + + peer_endpoint = input("Enter Wireguard Peer Endpoint (ID from tunnel list, keep empty on server side): ").strip() + if peer_endpoint: + peer_keepalive = input("Enter Wireguard Peer Keep Alive seconds (default to 30): ").strip() or "30" + else: + peer_keepalive = "30" + + config["peers"].append({ + "pubkey": peer_pubk, + "allowed": peer_allowed, + "endpoint": peer_endpoint, + "keepalive": peer_keepalive + }) + + print("Saving config...") + save_config(config) + + if not input("Add more peers? (Keep empty to finish)").strip(): + break diff --git a/tool_generate.py b/tool_generate.py index ec00f51..20e5249 100644 --- a/tool_generate.py +++ b/tool_generate.py @@ -1,19 +1,19 @@ # -*- coding: utf-8 -*- import os -import json -import traceback +from tool_common import load_config, SimpleLogger -try: - with open("local/config.json") as f: - content = f.read() - config = json.loads(content) -except Exception: - print(traceback.format_exc()) - print("[ERROR] No valid config found.") + +logger = SimpleLogger() + + +config = load_config() +if not config: + logger.error("No valid config found.") + exit(1) if "version" not in config or int(config["version"]) < 1: - print("[WARN] Legacy version of config found. This may cause issues.") + logger.warn("[WARN] Legacy version of config found. This may cause issues.") op_mode = config["mode"] @@ -21,7 +21,7 @@ udp_clients = config["udp2raw"]["client"] udp_servers = config["udp2raw"]["server"] -print("Generating wireguard config...") +logger.info("Generating wireguard config...") with open("local/{}.conf".format(config["interface"]), "w", encoding='utf-8') as f: f.write('''[Interface] Address = {} @@ -48,7 +48,7 @@ AllowedIPs = {} os.system("chmod 600 {}.conf".format(config["interface"])) -print("Generating start script...") +logger.info("Generating start script...") with open("start.sh", "w", encoding='utf-8') as f: f.write('''#!/bin/bash set -e @@ -86,7 +86,7 @@ tmux attach-session -t tunnel '''.format(config["interface"])) -print("Generating stop script...") +logger.info("Generating stop script...") with open("stop.sh", "w", encoding='utf-8') as f: f.write('''#!/bin/bash set -e @@ -95,8 +95,20 @@ wg-quick down {} tmux kill-session -t tunnel '''.format(config["interface"])) +os.system("chmod +x stop.sh") -print('''[OK] Config generated. Before you run start.sh, besure to: + +logger.info("Generating restart script...") +with open("restart.sh", "w", encoding='utf-8') as f: + f.write('''#!/bin/bash +set -e +./stop.sh && ./start.sh +''') + +os.system("chmod +x restart.sh") + + +logger.info('''[Done] Config generated. Before you run start.sh, besure to: 1. Disable SSH Server password login. 2. Enable UFW (or any other firewall)