mirror of
https://github.com/masterking32/MasterHttpRelayVPN.git
synced 2026-05-17 21:24:37 +03:00
Merge pull request #6 from ramzxy/simplify-install
Add one-click launcher and interactive setup wizard
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
|
### Step 1: Download This Project
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone -b python_testing https://github.com/masterking32/MasterHttpRelayVPN.git
|
git clone https://github.com/masterking32/MasterHttpRelayVPN.git
|
||||||
cd MasterHttpRelayVPN
|
cd MasterHttpRelayVPN
|
||||||
pip install -r requirements.txt
|
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
|
> 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)
|
### 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
|
### 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:
|
1. Copy the example config file:
|
||||||
```bash
|
```bash
|
||||||
cp config.example.json config.json
|
cp config.example.json config.json
|
||||||
@@ -298,8 +337,10 @@ python3 main.py --no-cert-check # Skip automatic CA install check on st
|
|||||||
```
|
```
|
||||||
MasterHttpRelayVPN/
|
MasterHttpRelayVPN/
|
||||||
├── main.py # Entry point: starts the proxy
|
├── 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
|
├── config.example.json # Copy to config.json and fill in your values
|
||||||
├── requirements.txt # Optional Python dependencies
|
├── requirements.txt # Python dependencies
|
||||||
├── apps_script/
|
├── apps_script/
|
||||||
│ └── Code.gs # The relay script you deploy to Google Apps Script
|
│ └── Code.gs # The relay script you deploy to Google Apps Script
|
||||||
├── ca/ # Generated MITM CA (do NOT share)
|
├── ca/ # Generated MITM CA (do NOT share)
|
||||||
|
|||||||
@@ -104,8 +104,31 @@ def main():
|
|||||||
config = json.load(f)
|
config = json.load(f)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
print(f"Config not found: {config_path}")
|
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.
|
||||||
sys.exit(1)
|
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:
|
except json.JSONDecodeError as e:
|
||||||
print(f"Invalid JSON in config: {e}")
|
print(f"Invalid JSON in config: {e}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|||||||
@@ -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