Merge pull request #6 from ramzxy/simplify-install

Add one-click launcher and interactive setup wizard
This commit is contained in:
Abolfazl Ghaemi
2026-04-22 23:49:38 +03:30
committed by GitHub
5 changed files with 387 additions and 6 deletions
+45 -4
View File
@@ -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)
+24 -1
View File
@@ -104,7 +104,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}")
+191
View File
@@ -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)
+72
View File
@@ -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%
+54
View File
@@ -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 "$@"