diff --git a/README.md b/README.md index cbc1ad1..ceff8f3 100644 --- a/README.md +++ b/README.md @@ -45,12 +45,42 @@ This means the filter sees normal-looking Google traffic, while the actual desti --- -## Step-by-Step Setup Guide +## Quick Start (Recommended) + +One command sets up a virtualenv, installs dependencies, launches an interactive +config wizard, and starts the proxy. + +**Windows:** +```cmd +git clone https://github.com/masterking32/MasterHttpRelayVPN.git +cd MasterHttpRelayVPN +start.bat +``` + +**Linux / macOS:** +```bash +git clone https://github.com/masterking32/MasterHttpRelayVPN.git +cd MasterHttpRelayVPN +chmod +x start.sh +./start.sh +``` + +The first time it runs, the wizard asks for your Google Apps Script Deployment ID +and generates a strong random password for you. Follow the Apps Script deployment +instructions in **Step 2** below before running the wizard so you have a +Deployment ID ready. + +After it's running, jump to **Step 5** (browser proxy) and **Step 6** (CA +certificate). + +--- + +## Step-by-Step Setup Guide (Manual) ### Step 1: Download This Project ```bash -git clone -b python_testing https://github.com/masterking32/MasterHttpRelayVPN.git +git clone https://github.com/masterking32/MasterHttpRelayVPN.git cd MasterHttpRelayVPN pip install -r requirements.txt ``` @@ -60,7 +90,7 @@ pip install -r requirements.txt > pip install -r requirements.txt -i https://mirror-pypi.runflare.com/simple/ --trusted-host mirror-pypi.runflare.com > ``` -Or download the ZIP from [GitHub](https://github.com/masterking32/MasterHttpRelayVPN/tree/python_testing) and extract it. +Or download the ZIP from [GitHub](https://github.com/masterking32/MasterHttpRelayVPN) and extract it. ### Step 2: Set Up the Google Relay (Code.gs) @@ -86,6 +116,15 @@ This is the "relay" that sits on Google's servers and fetches websites for you. ### Step 3: Configure +**Option A — interactive wizard (recommended):** +```bash +python setup.py +``` +It'll prompt for your Deployment ID, generate a random `auth_key`, and write +`config.json` for you. + +**Option B — manual:** + 1. Copy the example config file: ```bash cp config.example.json config.json @@ -298,8 +337,10 @@ python3 main.py --no-cert-check # Skip automatic CA install check on st ``` MasterHttpRelayVPN/ ├── main.py # Entry point: starts the proxy +├── setup.py # Interactive wizard — writes config.json +├── start.bat / start.sh # One-click launcher (venv + deps + wizard + run) ├── config.example.json # Copy to config.json and fill in your values -├── requirements.txt # Optional Python dependencies +├── requirements.txt # Python dependencies ├── apps_script/ │ └── Code.gs # The relay script you deploy to Google Apps Script ├── ca/ # Generated MITM CA (do NOT share) diff --git a/main.py b/main.py index d6f59bf..adad513 100644 --- a/main.py +++ b/main.py @@ -104,8 +104,31 @@ def main(): config = json.load(f) except FileNotFoundError: print(f"Config not found: {config_path}") - print("Copy config.example.json to config.json and fill in your values.") - sys.exit(1) + # Offer the interactive wizard if it's available and we're on a TTY. + wizard = os.path.join(os.path.dirname(os.path.abspath(__file__)), "setup.py") + if os.path.exists(wizard) and sys.stdin.isatty(): + try: + answer = input("Run the interactive setup wizard now? [Y/n]: ").strip().lower() + except EOFError: + answer = "n" + if answer in ("", "y", "yes"): + import subprocess + rc = subprocess.call([sys.executable, wizard]) + if rc != 0: + sys.exit(rc) + try: + with open(config_path) as f: + config = json.load(f) + except Exception as e: + print(f"Could not load config after setup: {e}") + sys.exit(1) + else: + print("Copy config.example.json to config.json and fill in your values,") + print("or run: python setup.py") + sys.exit(1) + else: + print("Run: python setup.py (or copy config.example.json to config.json)") + sys.exit(1) except json.JSONDecodeError as e: print(f"Invalid JSON in config: {e}") sys.exit(1) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..7b0d78c --- /dev/null +++ b/setup.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python3 +"""Interactive setup wizard for MasterHttpRelayVPN. + +Writes a ready-to-use config.json by prompting only for the values +the user really has to choose. Everything else gets a sane default. + +Run: + python setup.py +""" + +from __future__ import annotations + +import json +import os +import secrets +import shutil +import string +import sys +from pathlib import Path + +HERE = Path(__file__).resolve().parent +CONFIG_PATH = HERE / "config.json" +EXAMPLE_PATH = HERE / "config.example.json" + + +def _c(code: str, text: str) -> str: + if os.environ.get("NO_COLOR") or not sys.stdout.isatty(): + return text + return f"\033[{code}m{text}\033[0m" + + +def bold(t: str) -> str: return _c("1", t) +def cyan(t: str) -> str: return _c("36", t) +def green(t: str) -> str: return _c("32", t) +def yellow(t: str) -> str: return _c("33", t) +def red(t: str) -> str: return _c("31", t) +def dim(t: str) -> str: return _c("2", t) + + +def prompt(question: str, default: str | None = None) -> str: + suffix = f" [{dim(default)}]" if default else "" + while True: + try: + raw = input(f"{cyan('?')} {question}{suffix}: ").strip() + except EOFError: + print() + sys.exit(1) + if not raw and default is not None: + return default + if raw: + return raw + print(red(" value required")) + + +def prompt_yes_no(question: str, default: bool = True) -> bool: + hint = "Y/n" if default else "y/N" + while True: + raw = input(f"{cyan('?')} {question} [{hint}]: ").strip().lower() + if not raw: + return default + if raw in ("y", "yes"): + return True + if raw in ("n", "no"): + return False + + +def random_auth_key(length: int = 32) -> str: + alphabet = string.ascii_letters + string.digits + return "".join(secrets.choice(alphabet) for _ in range(length)) + + +def load_base_config() -> dict: + if EXAMPLE_PATH.exists(): + try: + with EXAMPLE_PATH.open() as f: + return json.load(f) + except Exception: + pass + return { + "mode": "apps_script", + "google_ip": "216.239.38.120", + "front_domain": "www.google.com", + "listen_host": "127.0.0.1", + "listen_port": 8085, + "socks5_enabled": True, + "socks5_port": 1080, + "log_level": "INFO", + "verify_ssl": True, + "hosts": {}, + } + + +def configure_apps_script(cfg: dict) -> dict: + print() + print(bold("Google Apps Script setup")) + print(dim(" 1. Open https://script.google.com -> New project")) + print(dim(" 2. Paste apps_script/Code.gs from this repo into the editor")) + print(dim(" 3. Set AUTH_KEY in Code.gs to the password below")) + print(dim(" 4. Deploy -> New deployment -> Web app")) + print(dim(" Execute as: Me | Who has access: Anyone")) + print(dim(" 5. Copy the Deployment ID and paste it here")) + print() + + ids_raw = prompt( + "Deployment ID(s) - comma-separated for load balancing", + default=None, + ) + ids = [x.strip() for x in ids_raw.split(",") if x.strip()] + if len(ids) == 1: + cfg["script_id"] = ids[0] + cfg.pop("script_ids", None) + else: + cfg["script_ids"] = ids + cfg.pop("script_id", None) + return cfg + + +def configure_network(cfg: dict) -> dict: + print() + print(bold("Network settings") + dim(" (press enter to accept defaults)")) + cfg["listen_host"] = prompt("Listen host", default=str(cfg.get("listen_host", "127.0.0.1"))) + + port = prompt("HTTP proxy port", default=str(cfg.get("listen_port", 8085))) + try: + cfg["listen_port"] = int(port) + except ValueError: + cfg["listen_port"] = 8085 + + socks5 = prompt_yes_no("Enable SOCKS5 proxy?", default=bool(cfg.get("socks5_enabled", True))) + cfg["socks5_enabled"] = socks5 + if socks5: + sport = prompt("SOCKS5 port", default=str(cfg.get("socks5_port", 1080))) + try: + cfg["socks5_port"] = int(sport) + except ValueError: + cfg["socks5_port"] = 1080 + return cfg + + +def write_config(cfg: dict) -> None: + if CONFIG_PATH.exists(): + backup = CONFIG_PATH.with_suffix(".json.bak") + shutil.copy2(CONFIG_PATH, backup) + print(yellow(f" existing config.json backed up to {backup.name}")) + with CONFIG_PATH.open("w", encoding="utf-8") as f: + json.dump(cfg, f, indent=2) + f.write("\n") + + +def main() -> int: + print() + print(bold("MasterHttpRelayVPN - setup wizard")) + print(dim("Answer a few questions and we'll write config.json for you.")) + + if CONFIG_PATH.exists(): + if not prompt_yes_no("config.json already exists. Overwrite?", default=False): + print(dim("Nothing changed.")) + return 0 + + cfg = load_base_config() + cfg["mode"] = "apps_script" + + suggested_key = random_auth_key() + print() + print(bold("Shared password (auth_key)")) + print(dim(" Must match AUTH_KEY inside apps_script/Code.gs.")) + cfg["auth_key"] = prompt("auth_key", default=suggested_key) + + cfg = configure_apps_script(cfg) + cfg = configure_network(cfg) + + write_config(cfg) + + print() + print(green(f"[OK] wrote {CONFIG_PATH.name}")) + print() + print(bold("Next step:")) + print(f" python main.py") + print() + print(yellow("Reminder: the AUTH_KEY inside apps_script/Code.gs must match the auth_key")) + print(yellow("you just entered - otherwise the relay will return 'unauthorized'.")) + return 0 + + +if __name__ == "__main__": + try: + sys.exit(main()) + except KeyboardInterrupt: + print() + print(dim("Cancelled.")) + sys.exit(130) diff --git a/start.bat b/start.bat new file mode 100644 index 0000000..69dbfbb --- /dev/null +++ b/start.bat @@ -0,0 +1,72 @@ +@echo off +setlocal enabledelayedexpansion +cd /d "%~dp0" + +REM -------- MasterHttpRelayVPN one-click launcher (Windows) -------- +REM Creates a local virtualenv, installs deps, runs the setup wizard +REM if needed, then starts the proxy. + +set "VENV_DIR=.venv" +set "PY=" + +where py >nul 2>&1 +if %errorlevel%==0 ( + set "PY=py -3" +) else ( + where python >nul 2>&1 + if %errorlevel%==0 ( + set "PY=python" + ) +) + +if "%PY%"=="" ( + echo [X] Python 3.10+ was not found on PATH. + echo Install from https://www.python.org/downloads/ and re-run this script. + pause + exit /b 1 +) + +if not exist "%VENV_DIR%\Scripts\python.exe" ( + echo [*] Creating virtual environment in %VENV_DIR% ... + %PY% -m venv "%VENV_DIR%" + if errorlevel 1 ( + echo [X] Failed to create virtualenv. + pause + exit /b 1 + ) +) + +set "VPY=%VENV_DIR%\Scripts\python.exe" + +echo [*] Installing dependencies ... +"%VPY%" -m pip install --disable-pip-version-check -q --upgrade pip >nul +"%VPY%" -m pip install --disable-pip-version-check -q -r requirements.txt +if errorlevel 1 ( + echo [!] PyPI install failed. Retrying via runflare mirror ... + "%VPY%" -m pip install --disable-pip-version-check -q -r requirements.txt ^ + -i https://mirror-pypi.runflare.com/simple/ ^ + --trusted-host mirror-pypi.runflare.com + if errorlevel 1 ( + echo [X] Could not install dependencies. + pause + exit /b 1 + ) +) + +if not exist "config.json" ( + echo [*] No config.json found — launching setup wizard ... + "%VPY%" setup.py + if errorlevel 1 ( + echo [X] Setup cancelled. + pause + exit /b 1 + ) +) + +echo. +echo [*] Starting MasterHttpRelayVPN ... +echo. +"%VPY%" main.py %* +set "RC=%errorlevel%" +if not "%RC%"=="0" pause +exit /b %RC% diff --git a/start.sh b/start.sh new file mode 100644 index 0000000..ee37246 --- /dev/null +++ b/start.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# MasterHttpRelayVPN one-click launcher (Linux / macOS) +# Creates a local virtualenv, installs deps, runs the setup wizard +# if needed, then starts the proxy. + +set -e +cd "$(dirname "$0")" + +VENV_DIR=".venv" + +find_python() { + for cmd in python3.12 python3.11 python3.10 python3 python; do + if command -v "$cmd" >/dev/null 2>&1; then + ver=$("$cmd" -c 'import sys;print("%d.%d"%sys.version_info[:2])' 2>/dev/null || echo "0.0") + major=${ver%.*}; minor=${ver#*.} + if [ "$major" -ge 3 ] && [ "$minor" -ge 10 ]; then + echo "$cmd" + return 0 + fi + fi + done + return 1 +} + +PY=$(find_python) || { + echo "[X] Python 3.10+ not found. Install it and re-run this script." >&2 + exit 1 +} + +if [ ! -x "$VENV_DIR/bin/python" ]; then + echo "[*] Creating virtual environment in $VENV_DIR ..." + "$PY" -m venv "$VENV_DIR" +fi + +VPY="$VENV_DIR/bin/python" + +echo "[*] Installing dependencies ..." +"$VPY" -m pip install --disable-pip-version-check -q --upgrade pip >/dev/null +if ! "$VPY" -m pip install --disable-pip-version-check -q -r requirements.txt; then + echo "[!] PyPI install failed. Retrying via runflare mirror ..." + "$VPY" -m pip install --disable-pip-version-check -q -r requirements.txt \ + -i https://mirror-pypi.runflare.com/simple/ \ + --trusted-host mirror-pypi.runflare.com +fi + +if [ ! -f "config.json" ]; then + echo "[*] No config.json found — launching setup wizard ..." + "$VPY" setup.py +fi + +echo +echo "[*] Starting MasterHttpRelayVPN ..." +echo +exec "$VPY" main.py "$@"