mirror of
https://github.com/masterking32/MasterHttpRelayVPN.git
synced 2026-05-17 21:24:37 +03:00
Add one-click launcher and interactive setup wizard
- setup.py: prompts only for the values the user must choose, generates a random auth_key, writes config.json (backs up any existing one). - start.bat / start.sh: create a local venv, install deps (with PyPI mirror fallback), run the wizard if config.json is missing, then launch main.py. - main.py: when config.json is missing on an interactive TTY, offer to run the wizard instead of just exiting. - README: new "Quick Start" section up top; manual steps kept below. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
@@ -273,8 +312,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)
|
||||
|
||||
@@ -103,7 +103,30 @@ 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.")
|
||||
# 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}")
|
||||
|
||||
@@ -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)
|
||||
@@ -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%
|
||||
@@ -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 "$@"
|
||||
Reference in New Issue
Block a user