Compare commits

..

14 Commits
3.3.38 ... main

Author SHA1 Message Date
Alexey 6996d6e597
Update LICENSE 2026-04-06 13:21:16 +03:00
Alexey b3f11624c9
Update LICENSE 2026-04-06 13:12:06 +03:00
Alexey 4d87a790cc
Merge pull request #626 from vladon/fix/strip-release-binaries
[codex] Strip release binaries before packaging
2026-04-05 21:12:07 +03:00
Alexey 07fed8f871
Merge pull request #632 from SysAdminKo/main
Актуализация документации CONFIG_PARAMS
2026-04-05 21:10:58 +03:00
Alexey 407d686d49
Merge pull request #638 from Dimasssss/patch-1
Update install.sh - add port availability check and new CLI arguments + update QUICK_START_GUIDE - add CAP_NET_ADMIN Service
2026-04-05 21:06:29 +03:00
Dimasssss eac5cc81fb
Update QUICK_START_GUIDE.ru.md 2026-04-05 18:53:16 +03:00
Dimasssss c51d16f403
Update QUICK_START_GUIDE.en.md 2026-04-05 18:53:06 +03:00
Dimasssss b5146bba94
Update install.sh 2026-04-05 18:43:08 +03:00
SysAdminKo 5ed525fa48
Add server.conntrack_control configuration section with detailed parameters and descriptions
This update introduces a new section in the configuration documentation for `server.conntrack_control`, outlining various parameters such as `inline_conntrack_control`, `mode`, `backend`, `profile`, `hybrid_listener_ips`, `pressure_high_watermark_pct`, `pressure_low_watermark_pct`, and `delete_budget_per_sec`. Each parameter includes constraints, descriptions, and examples to assist users in configuring conntrack control effectively.
2026-04-05 18:05:13 +03:00
Олегсей Бреднев 9f7c1693ce
Merge branch 'telemt:main' into main 2026-04-05 17:42:08 +03:00
Dimasssss 1524396e10
Update install.sh
Новые аргументы командной строки:
-d, --domain : TLS-домен (дефолт: petrovich.ru)
-p, --port : Порт сервера (дефолт: 443)
-s, --secret : Секрет пользователя (32 hex-символа)
-a, --ad-tag : Установка ad_tag

⚠️ Если эти флаги переданы при запуске, они заменят собой старые сохраненные значения.
2026-04-05 17:32:21 +03:00
SysAdminKo 444a20672d
Refine CONFIG_PARAMS documentation by updating default values to use a dash (—) for optional parameters instead of null. Adjust constraints for clarity, ensuring all types are accurately represented as required. Enhance descriptions for better understanding of configuration options. 2026-04-04 21:56:24 +03:00
Vlad Yaroslavlev d673935b6d
Merge branch 'main' into fix/strip-release-binaries 2026-04-03 00:35:20 +03:00
Vladislav Yaroslavlev 363b5014f7
Strip release binaries before packaging 2026-04-03 00:17:43 +03:00
6 changed files with 421 additions and 155 deletions

View File

@ -151,6 +151,14 @@ jobs:
mkdir -p dist mkdir -p dist
cp "target/${{ matrix.target }}/release/${{ env.BINARY_NAME }}" dist/telemt 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 cd dist
tar -czf "${{ matrix.asset }}.tar.gz" \ tar -czf "${{ matrix.asset }}.tar.gz" \
--owner=0 --group=0 --numeric-owner \ --owner=0 --group=0 --numeric-owner \
@ -279,6 +287,14 @@ jobs:
mkdir -p dist mkdir -p dist
cp "target/${{ matrix.target }}/release/${{ env.BINARY_NAME }}" dist/telemt 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 cd dist
tar -czf "${{ matrix.asset }}.tar.gz" \ tar -czf "${{ matrix.asset }}.tar.gz" \
--owner=0 --group=0 --numeric-owner \ --owner=0 --group=0 --numeric-owner \

16
LICENSE
View File

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

View File

