mirror of
https://github.com/ScuroNeko/mtg.git
synced 2026-09-01 14:04:02 +03:00
Add docker-compose example with HAProxy SNI router
Turnkey deployment: HAProxy on :443 peeks at the TLS SNI and routes Telegram clients to mtg while forwarding everything else (including DPI probes) to a real Caddy web server with automatic HTTPS. This is the setup recommended in BEST_PRACTICES.md, packaged so that operators can clone and run it with minimal configuration. Refs: #458
This commit is contained in:
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
# Caddy listens on 8443 behind HAProxy, which passes raw TLS through.
|
||||||
|
# Caddy terminates TLS itself and auto-obtains a Let's Encrypt certificate.
|
||||||
|
#
|
||||||
|
# If your domain's DNS already points to this server, ACME HTTP-01 challenge
|
||||||
|
# works through the HAProxy http frontend (:80 → redirect). For DNS-01
|
||||||
|
# or other ACME methods, see https://caddyserver.com/docs/automatic-https
|
||||||
|
}
|
||||||
|
|
||||||
|
{$DOMAIN}:8443 {
|
||||||
|
tls {
|
||||||
|
# Use the ACME HTTP-01 challenge on port 80.
|
||||||
|
# HAProxy forwards :80 as HTTP, so Caddy can answer the challenge
|
||||||
|
# if you add an acl exception in haproxy.cfg (see README), or use
|
||||||
|
# DNS-01 instead.
|
||||||
|
}
|
||||||
|
root * /srv
|
||||||
|
file_server
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# SNI-routing deployment for mtg
|
||||||
|
|
||||||
|
A turnkey `docker compose` setup that puts an SNI-aware TCP router
|
||||||
|
(HAProxy) in front of mtg **and** a real web server (Caddy with
|
||||||
|
automatic HTTPS).
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Modern DPI systems actively probe suspected proxies. If the server
|
||||||
|
closes the connection or returns something unexpected, the IP gets
|
||||||
|
flagged. With this setup:
|
||||||
|
|
||||||
|
- **Telegram clients** connect to port 443, HAProxy sees the configured
|
||||||
|
SNI and routes them to mtg (FakeTLS).
|
||||||
|
- **Everything else** (browsers, DPI probes, scanners) is routed to
|
||||||
|
Caddy, which responds with a real Let's Encrypt certificate and serves
|
||||||
|
genuine web content.
|
||||||
|
|
||||||
|
Because your domain's DNS points to this server, the SNI/IP match is
|
||||||
|
natural and passive DPI has nothing to flag.
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Point your domain's DNS A/AAAA record to this server's IP.
|
||||||
|
|
||||||
|
# 2. Generate an mtg secret:
|
||||||
|
docker run --rm nineseconds/mtg:2 generate-secret --hex YOUR_DOMAIN
|
||||||
|
|
||||||
|
# 3. Edit the config files:
|
||||||
|
# - mtg-config.toml → paste the secret
|
||||||
|
# - haproxy.cfg → replace "example.com" in the SNI ACL
|
||||||
|
# - .env or export → DOMAIN=your.domain
|
||||||
|
|
||||||
|
# 4. (Optional) put your site content into www/
|
||||||
|
|
||||||
|
# 5. Start:
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
# 6. Verify:
|
||||||
|
# - Open https://YOUR_DOMAIN in a browser → you should see the web page
|
||||||
|
# - Configure Telegram with the proxy link from:
|
||||||
|
docker compose exec mtg mtg access /config/config.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
## ACME (Let's Encrypt) notes
|
||||||
|
|
||||||
|
Caddy needs to answer the ACME HTTP-01 challenge on port 80. The
|
||||||
|
default `haproxy.cfg` redirects all `:80` traffic to HTTPS. If Caddy
|
||||||
|
cannot obtain a certificate, either:
|
||||||
|
|
||||||
|
1. Temporarily stop HAProxy, let Caddy bind `:80` directly for the
|
||||||
|
initial certificate, then start the full stack; or
|
||||||
|
2. Use DNS-01 validation in the Caddyfile (requires a DNS provider
|
||||||
|
plugin); or
|
||||||
|
3. Add an HAProxy ACL that passes `/.well-known/acme-challenge/`
|
||||||
|
requests to the Caddy backend instead of redirecting.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────┐
|
||||||
|
:443 ──────>│ HAProxy │
|
||||||
|
│ (TCP, SNI peek) │
|
||||||
|
└──┬───────────┬───┘
|
||||||
|
SNI match │ │ default
|
||||||
|
v v
|
||||||
|
┌─────────┐ ┌─────────┐
|
||||||
|
│ mtg │ │ Caddy │
|
||||||
|
│ :3128 │ │ :8443 │
|
||||||
|
│ FakeTLS │ │ real TLS│
|
||||||
|
└─────────┘ └─────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `docker-compose.yml` | Service definitions |
|
||||||
|
| `haproxy.cfg` | SNI routing rules — **edit the domain** |
|
||||||
|
| `mtg-config.toml` | mtg proxy config — **paste your secret** |
|
||||||
|
| `Caddyfile` | Web server config (auto-HTTPS) |
|
||||||
|
| `www/` | Static site content served by Caddy |
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# SNI-routing deployment: HAProxy (443) -> mtg + real web backend
|
||||||
|
#
|
||||||
|
# This setup puts an SNI-aware TCP router in front of mtg so that:
|
||||||
|
# - Telegram clients (FakeTLS with the correct SNI) are routed to mtg
|
||||||
|
# - All other TLS traffic (including DPI probes) reaches the real web
|
||||||
|
# server, which responds with a genuine certificate
|
||||||
|
#
|
||||||
|
# The result: active probes see a real website; passive DPI sees matching
|
||||||
|
# SNI/IP because the domain resolves to this server's IP.
|
||||||
|
#
|
||||||
|
# Quick start:
|
||||||
|
# 1. Set YOUR_DOMAIN below (and in mtg-config.toml)
|
||||||
|
# 2. docker compose up -d
|
||||||
|
# 3. mtg generate-secret YOUR_DOMAIN -> put it in mtg-config.toml
|
||||||
|
# 4. docker compose restart mtg
|
||||||
|
#
|
||||||
|
# See BEST_PRACTICES.md and the project wiki for background.
|
||||||
|
|
||||||
|
services:
|
||||||
|
haproxy:
|
||||||
|
image: haproxy:lts-alpine
|
||||||
|
ports:
|
||||||
|
- "443:443"
|
||||||
|
- "80:80"
|
||||||
|
volumes:
|
||||||
|
- ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
|
||||||
|
depends_on:
|
||||||
|
- mtg
|
||||||
|
- web
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
mtg:
|
||||||
|
image: nineseconds/mtg:2
|
||||||
|
volumes:
|
||||||
|
- ./mtg-config.toml:/config/config.toml:ro
|
||||||
|
expose:
|
||||||
|
- "3128"
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
web:
|
||||||
|
image: caddy:alpine
|
||||||
|
volumes:
|
||||||
|
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||||
|
- caddy_data:/data
|
||||||
|
- ./www:/srv:ro
|
||||||
|
expose:
|
||||||
|
- "8443"
|
||||||
|
environment:
|
||||||
|
DOMAIN: ${DOMAIN:-example.com}
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
caddy_data:
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# HAProxy SNI router — Layer 4 (TCP mode)
|
||||||
|
#
|
||||||
|
# Inspects the SNI in the TLS ClientHello and routes traffic:
|
||||||
|
# - SNI matching the mtg secret domain -> mtg (FakeTLS / MTProto)
|
||||||
|
# - Everything else -> real web backend (Caddy)
|
||||||
|
#
|
||||||
|
# Because routing happens before TLS termination, each backend sees the
|
||||||
|
# raw ClientHello and handles TLS itself. The real web backend therefore
|
||||||
|
# presents a genuine certificate to any probe or browser.
|
||||||
|
|
||||||
|
global
|
||||||
|
log stdout format raw local0 info
|
||||||
|
maxconn 4096
|
||||||
|
|
||||||
|
defaults
|
||||||
|
log global
|
||||||
|
mode tcp
|
||||||
|
option tcplog
|
||||||
|
timeout connect 5s
|
||||||
|
timeout client 60s
|
||||||
|
timeout server 60s
|
||||||
|
|
||||||
|
# --- HTTP :80 — redirect to HTTPS -------------------------------------------
|
||||||
|
|
||||||
|
frontend http
|
||||||
|
bind *:80
|
||||||
|
mode http
|
||||||
|
http-request redirect scheme https code 301
|
||||||
|
|
||||||
|
# --- TLS :443 — SNI-based routing -------------------------------------------
|
||||||
|
|
||||||
|
frontend tls
|
||||||
|
bind *:443
|
||||||
|
tcp-request inspect-delay 5s
|
||||||
|
tcp-request content accept if { req_ssl_hello_type 1 }
|
||||||
|
|
||||||
|
# Route Telegram clients to mtg.
|
||||||
|
# Replace "example.com" with the domain from your mtg secret.
|
||||||
|
use_backend mtg if { req_ssl_sni -i example.com }
|
||||||
|
|
||||||
|
default_backend web
|
||||||
|
|
||||||
|
backend mtg
|
||||||
|
server mtg mtg:3128
|
||||||
|
|
||||||
|
backend web
|
||||||
|
server web web:8443
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Minimal mtg configuration for the SNI-router setup.
|
||||||
|
#
|
||||||
|
# 1. Generate a secret: mtg generate-secret --hex example.com
|
||||||
|
# 2. Paste it below.
|
||||||
|
# 3. Replace example.com with your actual domain everywhere.
|
||||||
|
|
||||||
|
secret = "PASTE_YOUR_SECRET_HERE"
|
||||||
|
bind-to = "0.0.0.0:3128"
|
||||||
|
|
||||||
|
[defense.anti-replay]
|
||||||
|
enabled = true
|
||||||
|
max-size = "1mib"
|
||||||
|
error-rate = 0.001
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head><meta charset="utf-8"><title>Welcome</title></head>
|
||||||
|
<body><h1>It works!</h1><p>Replace this with your own content.</p></body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user