mirror of
https://github.com/telemt/telemt.git
synced 2026-04-15 01:24:09 +03:00
Compare commits
96 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9303c7854a | ||
|
|
afc07345f5 | ||
|
|
a965b38bd4 | ||
|
|
f0ebbac338 | ||
|
|
286662fc51 | ||
|
|
c5390baaf1 | ||
|
|
1cd1e96079 | ||
|
|
2b995c31b0 | ||
|
|
442320302d | ||
|
|
ac0dde567b | ||
|
|
b2fe9b78d8 | ||
|
|
f039ce1827 | ||
|
|
5f5a3e3fa0 | ||
|
|
f9e54ee739 | ||
|
|
d477d6ee29 | ||
|
|
1383dfcbb1 | ||
|
|
107a7cc758 | ||
|
|
4f3193fdaa | ||
|
|
d6be691c67 | ||
|
|
0b0be07a9c | ||
|
|
a728c727bc | ||
|
|
d23ce4a184 | ||
|
|
e48e1b141d | ||
|
|
82da541f9c | ||
|
|
6d5a1a29df | ||
|
|
026ca5cc1d | ||
|
|
b11dec7f91 | ||
|
|
edd1405562 | ||
|
|
45dd7485a9 | ||
|
|
901cf11c51 | ||
|
|
7acc76b422 | ||
|
|
227a64ef06 | ||
|
|
6748ed920e | ||
|
|
303b273c77 | ||
|
|
3bcc129b8d | ||
|
|
3ffbd294d2 | ||
|
|
ddeda8d914 | ||
|
|
17fd01a2c4 | ||
|
|
8ed43a562c | ||
|
|
fd6243b6cc | ||
|
|
44127c6f96 | ||
|
|
a0c7a9e62c | ||
|
|
d7af1cc206 | ||
|
|
f8e22970c1 | ||
|
|
792f626336 | ||
|
|
c5c98bb7fa | ||
|
|
6102280345 | ||
|
|
177f0f0325 | ||
|
|
abcce12368 | ||
|
|
31cbf31491 | ||
|
|
f479ecd1ad | ||
|
|
5c953eb4ba | ||
|
|
3771eb4ab2 | ||
|
|
b246f0ed99 | ||
|
|
07d19027f6 | ||
|
|
877d16659e | ||
|
|
1265234491 | ||
|
|
07b53785c5 | ||
|
|
1e3522652c | ||
|
|
79f4ff4eec | ||
|
|
e6c64525e3 | ||
|
|
ec231aade6 | ||
|
|
59df74e341 | ||
|
|
21a33e4d2a | ||
|
|
73bf23eb61 | ||
|
|
4a904568da | ||
|
|
a526fee728 | ||
|
|
265478b9ca | ||
|
|
038f688e75 | ||
|
|
fa3a1b4dbc | ||
|
|
e2e8b54f87 | ||
|
|
970313edcb | ||
|
|
45c66bc823 | ||
|
|
5e38a72add | ||
|
|
731619bfaa | ||
|
|
c23cdddbd2 | ||
|
|
7ba02ea3d5 | ||
|
|
1e06c32718 | ||
|
|
38c5f73d6a | ||
|
|
010f176ad4 | ||
|
|
2f616500c9 | ||
|
|
852dc11722 | ||
|
|
cda9600169 | ||
|
|
dc03c73dd6 | ||
|
|
c99f55f216 | ||
|
|
f5786d284b | ||
|
|
0281cad564 | ||
|
|
91d9cb8de0 | ||
|
|
9e74a78209 | ||
|
|
9933cdf245 | ||
|
|
b4a3ad9aad | ||
|
|
23156a840d | ||
|
|
cf9d4b2c61 | ||
|
|
63cfc067f6 | ||
|
|
5863b33b81 | ||
|
|
7ce87749c0 |
16
.github/FUNDING.yml
vendored
Normal file
16
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||||
|
patreon: # Replace with a single Patreon username
|
||||||
|
open_collective: # Replace with a single Open Collective username
|
||||||
|
ko_fi: # Replace with a single Ko-fi username
|
||||||
|
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||||
|
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||||
|
liberapay: # Replace with a single Liberapay username
|
||||||
|
issuehunt: # Replace with a single IssueHunt username
|
||||||
|
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||||
|
polar: # Replace with a single Polar username
|
||||||
|
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
|
||||||
|
thanks_dev: # Replace with a single thanks.dev username
|
||||||
|
custom:
|
||||||
|
- https://nowpayments.io/donation?api_key=2bf1afd2-abc2-49f9-a012-f1e715b37223
|
||||||
@@ -3,50 +3,39 @@
|
|||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
**Telemt exists to solve technical problems.**
|
**Telemt exists to solve technical problems.**
|
||||||
|
- Telemt is open to contributors who want to learn, improve and build meaningful systems together.
|
||||||
|
- It is a place for building, testing, reasoning, documenting, and improving systems.
|
||||||
|
- Discussions that advance this work are in scope, discussions that divert it are not.
|
||||||
|
- Technology has consequences, responsibility is inherent.
|
||||||
|
|
||||||
Telemt is open to contributors who want to learn, improve and build meaningful systems together.
|
> **Absicht bestimmt die Form**
|
||||||
|
|
||||||
It is a place for building, testing, reasoning, documenting, and improving systems.
|
> Design follows intent
|
||||||
|
|
||||||
Discussions that advance this work are in scope. Discussions that divert it are not.
|
|
||||||
|
|
||||||
Technology has consequences. Responsibility is inherent.
|
|
||||||
|
|
||||||
> **Zweck bestimmt die Form.**
|
|
||||||
|
|
||||||
> Purpose defines form.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Principles
|
## Principles
|
||||||
|
|
||||||
* **Technical over emotional**
|
* **Technical over emotional**
|
||||||
|
- Arguments are grounded in data, logs, reproducible cases, or clear reasoning.
|
||||||
Arguments are grounded in data, logs, reproducible cases, or clear reasoning.
|
|
||||||
|
|
||||||
* **Clarity over noise**
|
* **Clarity over noise**
|
||||||
|
- Communication is structured, concise, and relevant.
|
||||||
Communication is structured, concise, and relevant.
|
|
||||||
|
|
||||||
* **Openness with standards**
|
* **Openness with standards**
|
||||||
|
- Participation is open. The work remains disciplined.
|
||||||
Participation is open. The work remains disciplined.
|
|
||||||
|
|
||||||
* **Independence of judgment**
|
* **Independence of judgment**
|
||||||
|
- Claims are evaluated on technical merit, not affiliation or posture.
|
||||||
Claims are evaluated on technical merit, not affiliation or posture.
|
|
||||||
|
|
||||||
* **Responsibility over capability**
|
* **Responsibility over capability**
|
||||||
|
- Capability does not justify careless use.
|
||||||
Capability does not justify careless use.
|
|
||||||
|
|
||||||
* **Cooperation over friction**
|
* **Cooperation over friction**
|
||||||
|
- Progress depends on coordination, mutual support, and honest review.
|
||||||
Progress depends on coordination, mutual support, and honest review.
|
|
||||||
|
|
||||||
* **Good intent, rigorous method**
|
* **Good intent, rigorous method**
|
||||||
|
- Assume good intent, but require rigor.
|
||||||
Assume good intent, but require rigor.
|
|
||||||
|
|
||||||
> **Aussagen gelten nach ihrer Begründung.**
|
> **Aussagen gelten nach ihrer Begründung.**
|
||||||
|
|
||||||
@@ -68,7 +57,9 @@ Participants are expected to:
|
|||||||
|
|
||||||
Precision is learned.
|
Precision is learned.
|
||||||
|
|
||||||
New contributors are welcome. They are expected to grow into these standards. Existing contributors are expected to make that growth possible.
|
- New contributors are welcome
|
||||||
|
- They are expected to grow into these standards
|
||||||
|
- Existing contributors are expected to make that growth possible
|
||||||
|
|
||||||
> **Wer behauptet, belegt.**
|
> **Wer behauptet, belegt.**
|
||||||
|
|
||||||
@@ -112,7 +103,7 @@ Security is both technical and behavioral.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. Openness
|
## Openness
|
||||||
|
|
||||||
Telemt is open to contributors of different backgrounds, experience levels, and working styles.
|
Telemt is open to contributors of different backgrounds, experience levels, and working styles.
|
||||||
|
|
||||||
@@ -148,10 +139,9 @@ Judgment should be exercised with restraint, consistency, and institutional resp
|
|||||||
|
|
||||||
All decisions are expected to serve the durability, clarity, and integrity of Telemt.
|
All decisions are expected to serve the durability, clarity, and integrity of Telemt.
|
||||||
|
|
||||||
> **Ordnung ist Voraussetzung der Funktion.**
|
> **Klarheit vor Zustimmung - Bestand vor Beifall**
|
||||||
|
|
||||||
> Order is the precondition of function.
|
|
||||||
|
|
||||||
|
> Clarity above approval - substantiality before success
|
||||||
---
|
---
|
||||||
|
|
||||||
## Enforcement
|
## Enforcement
|
||||||
@@ -171,42 +161,41 @@ Actions are taken to maintain function, continuity, and signal quality.
|
|||||||
|
|
||||||
## Final
|
## Final
|
||||||
|
|
||||||
Telemt is built on discipline, structure, and shared intent.
|
**Telemt is built on discipline, structure, and shared intent**
|
||||||
- Signal over noise.
|
- Signal over noise
|
||||||
- Facts over opinion.
|
- Facts over opinion
|
||||||
- Systems over rhetoric.
|
- Systems over rhetoric
|
||||||
|
- Work is collective
|
||||||
|
- Outcomes are shared
|
||||||
|
- Responsibility is distributed
|
||||||
|
- Precision is learned
|
||||||
|
- Rigor is expected
|
||||||
|
- Help is part of the work
|
||||||
|
|
||||||
- Work is collective.
|
> **Ordnung ist Voraussetzung der Freiheit**
|
||||||
- Outcomes are shared.
|
|
||||||
- Responsibility is distributed.
|
|
||||||
|
|
||||||
- Precision is learned.
|
- If you contribute — contribute with care
|
||||||
- Rigor is expected.
|
- If you speak — speak with substance
|
||||||
- Help is part of the work.
|
- If you engage — engage constructively
|
||||||
|
|
||||||
> **Ordnung ist Voraussetzung der Freiheit.**
|
|
||||||
|
|
||||||
- If you contribute — contribute with care.
|
|
||||||
- If you speak — speak with substance.
|
|
||||||
- If you engage — engage constructively.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## After All
|
## After All
|
||||||
|
|
||||||
Systems outlive intentions.
|
Systems outlive intentions
|
||||||
- What is built will be used.
|
- What is built will be used
|
||||||
- What is released will propagate.
|
- What is released will propagate
|
||||||
- What is maintained will define the future state.
|
- What is maintained will define the future state
|
||||||
|
|
||||||
There is no neutral infrastructure, only infrastructure shaped well or poorly.
|
There is no neutral infrastructure, only infrastructure shaped well or poorly
|
||||||
|
|
||||||
> **Jedes System trägt Verantwortung.**
|
> **Ordnung → Umsetzung → Ergebnis**
|
||||||
|
|
||||||
> Every system carries responsibility.
|
> Order → Implementation → Result
|
||||||
|
|
||||||
- Stability requires discipline.
|
- Stability requires discipline
|
||||||
- Freedom requires structure.
|
- Freedom requires structure
|
||||||
- Trust requires honesty.
|
- Trust requires honesty
|
||||||
|
|
||||||
|
In the end: the system reflects its contributors
|
||||||
|
|
||||||
In the end: the system reflects its contributors.
|
|
||||||
|
|||||||
262
README.md
262
README.md
@@ -1,189 +1,57 @@
|
|||||||
# Telemt - MTProxy on Rust + Tokio
|
# Telemt - MTProxy on Rust + Tokio
|
||||||
|
|
||||||
|
   [](https://t.me/telemtrs)
|
||||||
|
|
||||||
|
[🇷🇺 README на русском](https://github.com/telemt/telemt/blob/main/README.ru.md)
|
||||||
|
|
||||||
***Löst Probleme, bevor andere überhaupt wissen, dass sie existieren*** / ***It solves problems before others even realize they exist***
|
***Löst Probleme, bevor andere überhaupt wissen, dass sie existieren*** / ***It solves problems before others even realize they exist***
|
||||||
|
|
||||||
### [**Telemt Chat in Telegram**](https://t.me/telemtrs)
|
> [!NOTE]
|
||||||
#### Fixed TLS ClientHello is now available in Telegram Desktop starting from version 6.7.2: to work with EE-MTProxy, please update your client;
|
>
|
||||||
#### Fixed TLS ClientHello for Telegram Android Client is available in [our chat](https://t.me/telemtrs/30234/36441); official releases for Android and iOS are "work in progress";
|
> Fixed TLS ClientHello is now available in official clients for Desktop / Android / iOS
|
||||||
|
>
|
||||||
|
> To work with EE-MTProxy, please update your client!
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://t.me/telemtrs">
|
||||||
|
<img src="/docs/assets/telegram_button.svg" width="150"/>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
**Telemt** is a fast, secure, and feature-rich server written in Rust: it fully implements the official Telegram proxy algo and adds many production-ready improvements such as:
|
**Telemt** is a fast, secure, and feature-rich server written in Rust: it fully implements the official Telegram proxy algo and adds many production-ready improvements
|
||||||
- [ME Pool + Reader/Writer + Registry + Refill + Adaptive Floor + Trio-State + Generation Lifecycle](https://github.com/telemt/telemt/blob/main/docs/model/MODEL.en.md)
|
|
||||||
- [Full-covered API w/ management](https://github.com/telemt/telemt/blob/main/docs/API.md)
|
|
||||||
- Anti-Replay on Sliding Window
|
|
||||||
- Prometheus-format Metrics
|
|
||||||
- TLS-Fronting and TCP-Splicing for masking from "prying" eyes
|
|
||||||
|
|
||||||
⚓ Our implementation of **TLS-fronting** is one of the most deeply debugged, focused, advanced and *almost* **"behaviorally consistent to real"**: we are confident we have it right - [see evidence on our validation and traces](#recognizability-for-dpi-and-crawler)
|
### One-command Install and Update
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/telemt/telemt/main/install.sh | sh
|
||||||
|
```
|
||||||
|
- [Quick Start Guide](docs/Quick_start/QUICK_START_GUIDE.en.md)
|
||||||
|
- [Инструкция по быстрому запуску](docs/Quick_start/QUICK_START_GUIDE.ru.md)
|
||||||
|
|
||||||
⚓ Our ***Middle-End Pool*** is fastest by design in standard scenarios, compared to other implementations of connecting to the Middle-End Proxy: non dramatically, but usual
|
## Features
|
||||||
|
Our implementation of **TLS-fronting** is one of the most deeply debugged, focused, advanced and *almost* **"behaviorally consistent to real"**: we are confident we have it right - [see evidence on our validation and traces](docs/FAQ.en.md#recognizability-for-dpi-and-crawler)
|
||||||
|
|
||||||
|
Our ***Middle-End Pool*** is fastest by design in standard scenarios, compared to other implementations of connecting to the Middle-End Proxy: non dramatically, but usual
|
||||||
|
|
||||||
- Full support for all official MTProto proxy modes:
|
- Full support for all official MTProto proxy modes:
|
||||||
- Classic
|
- Classic;
|
||||||
- Secure - with `dd` prefix
|
- Secure - with `dd` prefix;
|
||||||
- Fake TLS - with `ee` prefix + SNI fronting
|
- Fake TLS - with `ee` prefix + SNI fronting;
|
||||||
- Replay attack protection
|
- Replay attack protection;
|
||||||
- Optional traffic masking: forward unrecognized connections to a real web server, e.g. GitHub 🤪
|
- Optional traffic masking: forward unrecognized connections to a real web server, e.g. GitHub 🤪;
|
||||||
- Configurable keepalives + timeouts + IPv6 and "Fast Mode"
|
- Configurable keepalives + timeouts + IPv6 and "Fast Mode";
|
||||||
- Graceful shutdown on Ctrl+C
|
- Graceful shutdown on Ctrl+C;
|
||||||
- Extensive logging via `trace` and `debug` with `RUST_LOG` method
|
- Extensive logging via `trace` and `debug` with `RUST_LOG` method.
|
||||||
|
|
||||||
# GOTO
|
|
||||||
- [Quick Start Guide](#quick-start-guide)
|
|
||||||
- [FAQ](#faq)
|
|
||||||
- [Recognizability for DPI and crawler](#recognizability-for-dpi-and-crawler)
|
|
||||||
- [Client WITH secret-key accesses the MTProxy resource:](#client-with-secret-key-accesses-the-mtproxy-resource)
|
|
||||||
- [Client WITHOUT secret-key gets transparent access to the specified resource:](#client-without-secret-key-gets-transparent-access-to-the-specified-resource)
|
|
||||||
- [Telegram Calls via MTProxy](#telegram-calls-via-mtproxy)
|
|
||||||
- [How does DPI see MTProxy TLS?](#how-does-dpi-see-mtproxy-tls)
|
|
||||||
- [Whitelist on IP](#whitelist-on-ip)
|
|
||||||
- [Too many open files](#too-many-open-files)
|
|
||||||
- [Build](#build)
|
|
||||||
- [Why Rust?](#why-rust)
|
|
||||||
- [Issues](#issues)
|
|
||||||
- [Roadmap](#roadmap)
|
|
||||||
|
|
||||||
|
|
||||||
## Quick Start Guide
|
|
||||||
- [Quick Start Guide RU](docs/QUICK_START_GUIDE.ru.md)
|
|
||||||
- [Quick Start Guide EN](docs/QUICK_START_GUIDE.en.md)
|
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
- [FAQ RU](docs/FAQ.ru.md)
|
- [FAQ RU](docs/FAQ.ru.md)
|
||||||
- [FAQ EN](docs/FAQ.en.md)
|
- [FAQ EN](docs/FAQ.en.md)
|
||||||
|
|
||||||
### Recognizability for DPI and crawler
|
# Learn more about Telemt
|
||||||
|
- [Our Architecture](docs/Architecture)
|
||||||
On April 1, 2026, we became aware of a method for detecting MTProxy Fake-TLS,
|
- [All Config Options](docs/Config_params)
|
||||||
based on the ECH extension and the ordering of cipher suites,
|
- [How to build your own Telemt?](#build)
|
||||||
as well as an overall unique JA3/JA4 fingerprint
|
- [Running on BSD](docs/Quick_start/OPENBSD_QUICK_START_GUIDE.en.md)
|
||||||
that does not occur in modern browsers:
|
- [Why Rust?](#why-rust)
|
||||||
we have already submitted initial changes to the Telegram Desktop developers and are working on updates for other clients.
|
|
||||||
|
|
||||||
- We consider this a breakthrough aspect, which has no stable analogues today
|
|
||||||
- Based on this: if `telemt` configured correctly, **TLS mode is completely identical to real-life handshake + communication** with a specified host
|
|
||||||
- Here is our evidence:
|
|
||||||
- 212.220.88.77 - "dummy" host, running `telemt`
|
|
||||||
- `petrovich.ru` - `tls` + `masking` host, in HEX: `706574726f766963682e7275`
|
|
||||||
- **No MITM + No Fake Certificates/Crypto** = pure transparent *TCP Splice* to "best" upstream: MTProxy or tls/mask-host:
|
|
||||||
- DPI see legitimate HTTPS to `tls_host`, including *valid chain-of-trust* and entropy
|
|
||||||
- Crawlers completely satisfied receiving responses from `mask_host`
|
|
||||||
#### Client WITH secret-key accesses the MTProxy resource:
|
|
||||||
|
|
||||||
<img width="360" height="439" alt="telemt" src="https://github.com/user-attachments/assets/39352afb-4a11-4ecc-9d91-9e8cfb20607d" />
|
|
||||||
|
|
||||||
#### Client WITHOUT secret-key gets transparent access to the specified resource:
|
|
||||||
- with trusted certificate
|
|
||||||
- with original handshake
|
|
||||||
- with full request-response way
|
|
||||||
- with low-latency overhead
|
|
||||||
```bash
|
|
||||||
root@debian:~/telemt# curl -v -I --resolve petrovich.ru:443:212.220.88.77 https://petrovich.ru/
|
|
||||||
* Added petrovich.ru:443:212.220.88.77 to DNS cache
|
|
||||||
* Hostname petrovich.ru was found in DNS cache
|
|
||||||
* Trying 212.220.88.77:443...
|
|
||||||
* Connected to petrovich.ru (212.220.88.77) port 443 (#0)
|
|
||||||
* ALPN: offers h2,http/1.1
|
|
||||||
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
|
|
||||||
* CAfile: /etc/ssl/certs/ca-certificates.crt
|
|
||||||
* CApath: /etc/ssl/certs
|
|
||||||
* TLSv1.3 (IN), TLS handshake, Server hello (2):
|
|
||||||
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
|
|
||||||
* TLSv1.3 (IN), TLS handshake, Certificate (11):
|
|
||||||
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
|
|
||||||
* TLSv1.3 (IN), TLS handshake, Finished (20):
|
|
||||||
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
|
|
||||||
* TLSv1.3 (OUT), TLS handshake, Finished (20):
|
|
||||||
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
|
|
||||||
* ALPN: server did not agree on a protocol. Uses default.
|
|
||||||
* Server certificate:
|
|
||||||
* subject: C=RU; ST=Saint Petersburg; L=Saint Petersburg; O=STD Petrovich; CN=*.petrovich.ru
|
|
||||||
* start date: Jan 28 11:21:01 2025 GMT
|
|
||||||
* expire date: Mar 1 11:21:00 2026 GMT
|
|
||||||
* subjectAltName: host "petrovich.ru" matched cert's "petrovich.ru"
|
|
||||||
* issuer: C=BE; O=GlobalSign nv-sa; CN=GlobalSign RSA OV SSL CA 2018
|
|
||||||
* SSL certificate verify ok.
|
|
||||||
* using HTTP/1.x
|
|
||||||
> HEAD / HTTP/1.1
|
|
||||||
> Host: petrovich.ru
|
|
||||||
> User-Agent: curl/7.88.1
|
|
||||||
> Accept: */*
|
|
||||||
>
|
|
||||||
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
|
|
||||||
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
|
|
||||||
* old SSL session ID is stale, removing
|
|
||||||
< HTTP/1.1 200 OK
|
|
||||||
HTTP/1.1 200 OK
|
|
||||||
< Server: Variti/0.9.3a
|
|
||||||
Server: Variti/0.9.3a
|
|
||||||
< Date: Thu, 01 Jan 2026 00:0000 GMT
|
|
||||||
Date: Thu, 01 Jan 2026 00:0000 GMT
|
|
||||||
< Access-Control-Allow-Origin: *
|
|
||||||
Access-Control-Allow-Origin: *
|
|
||||||
< Content-Type: text/html
|
|
||||||
Content-Type: text/html
|
|
||||||
< Cache-Control: no-store
|
|
||||||
Cache-Control: no-store
|
|
||||||
< Expires: Thu, 01 Jan 2026 00:0000 GMT
|
|
||||||
Expires: Thu, 01 Jan 2026 00:0000 GMT
|
|
||||||
< Pragma: no-cache
|
|
||||||
Pragma: no-cache
|
|
||||||
< Set-Cookie: ipp_uid=XXXXX/XXXXX/XXXXX==; Expires=Tue, 31 Dec 2040 23:59:59 GMT; Domain=.petrovich.ru; Path=/
|
|
||||||
Set-Cookie: ipp_uid=XXXXX/XXXXX/XXXXX==; Expires=Tue, 31 Dec 2040 23:59:59 GMT; Domain=.petrovich.ru; Path=/
|
|
||||||
< Content-Type: text/html
|
|
||||||
Content-Type: text/html
|
|
||||||
< Content-Length: 31253
|
|
||||||
Content-Length: 31253
|
|
||||||
< Connection: keep-alive
|
|
||||||
Connection: keep-alive
|
|
||||||
< Keep-Alive: timeout=60
|
|
||||||
Keep-Alive: timeout=60
|
|
||||||
|
|
||||||
<
|
|
||||||
* Connection #0 to host petrovich.ru left intact
|
|
||||||
|
|
||||||
```
|
|
||||||
- We challenged ourselves, we kept trying and we didn't only *beat the air*: now, we have something to show you
|
|
||||||
- Do not just take our word for it? - This is great and we respect that: you can build your own `telemt` or download a build and check it right now
|
|
||||||
### Telegram Calls via MTProxy
|
|
||||||
- Telegram architecture **does NOT allow calls via MTProxy**, but only via SOCKS5, which cannot be obfuscated
|
|
||||||
### How does DPI see MTProxy TLS?
|
|
||||||
- DPI sees MTProxy in Fake TLS (ee) mode as TLS 1.3
|
|
||||||
- the SNI you specify sends both the client and the server;
|
|
||||||
- ALPN is similar to HTTP 1.1/2;
|
|
||||||
- high entropy, which is normal for AES-encrypted traffic;
|
|
||||||
### Whitelist on IP
|
|
||||||
- MTProxy cannot work when there is:
|
|
||||||
- no IP connectivity to the target host: Russian Whitelist on Mobile Networks - "Белый список"
|
|
||||||
- OR all TCP traffic is blocked
|
|
||||||
- OR high entropy/encrypted traffic is blocked: content filters at universities and critical infrastructure
|
|
||||||
- OR all TLS traffic is blocked
|
|
||||||
- OR specified port is blocked: use 443 to make it "like real"
|
|
||||||
- OR provided SNI is blocked: use "officially approved"/innocuous name
|
|
||||||
- like most protocols on the Internet;
|
|
||||||
- these situations are observed:
|
|
||||||
- in China behind the Great Firewall
|
|
||||||
- in Russia on mobile networks, less in wired networks
|
|
||||||
- in Iran during "activity"
|
|
||||||
### Too many open files
|
|
||||||
- On a fresh Linux install the default open file limit is low; under load `telemt` may fail with `Accept error: Too many open files`
|
|
||||||
- **Systemd**: add `LimitNOFILE=65536` to the `[Service]` section (already included in the example above)
|
|
||||||
- **Docker**: add `--ulimit nofile=65536:65536` to your `docker run` command, or in `docker-compose.yml`:
|
|
||||||
```yaml
|
|
||||||
ulimits:
|
|
||||||
nofile:
|
|
||||||
soft: 65536
|
|
||||||
hard: 65536
|
|
||||||
```
|
|
||||||
- **System-wide** (optional): add to `/etc/security/limits.conf`:
|
|
||||||
```
|
|
||||||
* soft nofile 1048576
|
|
||||||
* hard nofile 1048576
|
|
||||||
root soft nofile 1048576
|
|
||||||
root hard nofile 1048576
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
```bash
|
```bash
|
||||||
@@ -194,9 +62,8 @@ cd telemt
|
|||||||
# Starting Release Build
|
# Starting Release Build
|
||||||
cargo build --release
|
cargo build --release
|
||||||
|
|
||||||
# Low-RAM devices (1 GB, e.g. NanoPi Neo3 / Raspberry Pi Zero 2):
|
# Current release profile uses lto = "fat" for maximum optimization (see Cargo.toml).
|
||||||
# release profile uses lto = "thin" to reduce peak linker memory.
|
# On low-RAM systems (~1 GB) you can override it to "thin".
|
||||||
# If your custom toolchain overrides profiles, avoid enabling fat LTO.
|
|
||||||
|
|
||||||
# Move to /bin
|
# Move to /bin
|
||||||
mv ./target/release/telemt /bin
|
mv ./target/release/telemt /bin
|
||||||
@@ -206,12 +73,6 @@ chmod +x /bin/telemt
|
|||||||
telemt config.toml
|
telemt config.toml
|
||||||
```
|
```
|
||||||
|
|
||||||
### OpenBSD
|
|
||||||
- Build and service setup guide: [OpenBSD Guide (EN)](docs/OPENBSD.en.md)
|
|
||||||
- Example rc.d script: [contrib/openbsd/telemt.rcd](contrib/openbsd/telemt.rcd)
|
|
||||||
- Status: OpenBSD sandbox hardening with `pledge(2)` and `unveil(2)` is not implemented yet.
|
|
||||||
|
|
||||||
|
|
||||||
## Why Rust?
|
## Why Rust?
|
||||||
- Long-running reliability and idempotent behavior
|
- Long-running reliability and idempotent behavior
|
||||||
- Rust's deterministic resource management - RAII
|
- Rust's deterministic resource management - RAII
|
||||||
@@ -219,23 +80,26 @@ telemt config.toml
|
|||||||
- Memory safety and reduced attack surface
|
- Memory safety and reduced attack surface
|
||||||
- Tokio's asynchronous architecture
|
- Tokio's asynchronous architecture
|
||||||
|
|
||||||
## Issues
|
## Support Telemt
|
||||||
- ✅ [SOCKS5 as Upstream](https://github.com/telemt/telemt/issues/1) -> added Upstream Management
|
|
||||||
- ✅ [iOS - Media Upload Hanging-in-Loop](https://github.com/telemt/telemt/issues/2)
|
|
||||||
|
|
||||||
## Roadmap
|
Telemt is free, open-source, and built in personal time.
|
||||||
- Public IP in links
|
If it helps you — consider supporting continued development.
|
||||||
- Config Reload-on-fly
|
|
||||||
- Bind to device or IP for outbound/inbound connections
|
Any cryptocurrency (BTC, ETH, USDT, 350+ coins):
|
||||||
- Adtag Support per SNI / Secret
|
|
||||||
- Fail-fast on start + Fail-soft on runtime (only WARN/ERROR)
|
<p align="center">
|
||||||
- Zero-copy, minimal allocs on hotpath
|
<a href="https://nowpayments.io/donation?api_key=2bf1afd2-abc2-49f9-a012-f1e715b37223" target="_blank" rel="noreferrer noopener">
|
||||||
- DC Healthchecks + global fallback
|
<img src="https://nowpayments.io/images/embeds/donation-button-white.svg" alt="Cryptocurrency & Bitcoin donation button by NOWPayments" height="80">
|
||||||
- No global mutable state
|
</a>
|
||||||
- Client isolation + Fair Bandwidth
|
</p>
|
||||||
- Backpressure-aware IO
|
|
||||||
- "Secret Policy" - SNI / Secret Routing :D
|
Monero (XMR) directly:
|
||||||
- Multi-upstream Balancer and Failover
|
|
||||||
- Strict FSM per handshake
|
```
|
||||||
- Session-based Antireplay with Sliding window, non-broking reconnects
|
8Bk4tZEYPQWSypeD2hrUXG2rKbAKF16GqEN942ZdAP5cFdSqW6h4DwkP5cJMAdszzuPeHeHZPTyjWWFwzeFdjuci3ktfMoB
|
||||||
- Web Control: statistic, state of health, latency, client experience...
|
```
|
||||||
|
|
||||||
|
All donations go toward infrastructure, development, and research.
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|||||||
109
README.ru.md
Normal file
109
README.ru.md
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
# Telemt — MTProxy на Rust + Tokio
|
||||||
|
|
||||||
|
   [](https://t.me/telemtrs)
|
||||||
|
|
||||||
|
***Решает проблемы раньше, чем другие узнают об их существовании***
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
>
|
||||||
|
> Исправленный TLS ClientHello доступен в Telegram для настольных ПК, Android и iOS.
|
||||||
|
>
|
||||||
|
> Пожалуйста, обновите клиентское приложение для работы с EE-MTProxy.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://t.me/telemtrs">
|
||||||
|
<img src="/docs/assets/telegram_button.svg" width="150"/>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
**Telemt** — это быстрый, безопасный и функциональный сервер, написанный на Rust. Он полностью реализует официальный алгоритм прокси Telegram и добавляет множество улучшений для продакшена:
|
||||||
|
|
||||||
|
## Установка и обновление одной командой
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/telemt/telemt/main/install.sh | sh
|
||||||
|
```
|
||||||
|
|
||||||
|
- [Инструкция по быстрому запуску](docs/Quick_start/QUICK_START_GUIDE.ru.md)
|
||||||
|
- [Quick Start Guide](docs/Quick_start/QUICK_START_GUIDE.en.md)
|
||||||
|
|
||||||
|
Реализация **TLS-fronting** максимально приближена к поведению реального HTTPS-трафика (подробнее - [FAQ](docs/FAQ.ru.md#распознаваемость-для-dpi-и-сканеров)).
|
||||||
|
|
||||||
|
***Middle-End Pool*** оптимизирован для высокой производительности.
|
||||||
|
|
||||||
|
- Поддержка всех режимов MTProto proxy:
|
||||||
|
- Classic;
|
||||||
|
- Secure (префикс `dd`);
|
||||||
|
- Fake TLS (префикс `ee` + SNI fronting);
|
||||||
|
- Защита от replay-атак;
|
||||||
|
- Маскировка трафика (перенаправление неизвестных подключений на реальные сайты);
|
||||||
|
- Настраиваемые keepalive, таймауты, IPv6 и «быстрый режим»;
|
||||||
|
- Корректное завершение работы (Ctrl+C);
|
||||||
|
- Подробное логирование через `trace` и `debug`.
|
||||||
|
|
||||||
|
# Подробнее о Telemt
|
||||||
|
- [FAQ](#faq)
|
||||||
|
- [Архитектура](docs/Architecture)
|
||||||
|
- [Параметры конфигурационного файла](docs/Config_params)
|
||||||
|
- [Сборка](#build)
|
||||||
|
- [Установка на BSD](#%D1%83%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0-%D0%BD%D0%B0-bsd)
|
||||||
|
- [Почему Rust?](#why-rust)
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
- [FAQ RU](docs/FAQ.ru.md)
|
||||||
|
- [FAQ EN](docs/FAQ.en.md)
|
||||||
|
|
||||||
|
## Сборка
|
||||||
|
```bash
|
||||||
|
# Клонируйте репозиторий
|
||||||
|
git clone https://github.com/telemt/telemt
|
||||||
|
# Смените каталог на telemt
|
||||||
|
cd telemt
|
||||||
|
# Начните процесс сборки
|
||||||
|
cargo build --release
|
||||||
|
|
||||||
|
# В текущем release-профиле используется lto = "fat" для максимальной оптимизации (см. Cargo.toml).
|
||||||
|
# На системах с малым объёмом RAM (~1 ГБ) можно переопределить это значение на "thin".
|
||||||
|
|
||||||
|
# Перейдите в каталог /bin
|
||||||
|
mv ./target/release/telemt /bin
|
||||||
|
# Сделайте файл исполняемым
|
||||||
|
chmod +x /bin/telemt
|
||||||
|
# Запустите!
|
||||||
|
telemt config.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Установка на BSD
|
||||||
|
- Руководство по сборке и настройке на английском языке [OpenBSD Guide (EN)](docs/Quick_start/OPENBSD_QUICK_START_GUIDE.en.md);
|
||||||
|
- Пример rc.d скрипта: [contrib/openbsd/telemt.rcd](contrib/openbsd/telemt.rcd);
|
||||||
|
- Поддержка sandbox с `pledge(2)` и `unveil(2)` пока не реализована.
|
||||||
|
|
||||||
|
## Почему Rust?
|
||||||
|
- Надёжность для долгоживущих процессов;
|
||||||
|
- Детерминированное управление ресурсами (RAII);
|
||||||
|
- Отсутствие сборщика мусора;
|
||||||
|
- Безопасность памяти;
|
||||||
|
- Асинхронная архитектура Tokio.
|
||||||
|
|
||||||
|
## Поддержать Telemt
|
||||||
|
|
||||||
|
Telemt — это бесплатное программное обеспечение с открытым исходным кодом, разработанное в свободное время.
|
||||||
|
Если оно оказалось вам полезным, вы можете поддержать дальнейшую разработку.
|
||||||
|
|
||||||
|
Принимаемые криптовалюты (BTC, ETH, USDT, 350+ и другие):
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://nowpayments.io/donation?api_key=2bf1afd2-abc2-49f9-a012-f1e715b37223" target="_blank" rel="noreferrer noopener">
|
||||||
|
<img src="https://nowpayments.io/images/embeds/donation-button-white.svg" alt="Cryptocurrency & Bitcoin donation button by NOWPayments" height="80">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
Monero (XMR) напрямую:
|
||||||
|
|
||||||
|
```
|
||||||
|
8Bk4tZEYPQWSypeD2hrUXG2rKbAKF16GqEN942ZdAP5cFdSqW6h4DwkP5cJMAdszzuPeHeHZPTyjWWFwzeFdjuci3ktfMoB
|
||||||
|
```
|
||||||
|
|
||||||
|
Все пожертвования пойдут на инфраструктуру, разработку и исследования.
|
||||||
|
|
||||||
|

|
||||||
13
config.toml
13
config.toml
@@ -32,13 +32,13 @@ show = "*"
|
|||||||
port = 443
|
port = 443
|
||||||
# proxy_protocol = false # Enable if behind HAProxy/nginx with PROXY protocol
|
# proxy_protocol = false # Enable if behind HAProxy/nginx with PROXY protocol
|
||||||
# metrics_port = 9090
|
# metrics_port = 9090
|
||||||
# metrics_listen = "0.0.0.0:9090" # Listen address for metrics (overrides metrics_port)
|
# metrics_listen = "127.0.0.1:9090" # Listen address for metrics (overrides metrics_port)
|
||||||
# metrics_whitelist = ["127.0.0.1", "::1", "0.0.0.0/0"]
|
# metrics_whitelist = ["127.0.0.1/32", "::1/128"]
|
||||||
|
|
||||||
[server.api]
|
[server.api]
|
||||||
enabled = true
|
enabled = true
|
||||||
listen = "0.0.0.0:9091"
|
listen = "127.0.0.1:9091"
|
||||||
whitelist = ["127.0.0.0/8"]
|
whitelist = ["127.0.0.1/32", "::1/128"]
|
||||||
minimal_runtime_enabled = false
|
minimal_runtime_enabled = false
|
||||||
minimal_runtime_cache_ttl_ms = 1000
|
minimal_runtime_cache_ttl_ms = 1000
|
||||||
|
|
||||||
@@ -48,9 +48,12 @@ ip = "0.0.0.0"
|
|||||||
|
|
||||||
# === Anti-Censorship & Masking ===
|
# === Anti-Censorship & Masking ===
|
||||||
[censorship]
|
[censorship]
|
||||||
|
# Fake-TLS / SNI masking domain used in generated ee-links.
|
||||||
|
# Changing tls_domain invalidates previously generated TLS links.
|
||||||
tls_domain = "petrovich.ru"
|
tls_domain = "petrovich.ru"
|
||||||
|
|
||||||
mask = true
|
mask = true
|
||||||
tls_emulation = true # Fetch real cert lengths and emulate TLS records
|
tls_emulation = true # Fetch real cert lengths and emulate TLS records
|
||||||
tls_front_dir = "tlsfront" # Cache directory for TLS emulation
|
tls_front_dir = "tlsfront" # Cache directory for TLS emulation
|
||||||
|
|
||||||
[access.users]
|
[access.users]
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ services:
|
|||||||
- "127.0.0.1:9090:9090"
|
- "127.0.0.1:9090:9090"
|
||||||
- "127.0.0.1:9091:9091"
|
- "127.0.0.1:9091:9091"
|
||||||
# Allow caching 'proxy-secret' in read-only container
|
# Allow caching 'proxy-secret' in read-only container
|
||||||
working_dir: /run/telemt
|
working_dir: /etc/telemt
|
||||||
volumes:
|
volumes:
|
||||||
- ./config.toml:/run/telemt/config.toml:ro
|
- ./config.toml:/etc/telemt/config.toml:ro
|
||||||
tmpfs:
|
tmpfs:
|
||||||
- /run/telemt:rw,mode=1777,size=1m
|
- /etc/telemt:rw,mode=1777,size=4m
|
||||||
environment:
|
environment:
|
||||||
- RUST_LOG=info
|
- RUST_LOG=info
|
||||||
# Uncomment this line if you want to use host network for IPv6, but bridge is default and usually better
|
# Uncomment this line if you want to use host network for IPv6, but bridge is default and usually better
|
||||||
@@ -21,11 +21,12 @@ services:
|
|||||||
cap_drop:
|
cap_drop:
|
||||||
- ALL
|
- ALL
|
||||||
cap_add:
|
cap_add:
|
||||||
- NET_BIND_SERVICE # allow binding to port 443
|
- NET_BIND_SERVICE
|
||||||
|
- NET_ADMIN
|
||||||
read_only: true
|
read_only: true
|
||||||
security_opt:
|
security_opt:
|
||||||
- no-new-privileges:true
|
- no-new-privileges:true
|
||||||
ulimits:
|
ulimits:
|
||||||
nofile:
|
nofile:
|
||||||
soft: 65536
|
soft: 65536
|
||||||
hard: 65536
|
hard: 262144
|
||||||
|
|||||||
141
docs/Advanced_settings/HIGH_LOAD.en.md
Normal file
141
docs/Advanced_settings/HIGH_LOAD.en.md
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
# High-Load Configuration & Tuning Guide
|
||||||
|
When deploying Telemt under high-traffic load (tens or hundreds of thousands of concurrent connections), the standard OS network stack limits can lead to packet drops, high CPU context switching, and connection failures. This guide covers Linux kernel tuning, hardware configuration, and architecture optimizations required to prepare the server for high-load scenarios.
|
||||||
|
|
||||||
|
---
|
||||||
|
## 1. System Limits & File Descriptors
|
||||||
|
Every TCP connection requires a file descriptor. At 100k connections, standard Linux limits (often 1024 or 65535) will be exhausted immediately.
|
||||||
|
### System-Wide Limits (`sysctl`)
|
||||||
|
Increase the global file descriptor limit in `/etc/sysctl.conf`:
|
||||||
|
```ini
|
||||||
|
fs.file-max = 2097152
|
||||||
|
fs.nr_open = 2097152
|
||||||
|
```
|
||||||
|
### User-Level Limits (`limits.conf`)
|
||||||
|
Edit `/etc/security/limits.conf` to allow the telemt (or proxy) user to allocate them:
|
||||||
|
```conf
|
||||||
|
* soft nofile 1048576
|
||||||
|
* hard nofile 1048576
|
||||||
|
root soft nofile 1048576
|
||||||
|
root hard nofile 1048576
|
||||||
|
```
|
||||||
|
### Systemd / Docker Overrides
|
||||||
|
If using **Systemd**, add to your `telemt.service`:
|
||||||
|
```ini
|
||||||
|
[Service]
|
||||||
|
LimitNOFILE=1048576
|
||||||
|
LimitNPROC=65535
|
||||||
|
TasksMax=infinity
|
||||||
|
```
|
||||||
|
If using **Docker**, configure `ulimits` in `docker-compose.yaml`:
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
telemt:
|
||||||
|
ulimits:
|
||||||
|
nofile:
|
||||||
|
soft: 1048576
|
||||||
|
hard: 1048576
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
## 2. Kernel Network Stack Tuning (`sysctl`)
|
||||||
|
Create a dedicated file `/etc/sysctl.d/99-telemt-highload.conf` and apply it via `sysctl -p /etc/sysctl.d/99-telemt-highload.conf`.
|
||||||
|
### 2.1 Connection Queues & SYN Flood Protection
|
||||||
|
Increase the size of accept queues to absorb sudden connection spikes (bursts) and mitigate SYN floods:
|
||||||
|
```ini
|
||||||
|
net.core.somaxconn = 65535
|
||||||
|
net.core.netdev_max_backlog = 65535
|
||||||
|
net.ipv4.tcp_max_syn_backlog = 65535
|
||||||
|
net.ipv4.tcp_syncookies = 1
|
||||||
|
```
|
||||||
|
### 2.2 Port Exhaustion & TIME-WAIT Sockets
|
||||||
|
High churn rates lead to ephemeral port exhaustion. Expand the range and rapidly recycle closed sockets:
|
||||||
|
```ini
|
||||||
|
net.ipv4.ip_local_port_range = 10000 65535
|
||||||
|
net.ipv4.tcp_fin_timeout = 15
|
||||||
|
net.ipv4.tcp_tw_reuse = 1
|
||||||
|
net.ipv4.tcp_max_tw_buckets = 2000000
|
||||||
|
```
|
||||||
|
### 2.3 TCP Keepalive (Aggressive Dead Connection Culling)
|
||||||
|
By default, Linux keeps silent, dropped connections open for over 2 hours. This consumes memory at scale. Configure the system to detect and drop them in < 5 minutes:
|
||||||
|
```ini
|
||||||
|
net.ipv4.tcp_keepalive_time = 300
|
||||||
|
net.ipv4.tcp_keepalive_intvl = 30
|
||||||
|
net.ipv4.tcp_keepalive_probes = 5
|
||||||
|
```
|
||||||
|
### 2.4 TCP Buffers & Congestion Control
|
||||||
|
Optimize memory usage per socket and switch to BBR (Bottleneck Bandwidth and Round-trip propagation time) to improve latency on lossy networks:
|
||||||
|
```ini
|
||||||
|
# Core buffer sizes
|
||||||
|
net.core.rmem_default = 262144
|
||||||
|
net.core.wmem_default = 262144
|
||||||
|
net.core.rmem_max = 16777216
|
||||||
|
net.core.wmem_max = 16777216
|
||||||
|
# TCP specific buffers (min, default, max)
|
||||||
|
net.ipv4.tcp_rmem = 4096 87380 16777216
|
||||||
|
net.ipv4.tcp_wmem = 4096 65536 16777216
|
||||||
|
# Enable BBR
|
||||||
|
net.core.default_qdisc = fq
|
||||||
|
net.ipv4.tcp_congestion_control = bbr
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
## 3. Conntrack (Netfilter) Tuning
|
||||||
|
If your server uses `iptables`, `ufw`, or `firewalld`, the Linux kernel tracks every connection state in a table (`nf_conntrack`). When this table fills up, Linux drops new packets.
|
||||||
|
Check your current limit and usage:
|
||||||
|
```bash
|
||||||
|
sysctl net.netfilter.nf_conntrack_max
|
||||||
|
sysctl net.netfilter.nf_conntrack_count
|
||||||
|
```
|
||||||
|
If it gets close to the limit, tune it up, and reduce the time established connections linger in the tracker:
|
||||||
|
```ini
|
||||||
|
# In /etc/sysctl.d/99-telemt-highload.conf
|
||||||
|
net.netfilter.nf_conntrack_max = 2097152
|
||||||
|
# Reduce timeout from default 5 days to 1 hour
|
||||||
|
net.netfilter.nf_conntrack_tcp_timeout_established = 3600
|
||||||
|
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 12
|
||||||
|
```
|
||||||
|
*Note: Depending on your OS, you may need to run `modprobe nf_conntrack` before setting these parameters.*
|
||||||
|
|
||||||
|
---
|
||||||
|
## 4. Multi-Tier Architecture: HAProxy Setup
|
||||||
|
For massive traffic loads, buffering Telemt behind a reverse proxy like HAProxy can help absorb connection spikes and handle basic TCP connections before handing them off.
|
||||||
|
### HAProxy High-Load `haproxy.cfg`
|
||||||
|
```haproxy
|
||||||
|
global
|
||||||
|
# Disable detailed logging under load
|
||||||
|
log stdout format raw local0 err
|
||||||
|
# maxconn 250000
|
||||||
|
|
||||||
|
# Buffer tuning
|
||||||
|
tune.bufsize 16384
|
||||||
|
tune.maxaccept 64
|
||||||
|
defaults
|
||||||
|
log global
|
||||||
|
mode tcp
|
||||||
|
option clitcpka
|
||||||
|
option srvtcpka
|
||||||
|
timeout connect 5s
|
||||||
|
timeout client 1h
|
||||||
|
timeout server 1h
|
||||||
|
# Quick purge for dead peers
|
||||||
|
timeout client-fin 10s
|
||||||
|
timeout server-fin 10s
|
||||||
|
frontend proxy_in
|
||||||
|
bind *:443
|
||||||
|
maxconn 250000
|
||||||
|
option tcp-smart-accept
|
||||||
|
default_backend telemt_backend
|
||||||
|
backend telemt_backend
|
||||||
|
option tcp-smart-connect
|
||||||
|
# Send-Proxy-V2 to preserve Client IP for Telemt's internal logic
|
||||||
|
server telemt_core 10.10.10.1:443 maxconn 250000 send-proxy-v2 check inter 5s
|
||||||
|
```
|
||||||
|
**Important**: Telemt must be configured to process the `PROXY` protocol on port `443` for this chain to work and preserve client IPs.
|
||||||
|
|
||||||
|
---
|
||||||
|
## 5. Diagnostics & Monitoring
|
||||||
|
When operating under load, these commands are useful for diagnostics:
|
||||||
|
* **Checking dropped connections (Queues full)**: `netstat -s | grep "times the listen queue of a socket overflowed"`
|
||||||
|
* **Checking Conntrack drops**: `dmesg | grep conntrack`
|
||||||
|
* **Checking File Descriptor usage**: `cat /proc/sys/fs/file-nr`
|
||||||
|
* **Real-time connection states**: `ss -s` (Avoid using `netstat` on heavy loads).
|
||||||
139
docs/Advanced_settings/HIGH_LOAD.ru.md
Normal file
139
docs/Advanced_settings/HIGH_LOAD.ru.md
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
# Руководство по High-Load конфигурации и тюнингу
|
||||||
|
При развертывании Telemt под высокой нагрузкой (десятки и сотни тысяч одновременных подключений), стандартные ограничения сетевого стека ОС могут приводить к потерям пакетов, переключениям контекста CPU и отказам в соединениях. В данном руководстве описана настройка ядра Linux, системных лимитов и аппаратной конфигурации для работы в подобных сценариях.
|
||||||
|
|
||||||
|
---
|
||||||
|
## 1. Системные лимиты и файловые дескрипторы
|
||||||
|
Каждое TCP-сосоединение требует файлового дескриптора. При 100 тысячах соединений стандартные лимиты Linux (зачастую 1024 или 65535) будут исчерпаны немедленно.
|
||||||
|
### Общесистемные лимиты (`sysctl`)
|
||||||
|
Увеличьте глобальный лимит файловых дескрипторов в `/etc/sysctl.conf`:
|
||||||
|
```ini
|
||||||
|
fs.file-max = 2097152
|
||||||
|
fs.nr_open = 2097152
|
||||||
|
```
|
||||||
|
### На уровне пользователя (`limits.conf`)
|
||||||
|
Отредактируйте `/etc/security/limits.conf`, чтобы разрешить пользователю (от которого запущен telemt) резервировать дескрипторы:
|
||||||
|
```conf
|
||||||
|
* soft nofile 1048576
|
||||||
|
* hard nofile 1048576
|
||||||
|
root soft nofile 1048576
|
||||||
|
root hard nofile 1048576
|
||||||
|
```
|
||||||
|
### Переопределения для Systemd / Docker
|
||||||
|
Если используется **Systemd**, добавьте в ваш `telemt.service`:
|
||||||
|
```ini
|
||||||
|
[Service]
|
||||||
|
LimitNOFILE=1048576
|
||||||
|
LimitNPROC=65535
|
||||||
|
TasksMax=infinity
|
||||||
|
```
|
||||||
|
Если используется **Docker**, задайте `ulimits` в `docker-compose.yaml`:
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
telemt:
|
||||||
|
ulimits:
|
||||||
|
nofile:
|
||||||
|
soft: 1048576
|
||||||
|
hard: 1048576
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
## 2. Тонкая настройка сетевого стека ядра (`sysctl`)
|
||||||
|
Создайте выделенный файл `/etc/sysctl.d/99-telemt-highload.conf` и примените его через `sysctl -p /etc/sysctl.d/99-telemt-highload.conf`.
|
||||||
|
### 2.1 Очереди соединений и защита от SYN-флуда
|
||||||
|
Увеличьте размеры очередей, чтобы поглощать внезапные всплески соединений и смягчить атаки типа SYN flood:
|
||||||
|
```ini
|
||||||
|
net.core.somaxconn = 65535
|
||||||
|
net.core.netdev_max_backlog = 65535
|
||||||
|
net.ipv4.tcp_max_syn_backlog = 65535
|
||||||
|
net.ipv4.tcp_syncookies = 1
|
||||||
|
```
|
||||||
|
### 2.2 Исчерпание портов и TIME-WAIT сокеты
|
||||||
|
Высокая текучесть приводит к нехватке временных (ephemeral) портов. Расширьте диапазон портов и позвольте ядру быстро переиспользовать закрытые сокеты:
|
||||||
|
```ini
|
||||||
|
net.ipv4.ip_local_port_range = 10000 65535
|
||||||
|
net.ipv4.tcp_fin_timeout = 15
|
||||||
|
net.ipv4.tcp_tw_reuse = 1
|
||||||
|
net.ipv4.tcp_max_tw_buckets = 2000000
|
||||||
|
```
|
||||||
|
### 2.3 TCP Keepalive (Агрессивная очистка мертвых соединений)
|
||||||
|
По умолчанию Linux держит "оборванные" TCP-сессии более 2 часов. Задайте параметры для обнаружения и сброса мертвых соединений за менее чем 5 минут:
|
||||||
|
```ini
|
||||||
|
net.ipv4.tcp_keepalive_time = 300
|
||||||
|
net.ipv4.tcp_keepalive_intvl = 30
|
||||||
|
net.ipv4.tcp_keepalive_probes = 5
|
||||||
|
```
|
||||||
|
### 2.4 Буферы TCP и управление перегрузками (Congestion Control)
|
||||||
|
Оптимизируйте использование памяти на сокет и переключитесь на алгоритм BBR (Bottleneck Bandwidth and Round-trip propagation time) для улучшения задержки на плохих сетях:
|
||||||
|
```ini
|
||||||
|
# Размеры буферов ядра (по умолчанию и макс)
|
||||||
|
net.core.rmem_default = 262144
|
||||||
|
net.core.wmem_default = 262144
|
||||||
|
net.core.rmem_max = 16777216
|
||||||
|
net.core.wmem_max = 16777216
|
||||||
|
# Специфичные TCP буферы (min, default, max)
|
||||||
|
net.ipv4.tcp_rmem = 4096 87380 16777216
|
||||||
|
net.ipv4.tcp_wmem = 4096 65536 16777216
|
||||||
|
# Включение BBR
|
||||||
|
net.core.default_qdisc = fq
|
||||||
|
net.ipv4.tcp_congestion_control = bbr
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
## 3. Тюнинг Conntrack (Netfilter)
|
||||||
|
Если ваш сервер использует `iptables`, `ufw` или `firewalld`, ядро вынуждено отслеживать каждое соединение в таблице состояний (`nf_conntrack`). Когда эта таблица переполняется, Linux отбрасывает новые пакеты без уведомления приложения.
|
||||||
|
Проверьте текущие лимиты и использование:
|
||||||
|
```bash
|
||||||
|
sysctl net.netfilter.nf_conntrack_max
|
||||||
|
sysctl net.netfilter.nf_conntrack_count
|
||||||
|
```
|
||||||
|
Если вы близки к пределу, увеличьте таблицу и заставьте ядро быстрее удалять установленные соединения. Добавьте в `/etc/sysctl.d/99-telemt-highload.conf`:
|
||||||
|
```ini
|
||||||
|
net.netfilter.nf_conntrack_max = 2097152
|
||||||
|
# Снижаем таймаут с дефолтных 5 дней до 1 часа
|
||||||
|
net.netfilter.nf_conntrack_tcp_timeout_established = 3600
|
||||||
|
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 12
|
||||||
|
```
|
||||||
|
*Внимание: в зависимости от ОС, вам может потребоваться выполнить `modprobe nf_conntrack` перед установкой этих параметров.*
|
||||||
|
|
||||||
|
---
|
||||||
|
## 4. Архитектура: Развертывание за HAProxy
|
||||||
|
Для максимальных нагрузок выставление Telemt напрямую в интернет менее эффективно, чем использование оптимизированного L4-балансировщика. HAProxy эффективен в поглощении TCP атак, обработке рукопожатий и сглаживании всплесков подключений.
|
||||||
|
### Оптимизация `haproxy.cfg` для High-Load
|
||||||
|
```haproxy
|
||||||
|
global
|
||||||
|
# Отключить детальные логи соединений под нагрузкой
|
||||||
|
log stdout format raw local0 err
|
||||||
|
maxconn 250000
|
||||||
|
# Тюнинг буферов и приема сокетов
|
||||||
|
tune.bufsize 16384
|
||||||
|
tune.maxaccept 64
|
||||||
|
defaults
|
||||||
|
log global
|
||||||
|
mode tcp
|
||||||
|
option clitcpka
|
||||||
|
option srvtcpka
|
||||||
|
timeout connect 5s
|
||||||
|
timeout client 1h
|
||||||
|
timeout server 1h
|
||||||
|
# Быстрая очистка мертвых пиров
|
||||||
|
timeout client-fin 10s
|
||||||
|
timeout server-fin 10s
|
||||||
|
frontend proxy_in
|
||||||
|
bind *:443
|
||||||
|
maxconn 250000
|
||||||
|
option tcp-smart-accept
|
||||||
|
default_backend telemt_backend
|
||||||
|
backend telemt_backend
|
||||||
|
option tcp-smart-connect
|
||||||
|
# Send-Proxy-V2 обязателен для сохранения IP клиента внутри внутренней логики Telemt
|
||||||
|
server telemt_core 10.10.10.1:443 maxconn 250000 send-proxy-v2 check inter 5s
|
||||||
|
```
|
||||||
|
**Важно**: Telemt должен быть настроен на обработку протокола `PROXY` на порту `443`, чтобы получать оригинальные IP-адреса клиентов.
|
||||||
|
|
||||||
|
---
|
||||||
|
## 5. Диагностика
|
||||||
|
Команды для выявления узких мест:
|
||||||
|
* **Проверка дропов TCP (переполнение очередей)**: `netstat -s | grep "times the listen queue of a socket overflowed"`
|
||||||
|
* **Контроль отбрасывания пакетов Conntrack**: `dmesg | grep conntrack`
|
||||||
|
* **Проверка использования файловых дескрипторов**: `cat /proc/sys/fs/file-nr`
|
||||||
|
* **Отображение состояния сокетов**: `ss -s` (Избегайте использования `netstat` под высокой нагрузкой).
|
||||||
@@ -9,12 +9,12 @@ API runtime is configured in `[server.api]`.
|
|||||||
|
|
||||||
| Field | Type | Default | Description |
|
| Field | Type | Default | Description |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `enabled` | `bool` | `false` | Enables REST API listener. |
|
| `enabled` | `bool` | `true` | Enables REST API listener. |
|
||||||
| `listen` | `string` (`IP:PORT`) | `127.0.0.1:9091` | API bind address. |
|
| `listen` | `string` (`IP:PORT`) | `0.0.0.0:9091` | API bind address. |
|
||||||
| `whitelist` | `CIDR[]` | `127.0.0.1/32, ::1/128` | Source IP allowlist. Empty list means allow all. |
|
| `whitelist` | `CIDR[]` | `127.0.0.0/8` | Source IP allowlist. Empty list means allow all. |
|
||||||
| `auth_header` | `string` | `""` | Exact value for `Authorization` header. Empty disables header auth. |
|
| `auth_header` | `string` | `""` | Exact value for `Authorization` header. Empty disables header auth. |
|
||||||
| `request_body_limit_bytes` | `usize` | `65536` | Maximum request body size. Must be `> 0`. |
|
| `request_body_limit_bytes` | `usize` | `65536` | Maximum request body size. Must be `> 0`. |
|
||||||
| `minimal_runtime_enabled` | `bool` | `false` | Enables runtime snapshot endpoints requiring ME pool read-lock aggregation. |
|
| `minimal_runtime_enabled` | `bool` | `true` | Enables runtime snapshot endpoints requiring ME pool read-lock aggregation. |
|
||||||
| `minimal_runtime_cache_ttl_ms` | `u64` | `1000` | Cache TTL for minimal snapshots. `0` disables cache; valid range is `[0, 60000]`. |
|
| `minimal_runtime_cache_ttl_ms` | `u64` | `1000` | Cache TTL for minimal snapshots. `0` disables cache; valid range is `[0, 60000]`. |
|
||||||
| `runtime_edge_enabled` | `bool` | `false` | Enables runtime edge endpoints with cached aggregation payloads. |
|
| `runtime_edge_enabled` | `bool` | `false` | Enables runtime edge endpoints with cached aggregation payloads. |
|
||||||
| `runtime_edge_cache_ttl_ms` | `u64` | `1000` | Cache TTL for runtime edge summary payloads. `0` disables cache. |
|
| `runtime_edge_cache_ttl_ms` | `u64` | `1000` | Cache TTL for runtime edge summary payloads. `0` disables cache. |
|
||||||
|
Before Width: | Height: | Size: 650 KiB After Width: | Height: | Size: 650 KiB |
|
Before Width: | Height: | Size: 838 KiB After Width: | Height: | Size: 838 KiB |
File diff suppressed because it is too large
Load Diff
3072
docs/Config_params/CONFIG_PARAMS.ru.md
Normal file
3072
docs/Config_params/CONFIG_PARAMS.ru.md
Normal file
File diff suppressed because it is too large
Load Diff
172
docs/FAQ.en.md
172
docs/FAQ.en.md
@@ -1,5 +1,4 @@
|
|||||||
## How to set up a "proxy sponsor" channel and statistics via the @MTProxybot
|
## How to set up a "proxy sponsor" channel and statistics via the @MTProxybot
|
||||||
|
|
||||||
1. Go to the @MTProxybot.
|
1. Go to the @MTProxybot.
|
||||||
2. Enter the `/newproxy` command.
|
2. Enter the `/newproxy` command.
|
||||||
3. Send your server's IP address and port. For example: `1.2.3.4:443`.
|
3. Send your server's IP address and port. For example: `1.2.3.4:443`.
|
||||||
@@ -32,23 +31,161 @@ use_middle_proxy = true
|
|||||||
hello = "ad_tag"
|
hello = "ad_tag"
|
||||||
hello2 = "ad_tag2"
|
hello2 = "ad_tag2"
|
||||||
```
|
```
|
||||||
|
## Recognizability for DPI and crawler
|
||||||
|
|
||||||
## Why do you need a middle proxy (ME)
|
On April 1, 2026, we became aware of a method for detecting MTProxy Fake-TLS,
|
||||||
|
based on the ECH extension and the ordering of cipher suites,
|
||||||
|
as well as an overall unique JA3/JA4 fingerprint
|
||||||
|
that does not occur in modern browsers.
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> TLS fingerprint has been fixed in latest version of clients for Desktop / Android / iOS.
|
||||||
|
> Please update your client for MTProxy Fake-TLS to work correctly.
|
||||||
|
|
||||||
|
- We consider this a breakthrough aspect, which has no stable analogues today
|
||||||
|
- Based on this: if `telemt` configured correctly, **TLS mode is completely identical to real-life handshake + communication** with a specified host
|
||||||
|
- Here is our evidence:
|
||||||
|
- 212.220.88.77 - "dummy" host, running `telemt`
|
||||||
|
- `petrovich.ru` - `tls` + `masking` host, in HEX: `706574726f766963682e7275`
|
||||||
|
- **No MITM + No Fake Certificates/Crypto** = pure transparent *TCP Splice* to "best" upstream: MTProxy or tls/mask-host:
|
||||||
|
- DPI see legitimate HTTPS to `tls_host`, including *valid chain-of-trust* and entropy
|
||||||
|
- Crawlers completely satisfied receiving responses from `mask_host`
|
||||||
|
### Client WITH secret-key accesses the MTProxy resource:
|
||||||
|
|
||||||
|
<img width="360" height="439" alt="telemt" src="https://github.com/user-attachments/assets/39352afb-4a11-4ecc-9d91-9e8cfb20607d" />
|
||||||
|
|
||||||
|
### Client WITHOUT secret-key gets transparent access to the specified resource:
|
||||||
|
- with trusted certificate
|
||||||
|
- with original handshake
|
||||||
|
- with full request-response way
|
||||||
|
- with low-latency overhead
|
||||||
|
```bash
|
||||||
|
root@debian:~/telemt# curl -v -I --resolve petrovich.ru:443:212.220.88.77 https://petrovich.ru/
|
||||||
|
* Added petrovich.ru:443:212.220.88.77 to DNS cache
|
||||||
|
* Hostname petrovich.ru was found in DNS cache
|
||||||
|
* Trying 212.220.88.77:443...
|
||||||
|
* Connected to petrovich.ru (212.220.88.77) port 443 (#0)
|
||||||
|
* ALPN: offers h2,http/1.1
|
||||||
|
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
|
||||||
|
* CAfile: /etc/ssl/certs/ca-certificates.crt
|
||||||
|
* CApath: /etc/ssl/certs
|
||||||
|
* TLSv1.3 (IN), TLS handshake, Server hello (2):
|
||||||
|
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
|
||||||
|
* TLSv1.3 (IN), TLS handshake, Certificate (11):
|
||||||
|
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
|
||||||
|
* TLSv1.3 (IN), TLS handshake, Finished (20):
|
||||||
|
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
|
||||||
|
* TLSv1.3 (OUT), TLS handshake, Finished (20):
|
||||||
|
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
|
||||||
|
* ALPN: server did not agree on a protocol. Uses default.
|
||||||
|
* Server certificate:
|
||||||
|
* subject: C=RU; ST=Saint Petersburg; L=Saint Petersburg; O=STD Petrovich; CN=*.petrovich.ru
|
||||||
|
* start date: Jan 28 11:21:01 2025 GMT
|
||||||
|
* expire date: Mar 1 11:21:00 2026 GMT
|
||||||
|
* subjectAltName: host "petrovich.ru" matched cert's "petrovich.ru"
|
||||||
|
* issuer: C=BE; O=GlobalSign nv-sa; CN=GlobalSign RSA OV SSL CA 2018
|
||||||
|
* SSL certificate verify ok.
|
||||||
|
* using HTTP/1.x
|
||||||
|
> HEAD / HTTP/1.1
|
||||||
|
> Host: petrovich.ru
|
||||||
|
> User-Agent: curl/7.88.1
|
||||||
|
> Accept: */*
|
||||||
|
>
|
||||||
|
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
|
||||||
|
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
|
||||||
|
* old SSL session ID is stale, removing
|
||||||
|
< HTTP/1.1 200 OK
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
< Server: Variti/0.9.3a
|
||||||
|
Server: Variti/0.9.3a
|
||||||
|
< Date: Thu, 01 Jan 2026 00:0000 GMT
|
||||||
|
Date: Thu, 01 Jan 2026 00:0000 GMT
|
||||||
|
< Access-Control-Allow-Origin: *
|
||||||
|
Access-Control-Allow-Origin: *
|
||||||
|
< Content-Type: text/html
|
||||||
|
Content-Type: text/html
|
||||||
|
< Cache-Control: no-store
|
||||||
|
Cache-Control: no-store
|
||||||
|
< Expires: Thu, 01 Jan 2026 00:0000 GMT
|
||||||
|
Expires: Thu, 01 Jan 2026 00:0000 GMT
|
||||||
|
< Pragma: no-cache
|
||||||
|
Pragma: no-cache
|
||||||
|
< Set-Cookie: ipp_uid=XXXXX/XXXXX/XXXXX==; Expires=Tue, 31 Dec 2040 23:59:59 GMT; Domain=.petrovich.ru; Path=/
|
||||||
|
Set-Cookie: ipp_uid=XXXXX/XXXXX/XXXXX==; Expires=Tue, 31 Dec 2040 23:59:59 GMT; Domain=.petrovich.ru; Path=/
|
||||||
|
< Content-Type: text/html
|
||||||
|
Content-Type: text/html
|
||||||
|
< Content-Length: 31253
|
||||||
|
Content-Length: 31253
|
||||||
|
< Connection: keep-alive
|
||||||
|
Connection: keep-alive
|
||||||
|
< Keep-Alive: timeout=60
|
||||||
|
Keep-Alive: timeout=60
|
||||||
|
|
||||||
|
<
|
||||||
|
* Connection #0 to host petrovich.ru left intact
|
||||||
|
|
||||||
|
```
|
||||||
|
- We challenged ourselves, we kept trying and we didn't only *beat the air*: now, we have something to show you
|
||||||
|
- Do not just take our word for it? - This is great and we respect that: you can build your own `telemt` or download a build and check it right now
|
||||||
|
|
||||||
|
|
||||||
|
## F.A.Q.
|
||||||
|
|
||||||
|
### Telegram Calls via MTProxy
|
||||||
|
- Telegram architecture **does NOT allow calls via MTProxy**, but only via SOCKS5, which cannot be obfuscated
|
||||||
|
|
||||||
|
### How does DPI see MTProxy TLS?
|
||||||
|
- DPI sees MTProxy in Fake TLS (ee) mode as TLS 1.3
|
||||||
|
- the SNI you specify sends both the client and the server;
|
||||||
|
- ALPN is similar to HTTP 1.1/2;
|
||||||
|
- high entropy, which is normal for AES-encrypted traffic;
|
||||||
|
|
||||||
|
### Whitelist on IP
|
||||||
|
- MTProxy cannot work when there is:
|
||||||
|
- no IP connectivity to the target host: Russian Whitelist on Mobile Networks - "Белый список"
|
||||||
|
- OR all TCP traffic is blocked
|
||||||
|
- OR high entropy/encrypted traffic is blocked: content filters at universities and critical infrastructure
|
||||||
|
- OR all TLS traffic is blocked
|
||||||
|
- OR specified port is blocked: use 443 to make it "like real"
|
||||||
|
- OR provided SNI is blocked: use "officially approved"/innocuous name
|
||||||
|
- like most protocols on the Internet;
|
||||||
|
- these situations are observed:
|
||||||
|
- in China behind the Great Firewall
|
||||||
|
- in Russia on mobile networks, less in wired networks
|
||||||
|
- in Iran during "activity"
|
||||||
|
|
||||||
|
### Why do you need a middle proxy (ME)
|
||||||
https://github.com/telemt/telemt/discussions/167
|
https://github.com/telemt/telemt/discussions/167
|
||||||
|
|
||||||
|
## How clients interact with Telegram DCs
|
||||||
|
When you register a Telegram account, it gets permanently bound to one of Telegram's data centers (DCs).
|
||||||
|
It is deciced beforehand by Telegram based on the phone number's region.
|
||||||
|
This DC becomes your **home DC**: all content you upload (photos, videos, files, messages) is stored there.
|
||||||
|
Your client authenticates on it with every connection.
|
||||||
|
|
||||||
## How many people can use one link
|
For example, if your account is registered on **DC2**, your client will always connect to DC2 first.
|
||||||
|
When you open a chat with another user whose home DC is **DC5**, your client opens an additional connection to DC5 to download their media.
|
||||||
|
Those cross-DC requests are normal and happen constantly.
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> Because every session is anchored to your home DC, an outage there causes other DCs to be unavaliable.
|
||||||
|
> If your home DC is DC2 and DC2 goes down, you **cannot** reach DC5 even though DC5 itself is perfectly healthy.
|
||||||
|
> The client has no valid session to route the request through.
|
||||||
|
|
||||||
|
This is also why an MTProxy only needs to reach Telegram's DC infrastructure as a whole.
|
||||||
|
The proxy itself doesn't care which DC your account lives on. The client negotiates the correct DC through the proxy after connecting.
|
||||||
|
|
||||||
|
### How many people can use one link
|
||||||
By default, an unlimited number of people can use a single link.
|
By default, an unlimited number of people can use a single link.
|
||||||
However, you can limit the number of unique IP addresses for each user:
|
However, you can limit the number of unique IP addresses for each user:
|
||||||
```toml
|
```toml
|
||||||
[access.user_max_unique_ips]
|
[access.user_max_unique_ips]
|
||||||
hello = 1
|
hello = 1
|
||||||
```
|
```
|
||||||
This parameter sets the maximum number of unique IP addresses from which a single link can be used simultaneously. If the first user disconnects, a second one can connect. At the same time, multiple users can connect from a single IP address simultaneously (for example, devices on the same Wi-Fi network).
|
This parameter sets the maximum number of unique IP addresses from which a single link can be used simultaneously. If the first user disconnects, a second one can connect.
|
||||||
|
At the same time, multiple users can connect from a single IP address simultaneously (for example, devices on the same Wi-Fi network).
|
||||||
## How to create multiple different links
|
|
||||||
|
|
||||||
|
### How to create multiple different links
|
||||||
1. Generate the required number of secrets using the command: `openssl rand -hex 16`.
|
1. Generate the required number of secrets using the command: `openssl rand -hex 16`.
|
||||||
2. Open the configuration file: `nano /etc/telemt/telemt.toml`.
|
2. Open the configuration file: `nano /etc/telemt/telemt.toml`.
|
||||||
3. Add new users to the `[access.users]` section:
|
3. Add new users to the `[access.users]` section:
|
||||||
@@ -64,7 +201,7 @@ user3 = "00000000000000000000000000000003"
|
|||||||
curl -s http://127.0.0.1:9091/v1/users | jq
|
curl -s http://127.0.0.1:9091/v1/users | jq
|
||||||
```
|
```
|
||||||
|
|
||||||
## "Unknown TLS SNI" error
|
### "Unknown TLS SNI" error
|
||||||
Usually, this error occurs if you have changed the `tls_domain` parameter, but users continue to connect using old links with the previous domain.
|
Usually, this error occurs if you have changed the `tls_domain` parameter, but users continue to connect using old links with the previous domain.
|
||||||
|
|
||||||
If you need to allow connections with any domains (ignoring SNI mismatches), add the following parameters:
|
If you need to allow connections with any domains (ignoring SNI mismatches), add the following parameters:
|
||||||
@@ -73,7 +210,7 @@ If you need to allow connections with any domains (ignoring SNI mismatches), add
|
|||||||
unknown_sni_action = "mask"
|
unknown_sni_action = "mask"
|
||||||
```
|
```
|
||||||
|
|
||||||
## How to view metrics
|
### How to view metrics
|
||||||
|
|
||||||
1. Open the configuration file: `nano /etc/telemt/telemt.toml`.
|
1. Open the configuration file: `nano /etc/telemt/telemt.toml`.
|
||||||
2. Add the following parameters:
|
2. Add the following parameters:
|
||||||
@@ -87,6 +224,25 @@ metrics_whitelist = ["127.0.0.1/32", "::1/128", "0.0.0.0/0"]
|
|||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> The value `"0.0.0.0/0"` in `metrics_whitelist` opens access to metrics from any IP address. It is recommended to replace it with your personal IP, for example: `"1.2.3.4/32"`.
|
> The value `"0.0.0.0/0"` in `metrics_whitelist` opens access to metrics from any IP address. It is recommended to replace it with your personal IP, for example: `"1.2.3.4/32"`.
|
||||||
|
|
||||||
|
### Too many open files
|
||||||
|
- On a fresh Linux install the default open file limit is low; under load `telemt` may fail with `Accept error: Too many open files`
|
||||||
|
- **Systemd**: add `LimitNOFILE=65536` to the `[Service]` section (already included in the example above)
|
||||||
|
- **Docker**: add `--ulimit nofile=65536:65536` to your `docker run` command, or in `docker-compose.yml`:
|
||||||
|
```yaml
|
||||||
|
ulimits:
|
||||||
|
nofile:
|
||||||
|
soft: 65536
|
||||||
|
hard: 65536
|
||||||
|
```
|
||||||
|
- **System-wide** (optional): add to `/etc/security/limits.conf`:
|
||||||
|
```
|
||||||
|
* soft nofile 1048576
|
||||||
|
* hard nofile 1048576
|
||||||
|
root soft nofile 1048576
|
||||||
|
root hard nofile 1048576
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Additional parameters
|
## Additional parameters
|
||||||
|
|
||||||
### Domain in the link instead of IP
|
### Domain in the link instead of IP
|
||||||
|
|||||||
172
docs/FAQ.ru.md
172
docs/FAQ.ru.md
@@ -32,10 +32,164 @@ use_middle_proxy = true
|
|||||||
hello = "ad_tag"
|
hello = "ad_tag"
|
||||||
hello2 = "ad_tag2"
|
hello2 = "ad_tag2"
|
||||||
```
|
```
|
||||||
|
## Распознаваемость для DPI и сканеров
|
||||||
|
1 апреля 2026 года нам стало известно о методе обнаружения MTProxy Fake-TLS, основанном на расширении ECH и порядке набора шифров,
|
||||||
|
а также об общем уникальном отпечатке JA3/JA4, который не встречается в современных браузерах.
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> Проблема с TLS отпечатком исправлена в последних версиях клиентов Telegram для Desktop / Android / iOS.
|
||||||
|
> Обновите свой клиент для корректной работы с MTProxy Fake-TLS!
|
||||||
|
|
||||||
|
- Мы считаем это прорывом, которому на сегодняшний день нет стабильных аналогов;
|
||||||
|
- Исходя из этого: если `telemt` настроен правильно, **режим TLS полностью идентичен реальному «рукопожатию» + обмену данными** с указанным хостом;
|
||||||
|
- Вот наши доказательства:
|
||||||
|
- 212.220.88.77 — «фиктивный» хост, на котором запущен `telemt`;
|
||||||
|
- `petrovich.ru` — хост с `tls` + `masking`, в HEX: `706574726f766963682e7275`;
|
||||||
|
- **Без MITM + без поддельных сертификатов/шифрования** = чистое прозрачное *TCP Splice* к «лучшему» исходному серверу: MTProxy или tls/mask-host:
|
||||||
|
- DPI видит легитимный HTTPS к `tls_host`, включая *достоверную цепочку доверия* и энтропию;
|
||||||
|
- Краулеры полностью удовлетворены получением ответов от `mask_host`.
|
||||||
|
|
||||||
|
### Клиент С секретным ключом получает доступ к ресурсу MTProxy:
|
||||||
|
|
||||||
|
<img width="360" height="439" alt="telemt" src="https://github.com/user-attachments/assets/39352afb-4a11-4ecc-9d91-9e8cfb20607d" />
|
||||||
|
|
||||||
|
### Клиент БЕЗ секретного ключа получает прозрачный доступ к указанному ресурсу:
|
||||||
|
- с доверенным сертификатом;
|
||||||
|
- с исходным «рукопожатием»;
|
||||||
|
- с полным циклом запрос-ответ;
|
||||||
|
- с низкой задержкой.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
root@debian:~/telemt# curl -v -I --resolve petrovich.ru:443:212.220.88.77 https://petrovich.ru/
|
||||||
|
* Added petrovich.ru:443:212.220.88.77 to DNS cache
|
||||||
|
* Hostname petrovich.ru was found in DNS cache
|
||||||
|
* Trying 212.220.88.77:443...
|
||||||
|
* Connected to petrovich.ru (212.220.88.77) port 443 (#0)
|
||||||
|
* ALPN: offers h2,http/1.1
|
||||||
|
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
|
||||||
|
* CAfile: /etc/ssl/certs/ca-certificates.crt
|
||||||
|
* CApath: /etc/ssl/certs
|
||||||
|
* TLSv1.3 (IN), TLS handshake, Server hello (2):
|
||||||
|
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
|
||||||
|
* TLSv1.3 (IN), TLS handshake, Certificate (11):
|
||||||
|
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
|
||||||
|
* TLSv1.3 (IN), TLS handshake, Finished (20):
|
||||||
|
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
|
||||||
|
* TLSv1.3 (OUT), TLS handshake, Finished (20):
|
||||||
|
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
|
||||||
|
* ALPN: server did not agree on a protocol. Uses default.
|
||||||
|
* Server certificate:
|
||||||
|
* subject: C=RU; ST=Saint Petersburg; L=Saint Petersburg; O=STD Petrovich; CN=*.petrovich.ru
|
||||||
|
* start date: Jan 28 11:21:01 2025 GMT
|
||||||
|
* expire date: Mar 1 11:21:00 2026 GMT
|
||||||
|
* subjectAltName: host "petrovich.ru" matched cert's "petrovich.ru"
|
||||||
|
* issuer: C=BE; O=GlobalSign nv-sa; CN=GlobalSign RSA OV SSL CA 2018
|
||||||
|
* SSL certificate verify ok.
|
||||||
|
* using HTTP/1.x
|
||||||
|
> HEAD / HTTP/1.1
|
||||||
|
> Host: petrovich.ru
|
||||||
|
> User-Agent: curl/7.88.1
|
||||||
|
> Accept: */*
|
||||||
|
>
|
||||||
|
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
|
||||||
|
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
|
||||||
|
* old SSL session ID is stale, removing
|
||||||
|
< HTTP/1.1 200 OK
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
< Server: Variti/0.9.3a
|
||||||
|
Server: Variti/0.9.3a
|
||||||
|
< Date: Thu, 01 Jan 2026 00:0000 GMT
|
||||||
|
Date: Thu, 01 Jan 2026 00:0000 GMT
|
||||||
|
< Access-Control-Allow-Origin: *
|
||||||
|
Access-Control-Allow-Origin: *
|
||||||
|
< Content-Type: text/html
|
||||||
|
Content-Type: text/html
|
||||||
|
< Cache-Control: no-store
|
||||||
|
Cache-Control: no-store
|
||||||
|
< Expires: Thu, 01 Jan 2026 00:0000 GMT
|
||||||
|
Expires: Thu, 01 Jan 2026 00:0000 GMT
|
||||||
|
< Pragma: no-cache
|
||||||
|
Pragma: no-cache
|
||||||
|
< Set-Cookie: ipp_uid=XXXXX/XXXXX/XXXXX==; Expires=Tue, 31 Dec 2040 23:59:59 GMT; Domain=.petrovich.ru; Path=/
|
||||||
|
Set-Cookie: ipp_uid=XXXXX/XXXXX/XXXXX==; Expires=Tue, 31 Dec 2040 23:59:59 GMT; Domain=.petrovich.ru; Path=/
|
||||||
|
< Content-Type: text/html
|
||||||
|
Content-Type: text/html
|
||||||
|
< Content-Length: 31253
|
||||||
|
Content-Length: 31253
|
||||||
|
< Connection: keep-alive
|
||||||
|
Connection: keep-alive
|
||||||
|
< Keep-Alive: timeout=60
|
||||||
|
Keep-Alive: timeout=60
|
||||||
|
|
||||||
|
<
|
||||||
|
* Connection #0 to host petrovich.ru left intact
|
||||||
|
|
||||||
|
```
|
||||||
|
- Мы поставили перед собой задачу, не сдавались и не просто «бились в пустоту»: теперь у нас есть что вам показать.
|
||||||
|
- Не верите нам на слово? — Это прекрасно, и мы уважаем ваше решение: вы можете собрать свой собственный `telemt` или скачать готовую сборку и проверить её прямо сейчас.
|
||||||
|
|
||||||
|
### Звонки в Telegram через MTProxy
|
||||||
|
- Архитектура Telegram **НЕ поддерживает звонки через MTProxy**, а только через SOCKS5, который невозможно замаскировать
|
||||||
|
|
||||||
|
### Как DPI распознает TLS-соединение MTProxy?
|
||||||
|
- DPI распознает MTProxy в режиме Fake TLS (ee) как TLS 1.3
|
||||||
|
- указанный вами SNI отправляется как клиентом, так и сервером;
|
||||||
|
- ALPN аналогичен HTTP 1.1/2;
|
||||||
|
- высокая энтропия, что нормально для трафика, зашифрованного AES;
|
||||||
|
|
||||||
|
### Белый список по IP
|
||||||
|
- MTProxy не может работать, если:
|
||||||
|
- отсутствует IP-связь с целевым хостом: российский белый список в мобильных сетях — «Белый список»;
|
||||||
|
- ИЛИ весь TCP-трафик заблокирован;
|
||||||
|
- ИЛИ трафик с высокой энтропией/зашифрованный трафик заблокирован: контент-фильтры в университетах и критически важной инфраструктуре;
|
||||||
|
- ИЛИ весь TLS-трафик заблокирован;
|
||||||
|
- ИЛИ заблокирован указанный порт: используйте 443, чтобы сделать его «как настоящий»;
|
||||||
|
- ИЛИ заблокирован предоставленный SNI: используйте «официально одобренное»/безобидное имя;
|
||||||
|
- как и большинство протоколов в Интернете;
|
||||||
|
- такие ситуации наблюдаются:
|
||||||
|
- в Китае за Великим файрволом;
|
||||||
|
- в России в мобильных сетях, реже в проводных сетях;
|
||||||
|
- в Иране во время «активности».
|
||||||
|
|
||||||
|
|
||||||
## Зачем нужен middle proxy (ME)
|
## Зачем нужен middle proxy (ME)
|
||||||
https://github.com/telemt/telemt/discussions/167
|
https://github.com/telemt/telemt/discussions/167
|
||||||
|
|
||||||
|
## Как клиенты взаимодействуют с дата-центрами Telegram
|
||||||
|
При регистрации аккаунта Telegram он навсегда привязывается к одному из дата-центров (DC).
|
||||||
|
Telegram заранее определяет к какому DC привязать аккаунт исходя из региона, к которому относиться номер телефона.
|
||||||
|
Этот DC становится вашим **домашним**: именно там хранится весь контент, который вы загружаете (фото, видео, файлы, сообщения).
|
||||||
|
И именно на нем клиент авторизуется при каждом подключении.
|
||||||
|
|
||||||
|
Например, если ваш аккаунт зарегистрирован на **DC2**, клиент всегда будет подключаться в первую очередь к DC2.
|
||||||
|
Когда вы открываете переписку с пользователем, чей домашний DC — **DC5**, клиент устанавливает доп. соединение с DC5, чтобы загрузить его контент.
|
||||||
|
Такие кросс-запросы к DC — это нормальная часть работы Telegram.
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> Поскольку аккаунт всегда привязан к домашнему DC, при его падении контент с других DC будет недоступен.
|
||||||
|
> Если ваш домашний DC — DC2, и DC2 лежит, вы **не сможете** достучаться и до DC5, даже если сам DC5 полностью исправен.
|
||||||
|
> У клиента просто нет валидной сессии, через которую можно было бы направить запрос.
|
||||||
|
|
||||||
|
По той же причине MTProxy достаточно иметь доступ к инфраструктуре Telegram в целом.
|
||||||
|
Cамому MTProxy всё равно, на каком DC живёт ваш аккаунт. Клиент cам договаривается о нужном DC через прокси уже после подключения.
|
||||||
|
|
||||||
|
## Что такое dd и ee в контексте MTProxy?
|
||||||
|
|
||||||
|
Это два разных режима работы прокси. Понять, какой режим используется, можно взглянув на начало секрета — там будет dd или ee, вот пример:
|
||||||
|
tg://proxy?server=s1.dimasssss.space&port=443&secret=eebe3007e927acd147dde12bee8b1a7c9364726976652e676f6f676c652e636f6d
|
||||||
|
|
||||||
|
dd — режим с мусорным трафиком, обфускацией данных, похожий на shadowsocks. У такого трафика есть заметный паттерн, который DPI умеют распознавать и впоследствии блокировать. Использовать этот режим на текущий момент не рекомендуется.
|
||||||
|
|
||||||
|
ee — режим маскировки под существующий домен (FakeTLS), словно вы сёрфите в интернете через браузер. На текущий момент не попадает под блокировку.
|
||||||
|
|
||||||
|
### Где эти режимы настраиваются?
|
||||||
|
|
||||||
|
```toml
|
||||||
|
В конфиге telemt.toml в разделе [general.modes]:
|
||||||
|
classic = false # классический режим, давно стал бесполезным
|
||||||
|
secure = false # переменная dd-режима
|
||||||
|
tls = true # переменная ee-режима
|
||||||
|
```
|
||||||
|
|
||||||
## Сколько человек может пользоваться одной ссылкой
|
## Сколько человек может пользоваться одной ссылкой
|
||||||
|
|
||||||
@@ -104,7 +258,7 @@ max_connections = 10000 # 0 - без ограничений, 10000 - по у
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Upstream Manager
|
### Upstream Manager
|
||||||
Для настройки исходящих подключений (апстримов) добавьте соответствующие параметры в секцию `[[upstreams]]` файла конфигурации:
|
Для настройки исходящих подключений (Upstreams) добавьте соответствующие параметры в секцию `[[upstreams]]` файла конфигурации:
|
||||||
|
|
||||||
#### Привязка к исходящему IP-адресу
|
#### Привязка к исходящему IP-адресу
|
||||||
```toml
|
```toml
|
||||||
@@ -119,20 +273,20 @@ interface = "192.168.1.100" # Замените на ваш исходящий IP
|
|||||||
- Без авторизации:
|
- Без авторизации:
|
||||||
```toml
|
```toml
|
||||||
[[upstreams]]
|
[[upstreams]]
|
||||||
type = "socks5" # Specify SOCKS4 or SOCKS5
|
type = "socks5" # выбор типа SOCKS4 или SOCKS5
|
||||||
address = "1.2.3.4:1234" # SOCKS-server Address
|
address = "1.2.3.4:1234" # адрес сервера SOCKS
|
||||||
weight = 1 # Set Weight for Scenarios
|
weight = 1 # вес
|
||||||
enabled = true
|
enabled = true
|
||||||
```
|
```
|
||||||
|
|
||||||
- С авторизацией:
|
- С авторизацией:
|
||||||
```toml
|
```toml
|
||||||
[[upstreams]]
|
[[upstreams]]
|
||||||
type = "socks5" # Specify SOCKS4 or SOCKS5
|
type = "socks5" # выбор типа SOCKS4 или SOCKS5
|
||||||
address = "1.2.3.4:1234" # SOCKS-server Address
|
address = "1.2.3.4:1234" # адрес сервера SOCKS
|
||||||
username = "user" # Username for Auth on SOCKS-server
|
username = "user" # имя пользователя
|
||||||
password = "pass" # Password for Auth on SOCKS-server
|
password = "pass" # пароль
|
||||||
weight = 1 # Set Weight for Scenarios
|
weight = 1 # вес
|
||||||
enabled = true
|
enabled = true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ cargo build --release
|
|||||||
./target/release/telemt --version
|
./target/release/telemt --version
|
||||||
```
|
```
|
||||||
|
|
||||||
For low-RAM systems, this repository already uses `lto = "thin"` in release profile.
|
For low-RAM systems, note that this repository currently uses `lto = "fat"` in release profile.
|
||||||
|
On constrained builders, a local override to `lto = "thin"` may be more practical.
|
||||||
|
|
||||||
## 3. Install binary and config
|
## 3. Install binary and config
|
||||||
|
|
||||||
@@ -1,3 +1,46 @@
|
|||||||
|
# Installation Options
|
||||||
|
There are three options for installing Telemt:
|
||||||
|
- [Automated installation using a script](#very-quick-start).
|
||||||
|
- [Manual installation of Telemt as a service](#telemt-via-systemd).
|
||||||
|
- [Installation using Docker Compose](#telemt-via-docker-compose).
|
||||||
|
|
||||||
|
# Very quick start
|
||||||
|
|
||||||
|
### One-command installation / update on re-run
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/telemt/telemt/main/install.sh | sh
|
||||||
|
```
|
||||||
|
|
||||||
|
After starting, the script will prompt for:
|
||||||
|
- Your language (1 - English, 2 - Russian);
|
||||||
|
- Your TLS domain (press Enter for petrovich.ru).
|
||||||
|
|
||||||
|
The script checks if the port (default **443**) is free. If the port is already in use, installation will fail. You need to free up the port or use the **-p** flag with a different port to retry the installation.
|
||||||
|
|
||||||
|
To modify the script’s startup parameters, you can use the following flags:
|
||||||
|
- **-d, --domain** - TLS domain;
|
||||||
|
- **-p, --port** - server port (1–65535);
|
||||||
|
- **-s, --secret** - 32 hex secret;
|
||||||
|
- **-a, --ad-tag** - ad_tag;
|
||||||
|
- **-l, --lan**g - language (1/en or 2/ru);
|
||||||
|
|
||||||
|
Providing all options skips interactive prompts.
|
||||||
|
|
||||||
|
After completion, the script will provide a link for client connections:
|
||||||
|
```bash
|
||||||
|
tg://proxy?server=IP&port=PORT&secret=SECRET
|
||||||
|
```
|
||||||
|
|
||||||
|
### Installing a specific version
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/telemt/telemt/main/install.sh | sh -s -- 3.3.39
|
||||||
|
```
|
||||||
|
|
||||||
|
### Uninstall with full cleanup
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/telemt/telemt/main/install.sh | sh -s -- purge
|
||||||
|
```
|
||||||
|
|
||||||
# Telemt via Systemd
|
# Telemt via Systemd
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
@@ -62,28 +105,60 @@ nano /etc/telemt/telemt.toml
|
|||||||
Insert your configuration:
|
Insert your configuration:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
|
### Telemt Based Config.toml
|
||||||
|
# We believe that these settings are sufficient for most scenarios
|
||||||
|
# where cutting-egde methods and parameters or special solutions are not needed
|
||||||
|
|
||||||
# === General Settings ===
|
# === General Settings ===
|
||||||
[general]
|
[general]
|
||||||
|
use_middle_proxy = true
|
||||||
|
# Global ad_tag fallback when user has no per-user tag in [access.user_ad_tags]
|
||||||
# ad_tag = "00000000000000000000000000000000"
|
# ad_tag = "00000000000000000000000000000000"
|
||||||
use_middle_proxy = false
|
# Per-user ad_tag in [access.user_ad_tags] (32 hex from @MTProxybot)
|
||||||
|
|
||||||
|
# === Log Level ===
|
||||||
|
# Log level: debug | verbose | normal | silent
|
||||||
|
# Can be overridden with --silent or --log-level CLI flags
|
||||||
|
# RUST_LOG env var takes absolute priority over all of these
|
||||||
|
log_level = "normal"
|
||||||
|
|
||||||
[general.modes]
|
[general.modes]
|
||||||
classic = false
|
classic = false
|
||||||
secure = false
|
secure = false
|
||||||
tls = true
|
tls = true
|
||||||
|
|
||||||
|
[general.links]
|
||||||
|
show = "*"
|
||||||
|
# show = ["alice", "bob"] # Only show links for alice and bob
|
||||||
|
# show = "*" # Show links for all users
|
||||||
|
# public_host = "proxy.example.com" # Host (IP or domain) for tg:// links
|
||||||
|
# public_port = 443 # Port for tg:// links (default: server.port)
|
||||||
|
|
||||||
|
# === Server Binding ===
|
||||||
[server]
|
[server]
|
||||||
port = 443
|
port = 443
|
||||||
|
# proxy_protocol = false # Enable if behind HAProxy/nginx with PROXY protocol
|
||||||
|
# metrics_port = 9090
|
||||||
|
# metrics_listen = "127.0.0.1:9090" # Listen address for metrics (overrides metrics_port)
|
||||||
|
# metrics_whitelist = ["127.0.0.1/32", "::1/128"]
|
||||||
|
|
||||||
[server.api]
|
[server.api]
|
||||||
enabled = true
|
enabled = true
|
||||||
# listen = "127.0.0.1:9091"
|
listen = "127.0.0.1:9091"
|
||||||
# whitelist = ["127.0.0.1/32"]
|
whitelist = ["127.0.0.1/32", "::1/128"]
|
||||||
# read_only = true
|
minimal_runtime_enabled = false
|
||||||
|
minimal_runtime_cache_ttl_ms = 1000
|
||||||
|
|
||||||
|
# Listen on multiple interfaces/IPs - IPv4
|
||||||
|
[[server.listeners]]
|
||||||
|
ip = "0.0.0.0"
|
||||||
|
|
||||||
# === Anti-Censorship & Masking ===
|
# === Anti-Censorship & Masking ===
|
||||||
[censorship]
|
[censorship]
|
||||||
tls_domain = "petrovich.ru"
|
tls_domain = "petrovich.ru" # Fake-TLS / SNI masking domain used in generated ee-links
|
||||||
|
mask = true
|
||||||
|
tls_emulation = true # Fetch real cert lengths and emulate TLS records
|
||||||
|
tls_front_dir = "tlsfront" # Cache directory for TLS emulation
|
||||||
|
|
||||||
[access.users]
|
[access.users]
|
||||||
# format: "username" = "32_hex_chars_secret"
|
# format: "username" = "32_hex_chars_secret"
|
||||||
@@ -93,9 +168,9 @@ hello = "00000000000000000000000000000000"
|
|||||||
then Ctrl+S -> Ctrl+X to save
|
then Ctrl+S -> Ctrl+X to save
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> Replace the value of the hello parameter with the value you obtained in step 0.
|
> Replace the value of the `hello` parameter with the value you obtained in step 0.
|
||||||
> Additionally, change the value of the tls_domain parameter to a different website.
|
> Additionally, change the value of the `tls_domain` parameter to a different website.
|
||||||
> Changing the tls_domain parameter will break all links that use the old domain!
|
> Changing the `tls_domain` parameter will break all links that use the old domain!
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -1,4 +1,46 @@
|
|||||||
# Telemt через Systemd
|
# Варианты установки
|
||||||
|
Имеется три варианта установки Telemt:
|
||||||
|
- [Автоматизированная установка с помощью скрипта](#очень-быстрый-старт).
|
||||||
|
- [Ручная установка Telemt в качестве службы](#telemt-через-systemd-вручную).
|
||||||
|
- [Установка через Docker Compose](#telemt-через-docker-compose).
|
||||||
|
|
||||||
|
# Очень быстрый старт
|
||||||
|
|
||||||
|
### Установка одной командой / обновление при повторном запуске
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/telemt/telemt/main/install.sh | sh
|
||||||
|
```
|
||||||
|
После запуска скрипт запросит:
|
||||||
|
- ваш язык (1 - English, 2 - Русский);
|
||||||
|
- ваш TLS-домен (нажмите Enter для petrovich.ru).
|
||||||
|
|
||||||
|
Во время установки скрипт проверяет, свободен ли порт (по умолчанию **443**). Если порт занят другим процессом - установка завершится с ошибкой. Для повторной установки необходимо освободить порт или указать другой через флаг **-p**.
|
||||||
|
|
||||||
|
Для изменения параметров запуска скрипта можно использовать следующие флаги:
|
||||||
|
- **-d, --domain** - TLS-домен;
|
||||||
|
- **-p, --port** - порт (1–65535);
|
||||||
|
- **-s, --secret** - секрет (32 hex символа);
|
||||||
|
- **-a, --ad-tag** - ad_tag;
|
||||||
|
- **-l, --lang** - язык (1/en или 2/ru).
|
||||||
|
|
||||||
|
Если заданы флаги для языка и домена, интерактивных вопросов не будет.
|
||||||
|
|
||||||
|
После завершения установки скрипт выдаст ссылку для подключения клиентов:
|
||||||
|
```bash
|
||||||
|
tg://proxy?server=IP&port=PORT&secret=SECRET
|
||||||
|
```
|
||||||
|
|
||||||
|
### Установка нужной версии
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/telemt/telemt/main/install.sh | sh -s -- 3.3.39
|
||||||
|
```
|
||||||
|
|
||||||
|
### Удаление с полной очисткой
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/telemt/telemt/main/install.sh | sh -s -- purge
|
||||||
|
```
|
||||||
|
|
||||||
|
# Telemt через Systemd вручную
|
||||||
|
|
||||||
## Установка
|
## Установка
|
||||||
|
|
||||||
@@ -62,40 +104,72 @@ nano /etc/telemt/telemt.toml
|
|||||||
Вставьте свою конфигурацию
|
Вставьте свою конфигурацию
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
# === General Settings ===
|
### Конфигурационный файл на основе Telemt
|
||||||
|
# Мы полагаем, что этих настроек достаточно для большинства сценариев,
|
||||||
|
# где не требуются передовые методы, параметры или специальные решения
|
||||||
|
|
||||||
|
# === Общие настройки ===
|
||||||
[general]
|
[general]
|
||||||
|
use_middle_proxy = true
|
||||||
|
# Глобальный ad_tag, если у пользователя нет индивидуального тега в [access.user_ad_tags]
|
||||||
# ad_tag = "00000000000000000000000000000000"
|
# ad_tag = "00000000000000000000000000000000"
|
||||||
use_middle_proxy = false
|
# Индивидуальный ad_tag в [access.user_ad_tags] (32 шестнадцатеричных символа от @MTProxybot)
|
||||||
|
|
||||||
|
# === Уровень логирования ===
|
||||||
|
# Уровень логирования: debug | verbose | normal | silent
|
||||||
|
# Можно переопределить с помощью флагов командной строки --silent или --log-level
|
||||||
|
# Переменная окружения RUST_LOG имеет абсолютный приоритет над всеми этими настройками
|
||||||
|
log_level = "normal"
|
||||||
|
|
||||||
[general.modes]
|
[general.modes]
|
||||||
classic = false
|
classic = false
|
||||||
secure = false
|
secure = false
|
||||||
tls = true
|
tls = true
|
||||||
|
|
||||||
|
[general.links]
|
||||||
|
show = "*"
|
||||||
|
# show = ["alice", "bob"] # Показывать ссылки только для alice и bob
|
||||||
|
# show = "*" # Показывать ссылки для всех пользователей
|
||||||
|
# public_host = "proxy.example.com" # Хост (IP-адрес или домен) для ссылок tg://
|
||||||
|
# public_port = 443 # Порт для ссылок tg:// (по умолчанию: server.port)
|
||||||
|
|
||||||
|
# === Привязка сервера ===
|
||||||
[server]
|
[server]
|
||||||
port = 443
|
port = 443
|
||||||
|
# proxy_protocol = false # Включите, если сервер находится за HAProxy/nginx с протоколом PROXY
|
||||||
|
# metrics_port = 9090
|
||||||
|
# metrics_listen = "127.0.0.1:9090" # Адрес прослушивания для метрик (переопределяет metrics_port)
|
||||||
|
# metrics_whitelist = ["127.0.0.1/32", "::1/128"]
|
||||||
|
|
||||||
[server.api]
|
[server.api]
|
||||||
enabled = true
|
enabled = true
|
||||||
# listen = "127.0.0.1:9091"
|
listen = "127.0.0.1:9091"
|
||||||
# whitelist = ["127.0.0.1/32"]
|
whitelist = ["127.0.0.1/32", "::1/128"]
|
||||||
# read_only = true
|
minimal_runtime_enabled = false
|
||||||
|
minimal_runtime_cache_ttl_ms = 1000
|
||||||
|
|
||||||
# === Anti-Censorship & Masking ===
|
# Прослушивание на нескольких интерфейсах/IP-адресах - IPv4
|
||||||
|
[[server.listeners]]
|
||||||
|
ip = "0.0.0.0"
|
||||||
|
|
||||||
|
# === Обход блокировок и маскировка ===
|
||||||
[censorship]
|
[censorship]
|
||||||
tls_domain = "petrovich.ru"
|
tls_domain = "petrovich.ru" # Домен Fake-TLS / SNI, который будет использоваться в сгенерированных ee-ссылках
|
||||||
|
mask = true
|
||||||
|
tls_emulation = true # Получить реальную длину сертификата и эмулировать запись TLS
|
||||||
|
tls_front_dir = "tlsfront" # Директория кэша для эмуляции TLS
|
||||||
|
|
||||||
[access.users]
|
[access.users]
|
||||||
# format: "username" = "32_hex_chars_secret"
|
# формат: "имя_пользователя" = "секрет_из_32_шестнадцатеричных_символов"
|
||||||
hello = "00000000000000000000000000000000"
|
hello = "00000000000000000000000000000000"
|
||||||
```
|
```
|
||||||
|
|
||||||
Затем нажмите Ctrl+S -> Ctrl+X, чтобы сохранить
|
Затем нажмите Ctrl+S -> Ctrl+X, чтобы сохранить
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> Замените значение параметра hello на значение, которое вы получили в пункте 0.
|
> Замените значение параметра `hello` на значение, которое вы получили в пункте 0.
|
||||||
> Так же замените значение параметра tls_domain на другой сайт.
|
> Так же замените значение параметра `tls_domain` на другой сайт.
|
||||||
> Изменение параметра tls_domain сделает нерабочими все ссылки, использующие старый домен!
|
> Изменение параметра `tls_domain` сделает нерабочими все ссылки, использующие старый домен!
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ PING 10.10.10.1 (10.10.10.1) 56(84) bytes of data.
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Step 2. Installing telemt on Server B (conditionally Netherlands)
|
## Step 2. Installing telemt on Server B (conditionally Netherlands)
|
||||||
Installation and configuration are described [here](https://github.com/telemt/telemt/blob/main/docs/QUICK_START_GUIDE.ru.md) or [here](https://gitlab.com/An0nX/telemt-docker#-quick-start-docker-compose).\
|
Installation and configuration are described [here](https://github.com/telemt/telemt/blob/main/docs/Quick_start/QUICK_START_GUIDE.en.md) or [here](https://gitlab.com/An0nX/telemt-docker#-quick-start-docker-compose).\
|
||||||
It is assumed that telemt expects connections on port `443\tcp`.
|
It is assumed that telemt expects connections on port `443\tcp`.
|
||||||
|
|
||||||
In the telemt config, you must enable the `Proxy` protocol and restrict connections to it only through the tunnel.
|
In the telemt config, you must enable the `Proxy` protocol and restrict connections to it only through the tunnel.
|
||||||
@@ -166,7 +166,7 @@ PING 10.10.10.1 (10.10.10.1) 56(84) bytes of data.
|
|||||||
|
|
||||||
## Шаг 2. Установка telemt на Сервере B (_условно Нидерланды_)
|
## Шаг 2. Установка telemt на Сервере B (_условно Нидерланды_)
|
||||||
|
|
||||||
Установка и настройка описаны [здесь](https://github.com/telemt/telemt/blob/main/docs/QUICK_START_GUIDE.ru.md) или [здесь](https://gitlab.com/An0nX/telemt-docker#-quick-start-docker-compose).\
|
Установка и настройка описаны [здесь](https://github.com/telemt/telemt/blob/main/docs/Quick_start/QUICK_START_GUIDE.ru.md) или [здесь](https://gitlab.com/An0nX/telemt-docker#-quick-start-docker-compose).\
|
||||||
Подразумевается что telemt ожидает подключения на порту `443\tcp`.
|
Подразумевается что telemt ожидает подключения на порту `443\tcp`.
|
||||||
|
|
||||||
В конфиге telemt необходимо включить протокол `Proxy` и ограничить подключения к нему только через туннель.
|
В конфиге telemt необходимо включить протокол `Proxy` и ограничить подключения к нему только через туннель.
|
||||||
273
docs/Setup_examples/XRAY_DOUBLE_HOP.en.md
Normal file
273
docs/Setup_examples/XRAY_DOUBLE_HOP.en.md
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
<img src="https://gist.githubusercontent.com/avbor/1f8a128e628f47249aae6e058a57610b/raw/19013276c035e91058e0a9799ab145f8e70e3ff5/scheme.svg">
|
||||||
|
|
||||||
|
## Concept
|
||||||
|
- **Server A** (_e.g., RU_):\
|
||||||
|
Entry point, accepts Telegram proxy user traffic via **Xray** (port `443\tcp`)\
|
||||||
|
and sends it through the tunnel to Server **B**.\
|
||||||
|
Public port for Telegram clients — `443\tcp`
|
||||||
|
- **Server B** (_e.g., NL_):\
|
||||||
|
Exit point, runs the **Xray server** (to terminate the tunnel entry point) and **telemt**.\
|
||||||
|
The server must have unrestricted access to Telegram Data Centers.\
|
||||||
|
Public port for VLESS/REALITY (incoming) — `443\tcp`\
|
||||||
|
Internal telemt port (where decrypted Xray traffic ends up) — `8443\tcp`
|
||||||
|
|
||||||
|
The tunnel works over the `VLESS-XTLS-Reality` (or `VLESS/xhttp/reality`) protocol. The original client IP address is preserved thanks to the PROXYv2 protocol, which Xray on Server A dynamically injects via a local loopback before wrapping the traffic into Reality, transparently delivering the real IPs to telemt on Server B.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1. Setup Xray Tunnel (A <-> B)
|
||||||
|
|
||||||
|
You must install **Xray-core** (version 1.8.4 or newer recommended) on both servers.
|
||||||
|
Official installation script (run on both servers):
|
||||||
|
```bash
|
||||||
|
bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key and Parameter Generation (Run Once)
|
||||||
|
For configuration, you need a unique UUID and Xray Reality keys. Run on any server with Xray installed:
|
||||||
|
1. **Client UUID:**
|
||||||
|
```bash
|
||||||
|
xray uuid
|
||||||
|
# Save the output (e.g.: 12345678-abcd-1234-abcd-1234567890ab) — this is <XRAY_UUID>
|
||||||
|
```
|
||||||
|
2. **X25519 Keypair (Private & Public) for Reality:**
|
||||||
|
```bash
|
||||||
|
xray x25519
|
||||||
|
# Save the Private key (<SERVER_B_PRIVATE_KEY>) and Public key (<SERVER_B_PUBLIC_KEY>)
|
||||||
|
```
|
||||||
|
3. **Short ID (Reality identifier):**
|
||||||
|
```bash
|
||||||
|
openssl rand -hex 16
|
||||||
|
# Save the output (e.g.: 0123456789abcdef0123456789abcdef) — this is <SHORT_ID>
|
||||||
|
```
|
||||||
|
4. **Random Path (for xhttp):**
|
||||||
|
```bash
|
||||||
|
openssl rand -hex 8
|
||||||
|
# Save the output (e.g., abc123def456) to replace <YOUR_RANDOM_PATH> in configs
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Configuration for Server B (_EU_):
|
||||||
|
|
||||||
|
Create or edit the file `/usr/local/etc/xray/config.json`.
|
||||||
|
This Xray instance will listen on the public `443` port and proxy valid Reality traffic, while routing "disguised" traffic (e.g., direct web browser scans) to `yahoo.com`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nano /usr/local/etc/xray/config.json
|
||||||
|
```
|
||||||
|
|
||||||
|
File content:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"loglevel": "error",
|
||||||
|
"access": "none"
|
||||||
|
},
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"tag": "vless-in",
|
||||||
|
"port": 443,
|
||||||
|
"protocol": "vless",
|
||||||
|
"settings": {
|
||||||
|
"clients": [
|
||||||
|
{
|
||||||
|
"id": "<XRAY_UUID>"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"decryption": "none"
|
||||||
|
},
|
||||||
|
"streamSettings": {
|
||||||
|
"network": "xhttp",
|
||||||
|
"security": "reality",
|
||||||
|
"realitySettings": {
|
||||||
|
"dest": "yahoo.com:443",
|
||||||
|
"serverNames": [
|
||||||
|
"yahoo.com"
|
||||||
|
],
|
||||||
|
"privateKey": "<SERVER_B_PRIVATE_KEY>",
|
||||||
|
"shortIds": [
|
||||||
|
"<SHORT_ID>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"xhttpSettings": {
|
||||||
|
"path": "/<YOUR_RANDOM_PATH>",
|
||||||
|
"mode": "auto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"tag": "tunnel-to-telemt",
|
||||||
|
"protocol": "freedom",
|
||||||
|
"settings": {
|
||||||
|
"destination": "127.0.0.1:8443"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"routing": {
|
||||||
|
"domainStrategy": "AsIs",
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"inboundTag": [
|
||||||
|
"vless-in"
|
||||||
|
],
|
||||||
|
"outboundTag": "tunnel-to-telemt"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Open the firewall port (if enabled):
|
||||||
|
```bash
|
||||||
|
sudo ufw allow 443/tcp
|
||||||
|
```
|
||||||
|
Restart and setup Xray to run at boot:
|
||||||
|
```bash
|
||||||
|
sudo systemctl restart xray
|
||||||
|
sudo systemctl enable xray
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Configuration for Server A (_RU_):
|
||||||
|
|
||||||
|
Similarly, edit `/usr/local/etc/xray/config.json`.
|
||||||
|
Here Xray acts as the public entry point: it listens on `443\tcp`, uses a local loopback (via internal port `10444`) to prepend the `PROXYv2` header, and encapsulates the payload via Reality to Server B, instructing Server B to deliver it to its *local* `127.0.0.1:8443` port (where telemt will listen).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nano /usr/local/etc/xray/config.json
|
||||||
|
```
|
||||||
|
|
||||||
|
File content:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"loglevel": "error",
|
||||||
|
"access": "none"
|
||||||
|
},
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"tag": "public-in",
|
||||||
|
"port": 443,
|
||||||
|
"listen": "0.0.0.0",
|
||||||
|
"protocol": "dokodemo-door",
|
||||||
|
"settings": {
|
||||||
|
"address": "127.0.0.1",
|
||||||
|
"port": 10444,
|
||||||
|
"network": "tcp"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "tunnel-in",
|
||||||
|
"port": 10444,
|
||||||
|
"listen": "127.0.0.1",
|
||||||
|
"protocol": "dokodemo-door",
|
||||||
|
"settings": {
|
||||||
|
"address": "127.0.0.1",
|
||||||
|
"port": 8443,
|
||||||
|
"network": "tcp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"tag": "local-injector",
|
||||||
|
"protocol": "freedom",
|
||||||
|
"settings": {
|
||||||
|
"proxyProtocol": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "vless-out",
|
||||||
|
"protocol": "vless",
|
||||||
|
"settings": {
|
||||||
|
"vnext": [
|
||||||
|
{
|
||||||
|
"address": "<PUBLIC_IP_SERVER_B>",
|
||||||
|
"port": 443,
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"id": "<XRAY_UUID>",
|
||||||
|
"encryption": "none"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"streamSettings": {
|
||||||
|
"network": "xhttp",
|
||||||
|
"security": "reality",
|
||||||
|
"realitySettings": {
|
||||||
|
"serverName": "yahoo.com",
|
||||||
|
"publicKey": "<SERVER_B_PUBLIC_KEY>",
|
||||||
|
"shortId": "<SHORT_ID>",
|
||||||
|
"spiderX": "/",
|
||||||
|
"fingerprint": "chrome"
|
||||||
|
},
|
||||||
|
"xhttpSettings": {
|
||||||
|
"path": "/<YOUR_RANDOM_PATH>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"routing": {
|
||||||
|
"domainStrategy": "AsIs",
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"inboundTag": ["public-in"],
|
||||||
|
"outboundTag": "local-injector"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"inboundTag": ["tunnel-in"],
|
||||||
|
"outboundTag": "vless-out"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*Replace `<PUBLIC_IP_SERVER_B>` with the public IP address of Server B.*
|
||||||
|
|
||||||
|
Open the firewall port for clients (if enabled):
|
||||||
|
```bash
|
||||||
|
sudo ufw allow 443/tcp
|
||||||
|
```
|
||||||
|
|
||||||
|
Restart and setup Xray to run at boot:
|
||||||
|
```bash
|
||||||
|
sudo systemctl restart xray
|
||||||
|
sudo systemctl enable xray
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2. Install telemt on Server B (_EU_)
|
||||||
|
|
||||||
|
telemt installation is heavily covered in the [Quick Start Guide](../Quick_start/QUICK_START_GUIDE.en.md).
|
||||||
|
By contrast to standard setups, telemt must listen strictly _locally_ (since Xray occupies the public `443` interface) and must expect `PROXYv2` packets.
|
||||||
|
|
||||||
|
Edit the configuration file (`config.toml`) on Server B accordingly:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[server]
|
||||||
|
port = 8443
|
||||||
|
listen_addr_ipv4 = "127.0.0.1"
|
||||||
|
proxy_protocol = true
|
||||||
|
|
||||||
|
[general.links]
|
||||||
|
show = "*"
|
||||||
|
public_host = "<FQDN_OR_IP_SERVER_A>"
|
||||||
|
public_port = 443
|
||||||
|
```
|
||||||
|
|
||||||
|
- Address `127.0.0.1` and `port = 8443` instructs the core proxy router to process connections unpacked locally via Xray-server.
|
||||||
|
- `proxy_protocol = true` commands telemt to parse the injected PROXY header (from Server A's Xray local loopback) and log genuine end-user IPs.
|
||||||
|
- Under `public_host`, place Server A's public IP address or FQDN to ensure working links are generated for Telegram users.
|
||||||
|
|
||||||
|
Restart `telemt`. Your server is now robust against DPI scanners, passing traffic optimally.
|
||||||
|
|
||||||
272
docs/Setup_examples/XRAY_DOUBLE_HOP.ru.md
Normal file
272
docs/Setup_examples/XRAY_DOUBLE_HOP.ru.md
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
<img src="https://gist.githubusercontent.com/avbor/1f8a128e628f47249aae6e058a57610b/raw/19013276c035e91058e0a9799ab145f8e70e3ff5/scheme.svg">
|
||||||
|
|
||||||
|
## Концепция
|
||||||
|
- **Сервер A** (_РФ_):\
|
||||||
|
Точка входа, принимает трафик пользователей Telegram-прокси напрямую через **Xray** (порт `443\tcp`)\
|
||||||
|
и отправляет его в туннель на Сервер **B**.\
|
||||||
|
Порт для клиентов Telegram — `443\tcp`
|
||||||
|
- **Сервер B** (_условно Нидерланды_):\
|
||||||
|
Точка выхода, на нем работает **Xray-сервер** (принимает подключения точки входа) и **telemt**.\
|
||||||
|
На сервере должен быть неограниченный доступ до серверов Telegram.\
|
||||||
|
Порт для VLESS/REALITY (вход) — `443\tcp`\
|
||||||
|
Внутренний порт telemt (куда пробрасывается трафик) — `8443\tcp`
|
||||||
|
|
||||||
|
Туннель работает по протоколу VLESS-XTLS-Reality (или VLESS/xhttp/reality). Оригинальный IP-адрес клиента сохраняется благодаря протоколу PROXYv2, который Xray на Сервере А добавляет через локальный loopback перед упаковкой в туннель, благодаря чему прозрачно доходит до telemt.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Шаг 1. Настройка туннеля Xray (A <-> B)
|
||||||
|
|
||||||
|
На обоих серверах необходимо установить **Xray-core** (рекомендуется версия 1.8.4 или новее).
|
||||||
|
Официальный скрипт установки (выполнить на обоих серверах):
|
||||||
|
```bash
|
||||||
|
bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Генерация ключей и параметров (выполнить один раз)
|
||||||
|
Для конфигурации потребуются уникальные ID и ключи Xray Reality. Выполните на любом сервере с установленным Xray:
|
||||||
|
1. **UUID клиента:**
|
||||||
|
```bash
|
||||||
|
xray uuid
|
||||||
|
# Сохраните вывод (например: 12345678-abcd-1234-abcd-1234567890ab) — это <XRAY_UUID>
|
||||||
|
```
|
||||||
|
2. **Пара ключей X25519 (Private & Public) для Reality:**
|
||||||
|
```bash
|
||||||
|
xray x25519
|
||||||
|
# Сохраните Private key (<SERVER_B_PRIVATE_KEY>) и Public key (<SERVER_B_PUBLIC_KEY>)
|
||||||
|
```
|
||||||
|
3. **Short ID (идентификатор Reality):**
|
||||||
|
```bash
|
||||||
|
openssl rand -hex 16
|
||||||
|
# Сохраните вывод (например: 0123456789abcdef0123456789abcdef) — это <SHORT_ID>
|
||||||
|
```
|
||||||
|
4. **Random Path (путь для xhttp):**
|
||||||
|
```bash
|
||||||
|
openssl rand -hex 8
|
||||||
|
# Сохраните вывод (например, abc123def456), чтобы заменить <YOUR_RANDOM_PATH> в конфигах
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Конфигурация Сервера B (_Нидерланды_):
|
||||||
|
|
||||||
|
Создаем или редактируем файл `/usr/local/etc/xray/config.json`.
|
||||||
|
Этот Xray-сервер будет слушать порт `443` и прозрачно пропускать валидный Reality трафик дальше, а "замаскированный" трафик (например, если кто-то стучится в лоб веб-браузером) пойдет на `yahoo.com`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nano /usr/local/etc/xray/config.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Содержимое файла:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"loglevel": "error",
|
||||||
|
"access": "none"
|
||||||
|
},
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"tag": "vless-in",
|
||||||
|
"port": 443,
|
||||||
|
"protocol": "vless",
|
||||||
|
"settings": {
|
||||||
|
"clients": [
|
||||||
|
{
|
||||||
|
"id": "<XRAY_UUID>"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"decryption": "none"
|
||||||
|
},
|
||||||
|
"streamSettings": {
|
||||||
|
"network": "xhttp",
|
||||||
|
"security": "reality",
|
||||||
|
"realitySettings": {
|
||||||
|
"dest": "yahoo.com:443",
|
||||||
|
"serverNames": [
|
||||||
|
"yahoo.com"
|
||||||
|
],
|
||||||
|
"privateKey": "<SERVER_B_PRIVATE_KEY>",
|
||||||
|
"shortIds": [
|
||||||
|
"<SHORT_ID>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"xhttpSettings": {
|
||||||
|
"path": "/<YOUR_RANDOM_PATH>",
|
||||||
|
"mode": "auto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"tag": "tunnel-to-telemt",
|
||||||
|
"protocol": "freedom",
|
||||||
|
"settings": {
|
||||||
|
"destination": "127.0.0.1:8443"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"routing": {
|
||||||
|
"domainStrategy": "AsIs",
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"inboundTag": [
|
||||||
|
"vless-in"
|
||||||
|
],
|
||||||
|
"outboundTag": "tunnel-to-telemt"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Открываем порт на фаерволе (если включен):
|
||||||
|
```bash
|
||||||
|
sudo ufw allow 443/tcp
|
||||||
|
```
|
||||||
|
Перезапускаем Xray:
|
||||||
|
```bash
|
||||||
|
sudo systemctl restart xray
|
||||||
|
sudo systemctl enable xray
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Конфигурация Сервера A (_РФ_):
|
||||||
|
|
||||||
|
Аналогично, редактируем `/usr/local/etc/xray/config.json`.
|
||||||
|
Здесь Xray выступает публичной точкой: он принимает трафик на внешний порт `443\tcp`, пропускает через локальный loopback (порт `10444`) для добавления PROXYv2-заголовка, и упаковывает в Reality до Сервера B, прося тот доставить данные на *свой локальный* порт `127.0.0.1:8443` (именно там будет слушать telemt).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nano /usr/local/etc/xray/config.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Содержимое файла:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"loglevel": "error",
|
||||||
|
"access": "none"
|
||||||
|
},
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"tag": "public-in",
|
||||||
|
"port": 443,
|
||||||
|
"listen": "0.0.0.0",
|
||||||
|
"protocol": "dokodemo-door",
|
||||||
|
"settings": {
|
||||||
|
"address": "127.0.0.1",
|
||||||
|
"port": 10444,
|
||||||
|
"network": "tcp"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "tunnel-in",
|
||||||
|
"port": 10444,
|
||||||
|
"listen": "127.0.0.1",
|
||||||
|
"protocol": "dokodemo-door",
|
||||||
|
"settings": {
|
||||||
|
"address": "127.0.0.1",
|
||||||
|
"port": 8443,
|
||||||
|
"network": "tcp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"tag": "local-injector",
|
||||||
|
"protocol": "freedom",
|
||||||
|
"settings": {
|
||||||
|
"proxyProtocol": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "vless-out",
|
||||||
|
"protocol": "vless",
|
||||||
|
"settings": {
|
||||||
|
"vnext": [
|
||||||
|
{
|
||||||
|
"address": "<PUBLIC_IP_SERVER_B>",
|
||||||
|
"port": 443,
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"id": "<XRAY_UUID>",
|
||||||
|
"encryption": "none"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"streamSettings": {
|
||||||
|
"network": "xhttp",
|
||||||
|
"security": "reality",
|
||||||
|
"realitySettings": {
|
||||||
|
"serverName": "yahoo.com",
|
||||||
|
"publicKey": "<SERVER_B_PUBLIC_KEY>",
|
||||||
|
"shortId": "<SHORT_ID>",
|
||||||
|
"spiderX": "/",
|
||||||
|
"fingerprint": "chrome"
|
||||||
|
},
|
||||||
|
"xhttpSettings": {
|
||||||
|
"path": "/<YOUR_RANDOM_PATH>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"routing": {
|
||||||
|
"domainStrategy": "AsIs",
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"inboundTag": ["public-in"],
|
||||||
|
"outboundTag": "local-injector"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"inboundTag": ["tunnel-in"],
|
||||||
|
"outboundTag": "vless-out"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*Замените `<PUBLIC_IP_SERVER_B>` на внешний IP-адрес Сервера B.*
|
||||||
|
|
||||||
|
Открываем порт на фаерволе для клиентов:
|
||||||
|
```bash
|
||||||
|
sudo ufw allow 443/tcp
|
||||||
|
```
|
||||||
|
|
||||||
|
Перезапускаем Xray:
|
||||||
|
```bash
|
||||||
|
sudo systemctl restart xray
|
||||||
|
sudo systemctl enable xray
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Шаг 2. Установка и настройка telemt на Сервере B (_Нидерланды_)
|
||||||
|
|
||||||
|
Установка telemt описана [в основной инструкции](../Quick_start/QUICK_START_GUIDE.ru.md).
|
||||||
|
Отличие в том, что telemt должен слушать *внутренний* порт (так как 443 занят Xray-сервером), а также ожидать `PROXY` протокол из Xray туннеля.
|
||||||
|
|
||||||
|
В конфиге `config.toml` прокси (на Сервере B) укажите:
|
||||||
|
```toml
|
||||||
|
[server]
|
||||||
|
port = 8443
|
||||||
|
listen_addr_ipv4 = "127.0.0.1"
|
||||||
|
proxy_protocol = true
|
||||||
|
|
||||||
|
[general.links]
|
||||||
|
show = "*"
|
||||||
|
public_host = "<FQDN_OR_IP_SERVER_A>"
|
||||||
|
public_port = 443
|
||||||
|
```
|
||||||
|
|
||||||
|
- `port = 8443` и `listen_addr_ipv4 = "127.0.0.1"` означают, что telemt принимает подключения только изнутри (приходящие от локального Xray-процесса).
|
||||||
|
- `proxy_protocol = true` заставляет telemt парсить PROXYv2-заголовок (который добавил Xray на Сервере A через loopback), восстанавливая IP-адрес конечного пользователя (РФ).
|
||||||
|
- В `public_host` укажите публичный IP-адрес или домен Сервера A, чтобы ссылки на подключение генерировались корректно.
|
||||||
|
|
||||||
|
Перезапустите `telemt`, и клиенты смогут подключаться по выданным ссылкам.
|
||||||
|
|
||||||
1
docs/assets/telegram_button.svg
Normal file
1
docs/assets/telegram_button.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 150 30" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><path d="M150,15c0,8.279 -6.721,15 -15,15l-120,0c-8.279,0 -15,-6.721 -15,-15c0,-8.279 6.721,-15 15,-15l120,0c8.279,0 15,6.721 15,15Z" style="fill:#24a1ed;"/><g transform="matrix(20.833333,0,0,20.833333,111.464184,22.329305)"></g><text x="39.666px" y="22.329px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:20.833px;fill:#fff;">Join us!</text></svg>
|
||||||
|
After Width: | Height: | Size: 804 B |
BIN
docs/assets/telemt.png
Normal file
BIN
docs/assets/telemt.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 161 KiB |
482
install.sh
482
install.sh
@@ -21,47 +21,212 @@ PORT_PROVIDED=0
|
|||||||
SECRET_PROVIDED=0
|
SECRET_PROVIDED=0
|
||||||
AD_TAG_PROVIDED=0
|
AD_TAG_PROVIDED=0
|
||||||
DOMAIN_PROVIDED=0
|
DOMAIN_PROVIDED=0
|
||||||
|
LANG_PROVIDED=0
|
||||||
|
|
||||||
ACTION="install"
|
ACTION="install"
|
||||||
TARGET_VERSION="${VERSION:-latest}"
|
TARGET_VERSION="${VERSION:-latest}"
|
||||||
|
LANG_CHOICE="en"
|
||||||
|
|
||||||
|
set_language() {
|
||||||
|
case "$1" in
|
||||||
|
ru)
|
||||||
|
L_ERR_DOMAIN_REQ="требует аргумент (домен)."
|
||||||
|
L_ERR_PORT_REQ="требует аргумент (порт)."
|
||||||
|
L_ERR_PORT_NUM="Порт должен быть числом."
|
||||||
|
L_ERR_PORT_RANGE="Порт должен быть от 1 до 65535."
|
||||||
|
L_ERR_SECRET_REQ="требует аргумент (секрет)."
|
||||||
|
L_ERR_SECRET_HEX="Секрет должен содержать только HEX символы."
|
||||||
|
L_ERR_SECRET_LEN="Секрет должен состоять ровно из 32 символов."
|
||||||
|
L_ERR_ADTAG_REQ="требует аргумент (ad_tag)."
|
||||||
|
L_ERR_UNKNOWN_OPT="Неизвестная опция:"
|
||||||
|
L_WARN_EXTRA_ARG="Игнорируется лишний аргумент:"
|
||||||
|
L_ERR_REQ_ARG="требует аргумент (1, 2, en или ru)."
|
||||||
|
L_ERR_EMPTY_VAR="не может быть пустым."
|
||||||
|
L_ERR_INV_VER="Недопустимые символы в версии."
|
||||||
|
L_ERR_INV_BIN="Недопустимые символы в BIN_NAME."
|
||||||
|
L_ERR_ROOT="Для работы скрипта требуются права root или sudo."
|
||||||
|
L_ERR_SUDO_TTY="sudo требует пароль, но терминал (TTY) не обнаружен."
|
||||||
|
L_ERR_DIR_CHECK="Ошибка: конфиг является директорией."
|
||||||
|
L_ERR_CMD_NOT_FOUND="Необходимая команда не найдена:"
|
||||||
|
L_ERR_NO_DL_TOOL="Не установлен curl или wget."
|
||||||
|
L_ERR_NO_CP_TOOL="Необходима утилита cp или install."
|
||||||
|
L_WARN_NO_NET_TOOL="Утилиты сети не найдены. Проверка порта пропущена."
|
||||||
|
L_INFO_PORT_IGNORE="Порт занят текущим процессом телеметрии. Игнорируем."
|
||||||
|
L_ERR_PORT_IN_USE="Порт уже занят другим процессом:"
|
||||||
|
L_ERR_PORT_FREE="Освободите порт или укажите другой и попробуйте снова."
|
||||||
|
L_ERR_UNSUP_ARCH="Неподдерживаемая архитектура:"
|
||||||
|
L_ERR_CREATE_GRP="Не удалось создать группу"
|
||||||
|
L_ERR_CREATE_USR="Не удалось создать пользователя"
|
||||||
|
L_ERR_MKDIR="Не удалось создать директории"
|
||||||
|
L_ERR_INSTALL_DIR="не является директорией."
|
||||||
|
L_ERR_BIN_INSTALL="Не удалось установить бинарный файл"
|
||||||
|
L_ERR_BIN_COPY="Не удалось скопировать бинарный файл"
|
||||||
|
L_ERR_BIN_EXEC="Бинарный файл не исполняемый."
|
||||||
|
L_ERR_GEN_SEC="Не удалось сгенерировать секрет."
|
||||||
|
L_INFO_CONF_EXISTS="Конфиг уже существует. Обновление параметров..."
|
||||||
|
L_INFO_UPD_PORT="Обновлен порт:"
|
||||||
|
L_INFO_UPD_SEC="Обновлен секрет для пользователя 'hello'"
|
||||||
|
L_INFO_UPD_DOM="Обновлен tls_domain:"
|
||||||
|
L_INFO_UPD_TAG="Обновлен ad_tag"
|
||||||
|
L_ERR_CONF_INST="Не удалось установить конфиг"
|
||||||
|
L_INFO_CONF_OK="Конфиг успешно создан."
|
||||||
|
L_INFO_CONF_SEC="Настроен секрет для пользователя 'hello':"
|
||||||
|
L_WARN_SVC_FAIL="Не удалось запустить службу"
|
||||||
|
L_INFO_MANUAL_START="Менеджер служб не найден. Запустите вручную:"
|
||||||
|
L_INFO_UNINST_START="Начинается удаление"
|
||||||
|
L_U_STAGE_1=">>> Этап 1: Остановка служб"
|
||||||
|
L_U_STAGE_2=">>> Этап 2: Удаление конфигурации службы"
|
||||||
|
L_U_STAGE_3=">>> Этап 3: Завершение процессов пользователя"
|
||||||
|
L_U_STAGE_4=">>> Этап 4: Удаление бинарного файла"
|
||||||
|
L_U_STAGE_5=">>> Этап 5: Полная очистка (конфиг, данные, пользователь)"
|
||||||
|
L_INFO_KEEP_CONF="Примечание: Конфигурация сохранена. Используйте 'purge' для очистки."
|
||||||
|
L_INFO_I_START="Начинается установка"
|
||||||
|
L_I_STAGE_1=">>> Этап 1: Проверка окружения и зависимостей"
|
||||||
|
L_I_STAGE_1_5=">>> Этап 1.5: Интерактивная настройка"
|
||||||
|
L_I_PROMPT_DOM="\nПожалуйста, укажите домен TLS\nНажмите Enter, чтобы оставить по умолчанию [%s]: "
|
||||||
|
L_WARN_NO_TTY="Интерактивный режим недоступен (нет TTY). Используется:"
|
||||||
|
L_I_STAGE_2=">>> Этап 2: Загрузка архива"
|
||||||
|
L_ERR_TMP_DIR="Не удалось создать временную директорию"
|
||||||
|
L_ERR_TMP_INV="Временная директория недействительна"
|
||||||
|
L_INFO_FALLBACK="Сборка x86_64-v3 не найдена, откат к стандартной x86_64..."
|
||||||
|
L_ERR_DL_FAIL="Ошибка загрузки архива"
|
||||||
|
L_I_STAGE_3=">>> Этап 3: Распаковка архива"
|
||||||
|
L_ERR_EXTRACT="Ошибка распаковки архива."
|
||||||
|
L_ERR_BIN_NOT_FOUND="Бинарный файл не найден в архиве"
|
||||||
|
L_I_STAGE_4=">>> Этап 4: Настройка окружения (Юзер, Группа, Папки)"
|
||||||
|
L_I_STAGE_5=">>> Этап 5: Установка бинарного файла"
|
||||||
|
L_I_STAGE_6=">>> Этап 6: Генерация/Обновление конфигурации"
|
||||||
|
L_I_STAGE_7=">>> Этап 7: Установка и запуск службы"
|
||||||
|
L_OUT_WARN_H="УСТАНОВКА ЗАВЕРШЕНА С ПРЕДУПРЕЖДЕНИЯМИ"
|
||||||
|
L_OUT_WARN_D="Служба установлена, но не запустилась.\nПожалуйста, проверьте логи.\n"
|
||||||
|
L_OUT_SUCC_H="УСТАНОВКА УСПЕШНО ЗАВЕРШЕНА"
|
||||||
|
L_OUT_UNINST_H="УДАЛЕНИЕ ЗАВЕРШЕНО"
|
||||||
|
L_OUT_LINK="Ваша ссылка для подключения к Telegram Proxy:\n"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
L_ERR_DOMAIN_REQ="requires a domain argument."
|
||||||
|
L_ERR_PORT_REQ="requires a port argument."
|
||||||
|
L_ERR_PORT_NUM="Port must be a valid number."
|
||||||
|
L_ERR_PORT_RANGE="Port must be between 1 and 65535."
|
||||||
|
L_ERR_SECRET_REQ="requires a secret argument."
|
||||||
|
L_ERR_SECRET_HEX="Secret must contain only hex characters."
|
||||||
|
L_ERR_SECRET_LEN="Secret must be exactly 32 chars."
|
||||||
|
L_ERR_ADTAG_REQ="requires an ad_tag argument."
|
||||||
|
L_ERR_UNKNOWN_OPT="Unknown option:"
|
||||||
|
L_WARN_EXTRA_ARG="Ignoring extra argument:"
|
||||||
|
L_ERR_REQ_ARG="requires an argument (1, 2, en, ru)."
|
||||||
|
L_ERR_EMPTY_VAR="cannot be empty."
|
||||||
|
L_ERR_INV_VER="Invalid characters in version."
|
||||||
|
L_ERR_INV_BIN="Invalid characters in BIN_NAME."
|
||||||
|
L_ERR_ROOT="This script requires root or sudo."
|
||||||
|
L_ERR_SUDO_TTY="sudo requires a password, but no TTY detected."
|
||||||
|
L_ERR_DIR_CHECK="Safety check failed: Config is a directory."
|
||||||
|
L_ERR_CMD_NOT_FOUND="Required command not found:"
|
||||||
|
L_ERR_NO_DL_TOOL="Neither curl nor wget is installed."
|
||||||
|
L_ERR_NO_CP_TOOL="Need cp or install."
|
||||||
|
L_WARN_NO_NET_TOOL="Network tools not found. Skipping port check."
|
||||||
|
L_INFO_PORT_IGNORE="Port is in use by telemt. Ignoring as it will be restarted."
|
||||||
|
L_ERR_PORT_IN_USE="Port is already in use by another process:"
|
||||||
|
L_ERR_PORT_FREE="Please free the port or change it and try again."
|
||||||
|
L_ERR_UNSUP_ARCH="Unsupported architecture:"
|
||||||
|
L_ERR_CREATE_GRP="Cannot create group"
|
||||||
|
L_ERR_CREATE_USR="Cannot create user"
|
||||||
|
L_ERR_MKDIR="Failed to create directories"
|
||||||
|
L_ERR_INSTALL_DIR="is not a directory."
|
||||||
|
L_ERR_BIN_INSTALL="Failed to install binary"
|
||||||
|
L_ERR_BIN_COPY="Failed to copy binary"
|
||||||
|
L_ERR_BIN_EXEC="Binary not executable."
|
||||||
|
L_ERR_GEN_SEC="Failed to generate secret."
|
||||||
|
L_INFO_CONF_EXISTS="Config already exists. Updating parameters..."
|
||||||
|
L_INFO_UPD_PORT="Updated port:"
|
||||||
|
L_INFO_UPD_SEC="Updated secret for user 'hello'"
|
||||||
|
L_INFO_UPD_DOM="Updated tls_domain:"
|
||||||
|
L_INFO_UPD_TAG="Updated ad_tag"
|
||||||
|
L_ERR_CONF_INST="Failed to install config"
|
||||||
|
L_INFO_CONF_OK="Config created successfully."
|
||||||
|
L_INFO_CONF_SEC="Configured secret for user 'hello':"
|
||||||
|
L_WARN_SVC_FAIL="Failed to start service"
|
||||||
|
L_INFO_MANUAL_START="Service manager not found. Start manually:"
|
||||||
|
L_INFO_UNINST_START="Starting uninstallation of"
|
||||||
|
L_U_STAGE_1=">>> Stage 1: Stopping services"
|
||||||
|
L_U_STAGE_2=">>> Stage 2: Removing service configuration"
|
||||||
|
L_U_STAGE_3=">>> Stage 3: Terminating user processes"
|
||||||
|
L_U_STAGE_4=">>> Stage 4: Removing binary"
|
||||||
|
L_U_STAGE_5=">>> Stage 5: Purging configuration, data, and user"
|
||||||
|
L_INFO_KEEP_CONF="Note: Configuration kept. Run with 'purge' to remove completely."
|
||||||
|
L_INFO_I_START="Starting installation of"
|
||||||
|
L_I_STAGE_1=">>> Stage 1: Verifying environment and dependencies"
|
||||||
|
L_I_STAGE_1_5=">>> Stage 1.5: Interactive Setup"
|
||||||
|
L_I_PROMPT_DOM="\nPlease specify the TLS Domain\nPress Enter to keep default [%s]: "
|
||||||
|
L_WARN_NO_TTY="Interactive mode unavailable (no TTY). Using:"
|
||||||
|
L_I_STAGE_2=">>> Stage 2: Downloading archive"
|
||||||
|
L_ERR_TMP_DIR="Temp directory creation failed"
|
||||||
|
L_ERR_TMP_INV="Temp directory is invalid or was not created"
|
||||||
|
L_INFO_FALLBACK="x86_64-v3 build not found, falling back to standard x86_64..."
|
||||||
|
L_ERR_DL_FAIL="Download failed"
|
||||||
|
L_I_STAGE_3=">>> Stage 3: Extracting archive"
|
||||||
|
L_ERR_EXTRACT="Extraction failed."
|
||||||
|
L_ERR_BIN_NOT_FOUND="Binary not found in archive"
|
||||||
|
L_I_STAGE_4=">>> Stage 4: Setting up environment (User, Group, Directories)"
|
||||||
|
L_I_STAGE_5=">>> Stage 5: Installing binary"
|
||||||
|
L_I_STAGE_6=">>> Stage 6: Generating/Updating configuration"
|
||||||
|
L_I_STAGE_7=">>> Stage 7: Installing and starting service"
|
||||||
|
L_OUT_WARN_H="INSTALLATION COMPLETED WITH WARNINGS"
|
||||||
|
L_OUT_WARN_D="The service was installed but failed to start.\nPlease check the logs to determine the issue.\n"
|
||||||
|
L_OUT_SUCC_H="INSTALLATION SUCCESS"
|
||||||
|
L_OUT_UNINST_H="UNINSTALLATION COMPLETE"
|
||||||
|
L_OUT_LINK="Your Telegram Proxy connection link:\n"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
set_language "$LANG_CHOICE"
|
||||||
|
|
||||||
while [ $# -gt 0 ]; do
|
while [ $# -gt 0 ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
-h|--help) ACTION="help"; shift ;;
|
-h|--help) ACTION="help"; shift ;;
|
||||||
|
-l|--lang)
|
||||||
|
if [ "$#" -lt 2 ] || [ -z "$2" ]; then
|
||||||
|
printf '[ERROR] %s %s\n' "$1" "$L_ERR_REQ_ARG" >&2; exit 1
|
||||||
|
fi
|
||||||
|
case "$2" in
|
||||||
|
ru|2) LANG_CHOICE="ru"; set_language "$LANG_CHOICE"; LANG_PROVIDED=1 ;;
|
||||||
|
en|1) LANG_CHOICE="en"; set_language "$LANG_CHOICE"; LANG_PROVIDED=1 ;;
|
||||||
|
*) printf '[ERROR] %s %s\n' "$1" "$L_ERR_REQ_ARG" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
shift 2 ;;
|
||||||
-d|--domain)
|
-d|--domain)
|
||||||
if [ "$#" -lt 2 ] || [ -z "$2" ]; then
|
if [ "$#" -lt 2 ] || [ -z "$2" ]; then
|
||||||
printf '[ERROR] %s requires a domain argument.\n' "$1" >&2
|
printf '[ERROR] %s %s\n' "$1" "$L_ERR_DOMAIN_REQ" >&2; exit 1
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
TLS_DOMAIN="$2"; DOMAIN_PROVIDED=1; shift 2 ;;
|
TLS_DOMAIN="$2"; DOMAIN_PROVIDED=1; shift 2 ;;
|
||||||
-p|--port)
|
-p|--port)
|
||||||
if [ "$#" -lt 2 ] || [ -z "$2" ]; then
|
if [ "$#" -lt 2 ] || [ -z "$2" ]; then
|
||||||
printf '[ERROR] %s requires a port argument.\n' "$1" >&2; exit 1
|
printf '[ERROR] %s %s\n' "$1" "$L_ERR_PORT_REQ" >&2; exit 1
|
||||||
fi
|
fi
|
||||||
case "$2" in
|
case "$2" in
|
||||||
*[!0-9]*) printf '[ERROR] Port must be a valid number.\n' >&2; exit 1 ;;
|
*[!0-9]*) printf '[ERROR] %s\n' "$L_ERR_PORT_NUM" >&2; exit 1 ;;
|
||||||
esac
|
esac
|
||||||
port_num="$(printf '%s\n' "$2" | sed 's/^0*//')"
|
port_num="$(printf '%s\n' "$2" | sed 's/^0*//')"
|
||||||
[ -z "$port_num" ] && port_num="0"
|
[ -z "$port_num" ] && port_num="0"
|
||||||
if [ "${#port_num}" -gt 5 ] || [ "$port_num" -lt 1 ] || [ "$port_num" -gt 65535 ]; then
|
if [ "${#port_num}" -gt 5 ] || [ "$port_num" -lt 1 ] || [ "$port_num" -gt 65535 ]; then
|
||||||
printf '[ERROR] Port must be between 1 and 65535.\n' >&2; exit 1
|
printf '[ERROR] %s\n' "$L_ERR_PORT_RANGE" >&2; exit 1
|
||||||
fi
|
fi
|
||||||
SERVER_PORT="$port_num"; PORT_PROVIDED=1; shift 2 ;;
|
SERVER_PORT="$port_num"; PORT_PROVIDED=1; shift 2 ;;
|
||||||
-s|--secret)
|
-s|--secret)
|
||||||
if [ "$#" -lt 2 ] || [ -z "$2" ]; then
|
if [ "$#" -lt 2 ] || [ -z "$2" ]; then
|
||||||
printf '[ERROR] %s requires a secret argument.\n' "$1" >&2; exit 1
|
printf '[ERROR] %s %s\n' "$1" "$L_ERR_SECRET_REQ" >&2; exit 1
|
||||||
fi
|
fi
|
||||||
case "$2" in
|
case "$2" in
|
||||||
*[!0-9a-fA-F]*)
|
*[!0-9a-fA-F]*) printf '[ERROR] %s\n' "$L_ERR_SECRET_HEX" >&2; exit 1 ;;
|
||||||
printf '[ERROR] Secret must contain only hex characters.\n' >&2; exit 1 ;;
|
|
||||||
esac
|
esac
|
||||||
if [ "${#2}" -ne 32 ]; then
|
if [ "${#2}" -ne 32 ]; then
|
||||||
printf '[ERROR] Secret must be exactly 32 chars.\n' >&2; exit 1
|
printf '[ERROR] %s\n' "$L_ERR_SECRET_LEN" >&2; exit 1
|
||||||
fi
|
fi
|
||||||
USER_SECRET="$2"; SECRET_PROVIDED=1; shift 2 ;;
|
USER_SECRET="$2"; SECRET_PROVIDED=1; shift 2 ;;
|
||||||
-a|--ad-tag|--ad_tag)
|
-a|--ad-tag|--ad_tag)
|
||||||
if [ "$#" -lt 2 ] || [ -z "$2" ]; then
|
if [ "$#" -lt 2 ] || [ -z "$2" ]; then
|
||||||
printf '[ERROR] %s requires an ad_tag argument.\n' "$1" >&2; exit 1
|
printf '[ERROR] %s %s\n' "$1" "$L_ERR_ADTAG_REQ" >&2; exit 1
|
||||||
fi
|
fi
|
||||||
AD_TAG="$2"; AD_TAG_PROVIDED=1; shift 2 ;;
|
AD_TAG="$2"; AD_TAG_PROVIDED=1; shift 2 ;;
|
||||||
uninstall|--uninstall)
|
uninstall|--uninstall)
|
||||||
@@ -69,14 +234,31 @@ while [ $# -gt 0 ]; do
|
|||||||
shift ;;
|
shift ;;
|
||||||
purge|--purge) ACTION="purge"; shift ;;
|
purge|--purge) ACTION="purge"; shift ;;
|
||||||
install|--install) ACTION="install"; shift ;;
|
install|--install) ACTION="install"; shift ;;
|
||||||
-*) printf '[ERROR] Unknown option: %s\n' "$1" >&2; exit 1 ;;
|
-*) printf '[ERROR] %s %s\n' "$L_ERR_UNKNOWN_OPT" "$1" >&2; exit 1 ;;
|
||||||
*)
|
*)
|
||||||
if [ "$ACTION" = "install" ]; then TARGET_VERSION="$1"
|
if [ "$ACTION" = "install" ]; then TARGET_VERSION="$1"
|
||||||
else printf '[WARNING] Ignoring extra argument: %s\n' "$1" >&2; fi
|
else printf '[WARNING] %s %s\n' "$L_WARN_EXTRA_ARG" "$1" >&2; fi
|
||||||
shift ;;
|
shift ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
if [ "$ACTION" != "help" ] && [ "$LANG_PROVIDED" -eq 0 ]; then
|
||||||
|
if [ -t 0 ] || [ -c /dev/tty ]; then
|
||||||
|
printf "\nSelect language / Выберите язык:\n"
|
||||||
|
printf " 1) English (default)\n"
|
||||||
|
printf " 2) Русский\n"
|
||||||
|
printf "Your choice / Ваш выбор [1/2]: "
|
||||||
|
read -r input_lang </dev/tty || input_lang=""
|
||||||
|
case "$input_lang" in
|
||||||
|
2) LANG_CHOICE="ru" ;;
|
||||||
|
*) LANG_CHOICE="en" ;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
LANG_CHOICE="en"
|
||||||
|
fi
|
||||||
|
set_language "$LANG_CHOICE"
|
||||||
|
fi
|
||||||
|
|
||||||
say() {
|
say() {
|
||||||
if [ "$#" -eq 0 ] || [ -z "${1:-}" ]; then
|
if [ "$#" -eq 0 ] || [ -z "${1:-}" ]; then
|
||||||
printf '\n'
|
printf '\n'
|
||||||
@@ -96,17 +278,33 @@ cleanup() {
|
|||||||
trap cleanup EXIT INT TERM
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
show_help() {
|
show_help() {
|
||||||
say "Usage: $0 [ <version> | install | uninstall | purge ] [ options ]"
|
if [ "$LANG_CHOICE" = "ru" ]; then
|
||||||
say " <version> Install specific version (e.g. 3.3.15, default: latest)"
|
say "Использование: $0 [ <версия> | install | uninstall | purge ] [ опции ]"
|
||||||
say " install Install the latest version"
|
say " <версия> Установить конкретную версию (например, 3.3.15, по умолчанию: latest)"
|
||||||
say " uninstall Remove the binary and service"
|
say " install Установить последнюю версию"
|
||||||
say " purge Remove everything including configuration, data, and user"
|
say " uninstall Удалить бинарный файл и службу"
|
||||||
say ""
|
say " purge Полностью удалить вместе с конфигурацией, данными и пользователем"
|
||||||
say "Options:"
|
say ""
|
||||||
say " -d, --domain Set TLS domain (default: petrovich.ru)"
|
say "Опции:"
|
||||||
say " -p, --port Set server port (default: 443)"
|
say " -d, --domain Указать домен TLS (по умолчанию: petrovich.ru)"
|
||||||
say " -s, --secret Set specific user secret (32 hex characters)"
|
say " -p, --port Указать порт сервера (по умолчанию: 443)"
|
||||||
say " -a, --ad-tag Set ad_tag"
|
say " -s, --secret Указать секрет пользователя (32 hex символа)"
|
||||||
|
say " -a, --ad-tag Указать ad_tag"
|
||||||
|
say " -l, --lang Выбрать язык вывода (1/en или 2/ru)"
|
||||||
|
else
|
||||||
|
say "Usage: $0 [ <version> | install | uninstall | purge ] [ options ]"
|
||||||
|
say " <version> Install specific version (e.g. 3.3.15, default: latest)"
|
||||||
|
say " install Install the latest version"
|
||||||
|
say " uninstall Remove the binary and service"
|
||||||
|
say " purge Remove everything including configuration, data, and user"
|
||||||
|
say ""
|
||||||
|
say "Options:"
|
||||||
|
say " -d, --domain Set TLS domain (default: petrovich.ru)"
|
||||||
|
say " -p, --port Set server port (default: 443)"
|
||||||
|
say " -s, --secret Set specific user secret (32 hex characters)"
|
||||||
|
say " -a, --ad-tag Set ad_tag"
|
||||||
|
say " -l, --lang Set output language (1/en or 2/ru)"
|
||||||
|
fi
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,17 +369,13 @@ is_config_exists() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
verify_common() {
|
verify_common() {
|
||||||
[ -n "$BIN_NAME" ] || die "BIN_NAME cannot be empty."
|
[ -n "$BIN_NAME" ] || die "BIN_NAME $L_ERR_EMPTY_VAR"
|
||||||
[ -n "$INSTALL_DIR" ] || die "INSTALL_DIR cannot be empty."
|
[ -n "$INSTALL_DIR" ] || die "INSTALL_DIR $L_ERR_EMPTY_VAR"
|
||||||
[ -n "$CONFIG_DIR" ] || die "CONFIG_DIR cannot be empty."
|
[ -n "$CONFIG_DIR" ] || die "CONFIG_DIR $L_ERR_EMPTY_VAR"
|
||||||
[ -n "$CONFIG_FILE" ] || die "CONFIG_FILE cannot be empty."
|
[ -n "$CONFIG_FILE" ] || die "CONFIG_FILE $L_ERR_EMPTY_VAR"
|
||||||
|
|
||||||
case "${INSTALL_DIR}${CONFIG_DIR}${WORK_DIR}${CONFIG_FILE}" in
|
case "$TARGET_VERSION" in *[!a-zA-Z0-9_.-]*) die "$L_ERR_INV_VER" ;; esac
|
||||||
*[!a-zA-Z0-9_./-]*) die "Invalid characters in paths." ;;
|
case "$BIN_NAME" in *[!a-zA-Z0-9_-]*) die "$L_ERR_INV_BIN" ;; esac
|
||||||
esac
|
|
||||||
|
|
||||||
case "$TARGET_VERSION" in *[!a-zA-Z0-9_.-]*) die "Invalid characters in version." ;; esac
|
|
||||||
case "$BIN_NAME" in *[!a-zA-Z0-9_-]*) die "Invalid characters in BIN_NAME." ;; esac
|
|
||||||
|
|
||||||
INSTALL_DIR="$(get_realpath "$INSTALL_DIR")"
|
INSTALL_DIR="$(get_realpath "$INSTALL_DIR")"
|
||||||
CONFIG_DIR="$(get_realpath "$CONFIG_DIR")"
|
CONFIG_DIR="$(get_realpath "$CONFIG_DIR")"
|
||||||
@@ -195,42 +389,42 @@ verify_common() {
|
|||||||
if [ "$(id -u)" -eq 0 ]; then
|
if [ "$(id -u)" -eq 0 ]; then
|
||||||
SUDO=""
|
SUDO=""
|
||||||
else
|
else
|
||||||
command -v sudo >/dev/null 2>&1 || die "This script requires root or sudo."
|
command -v sudo >/dev/null 2>&1 || die "$L_ERR_ROOT"
|
||||||
SUDO="sudo"
|
SUDO="sudo"
|
||||||
if ! sudo -n true 2>/dev/null; then
|
if ! sudo -n true 2>/dev/null; then
|
||||||
if ! [ -t 0 ]; then
|
if ! [ -t 0 ]; then
|
||||||
die "sudo requires a password, but no TTY detected."
|
die "$L_ERR_SUDO_TTY"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -n "$SUDO" ]; then
|
if [ -n "$SUDO" ]; then
|
||||||
if $SUDO sh -c '[ -d "$1" ]' _ "$CONFIG_FILE"; then
|
if $SUDO sh -c '[ -d "$1" ]' _ "$CONFIG_FILE"; then
|
||||||
die "Safety check failed: CONFIG_FILE '$CONFIG_FILE' is a directory."
|
die "$L_ERR_DIR_CHECK"
|
||||||
fi
|
fi
|
||||||
elif [ -d "$CONFIG_FILE" ]; then
|
elif [ -d "$CONFIG_FILE" ]; then
|
||||||
die "Safety check failed: CONFIG_FILE '$CONFIG_FILE' is a directory."
|
die "$L_ERR_DIR_CHECK"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for cmd in id uname awk grep find rm chown chmod mv mktemp mkdir tr dd sed ps head sleep cat tar gzip; do
|
for cmd in id uname awk grep find rm chown chmod mv mktemp mkdir tr dd sed ps head sleep cat tar gzip; do
|
||||||
command -v "$cmd" >/dev/null 2>&1 || die "Required command not found: $cmd"
|
command -v "$cmd" >/dev/null 2>&1 || die "$L_ERR_CMD_NOT_FOUND $cmd"
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
verify_install_deps() {
|
verify_install_deps() {
|
||||||
command -v curl >/dev/null 2>&1 || command -v wget >/dev/null 2>&1 || die "Neither curl nor wget is installed."
|
command -v curl >/dev/null 2>&1 || command -v wget >/dev/null 2>&1 || die "$L_ERR_NO_DL_TOOL"
|
||||||
command -v cp >/dev/null 2>&1 || command -v install >/dev/null 2>&1 || die "Need cp or install"
|
command -v cp >/dev/null 2>&1 || command -v install >/dev/null 2>&1 || die "$L_ERR_NO_CP_TOOL"
|
||||||
|
|
||||||
if ! command -v setcap >/dev/null 2>&1 || ! command -v conntrack >/dev/null 2>&1; then
|
if ! command -v setcap >/dev/null 2>&1; then
|
||||||
if command -v apk >/dev/null 2>&1; then
|
if command -v apk >/dev/null 2>&1; then
|
||||||
$SUDO apk add --no-cache libcap-utils libcap conntrack-tools >/dev/null 2>&1 || true
|
$SUDO apk add --no-cache libcap-utils libcap >/dev/null 2>&1 || true
|
||||||
elif command -v apt-get >/dev/null 2>&1; then
|
elif command -v apt-get >/dev/null 2>&1; then
|
||||||
$SUDO env DEBIAN_FRONTEND=noninteractive apt-get install -y -q libcap2-bin conntrack >/dev/null 2>&1 || {
|
$SUDO env DEBIAN_FRONTEND=noninteractive apt-get install -y -q libcap2-bin >/dev/null 2>&1 || {
|
||||||
$SUDO env DEBIAN_FRONTEND=noninteractive apt-get update -q >/dev/null 2>&1 || true
|
$SUDO env DEBIAN_FRONTEND=noninteractive apt-get update -q >/dev/null 2>&1 || true
|
||||||
$SUDO env DEBIAN_FRONTEND=noninteractive apt-get install -y -q libcap2-bin conntrack >/dev/null 2>&1 || true
|
$SUDO env DEBIAN_FRONTEND=noninteractive apt-get install -y -q libcap2-bin >/dev/null 2>&1 || true
|
||||||
}
|
}
|
||||||
elif command -v dnf >/dev/null 2>&1; then $SUDO dnf install -y -q libcap conntrack-tools >/dev/null 2>&1 || true
|
elif command -v dnf >/dev/null 2>&1; then $SUDO dnf install -y -q libcap >/dev/null 2>&1 || true
|
||||||
elif command -v yum >/dev/null 2>&1; then $SUDO yum install -y -q libcap conntrack-tools >/dev/null 2>&1 || true
|
elif command -v yum >/dev/null 2>&1; then $SUDO yum install -y -q libcap >/dev/null 2>&1 || true
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -245,17 +439,17 @@ check_port_availability() {
|
|||||||
elif command -v lsof >/dev/null 2>&1; then
|
elif command -v lsof >/dev/null 2>&1; then
|
||||||
port_info=$($SUDO lsof -i :${SERVER_PORT} 2>/dev/null | grep LISTEN || true)
|
port_info=$($SUDO lsof -i :${SERVER_PORT} 2>/dev/null | grep LISTEN || true)
|
||||||
else
|
else
|
||||||
say "[WARNING] Network diagnostic tools (ss, netstat, lsof) not found. Skipping port check."
|
say "[WARNING] $L_WARN_NO_NET_TOOL"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -n "$port_info" ]; then
|
if [ -n "$port_info" ]; then
|
||||||
if printf '%s\n' "$port_info" | grep -q "${BIN_NAME}"; then
|
if printf '%s\n' "$port_info" | grep -q "${BIN_NAME}"; then
|
||||||
say " -> Port ${SERVER_PORT} is in use by ${BIN_NAME}. Ignoring as it will be restarted."
|
say " -> $L_INFO_PORT_IGNORE"
|
||||||
else
|
else
|
||||||
say "[ERROR] Port ${SERVER_PORT} is already in use by another process:"
|
say "[ERROR] $L_ERR_PORT_IN_USE $SERVER_PORT:"
|
||||||
printf ' %s\n' "$port_info"
|
printf ' %s\n' "$port_info"
|
||||||
die "Please free the port ${SERVER_PORT} or change it and try again."
|
die "$L_ERR_PORT_FREE"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -271,7 +465,7 @@ detect_arch() {
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
aarch64|arm64) echo "aarch64" ;;
|
aarch64|arm64) echo "aarch64" ;;
|
||||||
*) die "Unsupported architecture: $sys_arch" ;;
|
*) die "$L_ERR_UNSUP_ARCH $sys_arch" ;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,7 +489,7 @@ ensure_user_group() {
|
|||||||
if ! check_os_entity group telemt; then
|
if ! check_os_entity group telemt; then
|
||||||
if command -v groupadd >/dev/null 2>&1; then $SUDO groupadd -r telemt
|
if command -v groupadd >/dev/null 2>&1; then $SUDO groupadd -r telemt
|
||||||
elif command -v addgroup >/dev/null 2>&1; then $SUDO addgroup -S telemt
|
elif command -v addgroup >/dev/null 2>&1; then $SUDO addgroup -S telemt
|
||||||
else die "Cannot create group"; fi
|
else die "$L_ERR_CREATE_GRP" ; fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! check_os_entity passwd telemt; then
|
if ! check_os_entity passwd telemt; then
|
||||||
@@ -307,12 +501,12 @@ ensure_user_group() {
|
|||||||
else
|
else
|
||||||
$SUDO adduser --system --home "$WORK_DIR" --shell "$nologin_bin" --no-create-home --ingroup telemt --disabled-password telemt
|
$SUDO adduser --system --home "$WORK_DIR" --shell "$nologin_bin" --no-create-home --ingroup telemt --disabled-password telemt
|
||||||
fi
|
fi
|
||||||
else die "Cannot create user"; fi
|
else die "$L_ERR_CREATE_USR"; fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
setup_dirs() {
|
setup_dirs() {
|
||||||
$SUDO mkdir -p "$WORK_DIR" "$CONFIG_DIR" "$CONFIG_PARENT_DIR" || die "Failed to create directories"
|
$SUDO mkdir -p "$WORK_DIR" "$CONFIG_DIR" "$CONFIG_PARENT_DIR" || die "$L_ERR_MKDIR"
|
||||||
|
|
||||||
$SUDO chown telemt:telemt "$WORK_DIR" && $SUDO chmod 750 "$WORK_DIR"
|
$SUDO chown telemt:telemt "$WORK_DIR" && $SUDO chmod 750 "$WORK_DIR"
|
||||||
$SUDO chown telemt:telemt "$CONFIG_DIR" && $SUDO chmod 750 "$CONFIG_DIR"
|
$SUDO chown telemt:telemt "$CONFIG_DIR" && $SUDO chmod 750 "$CONFIG_DIR"
|
||||||
@@ -334,20 +528,20 @@ stop_service() {
|
|||||||
install_binary() {
|
install_binary() {
|
||||||
bin_src="$1"; bin_dst="$2"
|
bin_src="$1"; bin_dst="$2"
|
||||||
if [ -e "$INSTALL_DIR" ] && [ ! -d "$INSTALL_DIR" ]; then
|
if [ -e "$INSTALL_DIR" ] && [ ! -d "$INSTALL_DIR" ]; then
|
||||||
die "'$INSTALL_DIR' is not a directory."
|
die "'$INSTALL_DIR' $L_ERR_INSTALL_DIR"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
$SUDO mkdir -p "$INSTALL_DIR" || die "Failed to create install directory"
|
$SUDO mkdir -p "$INSTALL_DIR" || die "$L_ERR_MKDIR"
|
||||||
|
|
||||||
$SUDO rm -f "$bin_dst" 2>/dev/null || true
|
$SUDO rm -f "$bin_dst" 2>/dev/null || true
|
||||||
|
|
||||||
if command -v install >/dev/null 2>&1; then
|
if command -v install >/dev/null 2>&1; then
|
||||||
$SUDO install -m 0755 "$bin_src" "$bin_dst" || die "Failed to install binary"
|
$SUDO install -m 0755 "$bin_src" "$bin_dst" || die "$L_ERR_BIN_INSTALL"
|
||||||
else
|
else
|
||||||
$SUDO cp "$bin_src" "$bin_dst" && $SUDO chmod 0755 "$bin_dst" || die "Failed to copy binary"
|
$SUDO cp "$bin_src" "$bin_dst" && $SUDO chmod 0755 "$bin_dst" || die "$L_ERR_BIN_COPY"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
$SUDO sh -c '[ -x "$1" ]' _ "$bin_dst" || die "Binary not executable: $bin_dst"
|
$SUDO sh -c '[ -x "$1" ]' _ "$bin_dst" || die "$L_ERR_BIN_EXEC $bin_dst"
|
||||||
|
|
||||||
if command -v setcap >/dev/null 2>&1; then
|
if command -v setcap >/dev/null 2>&1; then
|
||||||
$SUDO setcap cap_net_bind_service,cap_net_admin=+ep "$bin_dst" 2>/dev/null || true
|
$SUDO setcap cap_net_bind_service,cap_net_admin=+ep "$bin_dst" 2>/dev/null || true
|
||||||
@@ -404,40 +598,32 @@ EOF
|
|||||||
|
|
||||||
install_config() {
|
install_config() {
|
||||||
if is_config_exists; then
|
if is_config_exists; then
|
||||||
say " -> Config already exists at $CONFIG_FILE. Updating parameters..."
|
say " -> $L_INFO_CONF_EXISTS"
|
||||||
|
|
||||||
tmp_conf="${TEMP_DIR}/config.tmp"
|
tmp_conf="${TEMP_DIR}/config.tmp"
|
||||||
$SUDO cat "$CONFIG_FILE" > "$tmp_conf"
|
$SUDO cat "$CONFIG_FILE" > "$tmp_conf"
|
||||||
|
|
||||||
escaped_domain="$(printf '%s\n' "$TLS_DOMAIN" | tr -d '[:cntrl:]' | sed 's/\\/\\\\/g; s/"/\\"/g')"
|
escaped_domain="$(printf '%s\n' "$TLS_DOMAIN" | tr -d '[:cntrl:]' | sed 's/\\/\\\\/g; s/"/\\"/g')"
|
||||||
|
|
||||||
export AWK_PORT="$SERVER_PORT"
|
awk -v port="$SERVER_PORT" -v secret="$USER_SECRET" -v domain="$escaped_domain" -v ad_tag="$AD_TAG" \
|
||||||
export AWK_SECRET="$USER_SECRET"
|
-v flag_p="$PORT_PROVIDED" -v flag_s="$SECRET_PROVIDED" -v flag_d="$DOMAIN_PROVIDED" -v flag_a="$AD_TAG_PROVIDED" '
|
||||||
export AWK_DOMAIN="$escaped_domain"
|
|
||||||
export AWK_AD_TAG="$AD_TAG"
|
|
||||||
export AWK_FLAG_P="$PORT_PROVIDED"
|
|
||||||
export AWK_FLAG_S="$SECRET_PROVIDED"
|
|
||||||
export AWK_FLAG_D="$DOMAIN_PROVIDED"
|
|
||||||
export AWK_FLAG_A="$AD_TAG_PROVIDED"
|
|
||||||
|
|
||||||
awk '
|
|
||||||
BEGIN { ad_tag_handled = 0 }
|
BEGIN { ad_tag_handled = 0 }
|
||||||
|
|
||||||
ENVIRON["AWK_FLAG_P"] == "1" && /^[ \t]*port[ \t]*=/ { print "port = " ENVIRON["AWK_PORT"]; next }
|
flag_p == "1" && /^[ \t]*port[ \t]*=/ { print "port = " port; next }
|
||||||
ENVIRON["AWK_FLAG_S"] == "1" && /^[ \t]*hello[ \t]*=/ { print "hello = \"" ENVIRON["AWK_SECRET"] "\""; next }
|
flag_s == "1" && /^[ \t]*hello[ \t]*=/ { print "hello = \"" secret "\""; next }
|
||||||
ENVIRON["AWK_FLAG_D"] == "1" && /^[ \t]*tls_domain[ \t]*=/ { print "tls_domain = \"" ENVIRON["AWK_DOMAIN"] "\""; next }
|
flag_d == "1" && /^[ \t]*tls_domain[ \t]*=/ { print "tls_domain = \"" domain "\""; next }
|
||||||
|
|
||||||
ENVIRON["AWK_FLAG_A"] == "1" && /^[ \t]*ad_tag[ \t]*=/ {
|
flag_a == "1" && /^[ \t]*ad_tag[ \t]*=/ {
|
||||||
if (!ad_tag_handled) {
|
if (!ad_tag_handled) {
|
||||||
print "ad_tag = \"" ENVIRON["AWK_AD_TAG"] "\"";
|
print "ad_tag = \"" ad_tag "\"";
|
||||||
ad_tag_handled = 1;
|
ad_tag_handled = 1;
|
||||||
}
|
}
|
||||||
next
|
next
|
||||||
}
|
}
|
||||||
ENVIRON["AWK_FLAG_A"] == "1" && /^\[general\]/ {
|
flag_a == "1" && /^\[general\]/ {
|
||||||
print;
|
print;
|
||||||
if (!ad_tag_handled) {
|
if (!ad_tag_handled) {
|
||||||
print "ad_tag = \"" ENVIRON["AWK_AD_TAG"] "\"";
|
print "ad_tag = \"" ad_tag "\"";
|
||||||
ad_tag_handled = 1;
|
ad_tag_handled = 1;
|
||||||
}
|
}
|
||||||
next
|
next
|
||||||
@@ -446,10 +632,10 @@ install_config() {
|
|||||||
{ print }
|
{ print }
|
||||||
' "$tmp_conf" > "${tmp_conf}.new" && mv "${tmp_conf}.new" "$tmp_conf"
|
' "$tmp_conf" > "${tmp_conf}.new" && mv "${tmp_conf}.new" "$tmp_conf"
|
||||||
|
|
||||||
[ "$PORT_PROVIDED" -eq 1 ] && say " -> Updated port: $SERVER_PORT"
|
[ "$PORT_PROVIDED" -eq 1 ] && say " -> $L_INFO_UPD_PORT $SERVER_PORT"
|
||||||
[ "$SECRET_PROVIDED" -eq 1 ] && say " -> Updated secret for user 'hello'"
|
[ "$SECRET_PROVIDED" -eq 1 ] && say " -> $L_INFO_UPD_SEC"
|
||||||
[ "$DOMAIN_PROVIDED" -eq 1 ] && say " -> Updated tls_domain: $TLS_DOMAIN"
|
[ "$DOMAIN_PROVIDED" -eq 1 ] && say " -> $L_INFO_UPD_DOM $TLS_DOMAIN"
|
||||||
[ "$AD_TAG_PROVIDED" -eq 1 ] && say " -> Updated ad_tag"
|
[ "$AD_TAG_PROVIDED" -eq 1 ] && say " -> $L_INFO_UPD_TAG"
|
||||||
|
|
||||||
write_root "$CONFIG_FILE" < "$tmp_conf"
|
write_root "$CONFIG_FILE" < "$tmp_conf"
|
||||||
rm -f "$tmp_conf"
|
rm -f "$tmp_conf"
|
||||||
@@ -457,14 +643,14 @@ install_config() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$USER_SECRET" ]; then
|
if [ -z "$USER_SECRET" ]; then
|
||||||
USER_SECRET="$(generate_secret)" || die "Failed to generate secret."
|
USER_SECRET="$(generate_secret)" || die "$L_ERR_GEN_SEC"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
generate_config_content "$USER_SECRET" "$AD_TAG" | write_root "$CONFIG_FILE" || die "Failed to install config"
|
generate_config_content "$USER_SECRET" "$AD_TAG" | write_root "$CONFIG_FILE" || die "$L_ERR_CONF_INST"
|
||||||
$SUDO chown root:telemt "$CONFIG_FILE" && $SUDO chmod 640 "$CONFIG_FILE"
|
$SUDO chown root:telemt "$CONFIG_FILE" && $SUDO chmod 640 "$CONFIG_FILE"
|
||||||
|
|
||||||
say " -> Config created successfully."
|
say " -> $L_INFO_CONF_OK"
|
||||||
say " -> Configured secret for user 'hello': $USER_SECRET"
|
say " -> $L_INFO_CONF_SEC $USER_SECRET"
|
||||||
}
|
}
|
||||||
|
|
||||||
generate_systemd_content() {
|
generate_systemd_content() {
|
||||||
@@ -517,7 +703,7 @@ install_service() {
|
|||||||
$SUDO systemctl enable "$SERVICE_NAME" || true
|
$SUDO systemctl enable "$SERVICE_NAME" || true
|
||||||
|
|
||||||
if ! $SUDO systemctl start "$SERVICE_NAME"; then
|
if ! $SUDO systemctl start "$SERVICE_NAME"; then
|
||||||
say "[WARNING] Failed to start service"
|
say "[WARNING] $L_WARN_SVC_FAIL"
|
||||||
SERVICE_START_FAILED=1
|
SERVICE_START_FAILED=1
|
||||||
fi
|
fi
|
||||||
elif [ "$svc" = "openrc" ]; then
|
elif [ "$svc" = "openrc" ]; then
|
||||||
@@ -527,15 +713,15 @@ install_service() {
|
|||||||
$SUDO rc-update add "$SERVICE_NAME" default 2>/dev/null || true
|
$SUDO rc-update add "$SERVICE_NAME" default 2>/dev/null || true
|
||||||
|
|
||||||
if ! $SUDO rc-service "$SERVICE_NAME" start 2>/dev/null; then
|
if ! $SUDO rc-service "$SERVICE_NAME" start 2>/dev/null; then
|
||||||
say "[WARNING] Failed to start service"
|
say "[WARNING] $L_WARN_SVC_FAIL"
|
||||||
SERVICE_START_FAILED=1
|
SERVICE_START_FAILED=1
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
cmd="\"${INSTALL_DIR}/${BIN_NAME}\" \"${CONFIG_FILE}\""
|
cmd="\"${INSTALL_DIR}/${BIN_NAME}\" \"${CONFIG_FILE}\""
|
||||||
if [ -n "$SUDO" ]; then
|
if [ -n "$SUDO" ]; then
|
||||||
say " -> Service manager not found. Start manually: sudo -u telemt $cmd"
|
say " -> $L_INFO_MANUAL_START sudo -u telemt $cmd"
|
||||||
else
|
else
|
||||||
say " -> Service manager not found. Start manually: su -s /bin/sh telemt -c '$cmd'"
|
say " -> $L_INFO_MANUAL_START su -s /bin/sh telemt -c '$cmd'"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -566,12 +752,12 @@ kill_user_procs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uninstall() {
|
uninstall() {
|
||||||
say "Starting uninstallation of $BIN_NAME..."
|
say "$L_INFO_UNINST_START $BIN_NAME..."
|
||||||
|
|
||||||
say ">>> Stage 1: Stopping services"
|
say "$L_U_STAGE_1"
|
||||||
stop_service
|
stop_service
|
||||||
|
|
||||||
say ">>> Stage 2: Removing service configuration"
|
say "$L_U_STAGE_2"
|
||||||
svc="$(get_svc_mgr)"
|
svc="$(get_svc_mgr)"
|
||||||
if [ "$svc" = "systemd" ]; then
|
if [ "$svc" = "systemd" ]; then
|
||||||
$SUDO systemctl disable "$SERVICE_NAME" 2>/dev/null || true
|
$SUDO systemctl disable "$SERVICE_NAME" 2>/dev/null || true
|
||||||
@@ -582,28 +768,30 @@ uninstall() {
|
|||||||
$SUDO rm -f "/etc/init.d/${SERVICE_NAME}"
|
$SUDO rm -f "/etc/init.d/${SERVICE_NAME}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
say ">>> Stage 3: Terminating user processes"
|
say "$L_U_STAGE_3"
|
||||||
kill_user_procs
|
kill_user_procs
|
||||||
|
|
||||||
say ">>> Stage 4: Removing binary"
|
say "$L_U_STAGE_4"
|
||||||
$SUDO rm -f "${INSTALL_DIR}/${BIN_NAME}"
|
$SUDO rm -f "${INSTALL_DIR}/${BIN_NAME}"
|
||||||
|
|
||||||
if [ "$ACTION" = "purge" ]; then
|
if [ "$ACTION" = "purge" ]; then
|
||||||
say ">>> Stage 5: Purging configuration, data, and user"
|
say "$L_U_STAGE_5"
|
||||||
$SUDO rm -rf "$CONFIG_DIR" "$WORK_DIR"
|
$SUDO rm -rf "$CONFIG_DIR" "$WORK_DIR"
|
||||||
$SUDO rm -f "$CONFIG_FILE"
|
$SUDO rm -f "$CONFIG_FILE"
|
||||||
sleep 1
|
|
||||||
$SUDO userdel telemt 2>/dev/null || $SUDO deluser telemt 2>/dev/null || true
|
if check_os_entity passwd telemt; then
|
||||||
|
$SUDO userdel telemt 2>/dev/null || $SUDO deluser telemt 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
if check_os_entity group telemt; then
|
if check_os_entity group telemt; then
|
||||||
$SUDO groupdel telemt 2>/dev/null || $SUDO delgroup telemt 2>/dev/null || true
|
$SUDO groupdel telemt 2>/dev/null || $SUDO delgroup telemt 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
say "Note: Configuration and user kept. Run with 'purge' to remove completely."
|
say "$L_INFO_KEEP_CONF"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
printf '\n====================================================================\n'
|
printf '\n====================================================================\n'
|
||||||
printf ' UNINSTALLATION COMPLETE\n'
|
printf ' %s\n' "$L_OUT_UNINST_H"
|
||||||
printf '====================================================================\n\n'
|
printf '====================================================================\n\n'
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
@@ -612,21 +800,45 @@ case "$ACTION" in
|
|||||||
help) show_help ;;
|
help) show_help ;;
|
||||||
uninstall|purge) verify_common; uninstall ;;
|
uninstall|purge) verify_common; uninstall ;;
|
||||||
install)
|
install)
|
||||||
say "Starting installation of $BIN_NAME (Version: $TARGET_VERSION)"
|
say "$L_INFO_I_START $BIN_NAME (Version: $TARGET_VERSION)"
|
||||||
|
|
||||||
say ">>> Stage 1: Verifying environment and dependencies"
|
say "$L_I_STAGE_1"
|
||||||
verify_common
|
verify_common
|
||||||
verify_install_deps
|
verify_install_deps
|
||||||
|
|
||||||
if is_config_exists && [ "$PORT_PROVIDED" -eq 0 ]; then
|
if is_config_exists; then
|
||||||
ext_port="$($SUDO awk -F'=' '/^[ \t]*port[ \t]*=/ {gsub(/[^0-9]/, "", $2); print $2; exit}' "$CONFIG_FILE" 2>/dev/null || true)"
|
ext_port="$($SUDO awk -F'=' '/^[ \t]*port[ \t]*=/ {gsub(/[^0-9]/, "", $2); print $2; exit}' "$CONFIG_FILE" 2>/dev/null || true)"
|
||||||
if [ -n "$ext_port" ]; then
|
if [ -n "$ext_port" ] && [ "$PORT_PROVIDED" -eq 0 ]; then
|
||||||
SERVER_PORT="$ext_port"
|
SERVER_PORT="$ext_port"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
ext_secret="$($SUDO awk -F'"' '/^[ \t]*hello[ \t]*=/ {print $2; exit}' "$CONFIG_FILE" 2>/dev/null || true)"
|
||||||
|
if [ -n "$ext_secret" ] && [ "$SECRET_PROVIDED" -eq 0 ]; then
|
||||||
|
USER_SECRET="$ext_secret"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ext_domain="$($SUDO awk -F'"' '/^[ \t]*tls_domain[ \t]*=/ {print $2; exit}' "$CONFIG_FILE" 2>/dev/null || true)"
|
||||||
|
if [ -n "$ext_domain" ] && [ "$DOMAIN_PROVIDED" -eq 0 ]; then
|
||||||
|
TLS_DOMAIN="$ext_domain"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
check_port_availability
|
check_port_availability
|
||||||
|
|
||||||
|
if [ "$DOMAIN_PROVIDED" -eq 0 ]; then
|
||||||
|
say "$L_I_STAGE_1_5"
|
||||||
|
if [ -t 0 ] || [ -c /dev/tty ]; then
|
||||||
|
printf "$L_I_PROMPT_DOM" "$TLS_DOMAIN"
|
||||||
|
read -r input_domain </dev/tty || input_domain=""
|
||||||
|
if [ -n "$input_domain" ]; then
|
||||||
|
TLS_DOMAIN="$input_domain"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
say "[WARNING] $L_WARN_NO_TTY $TLS_DOMAIN"
|
||||||
|
fi
|
||||||
|
DOMAIN_PROVIDED=1
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$TARGET_VERSION" != "latest" ]; then
|
if [ "$TARGET_VERSION" != "latest" ]; then
|
||||||
TARGET_VERSION="${TARGET_VERSION#v}"
|
TARGET_VERSION="${TARGET_VERSION#v}"
|
||||||
fi
|
fi
|
||||||
@@ -640,15 +852,15 @@ case "$ACTION" in
|
|||||||
DL_URL="https://github.com/${REPO}/releases/download/${TARGET_VERSION}/${FILE_NAME}"
|
DL_URL="https://github.com/${REPO}/releases/download/${TARGET_VERSION}/${FILE_NAME}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
say ">>> Stage 2: Downloading archive"
|
say "$L_I_STAGE_2"
|
||||||
TEMP_DIR="$(mktemp -d)" || die "Temp directory creation failed"
|
TEMP_DIR="$(mktemp -d)" || die "$L_ERR_TMP_DIR"
|
||||||
if [ -z "$TEMP_DIR" ] || [ ! -d "$TEMP_DIR" ]; then
|
if [ -z "$TEMP_DIR" ] || [ ! -d "$TEMP_DIR" ]; then
|
||||||
die "Temp directory is invalid or was not created"
|
die "$L_ERR_TMP_INV"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! fetch_file "$DL_URL" "${TEMP_DIR}/${FILE_NAME}"; then
|
if ! fetch_file "$DL_URL" "${TEMP_DIR}/${FILE_NAME}"; then
|
||||||
if [ "$ARCH" = "x86_64-v3" ]; then
|
if [ "$ARCH" = "x86_64-v3" ]; then
|
||||||
say " -> x86_64-v3 build not found, falling back to standard x86_64..."
|
say " -> $L_INFO_FALLBACK"
|
||||||
ARCH="x86_64"
|
ARCH="x86_64"
|
||||||
FILE_NAME="${BIN_NAME}-${ARCH}-linux-${LIBC}.tar.gz"
|
FILE_NAME="${BIN_NAME}-${ARCH}-linux-${LIBC}.tar.gz"
|
||||||
if [ "$TARGET_VERSION" = "latest" ]; then
|
if [ "$TARGET_VERSION" = "latest" ]; then
|
||||||
@@ -656,64 +868,58 @@ case "$ACTION" in
|
|||||||
else
|
else
|
||||||
DL_URL="https://github.com/${REPO}/releases/download/${TARGET_VERSION}/${FILE_NAME}"
|
DL_URL="https://github.com/${REPO}/releases/download/${TARGET_VERSION}/${FILE_NAME}"
|
||||||
fi
|
fi
|
||||||
fetch_file "$DL_URL" "${TEMP_DIR}/${FILE_NAME}" || die "Download failed"
|
fetch_file "$DL_URL" "${TEMP_DIR}/${FILE_NAME}" || die "$L_ERR_DL_FAIL"
|
||||||
else
|
else
|
||||||
die "Download failed"
|
die "$L_ERR_DL_FAIL"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
say ">>> Stage 3: Extracting archive"
|
say "$L_I_STAGE_3"
|
||||||
if ! gzip -dc "${TEMP_DIR}/${FILE_NAME}" | tar -xf - -C "$TEMP_DIR" 2>/dev/null; then
|
if ! gzip -dc "${TEMP_DIR}/${FILE_NAME}" | tar -xf - -C "$TEMP_DIR" 2>/dev/null; then
|
||||||
die "Extraction failed (downloaded archive might be invalid or 404)."
|
die "$L_ERR_EXTRACT"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
EXTRACTED_BIN="$(find "$TEMP_DIR" -type f -name "$BIN_NAME" -print 2>/dev/null | head -n 1 || true)"
|
EXTRACTED_BIN="$(find "$TEMP_DIR" -type f -name "$BIN_NAME" -print 2>/dev/null | head -n 1 || true)"
|
||||||
[ -n "$EXTRACTED_BIN" ] || die "Binary '$BIN_NAME' not found in archive"
|
[ -n "$EXTRACTED_BIN" ] || die "$L_ERR_BIN_NOT_FOUND"
|
||||||
|
|
||||||
say ">>> Stage 4: Setting up environment (User, Group, Directories)"
|
say "$L_I_STAGE_4"
|
||||||
ensure_user_group; setup_dirs; stop_service
|
ensure_user_group; setup_dirs; stop_service
|
||||||
|
|
||||||
say ">>> Stage 5: Installing binary"
|
say "$L_I_STAGE_5"
|
||||||
install_binary "$EXTRACTED_BIN" "${INSTALL_DIR}/${BIN_NAME}"
|
install_binary "$EXTRACTED_BIN" "${INSTALL_DIR}/${BIN_NAME}"
|
||||||
|
|
||||||
say ">>> Stage 6: Generating/Updating configuration"
|
say "$L_I_STAGE_6"
|
||||||
install_config
|
install_config
|
||||||
|
|
||||||
say ">>> Stage 7: Installing and starting service"
|
say "$L_I_STAGE_7"
|
||||||
install_service
|
install_service
|
||||||
|
|
||||||
if [ "${SERVICE_START_FAILED:-0}" -eq 1 ]; then
|
if [ "${SERVICE_START_FAILED:-0}" -eq 1 ]; then
|
||||||
printf '\n====================================================================\n'
|
printf '\n====================================================================\n'
|
||||||
printf ' INSTALLATION COMPLETED WITH WARNINGS\n'
|
printf ' %s\n' "$L_OUT_WARN_H"
|
||||||
printf '====================================================================\n\n'
|
printf '====================================================================\n\n'
|
||||||
printf 'The service was installed but failed to start automatically.\n'
|
printf '%b' "$L_OUT_WARN_D"
|
||||||
printf 'Please check the logs to determine the issue.\n\n'
|
|
||||||
else
|
else
|
||||||
printf '\n====================================================================\n'
|
printf '\n====================================================================\n'
|
||||||
printf ' INSTALLATION SUCCESS\n'
|
printf ' %s\n' "$L_OUT_SUCC_H"
|
||||||
printf '====================================================================\n\n'
|
printf '====================================================================\n\n'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
svc="$(get_svc_mgr)"
|
SERVER_IP=""
|
||||||
if [ "$svc" = "systemd" ]; then
|
if command -v curl >/dev/null 2>&1; then SERVER_IP="$(curl -s4 -m 3 ifconfig.me 2>/dev/null || curl -s4 -m 3 api.ipify.org 2>/dev/null || true)"
|
||||||
printf 'To check the status of your proxy service, run:\n'
|
elif command -v wget >/dev/null 2>&1; then SERVER_IP="$(wget -qO- -T 3 ifconfig.me 2>/dev/null || wget -qO- -T 3 api.ipify.org 2>/dev/null || true)"; fi
|
||||||
printf ' systemctl status %s\n\n' "$SERVICE_NAME"
|
[ -z "$SERVER_IP" ] && SERVER_IP="<YOUR_SERVER_IP>"
|
||||||
elif [ "$svc" = "openrc" ]; then
|
|
||||||
printf 'To check the status of your proxy service, run:\n'
|
if command -v xxd >/dev/null 2>&1; then HEX_DOMAIN="$(printf '%s' "$TLS_DOMAIN" | xxd -p | tr -d '\n')"
|
||||||
printf ' rc-service %s status\n\n' "$SERVICE_NAME"
|
elif command -v hexdump >/dev/null 2>&1; then HEX_DOMAIN="$(printf '%s' "$TLS_DOMAIN" | hexdump -v -e '/1 "%02x"')"
|
||||||
fi
|
elif command -v od >/dev/null 2>&1; then HEX_DOMAIN="$(printf '%s' "$TLS_DOMAIN" | od -A n -t x1 | tr -d ' \n')"
|
||||||
|
else HEX_DOMAIN=""; fi
|
||||||
|
|
||||||
API_LISTEN="$($SUDO awk -F'"' '/^[ \t]*listen[ \t]*=/ {print $2; exit}' "$CONFIG_FILE" 2>/dev/null || true)"
|
CLIENT_SECRET="ee${USER_SECRET}${HEX_DOMAIN}"
|
||||||
API_LISTEN="${API_LISTEN:-127.0.0.1:9091}"
|
|
||||||
|
|
||||||
printf 'To get your user connection links (for Telegram), run:\n'
|
printf '%b\n' "$L_OUT_LINK"
|
||||||
if command -v jq >/dev/null 2>&1; then
|
printf ' tg://proxy?server=%s&port=%s&secret=%s\n\n' "$SERVER_IP" "$SERVER_PORT" "$CLIENT_SECRET"
|
||||||
printf ' curl -s http://%s/v1/users | jq -r '\''.data[]? | "User: \\(.username)\\n\\(.links.tls[0] // empty)\\n"'\''\n' "$API_LISTEN"
|
|
||||||
else
|
|
||||||
printf ' curl -s http://%s/v1/users\n' "$API_LISTEN"
|
|
||||||
printf ' (Tip: Install '\''jq'\'' for a much cleaner output)\n'
|
|
||||||
fi
|
|
||||||
|
|
||||||
printf '\n====================================================================\n'
|
printf '====================================================================\n'
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -159,6 +159,21 @@ impl MeBindStaleMode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// RST-on-close mode for accepted client sockets.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum RstOnCloseMode {
|
||||||
|
/// Normal FIN on all closes (default, no behaviour change).
|
||||||
|
#[default]
|
||||||
|
Off,
|
||||||
|
/// SO_LINGER(0) on accept; cleared after successful auth.
|
||||||
|
/// Pre-handshake failures (scanners, DPI, timeouts) send RST;
|
||||||
|
/// authenticated relay sessions close gracefully with FIN.
|
||||||
|
Errors,
|
||||||
|
/// SO_LINGER(0) on accept, never cleared — all closes send RST.
|
||||||
|
Always,
|
||||||
|
}
|
||||||
|
|
||||||
/// Middle-End writer floor policy mode.
|
/// Middle-End writer floor policy mode.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
@@ -925,6 +940,14 @@ pub struct GeneralConfig {
|
|||||||
/// Minimum unavailable ME DC groups before degrading.
|
/// Minimum unavailable ME DC groups before degrading.
|
||||||
#[serde(default = "default_degradation_min_unavailable_dc_groups")]
|
#[serde(default = "default_degradation_min_unavailable_dc_groups")]
|
||||||
pub degradation_min_unavailable_dc_groups: u8,
|
pub degradation_min_unavailable_dc_groups: u8,
|
||||||
|
|
||||||
|
/// RST-on-close mode for accepted client sockets.
|
||||||
|
/// `off` — normal FIN on all closes (default).
|
||||||
|
/// `errors` — SO_LINGER(0) on accept, cleared after successful auth;
|
||||||
|
/// pre-handshake failures send RST, relayed sessions close gracefully.
|
||||||
|
/// `always` — SO_LINGER(0) on accept, never cleared; all closes send RST.
|
||||||
|
#[serde(default)]
|
||||||
|
pub rst_on_close: RstOnCloseMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for GeneralConfig {
|
impl Default for GeneralConfig {
|
||||||
@@ -1086,6 +1109,7 @@ impl Default for GeneralConfig {
|
|||||||
ntp_servers: default_ntp_servers(),
|
ntp_servers: default_ntp_servers(),
|
||||||
auto_degradation_enabled: default_true(),
|
auto_degradation_enabled: default_true(),
|
||||||
degradation_min_unavailable_dc_groups: default_degradation_min_unavailable_dc_groups(),
|
degradation_min_unavailable_dc_groups: default_degradation_min_unavailable_dc_groups(),
|
||||||
|
rst_on_close: RstOnCloseMode::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use tokio::net::UnixListener;
|
|||||||
use tokio::sync::{Semaphore, watch};
|
use tokio::sync::{Semaphore, watch};
|
||||||
use tracing::{debug, error, info, warn};
|
use tracing::{debug, error, info, warn};
|
||||||
|
|
||||||
use crate::config::ProxyConfig;
|
use crate::config::{ProxyConfig, RstOnCloseMode};
|
||||||
use crate::crypto::SecureRandom;
|
use crate::crypto::SecureRandom;
|
||||||
use crate::ip_tracker::UserIpTracker;
|
use crate::ip_tracker::UserIpTracker;
|
||||||
use crate::proxy::ClientHandler;
|
use crate::proxy::ClientHandler;
|
||||||
@@ -21,6 +21,7 @@ use crate::stats::{ReplayChecker, Stats};
|
|||||||
use crate::stream::BufferPool;
|
use crate::stream::BufferPool;
|
||||||
use crate::tls_front::TlsFrontCache;
|
use crate::tls_front::TlsFrontCache;
|
||||||
use crate::transport::middle_proxy::MePool;
|
use crate::transport::middle_proxy::MePool;
|
||||||
|
use crate::transport::socket::set_linger_zero;
|
||||||
use crate::transport::{ListenOptions, UpstreamManager, create_listener, find_listener_processes};
|
use crate::transport::{ListenOptions, UpstreamManager, create_listener, find_listener_processes};
|
||||||
|
|
||||||
use super::helpers::{is_expected_handshake_eof, print_proxy_links};
|
use super::helpers::{is_expected_handshake_eof, print_proxy_links};
|
||||||
@@ -380,6 +381,15 @@ pub(crate) fn spawn_tcp_accept_loops(
|
|||||||
loop {
|
loop {
|
||||||
match listener.accept().await {
|
match listener.accept().await {
|
||||||
Ok((stream, peer_addr)) => {
|
Ok((stream, peer_addr)) => {
|
||||||
|
let rst_mode = config_rx.borrow().general.rst_on_close;
|
||||||
|
#[cfg(unix)]
|
||||||
|
let raw_fd = {
|
||||||
|
use std::os::unix::io::AsRawFd;
|
||||||
|
stream.as_raw_fd()
|
||||||
|
};
|
||||||
|
if matches!(rst_mode, RstOnCloseMode::Errors | RstOnCloseMode::Always) {
|
||||||
|
let _ = set_linger_zero(&stream);
|
||||||
|
}
|
||||||
if !*admission_rx_tcp.borrow() {
|
if !*admission_rx_tcp.borrow() {
|
||||||
debug!(peer = %peer_addr, "Admission gate closed, dropping connection");
|
debug!(peer = %peer_addr, "Admission gate closed, dropping connection");
|
||||||
drop(stream);
|
drop(stream);
|
||||||
@@ -454,6 +464,9 @@ pub(crate) fn spawn_tcp_accept_loops(
|
|||||||
shared,
|
shared,
|
||||||
proxy_protocol_enabled,
|
proxy_protocol_enabled,
|
||||||
real_peer_report_for_handler,
|
real_peer_report_for_handler,
|
||||||
|
#[cfg(unix)]
|
||||||
|
raw_fd,
|
||||||
|
rst_mode,
|
||||||
)
|
)
|
||||||
.run()
|
.run()
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -804,6 +804,9 @@ pub struct RunningClientHandler {
|
|||||||
beobachten: Arc<BeobachtenStore>,
|
beobachten: Arc<BeobachtenStore>,
|
||||||
shared: Arc<ProxySharedState>,
|
shared: Arc<ProxySharedState>,
|
||||||
proxy_protocol_enabled: bool,
|
proxy_protocol_enabled: bool,
|
||||||
|
#[cfg(unix)]
|
||||||
|
raw_fd: std::os::unix::io::RawFd,
|
||||||
|
rst_on_close: crate::config::RstOnCloseMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientHandler {
|
impl ClientHandler {
|
||||||
@@ -825,6 +828,11 @@ impl ClientHandler {
|
|||||||
proxy_protocol_enabled: bool,
|
proxy_protocol_enabled: bool,
|
||||||
real_peer_report: Arc<std::sync::Mutex<Option<SocketAddr>>>,
|
real_peer_report: Arc<std::sync::Mutex<Option<SocketAddr>>>,
|
||||||
) -> RunningClientHandler {
|
) -> RunningClientHandler {
|
||||||
|
#[cfg(unix)]
|
||||||
|
let raw_fd = {
|
||||||
|
use std::os::unix::io::AsRawFd;
|
||||||
|
stream.as_raw_fd()
|
||||||
|
};
|
||||||
Self::new_with_shared(
|
Self::new_with_shared(
|
||||||
stream,
|
stream,
|
||||||
peer,
|
peer,
|
||||||
@@ -842,6 +850,9 @@ impl ClientHandler {
|
|||||||
ProxySharedState::new(),
|
ProxySharedState::new(),
|
||||||
proxy_protocol_enabled,
|
proxy_protocol_enabled,
|
||||||
real_peer_report,
|
real_peer_report,
|
||||||
|
#[cfg(unix)]
|
||||||
|
raw_fd,
|
||||||
|
crate::config::RstOnCloseMode::Off,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -863,6 +874,8 @@ impl ClientHandler {
|
|||||||
shared: Arc<ProxySharedState>,
|
shared: Arc<ProxySharedState>,
|
||||||
proxy_protocol_enabled: bool,
|
proxy_protocol_enabled: bool,
|
||||||
real_peer_report: Arc<std::sync::Mutex<Option<SocketAddr>>>,
|
real_peer_report: Arc<std::sync::Mutex<Option<SocketAddr>>>,
|
||||||
|
#[cfg(unix)] raw_fd: std::os::unix::io::RawFd,
|
||||||
|
rst_on_close: crate::config::RstOnCloseMode,
|
||||||
) -> RunningClientHandler {
|
) -> RunningClientHandler {
|
||||||
let normalized_peer = normalize_ip(peer);
|
let normalized_peer = normalize_ip(peer);
|
||||||
RunningClientHandler {
|
RunningClientHandler {
|
||||||
@@ -883,6 +896,9 @@ impl ClientHandler {
|
|||||||
beobachten,
|
beobachten,
|
||||||
shared,
|
shared,
|
||||||
proxy_protocol_enabled,
|
proxy_protocol_enabled,
|
||||||
|
#[cfg(unix)]
|
||||||
|
raw_fd,
|
||||||
|
rst_on_close,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -901,6 +917,10 @@ impl RunningClientHandler {
|
|||||||
debug!(peer = %peer, error = %e, "Failed to configure client socket");
|
debug!(peer = %peer, error = %e, "Failed to configure client socket");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
let raw_fd = self.raw_fd;
|
||||||
|
let rst_on_close = self.rst_on_close;
|
||||||
|
|
||||||
let outcome = match self.do_handshake().await? {
|
let outcome = match self.do_handshake().await? {
|
||||||
Some(outcome) => outcome,
|
Some(outcome) => outcome,
|
||||||
None => return Ok(()),
|
None => return Ok(()),
|
||||||
@@ -908,7 +928,14 @@ impl RunningClientHandler {
|
|||||||
|
|
||||||
// Phase 2: relay (WITHOUT handshake timeout — relay has its own activity timeouts)
|
// Phase 2: relay (WITHOUT handshake timeout — relay has its own activity timeouts)
|
||||||
match outcome {
|
match outcome {
|
||||||
HandshakeOutcome::NeedsRelay(fut) | HandshakeOutcome::NeedsMasking(fut) => fut.await,
|
HandshakeOutcome::NeedsRelay(fut) => {
|
||||||
|
#[cfg(unix)]
|
||||||
|
if matches!(rst_on_close, crate::config::RstOnCloseMode::Errors) {
|
||||||
|
let _ = crate::transport::socket::clear_linger_fd(raw_fd);
|
||||||
|
}
|
||||||
|
fut.await
|
||||||
|
}
|
||||||
|
HandshakeOutcome::NeedsMasking(fut) => fut.await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -102,14 +102,29 @@ pub fn configure_client_socket(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set socket to send RST on close (for masking)
|
/// Set socket to send RST on close instead of FIN, eliminating
|
||||||
#[allow(dead_code)]
|
/// FIN-WAIT-1 and orphan socket accumulation on high-churn workloads.
|
||||||
pub fn set_linger_zero(stream: &TcpStream) -> Result<()> {
|
pub fn set_linger_zero(stream: &TcpStream) -> Result<()> {
|
||||||
let socket = socket2::SockRef::from(stream);
|
let socket = socket2::SockRef::from(stream);
|
||||||
socket.set_linger(Some(Duration::ZERO))?;
|
socket.set_linger(Some(Duration::ZERO))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Restore default linger behaviour (graceful FIN) on a socket
|
||||||
|
/// identified by its raw file descriptor. Safe to call after
|
||||||
|
/// `TcpStream::into_split()` because the fd remains valid until
|
||||||
|
/// both halves are dropped.
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub fn clear_linger_fd(fd: std::os::unix::io::RawFd) -> Result<()> {
|
||||||
|
use std::os::unix::io::BorrowedFd;
|
||||||
|
// SAFETY: the fd is still open — the caller guarantees the
|
||||||
|
// TcpStream (or its split halves) is alive.
|
||||||
|
let borrowed = unsafe { BorrowedFd::borrow_raw(fd) };
|
||||||
|
let socket = socket2::SockRef::from(&borrowed);
|
||||||
|
socket.set_linger(None)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new TCP socket for outgoing connections
|
/// Create a new TCP socket for outgoing connections
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn create_outgoing_socket(addr: SocketAddr) -> Result<Socket> {
|
pub fn create_outgoing_socket(addr: SocketAddr) -> Result<Socket> {
|
||||||
|
|||||||
1222
tools/grafana-dashboard-by-user.json
Normal file
1222
tools/grafana-dashboard-by-user.json
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -24,7 +24,7 @@ from urllib.request import Request, urlopen
|
|||||||
# Exceptions
|
# Exceptions
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
class TememtAPIError(Exception):
|
class TelemtAPIError(Exception):
|
||||||
"""Raised when the API returns an error envelope or a transport error."""
|
"""Raised when the API returns an error envelope or a transport error."""
|
||||||
|
|
||||||
def __init__(self, message: str, code: str | None = None,
|
def __init__(self, message: str, code: str | None = None,
|
||||||
@@ -35,7 +35,7 @@ class TememtAPIError(Exception):
|
|||||||
self.request_id = request_id
|
self.request_id = request_id
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (f"TememtAPIError(message={str(self)!r}, code={self.code!r}, "
|
return (f"TelemtAPIError(message={str(self)!r}, code={self.code!r}, "
|
||||||
f"http_status={self.http_status}, request_id={self.request_id})")
|
f"http_status={self.http_status}, request_id={self.request_id})")
|
||||||
|
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ class APIResponse:
|
|||||||
# Main client
|
# Main client
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
class TememtAPI:
|
class TelemtAPI:
|
||||||
"""
|
"""
|
||||||
HTTP client for the Telemt Control API.
|
HTTP client for the Telemt Control API.
|
||||||
|
|
||||||
@@ -75,10 +75,10 @@ class TememtAPI:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
base_url: str = "http://127.0.0.1:9091",
|
base_url: str = "http://127.0.0.1:9091",
|
||||||
auth_header: str | None = None,
|
auth_header: str | None = None,
|
||||||
timeout: int = 10,
|
timeout: int = 10,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.base_url = base_url.rstrip("/")
|
self.base_url = base_url.rstrip("/")
|
||||||
self.auth_header = auth_header
|
self.auth_header = auth_header
|
||||||
@@ -98,12 +98,12 @@ class TememtAPI:
|
|||||||
return h
|
return h
|
||||||
|
|
||||||
def _request(
|
def _request(
|
||||||
self,
|
self,
|
||||||
method: str,
|
method: str,
|
||||||
path: str,
|
path: str,
|
||||||
body: dict | None = None,
|
body: dict | None = None,
|
||||||
if_match: str | None = None,
|
if_match: str | None = None,
|
||||||
query: dict | None = None,
|
query: dict | None = None,
|
||||||
) -> APIResponse:
|
) -> APIResponse:
|
||||||
url = self.base_url + path
|
url = self.base_url + path
|
||||||
if query:
|
if query:
|
||||||
@@ -133,22 +133,22 @@ class TememtAPI:
|
|||||||
try:
|
try:
|
||||||
payload = json.loads(raw)
|
payload = json.loads(raw)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise TememtAPIError(
|
raise TelemtAPIError(
|
||||||
str(exc), http_status=exc.code
|
str(exc), http_status=exc.code
|
||||||
) from exc
|
) from exc
|
||||||
err = payload.get("error", {})
|
err = payload.get("error", {})
|
||||||
raise TememtAPIError(
|
raise TelemtAPIError(
|
||||||
err.get("message", str(exc)),
|
err.get("message", str(exc)),
|
||||||
code=err.get("code"),
|
code=err.get("code"),
|
||||||
http_status=exc.code,
|
http_status=exc.code,
|
||||||
request_id=payload.get("request_id"),
|
request_id=payload.get("request_id"),
|
||||||
) from exc
|
) from exc
|
||||||
except URLError as exc:
|
except URLError as exc:
|
||||||
raise TememtAPIError(str(exc)) from exc
|
raise TelemtAPIError(str(exc)) from exc
|
||||||
|
|
||||||
if not payload.get("ok"):
|
if not payload.get("ok"):
|
||||||
err = payload.get("error", {})
|
err = payload.get("error", {})
|
||||||
raise TememtAPIError(
|
raise TelemtAPIError(
|
||||||
err.get("message", "unknown error"),
|
err.get("message", "unknown error"),
|
||||||
code=err.get("code"),
|
code=err.get("code"),
|
||||||
request_id=payload.get("request_id"),
|
request_id=payload.get("request_id"),
|
||||||
@@ -298,16 +298,16 @@ class TememtAPI:
|
|||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
def create_user(
|
def create_user(
|
||||||
self,
|
self,
|
||||||
username: str,
|
username: str,
|
||||||
*,
|
*,
|
||||||
secret: str | None = None,
|
secret: str | None = None,
|
||||||
user_ad_tag: str | None = None,
|
user_ad_tag: str | None = None,
|
||||||
max_tcp_conns: int | None = None,
|
max_tcp_conns: int | None = None,
|
||||||
expiration_rfc3339: str | None = None,
|
expiration_rfc3339: str | None = None,
|
||||||
data_quota_bytes: int | None = None,
|
data_quota_bytes: int | None = None,
|
||||||
max_unique_ips: int | None = None,
|
max_unique_ips: int | None = None,
|
||||||
if_match: str | None = None,
|
if_match: str | None = None,
|
||||||
) -> APIResponse:
|
) -> APIResponse:
|
||||||
"""POST /v1/users — create a new user.
|
"""POST /v1/users — create a new user.
|
||||||
|
|
||||||
@@ -340,16 +340,16 @@ class TememtAPI:
|
|||||||
return self._post("/v1/users", body=body, if_match=if_match)
|
return self._post("/v1/users", body=body, if_match=if_match)
|
||||||
|
|
||||||
def patch_user(
|
def patch_user(
|
||||||
self,
|
self,
|
||||||
username: str,
|
username: str,
|
||||||
*,
|
*,
|
||||||
secret: str | None = None,
|
secret: str | None = None,
|
||||||
user_ad_tag: str | None = None,
|
user_ad_tag: str | None = None,
|
||||||
max_tcp_conns: int | None = None,
|
max_tcp_conns: int | None = None,
|
||||||
expiration_rfc3339: str | None = None,
|
expiration_rfc3339: str | None = None,
|
||||||
data_quota_bytes: int | None = None,
|
data_quota_bytes: int | None = None,
|
||||||
max_unique_ips: int | None = None,
|
max_unique_ips: int | None = None,
|
||||||
if_match: str | None = None,
|
if_match: str | None = None,
|
||||||
) -> APIResponse:
|
) -> APIResponse:
|
||||||
"""PATCH /v1/users/{username} — partial update; only provided fields change.
|
"""PATCH /v1/users/{username} — partial update; only provided fields change.
|
||||||
|
|
||||||
@@ -385,10 +385,10 @@ class TememtAPI:
|
|||||||
if_match=if_match)
|
if_match=if_match)
|
||||||
|
|
||||||
def delete_user(
|
def delete_user(
|
||||||
self,
|
self,
|
||||||
username: str,
|
username: str,
|
||||||
*,
|
*,
|
||||||
if_match: str | None = None,
|
if_match: str | None = None,
|
||||||
) -> APIResponse:
|
) -> APIResponse:
|
||||||
"""DELETE /v1/users/{username} — remove user; blocks deletion of last user.
|
"""DELETE /v1/users/{username} — remove user; blocks deletion of last user.
|
||||||
|
|
||||||
@@ -403,11 +403,11 @@ class TememtAPI:
|
|||||||
# in the route matcher (documented limitation). The method is provided
|
# in the route matcher (documented limitation). The method is provided
|
||||||
# for completeness and future compatibility.
|
# for completeness and future compatibility.
|
||||||
def rotate_secret(
|
def rotate_secret(
|
||||||
self,
|
self,
|
||||||
username: str,
|
username: str,
|
||||||
*,
|
*,
|
||||||
secret: str | None = None,
|
secret: str | None = None,
|
||||||
if_match: str | None = None,
|
if_match: str | None = None,
|
||||||
) -> APIResponse:
|
) -> APIResponse:
|
||||||
"""POST /v1/users/{username}/rotate-secret — rotate user secret.
|
"""POST /v1/users/{username}/rotate-secret — rotate user secret.
|
||||||
|
|
||||||
@@ -533,12 +533,12 @@ EXAMPLES
|
|||||||
help="Username for user commands")
|
help="Username for user commands")
|
||||||
|
|
||||||
# user create/patch fields
|
# user create/patch fields
|
||||||
p.add_argument("--secret", default=None)
|
p.add_argument("--secret", default=None)
|
||||||
p.add_argument("--ad-tag", dest="ad_tag", default=None)
|
p.add_argument("--ad-tag", dest="ad_tag", default=None)
|
||||||
p.add_argument("--max-conns", dest="max_conns", type=int, default=None)
|
p.add_argument("--max-conns", dest="max_conns", type=int, default=None)
|
||||||
p.add_argument("--expires", default=None)
|
p.add_argument("--expires", default=None)
|
||||||
p.add_argument("--quota", type=int, default=None)
|
p.add_argument("--quota", type=int, default=None)
|
||||||
p.add_argument("--max-ips", dest="max_ips", type=int, default=None)
|
p.add_argument("--max-ips", dest="max_ips", type=int, default=None)
|
||||||
|
|
||||||
# events
|
# events
|
||||||
p.add_argument("--limit", type=int, default=None,
|
p.add_argument("--limit", type=int, default=None,
|
||||||
@@ -564,10 +564,10 @@ if __name__ == "__main__":
|
|||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
if cmd == "gen-secret":
|
if cmd == "gen-secret":
|
||||||
print(TememtAPI.generate_secret())
|
print(TelemtAPI.generate_secret())
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
api = TememtAPI(args.url, auth_header=args.auth, timeout=args.timeout)
|
api = TelemtAPI(args.url, auth_header=args.auth, timeout=args.timeout)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# -- read endpoints --------------------------------------------------
|
# -- read endpoints --------------------------------------------------
|
||||||
@@ -690,7 +690,8 @@ if __name__ == "__main__":
|
|||||||
parser.error("patch command requires <username>")
|
parser.error("patch command requires <username>")
|
||||||
if not any([args.secret, args.ad_tag, args.max_conns,
|
if not any([args.secret, args.ad_tag, args.max_conns,
|
||||||
args.expires, args.quota, args.max_ips]):
|
args.expires, args.quota, args.max_ips]):
|
||||||
parser.error("patch requires at least one field (--secret, --max-conns, --expires, --quota, --max-ips, --ad-tag)")
|
parser.error(
|
||||||
|
"patch requires at least one field (--secret, --max-conns, --expires, --quota, --max-ips, --ad-tag)")
|
||||||
_print(api.patch_user(
|
_print(api.patch_user(
|
||||||
args.arg,
|
args.arg,
|
||||||
secret=args.secret,
|
secret=args.secret,
|
||||||
@@ -721,7 +722,7 @@ if __name__ == "__main__":
|
|||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
except TememtAPIError as exc:
|
except TelemtAPIError as exc:
|
||||||
print(f"API error [{exc.http_status}] {exc.code}: {exc}", file=sys.stderr)
|
print(f"API error [{exc.http_status}] {exc.code}: {exc}", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
|||||||
Reference in New Issue
Block a user