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
This commit is contained in:
502
README.md
Normal file
502
README.md
Normal file
@@ -0,0 +1,502 @@
|
|||||||
|
<p align="center">
|
||||||
|
<h1 align="center">MTProxyMax</h1>
|
||||||
|
<p align="center"><b>The Ultimate Telegram MTProto Proxy Manager</b></p>
|
||||||
|
<p align="center">
|
||||||
|
One script. Full control. Zero hassle.
|
||||||
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
<a href="#quick-start">Quick Start</a> •
|
||||||
|
<a href="#features">Features</a> •
|
||||||
|
<a href="#comparison">Comparison</a> •
|
||||||
|
<a href="#telegram-bot">Telegram Bot</a> •
|
||||||
|
<a href="#cli-reference">CLI Reference</a>
|
||||||
|
</p>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
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 <label>` | Add new user, get link instantly |
|
||||||
|
| `/mp_remove <label>` | Delete user |
|
||||||
|
| `/mp_rotate <label>` | Generate new key for user |
|
||||||
|
| `/mp_enable <label>` | Re-enable disabled user |
|
||||||
|
| `/mp_disable <label>` | 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 <hex_from_MTProxyBot>
|
||||||
|
```
|
||||||
|
|
||||||
|
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 <label> # Add user
|
||||||
|
mtproxymax secret remove <label> # Remove user
|
||||||
|
mtproxymax secret list # List all users
|
||||||
|
mtproxymax secret rotate <label> # New key, same label
|
||||||
|
mtproxymax secret enable <label> # Re-enable user
|
||||||
|
mtproxymax secret disable <label> # Temporarily disable
|
||||||
|
mtproxymax secret link [label] # Show proxy link
|
||||||
|
mtproxymax secret qr [label] # Show QR code
|
||||||
|
mtproxymax secret setlimit <label> <type> <value> # Set individual limit
|
||||||
|
mtproxymax secret setlimits <label> <conns> <ips> <quota> [expires] # Set all limits
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mtproxymax port [get|<number>] # Get/set proxy port
|
||||||
|
mtproxymax domain [get|clear|<host>] # Get/set FakeTLS domain
|
||||||
|
mtproxymax adtag set <hex> # Set ad-tag
|
||||||
|
mtproxymax adtag remove # Remove ad-tag
|
||||||
|
```
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mtproxymax geoblock add <CC> # Block country
|
||||||
|
mtproxymax geoblock remove <CC> # Unblock country
|
||||||
|
mtproxymax geoblock list # List blocked countries
|
||||||
|
```
|
||||||
|
|
||||||
|
### Upstream Routing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mtproxymax upstream list # List upstreams
|
||||||
|
mtproxymax upstream add <name> <type> <addr> [user] [pass] [weight]
|
||||||
|
mtproxymax upstream remove <name> # Remove upstream
|
||||||
|
mtproxymax upstream enable <name> # Enable upstream
|
||||||
|
mtproxymax upstream disable <name> # Disable upstream
|
||||||
|
mtproxymax upstream test <name> # 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 <commit> # 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
|
||||||
241
mtproxymax.sh
241
mtproxymax.sh
@@ -125,15 +125,24 @@ TERM_WIDTH=$(tput cols 2>/dev/null || echo 60)
|
|||||||
|
|
||||||
# Get string display length (strips ANSI escape codes)
|
# Get string display length (strips ANSI escape codes)
|
||||||
_strlen() {
|
_strlen() {
|
||||||
local str="$1"
|
local clean="$1"
|
||||||
str=$(echo -e "$str" | sed 's/\x1b\[[0-9;]*m//g')
|
local esc=$'\033'
|
||||||
echo ${#str}
|
# 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() {
|
_repeat() {
|
||||||
local char="$1" count="$2"
|
local char="$1" count="$2" str
|
||||||
printf '%0.s'"$char" $(seq 1 "$count")
|
printf -v str '%*s' "$count" ''
|
||||||
|
printf '%s' "${str// /$char}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Draw a horizontal line
|
# Draw a horizontal line
|
||||||
@@ -393,13 +402,24 @@ escape_md() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Get public IP address
|
# Get public IP address
|
||||||
|
_PUBLIC_IP_CACHE=""
|
||||||
|
_PUBLIC_IP_CACHE_AGE=0
|
||||||
|
|
||||||
get_public_ip() {
|
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=""
|
local ip=""
|
||||||
ip=$(curl -s --max-time 5 https://api.ipify.org 2>/dev/null) ||
|
ip=$(curl -s --max-time 3 https://api.ipify.org 2>/dev/null) ||
|
||||||
ip=$(curl -s --max-time 5 https://ifconfig.me 2>/dev/null) ||
|
ip=$(curl -s --max-time 3 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://icanhazip.com 2>/dev/null) ||
|
||||||
ip=""
|
ip=""
|
||||||
if [[ "$ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || [[ "$ip" =~ : ]]; then
|
if [[ "$ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || [[ "$ip" =~ : ]]; then
|
||||||
|
_PUBLIC_IP_CACHE="$ip"
|
||||||
|
_PUBLIC_IP_CACHE_AGE=$now
|
||||||
echo "$ip"
|
echo "$ip"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -2105,6 +2125,7 @@ run_proxy_container() {
|
|||||||
if is_proxy_running; then
|
if is_proxy_running; then
|
||||||
log_success "Proxy is running on port ${PROXY_PORT}"
|
log_success "Proxy is running on port ${PROXY_PORT}"
|
||||||
traffic_tracking_setup
|
traffic_tracking_setup
|
||||||
|
geoblock_reapply_all
|
||||||
|
|
||||||
# Show links for all enabled secrets
|
# Show links for all enabled secrets
|
||||||
local server_ip
|
local server_ip
|
||||||
@@ -2213,13 +2234,125 @@ generate_qr_url() {
|
|||||||
|
|
||||||
# ── Section 11: Geo-Blocking ────────────────────────────────
|
# ── Section 11: Geo-Blocking ────────────────────────────────
|
||||||
|
|
||||||
build_blocklist_config() {
|
GEOBLOCK_CACHE_DIR="${INSTALL_DIR}/geoblock"
|
||||||
local countries="$BLOCKLIST_COUNTRIES"
|
GEOBLOCK_IPSET_PREFIX="mtpmax_"
|
||||||
[ -z "$countries" ] && return
|
GEOBLOCK_COMMENT="mtproxymax-geoblock"
|
||||||
|
|
||||||
# This would be added to telemt config if telemt supports it
|
# Ensure ipset is installed
|
||||||
# For now, use iptables-based blocking
|
_ensure_ipset() {
|
||||||
log_info "Geo-blocking configured for: ${countries}"
|
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() {
|
show_geoblock_menu() {
|
||||||
@@ -2250,14 +2383,12 @@ show_geoblock_menu() {
|
|||||||
if [[ "$code" =~ ^[a-z]{2}$ ]]; then
|
if [[ "$code" =~ ^[a-z]{2}$ ]]; then
|
||||||
if echo ",$BLOCKLIST_COUNTRIES," | grep -q ",${code},"; then
|
if echo ",$BLOCKLIST_COUNTRIES," | grep -q ",${code},"; then
|
||||||
log_info "Country '${code}' is already blocked"
|
log_info "Country '${code}' is already blocked"
|
||||||
elif [ -z "$BLOCKLIST_COUNTRIES" ]; then
|
|
||||||
BLOCKLIST_COUNTRIES="$code"
|
|
||||||
save_settings
|
|
||||||
log_success "Added ${code} to blocklist"
|
|
||||||
else
|
else
|
||||||
BLOCKLIST_COUNTRIES="${BLOCKLIST_COUNTRIES},${code}"
|
_ensure_ipset && _download_country_cidrs "$code" && {
|
||||||
|
[ -z "$BLOCKLIST_COUNTRIES" ] && BLOCKLIST_COUNTRIES="$code" || BLOCKLIST_COUNTRIES="${BLOCKLIST_COUNTRIES},${code}"
|
||||||
save_settings
|
save_settings
|
||||||
log_success "Added ${code} to blocklist"
|
_apply_country_rules "$code"
|
||||||
|
}
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
log_error "Invalid country code (use 2-letter ISO code, e.g. us, de, ir)"
|
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
|
if echo ",$BLOCKLIST_COUNTRIES," | grep -q ",${rm_code},"; then
|
||||||
BLOCKLIST_COUNTRIES=$(echo ",$BLOCKLIST_COUNTRIES," | sed "s/,${rm_code},/,/g;s/^,//;s/,$//")
|
BLOCKLIST_COUNTRIES=$(echo ",$BLOCKLIST_COUNTRIES," | sed "s/,${rm_code},/,/g;s/^,//;s/,$//")
|
||||||
save_settings
|
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
|
else
|
||||||
log_info "Country '${rm_code}' is not in the blocklist"
|
log_info "Country '${rm_code}' is not in the blocklist"
|
||||||
fi
|
fi
|
||||||
@@ -2283,9 +2416,16 @@ show_geoblock_menu() {
|
|||||||
press_any_key
|
press_any_key
|
||||||
;;
|
;;
|
||||||
3)
|
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=""
|
BLOCKLIST_COUNTRIES=""
|
||||||
save_settings
|
save_settings
|
||||||
log_success "Blocklist cleared"
|
log_success "All geo-blocks cleared"
|
||||||
press_any_key
|
press_any_key
|
||||||
;;
|
;;
|
||||||
0|"") return ;;
|
0|"") return ;;
|
||||||
@@ -3659,6 +3799,9 @@ uninstall() {
|
|||||||
|
|
||||||
systemctl daemon-reload 2>/dev/null || true
|
systemctl daemon-reload 2>/dev/null || true
|
||||||
|
|
||||||
|
log_info "Removing geo-blocking rules..."
|
||||||
|
geoblock_remove_all
|
||||||
|
|
||||||
log_info "Removing traffic tracking..."
|
log_info "Removing traffic tracking..."
|
||||||
traffic_tracking_teardown
|
traffic_tracking_teardown
|
||||||
|
|
||||||
@@ -4004,11 +4147,13 @@ cli_main() {
|
|||||||
local code=$(echo "$2" | tr '[:upper:]' '[:lower:]')
|
local code=$(echo "$2" | tr '[:upper:]' '[:lower:]')
|
||||||
if [[ "$code" =~ ^[a-z]{2}$ ]]; then
|
if [[ "$code" =~ ^[a-z]{2}$ ]]; then
|
||||||
if echo ",$BLOCKLIST_COUNTRIES," | grep -q ",${code},"; then
|
if echo ",$BLOCKLIST_COUNTRIES," | grep -q ",${code},"; then
|
||||||
log_info "Country '${code}' is already blocked"
|
log_info "Country '${code^^}' is already blocked"
|
||||||
else
|
else
|
||||||
|
_ensure_ipset && _download_country_cidrs "$code" && {
|
||||||
[ -z "$BLOCKLIST_COUNTRIES" ] && BLOCKLIST_COUNTRIES="$code" || BLOCKLIST_COUNTRIES="${BLOCKLIST_COUNTRIES},${code}"
|
[ -z "$BLOCKLIST_COUNTRIES" ] && BLOCKLIST_COUNTRIES="$code" || BLOCKLIST_COUNTRIES="${BLOCKLIST_COUNTRIES},${code}"
|
||||||
save_settings
|
save_settings
|
||||||
log_success "Added ${code}"
|
_apply_country_rules "$code"
|
||||||
|
}
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
log_error "Invalid country code (use 2-letter ISO code, e.g. us, de, ir)"
|
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
|
if echo ",$BLOCKLIST_COUNTRIES," | grep -q ",${code},"; then
|
||||||
BLOCKLIST_COUNTRIES=$(echo ",$BLOCKLIST_COUNTRIES," | sed "s/,${code},/,/g;s/^,//;s/,$//")
|
BLOCKLIST_COUNTRIES=$(echo ",$BLOCKLIST_COUNTRIES," | sed "s/,${code},/,/g;s/^,//;s/,$//")
|
||||||
save_settings
|
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
|
else
|
||||||
log_info "Country '${code}' is not blocked"
|
log_info "Country '${code^^}' is not blocked"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
log_error "Invalid country code (use 2-letter ISO code)"
|
log_error "Invalid country code (use 2-letter ISO code)"
|
||||||
@@ -4031,9 +4178,16 @@ cli_main() {
|
|||||||
;;
|
;;
|
||||||
clear)
|
clear)
|
||||||
check_root
|
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=""
|
BLOCKLIST_COUNTRIES=""
|
||||||
save_settings
|
save_settings
|
||||||
log_success "Blocklist cleared"
|
log_success "All geo-blocks cleared"
|
||||||
;;
|
;;
|
||||||
list|"")
|
list|"")
|
||||||
echo -e " ${BOLD}Blocked countries:${NC} ${BLOCKLIST_COUNTRIES:-${DIM}none${NC}}"
|
echo -e " ${BOLD}Blocked countries:${NC} ${BLOCKLIST_COUNTRIES:-${DIM}none${NC}}"
|
||||||
@@ -4393,7 +4547,7 @@ show_upstream_menu() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
show_main_menu() {
|
show_main_menu() {
|
||||||
local _cached_telemt_ver
|
local _cached_telemt_ver _cached_start_epoch=""
|
||||||
_cached_telemt_ver=$(get_telemt_version)
|
_cached_telemt_ver=$(get_telemt_version)
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
@@ -4403,7 +4557,7 @@ show_main_menu() {
|
|||||||
|
|
||||||
show_banner
|
show_banner
|
||||||
|
|
||||||
# Status dashboard — single Docker check, cached
|
# Status dashboard — single Docker check
|
||||||
draw_box_top "$w"
|
draw_box_top "$w"
|
||||||
|
|
||||||
local _running=false
|
local _running=false
|
||||||
@@ -4414,24 +4568,21 @@ show_main_menu() {
|
|||||||
local status_str uptime_str traffic_in traffic_out connections
|
local status_str uptime_str traffic_in traffic_out connections
|
||||||
if [ "$_running" = "true" ]; then
|
if [ "$_running" = "true" ]; then
|
||||||
status_str=$(draw_status running)
|
status_str=$(draw_status running)
|
||||||
local started_at up_secs=0
|
# 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)
|
started_at=$(docker inspect --format '{{.State.StartedAt}}' "$CONTAINER_NAME" 2>/dev/null)
|
||||||
if [ -n "$started_at" ]; then
|
_cached_start_epoch=$(date -d "${started_at}" +%s 2>/dev/null || echo "0")
|
||||||
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))
|
|
||||||
fi
|
fi
|
||||||
|
local up_secs=$(( $(date +%s) - _cached_start_epoch ))
|
||||||
uptime_str=$(format_duration "$up_secs")
|
uptime_str=$(format_duration "$up_secs")
|
||||||
local stats
|
# Parse all stats fields in a single read (no awk subprocesses)
|
||||||
stats=$(get_proxy_stats)
|
read -r traffic_in traffic_out connections < <(get_proxy_stats)
|
||||||
traffic_in=$(echo "$stats" | awk '{print $1}')
|
|
||||||
traffic_out=$(echo "$stats" | awk '{print $2}')
|
|
||||||
connections=$(echo "$stats" | awk '{print $3}')
|
|
||||||
else
|
else
|
||||||
status_str=$(draw_status stopped)
|
status_str=$(draw_status stopped)
|
||||||
uptime_str="—"
|
uptime_str="—"
|
||||||
traffic_in=0; traffic_out=0; connections=0
|
traffic_in=0; traffic_out=0; connections=0
|
||||||
|
_cached_start_epoch="" # Reset so it re-fetches when container comes back up
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local active=0 disabled=0
|
local active=0 disabled=0
|
||||||
@@ -4489,11 +4640,9 @@ show_proxy_menu() {
|
|||||||
clear_screen
|
clear_screen
|
||||||
draw_header "PROXY MANAGEMENT"
|
draw_header "PROXY MANAGEMENT"
|
||||||
echo ""
|
echo ""
|
||||||
if is_proxy_running; then
|
local _pstatus
|
||||||
echo -e " Status: $(draw_status running)"
|
docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^${CONTAINER_NAME}$" && _pstatus="running" || _pstatus="stopped"
|
||||||
else
|
echo -e " Status: $(draw_status "$_pstatus")"
|
||||||
echo -e " Status: $(draw_status stopped)"
|
|
||||||
fi
|
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " ${DIM}[1]${NC} Start proxy"
|
echo -e " ${DIM}[1]${NC} Start proxy"
|
||||||
echo -e " ${DIM}[2]${NC} Stop proxy"
|
echo -e " ${DIM}[2]${NC} Stop proxy"
|
||||||
|
|||||||
Reference in New Issue
Block a user