Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 81 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,18 @@ services:
restart: unless-stopped
network_mode: "host"
environment:
# Replace these values with your own configuration
# Core configuration - replace these values with your own
- PORT=443
- TG_KEY=00000000000000000000000000000001
- SECURE_ONLY=true
- TLS_ONLY=true
- TLS_DOMAIN=www.drive.google.com
- AD_TAG=3c09c680b76ee91a4c25ad51f742267d
# Optional: Performance tuning
# - TO_CLT_BUFSIZE=65536
# - TO_TG_BUFSIZE=65536
# Optional: Metrics
# - METRICS_PORT=9090
volumes:
- ./config.py:/home/tgproxy/config.py
```
Expand All @@ -57,6 +63,80 @@ Then run: `docker-compose up -d`

To advertise a channel get a tag from **@MTProxybot** and put it to *config.py*.

## Environment Variables ##

All configuration options can be set via environment variables. This is particularly useful when running in Docker.

### Required/Core Configuration ###

| Variable | Default | Description |
|----------|---------|-------------|
| `PORT` | `443` | Listening port for the proxy |
| `TG_KEY` | `00000000000000000000000000000001` | User secret (32 hex characters) |
| `AD_TAG` | `3c09c680b76ee91a4c25ad51f742267d` | Tag for advertising, obtainable from @MTProxybot |

### Security Settings ###

| Variable | Default | Description |
|----------|---------|-------------|
| `SECURE_ONLY` | `true` | Makes the proxy harder to detect (incompatible with very old clients) |
| `TLS_ONLY` | `true` | Makes the proxy even harder to detect (compatible only with recent clients) |
| `TLS_DOMAIN` | `www.google.com` | Domain for TLS, bad clients are proxied there |

### SOCKS5 Proxy Settings (Optional) ###

| Variable | Default | Description |
|----------|---------|-------------|
| `SOCKS5_HOST` | `None` | SOCKS5 proxy hostname or IP address |
| `SOCKS5_PORT` | `None` | SOCKS5 proxy port |
| `SOCKS5_USER` | `None` | SOCKS5 username (optional) |
| `SOCKS5_PASS` | `None` | SOCKS5 password (optional) |

**Note:** When SOCKS5 is enabled, middle proxy advertising is automatically disabled.

### Performance Tuning (Optional) ###

| Variable | Default | Description |
|----------|---------|-------------|
| `TO_CLT_BUFSIZE` | `16384,100,131072` | Buffer size to client. Single integer or comma-separated tuple (low,users_margin,high) for adaptive sizing |
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default value documented here should be (16384, 100, 131072) (a tuple) not 16384,100,131072 (a string). According to mtprotoproxy.py:275, the default is a tuple: conf_dict.setdefault("TO_CLT_BUFSIZE", (16384, 100, 131072)).

The current documentation could be misleading as it suggests the default is a string format, when in fact the code defaults to a tuple when the variable is not set.

Suggested change
| `TO_CLT_BUFSIZE` | `16384,100,131072` | Buffer size to client. Single integer or comma-separated tuple (low,users_margin,high) for adaptive sizing |
| `TO_CLT_BUFSIZE` | `(16384, 100, 131072)` | Buffer size to client. Single integer or tuple (low, users_margin, high) for adaptive sizing. Default is a tuple. |

Copilot uses AI. Check for mistakes.
| `TO_TG_BUFSIZE` | `65536` | Buffer size to Telegram servers. Single integer or comma-separated tuple for adaptive sizing |
| `STATS_PRINT_PERIOD` | `600` | Statistics print period in seconds |
| `CLIENT_KEEPALIVE` | `600` | Client keepalive period in seconds (10 minutes) |
| `TG_CONNECT_TIMEOUT` | `10` | Telegram server connect timeout in seconds |
| `FAST_MODE` | `true` | Enable fast mode (disables some checks for better performance) |

### Network Settings (Optional) ###

| Variable | Default | Description |
|----------|---------|-------------|
| `LISTEN_ADDR_IPV4` | `0.0.0.0` | IPv4 listen address |
| `LISTEN_ADDR_IPV6` | `::` | IPv6 listen address |
| `PREFER_IPV6` | Auto-detected | Prefer IPv6 for outgoing connections |
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The documentation states the default is "Auto-detected" based on mtprotoproxy.py:164 which sets it to socket.has_ipv6. However, the table format should clarify this is a boolean value that gets auto-detected. Consider changing to something like: Auto-detected (socket.has_ipv6) or true if IPv6 available to be clearer about the data type.

Suggested change
| `PREFER_IPV6` | Auto-detected | Prefer IPv6 for outgoing connections |
| `PREFER_IPV6` | `true` if IPv6 available (auto-detected via socket.has_ipv6) | Prefer IPv6 for outgoing connections |

Copilot uses AI. Check for mistakes.

### Prometheus Metrics (Optional) ###

| Variable | Default | Description |
|----------|---------|-------------|
| `METRICS_PORT` | `None` | Prometheus exporter listen port (set to enable metrics) |
| `METRICS_EXPORT_LINKS` | `false` | Export proxy links in metrics |

### Example with Environment Variables ###

```bash
docker run -d --name mtprotoproxy \
--network host \
-e PORT=8443 \
-e TG_KEY=00000000000000000000000000000001 \
-e SECURE_ONLY=true \
-e TLS_ONLY=true \
-e TLS_DOMAIN=www.google.com \
-e AD_TAG=3c09c680b76ee91a4c25ad51f742267d \
-e TO_CLT_BUFSIZE=65536 \
-e TO_TG_BUFSIZE=65536 \
-e METRICS_PORT=9090 \
ghcr.io/xrh0905/mtprotoproxy:latest
```

## Performance ##

The proxy performance should be enough to comfortably serve about 4 000 simultaneous users on
Expand Down
69 changes: 68 additions & 1 deletion config.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ def str_to_bool(value):
return bool(value)


PORT = 443
# Listening port for the proxy
PORT = int(os.environ.get("PORT", 443))

# name -> secret (32 hex chars)
USERS = {
Expand Down Expand Up @@ -39,3 +40,69 @@ def str_to_bool(value):
SOCKS5_PORT = int(os.environ.get("SOCKS5_PORT", 0)) if os.environ.get("SOCKS5_PORT", "").isdigit() else None
SOCKS5_USER = os.environ.get("SOCKS5_USER", None)
SOCKS5_PASS = os.environ.get("SOCKS5_PASS", None)

# Buffer sizes (optional performance tuning)
# max socket buffer size to the client direction, the more the faster, but more RAM hungry
# Can be a single integer or a string like "16384,100,131072" for adaptive sizing (low,users_margin,high)
_to_clt_bufsize_env = os.environ.get("TO_CLT_BUFSIZE", None)
if _to_clt_bufsize_env:
if "," in _to_clt_bufsize_env:
TO_CLT_BUFSIZE = tuple(int(x.strip()) for x in _to_clt_bufsize_env.split(","))
else:
TO_CLT_BUFSIZE = int(_to_clt_bufsize_env)

Comment on lines +50 to +53
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing validation for tuple format. According to the code in mtprotoproxy.py:487, the tuple format requires exactly 3 elements (low, margin, high). If a user provides a string like "16384,100" or "16384,100,131072,999", this will either fail unpacking later or use incorrect values.

Consider adding validation:

if "," in _to_clt_bufsize_env:
    parts = [int(x.strip()) for x in _to_clt_bufsize_env.split(",")]
    if len(parts) != 3:
        print(f"Warning: TO_CLT_BUFSIZE tuple must have exactly 3 values (low,users_margin,high), got {len(parts)}")
    else:
        TO_CLT_BUFSIZE = tuple(parts)
Suggested change
TO_CLT_BUFSIZE = tuple(int(x.strip()) for x in _to_clt_bufsize_env.split(","))
else:
TO_CLT_BUFSIZE = int(_to_clt_bufsize_env)
parts = [int(x.strip()) for x in _to_clt_bufsize_env.split(",")]
if len(parts) != 3:
print(f"Warning: TO_CLT_BUFSIZE tuple must have exactly 3 values (low,users_margin,high), got {len(parts)}")
else:
TO_CLT_BUFSIZE = tuple(parts)
else:
TO_CLT_BUFSIZE = int(_to_clt_bufsize_env)

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +53
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error handling for invalid integer values. If TO_CLT_BUFSIZE is set to a non-numeric string (e.g., "invalid"), this will raise a ValueError. Consider wrapping the conversion in a try-except block or validating the input before conversion, similar to the existing pattern for SOCKS5_PORT at line 40 which uses .isdigit() check.

Example:

if _to_clt_bufsize_env:
    try:
        if "," in _to_clt_bufsize_env:
            TO_CLT_BUFSIZE = tuple(int(x.strip()) for x in _to_clt_bufsize_env.split(","))
        else:
            TO_CLT_BUFSIZE = int(_to_clt_bufsize_env)
    except ValueError:
        print(f"Warning: Invalid TO_CLT_BUFSIZE value: {_to_clt_bufsize_env}, using default")
Suggested change
if "," in _to_clt_bufsize_env:
TO_CLT_BUFSIZE = tuple(int(x.strip()) for x in _to_clt_bufsize_env.split(","))
else:
TO_CLT_BUFSIZE = int(_to_clt_bufsize_env)
try:
if "," in _to_clt_bufsize_env:
TO_CLT_BUFSIZE = tuple(int(x.strip()) for x in _to_clt_bufsize_env.split(","))
else:
TO_CLT_BUFSIZE = int(_to_clt_bufsize_env)
except ValueError:
print(f"Warning: Invalid TO_CLT_BUFSIZE value: {_to_clt_bufsize_env}, using default")

Copilot uses AI. Check for mistakes.
# max socket buffer size to the telegram servers direction, also can be the tuple
_to_tg_bufsize_env = os.environ.get("TO_TG_BUFSIZE", None)
if _to_tg_bufsize_env:
if "," in _to_tg_bufsize_env:
TO_TG_BUFSIZE = tuple(int(x.strip()) for x in _to_tg_bufsize_env.split(","))
else:
TO_TG_BUFSIZE = int(_to_tg_bufsize_env)

Comment on lines +57 to +61
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error handling for invalid integer values. If TO_TG_BUFSIZE is set to a non-numeric string, this will raise a ValueError. Consider adding the same error handling as suggested for TO_CLT_BUFSIZE.

Example:

if _to_tg_bufsize_env:
    try:
        if "," in _to_tg_bufsize_env:
            TO_TG_BUFSIZE = tuple(int(x.strip()) for x in _to_tg_bufsize_env.split(","))
        else:
            TO_TG_BUFSIZE = int(_to_tg_bufsize_env)
    except ValueError:
        print(f"Warning: Invalid TO_TG_BUFSIZE value: {_to_tg_bufsize_env}, using default")
Suggested change
if "," in _to_tg_bufsize_env:
TO_TG_BUFSIZE = tuple(int(x.strip()) for x in _to_tg_bufsize_env.split(","))
else:
TO_TG_BUFSIZE = int(_to_tg_bufsize_env)
try:
if "," in _to_tg_bufsize_env:
TO_TG_BUFSIZE = tuple(int(x.strip()) for x in _to_tg_bufsize_env.split(","))
else:
TO_TG_BUFSIZE = int(_to_tg_bufsize_env)
except ValueError:
print(f"Warning: Invalid TO_TG_BUFSIZE value: {_to_tg_bufsize_env}, using default")

Copilot uses AI. Check for mistakes.
Comment on lines +58 to +61
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing validation for tuple format. According to the code in mtprotoproxy.py:479, the tuple format requires exactly 3 elements (low, margin, high). If a user provides a string with wrong number of elements, this will either fail unpacking later or use incorrect values.

Consider adding the same validation as suggested for TO_CLT_BUFSIZE.

Suggested change
TO_TG_BUFSIZE = tuple(int(x.strip()) for x in _to_tg_bufsize_env.split(","))
else:
TO_TG_BUFSIZE = int(_to_tg_bufsize_env)
tg_bufsize_tuple = tuple(int(x.strip()) for x in _to_tg_bufsize_env.split(","))
if len(tg_bufsize_tuple) != 3:
raise ValueError("TO_TG_BUFSIZE must have exactly 3 comma-separated values (low, margin, high)")
TO_TG_BUFSIZE = tg_bufsize_tuple
else:
TO_TG_BUFSIZE = int(_to_tg_bufsize_env)

Copilot uses AI. Check for mistakes.
# Performance and timing settings (optional)
# Statistics print period in seconds
_stats_print_period = os.environ.get("STATS_PRINT_PERIOD", None)
if _stats_print_period:
STATS_PRINT_PERIOD = int(_stats_print_period)
Comment on lines +64 to +66
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error handling for invalid integer conversion. If STATS_PRINT_PERIOD is set to a non-numeric string, this will raise a ValueError. The same issue exists for CLIENT_KEEPALIVE (line 71), TG_CONNECT_TIMEOUT (line 76), and METRICS_PORT (line 103).

Consider adding try-except blocks or validation for all these integer conversions to prevent crashes on invalid input.

Copilot uses AI. Check for mistakes.

# Client keepalive period in seconds
_client_keepalive = os.environ.get("CLIENT_KEEPALIVE", None)
if _client_keepalive:
CLIENT_KEEPALIVE = int(_client_keepalive)
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error handling for invalid integer conversion. If CLIENT_KEEPALIVE is set to a non-numeric string, this will raise a ValueError. Consider adding error handling as mentioned in the comment for STATS_PRINT_PERIOD.

Suggested change
CLIENT_KEEPALIVE = int(_client_keepalive)
try:
CLIENT_KEEPALIVE = int(_client_keepalive)
except (ValueError, TypeError):
# Fallback to a default value, e.g., 60 seconds, and optionally log a warning
CLIENT_KEEPALIVE = 60
# print(f"Warning: Invalid CLIENT_KEEPALIVE value '{_client_keepalive}', using default 60")

Copilot uses AI. Check for mistakes.

# Telegram server connect timeout in seconds
_tg_connect_timeout = os.environ.get("TG_CONNECT_TIMEOUT", None)
if _tg_connect_timeout:
TG_CONNECT_TIMEOUT = int(_tg_connect_timeout)

Comment on lines +76 to +77
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error handling for invalid integer conversion. If TG_CONNECT_TIMEOUT is set to a non-numeric string, this will raise a ValueError. Consider adding error handling as mentioned in the comment for STATS_PRINT_PERIOD.

Suggested change
TG_CONNECT_TIMEOUT = int(_tg_connect_timeout)
try:
TG_CONNECT_TIMEOUT = int(_tg_connect_timeout)
except (ValueError, TypeError):
print(f"Warning: Invalid TG_CONNECT_TIMEOUT value '{_tg_connect_timeout}', using default (None).")
TG_CONNECT_TIMEOUT = None

Copilot uses AI. Check for mistakes.
# Network settings (optional)
# IPv4 listen address
_listen_addr_ipv4 = os.environ.get("LISTEN_ADDR_IPV4", None)
if _listen_addr_ipv4:
LISTEN_ADDR_IPV4 = _listen_addr_ipv4

# IPv6 listen address
_listen_addr_ipv6 = os.environ.get("LISTEN_ADDR_IPV6", None)
if _listen_addr_ipv6:
LISTEN_ADDR_IPV6 = _listen_addr_ipv6

# Prefer IPv6 for outgoing connections
_prefer_ipv6 = os.environ.get("PREFER_IPV6", None)
if _prefer_ipv6:
PREFER_IPV6 = str_to_bool(_prefer_ipv6)

# Enable fast mode (disables some checks for better performance)
_fast_mode = os.environ.get("FAST_MODE", None)
if _fast_mode:
FAST_MODE = str_to_bool(_fast_mode)

# Prometheus metrics settings (optional)
# Prometheus exporter listen port (None to disable)
_metrics_port = os.environ.get("METRICS_PORT", None)
if _metrics_port:
METRICS_PORT = int(_metrics_port)

Comment on lines +103 to +104
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error handling for invalid integer conversion. If METRICS_PORT is set to a non-numeric string, this will raise a ValueError. Consider adding error handling as mentioned in the comment for STATS_PRINT_PERIOD.

Suggested change
METRICS_PORT = int(_metrics_port)
try:
METRICS_PORT = int(_metrics_port)
except (ValueError, TypeError):
METRICS_PORT = None

Copilot uses AI. Check for mistakes.
# Export proxy links in metrics
_metrics_export_links = os.environ.get("METRICS_EXPORT_LINKS", None)
if _metrics_export_links:
METRICS_EXPORT_LINKS = str_to_bool(_metrics_export_links)
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ services:
restart: unless-stopped
network_mode: "host"
environment:
- PORT=443
- TG_KEY=00000000000000000000000000000001
- SECURE_ONLY=true
- TLS_ONLY=true
Expand Down