Compare commits

..

No commits in common. "main" and "3.3.38" have entirely different histories.
main ... 3.3.38

6 changed files with 155 additions and 421 deletions

View File

@ -151,14 +151,6 @@ jobs:
mkdir -p dist
cp "target/${{ matrix.target }}/release/${{ env.BINARY_NAME }}" dist/telemt
if [ "${{ matrix.target }}" = "aarch64-unknown-linux-gnu" ]; then
STRIP_BIN=aarch64-linux-gnu-strip
else
STRIP_BIN=strip
fi
"${STRIP_BIN}" dist/telemt
cd dist
tar -czf "${{ matrix.asset }}.tar.gz" \
--owner=0 --group=0 --numeric-owner \
@ -287,14 +279,6 @@ jobs:
mkdir -p dist
cp "target/${{ matrix.target }}/release/${{ env.BINARY_NAME }}" dist/telemt
if [ "${{ matrix.target }}" = "aarch64-unknown-linux-musl" ]; then
STRIP_BIN=aarch64-linux-musl-strip
else
STRIP_BIN=strip
fi
"${STRIP_BIN}" dist/telemt
cd dist
tar -czf "${{ matrix.asset }}.tar.gz" \
--owner=0 --group=0 --numeric-owner \

16
LICENSE
View File

@ -1,4 +1,4 @@
######## TELEMT LICENSE 3.3 #########
###### TELEMT Public License 3 ######
##### Copyright (c) 2026 Telemt #####
Permission is hereby granted, free of charge, to any person obtaining a copy
@ -14,15 +14,11 @@ are preserved and complied with.
The canonical version of this License is the English version.
Official translations are provided for informational purposes only
and for convenience, and do not have legal force. In case of any
discrepancy, the English version of this License shall prevail
/----------------------------------------------------------\
| Language | Location |
|-------------|--------------------------------------------|
| English | docs/LICENSE/TELEMT-LICENSE.en.md |
| German | docs/LICENSE/TELEMT-LICENSE.de.md |
| Russian | docs/LICENSE/TELEMT-LICENSE.ru.md |
\----------------------------------------------------------/
discrepancy, the English version of this License shall prevail.
Available versions:
- English in Markdown: docs/LICENSE/LICENSE.md
- German: docs/LICENSE/LICENSE.de.md
- Russian: docs/LICENSE/LICENSE.ru.md
### License Versioning Policy

View File