@ -14,10 +14,10 @@ This document lists all configuration keys accepted by `config.toml`.
| Key | Type | Default | | Key | Type | Default |
| --- | ---- | ------- | | --- | ---- | ------- |
| [`include`](#cfg-top-include) | `String` (special directive) | `null` | | [`include`](#cfg-top-include) | `String` (special directive) | |
| [`show_link`](#cfg-top-show_link) | `"*"` or `String[]` | `[]` (`ShowLink::None`) | | [`show_link`](#cfg-top-show_link) | `"*"` or `String[]` | `[]` (`ShowLink::None`) |
| [`dc_overrides`](#cfg-top-dc_overrides) | `Map<String, String or String[]>` | `{}` | | [`dc_overrides`](#cfg-top-dc_overrides) | `Map<String, String or String[]>` | `{}` |
| [`default_dc`](#cfg-top-default_dc) | `u8` or `null` | `null` (effective fallback: `2` in ME routing) | | [`default_dc`](#cfg-top-default_dc) | `u8` | — (effective fallback: `2` in ME routing) |
<a id="cfg-top-include"></a> <a id="cfg-top-include"></a>
- `include` - `include`
@ -68,17 +68,17 @@ This document lists all configuration keys accepted by `config.toml`.
| Key | Type | Default | | Key | Type | Default |
| --- | ---- | ------- | | --- | ---- | ------- |
| [`data_path`](#cfg-general-data_path) | `String` or `null` | `null` | | [`data_path`](#cfg-general-data_path) | `String` | — |
| [`prefer_ipv6`](#cfg-general-prefer_ipv6) | `bool` | `false` | | [`prefer_ipv6`](#cfg-general-prefer_ipv6) | `bool` | `false` |
| [`fast_mode`](#cfg-general-fast_mode) | `bool` | `true` | | [`fast_mode`](#cfg-general-fast_mode) | `bool` | `true` |
| [`use_middle_proxy`](#cfg-general-use_middle_proxy) | `bool` | `true` | | [`use_middle_proxy`](#cfg-general-use_middle_proxy) | `bool` | `true` |
| [`proxy_secret_path`](#cfg-general-proxy_secret_path) | `String` or `null` | `"proxy-secret"` | | [`proxy_secret_path`](#cfg-general-proxy_secret_path) | `String` | `"proxy-secret"` |
| [`proxy_config_v4_cache_path`](#cfg-general-proxy_config_v4_cache_path) | `String` or `null` | `"cache/proxy-config-v4.txt"` | | [`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` or `null` | `"cache/proxy-config-v6.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` or `null` | `null` | | [`ad_tag`](#cfg-general-ad_tag) | `String` | — |
| [`middle_proxy_nat_ip`](#cfg-general-middle_proxy_nat_ip) | `IpAddr` or `null` | `null` | | [`middle_proxy_nat_ip`](#cfg-general-middle_proxy_nat_ip) | `IpAddr` | — |
| [`middle_proxy_nat_probe`](#cfg-general-middle_proxy_nat_probe) | `bool` | `true` | | [`middle_proxy_nat_probe`](#cfg-general-middle_proxy_nat_probe) | `bool` | `true` |
| [`middle_proxy_nat_stun`](#cfg-general-middle_proxy_nat_stun) | `String` or `null` | `null` | | [`middle_proxy_nat_stun`](#cfg-general-middle_proxy_nat_stun) | `String` | — |
| [`middle_proxy_nat_stun_servers`](#cfg-general-middle_proxy_nat_stun_servers) | `String[]` | `[]` | | [`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` | | [`stun_nat_probe_concurrency`](#cfg-general-stun_nat_probe_concurrency) | `usize` | `8` |
| [`middle_proxy_pool_size`](#cfg-general-middle_proxy_pool_size) | `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_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` | | [`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` | | [`stun_iface_mismatch_ignore`](#cfg-general-stun_iface_mismatch_ignore) | `bool` | `false` |
| [`unknown_dc_log_path`](#cfg-general-unknown_dc_log_path) | `String` or `null` | `"unknown-dc.txt"` | | [`unknown_dc_log_path`](#cfg-general-unknown_dc_log_path) | `String` | `"unknown-dc.txt"` |
| [`unknown_dc_file_log_enabled`](#cfg-general-unknown_dc_file_log_enabled) | `bool` | `false` | | [`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"` | | [`log_level`](#cfg-general-log_level) | `"debug"`, `"verbose"`, `"normal"`, or `"silent"` | `"normal"` |
| [`disable_colors`](#cfg-general-disable_colors) | `bool` | `false` | | [`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_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` | | [`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` | | [`fast_mode_min_tls_record`](#cfg-general-fast_mode_min_tls_record) | `usize` | `0` |
| [`update_every`](#cfg-general-update_every) | `u64` or `null` | `300` | | [`update_every`](#cfg-general-update_every) | `u64` | `300` |
| [`me_reinit_every_secs`](#cfg-general-me_reinit_every_secs) | `u64` | `900` | | [`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_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` | | [`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> <a id="cfg-general-data_path"></a>
- `data_path` - `data_path`
- **Constraints / validation**: `String` or `null`. - **Constraints / validation**: `String` (optional).
- **Description**: Optional runtime data directory path. - **Description**: Optional runtime data directory path.
- **Example**: - **Example**:
@ -245,7 +245,7 @@ This document lists all configuration keys accepted by `config.toml`.
``` ```
<a id="cfg-general-proxy_secret_path"></a> <a id="cfg-general-proxy_secret_path"></a>
- `proxy_secret_path` - `proxy_secret_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). - **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).
- **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. - **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**: - **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> <a id="cfg-general-proxy_config_v4_cache_path"></a>
- `proxy_config_v4_cache_path` - `proxy_config_v4_cache_path`
- **Constraints / validation**: `String` or `null`. When set, must not be empty/whitespace-only. - **Constraints / validation**: `String`. 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. - **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**: - **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> <a id="cfg-general-proxy_config_v6_cache_path"></a>
- `proxy_config_v6_cache_path` - `proxy_config_v6_cache_path`
- **Constraints / validation**: `String` or `null`. When set, must not be empty/whitespace-only. - **Constraints / validation**: `String`. 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. - **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**: - **Example**:
@ -275,7 +275,7 @@ This document lists all configuration keys accepted by `config.toml`.
``` ```
<a id="cfg-general-ad_tag"></a> <a id="cfg-general-ad_tag"></a>
- `ad_tag` - `ad_tag`
- **Constraints / validation**: `String` or `null`. When set, must be exactly 32 hex characters; invalid values are disabled during config load. - **Constraints / validation**: `String` (optional). 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`. - **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**: - **Example**:
@ -285,7 +285,7 @@ This document lists all configuration keys accepted by `config.toml`.
``` ```
<a id="cfg-general-middle_proxy_nat_ip"></a> <a id="cfg-general-middle_proxy_nat_ip"></a>
- `middle_proxy_nat_ip` - `middle_proxy_nat_ip`
- **Constraints / validation**: `IpAddr` or `null`. - **Constraints / validation**: `IpAddr` (optional).
- **Description**: Manual public NAT IP override used as ME address material when set. - **Description**: Manual public NAT IP override used as ME address material when set.
- **Example**: - **Example**:
@ -967,8 +967,8 @@ This document lists all configuration keys accepted by `config.toml`.
``` ```
<a id="cfg-general-unknown_dc_log_path"></a> <a id="cfg-general-unknown_dc_log_path"></a>
- `unknown_dc_log_path` - `unknown_dc_log_path`
- **Constraints / validation**: `String` or `null`. Must be a safe path (no `..` components, parent directory must exist); unsafe paths are rejected at runtime. - **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`. Set to `null` to disable file logging. - **Description**: Log file path for unknown (non-standard) DC requests when `unknown_dc_file_log_enabled = true`. Omit this key to disable file logging.
- **Example**: - **Example**:
```toml ```toml
@ -1157,7 +1157,7 @@ This document lists all configuration keys accepted by `config.toml`.
``` ```
<a id="cfg-general-update_every"></a> <a id="cfg-general-update_every"></a>
- `update_every` - `update_every`
- **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`. - **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`).
- **Description**: Unified refresh interval for ME updater tasks (`getProxyConfig`, `getProxyConfigV6`, `getProxySecret`). When set, it overrides legacy proxy reload intervals. - **Description**: Unified refresh interval for ME updater tasks (`getProxyConfig`, `getProxyConfigV6`, `getProxySecret`). When set, it overrides legacy proxy reload intervals.
- **Example**: - **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> <a id="cfg-general-proxy_secret_auto_reload_secs"></a>
- `proxy_secret_auto_reload_secs` - `proxy_secret_auto_reload_secs`
- **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`. - **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`.
- **Description**: Deprecated legacy proxy-secret refresh interval. Used only when `general.update_every` is not set. - **Description**: Deprecated legacy proxy-secret refresh interval. Used only when `general.update_every` is not set.
- **Example**: - **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> <a id="cfg-general-proxy_config_auto_reload_secs"></a>
- `proxy_config_auto_reload_secs` - `proxy_config_auto_reload_secs`
- **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`. - **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`.
- **Description**: Deprecated legacy ME config refresh interval. Used only when `general.update_every` is not set. - **Description**: Deprecated legacy ME config refresh interval. Used only when `general.update_every` is not set.
- **Example**: - **Example**:
@ -1624,8 +1624,8 @@ This document lists all configuration keys accepted by `config.toml`.
| Key | Type | Default | | Key | Type | Default |
| --- | ---- | ------- | | --- | ---- | ------- |
| [`show`](#cfg-general-links-show) | `"*"` or `String[]` | `"*"` | | [`show`](#cfg-general-links-show) | `"*"` or `String[]` | `"*"` |
| [`public_host`](#cfg-general-links-public_host) | `String` or `null` | `null` | | [`public_host`](#cfg-general-links-public_host) | `String` | — |
| [`public_port`](#cfg-general-links-public_port) | `u16` or `null` | `null` | | [`public_port`](#cfg-general-links-public_port) | `u16` | — |
<a id="cfg-general-links-show"></a> <a id="cfg-general-links-show"></a>
- `show` - `show`
@ -1641,7 +1641,7 @@ This document lists all configuration keys accepted by `config.toml`.
``` ```
<a id="cfg-general-links-public_host"></a> <a id="cfg-general-links-public_host"></a>
- `public_host` - `public_host`
- **Constraints / validation**: `String` or `null`. - **Constraints / validation**: `String` (optional).
- **Description**: Public hostname/IP override used for generated `tg://` links (overrides detected IP). - **Description**: Public hostname/IP override used for generated `tg://` links (overrides detected IP).
- **Example**: - **Example**:
@ -1651,7 +1651,7 @@ This document lists all configuration keys accepted by `config.toml`.
``` ```
<a id="cfg-general-links-public_port"></a> <a id="cfg-general-links-public_port"></a>
- `public_port` - `public_port`
- **Constraints / validation**: `u16` or `null`. - **Constraints / validation**: `u16` (optional).
- **Description**: Public port override used for generated `tg://` links (overrides `server.port`). - **Description**: Public port override used for generated `tg://` links (overrides `server.port`).
- **Example**: - **Example**:
@ -1708,7 +1708,7 @@ This document lists all configuration keys accepted by `config.toml`.
| Key | Type | Default | | Key | Type | Default |
| --- | ---- | ------- | | --- | ---- | ------- |
| [`ipv4`](#cfg-network-ipv4) | `bool` | `true` | | [`ipv4`](#cfg-network-ipv4) | `bool` | `true` |
| [`ipv6`](#cfg-network-ipv6) | `bool` or `null` | `false` | | [`ipv6`](#cfg-network-ipv6) | `bool` | `false` |
| [`prefer`](#cfg-network-prefer) | `u8` | `4` | | [`prefer`](#cfg-network-prefer) | `u8` | `4` |
| [`multipath`](#cfg-network-multipath) | `bool` | `false` | | [`multipath`](#cfg-network-multipath) | `bool` | `false` |
| [`stun_use`](#cfg-network-stun_use) | `bool` | `true` | | [`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> <a id="cfg-network-ipv6"></a>
- `ipv6` - `ipv6`
- **Constraints / validation**: `bool` or `null`. `null` means "auto-detect IPv6 availability". - **Constraints / validation**: `bool`.
- **Description**: Enables/disables IPv6 when explicitly set; when `null`, Telemt will auto-detect IPv6 availability at runtime. - **Description**: Enables/disables IPv6 networking. When omitted, defaults to `false`.
- **Example**: - **Example**:
```toml ```toml
@ -1741,9 +1741,6 @@ This document lists all configuration keys accepted by `config.toml`.
# or: disable IPv6 explicitly # or: disable IPv6 explicitly
# ipv6 = false # ipv6 = false
# or: let Telemt auto-detect
# ipv6 = null
``` ```
<a id="cfg-network-prefer"></a> <a id="cfg-network-prefer"></a>
- `prefer` - `prefer`
@ -1842,16 +1839,16 @@ This document lists all configuration keys accepted by `config.toml`.
| Key | Type | Default | | Key | Type | Default |
| --- | ---- | ------- | | --- | ---- | ------- |
| [`port`](#cfg-server-port) | `u16` | `443` | | [`port`](#cfg-server-port) | `u16` | `443` |
| [`listen_addr_ipv4`](#cfg-server-listen_addr_ipv4) | `String` or `null` | `"0.0.0.0"` | | [`listen_addr_ipv4`](#cfg-server-listen_addr_ipv4) | `String` | `"0.0.0.0"` |
| [`listen_addr_ipv6`](#cfg-server-listen_addr_ipv6) | `String` or `null` | `"::"` | | [`listen_addr_ipv6`](#cfg-server-listen_addr_ipv6) | `String` | `"::"` |
| [`listen_unix_sock`](#cfg-server-listen_unix_sock) | `String` or `null` | `null` | | [`listen_unix_sock`](#cfg-server-listen_unix_sock) | `String` | — |
| [`listen_unix_sock_perm`](#cfg-server-listen_unix_sock_perm) | `String` or `null` | `null` | | [`listen_unix_sock_perm`](#cfg-server-listen_unix_sock_perm) | `String` | — |
| [`listen_tcp`](#cfg-server-listen_tcp) | `bool` or `null` | `null` (auto) | | [`listen_tcp`](#cfg-server-listen_tcp) | `bool` | — (auto) |
| [`proxy_protocol`](#cfg-server-proxy_protocol) | `bool` | `false` | | [`proxy_protocol`](#cfg-server-proxy_protocol) | `bool` | `false` |
| [`proxy_protocol_header_timeout_ms`](#cfg-server-proxy_protocol_header_timeout_ms) | `u64` | `500` | | [`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[]` | `[]` | | [`proxy_protocol_trusted_cidrs`](#cfg-server-proxy_protocol_trusted_cidrs) | `IpNetwork[]` | `[]` |
| [`metrics_port`](#cfg-server-metrics_port) | `u16` or `null` | `null` | | [`metrics_port`](#cfg-server-metrics_port) | `u16` | — |
| [`metrics_listen`](#cfg-server-metrics_listen) | `String` or `null` | `null` | | [`metrics_listen`](#cfg-server-metrics_listen) | `String` | — |
| [`metrics_whitelist`](#cfg-server-metrics_whitelist) | `IpNetwork[]` | `["127.0.0.1/32", "::1/128"]` | | [`metrics_whitelist`](#cfg-server-metrics_whitelist) | `IpNetwork[]` | `["127.0.0.1/32", "::1/128"]` |
| [`max_connections`](#cfg-server-max_connections) | `u32` | `10000` | | [`max_connections`](#cfg-server-max_connections) | `u32` | `10000` |
| [`accept_permit_timeout_ms`](#cfg-server-accept_permit_timeout_ms) | `u64` | `250` | | [`accept_permit_timeout_ms`](#cfg-server-accept_permit_timeout_ms) | `u64` | `250` |
@ -1868,8 +1865,8 @@ This document lists all configuration keys accepted by `config.toml`.
``` ```
<a id="cfg-server-listen_addr_ipv4"></a> <a id="cfg-server-listen_addr_ipv4"></a>
- `listen_addr_ipv4` - `listen_addr_ipv4`
- **Constraints / validation**: `String` or `null`. When set, must be a valid IPv4 address string. - **Constraints / validation**: `String` (optional). When set, must be a valid IPv4 address string.
- **Description**: IPv4 bind address for TCP listener (`null` disables IPv4 bind). - **Description**: IPv4 bind address for TCP listener (omit this key to disable IPv4 bind).
- **Example**: - **Example**:
```toml ```toml
@ -1878,8 +1875,8 @@ This document lists all configuration keys accepted by `config.toml`.
``` ```
<a id="cfg-server-listen_addr_ipv6"></a> <a id="cfg-server-listen_addr_ipv6"></a>
- `listen_addr_ipv6` - `listen_addr_ipv6`
- **Constraints / validation**: `String` or `null`. When set, must be a valid IPv6 address string. - **Constraints / validation**: `String` (optional). When set, must be a valid IPv6 address string.
- **Description**: IPv6 bind address for TCP listener (`null` disables IPv6 bind). - **Description**: IPv6 bind address for TCP listener (omit this key to disable IPv6 bind).
- **Example**: - **Example**:
```toml ```toml
@ -1888,7 +1885,7 @@ This document lists all configuration keys accepted by `config.toml`.
``` ```
<a id="cfg-server-listen_unix_sock"></a> <a id="cfg-server-listen_unix_sock"></a>
- `listen_unix_sock` - `listen_unix_sock`
- **Constraints / validation**: `String` or `null`. Must not be empty when set. Unix only. - **Constraints / validation**: `String` (optional). 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). - **Description**: Unix socket path for listener. When set, `server.listen_tcp` defaults to `false` (unless explicitly overridden).
- **Example**: - **Example**:
@ -1898,8 +1895,8 @@ This document lists all configuration keys accepted by `config.toml`.
``` ```
<a id="cfg-server-listen_unix_sock_perm"></a> <a id="cfg-server-listen_unix_sock_perm"></a>
- `listen_unix_sock_perm` - `listen_unix_sock_perm`
- **Constraints / validation**: `String` or `null`. When set, should be an octal permission string like `"0666"` or `"0777"`. - **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). `null` means "no change" (inherits umask). - **Description**: Optional Unix socket file permissions applied after bind (chmod). When omitted, permissions are not changed (inherits umask).
- **Example**: - **Example**:
```toml ```toml
@ -1909,7 +1906,7 @@ This document lists all configuration keys accepted by `config.toml`.
``` ```
<a id="cfg-server-listen_tcp"></a> <a id="cfg-server-listen_tcp"></a>
- `listen_tcp` - `listen_tcp`
- **Constraints / validation**: `bool` or `null`. `null` means auto: - **Constraints / validation**: `bool` (optional). When omitted, Telemt auto-detects:
- `true` when `listen_unix_sock` is not set - `true` when `listen_unix_sock` is not set
- `false` when `listen_unix_sock` is set - `false` when `listen_unix_sock` is set
- **Description**: Explicit TCP listener enable/disable override. - **Description**: Explicit TCP listener enable/disable override.
@ -1957,7 +1954,7 @@ This document lists all configuration keys accepted by `config.toml`.
``` ```
<a id="cfg-server-metrics_port"></a> <a id="cfg-server-metrics_port"></a>
- `metrics_port` - `metrics_port`
- **Constraints / validation**: `u16` or `null`. - **Constraints / validation**: `u16` (optional).
- **Description**: Prometheus-compatible metrics endpoint port. When set, enables the metrics listener (bind behavior can be overridden by `metrics_listen`). - **Description**: Prometheus-compatible metrics endpoint port. When set, enables the metrics listener (bind behavior can be overridden by `metrics_listen`).
- **Example**: - **Example**:
@ -1967,7 +1964,7 @@ This document lists all configuration keys accepted by `config.toml`.
``` ```
<a id="cfg-server-metrics_listen"></a> <a id="cfg-server-metrics_listen"></a>
- `metrics_listen` - `metrics_listen`
- **Constraints / validation**: `String` or `null`. When set, must be in `IP:PORT` format. - **Constraints / validation**: `String` (optional). 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. - **Description**: Full metrics bind address (`IP:PORT`), overrides `metrics_port` and binds on the specified address only.
- **Example**: - **Example**:
@ -2010,6 +2007,105 @@ 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. 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] ## [server.api]
Note: This section also accepts the legacy alias `[server.admin_api]` (same schema as `[server.api]`). Note: This section also accepts the legacy alias `[server.admin_api]` (same schema as `[server.api]`).
@ -2158,9 +2254,9 @@ Note: This section also accepts the legacy alias `[server.admin_api]` (same sche
| Key | Type | Default | | Key | Type | Default |
| --- | ---- | ------- | | --- | ---- | ------- |
| [`ip`](#cfg-server-listeners-ip) | `IpAddr` | — | | [`ip`](#cfg-server-listeners-ip) | `IpAddr` | — |
| [`announce`](#cfg-server-listeners-announce) | `String` or `null` | — | | [`announce`](#cfg-server-listeners-announce) | `String` | — |
| [`announce_ip`](#cfg-server-listeners-announce_ip) | `IpAddr` or `null` | — | | [`announce_ip`](#cfg-server-listeners-announce_ip) | `IpAddr` | — |
| [`proxy_protocol`](#cfg-server-listeners-proxy_protocol) | `bool` or `null` | `null` | | [`proxy_protocol`](#cfg-server-listeners-proxy_protocol) | `bool` | — |
| [`reuse_allow`](#cfg-server-listeners-reuse_allow) | `bool` | `false` | | [`reuse_allow`](#cfg-server-listeners-reuse_allow) | `bool` | `false` |
<a id="cfg-server-listeners-ip"></a> <a id="cfg-server-listeners-ip"></a>
@ -2175,7 +2271,7 @@ Note: This section also accepts the legacy alias `[server.admin_api]` (same sche
``` ```
<a id="cfg-server-listeners-announce"></a> <a id="cfg-server-listeners-announce"></a>
- `announce` - `announce`
- **Constraints / validation**: `String` or `null`. Must not be empty when set. - **Constraints / validation**: `String` (optional). Must not be empty when set.
- **Description**: Public IP/domain announced in proxy links for this listener. Takes precedence over `announce_ip`. - **Description**: Public IP/domain announced in proxy links for this listener. Takes precedence over `announce_ip`.
- **Example**: - **Example**:
@ -2186,7 +2282,7 @@ Note: This section also accepts the legacy alias `[server.admin_api]` (same sche
``` ```
<a id="cfg-server-listeners-announce_ip"></a> <a id="cfg-server-listeners-announce_ip"></a>
- `announce_ip` - `announce_ip`
- **Constraints / validation**: `IpAddr` or `null`. Deprecated. Use `announce`. - **Constraints / validation**: `IpAddr` (optional). Deprecated. Use `announce`.
- **Description**: Deprecated legacy announce IP. During config load it is migrated to `announce` when `announce` is not set. - **Description**: Deprecated legacy announce IP. During config load it is migrated to `announce` when `announce` is not set.
- **Example**: - **Example**:
@ -2197,7 +2293,7 @@ Note: This section also accepts the legacy alias `[server.admin_api]` (same sche
``` ```
<a id="cfg-server-listeners-proxy_protocol"></a> <a id="cfg-server-listeners-proxy_protocol"></a>
- `proxy_protocol` - `proxy_protocol`
- **Constraints / validation**: `bool` or `null`. When set, overrides `server.proxy_protocol` for this listener. - **Constraints / validation**: `bool` (optional). When set, overrides `server.proxy_protocol` for this listener.
- **Description**: Per-listener PROXY protocol override. - **Description**: Per-listener PROXY protocol override.
- **Example**: - **Example**:
@ -2351,9 +2447,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_scope`](#cfg-censorship-tls_fetch_scope) | `String` | `""` |
| [`tls_fetch`](#cfg-censorship-tls_fetch) | `Table` | built-in defaults | | [`tls_fetch`](#cfg-censorship-tls_fetch) | `Table` | built-in defaults |
| [`mask`](#cfg-censorship-mask) | `bool` | `true` | | [`mask`](#cfg-censorship-mask) | `bool` | `true` |
| [`mask_host`](#cfg-censorship-mask_host) | `String` or `null` | `null` | | [`mask_host`](#cfg-censorship-mask_host) | `String` | — |
| [`mask_port`](#cfg-censorship-mask_port) | `u16` | `443` | | [`mask_port`](#cfg-censorship-mask_port) | `u16` | `443` |
| [`mask_unix_sock`](#cfg-censorship-mask_unix_sock) | `String` or `null` | `null` | | [`mask_unix_sock`](#cfg-censorship-mask_unix_sock) | `String` | — |
| [`fake_cert_len`](#cfg-censorship-fake_cert_len) | `usize` | `2048` | | [`fake_cert_len`](#cfg-censorship-fake_cert_len) | `usize` | `2048` |
| [`tls_emulation`](#cfg-censorship-tls_emulation) | `bool` | `true` | | [`tls_emulation`](#cfg-censorship-tls_emulation) | `bool` | `true` |
| [`tls_front_dir`](#cfg-censorship-tls_front_dir) | `String` | `"tlsfront"` | | [`tls_front_dir`](#cfg-censorship-tls_front_dir) | `String` | `"tlsfront"` |
@ -2440,8 +2536,8 @@ Note: This section also accepts the legacy alias `[server.admin_api]` (same sche
``` ```
<a id="cfg-censorship-mask_host"></a> <a id="cfg-censorship-mask_host"></a>
- `mask_host` - `mask_host`
- **Constraints / validation**: `String` or `null`. - **Constraints / validation**: `String` (optional).
- If `mask_unix_sock` is set, `mask_host` must be `null` (mutually exclusive). - If `mask_unix_sock` is set, `mask_host` must be omitted (mutually exclusive).
- If `mask_host` is not set and `mask_unix_sock` is not set, Telemt defaults `mask_host` to `tls_domain`. - 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. - **Description**: Upstream mask host for TLS fronting relay.
- **Example**: - **Example**:
@ -2462,7 +2558,7 @@ Note: This section also accepts the legacy alias `[server.admin_api]` (same sche
``` ```
<a id="cfg-censorship-mask_unix_sock"></a> <a id="cfg-censorship-mask_unix_sock"></a>
- `mask_unix_sock` - `mask_unix_sock`
- **Constraints / validation**: `String` or `null`. - **Constraints / validation**: `String` (optional).
- Must not be empty when set. - Must not be empty when set.
- Unix only; rejected on non-Unix platforms. - Unix only; rejected on non-Unix platforms.
- On Unix, must be \(\le 107\) bytes (path length limit). - On Unix, must be \(\le 107\) bytes (path length limit).
@ -2882,6 +2978,7 @@ If your backend or network is very bandwidth-constrained, reduce cap first. If p
| [`users`](#cfg-access-users) | `Map<String, String>` | `{"default": "000…000"}` | | [`users`](#cfg-access-users) | `Map<String, String>` | `{"default": "000…000"}` |
| [`user_ad_tags`](#cfg-access-user_ad_tags) | `Map<String, String>` | `{}` | | [`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`](#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_expirations`](#cfg-access-user_expirations) | `Map<String, DateTime<Utc>>` | `{}` |
| [`user_data_quota`](#cfg-access-user_data_quota) | `Map<String, u64>` | `{}` | | [`user_data_quota`](#cfg-access-user_data_quota) | `Map<String, u64>` | `{}` |
| [`user_max_unique_ips`](#cfg-access-user_max_unique_ips) | `Map<String, usize>` | `{}` | | [`user_max_unique_ips`](#cfg-access-user_max_unique_ips) | `Map<String, usize>` | `{}` |
@ -2926,6 +3023,20 @@ If your backend or network is very bandwidth-constrained, reduce cap first. If p
[access.user_max_tcp_conns] [access.user_max_tcp_conns]
alice = 500 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> <a id="cfg-access-user_expirations"></a>
- `user_expirations` - `user_expirations`
- **Constraints / validation**: `Map<String, DateTime<Utc>>`. Each value must be a valid RFC3339 / ISO-8601 datetime. - **Constraints / validation**: `Map<String, DateTime<Utc>>`. Each value must be a valid RFC3339 / ISO-8601 datetime.
@ -3027,13 +3138,13 @@ If your backend or network is very bandwidth-constrained, reduce cap first. If p
| [`weight`](#cfg-upstreams-weight) | `u16` | `1` | | [`weight`](#cfg-upstreams-weight) | `u16` | `1` |
| [`enabled`](#cfg-upstreams-enabled) | `bool` | `true` | | [`enabled`](#cfg-upstreams-enabled) | `bool` | `true` |
| [`scopes`](#cfg-upstreams-scopes) | `String` | `""` | | [`scopes`](#cfg-upstreams-scopes) | `String` | `""` |
| [`interface`](#cfg-upstreams-interface) | `String` or `null` | `null` | | [`interface`](#cfg-upstreams-interface) | `String` | — |
| [`bind_addresses`](#cfg-upstreams-bind_addresses) | `String[]` or `null` | `null` | | [`bind_addresses`](#cfg-upstreams-bind_addresses) | `String[]` | — |
| [`url`](#cfg-upstreams-url) | `String` | — | | [`url`](#cfg-upstreams-url) | `String` | — |
| [`address`](#cfg-upstreams-address) | `String` | — | | [`address`](#cfg-upstreams-address) | `String` | — |
| [`user_id`](#cfg-upstreams-user_id) | `String` or `null` | `null` | | [`user_id`](#cfg-upstreams-user_id) | `String` | — |
| [`username`](#cfg-upstreams-username) | `String` or `null` | `null` | | [`username`](#cfg-upstreams-username) | `String` | — |
| [`password`](#cfg-upstreams-password) | `String` or `null` | `null` | | [`password`](#cfg-upstreams-password) | `String` | — |
<a id="cfg-upstreams-type"></a> <a id="cfg-upstreams-type"></a>
- `type` - `type`
@ -3090,7 +3201,7 @@ If your backend or network is very bandwidth-constrained, reduce cap first. If p
``` ```
<a id="cfg-upstreams-interface"></a> <a id="cfg-upstreams-interface"></a>
- `interface` - `interface`
- **Constraints / validation**: `String` or `null`. - **Constraints / validation**: `String` (optional).
- 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 `"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 `"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. - For `"shadowsocks"`: passed to the shadowsocks connector as an optional outbound bind hint.
@ -3109,7 +3220,7 @@ If your backend or network is very bandwidth-constrained, reduce cap first. If p
``` ```
<a id="cfg-upstreams-bind_addresses"></a> <a id="cfg-upstreams-bind_addresses"></a>
- `bind_addresses` - `bind_addresses`
- **Constraints / validation**: `String[]` or `null`. Applies only to `type = "direct"`. - **Constraints / validation**: `String[]` (optional). Applies only to `type = "direct"`.
- Each entry should be an IP address string. - 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. - 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. - **Description**: Explicit local source addresses for outgoing direct TCP connects. When multiple addresses are provided, selection is round-robin.
@ -3150,7 +3261,7 @@ If your backend or network is very bandwidth-constrained, reduce cap first. If p
``` ```
<a id="cfg-upstreams-user_id"></a> <a id="cfg-upstreams-user_id"></a>
- `user_id` - `user_id`
- **Constraints / validation**: `String` or `null`. Only for `type = "socks4"`. - **Constraints / validation**: `String` (optional). 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. - **Description**: SOCKS4 CONNECT user ID. Note: when a request scope is selected, Telemt may override this with the selected scope value.
- **Example**: - **Example**:
@ -3162,7 +3273,7 @@ If your backend or network is very bandwidth-constrained, reduce cap first. If p
``` ```
<a id="cfg-upstreams-username"></a> <a id="cfg-upstreams-username"></a>
- `username` - `username`
- **Constraints / validation**: `String` or `null`. Only for `type = "socks5"`. - **Constraints / validation**: `String` (optional). 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. - **Description**: SOCKS5 username (for username/password authentication). Note: when a request scope is selected, Telemt may override this with the selected scope value.
- **Example**: - **Example**:
@ -3174,7 +3285,7 @@ If your backend or network is very bandwidth-constrained, reduce cap first. If p
``` ```
<a id="cfg-upstreams-password"></a> <a id="cfg-upstreams-password"></a>
- `password` - `password`
- **Constraints / validation**: `String` or `null`. Only for `type = "socks5"`. - **Constraints / validation**: `String` (optional). 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. - **Description**: SOCKS5 password (for username/password authentication). Note: when a request scope is selected, Telemt may override this with the selected scope value.
- **Example**: - **Example**:

View File

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

View File

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

View File

@ -8,12 +8,20 @@ CONFIG_DIR="${CONFIG_DIR:-/etc/telemt}"
CONFIG_FILE="${CONFIG_FILE:-${CONFIG_DIR}/telemt.toml}" CONFIG_FILE="${CONFIG_FILE:-${CONFIG_DIR}/telemt.toml}"
WORK_DIR="${WORK_DIR:-/opt/telemt}" WORK_DIR="${WORK_DIR:-/opt/telemt}"
TLS_DOMAIN="${TLS_DOMAIN:-petrovich.ru}" TLS_DOMAIN="${TLS_DOMAIN:-petrovich.ru}"
SERVER_PORT="${SERVER_PORT:-443}"
USER_SECRET=""
AD_TAG=""
SERVICE_NAME="telemt" SERVICE_NAME="telemt"
TEMP_DIR="" TEMP_DIR=""
SUDO="" SUDO=""
CONFIG_PARENT_DIR="" CONFIG_PARENT_DIR=""
SERVICE_START_FAILED=0 SERVICE_START_FAILED=0
PORT_PROVIDED=0
SECRET_PROVIDED=0
AD_TAG_PROVIDED=0
DOMAIN_PROVIDED=0
ACTION="install" ACTION="install"
TARGET_VERSION="${VERSION:-latest}" TARGET_VERSION="${VERSION:-latest}"
@ -25,8 +33,37 @@ while [ $# -gt 0 ]; do
printf '[ERROR] %s requires a domain argument.\n' "$1" >&2 printf '[ERROR] %s requires a domain argument.\n' "$1" >&2
exit 1 exit 1
fi fi
TLS_DOMAIN="$2" TLS_DOMAIN="$2"; DOMAIN_PROVIDED=1; shift 2 ;;
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 ;;
uninstall|--uninstall) uninstall|--uninstall)
if [ "$ACTION" != "purge" ]; then ACTION="uninstall"; fi if [ "$ACTION" != "purge" ]; then ACTION="uninstall"; fi
shift ;; shift ;;
@ -59,12 +96,17 @@ cleanup() {
trap cleanup EXIT INT TERM trap cleanup EXIT INT TERM
show_help() { show_help() {
say "Usage: $0 [ <version> | install | uninstall | purge ] [ -d <domain> ] [ --help ]" say "Usage: $0 [ <version> | install | uninstall | purge ] [ options ]"
say " <version> Install specific version (e.g. 3.3.15, default: latest)" say " <version> Install specific version (e.g. 3.3.15, default: latest)"
say " install Install the latest version" say " install Install the latest version"
say " uninstall Remove the binary and service (keeps config and user)" say " uninstall Remove the binary and service"
say " purge Remove everything including configuration, data, and user" say " purge Remove everything including configuration, data, and user"
say ""
say "Options:"
say " -d, --domain Set TLS domain (default: petrovich.ru)" 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 exit 0
} }
@ -81,13 +123,13 @@ get_realpath() {
path_in="$1" path_in="$1"
case "$path_in" in /*) ;; *) path_in="$(pwd)/$path_in" ;; esac 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 if realpath_out="$(realpath -m "$path_in" 2>/dev/null)"; then
printf '%s\n' "$realpath_out" printf '%s\n' "$realpath_out"
return return
fi fi
fi fi
if command -v readlink >/dev/null 2>&1; then if command -v readlink >/dev/null 2>&1; then
resolved_path="$(readlink -f "$path_in" 2>/dev/null || true)" resolved_path="$(readlink -f "$path_in" 2>/dev/null || true)"
if [ -n "$resolved_path" ]; then if [ -n "$resolved_path" ]; then
@ -120,6 +162,14 @@ get_svc_mgr() {
else echo "none"; fi 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() { verify_common() {
[ -n "$BIN_NAME" ] || die "BIN_NAME cannot be empty." [ -n "$BIN_NAME" ] || die "BIN_NAME cannot be empty."
[ -n "$INSTALL_DIR" ] || die "INSTALL_DIR cannot be empty." [ -n "$INSTALL_DIR" ] || die "INSTALL_DIR cannot be empty."
@ -127,7 +177,7 @@ verify_common() {
[ -n "$CONFIG_FILE" ] || die "CONFIG_FILE cannot be empty." [ -n "$CONFIG_FILE" ] || die "CONFIG_FILE cannot be empty."
case "${INSTALL_DIR}${CONFIG_DIR}${WORK_DIR}${CONFIG_FILE}" in case "${INSTALL_DIR}${CONFIG_DIR}${WORK_DIR}${CONFIG_FILE}" in
*[!a-zA-Z0-9_./-]*) die "Invalid characters in paths. Only alphanumeric, _, ., -, and / allowed." ;; *[!a-zA-Z0-9_./-]*) die "Invalid characters in paths." ;;
esac esac
case "$TARGET_VERSION" in *[!a-zA-Z0-9_.-]*) die "Invalid characters in version." ;; esac case "$TARGET_VERSION" in *[!a-zA-Z0-9_.-]*) die "Invalid characters in version." ;; esac
@ -145,11 +195,11 @@ 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. Neither found." command -v sudo >/dev/null 2>&1 || die "This script requires root or sudo."
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. Aborting to prevent hang." die "sudo requires a password, but no TTY detected."
fi fi
fi fi
fi fi
@ -162,21 +212,7 @@ verify_common() {
die "Safety check failed: CONFIG_FILE '$CONFIG_FILE' is a directory." die "Safety check failed: CONFIG_FILE '$CONFIG_FILE' is a directory."
fi fi
for path in "$CONFIG_DIR" "$CONFIG_PARENT_DIR" "$WORK_DIR"; 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
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" command -v "$cmd" >/dev/null 2>&1 || die "Required command not found: $cmd"
done done
} }
@ -185,14 +221,41 @@ 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 "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" 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; then if ! command -v setcap >/dev/null 2>&1 || ! command -v conntrack >/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 >/dev/null 2>&1 || $SUDO apk add --no-cache libcap >/dev/null 2>&1 || true $SUDO apk add --no-cache libcap-utils libcap conntrack-tools >/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 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 || {
$SUDO apt-get install -y -q libcap2-bin >/dev/null 2>&1 || true $SUDO env DEBIAN_FRONTEND=noninteractive apt-get update -q >/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 $SUDO env DEBIAN_FRONTEND=noninteractive apt-get install -y -q libcap2-bin conntrack >/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 }
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."
fi fi
fi fi
} }
@ -250,10 +313,10 @@ ensure_user_group() {
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 "Failed to create directories"
$SUDO chown telemt:telemt "$WORK_DIR" && $SUDO chmod 750 "$WORK_DIR" $SUDO chown telemt:telemt "$WORK_DIR" && $SUDO chmod 750 "$WORK_DIR"
$SUDO chown root:telemt "$CONFIG_DIR" && $SUDO chmod 750 "$CONFIG_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 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" $SUDO chown root:telemt "$CONFIG_PARENT_DIR" && $SUDO chmod 750 "$CONFIG_PARENT_DIR"
fi fi
@ -275,17 +338,19 @@ install_binary() {
fi fi
$SUDO mkdir -p "$INSTALL_DIR" || die "Failed to create install directory" $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 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 "Failed to install binary"
else 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" $SUDO cp "$bin_src" "$bin_dst" && $SUDO chmod 0755 "$bin_dst" || die "Failed to copy binary"
fi fi
$SUDO sh -c '[ -x "$1" ]' _ "$bin_dst" || die "Binary not executable: $bin_dst" $SUDO sh -c '[ -x "$1" ]' _ "$bin_dst" || die "Binary not executable: $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=+ep "$bin_dst" 2>/dev/null || true $SUDO setcap cap_net_bind_service,cap_net_admin=+ep "$bin_dst" 2>/dev/null || true
fi fi
} }
@ -301,11 +366,20 @@ generate_secret() {
} }
generate_config_content() { 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')" escaped_tls_domain="$(printf '%s\n' "$TLS_DOMAIN" | tr -d '[:cntrl:]' | sed 's/\\/\\\\/g; s/"/\\"/g')"
cat <<EOF cat <<EOF
[general] [general]
use_middle_proxy = false use_middle_proxy = true
EOF
if [ -n "$conf_tag" ]; then
echo "ad_tag = \"${conf_tag}\""
fi
cat <<EOF
[general.modes] [general.modes]
classic = false classic = false
@ -313,7 +387,7 @@ secure = false
tls = true tls = true
[server] [server]
port = 443 port = ${SERVER_PORT}
[server.api] [server.api]
enabled = true enabled = true
@ -324,28 +398,73 @@ whitelist = ["127.0.0.1/32"]
tls_domain = "${escaped_tls_domain}" tls_domain = "${escaped_tls_domain}"
[access.users] [access.users]
hello = "$1" hello = "${conf_secret}"
EOF EOF
} }
install_config() { install_config() {
if [ -n "$SUDO" ]; then if is_config_exists; then
if $SUDO sh -c '[ -f "$1" ]' _ "$CONFIG_FILE"; then say " -> Config already exists at $CONFIG_FILE. Updating parameters..."
say " -> Config already exists at $CONFIG_FILE. Skipping creation."
return 0 tmp_conf="${TEMP_DIR}/config.tmp"
fi $SUDO cat "$CONFIG_FILE" > "$tmp_conf"
elif [ -f "$CONFIG_FILE" ]; then
say " -> Config already exists at $CONFIG_FILE. Skipping creation." 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"
return 0 return 0
fi fi
toml_secret="$(generate_secret)" || die "Failed to generate secret." if [ -z "$USER_SECRET" ]; then
USER_SECRET="$(generate_secret)" || die "Failed to generate secret."
fi
generate_config_content "$toml_secret" | write_root "$CONFIG_FILE" || die "Failed to install config" generate_config_content "$USER_SECRET" "$AD_TAG" | write_root "$CONFIG_FILE" || die "Failed to install config"
$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 " -> Config created successfully."
say " -> Generated secret for default user 'hello': $toml_secret" say " -> Configured secret for user 'hello': $USER_SECRET"
} }
generate_systemd_content() { generate_systemd_content() {
@ -362,9 +481,10 @@ Group=telemt
WorkingDirectory=$WORK_DIR WorkingDirectory=$WORK_DIR
ExecStart="${INSTALL_DIR}/${BIN_NAME}" "${CONFIG_FILE}" ExecStart="${INSTALL_DIR}/${BIN_NAME}" "${CONFIG_FILE}"
Restart=on-failure Restart=on-failure
RestartSec=5
LimitNOFILE=65536 LimitNOFILE=65536
AmbientCapabilities=CAP_NET_BIND_SERVICE AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_ADMIN
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_ADMIN
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
@ -395,7 +515,7 @@ install_service() {
$SUDO systemctl daemon-reload || true $SUDO systemctl daemon-reload || true
$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] Failed to start service"
SERVICE_START_FAILED=1 SERVICE_START_FAILED=1
@ -405,16 +525,16 @@ install_service() {
$SUDO chown root:root "/etc/init.d/${SERVICE_NAME}" && $SUDO chmod 0755 "/etc/init.d/${SERVICE_NAME}" $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 $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] Failed to start service"
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 " -> 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'" say " -> Service manager not found. Start manually: su -s /bin/sh telemt -c '$cmd'"
fi fi
fi fi
@ -429,9 +549,10 @@ kill_user_procs() {
if command -v pgrep >/dev/null 2>&1; then if command -v pgrep >/dev/null 2>&1; then
pids="$(pgrep -u telemt 2>/dev/null || true)" pids="$(pgrep -u telemt 2>/dev/null || true)"
else else
pids="$(ps -u telemt -o pid= 2>/dev/null || true)" 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)"
fi fi
if [ -n "$pids" ]; then if [ -n "$pids" ]; then
for pid in $pids; do for pid in $pids; do
case "$pid" in ''|*[!0-9]*) continue ;; *) $SUDO kill "$pid" 2>/dev/null || true ;; esac case "$pid" in ''|*[!0-9]*) continue ;; *) $SUDO kill "$pid" 2>/dev/null || true ;; esac
@ -471,15 +592,16 @@ uninstall() {
say ">>> Stage 5: Purging configuration, data, and user" say ">>> Stage 5: Purging configuration, data, and user"
$SUDO rm -rf "$CONFIG_DIR" "$WORK_DIR" $SUDO rm -rf "$CONFIG_DIR" "$WORK_DIR"
$SUDO rm -f "$CONFIG_FILE" $SUDO rm -f "$CONFIG_FILE"
if [ "$CONFIG_PARENT_DIR" != "$CONFIG_DIR" ] && [ "$CONFIG_PARENT_DIR" != "." ] && [ "$CONFIG_PARENT_DIR" != "/" ]; then sleep 1
$SUDO rmdir "$CONFIG_PARENT_DIR" 2>/dev/null || true
fi
$SUDO userdel telemt 2>/dev/null || $SUDO deluser telemt 2>/dev/null || true $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
if check_os_entity group telemt; then
$SUDO groupdel telemt 2>/dev/null || $SUDO delgroup telemt 2>/dev/null || true
fi
else else
say "Note: Configuration and user kept. Run with 'purge' to remove completely." say "Note: Configuration and user kept. Run with 'purge' to remove completely."
fi fi
printf '\n====================================================================\n' printf '\n====================================================================\n'
printf ' UNINSTALLATION COMPLETE\n' printf ' UNINSTALLATION COMPLETE\n'
printf '====================================================================\n\n' printf '====================================================================\n\n'
@ -493,18 +615,28 @@ case "$ACTION" in
say "Starting installation of $BIN_NAME (Version: $TARGET_VERSION)" say "Starting installation of $BIN_NAME (Version: $TARGET_VERSION)"
say ">>> Stage 1: Verifying environment and dependencies" say ">>> Stage 1: Verifying environment and dependencies"
verify_common; verify_install_deps verify_common
verify_install_deps
if [ "$TARGET_VERSION" != "latest" ]; then 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
TARGET_VERSION="${TARGET_VERSION#v}" TARGET_VERSION="${TARGET_VERSION#v}"
fi fi
ARCH="$(detect_arch)"; LIBC="$(detect_libc)" ARCH="$(detect_arch)"; LIBC="$(detect_libc)"
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
DL_URL="https://github.com/${REPO}/releases/latest/download/${FILE_NAME}" 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}" DL_URL="https://github.com/${REPO}/releases/download/${TARGET_VERSION}/${FILE_NAME}"
fi fi
@ -521,7 +653,7 @@ case "$ACTION" in
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
DL_URL="https://github.com/${REPO}/releases/latest/download/${FILE_NAME}" 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}" 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 "Download failed"
@ -540,13 +672,13 @@ case "$ACTION" in
say ">>> Stage 4: Setting up environment (User, Group, Directories)" say ">>> Stage 4: Setting up environment (User, Group, Directories)"
ensure_user_group; setup_dirs; stop_service ensure_user_group; setup_dirs; stop_service
say ">>> Stage 5: Installing binary" say ">>> Stage 5: Installing binary"
install_binary "$EXTRACTED_BIN" "${INSTALL_DIR}/${BIN_NAME}" install_binary "$EXTRACTED_BIN" "${INSTALL_DIR}/${BIN_NAME}"
say ">>> Stage 6: Generating configuration" say ">>> Stage 6: Generating/Updating configuration"
install_config install_config
say ">>> Stage 7: Installing and starting service" say ">>> Stage 7: Installing and starting service"
install_service install_service
@ -561,7 +693,7 @@ case "$ACTION" in
printf ' INSTALLATION SUCCESS\n' printf ' INSTALLATION SUCCESS\n'
printf '====================================================================\n\n' printf '====================================================================\n\n'
fi fi
svc="$(get_svc_mgr)" svc="$(get_svc_mgr)"
if [ "$svc" = "systemd" ]; then if [ "$svc" = "systemd" ]; then
printf 'To check the status of your proxy service, run:\n' printf 'To check the status of your proxy service, run:\n'
@ -570,15 +702,18 @@ case "$ACTION" in
printf 'To check the status of your proxy service, run:\n' printf 'To check the status of your proxy service, run:\n'
printf ' rc-service %s status\n\n' "$SERVICE_NAME" printf ' rc-service %s status\n\n' "$SERVICE_NAME"
fi 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' printf 'To get your user connection links (for Telegram), run:\n'
if command -v jq >/dev/null 2>&1; then if command -v jq >/dev/null 2>&1; then
printf ' curl -s http://127.0.0.1:9091/v1/users | jq -r '\''.data[] | "User: \\(.username)\\n\\(.links.tls[0] // empty)\\n"'\''\n' printf ' curl -s http://%s/v1/users | jq -r '\''.data[]? | "User: \\(.username)\\n\\(.links.tls[0] // empty)\\n"'\''\n' "$API_LISTEN"
else else
printf ' curl -s http://127.0.0.1:9091/v1/users\n' printf ' curl -s http://%s/v1/users\n' "$API_LISTEN"
printf ' (Tip: Install '\''jq'\'' for a much cleaner output)\n' printf ' (Tip: Install '\''jq'\'' for a much cleaner output)\n'
fi fi
printf '\n====================================================================\n' printf '\n====================================================================\n'
;; ;;
esac esac