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 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 \
@ -287,14 +279,6 @@ 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 LICENSE 3.3 ######### ###### TELEMT Public License 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,15 +14,11 @@ 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
| Language | Location | - German: docs/LICENSE/LICENSE.de.md
|-------------|--------------------------------------------| - 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) | | | [`include`](#cfg-top-include) | `String` (special directive) | `null` |
| [`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` | — (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> <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` | — | | [`data_path`](#cfg-general-data_path) | `String` or `null` | `null` |
| [`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` | `"proxy-secret"` | | [`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` | `"cache/proxy-config-v4.txt"` | | [`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` | `"cache/proxy-config-v6.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` | — | | [`ad_tag`](#cfg-general-ad_tag) | `String` or `null` | `null` |
| [`middle_proxy_nat_ip`](#cfg-general-middle_proxy_nat_ip) | `IpAddr` | — | | [`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_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[]` | `[]` | | [`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` | `"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` | | [`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` | `300` | | [`update_every`](#cfg-general-update_every) | `u64` or `null` | `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` (optional). - **Constraints / validation**: `String` or `null`.
- **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`. 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. - **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`. 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. - **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`. 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. - **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` (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`. - **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` (optional). - **Constraints / validation**: `IpAddr` or `null`.
- **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` (optional). Must be a safe path (no `..` components, parent directory must exist); unsafe paths are rejected at runtime. - **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`. Omit this key to disable file logging. - **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**: - **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). 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. - **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 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. - **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 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. - **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` | — | | [`public_host`](#cfg-general-links-public_host) | `String` or `null` | `null` |
| [`public_port`](#cfg-general-links-public_port) | `u16` | — | | [`public_port`](#cfg-general-links-public_port) | `u16` or `null` | `null` |
<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` (optional). - **Constraints / validation**: `String` or `null`.
- **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` (optional). - **Constraints / validation**: `u16` or `null`.
- **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` | `false` | | [`ipv6`](#cfg-network-ipv6) | `bool` or `null` | `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`. - **Constraints / validation**: `bool` or `null`. `null` means "auto-detect IPv6 availability".
- **Description**: Enables/disables IPv6 networking. When omitted, defaults to `false`. - **Description**: Enables/disables IPv6 when explicitly set; when `null`, Telemt will auto-detect IPv6 availability at runtime.
- **Example**: - **Example**:
```toml ```toml
@ -1741,6 +1741,9 @@ 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`
@ -1839,16 +1842,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` | `"0.0.0.0"` | | [`listen_addr_ipv4`](#cfg-server-listen_addr_ipv4) | `String` or `null` | `"0.0.0.0"` |
| [`listen_addr_ipv6`](#cfg-server-listen_addr_ipv6) | `String` | `"::"` | | [`listen_addr_ipv6`](#cfg-server-listen_addr_ipv6) | `String` or `null` | `"::"` |
| [`listen_unix_sock`](#cfg-server-listen_unix_sock) | `String` | — | | [`listen_unix_sock`](#cfg-server-listen_unix_sock) | `String` or `null` | `null` |
| [`listen_unix_sock_perm`](#cfg-server-listen_unix_sock_perm) | `String` | — | | [`listen_unix_sock_perm`](#cfg-server-listen_unix_sock_perm) | `String` or `null` | `null` |
| [`listen_tcp`](#cfg-server-listen_tcp) | `bool` | — (auto) | | [`listen_tcp`](#cfg-server-listen_tcp) | `bool` or `null` | `null` (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` | — | | [`metrics_port`](#cfg-server-metrics_port) | `u16` or `null` | `null` |
| [`metrics_listen`](#cfg-server-metrics_listen) | `String` | — | | [`metrics_listen`](#cfg-server-metrics_listen) | `String` or `null` | `null` |
| [`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` |
@ -1865,8 +1868,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` (optional). When set, must be a valid IPv4 address string. - **Constraints / validation**: `String` or `null`. When set, must be a valid IPv4 address string.
- **Description**: IPv4 bind address for TCP listener (omit this key to disable IPv4 bind). - **Description**: IPv4 bind address for TCP listener (`null` disables IPv4 bind).
- **Example**: - **Example**:
```toml ```toml
@ -1875,8 +1878,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` (optional). When set, must be a valid IPv6 address string. - **Constraints / validation**: `String` or `null`. When set, must be a valid IPv6 address string.
- **Description**: IPv6 bind address for TCP listener (omit this key to disable IPv6 bind). - **Description**: IPv6 bind address for TCP listener (`null` disables IPv6 bind).
- **Example**: - **Example**:
```toml ```toml
@ -1885,7 +1888,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` (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). - **Description**: Unix socket path for listener. When set, `server.listen_tcp` defaults to `false` (unless explicitly overridden).
- **Example**: - **Example**:
@ -1895,8 +1898,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` (optional). When set, should be an octal permission string like `"0666"` or `"0777"`. - **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). When omitted, permissions are not changed (inherits umask). - **Description**: Optional Unix socket file permissions applied after bind (chmod). `null` means "no change" (inherits umask).
- **Example**: - **Example**:
```toml ```toml
@ -1906,7 +1909,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` (optional). When omitted, Telemt auto-detects: - **Constraints / validation**: `bool` or `null`. `null` means auto:
- `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.
@ -1954,7 +1957,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` (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`). - **Description**: Prometheus-compatible metrics endpoint port. When set, enables the metrics listener (bind behavior can be overridden by `metrics_listen`).
- **Example**: - **Example**:
@ -1964,7 +1967,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` (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. - **Description**: Full metrics bind address (`IP:PORT`), overrides `metrics_port` and binds on the specified address only.
- **Example**: - **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. 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]`).
@ -2254,9 +2158,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` | — | | [`announce`](#cfg-server-listeners-announce) | `String` or `null` | — |
| [`announce_ip`](#cfg-server-listeners-announce_ip) | `IpAddr` | — | | [`announce_ip`](#cfg-server-listeners-announce_ip) | `IpAddr` or `null` | — |
| [`proxy_protocol`](#cfg-server-listeners-proxy_protocol) | `bool` | — | | [`proxy_protocol`](#cfg-server-listeners-proxy_protocol) | `bool` or `null` | `null` |
| [`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>
@ -2271,7 +2175,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` (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`. - **Description**: Public IP/domain announced in proxy links for this listener. Takes precedence over `announce_ip`.
- **Example**: - **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> <a id="cfg-server-listeners-announce_ip"></a>
- `announce_ip` - `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. - **Description**: Deprecated legacy announce IP. During config load it is migrated to `announce` when `announce` is not set.
- **Example**: - **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> <a id="cfg-server-listeners-proxy_protocol"></a>
- `proxy_protocol` - `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. - **Description**: Per-listener PROXY protocol override.
- **Example**: - **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_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` | — | | [`mask_host`](#cfg-censorship-mask_host) | `String` or `null` | `null` |
| [`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` | — | | [`mask_unix_sock`](#cfg-censorship-mask_unix_sock) | `String` or `null` | `null` |
| [`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"` |
@ -2536,8 +2440,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` (optional). - **Constraints / validation**: `String` or `null`.
- If `mask_unix_sock` is set, `mask_host` must be omitted (mutually exclusive). - 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`. - 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**:
@ -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> <a id="cfg-censorship-mask_unix_sock"></a>
- `mask_unix_sock` - `mask_unix_sock`
- **Constraints / validation**: `String` (optional). - **Constraints / validation**: `String` or `null`.
- 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).
@ -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"}` | | [`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>` | `{}` |
@ -3023,20 +2926,6 @@ 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.
@ -3138,13 +3027,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` | — | | [`interface`](#cfg-upstreams-interface) | `String` or `null` | `null` |
| [`bind_addresses`](#cfg-upstreams-bind_addresses) | `String[]` | — | | [`bind_addresses`](#cfg-upstreams-bind_addresses) | `String[]` or `null` | `null` |
| [`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` | — | | [`user_id`](#cfg-upstreams-user_id) | `String` or `null` | `null` |
| [`username`](#cfg-upstreams-username) | `String` | — | | [`username`](#cfg-upstreams-username) | `String` or `null` | `null` |
| [`password`](#cfg-upstreams-password) | `String` | — | | [`password`](#cfg-upstreams-password) | `String` or `null` | `null` |
<a id="cfg-upstreams-type"></a> <a id="cfg-upstreams-type"></a>
- `type` - `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> <a id="cfg-upstreams-interface"></a>
- `interface` - `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 `"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.
@ -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> <a id="cfg-upstreams-bind_addresses"></a>
- `bind_addresses` - `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. - 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.
@ -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> <a id="cfg-upstreams-user_id"></a>
- `user_id` - `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. - **Description**: SOCKS4 CONNECT user ID. Note: when a request scope is selected, Telemt may override this with the selected scope value.
- **Example**: - **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> <a id="cfg-upstreams-username"></a>
- `username` - `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. - **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**:
@ -3285,7 +3174,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` (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. - **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_ADMIN CAP_NET_BIND_SERVICE AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CapabilityBoundingSet=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_ADMIN CAP_NET_BIND_SERVICE AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CapabilityBoundingSet=CAP_NET_BIND_SERVICE
NoNewPrivileges=true NoNewPrivileges=true
[Install] [Install]

View File

@ -8,20 +8,12 @@ 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}"
@ -33,37 +25,8 @@ 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"; DOMAIN_PROVIDED=1; shift 2 ;; TLS_DOMAIN="$2"
-p|--port) shift 2 ;;
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 ;;
@ -96,17 +59,12 @@ cleanup() {
trap cleanup EXIT INT TERM trap cleanup EXIT INT TERM
show_help() { 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 " <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" say " uninstall Remove the binary and service (keeps config and user)"
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
} }
@ -162,14 +120,6 @@ 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."
@ -177,7 +127,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." ;; *[!a-zA-Z0-9_./-]*) die "Invalid characters in paths. Only alphanumeric, _, ., -, and / allowed." ;;
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
@ -195,11 +145,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." command -v sudo >/dev/null 2>&1 || die "This script requires root or sudo. Neither found."
SUDO="sudo" SUDO="sudo"
if ! sudo -n true 2>/dev/null; then if ! sudo -n true 2>/dev/null; then
if ! [ -t 0 ]; then if ! [ -t 0 ]; then
die "sudo requires a password, but no TTY detected." die "sudo requires a password, but no TTY detected. Aborting to prevent hang."
fi fi
fi fi
fi fi
@ -212,7 +162,21 @@ 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 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" command -v "$cmd" >/dev/null 2>&1 || die "Required command not found: $cmd"
done 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 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 || ! command -v conntrack >/dev/null 2>&1; then if ! command -v setcap >/dev/null 2>&1; then
if command -v apk >/dev/null 2>&1; then if command -v apk >/dev/null 2>&1; then
$SUDO apk add --no-cache libcap-utils libcap conntrack-tools >/dev/null 2>&1 || true $SUDO apk add --no-cache libcap-utils >/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 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 apt-get update -q >/dev/null 2>&1 || true
$SUDO env DEBIAN_FRONTEND=noninteractive apt-get update -q >/dev/null 2>&1 || true $SUDO apt-get install -y -q libcap2-bin >/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 >/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
} }
@ -338,19 +275,17 @@ 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,cap_net_admin=+ep "$bin_dst" 2>/dev/null || true $SUDO setcap cap_net_bind_service=+ep "$bin_dst" 2>/dev/null || true
fi fi
} }
@ -366,20 +301,11 @@ 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 = true use_middle_proxy = false
EOF
if [ -n "$conf_tag" ]; then
echo "ad_tag = \"${conf_tag}\""
fi
cat <<EOF
[general.modes] [general.modes]
classic = false classic = false
@ -387,7 +313,7 @@ secure = false
tls = true tls = true
[server] [server]
port = ${SERVER_PORT} port = 443
[server.api] [server.api]
enabled = true enabled = true
@ -398,73 +324,28 @@ whitelist = ["127.0.0.1/32"]
tls_domain = "${escaped_tls_domain}" tls_domain = "${escaped_tls_domain}"
[access.users] [access.users]
hello = "${conf_secret}" hello = "$1"
EOF EOF
} }
install_config() { install_config() {
if is_config_exists; then if [ -n "$SUDO" ]; then
say " -> Config already exists at $CONFIG_FILE. Updating parameters..." if $SUDO sh -c '[ -f "$1" ]' _ "$CONFIG_FILE"; then
say " -> Config already exists at $CONFIG_FILE. Skipping creation."
tmp_conf="${TEMP_DIR}/config.tmp" return 0
$SUDO cat "$CONFIG_FILE" > "$tmp_conf" fi
elif [ -f "$CONFIG_FILE" ]; then
escaped_domain="$(printf '%s\n' "$TLS_DOMAIN" | tr -d '[:cntrl:]' | sed 's/\\/\\\\/g; s/"/\\"/g')" say " -> Config already exists at $CONFIG_FILE. Skipping creation."
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
if [ -z "$USER_SECRET" ]; then toml_secret="$(generate_secret)" || die "Failed to generate secret."
USER_SECRET="$(generate_secret)" || die "Failed to generate secret."
fi
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" $SUDO chown root:telemt "$CONFIG_FILE" && $SUDO chmod 640 "$CONFIG_FILE"
say " -> Config created successfully." say " -> Config created successfully."
say " -> Configured secret for user 'hello': $USER_SECRET" say " -> Generated secret for default user 'hello': $toml_secret"
} }
generate_systemd_content() { generate_systemd_content() {
@ -481,10 +362,9 @@ 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 CAP_NET_ADMIN AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_ADMIN CapabilityBoundingSet=CAP_NET_BIND_SERVICE
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
@ -549,8 +429,7 @@ 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 -ef 2>/dev/null | awk '$1=="telemt"{print $2}' || true)" pids="$(ps -u telemt -o pid= 2>/dev/null || true)"
[ -z "$pids" ] && pids="$(ps 2>/dev/null | awk '$2=="telemt"{print $1}' || true)"
fi fi
if [ -n "$pids" ]; then if [ -n "$pids" ]; then
@ -592,12 +471,11 @@ 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"
sleep 1 if [ "$CONFIG_PARENT_DIR" != "$CONFIG_DIR" ] && [ "$CONFIG_PARENT_DIR" != "." ] && [ "$CONFIG_PARENT_DIR" != "/" ]; then
$SUDO userdel telemt 2>/dev/null || $SUDO deluser telemt 2>/dev/null || true $SUDO rmdir "$CONFIG_PARENT_DIR" 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 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 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
@ -615,17 +493,7 @@ 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_common; verify_install_deps
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}" TARGET_VERSION="${TARGET_VERSION#v}"
@ -676,7 +544,7 @@ case "$ACTION" in
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/Updating configuration" say ">>> Stage 6: Generating configuration"
install_config install_config
say ">>> Stage 7: Installing and starting service" say ">>> Stage 7: Installing and starting service"
@ -703,14 +571,11 @@ case "$ACTION" in
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://%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 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' printf ' (Tip: Install '\''jq'\'' for a much cleaner output)\n'
fi fi