From a36759dc1d653a300018ab6a40b2bc614590759f Mon Sep 17 00:00:00 2001
From: SamNet-dev
Date: Tue, 17 Feb 2026 09:49:17 -0600
Subject: [PATCH] Add geo-blocking, optimize TUI rendering, add README
- Implement real iptables/ipset geo-blocking with ipdeny.com CIDR lists
- Tag-based cleanup (mtproxymax-geoblock comment, mtpmax_ ipset prefix)
- Batch ipset loading via restore for fast rule application
- Auto-reapply geo-blocks on proxy start, full cleanup on uninstall
- Fix _repeat() and _strlen() to use pure bash (no subprocesses)
- Cache docker inspect result across main menu renders
- Cache get_public_ip() for 5 minutes, reduce timeout 5s -> 3s
- Parse proxy stats with single read instead of 3 awk subprocesses
- Cache _cached_start_epoch outside render loop
- Add comprehensive README with feature docs, comparison table,
user management recipes, and practical examples
---
README.md | 502 ++++++++++++++++++++++++++++++++++++++++++++++++++
mtproxymax.sh | 249 ++++++++++++++++++++-----
2 files changed, 701 insertions(+), 50 deletions(-)
create mode 100644 README.md
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a9aa062
--- /dev/null
+++ b/README.md
@@ -0,0 +1,502 @@
+
+
MTProxyMax
+ The Ultimate Telegram MTProto Proxy Manager
+
+ One script. Full control. Zero hassle.
+
+
+ Quick Start •
+ Features •
+ Comparison •
+ Telegram Bot •
+ CLI Reference
+
+
+
+---
+
+MTProxyMax is a full-featured Telegram MTProto proxy manager powered by the **telemt 3.x Rust engine**. It wraps the raw proxy engine with an interactive TUI, a complete CLI, a Telegram bot for remote management, per-user access control, traffic monitoring, proxy chaining, and automatic updates — all in a single bash script.
+
+```bash
+sudo bash -c "$(curl -fsSL https://raw.githubusercontent.com/SamNet-dev/MTProxyMax/main/install.sh)"
+```
+
+## Why MTProxyMax?
+
+Most MTProxy tools give you a proxy and a link. That's it. MTProxyMax gives you a **full management platform**:
+
+- **Multi-user secrets** with individual bandwidth quotas, device limits, and expiry dates
+- **Telegram bot** with 17 commands — manage everything from your phone
+- **Interactive TUI** — no need to memorize commands, menu-driven setup
+- **Prometheus metrics** — real per-user traffic stats, not just iptables guesses
+- **Proxy chaining** — route through SOCKS5 upstreams for extra privacy
+- **Auto-recovery** — detects downtime, restarts automatically, alerts you on Telegram
+- **Pre-built Docker images** — installs in seconds, not minutes
+
+---
+
+## Quick Start
+
+### One-Line Install
+
+```bash
+sudo bash -c "$(curl -fsSL https://raw.githubusercontent.com/SamNet-dev/MTProxyMax/main/install.sh)"
+```
+
+The interactive wizard walks you through everything: port, domain, first user secret, and optional Telegram bot setup.
+
+### Manual Install
+
+```bash
+curl -fsSL https://raw.githubusercontent.com/SamNet-dev/MTProxyMax/main/mtproxymax.sh -o mtproxymax
+chmod +x mtproxymax
+sudo ./mtproxymax install
+```
+
+### After Install
+
+```bash
+mtproxymax menu # Open interactive TUI
+mtproxymax secret list # See your users
+mtproxymax status # Check proxy health
+```
+
+---
+
+## Features
+
+### FakeTLS Obfuscation
+
+MTProxyMax uses **FakeTLS (TLS 1.3)** by default. Your proxy traffic looks identical to normal HTTPS traffic to any network observer or DPI system. The TLS handshake SNI points to a cover domain (e.g., `cloudflare.com`), making it indistinguishable from regular web browsing.
+
+**Traffic masking** goes further: when a non-Telegram client connects (e.g., a censor probing your server), the connection is seamlessly forwarded to the real cover domain. Your server responds exactly like cloudflare.com would — because it's actually proxying to it.
+
+### Multi-User Secret Management
+
+Each user gets their own **secret key** with a human-readable label. You can:
+
+- **Add/remove** users instantly — config regenerates and proxy hot-reloads
+- **Enable/disable** access without deleting the key
+- **Rotate** a user's secret — new key, same label, old link stops working
+- **Generate links** — both `tg://` and `https://t.me/proxy` formats
+- **QR codes** — scannable directly in Telegram settings
+
+### Per-User Access Control
+
+Fine-grained limits enforced at the engine level:
+
+| Limit | Description | Example |
+|-------|-------------|---------|
+| **Max Connections** | Simultaneous TCP connections | `100` |
+| **Max IPs** | Unique devices/IPs allowed | `5` |
+| **Data Quota** | Total bandwidth cap | `10G`, `500M` |
+| **Expiry Date** | Auto-disable after date | `2026-12-31` |
+
+```bash
+mtproxymax secret setlimit alice 100 5 10G 2026-12-31
+```
+
+This means: Alice can use up to 100 simultaneous connections from max 5 devices, with 10GB total bandwidth, expiring Dec 31, 2026.
+
+### User Management Recipes
+
+#### Prevent Key Sharing
+
+By default a secret key is unlimited — anyone who has the link can use it from any device or IP. To lock it to one person:
+
+```bash
+mtproxymax secret setlimit alice ips 1 # Alice only, no sharing possible
+mtproxymax secret setlimit family ips 5 # Family of up to 5 devices
+```
+
+If someone with `ips 1` shares their link, the second IP that tries to connect gets rejected by the engine automatically.
+
+#### IP Limit Tiers
+
+| Scenario | `max_ips` |
+|----------|-----------|
+| Single person, one device | `1` |
+| Single person, multiple devices | `2-3` |
+| Small family | `5` |
+| Small group / office | `20-30` |
+| Public/open link | `0` (unlimited) |
+
+#### Create a Time-Limited Sharing Link
+
+```bash
+# Public link: 50 simultaneous connections, 30 unique IPs, 10GB cap, expires June 1st
+mtproxymax secret add shared-link
+mtproxymax secret setlimits shared-link 50 30 10G 2026-06-01
+```
+
+When the expiry date hits, the link stops working automatically. No manual cleanup needed.
+
+#### Per-Person Keys (Recommended for Control)
+
+```bash
+mtproxymax secret add alice
+mtproxymax secret add bob
+mtproxymax secret add charlie
+
+# Each person gets their own link — revoke individually without affecting others
+mtproxymax secret setlimit alice ips 2
+mtproxymax secret setlimit bob ips 1
+mtproxymax secret setlimit charlie ips 3
+```
+
+#### Temporarily Cut Someone Off
+
+```bash
+mtproxymax secret disable bob # Bob can't connect, link preserved
+mtproxymax secret enable bob # Bob is back, same link works
+```
+
+#### Revoke a Leaked Link
+
+```bash
+mtproxymax secret rotate alice # New key generated, old link dies immediately
+```
+
+Alice gets a new link. Anyone who had the old link is disconnected and can't reconnect.
+
+#### Full Cleanup
+
+```bash
+mtproxymax secret remove bob # Permanent — key gone, link dead forever
+```
+
+All other users are completely unaffected since each secret is independent.
+
+---
+
+### Telegram Bot (17 Commands)
+
+Full proxy management from your phone. Setup takes 60 seconds:
+
+```bash
+mtproxymax telegram setup
+```
+
+| Command | Description |
+|---------|-------------|
+| `/mp_status` | Proxy status, uptime, connections |
+| `/mp_secrets` | List all users with active connections |
+| `/mp_link` | Get proxy links + QR code image |
+| `/mp_add ` | Add new user, get link instantly |
+| `/mp_remove ` | Delete user |
+| `/mp_rotate ` | Generate new key for user |
+| `/mp_enable ` | Re-enable disabled user |
+| `/mp_disable ` | Temporarily disable user |
+| `/mp_limits` | Show all user limits |
+| `/mp_setlimit` | Set user limits |
+| `/mp_traffic` | Per-user traffic breakdown |
+| `/mp_upstreams` | List proxy chains |
+| `/mp_health` | Run diagnostics |
+| `/mp_restart` | Restart proxy |
+| `/mp_update` | Check for updates |
+| `/mp_help` | Show all commands |
+
+**Automatic alerts:**
+- Proxy goes down → instant Telegram notification + auto-restart attempt
+- Proxy starts up → sends all active links + QR codes
+- Periodic traffic reports at your chosen interval
+
+### Proxy Chaining (Upstream Routing)
+
+Route proxy traffic through intermediate servers to hide your IP or bypass network restrictions:
+
+```bash
+# Route 20% of traffic through Cloudflare WARP
+mtproxymax upstream add warp socks5 127.0.0.1:40000 - - 20
+
+# Route through a backup VPS
+mtproxymax upstream add backup socks5 203.0.113.50:1080 user pass 80
+```
+
+Supports **SOCKS5** (with auth), **SOCKS4**, and **direct** routing with weight-based load balancing.
+
+### Real-Time Traffic Monitoring
+
+**Prometheus metrics** give you real per-user stats — not just iptables byte counters:
+
+```bash
+mtproxymax traffic # Per-user breakdown
+mtproxymax status # Overview with connections count
+```
+
+Metrics include:
+- Bytes uploaded/downloaded per user
+- Active connections per user
+- Total traffic with cumulative tracking across restarts
+
+### Geo-Blocking
+
+Block entire countries from accessing your proxy:
+
+```bash
+mtproxymax geoblock add ir # Block Iran
+mtproxymax geoblock add cn # Block China
+mtproxymax geoblock list # See blocked countries
+```
+
+Uses IP-level CIDR blocklists enforced via iptables — traffic is dropped before it reaches the proxy.
+
+### Ad-Tag Monetization
+
+Pin a promoted channel in your users' Telegram chat list:
+
+```bash
+mtproxymax adtag set
+```
+
+Get your ad-tag from [@MTProxyBot](https://t.me/MTProxyBot) on Telegram. Users see a pinned channel — you earn from the proxy.
+
+### Engine Management
+
+MTProxyMax builds telemt from source, pinned to a known-good commit. You control exactly what version runs:
+
+```bash
+mtproxymax engine status # Current version + available updates
+mtproxymax engine latest # Update to newest commit
+mtproxymax engine switch abc1234 # Pin to specific commit
+mtproxymax rebuild # Force rebuild from source
+```
+
+Pre-built multi-arch Docker images (amd64 + arm64) are pulled automatically. Source compilation is the automatic fallback.
+
+### Interactive TUI
+
+Run `mtproxymax menu` for a full terminal UI:
+
+```
+╔═══════════════════════════════════════╗
+║ MTProxyMax Manager ║
+╠═══════════════════════════════════════╣
+║ [1] Proxy Status ║
+║ [2] Secrets Management ║
+║ [3] Security & Routing ║
+║ [4] Traffic & Logs ║
+║ [5] Telegram Bot ║
+║ [6] Settings ║
+║ [7] Engine Management ║
+║ [8] Info & Help ║
+║ [9] Advanced Tools ║
+║ [0] Exit ║
+╚═══════════════════════════════════════╝
+```
+
+Every feature is accessible through the menu. Built-in help pages explain FakeTLS, traffic masking, proxy chaining, user limits, and more.
+
+---
+
+## Comparison
+
+### MTProxyMax vs Other MTProxy Solutions
+
+| Feature | **MTProxyMax** | **mtg v2** (Go) | **Official MTProxy** (C) | **Bash Installers** |
+|---------|:-:|:-:|:-:|:-:|
+| **Engine** | telemt 3.x (Rust) | mtg (Go) | MTProxy (C) | Various |
+| **FakeTLS** | Yes | Yes | No (needs patches) | Varies |
+| **Traffic Masking** | Yes | No | No | No |
+| **Multi-User Secrets** | Yes (unlimited) | No (1 secret) | Multi-secret | Usually 1 |
+| **Per-User Limits** | Yes (conns, IPs, quota, expiry) | No | No | No |
+| **Per-User Traffic Stats** | Yes (Prometheus) | No | No | No |
+| **Telegram Bot** | Yes (17 commands) | No | No | No |
+| **Interactive TUI** | Yes (full menus) | No | No | No |
+| **Proxy Chaining** | Yes (SOCKS5/4, weighted) | Yes (SOCKS5) | No | No |
+| **Geo-Blocking** | Yes | IP allowlist/blocklist | No | No |
+| **Ad-Tag Support** | Yes | No (removed in v2) | Yes | Varies |
+| **QR Code Generation** | Yes | No | No | Some |
+| **Auto-Recovery** | Yes (with alerts) | No | No | No |
+| **Prometheus Metrics** | Yes (built-in) | Yes | No | No |
+| **Auto-Update** | Yes (with rollback) | No | No | No |
+| **Health Diagnostics** | Yes | No | No | No |
+| **Docker** | Yes (multi-arch) | Yes | No (manual) | Varies |
+| **CLI + TUI** | Both | CLI only | CLI only | CLI only |
+| **User Expiry Dates** | Yes | No | No | No |
+| **Bandwidth Quotas** | Yes | No | No | No |
+| **Device Limits** | Yes | No | No | No |
+| **Active Development** | Yes | Yes | Abandoned | Varies |
+
+### Why Not mtg?
+
+[mtg](https://github.com/9seconds/mtg) is a solid, minimal proxy — and that's by design. It's **"highly opinionated"** and intentionally barebones. If you want a fire-and-forget single-user proxy, mtg is fine.
+
+But mtg v2:
+- **Dropped ad-tag support** entirely (v1 has it, but v1 is in maintenance-only mode)
+- **Only supports one secret** — no multi-user management
+- **No user limits** — can't restrict bandwidth, devices, or set expiry dates
+- **No management interface** — no TUI, no Telegram bot, no traffic breakdown per user
+- **No traffic masking** — non-proxy connections aren't forwarded to a cover site
+- **No auto-recovery** — if it goes down, you won't know until users complain
+
+### Why Not the Official MTProxy?
+
+[Telegram's official MTProxy](https://github.com/TelegramMessenger/MTProxy) (C implementation) was **last updated in 2019** and is effectively abandoned:
+- No FakeTLS support (requires community patches)
+- No traffic masking
+- No per-user controls
+- Manual compilation, no Docker support
+- No monitoring or management tools
+
+### Why Not a Simple Bash Installer?
+
+Scripts like [MTProtoProxyInstaller](https://github.com/HirbodBehnam/MTProtoProxyInstaller) are simple wrappers: they install a proxy and give you a link. That's it. No user management, no monitoring, no bot, no updates, no recovery.
+
+MTProxyMax is not just an installer — it's a **management platform** that happens to install itself.
+
+---
+
+## Architecture
+
+```
+Telegram Client
+ │
+ ▼
+┌─────────────────────────┐
+│ Your Server (port 443) │
+│ ┌───────────────────┐ │
+│ │ Docker Container │ │
+│ │ ┌─────────────┐ │ │
+│ │ │ telemt │ │ │ ← Rust/Tokio engine
+│ │ │ (FakeTLS) │ │ │
+│ │ └──────┬──────┘ │ │
+│ └─────────┼─────────┘ │
+│ │ │
+│ ┌──────┴──────┐ │
+│ ▼ ▼ │
+│ Direct SOCKS5 │ ← Upstream routing
+│ routing chaining │
+└─────────┬───────────────┘
+ │
+ ▼
+ Telegram Servers
+```
+
+**Components:**
+- **mtproxymax.sh** — Single bash script: CLI dispatcher, TUI renderer, config manager
+- **telemt** — Rust MTProto engine running inside Docker (handles all proxy traffic)
+- **Telegram bot service** — Independent systemd service polling Telegram Bot API
+- **Prometheus endpoint** — telemt exposes `/metrics` on port 9090 (localhost only)
+
+**Data flow:**
+- User commands → bash script → regenerates TOML config → restarts container
+- Traffic stats → Prometheus `/metrics` → parsed by script → displayed in TUI/CLI/Telegram
+- Telegram bot commands → polling service → executes mtproxymax CLI → returns result
+
+---
+
+## CLI Reference
+
+### Proxy Management
+
+```bash
+mtproxymax install # Run installation wizard
+mtproxymax uninstall # Remove everything
+mtproxymax start # Start proxy
+mtproxymax stop # Stop proxy
+mtproxymax restart # Restart proxy
+mtproxymax status # Show proxy status
+mtproxymax menu # Open interactive TUI
+```
+
+### User Secrets
+
+```bash
+mtproxymax secret add # Add user
+mtproxymax secret remove # Remove user
+mtproxymax secret list # List all users
+mtproxymax secret rotate # New key, same label
+mtproxymax secret enable # Re-enable user
+mtproxymax secret disable # Temporarily disable
+mtproxymax secret link [label] # Show proxy link
+mtproxymax secret qr [label] # Show QR code
+mtproxymax secret setlimit # Set individual limit
+mtproxymax secret setlimits [expires] # Set all limits
+```
+
+### Configuration
+
+```bash
+mtproxymax port [get|] # Get/set proxy port
+mtproxymax domain [get|clear|] # Get/set FakeTLS domain
+mtproxymax adtag set # Set ad-tag
+mtproxymax adtag remove # Remove ad-tag
+```
+
+### Security
+
+```bash
+mtproxymax geoblock add # Block country
+mtproxymax geoblock remove # Unblock country
+mtproxymax geoblock list # List blocked countries
+```
+
+### Upstream Routing
+
+```bash
+mtproxymax upstream list # List upstreams
+mtproxymax upstream add [user] [pass] [weight]
+mtproxymax upstream remove # Remove upstream
+mtproxymax upstream enable # Enable upstream
+mtproxymax upstream disable # Disable upstream
+mtproxymax upstream test # Test connectivity
+```
+
+### Monitoring
+
+```bash
+mtproxymax traffic # Per-user traffic breakdown
+mtproxymax logs # Stream live logs
+mtproxymax health # Run diagnostics
+```
+
+### Engine & Updates
+
+```bash
+mtproxymax engine status # Show version + check updates
+mtproxymax engine latest # Update to latest commit
+mtproxymax engine switch # Switch to specific commit
+mtproxymax rebuild # Force rebuild from source
+mtproxymax update # Check for script updates
+mtproxymax version # Show version info
+```
+
+### Telegram Bot
+
+```bash
+mtproxymax telegram setup # Interactive bot setup
+mtproxymax telegram status # Show bot status
+mtproxymax telegram test # Send test message
+mtproxymax telegram disable # Disable bot
+mtproxymax telegram remove # Remove bot completely
+```
+
+---
+
+## System Requirements
+
+- **OS:** Ubuntu, Debian, CentOS, RHEL, Fedora, Rocky, AlmaLinux, Alpine
+- **Docker:** Auto-installed if not present
+- **RAM:** 256MB minimum (configurable)
+- **Access:** Root required
+- **Bash:** 4.2+
+
+---
+
+## Configuration Files
+
+| File | Purpose |
+|------|---------|
+| `/opt/mtproxymax/settings.conf` | Proxy settings (port, domain, limits) |
+| `/opt/mtproxymax/secrets.conf` | User keys, limits, expiry dates |
+| `/opt/mtproxymax/upstreams.conf` | Upstream routing rules |
+| `/opt/mtproxymax/mtproxy/config.toml` | Generated telemt engine config |
+
+---
+
+## License
+
+MIT License - see [LICENSE](LICENSE) for details.
+
+Copyright (c) 2026 SamNet Technologies
diff --git a/mtproxymax.sh b/mtproxymax.sh
index efe76c7..1913b6c 100644
--- a/mtproxymax.sh
+++ b/mtproxymax.sh
@@ -125,15 +125,24 @@ TERM_WIDTH=$(tput cols 2>/dev/null || echo 60)
# Get string display length (strips ANSI escape codes)
_strlen() {
- local str="$1"
- str=$(echo -e "$str" | sed 's/\x1b\[[0-9;]*m//g')
- echo ${#str}
+ local clean="$1"
+ local esc=$'\033'
+ # Strip ANSI escape sequences in pure bash (no subprocesses)
+ while [[ "$clean" == *"${esc}["* ]]; do
+ local before="${clean%%${esc}\[*}"
+ local rest="${clean#*${esc}\[}"
+ local after="${rest#*m}"
+ [ "$rest" = "$after" ] && break
+ clean="${before}${after}"
+ done
+ echo "${#clean}"
}
-# Repeat a character n times
+# Repeat a character n times (pure bash, no subprocesses)
_repeat() {
- local char="$1" count="$2"
- printf '%0.s'"$char" $(seq 1 "$count")
+ local char="$1" count="$2" str
+ printf -v str '%*s' "$count" ''
+ printf '%s' "${str// /$char}"
}
# Draw a horizontal line
@@ -393,13 +402,24 @@ escape_md() {
}
# Get public IP address
+_PUBLIC_IP_CACHE=""
+_PUBLIC_IP_CACHE_AGE=0
+
get_public_ip() {
+ local now; now=$(date +%s)
+ # Return cached IP if less than 5 minutes old
+ if [ -n "$_PUBLIC_IP_CACHE" ] && [ $(( now - _PUBLIC_IP_CACHE_AGE )) -lt 300 ]; then
+ echo "$_PUBLIC_IP_CACHE"
+ return 0
+ fi
local ip=""
- ip=$(curl -s --max-time 5 https://api.ipify.org 2>/dev/null) ||
- ip=$(curl -s --max-time 5 https://ifconfig.me 2>/dev/null) ||
- ip=$(curl -s --max-time 5 https://icanhazip.com 2>/dev/null) ||
+ ip=$(curl -s --max-time 3 https://api.ipify.org 2>/dev/null) ||
+ ip=$(curl -s --max-time 3 https://ifconfig.me 2>/dev/null) ||
+ ip=$(curl -s --max-time 3 https://icanhazip.com 2>/dev/null) ||
ip=""
if [[ "$ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || [[ "$ip" =~ : ]]; then
+ _PUBLIC_IP_CACHE="$ip"
+ _PUBLIC_IP_CACHE_AGE=$now
echo "$ip"
fi
}
@@ -2105,6 +2125,7 @@ run_proxy_container() {
if is_proxy_running; then
log_success "Proxy is running on port ${PROXY_PORT}"
traffic_tracking_setup
+ geoblock_reapply_all
# Show links for all enabled secrets
local server_ip
@@ -2213,13 +2234,125 @@ generate_qr_url() {
# ── Section 11: Geo-Blocking ────────────────────────────────
-build_blocklist_config() {
- local countries="$BLOCKLIST_COUNTRIES"
- [ -z "$countries" ] && return
+GEOBLOCK_CACHE_DIR="${INSTALL_DIR}/geoblock"
+GEOBLOCK_IPSET_PREFIX="mtpmax_"
+GEOBLOCK_COMMENT="mtproxymax-geoblock"
- # This would be added to telemt config if telemt supports it
- # For now, use iptables-based blocking
- log_info "Geo-blocking configured for: ${countries}"
+# Ensure ipset is installed
+_ensure_ipset() {
+ command -v ipset &>/dev/null && return 0
+ log_info "Installing ipset..."
+ local os; os=$(detect_os)
+ case "$os" in
+ debian) apt-get install -y -qq ipset ;;
+ rhel) yum install -y -q ipset ;;
+ alpine) apk add --no-cache ipset ;;
+ esac
+ command -v ipset &>/dev/null || { log_error "Failed to install ipset"; return 1; }
+}
+
+# Download and cache CIDR list for a country
+_download_country_cidrs() {
+ local code="$1"
+ local cache_file="${GEOBLOCK_CACHE_DIR}/${code}.zone"
+ mkdir -p "$GEOBLOCK_CACHE_DIR"
+
+ # Use cached file if less than 24 hours old
+ if [ -f "$cache_file" ] && [ $(( $(date +%s) - $(stat -c %Y "$cache_file" 2>/dev/null || echo 0) )) -lt 86400 ]; then
+ return 0
+ fi
+
+ log_info "Downloading IP list for ${code^^}..."
+ local url="https://www.ipdeny.com/ipblocks/data/aggregated/${code}-aggregated.zone"
+ if ! curl -fsSL --max-time 30 "$url" -o "$cache_file" 2>/dev/null; then
+ rm -f "$cache_file"
+ log_error "Failed to download IP list for ${code^^} — check country code"
+ return 1
+ fi
+
+ local count; count=$(wc -l < "$cache_file")
+ log_info "Downloaded ${count} IP ranges for ${code^^}"
+}
+
+# Apply iptables/ipset rules for one country
+_apply_country_rules() {
+ local code="$1"
+ local setname="${GEOBLOCK_IPSET_PREFIX}${code}"
+ local cache_file="${GEOBLOCK_CACHE_DIR}/${code}.zone"
+
+ [ -f "$cache_file" ] || { log_error "No cached IP list for ${code}"; return 1; }
+
+ # Create if not exists, then flush to clear stale entries
+ ipset create -exist "$setname" hash:net family inet maxelem 131072
+ ipset flush "$setname"
+
+ # Batch load all CIDRs via ipset restore (fast, single pass)
+ awk -v s="$setname" 'NF && !/^#/ { print "add " s " " $1 }' "$cache_file" \
+ | ipset restore -exist
+
+ # Add iptables DROP rule if not already present
+ if ! iptables -C INPUT -m set --match-set "$setname" src \
+ -p tcp --dport "$PROXY_PORT" \
+ -m comment --comment "$GEOBLOCK_COMMENT" -j DROP 2>/dev/null; then
+ iptables -I INPUT -m set --match-set "$setname" src \
+ -p tcp --dport "$PROXY_PORT" \
+ -m comment --comment "$GEOBLOCK_COMMENT" -j DROP
+ fi
+
+ log_success "Geo-blocking active for ${code^^} (port ${PROXY_PORT})"
+}
+
+# Remove iptables rules and ipset for one country
+_remove_country_rules() {
+ local code="$1"
+ local setname="${GEOBLOCK_IPSET_PREFIX}${code}"
+
+ # Remove iptables rule
+ iptables -D INPUT -m set --match-set "$setname" src \
+ -p tcp --dport "$PROXY_PORT" \
+ -m comment --comment "$GEOBLOCK_COMMENT" -j DROP 2>/dev/null || true
+
+ # Destroy ipset
+ ipset destroy "$setname" 2>/dev/null || true
+}
+
+# Reapply all saved geoblock rules (called on proxy start)
+geoblock_reapply_all() {
+ [ -z "$BLOCKLIST_COUNTRIES" ] && return 0
+ command -v ipset &>/dev/null || return 0
+
+ local code
+ IFS=',' read -ra codes <<< "$BLOCKLIST_COUNTRIES"
+ for code in "${codes[@]}"; do
+ [ -z "$code" ] && continue
+ if [ -f "${GEOBLOCK_CACHE_DIR}/${code}.zone" ]; then
+ _apply_country_rules "$code" &>/dev/null || true
+ fi
+ done
+}
+
+# Remove ALL mtproxymax geoblock rules (called on uninstall)
+geoblock_remove_all() {
+ # Remove all tagged iptables rules
+ if command -v iptables &>/dev/null; then
+ iptables-save 2>/dev/null | grep -- "--comment ${GEOBLOCK_COMMENT}" | \
+ sed 's/^-A/-D/' | while IFS= read -r rule; do
+ iptables $rule 2>/dev/null || true
+ done
+ fi
+
+ # Destroy all mtpmax_ ipsets
+ if command -v ipset &>/dev/null; then
+ ipset list -n 2>/dev/null | grep "^${GEOBLOCK_IPSET_PREFIX}" | \
+ while IFS= read -r setname; do
+ ipset destroy "$setname" 2>/dev/null || true
+ done
+ fi
+}
+
+build_blocklist_config() {
+ [ -z "$BLOCKLIST_COUNTRIES" ] && return
+ geoblock_reapply_all
}
show_geoblock_menu() {
@@ -2250,14 +2383,12 @@ show_geoblock_menu() {
if [[ "$code" =~ ^[a-z]{2}$ ]]; then
if echo ",$BLOCKLIST_COUNTRIES," | grep -q ",${code},"; then
log_info "Country '${code}' is already blocked"
- elif [ -z "$BLOCKLIST_COUNTRIES" ]; then
- BLOCKLIST_COUNTRIES="$code"
- save_settings
- log_success "Added ${code} to blocklist"
else
- BLOCKLIST_COUNTRIES="${BLOCKLIST_COUNTRIES},${code}"
- save_settings
- log_success "Added ${code} to blocklist"
+ _ensure_ipset && _download_country_cidrs "$code" && {
+ [ -z "$BLOCKLIST_COUNTRIES" ] && BLOCKLIST_COUNTRIES="$code" || BLOCKLIST_COUNTRIES="${BLOCKLIST_COUNTRIES},${code}"
+ save_settings
+ _apply_country_rules "$code"
+ }
fi
else
log_error "Invalid country code (use 2-letter ISO code, e.g. us, de, ir)"
@@ -2273,7 +2404,9 @@ show_geoblock_menu() {
if echo ",$BLOCKLIST_COUNTRIES," | grep -q ",${rm_code},"; then
BLOCKLIST_COUNTRIES=$(echo ",$BLOCKLIST_COUNTRIES," | sed "s/,${rm_code},/,/g;s/^,//;s/,$//")
save_settings
- log_success "Removed ${rm_code}"
+ _remove_country_rules "$rm_code"
+ rm -f "${GEOBLOCK_CACHE_DIR}/${rm_code}.zone"
+ log_success "Removed ${rm_code^^} — rules and cache cleared"
else
log_info "Country '${rm_code}' is not in the blocklist"
fi
@@ -2283,9 +2416,16 @@ show_geoblock_menu() {
press_any_key
;;
3)
+ local code
+ IFS=',' read -ra codes <<< "$BLOCKLIST_COUNTRIES"
+ for code in "${codes[@]}"; do
+ [ -z "$code" ] && continue
+ _remove_country_rules "$code"
+ rm -f "${GEOBLOCK_CACHE_DIR}/${code}.zone"
+ done
BLOCKLIST_COUNTRIES=""
save_settings
- log_success "Blocklist cleared"
+ log_success "All geo-blocks cleared"
press_any_key
;;
0|"") return ;;
@@ -3659,6 +3799,9 @@ uninstall() {
systemctl daemon-reload 2>/dev/null || true
+ log_info "Removing geo-blocking rules..."
+ geoblock_remove_all
+
log_info "Removing traffic tracking..."
traffic_tracking_teardown
@@ -4004,11 +4147,13 @@ cli_main() {
local code=$(echo "$2" | tr '[:upper:]' '[:lower:]')
if [[ "$code" =~ ^[a-z]{2}$ ]]; then
if echo ",$BLOCKLIST_COUNTRIES," | grep -q ",${code},"; then
- log_info "Country '${code}' is already blocked"
+ log_info "Country '${code^^}' is already blocked"
else
- [ -z "$BLOCKLIST_COUNTRIES" ] && BLOCKLIST_COUNTRIES="$code" || BLOCKLIST_COUNTRIES="${BLOCKLIST_COUNTRIES},${code}"
- save_settings
- log_success "Added ${code}"
+ _ensure_ipset && _download_country_cidrs "$code" && {
+ [ -z "$BLOCKLIST_COUNTRIES" ] && BLOCKLIST_COUNTRIES="$code" || BLOCKLIST_COUNTRIES="${BLOCKLIST_COUNTRIES},${code}"
+ save_settings
+ _apply_country_rules "$code"
+ }
fi
else
log_error "Invalid country code (use 2-letter ISO code, e.g. us, de, ir)"
@@ -4021,9 +4166,11 @@ cli_main() {
if echo ",$BLOCKLIST_COUNTRIES," | grep -q ",${code},"; then
BLOCKLIST_COUNTRIES=$(echo ",$BLOCKLIST_COUNTRIES," | sed "s/,${code},/,/g;s/^,//;s/,$//")
save_settings
- log_success "Removed ${code}"
+ _remove_country_rules "$code"
+ rm -f "${GEOBLOCK_CACHE_DIR}/${code}.zone"
+ log_success "Removed ${code^^} — rules and cache cleared"
else
- log_info "Country '${code}' is not blocked"
+ log_info "Country '${code^^}' is not blocked"
fi
else
log_error "Invalid country code (use 2-letter ISO code)"
@@ -4031,9 +4178,16 @@ cli_main() {
;;
clear)
check_root
+ local code
+ IFS=',' read -ra codes <<< "$BLOCKLIST_COUNTRIES"
+ for code in "${codes[@]}"; do
+ [ -z "$code" ] && continue
+ _remove_country_rules "$code"
+ rm -f "${GEOBLOCK_CACHE_DIR}/${code}.zone"
+ done
BLOCKLIST_COUNTRIES=""
save_settings
- log_success "Blocklist cleared"
+ log_success "All geo-blocks cleared"
;;
list|"")
echo -e " ${BOLD}Blocked countries:${NC} ${BLOCKLIST_COUNTRIES:-${DIM}none${NC}}"
@@ -4393,7 +4547,7 @@ show_upstream_menu() {
}
show_main_menu() {
- local _cached_telemt_ver
+ local _cached_telemt_ver _cached_start_epoch=""
_cached_telemt_ver=$(get_telemt_version)
while true; do
@@ -4403,7 +4557,7 @@ show_main_menu() {
show_banner
- # Status dashboard — single Docker check, cached
+ # Status dashboard — single Docker check
draw_box_top "$w"
local _running=false
@@ -4414,24 +4568,21 @@ show_main_menu() {
local status_str uptime_str traffic_in traffic_out connections
if [ "$_running" = "true" ]; then
status_str=$(draw_status running)
- local started_at up_secs=0
- started_at=$(docker inspect --format '{{.State.StartedAt}}' "$CONTAINER_NAME" 2>/dev/null)
- if [ -n "$started_at" ]; then
- local start_epoch now_epoch
- start_epoch=$(date -d "${started_at}" +%s 2>/dev/null || echo "0")
- now_epoch=$(date +%s)
- up_secs=$((now_epoch - start_epoch))
+ # Cache docker inspect — skip on subsequent renders unless container restarted
+ if [ -z "$_cached_start_epoch" ]; then
+ local started_at
+ started_at=$(docker inspect --format '{{.State.StartedAt}}' "$CONTAINER_NAME" 2>/dev/null)
+ _cached_start_epoch=$(date -d "${started_at}" +%s 2>/dev/null || echo "0")
fi
+ local up_secs=$(( $(date +%s) - _cached_start_epoch ))
uptime_str=$(format_duration "$up_secs")
- local stats
- stats=$(get_proxy_stats)
- traffic_in=$(echo "$stats" | awk '{print $1}')
- traffic_out=$(echo "$stats" | awk '{print $2}')
- connections=$(echo "$stats" | awk '{print $3}')
+ # Parse all stats fields in a single read (no awk subprocesses)
+ read -r traffic_in traffic_out connections < <(get_proxy_stats)
else
status_str=$(draw_status stopped)
uptime_str="—"
traffic_in=0; traffic_out=0; connections=0
+ _cached_start_epoch="" # Reset so it re-fetches when container comes back up
fi
local active=0 disabled=0
@@ -4489,11 +4640,9 @@ show_proxy_menu() {
clear_screen
draw_header "PROXY MANAGEMENT"
echo ""
- if is_proxy_running; then
- echo -e " Status: $(draw_status running)"
- else
- echo -e " Status: $(draw_status stopped)"
- fi
+ local _pstatus
+ docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^${CONTAINER_NAME}$" && _pstatus="running" || _pstatus="stopped"
+ echo -e " Status: $(draw_status "$_pstatus")"
echo ""
echo -e " ${DIM}[1]${NC} Start proxy"
echo -e " ${DIM}[2]${NC} Stop proxy"