@ -14,10 +14,10 @@ This document lists all configuration keys accepted by `config.toml`.
| Key | Type | Default |
| --- | ---- | ------- |
| [`include`](#cfg-top-include) | `String` (special directive) | |
| [`include`](#cfg-top-include) | `String` (special directive) | `null` |
| [`show_link`](#cfg-top-show_link) | `"*"` or `String[]` | `[]` (`ShowLink::None`) |
| [`dc_overrides`](#cfg-top-dc_overrides) | `Map<String, String or String[]>` | `{}` |
| [`default_dc`](#cfg-top-default_dc) | `u8` | — (effective fallback: `2` in ME routing) |
| [`default_dc`](#cfg-top-default_dc) | `u8` or `null` | `null` (effective fallback: `2` in ME routing) |
<a id="cfg-top-include"></a>
- `include`
@ -68,17 +68,17 @@ This document lists all configuration keys accepted by `config.toml`.
| Key | Type | Default |
| --- | ---- | ------- |
| [`data_path`](#cfg-general-data_path) | `String` | — |
| [`data_path`](#cfg-general-data_path) | `String` or `null` | `null` |
| [`prefer_ipv6`](#cfg-general-prefer_ipv6) | `bool` | `false` |
| [`fast_mode`](#cfg-general-fast_mode) | `bool` | `true` |
| [`use_middle_proxy`](#cfg-general-use_middle_proxy) | `bool` | `true` |
| [`proxy_secret_path`](#cfg-general-proxy_secret_path) | `String` | `"proxy-secret"` |
| [`proxy_config_v4_cache_path`](#cfg-general-proxy_config_v4_cache_path) | `String` | `"cache/proxy-config-v4.txt"` |
| [`proxy_config_v6_cache_path`](#cfg-general-proxy_config_v6_cache_path) | `String` | `"cache/proxy-config-v6.txt"` |
| [`ad_tag`](#cfg-general-ad_tag) | `String` | — |
| [`middle_proxy_nat_ip`](#cfg-general-middle_proxy_nat_ip) | `IpAddr` | — |
| [`proxy_secret_path`](#cfg-general-proxy_secret_path) | `String` or `null` | `"proxy-secret"` |
| [`proxy_config_v4_cache_path`](#cfg-general-proxy_config_v4_cache_path) | `String` or `null` | `"cache/proxy-config-v4.txt"` |
| [`proxy_config_v6_cache_path`](#cfg-general-proxy_config_v6_cache_path) | `String` or `null` | `"cache/proxy-config-v6.txt"` |
| [`ad_tag`](#cfg-general-ad_tag) | `String` or `null` | `null` |
| [`middle_proxy_nat_ip`](#cfg-general-middle_proxy_nat_ip) | `IpAddr` or `null` | `null` |
| [`middle_proxy_nat_probe`](#cfg-general-middle_proxy_nat_probe) | `bool` | `true` |
| [`middle_proxy_nat_stun`](#cfg-general-middle_proxy_nat_stun) | `String` | — |
| [`middle_proxy_nat_stun`](#cfg-general-middle_proxy_nat_stun) | `String` or `null` | `null` |
| [`middle_proxy_nat_stun_servers`](#cfg-general-middle_proxy_nat_stun_servers) | `String[]` | `[]` |
| [`stun_nat_probe_concurrency`](#cfg-general-stun_nat_probe_concurrency) | `usize` | `8` |
| [`middle_proxy_pool_size`](#cfg-general-middle_proxy_pool_size) | `usize` | `8` |
@ -144,7 +144,7 @@ This document lists all configuration keys accepted by `config.toml`.
| [`upstream_unhealthy_fail_threshold`](#cfg-general-upstream_unhealthy_fail_threshold) | `u32` | `5` |
| [`upstream_connect_failfast_hard_errors`](#cfg-general-upstream_connect_failfast_hard_errors) | `bool` | `false` |
| [`stun_iface_mismatch_ignore`](#cfg-general-stun_iface_mismatch_ignore) | `bool` | `false` |
| [`unknown_dc_log_path`](#cfg-general-unknown_dc_log_path) | `String` | `"unknown-dc.txt"` |
| [`unknown_dc_log_path`](#cfg-general-unknown_dc_log_path) | `String` or `null` | `"unknown-dc.txt"` |
| [`unknown_dc_file_log_enabled`](#cfg-general-unknown_dc_file_log_enabled) | `bool` | `false` |
| [`log_level`](#cfg-general-log_level) | `"debug"`, `"verbose"`, `"normal"`, or `"silent"` | `"normal"` |
| [`disable_colors`](#cfg-general-disable_colors) | `bool` | `false` |
@ -163,7 +163,7 @@ This document lists all configuration keys accepted by `config.toml`.
| [`me_route_inline_recovery_attempts`](#cfg-general-me_route_inline_recovery_attempts) | `u32` | `3` |
| [`me_route_inline_recovery_wait_ms`](#cfg-general-me_route_inline_recovery_wait_ms) | `u64` | `3000` |
| [`fast_mode_min_tls_record`](#cfg-general-fast_mode_min_tls_record) | `usize` | `0` |
| [`update_every`](#cfg-general-update_every) | `u64` | `300` |
| [`update_every`](#cfg-general-update_every) | `u64` or `null` | `300` |
| [`me_reinit_every_secs`](#cfg-general-me_reinit_every_secs) | `u64` | `900` |
| [`me_hardswap_warmup_delay_min_ms`](#cfg-general-me_hardswap_warmup_delay_min_ms) | `u64` | `1000` |
| [`me_hardswap_warmup_delay_max_ms`](#cfg-general-me_hardswap_warmup_delay_max_ms) | `u64` | `2000` |
@ -205,7 +205,7 @@ This document lists all configuration keys accepted by `config.toml`.
<a id="cfg-general-data_path"></a>
- `data_path`
- **Constraints / validation**: `String` (optional).
- **Constraints / validation**: `String` or `null`.
- **Description**: Optional runtime data directory path.
- **Example**:
@ -245,7 +245,7 @@ This document lists all configuration keys accepted by `config.toml`.
```
<a id="cfg-general-proxy_secret_path"></a>
- `proxy_secret_path`
- **Constraints / validation**: `String`. When omitted, the default path is `"proxy-secret"`. Empty values are accepted by TOML/serde but will likely fail at runtime (invalid file path).
- **Constraints / validation**: `String` or `null`. If `null`, the effective cache path is `"proxy-secret"`. Empty values are accepted but will likely fail at runtime (invalid file path).
- **Description**: Path to Telegram infrastructure `proxy-secret` cache file used by ME handshake/RPC auth. Telemt always tries a fresh download from `https://core.telegram.org/getProxySecret` first, caches it to this path on success, and falls back to reading the cached file (any age) on download failure.
- **Example**:
@ -255,7 +255,7 @@ This document lists all configuration keys accepted by `config.toml`.
```
<a id="cfg-general-proxy_config_v4_cache_path"></a>
- `proxy_config_v4_cache_path`
- **Constraints / validation**: `String`. When set, must not be empty/whitespace-only.
- **Constraints / validation**: `String` or `null`. When set, must not be empty/whitespace-only.
- **Description**: Optional disk cache path for raw `getProxyConfig` (IPv4) snapshot. At startup Telemt tries to fetch a fresh snapshot first; on fetch failure or empty snapshot it falls back to this cache file when present and non-empty.
- **Example**:
@ -265,7 +265,7 @@ This document lists all configuration keys accepted by `config.toml`.
```
<a id="cfg-general-proxy_config_v6_cache_path"></a>
- `proxy_config_v6_cache_path`
- **Constraints / validation**: `String`. When set, must not be empty/whitespace-only.
- **Constraints / validation**: `String` or `null`. When set, must not be empty/whitespace-only.
- **Description**: Optional disk cache path for raw `getProxyConfigV6` (IPv6) snapshot. At startup Telemt tries to fetch a fresh snapshot first; on fetch failure or empty snapshot it falls back to this cache file when present and non-empty.
- **Example**:
@ -275,7 +275,7 @@ This document lists all configuration keys accepted by `config.toml`.
```
<a id="cfg-general-ad_tag"></a>
- `ad_tag`
- **Constraints / validation**: `String` (optional). When set, must be exactly 32 hex characters; invalid values are disabled during config load.
- **Constraints / validation**: `String` or `null`. When set, must be exactly 32 hex characters; invalid values are disabled during config load.
- **Description**: Global fallback sponsored-channel `ad_tag` (used when user has no override in `access.user_ad_tags`). An all-zero tag is accepted but has no effect (and is warned about) until replaced with a real tag from `@MTProxybot`.
- **Example**:
@ -285,7 +285,7 @@ This document lists all configuration keys accepted by `config.toml`.
```
<a id="cfg-general-middle_proxy_nat_ip"></a>
- `middle_proxy_nat_ip`
- **Constraints / validation**: `IpAddr` (optional).
- **Constraints / validation**: `IpAddr` or `null`.
- **Description**: Manual public NAT IP override used as ME address material when set.
- **Example**:
@ -967,8 +967,8 @@ This document lists all configuration keys accepted by `config.toml`.
```
<a id="cfg-general-unknown_dc_log_path"></a>
- `unknown_dc_log_path`
- **Constraints / validation**: `String` (optional). Must be a safe path (no `..` components, parent directory must exist); unsafe paths are rejected at runtime.
- **Description**: Log file path for unknown (non-standard) DC requests when `unknown_dc_file_log_enabled = true`. Omit this key to disable file logging.
- **Constraints / validation**: `String` or `null`. Must be a safe path (no `..` components, parent directory must exist); unsafe paths are rejected at runtime.
- **Description**: Log file path for unknown (non-standard) DC requests when `unknown_dc_file_log_enabled = true`. Set to `null` to disable file logging.
- **Example**:
```toml
@ -1157,7 +1157,7 @@ This document lists all configuration keys accepted by `config.toml`.
```
<a id="cfg-general-update_every"></a>
- `update_every`
- **Constraints / validation**: `u64` (seconds). If set, must be `> 0`. If this key is not explicitly set, legacy `proxy_secret_auto_reload_secs` and `proxy_config_auto_reload_secs` may be used (their effective minimum must be `> 0`).
- **Constraints / validation**: `u64` (seconds) or `null`. If set, must be `> 0`. If `null`, legacy `proxy_secret_auto_reload_secs` and `proxy_config_auto_reload_secs` are used and their effective minimum must be `> 0`.
- **Description**: Unified refresh interval for ME updater tasks (`getProxyConfig`, `getProxyConfigV6`, `getProxySecret`). When set, it overrides legacy proxy reload intervals.
- **Example**:
@ -1450,7 +1450,7 @@ This document lists all configuration keys accepted by `config.toml`.
```
<a id="cfg-general-proxy_secret_auto_reload_secs"></a>
- `proxy_secret_auto_reload_secs`
- **Constraints / validation**: Deprecated. Use `general.update_every`. When `general.update_every` is not explicitly set, the effective legacy refresh interval is `min(proxy_secret_auto_reload_secs, proxy_config_auto_reload_secs)` and must be `> 0`.
- **Constraints / validation**: Deprecated. Use `general.update_every`. When `general.update_every` is `null`, the effective legacy refresh interval is `min(proxy_secret_auto_reload_secs, proxy_config_auto_reload_secs)` and must be `> 0`.
- **Description**: Deprecated legacy proxy-secret refresh interval. Used only when `general.update_every` is not set.
- **Example**:
@ -1463,7 +1463,7 @@ This document lists all configuration keys accepted by `config.toml`.
```
<a id="cfg-general-proxy_config_auto_reload_secs"></a>
- `proxy_config_auto_reload_secs`
- **Constraints / validation**: Deprecated. Use `general.update_every`. When `general.update_every` is not explicitly set, the effective legacy refresh interval is `min(proxy_secret_auto_reload_secs, proxy_config_auto_reload_secs)` and must be `> 0`.
- **Constraints / validation**: Deprecated. Use `general.update_every`. When `general.update_every` is `null`, the effective legacy refresh interval is `min(proxy_secret_auto_reload_secs, proxy_config_auto_reload_secs)` and must be `> 0`.
- **Description**: Deprecated legacy ME config refresh interval. Used only when `general.update_every` is not set.
- **Example**:
@ -1624,8 +1624,8 @@ This document lists all configuration keys accepted by `config.toml`.
| Key | Type | Default |
| --- | ---- | ------- |
| [`show`](#cfg-general-links-show) | `"*"` or `String[]` | `"*"` |
| [`public_host`](#cfg-general-links-public_host) | `String` | — |
| [`public_port`](#cfg-general-links-public_port) | `u16` | — |
| [`public_host`](#cfg-general-links-public_host) | `String` or `null` | `null` |
| [`public_port`](#cfg-general-links-public_port) | `u16` or `null` | `null` |
<a id="cfg-general-links-show"></a>
- `show`
@ -1641,7 +1641,7 @@ This document lists all configuration keys accepted by `config.toml`.
```
<a id="cfg-general-links-public_host"></a>
- `public_host`
- **Constraints / validation**: `String` (optional).
- **Constraints / validation**: `String` or `null`.
- **Description**: Public hostname/IP override used for generated `tg://` links (overrides detected IP).
- **Example**:
@ -1651,7 +1651,7 @@ This document lists all configuration keys accepted by `config.toml`.
```
<a id="cfg-general-links-public_port"></a>
- `public_port`
- **Constraints / validation**: `u16` (optional).
- **Constraints / validation**: `u16` or `null`.
- **Description**: Public port override used for generated `tg://` links (overrides `server.port`).
- **Example**:
@ -1708,7 +1708,7 @@ This document lists all configuration keys accepted by `config.toml`.
| Key | Type | Default |
| --- | ---- | ------- |
| [`ipv4`](#cfg-network-ipv4) | `bool` | `true` |
| [`ipv6`](#cfg-network-ipv6) | `bool` | `false` |
| [`ipv6`](#cfg-network-ipv6) | `bool` or `null` | `false` |
| [`prefer`](#cfg-network-prefer) | `u8` | `4` |
| [`multipath`](#cfg-network-multipath) | `bool` | `false` |
| [`stun_use`](#cfg-network-stun_use) | `bool` | `true` |
@ -1730,8 +1730,8 @@ This document lists all configuration keys accepted by `config.toml`.
```
<a id="cfg-network-ipv6"></a>
- `ipv6`
- **Constraints / validation**: `bool`.
- **Description**: Enables/disables IPv6 networking. When omitted, defaults to `false`.
- **Constraints / validation**: `bool` or `null`. `null` means "auto-detect IPv6 availability".
- **Description**: Enables/disables IPv6 when explicitly set; when `null`, Telemt will auto-detect IPv6 availability at runtime.
- **Example**:
```toml
@ -1741,6 +1741,9 @@ This document lists all configuration keys accepted by `config.toml`.
# or: disable IPv6 explicitly
# ipv6 = false
# or: let Telemt auto-detect
# ipv6 = null
```
<a id="cfg-network-prefer"></a>
- `prefer`
@ -1839,16 +1842,16 @@ This document lists all configuration keys accepted by `config.toml`.
| Key | Type | Default |
| --- | ---- | ------- |
| [`port`](#cfg-server-port) | `u16` | `443` |
| [`listen_addr_ipv4`](#cfg-server-listen_addr_ipv4) | `String` | `"0.0.0.0"` |
| [`listen_addr_ipv6`](#cfg-server-listen_addr_ipv6) | `String` | `"::"` |
| [`listen_unix_sock`](#cfg-server-listen_unix_sock) | `String` | — |
| [`listen_unix_sock_perm`](#cfg-server-listen_unix_sock_perm) | `String` | — |
| [`listen_tcp`](#cfg-server-listen_tcp) | `bool` | — (auto) |
| [`listen_addr_ipv4`](#cfg-server-listen_addr_ipv4) | `String` or `null` | `"0.0.0.0"` |
| [`listen_addr_ipv6`](#cfg-server-listen_addr_ipv6) | `String` or `null` | `"::"` |
| [`listen_unix_sock`](#cfg-server-listen_unix_sock) | `String` or `null` | `null` |
| [`listen_unix_sock_perm`](#cfg-server-listen_unix_sock_perm) | `String` or `null` | `null` |
| [`listen_tcp`](#cfg-server-listen_tcp) | `bool` or `null` | `null` (auto) |
| [`proxy_protocol`](#cfg-server-proxy_protocol) | `bool` | `false` |
| [`proxy_protocol_header_timeout_ms`](#cfg-server-proxy_protocol_header_timeout_ms) | `u64` | `500` |
| [`proxy_protocol_trusted_cidrs`](#cfg-server-proxy_protocol_trusted_cidrs) | `IpNetwork[]` | `[]` |
| [`metrics_port`](#cfg-server-metrics_port) | `u16` | — |
| [`metrics_listen`](#cfg-server-metrics_listen) | `String` | — |
| [`metrics_port`](#cfg-server-metrics_port) | `u16` or `null` | `null` |
| [`metrics_listen`](#cfg-server-metrics_listen) | `String` or `null` | `null` |
| [`metrics_whitelist`](#cfg-server-metrics_whitelist) | `IpNetwork[]` | `["127.0.0.1/32", "::1/128"]` |
| [`max_connections`](#cfg-server-max_connections) | `u32` | `10000` |
| [`accept_permit_timeout_ms`](#cfg-server-accept_permit_timeout_ms) | `u64` | `250` |
@ -1865,8 +1868,8 @@ This document lists all configuration keys accepted by `config.toml`.
```
<a id="cfg-server-listen_addr_ipv4"></a>
- `listen_addr_ipv4`
- **Constraints / validation**: `String` (optional). When set, must be a valid IPv4 address string.
- **Description**: IPv4 bind address for TCP listener (omit this key to disable IPv4 bind).
- **Constraints / validation**: `String` or `null`. When set, must be a valid IPv4 address string.
- **Description**: IPv4 bind address for TCP listener (`null` disables IPv4 bind).
- **Example**:
```toml
@ -1875,8 +1878,8 @@ This document lists all configuration keys accepted by `config.toml`.
```
<a id="cfg-server-listen_addr_ipv6"></a>
- `listen_addr_ipv6`
- **Constraints / validation**: `String` (optional). When set, must be a valid IPv6 address string.
- **Description**: IPv6 bind address for TCP listener (omit this key to disable IPv6 bind).
- **Constraints / validation**: `String` or `null`. When set, must be a valid IPv6 address string.
- **Description**: IPv6 bind address for TCP listener (`null` disables IPv6 bind).
- **Example**:
```toml
@ -1885,7 +1888,7 @@ This document lists all configuration keys accepted by `config.toml`.
```
<a id="cfg-server-listen_unix_sock"></a>
- `listen_unix_sock`
- **Constraints / validation**: `String` (optional). Must not be empty when set. Unix only.
- **Constraints / validation**: `String` or `null`. Must not be empty when set. Unix only.
- **Description**: Unix socket path for listener. When set, `server.listen_tcp` defaults to `false` (unless explicitly overridden).
- **Example**:
@ -1895,8 +1898,8 @@ This document lists all configuration keys accepted by `config.toml`.
```
<a id="cfg-server-listen_unix_sock_perm"></a>
- `listen_unix_sock_perm`
- **Constraints / validation**: `String` (optional). When set, should be an octal permission string like `"0666"` or `"0777"`.
- **Description**: Optional Unix socket file permissions applied after bind (chmod). When omitted, permissions are not changed (inherits umask).
- **Constraints / validation**: `String` or `null`. When set, should be an octal permission string like `"0666"` or `"0777"`.
- **Description**: Optional Unix socket file permissions applied after bind (chmod). `null` means "no change" (inherits umask).
- **Example**:
```toml
@ -1906,7 +1909,7 @@ This document lists all configuration keys accepted by `config.toml`.
```
<a id="cfg-server-listen_tcp"></a>
- `listen_tcp`
- **Constraints / validation**: `bool` (optional). When omitted, Telemt auto-detects:
- **Constraints / validation**: `bool` or `null`. `null` means auto:
- `true` when `listen_unix_sock` is not set
- `false` when `listen_unix_sock` is set
- **Description**: Explicit TCP listener enable/disable override.
@ -1954,7 +1957,7 @@ This document lists all configuration keys accepted by `config.toml`.
```
<a id="cfg-server-metrics_port"></a>
- `metrics_port`
- **Constraints / validation**: `u16` (optional).
- **Constraints / validation**: `u16` or `null`.
- **Description**: Prometheus-compatible metrics endpoint port. When set, enables the metrics listener (bind behavior can be overridden by `metrics_listen`).
- **Example**:
@ -1964,7 +1967,7 @@ This document lists all configuration keys accepted by `config.toml`.
```
<a id="cfg-server-metrics_listen"></a>
- `metrics_listen`
- **Constraints / validation**: `String` (optional). When set, must be in `IP:PORT` format.
- **Constraints / validation**: `String` or `null`. When set, must be in `IP:PORT` format.
- **Description**: Full metrics bind address (`IP:PORT`), overrides `metrics_port` and binds on the specified address only.
- **Example**:
@ -2007,105 +2010,6 @@ This document lists all configuration keys accepted by `config.toml`.
Note: When `server.proxy_protocol` is enabled, incoming PROXY protocol headers are parsed from the first bytes of the connection and the client source address is replaced with `src_addr` from the header. For security, the peer source IP (the direct connection address) is verified against `server.proxy_protocol_trusted_cidrs`; if this list is empty, PROXY headers are rejected and the connection is considered untrusted.
## [server.conntrack_control]
Note: The conntrack-control worker runs **only on Linux**. On other operating systems it is not started; if `inline_conntrack_control` is `true`, a warning is logged. Effective operation also requires **CAP_NET_ADMIN** and a usable backend (`nft` or `iptables` / `ip6tables` on `PATH`). The `conntrack` utility is used for optional table entry deletes under pressure.
| Key | Type | Default |
| --- | ---- | ------- |
| [`inline_conntrack_control`](#cfg-server-conntrack_control-inline_conntrack_control) | `bool` | `true` |
| [`mode`](#cfg-server-conntrack_control-mode) | `String` | `"tracked"` |
| [`backend`](#cfg-server-conntrack_control-backend) | `String` | `"auto"` |
| [`profile`](#cfg-server-conntrack_control-profile) | `String` | `"balanced"` |
| [`hybrid_listener_ips`](#cfg-server-conntrack_control-hybrid_listener_ips) | `IpAddr[]` | `[]` |
| [`pressure_high_watermark_pct`](#cfg-server-conntrack_control-pressure_high_watermark_pct) | `u8` | `85` |
| [`pressure_low_watermark_pct`](#cfg-server-conntrack_control-pressure_low_watermark_pct) | `u8` | `70` |
| [`delete_budget_per_sec`](#cfg-server-conntrack_control-delete_budget_per_sec) | `u64` | `4096` |
<a id="cfg-server-conntrack_control-inline_conntrack_control"></a>
- `inline_conntrack_control`
- **Constraints / validation**: `bool`.
- **Description**: Master switch for the runtime conntrack-control task: reconciles **raw/notrack** netfilter rules for listener ingress (see `mode`), samples load every second, and may run **`conntrack -D`** deletes for qualifying close events while **pressure mode** is active (see `delete_budget_per_sec`). When `false`, notrack rules are cleared and pressure-driven deletes are disabled.
- **Example**:
```toml
[server.conntrack_control]
inline_conntrack_control = true
```
<a id="cfg-server-conntrack_control-mode"></a>
- `mode`
- **Constraints / validation**: One of `tracked`, `notrack`, `hybrid` (case-insensitive; serialized lowercase).
- **Description**: **`tracked`**: do not install telemt notrack rules (connections stay in conntrack). **`notrack`**: mark matching ingress TCP to `server.port` as notrack — targets are derived from `[[server.listeners]]` if any, otherwise from `server.listen_addr_ipv4` / `server.listen_addr_ipv6` (unspecified addresses mean “any” for that family). **`hybrid`**: notrack only for addresses listed in `hybrid_listener_ips` (must be non-empty; validated at load).
- **Example**:
```toml
[server.conntrack_control]
mode = "notrack"
```
<a id="cfg-server-conntrack_control-backend"></a>
- `backend`
- **Constraints / validation**: One of `auto`, `nftables`, `iptables` (case-insensitive; serialized lowercase).
- **Description**: Which command set applies notrack rules. **`auto`**: use `nft` if present on `PATH`, else `iptables`/`ip6tables` if present. **`nftables`** / **`iptables`**: force that backend; missing binary means rules cannot be applied. The nft path uses table `inet telemt_conntrack` and a prerouting raw hook; iptables uses chain `TELEMT_NOTRACK` in the `raw` table.
- **Example**:
```toml
[server.conntrack_control]
backend = "auto"
```
<a id="cfg-server-conntrack_control-profile"></a>
- `profile`
- **Constraints / validation**: One of `conservative`, `balanced`, `aggressive` (case-insensitive; serialized lowercase).
- **Description**: When **conntrack pressure mode** is active (`pressure_*` watermarks), caps idle and activity timeouts to reduce conntrack churn: e.g. **client first-byte idle** (`client.rs`), **direct relay activity timeout** (`direct_relay.rs`), and **middle-relay idle policy** caps (`middle_relay.rs` via `ConntrackPressureProfile::*_cap_secs` / `direct_activity_timeout_secs`). More aggressive profiles use shorter caps.
- **Example**:
```toml
[server.conntrack_control]
profile = "balanced"
```
<a id="cfg-server-conntrack_control-hybrid_listener_ips"></a>
- `hybrid_listener_ips`
- **Constraints / validation**: `IpAddr[]`. Required to be **non-empty** when `mode = "hybrid"`. Ignored for `tracked` / `notrack`.
- **Description**: Explicit listener addresses that receive notrack rules in hybrid mode (split into IPv4 vs IPv6 rules by the implementation).
- **Example**:
```toml
[server.conntrack_control]
mode = "hybrid"
hybrid_listener_ips = ["203.0.113.10", "2001:db8::1"]
```
<a id="cfg-server-conntrack_control-pressure_high_watermark_pct"></a>
- `pressure_high_watermark_pct`
- **Constraints / validation**: Must be within `[1, 100]`.
- **Description**: Pressure mode **enters** when any of: connection fill vs `server.max_connections` (percentage, if `max_connections > 0`), **file-descriptor** usage vs process soft `RLIMIT_NOFILE`, **non-zero** `accept_permit_timeout` events in the last sample window, or **ME c2me send-full** counter delta. Entry compares relevant percentages against this high watermark (see `update_pressure_state` in `conntrack_control.rs`).
- **Example**:
```toml
[server.conntrack_control]
pressure_high_watermark_pct = 85
```
<a id="cfg-server-conntrack_control-pressure_low_watermark_pct"></a>
- `pressure_low_watermark_pct`
- **Constraints / validation**: Must be **strictly less than** `pressure_high_watermark_pct`.
- **Description**: Pressure mode **clears** only after **three** consecutive one-second samples where all signals are at or below this low watermark and the accept-timeout / ME-queue deltas are zero (hysteresis).
- **Example**:
```toml
[server.conntrack_control]
pressure_low_watermark_pct = 70
```
<a id="cfg-server-conntrack_control-delete_budget_per_sec"></a>
- `delete_budget_per_sec`
- **Constraints / validation**: Must be `> 0`.
- **Description**: Maximum number of **`conntrack -D`** attempts **per second** while pressure mode is active (token bucket refilled each second). Deletes run only for close events with reasons **timeout**, **pressure**, or **reset**; each attempt consumes a token regardless of outcome.
- **Example**:
```toml
[server.conntrack_control]
delete_budget_per_sec = 4096
```
## [server.api]
Note: This section also accepts the legacy alias `[server.admin_api]` (same schema as `[server.api]`).
@ -2254,9 +2158,9 @@ Note: This section also accepts the legacy alias `[server.admin_api]` (same sche
| Key | Type | Default |
| --- | ---- | ------- |
| [`ip`](#cfg-server-listeners-ip) | `IpAddr` | — |
| [`announce`](#cfg-server-listeners-announce) | `String` | — |
| [`announce_ip`](#cfg-server-listeners-announce_ip) | `IpAddr` | — |
| [`proxy_protocol`](#cfg-server-listeners-proxy_protocol) | `bool` | — |
| [`announce`](#cfg-server-listeners-announce) | `String` or `null` | — |
| [`announce_ip`](#cfg-server-listeners-announce_ip) | `IpAddr` or `null` | — |
| [`proxy_protocol`](#cfg-server-listeners-proxy_protocol) | `bool` or `null` | `null` |
| [`reuse_allow`](#cfg-server-listeners-reuse_allow) | `bool` | `false` |
<a id="cfg-server-listeners-ip"></a>
@ -2271,7 +2175,7 @@ Note: This section also accepts the legacy alias `[server.admin_api]` (same sche
```
<a id="cfg-server-listeners-announce"></a>
- `announce`
- **Constraints / validation**: `String` (optional). Must not be empty when set.
- **Constraints / validation**: `String` or `null`. Must not be empty when set.
- **Description**: Public IP/domain announced in proxy links for this listener. Takes precedence over `announce_ip`.
- **Example**:
@ -2282,7 +2186,7 @@ Note: This section also accepts the legacy alias `[server.admin_api]` (same sche
```
<a id="cfg-server-listeners-announce_ip"></a>
- `announce_ip`
- **Constraints / validation**: `IpAddr` (optional). Deprecated. Use `announce`.
- **Constraints / validation**: `IpAddr` or `null`. Deprecated. Use `announce`.
- **Description**: Deprecated legacy announce IP. During config load it is migrated to `announce` when `announce` is not set.
- **Example**:
@ -2293,7 +2197,7 @@ Note: This section also accepts the legacy alias `[server.admin_api]` (same sche
```
<a id="cfg-server-listeners-proxy_protocol"></a>
- `proxy_protocol`
- **Constraints / validation**: `bool` (optional). When set, overrides `server.proxy_protocol` for this listener.
- **Constraints / validation**: `bool` or `null`. When set, overrides `server.proxy_protocol` for this listener.
- **Description**: Per-listener PROXY protocol override.
- **Example**:
@ -2447,9 +2351,9 @@ Note: This section also accepts the legacy alias `[server.admin_api]` (same sche
| [`tls_fetch_scope`](#cfg-censorship-tls_fetch_scope) | `String` | `""` |
| [`tls_fetch`](#cfg-censorship-tls_fetch) | `Table` | built-in defaults |
| [`mask`](#cfg-censorship-mask) | `bool` | `true` |
| [`mask_host`](#cfg-censorship-mask_host) | `String` | — |
| [`mask_host`](#cfg-censorship-mask_host) | `String` or `null` | `null` |
| [`mask_port`](#cfg-censorship-mask_port) | `u16` | `443` |
| [`mask_unix_sock`](#cfg-censorship-mask_unix_sock) | `String` | — |
| [`mask_unix_sock`](#cfg-censorship-mask_unix_sock) | `String` or `null` | `null` |
| [`fake_cert_len`](#cfg-censorship-fake_cert_len) | `usize` | `2048` |
| [`tls_emulation`](#cfg-censorship-tls_emulation) | `bool` | `true` |
| [`tls_front_dir`](#cfg-censorship-tls_front_dir) | `String` | `"tlsfront"` |
@ -2536,8 +2440,8 @@ Note: This section also accepts the legacy alias `[server.admin_api]` (same sche
```
<a id="cfg-censorship-mask_host"></a>
- `mask_host`
- **Constraints / validation**: `String` (optional).
- If `mask_unix_sock` is set, `mask_host` must be omitted (mutually exclusive).
- **Constraints / validation**: `String` or `null`.
- If `mask_unix_sock` is set, `mask_host` must be `null` (mutually exclusive).
- If `mask_host` is not set and `mask_unix_sock` is not set, Telemt defaults `mask_host` to `tls_domain`.
- **Description**: Upstream mask host for TLS fronting relay.
- **Example**:
@ -2558,7 +2462,7 @@ Note: This section also accepts the legacy alias `[server.admin_api]` (same sche
```
<a id="cfg-censorship-mask_unix_sock"></a>
- `mask_unix_sock`
- **Constraints / validation**: `String` (optional).
- **Constraints / validation**: `String` or `null`.
- Must not be empty when set.
- Unix only; rejected on non-Unix platforms.
- On Unix, must be \(\le 107\) bytes (path length limit).
@ -2978,7 +2882,6 @@ If your backend or network is very bandwidth-constrained, reduce cap first. If p
| [`users`](#cfg-access-users) | `Map<String, String>` | `{"default": "000…000"}` |
| [`user_ad_tags`](#cfg-access-user_ad_tags) | `Map<String, String>` | `{}` |
| [`user_max_tcp_conns`](#cfg-access-user_max_tcp_conns) | `Map<String, usize>` | `{}` |
| [`user_max_tcp_conns_global_each`](#cfg-access-user_max_tcp_conns_global_each) | `usize` | `0` |
| [`user_expirations`](#cfg-access-user_expirations) | `Map<String, DateTime<Utc>>` | `{}` |
| [`user_data_quota`](#cfg-access-user_data_quota) | `Map<String, u64>` | `{}` |
| [`user_max_unique_ips`](#cfg-access-user_max_unique_ips) | `Map<String, usize>` | `{}` |
@ -3023,20 +2926,6 @@ If your backend or network is very bandwidth-constrained, reduce cap first. If p
[access.user_max_tcp_conns]
alice = 500
```
<a id="cfg-access-user_max_tcp_conns_global_each"></a>
- `user_max_tcp_conns_global_each`
- **Constraints / validation**: `usize`. `0` disables the inherited limit.
- **Description**: Global per-user maximum concurrent TCP connections, applied when a user has **no positive** entry in `[access.user_max_tcp_conns]` (a missing key, or a value of `0`, both fall through to this setting). Per-user limits greater than `0` in `user_max_tcp_conns` take precedence.
- **Example**:
```toml
[access]
user_max_tcp_conns_global_each = 200
[access.user_max_tcp_conns]
alice = 500 # uses 500, not the global cap
# bob has no entry → uses 200
```
<a id="cfg-access-user_expirations"></a>
- `user_expirations`
- **Constraints / validation**: `Map<String, DateTime<Utc>>`. Each value must be a valid RFC3339 / ISO-8601 datetime.
@ -3138,13 +3027,13 @@ If your backend or network is very bandwidth-constrained, reduce cap first. If p
| [`weight`](#cfg-upstreams-weight) | `u16` | `1` |
| [`enabled`](#cfg-upstreams-enabled) | `bool` | `true` |
| [`scopes`](#cfg-upstreams-scopes) | `String` | `""` |
| [`interface`](#cfg-upstreams-interface) | `String` | — |
| [`bind_addresses`](#cfg-upstreams-bind_addresses) | `String[]` | — |
| [`interface`](#cfg-upstreams-interface) | `String` or `null` | `null` |
| [`bind_addresses`](#cfg-upstreams-bind_addresses) | `String[]` or `null` | `null` |
| [`url`](#cfg-upstreams-url) | `String` | — |
| [`address`](#cfg-upstreams-address) | `String` | — |
| [`user_id`](#cfg-upstreams-user_id) | `String` | — |
| [`username`](#cfg-upstreams-username) | `String` | — |
| [`password`](#cfg-upstreams-password) | `String` | — |
| [`user_id`](#cfg-upstreams-user_id) | `String` or `null` | `null` |
| [`username`](#cfg-upstreams-username) | `String` or `null` | `null` |
| [`password`](#cfg-upstreams-password) | `String` or `null` | `null` |
<a id="cfg-upstreams-type"></a>
- `type`
@ -3201,7 +3090,7 @@ If your backend or network is very bandwidth-constrained, reduce cap first. If p
```
<a id="cfg-upstreams-interface"></a>
- `interface`
- **Constraints / validation**: `String` (optional).
- **Constraints / validation**: `String` or `null`.
- For `"direct"`: may be an IP address (used as explicit local bind) or an OS interface name (resolved to an IP at runtime; Unix only).
- For `"socks4"`/`"socks5"`: supported only when `address` is an `IP:port` literal; when `address` is a hostname, interface binding is ignored.
- For `"shadowsocks"`: passed to the shadowsocks connector as an optional outbound bind hint.
@ -3220,7 +3109,7 @@ If your backend or network is very bandwidth-constrained, reduce cap first. If p
```
<a id="cfg-upstreams-bind_addresses"></a>
- `bind_addresses`
- **Constraints / validation**: `String[]` (optional). Applies only to `type = "direct"`.
- **Constraints / validation**: `String[]` or `null`. Applies only to `type = "direct"`.
- Each entry should be an IP address string.
- At runtime, Telemt selects an address that matches the target family (IPv4 vs IPv6). If `bind_addresses` is set and none match the target family, the connect attempt fails.
- **Description**: Explicit local source addresses for outgoing direct TCP connects. When multiple addresses are provided, selection is round-robin.
@ -3261,7 +3150,7 @@ If your backend or network is very bandwidth-constrained, reduce cap first. If p
```
<a id="cfg-upstreams-user_id"></a>
- `user_id`
- **Constraints / validation**: `String` (optional). Only for `type = "socks4"`.
- **Constraints / validation**: `String` or `null`. Only for `type = "socks4"`.
- **Description**: SOCKS4 CONNECT user ID. Note: when a request scope is selected, Telemt may override this with the selected scope value.
- **Example**:
@ -3273,7 +3162,7 @@ If your backend or network is very bandwidth-constrained, reduce cap first. If p
```
<a id="cfg-upstreams-username"></a>
- `username`
- **Constraints / validation**: `String` (optional). Only for `type = "socks5"`.
- **Constraints / validation**: `String` or `null`. Only for `type = "socks5"`.
- **Description**: SOCKS5 username (for username/password authentication). Note: when a request scope is selected, Telemt may override this with the selected scope value.
- **Example**:
@ -3285,7 +3174,7 @@ If your backend or network is very bandwidth-constrained, reduce cap first. If p
```
<a id="cfg-upstreams-password"></a>
- `password`
- **Constraints / validation**: `String` (optional). Only for `type = "socks5"`.
- **Constraints / validation**: `String` or `null`. Only for `type = "socks5"`.
- **Description**: SOCKS5 password (for username/password authentication). Note: when a request scope is selected, Telemt may override this with the selected scope value.
- **Example**:

View File

@ -128,8 +128,8 @@ WorkingDirectory=/opt/telemt
ExecStart=/bin/telemt /etc/telemt/telemt.toml
Restart=on-failure
LimitNOFILE=65536
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
[Install]

View File

@ -128,8 +128,8 @@ WorkingDirectory=/opt/telemt
ExecStart=/bin/telemt /etc/telemt/telemt.toml
Restart=on-failure
LimitNOFILE=65536
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
[Install]

View File

@ -8,20 +8,12 @@ CONFIG_DIR="${CONFIG_DIR:-/etc/telemt}"
CONFIG_FILE="${CONFIG_FILE:-${CONFIG_DIR}/telemt.toml}"
WORK_DIR="${WORK_DIR:-/opt/telemt}"
TLS_DOMAIN="${TLS_DOMAIN:-petrovich.ru}"
SERVER_PORT="${SERVER_PORT:-443}"
USER_SECRET=""
AD_TAG=""
SERVICE_NAME="telemt"
TEMP_DIR=""
SUDO=""
CONFIG_PARENT_DIR=""
SERVICE_START_FAILED=0
PORT_PROVIDED=0
SECRET_PROVIDED=0
AD_TAG_PROVIDED=0
DOMAIN_PROVIDED=0
ACTION="install"
TARGET_VERSION="${VERSION:-latest}"
@ -33,37 +25,8 @@ while [ $# -gt 0 ]; do
printf '[ERROR] %s requires a domain argument.\n' "$1" >&2
exit 1
fi
TLS_DOMAIN="$2"; DOMAIN_PROVIDED=1; shift 2 ;;
-p|--port)
if [ "$#" -lt 2 ] || [ -z "$2" ]; then
printf '[ERROR] %s requires a port argument.\n' "$1" >&2; exit 1
fi
case "$2" in
*[!0-9]*) printf '[ERROR] Port must be a valid number.\n' >&2; exit 1 ;;
esac
port_num="$(printf '%s\n' "$2" | sed 's/^0*//')"
[ -z "$port_num" ] && port_num="0"
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
fi
SERVER_PORT="$port_num"; PORT_PROVIDED=1; shift 2 ;;
-s|--secret)
if [ "$#" -lt 2 ] || [ -z "$2" ]; then
printf '[ERROR] %s requires a secret argument.\n' "$1" >&2; exit 1
fi
case "$2" in
*[!0-9a-fA-F]*)
printf '[ERROR] Secret must contain only hex characters.\n' >&2; exit 1 ;;
esac
if [ "${#2}" -ne 32 ]; then
printf '[ERROR] Secret must be exactly 32 chars.\n' >&2; exit 1
fi
USER_SECRET="$2"; SECRET_PROVIDED=1; shift 2 ;;
-a|--ad-tag|--ad_tag)
if [ "$#" -lt 2 ] || [ -z "$2" ]; then
printf '[ERROR] %s requires an ad_tag argument.\n' "$1" >&2; exit 1
fi
AD_TAG="$2"; AD_TAG_PROVIDED=1; shift 2 ;;
TLS_DOMAIN="$2"
shift 2 ;;
uninstall|--uninstall)
if [ "$ACTION" != "purge" ]; then ACTION="uninstall"; fi
shift ;;
@ -96,17 +59,12 @@ cleanup() {
trap cleanup EXIT INT TERM
show_help() {
say "Usage: $0 [ <version> | install | uninstall | purge ] [ options ]"
say "Usage: $0 [ <version> | install | uninstall | purge ] [ -d <domain> ] [ --help ]"
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 " uninstall Remove the binary and service (keeps config and user)"
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"
exit 0
}
@ -123,13 +81,13 @@ get_realpath() {
path_in="$1"
case "$path_in" in /*) ;; *) path_in="$(pwd)/$path_in" ;; esac
if command -v realpath >/dev/null 2>&1; then
if command -v realpath >/dev/null 2>&1; then
if realpath_out="$(realpath -m "$path_in" 2>/dev/null)"; then
printf '%s\n' "$realpath_out"
return
fi
fi
if command -v readlink >/dev/null 2>&1; then
resolved_path="$(readlink -f "$path_in" 2>/dev/null || true)"
if [ -n "$resolved_path" ]; then
@ -162,14 +120,6 @@ get_svc_mgr() {
else echo "none"; fi
}
is_config_exists() {
if [ -n "$SUDO" ]; then
$SUDO sh -c '[ -f "$1" ]' _ "$CONFIG_FILE"
else
[ -f "$CONFIG_FILE" ]
fi
}
verify_common() {
[ -n "$BIN_NAME" ] || die "BIN_NAME cannot be empty."
[ -n "$INSTALL_DIR" ] || die "INSTALL_DIR cannot be empty."
@ -177,7 +127,7 @@ verify_common() {
[ -n "$CONFIG_FILE" ] || die "CONFIG_FILE cannot be empty."
case "${INSTALL_DIR}${CONFIG_DIR}${WORK_DIR}${CONFIG_FILE}" in
*[!a-zA-Z0-9_./-]*) die "Invalid characters in paths." ;;
*[!a-zA-Z0-9_./-]*) die "Invalid characters in paths. Only alphanumeric, _, ., -, and / allowed." ;;
esac
case "$TARGET_VERSION" in *[!a-zA-Z0-9_.-]*) die "Invalid characters in version." ;; esac
@ -195,11 +145,11 @@ verify_common() {
if [ "$(id -u)" -eq 0 ]; then
SUDO=""
else
command -v sudo >/dev/null 2>&1 || die "This script requires root or sudo."
command -v sudo >/dev/null 2>&1 || die "This script requires root or sudo. Neither found."
SUDO="sudo"
if ! sudo -n true 2>/dev/null; then
if ! [ -t 0 ]; then
die "sudo requires a password, but no TTY detected."
die "sudo requires a password, but no TTY detected. Aborting to prevent hang."
fi
fi
fi
@ -212,7 +162,21 @@ verify_common() {
die "Safety check failed: CONFIG_FILE '$CONFIG_FILE' is a directory."
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 path in "$CONFIG_DIR" "$CONFIG_PARENT_DIR" "$WORK_DIR"; do
check_path="$(get_realpath "$path")"
case "$check_path" in
/|/bin|/sbin|/usr|/usr/bin|/usr/sbin|/usr/local|/usr/local/bin|/usr/local/sbin|/usr/local/etc|/usr/local/share|/etc|/var|/var/lib|/var/log|/var/run|/home|/root|/tmp|/lib|/lib64|/opt|/run|/boot|/dev|/sys|/proc)
die "Safety check failed: '$path' (resolved to '$check_path') is a critical system directory." ;;
esac
done
check_install_dir="$(get_realpath "$INSTALL_DIR")"
case "$check_install_dir" in
/|/etc|/var|/home|/root|/tmp|/usr|/usr/local|/opt|/boot|/dev|/sys|/proc|/run)
die "Safety check failed: INSTALL_DIR '$INSTALL_DIR' is a critical system directory." ;;
esac
for cmd in id uname grep find rm chown chmod mv mktemp mkdir tr dd sed ps head sleep cat tar gzip rmdir; do
command -v "$cmd" >/dev/null 2>&1 || die "Required command not found: $cmd"
done
}
@ -221,41 +185,14 @@ 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 cp >/dev/null 2>&1 || command -v install >/dev/null 2>&1 || die "Need cp or install"
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
$SUDO apk add --no-cache libcap-utils libcap conntrack-tools >/dev/null 2>&1 || true
$SUDO apk add --no-cache libcap-utils >/dev/null 2>&1 || $SUDO apk add --no-cache libcap >/dev/null 2>&1 || true
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 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
}
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 yum >/dev/null 2>&1; then $SUDO yum install -y -q libcap conntrack-tools >/dev/null 2>&1 || true
fi
fi
}
check_port_availability() {
port_info=""
if command -v ss >/dev/null 2>&1; then
port_info=$($SUDO ss -tulnp 2>/dev/null | grep -E ":${SERVER_PORT}([[:space:]]|$)" || true)
elif command -v netstat >/dev/null 2>&1; then
port_info=$($SUDO netstat -tulnp 2>/dev/null | grep -E ":${SERVER_PORT}([[:space:]]|$)" || true)
elif command -v lsof >/dev/null 2>&1; then
port_info=$($SUDO lsof -i :${SERVER_PORT} 2>/dev/null | grep LISTEN || true)
else
say "[WARNING] Network diagnostic tools (ss, netstat, lsof) not found. Skipping port check."
return 0
fi
if [ -n "$port_info" ]; 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."
else
say "[ERROR] Port ${SERVER_PORT} is already in use by another process:"
printf ' %s\n' "$port_info"
die "Please free the port ${SERVER_PORT} or change it and try again."
$SUDO apt-get update -q >/dev/null 2>&1 || true
$SUDO 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 >/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
}
@ -313,10 +250,10 @@ ensure_user_group() {
setup_dirs() {
$SUDO mkdir -p "$WORK_DIR" "$CONFIG_DIR" "$CONFIG_PARENT_DIR" || die "Failed to create directories"
$SUDO chown telemt:telemt "$WORK_DIR" && $SUDO chmod 750 "$WORK_DIR"
$SUDO chown root:telemt "$CONFIG_DIR" && $SUDO chmod 750 "$CONFIG_DIR"
if [ "$CONFIG_PARENT_DIR" != "$CONFIG_DIR" ] && [ "$CONFIG_PARENT_DIR" != "." ] && [ "$CONFIG_PARENT_DIR" != "/" ]; then
$SUDO chown root:telemt "$CONFIG_PARENT_DIR" && $SUDO chmod 750 "$CONFIG_PARENT_DIR"
fi
@ -338,19 +275,17 @@ install_binary() {
fi
$SUDO mkdir -p "$INSTALL_DIR" || die "Failed to create install directory"
$SUDO rm -f "$bin_dst" 2>/dev/null || true
if command -v install >/dev/null 2>&1; then
$SUDO install -m 0755 "$bin_src" "$bin_dst" || die "Failed to install binary"
else
$SUDO rm -f "$bin_dst" 2>/dev/null || true
$SUDO cp "$bin_src" "$bin_dst" && $SUDO chmod 0755 "$bin_dst" || die "Failed to copy binary"
fi
$SUDO sh -c '[ -x "$1" ]' _ "$bin_dst" || die "Binary not executable: $bin_dst"
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=+ep "$bin_dst" 2>/dev/null || true
fi
}
@ -366,20 +301,11 @@ generate_secret() {
}
generate_config_content() {
conf_secret="$1"
conf_tag="$2"
escaped_tls_domain="$(printf '%s\n' "$TLS_DOMAIN" | tr -d '[:cntrl:]' | sed 's/\\/\\\\/g; s/"/\\"/g')"
cat <<EOF
[general]
use_middle_proxy = true
EOF
if [ -n "$conf_tag" ]; then
echo "ad_tag = \"${conf_tag}\""
fi
cat <<EOF
use_middle_proxy = false
[general.modes]
classic = false
@ -387,7 +313,7 @@ secure = false
tls = true
[server]
port = ${SERVER_PORT}
port = 443
[server.api]
enabled = true
@ -398,73 +324,28 @@ whitelist = ["127.0.0.1/32"]
tls_domain = "${escaped_tls_domain}"
[access.users]
hello = "${conf_secret}"
hello = "$1"
EOF
}
install_config() {
if is_config_exists; then
say " -> Config already exists at $CONFIG_FILE. Updating parameters..."
tmp_conf="${TEMP_DIR}/config.tmp"
$SUDO cat "$CONFIG_FILE" > "$tmp_conf"
escaped_domain="$(printf '%s\n' "$TLS_DOMAIN" | tr -d '[:cntrl:]' | sed 's/\\/\\\\/g; s/"/\\"/g')"
export AWK_PORT="$SERVER_PORT"
export AWK_SECRET="$USER_SECRET"
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 }
ENVIRON["AWK_FLAG_P"] == "1" && /^[ \t]*port[ \t]*=/ { print "port = " ENVIRON["AWK_PORT"]; next }
ENVIRON["AWK_FLAG_S"] == "1" && /^[ \t]*hello[ \t]*=/ { print "hello = \"" ENVIRON["AWK_SECRET"] "\""; next }
ENVIRON["AWK_FLAG_D"] == "1" && /^[ \t]*tls_domain[ \t]*=/ { print "tls_domain = \"" ENVIRON["AWK_DOMAIN"] "\""; next }
ENVIRON["AWK_FLAG_A"] == "1" && /^[ \t]*ad_tag[ \t]*=/ {
if (!ad_tag_handled) {
print "ad_tag = \"" ENVIRON["AWK_AD_TAG"] "\"";
ad_tag_handled = 1;
}
next
}
ENVIRON["AWK_FLAG_A"] == "1" && /^\[general\]/ {
print;
if (!ad_tag_handled) {
print "ad_tag = \"" ENVIRON["AWK_AD_TAG"] "\"";
ad_tag_handled = 1;
}
next
}
{ print }
' "$tmp_conf" > "${tmp_conf}.new" && mv "${tmp_conf}.new" "$tmp_conf"
[ "$PORT_PROVIDED" -eq 1 ] && say " -> Updated port: $SERVER_PORT"
[ "$SECRET_PROVIDED" -eq 1 ] && say " -> Updated secret for user 'hello'"
[ "$DOMAIN_PROVIDED" -eq 1 ] && say " -> Updated tls_domain: $TLS_DOMAIN"
[ "$AD_TAG_PROVIDED" -eq 1 ] && say " -> Updated ad_tag"
write_root "$CONFIG_FILE" < "$tmp_conf"
rm -f "$tmp_conf"
if [ -n "$SUDO" ]; then
if $SUDO sh -c '[ -f "$1" ]' _ "$CONFIG_FILE"; then
say " -> Config already exists at $CONFIG_FILE. Skipping creation."
return 0
fi
elif [ -f "$CONFIG_FILE" ]; then
say " -> Config already exists at $CONFIG_FILE. Skipping creation."
return 0
fi
if [ -z "$USER_SECRET" ]; then
USER_SECRET="$(generate_secret)" || die "Failed to generate secret."
fi
toml_secret="$(generate_secret)" || die "Failed to generate secret."
generate_config_content "$USER_SECRET" "$AD_TAG" | write_root "$CONFIG_FILE" || die "Failed to install config"
generate_config_content "$toml_secret" | write_root "$CONFIG_FILE" || die "Failed to install config"
$SUDO chown root:telemt "$CONFIG_FILE" && $SUDO chmod 640 "$CONFIG_FILE"
say " -> Config created successfully."
say " -> Configured secret for user 'hello': $USER_SECRET"
say " -> Generated secret for default user 'hello': $toml_secret"
}
generate_systemd_content() {
@ -481,10 +362,9 @@ Group=telemt
WorkingDirectory=$WORK_DIR
ExecStart="${INSTALL_DIR}/${BIN_NAME}" "${CONFIG_FILE}"
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_ADMIN
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_ADMIN
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
[Install]
WantedBy=multi-user.target
@ -515,7 +395,7 @@ install_service() {
$SUDO systemctl daemon-reload || true
$SUDO systemctl enable "$SERVICE_NAME" || true
if ! $SUDO systemctl start "$SERVICE_NAME"; then
say "[WARNING] Failed to start service"
SERVICE_START_FAILED=1
@ -525,16 +405,16 @@ install_service() {
$SUDO chown root:root "/etc/init.d/${SERVICE_NAME}" && $SUDO chmod 0755 "/etc/init.d/${SERVICE_NAME}"
$SUDO rc-update add "$SERVICE_NAME" default 2>/dev/null || true
if ! $SUDO rc-service "$SERVICE_NAME" start 2>/dev/null; then
say "[WARNING] Failed to start service"
SERVICE_START_FAILED=1
fi
else
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"
else
else
say " -> Service manager not found. Start manually: su -s /bin/sh telemt -c '$cmd'"
fi
fi
@ -549,10 +429,9 @@ kill_user_procs() {
if command -v pgrep >/dev/null 2>&1; then
pids="$(pgrep -u telemt 2>/dev/null || true)"
else
pids="$(ps -ef 2>/dev/null | awk '$1=="telemt"{print $2}' || true)"
[ -z "$pids" ] && pids="$(ps 2>/dev/null | awk '$2=="telemt"{print $1}' || true)"
pids="$(ps -u telemt -o pid= 2>/dev/null || true)"
fi
if [ -n "$pids" ]; then
for pid in $pids; do
case "$pid" in ''|*[!0-9]*) continue ;; *) $SUDO kill "$pid" 2>/dev/null || true ;; esac
@ -592,16 +471,15 @@ uninstall() {
say ">>> Stage 5: Purging configuration, data, and user"
$SUDO rm -rf "$CONFIG_DIR" "$WORK_DIR"
$SUDO rm -f "$CONFIG_FILE"
sleep 1
$SUDO userdel telemt 2>/dev/null || $SUDO deluser telemt 2>/dev/null || true
if check_os_entity group telemt; then
$SUDO groupdel telemt 2>/dev/null || $SUDO delgroup telemt 2>/dev/null || true
if [ "$CONFIG_PARENT_DIR" != "$CONFIG_DIR" ] && [ "$CONFIG_PARENT_DIR" != "." ] && [ "$CONFIG_PARENT_DIR" != "/" ]; then
$SUDO rmdir "$CONFIG_PARENT_DIR" 2>/dev/null || true
fi
$SUDO userdel telemt 2>/dev/null || $SUDO deluser telemt 2>/dev/null || true
$SUDO groupdel telemt 2>/dev/null || $SUDO delgroup telemt 2>/dev/null || true
else
say "Note: Configuration and user kept. Run with 'purge' to remove completely."
fi
printf '\n====================================================================\n'
printf ' UNINSTALLATION COMPLETE\n'
printf '====================================================================\n\n'
@ -615,28 +493,18 @@ case "$ACTION" in
say "Starting installation of $BIN_NAME (Version: $TARGET_VERSION)"
say ">>> Stage 1: Verifying environment and dependencies"
verify_common
verify_install_deps
verify_common; verify_install_deps
if is_config_exists && [ "$PORT_PROVIDED" -eq 0 ]; then
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
SERVER_PORT="$ext_port"
fi
fi
check_port_availability
if [ "$TARGET_VERSION" != "latest" ]; then
if [ "$TARGET_VERSION" != "latest" ]; then
TARGET_VERSION="${TARGET_VERSION#v}"
fi
ARCH="$(detect_arch)"; LIBC="$(detect_libc)"
FILE_NAME="${BIN_NAME}-${ARCH}-linux-${LIBC}.tar.gz"
if [ "$TARGET_VERSION" = "latest" ]; then
DL_URL="https://github.com/${REPO}/releases/latest/download/${FILE_NAME}"
else
else
DL_URL="https://github.com/${REPO}/releases/download/${TARGET_VERSION}/${FILE_NAME}"
fi
@ -653,7 +521,7 @@ case "$ACTION" in
FILE_NAME="${BIN_NAME}-${ARCH}-linux-${LIBC}.tar.gz"
if [ "$TARGET_VERSION" = "latest" ]; then
DL_URL="https://github.com/${REPO}/releases/latest/download/${FILE_NAME}"
else
else
DL_URL="https://github.com/${REPO}/releases/download/${TARGET_VERSION}/${FILE_NAME}"
fi
fetch_file "$DL_URL" "${TEMP_DIR}/${FILE_NAME}" || die "Download failed"
@ -672,13 +540,13 @@ case "$ACTION" in
say ">>> Stage 4: Setting up environment (User, Group, Directories)"
ensure_user_group; setup_dirs; stop_service
say ">>> Stage 5: Installing binary"
install_binary "$EXTRACTED_BIN" "${INSTALL_DIR}/${BIN_NAME}"
say ">>> Stage 6: Generating/Updating configuration"
say ">>> Stage 6: Generating configuration"
install_config
say ">>> Stage 7: Installing and starting service"
install_service
@ -693,7 +561,7 @@ case "$ACTION" in
printf ' INSTALLATION SUCCESS\n'
printf '====================================================================\n\n'
fi
svc="$(get_svc_mgr)"
if [ "$svc" = "systemd" ]; then
printf 'To check the status of your proxy service, run:\n'
@ -702,18 +570,15 @@ case "$ACTION" in
printf 'To check the status of your proxy service, run:\n'
printf ' rc-service %s status\n\n' "$SERVICE_NAME"
fi
API_LISTEN="$($SUDO awk -F'"' '/^[ \t]*listen[ \t]*=/ {print $2; exit}' "$CONFIG_FILE" 2>/dev/null || true)"
API_LISTEN="${API_LISTEN:-127.0.0.1:9091}"
printf 'To get your user connection links (for Telegram), run:\n'
if command -v jq >/dev/null 2>&1; then
printf ' curl -s http://%s/v1/users | jq -r '\''.data[]? | "User: \\(.username)\\n\\(.links.tls[0] // empty)\\n"'\''\n' "$API_LISTEN"
printf ' curl -s http://127.0.0.1:9091/v1/users | jq -r '\''.data[] | "User: \\(.username)\\n\\(.links.tls[0] // empty)\\n"'\''\n'
else
printf ' curl -s http://%s/v1/users\n' "$API_LISTEN"
printf ' curl -s http://127.0.0.1:9091/v1/users\n'
printf ' (Tip: Install '\''jq'\'' for a much cleaner output)\n'
fi
printf '\n====================================================================\n'
;;
esac