Files
ai-setup/scripts/fuck-rkn.sh
2026-06-09 23:59:57 +07:00

1909 lines
89 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# =============================================================================
# setup.sh — Единый установщик:
# • Telegram MTProto прокси (nginx stream + telemt + fail2ban)
# • VPN обход блокировок (nginx stream + xray VLESS+Reality)
#
# Схема: клиент → nginx:443 (SNI-роутинг) → telemt | xray (127.0.0.1)
#
# Адаптировано под «сибирскую» схему DPI (июнь 2026, разбор @hyperion_cs):
# заморозка включается при совпадении трёх сигналов (логическое И):
# 1) подозрительная подсеть сервера — рвём выбором провайдера/CDN (вне скрипта);
# 2) массовый TLS-фингерпринт — random_fp() даёт только «лояльные»
# пресеты uTLS (firefox/edge/360/qq/android/random), без chrome/safari/ios
# и без синтетического randomized;
# 3) лавина TLS-соединений к одному SNI — лечим XHTTP + XMUX (mux в outbound).
# Достаточно стабильно нарушать ЛЮБОЕ одно звено — правило не срабатывает.
#
# Использование:
# bash setup.sh — интерактивное меню
# bash setup.sh telemt — telemt direct: 3 инстанса (443/5223/8530) + UFW rate-limit
# bash setup.sh telemt-rf — telemt РФ-нода: те же инстансы, но выход через SOCKS5
# (бэкенд systemd|docker спрашивается, или env TELEMT_BACKEND)
# bash setup.sh update [mss] — обновить telemt и задать client_mss во всех инстансах
# (mss: tspu|extreme-low|2in8|88..4096|"", по умолч. tspu)
# bash setup.sh xray-euro — установить xray евро-ноду (Reality direct, порт 443→853→8443)
# bash setup.sh xray-rf — установить xray РФ-ноду (Reality direct, порт 443→853→8443, мост → евро)
# bash setup.sh status — статус всех сервисов
# bash setup.sh purge — полное удаление всего
# =============================================================================
set -euo pipefail
# ══════════════════════════════════════════════════════════════════════════════
# КОНСТАНТЫ
# ══════════════════════════════════════════════════════════════════════════════
NGINX_CONF_MAIN="/etc/nginx/nginx.conf"
NGINX_STREAM_DIR="/etc/nginx/stream.d"
NGINX_STREAM_LOG="/var/log/nginx/stream-access.log"
TELEMT_STREAM_CONF="${NGINX_STREAM_DIR}/telemt.conf"
XRAY_STREAM_CONF="${NGINX_STREAM_DIR}/xray.conf"
F2B_FILTER="/etc/fail2ban/filter.d/nginx-stream-empty-sni.conf"
F2B_JAIL="/etc/fail2ban/jail.d/nginx-stream-empty-sni.local"
XRAY_DIR="/usr/local/etc/xray"
XRAY_BIN="/usr/local/bin/xray"
XRAY_STATE_FILE="/root/euro-node.conf"
NGINX_EXT_PORT=443 # внешний порт nginx (всегда 443)
# ══════════════════════════════════════════════════════════════════════════════
# ЦВЕТА И ВЫВОД
# ══════════════════════════════════════════════════════════════════════════════
RED='\033[0;31m'; YELLOW='\033[1;33m'; GREEN='\033[0;32m'
CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*" >&2; }
die() { echo -e "${RED}[ERROR]${NC} $*" >&2; exit 1; }
section() { echo -e "\n${BOLD}${CYAN}══════════ $* ══════════${NC}"; }
banner() {
echo -e "${CYAN}${BOLD}"
echo "╔══════════════════════════════════════════════════════════╗"
echo "║ nginx + telemt + xray — Установщик ║"
echo "╚══════════════════════════════════════════════════════════╝"
echo -e "${NC}"
}
# ══════════════════════════════════════════════════════════════════════════════
# ПРАВА И ОС
# ══════════════════════════════════════════════════════════════════════════════
[[ $EUID -eq 0 ]] || die "Запустите от root или через sudo"
detect_os() {
OS_FAMILY=""
if [[ -f /etc/os-release ]]; then
source /etc/os-release
case "${ID:-}" in
debian|ubuntu|linuxmint|raspbian) OS_FAMILY="debian" ;;
rhel|centos|fedora|rocky|almalinux) OS_FAMILY="rhel" ;;
alpine) OS_FAMILY="alpine" ;;
*)
case "${ID_LIKE:-}" in
*debian*) OS_FAMILY="debian" ;;
*rhel*|*fedora*) OS_FAMILY="rhel" ;;
esac ;;
esac
fi
[[ -f /etc/alpine-release ]] && OS_FAMILY="alpine"
[[ -z "$OS_FAMILY" ]] && die "Неподдерживаемый дистрибутив"
ok "ОС: ${OS_FAMILY} (${ID:-?} ${VERSION_ID:-})"
}
pkg_update() {
case "$OS_FAMILY" in
debian) DEBIAN_FRONTEND=noninteractive apt-get update -qq ;;
rhel) dnf makecache -q 2>/dev/null || yum makecache -q ;;
alpine) apk update -q ;;
esac
}
pkg_install() {
case "$OS_FAMILY" in
debian) DEBIAN_FRONTEND=noninteractive apt-get install -y -qq "$@" ;;
rhel) dnf install -y -q "$@" 2>/dev/null || yum install -y -q "$@" ;;
alpine) apk add --no-cache "$@" ;;
esac
}
svc_enable() { systemctl enable "$1" 2>/dev/null || rc-update add "$1" default 2>/dev/null || true; }
svc_start() { systemctl start "$1" 2>/dev/null || rc-service "$1" start 2>/dev/null || true; }
svc_restart() { systemctl restart "$1" 2>/dev/null || rc-service "$1" restart 2>/dev/null || true; }
svc_stop() { systemctl stop "$1" 2>/dev/null || rc-service "$1" stop 2>/dev/null || true; }
svc_disable() { systemctl disable "$1" 2>/dev/null || rc-update del "$1" 2>/dev/null || true; }
svc_active() { systemctl is-active --quiet "$1" 2>/dev/null; }
get_svc_mgr() {
if command -v systemctl &>/dev/null && [[ -d /run/systemd/system ]]; then echo "systemd"
elif command -v rc-service &>/dev/null; then echo "openrc"
else echo "none"; fi
}
# ══════════════════════════════════════════════════════════════════════════════
# ПОРТЫ
# ══════════════════════════════════════════════════════════════════════════════
port_in_use() {
local p="$1"
ss -tlnH 2>/dev/null | awk '{print $4}' | grep -qE ":${p}$" && return 0
netstat -tlnH 2>/dev/null | awk '{print $4}' | grep -qE ":${p}$" && return 0
local hex; hex="$(printf '%04X' "$p")"
grep -qi ":${hex} " /proc/net/tcp /proc/net/tcp6 2>/dev/null && return 0
return 1
}
find_free_port() {
local candidates=("$@")
for p in "${candidates[@]}"; do
port_in_use "$p" || { echo "$p"; return; }
done
echo "${candidates[-1]}"
}
show_ports() {
section "Занятые порты"
local watch="22 80 443 8080 8443 9090 9091 18443 19443 28443"
local raw; raw="$(ss -tlnpH 2>/dev/null || netstat -tlnpH 2>/dev/null || true)"
printf " ${BOLD}%-8s %-22s %-20s %s${NC}\n" "ПОРТ" "АДРЕС" "СЕРВИС" "ПРОЦЕСС"
printf " %s\n" "──────────────────────────────────────────────────────────"
local found=0
for p in $watch; do
local line; line="$(echo "$raw" | awk -v pt=":${p}" '$4 ~ pt"$" {print; exit}')"
[[ -z "$line" ]] && continue
found=1
local addr proc svc col
addr="$(echo "$line" | awk '{print $4}')"
proc="$(echo "$line" | grep -oP 'users:\(\("([^"]+)"' | head -1 | grep -oP '"[^"]+"' | tr -d '"' || echo "?")"
case "$p" in
22) svc="SSH"; col="$GREEN" ;;
443|8443) svc="HTTPS"; col="$CYAN" ;;
9091) svc="telemt API"; col="$CYAN" ;;
18443|19443|28443) svc="внутр.порт"; col="$CYAN" ;;
*) svc="—"; col="$YELLOW" ;;
esac
printf " ${col}%-8s %-22s %-20s %s${NC}\n" "$p" "$addr" "$svc" "${proc:-?}"
done
[[ $found -eq 0 ]] && echo " Ни один из отслеживаемых портов не занят"
printf " %s\n\n" "──────────────────────────────────────────────────────────"
}
get_public_ip() {
local ip=""
ip="$(curl -s4 -m 5 ifconfig.me 2>/dev/null || curl -s4 -m 5 api.ipify.org 2>/dev/null || true)"
echo "${ip:-<YOUR_SERVER_IP>}"
}
# ══════════════════════════════════════════════════════════════════════════════
# NGINX — общие функции (используются обоими модулями)
# ══════════════════════════════════════════════════════════════════════════════
# Проверка: установлен ли nginx с поддержкой stream
nginx_stream_ok() {
command -v nginx &>/dev/null || return 1
local tmpd; tmpd="$(mktemp -d)"
cat > "$tmpd/nginx.conf" << 'NCEOF'
include /etc/nginx/modules-enabled/*.conf;
events {}
stream {}
NCEOF
local out; out="$(nginx -t -c "$tmpd/nginx.conf" 2>&1)"
rm -rf "$tmpd"
echo "$out" | grep -q 'unknown directive "stream"' && return 1
return 0
}
# Устанавливаем nginx + stream модуль если нужно
nginx_ensure() {
section "nginx"
if nginx_stream_ok; then
ok "nginx со stream модулем уже установлен"
return
fi
info "Устанавливаю nginx..."
case "$OS_FAMILY" in
debian)
pkg_install nginx
pkg_install libnginx-mod-stream 2>/dev/null || pkg_install nginx-full 2>/dev/null || true ;;
rhel)
pkg_install nginx 2>/dev/null || true
pkg_install nginx-mod-stream 2>/dev/null || true ;;
alpine)
pkg_install nginx nginx-mod-stream ;;
esac
nginx_stream_ok || die "nginx stream модуль недоступен после установки. Попробуйте вручную: apt install libnginx-mod-stream"
ok "nginx установлен"
}
# Восстанавливаем nginx.conf если он повреждён (нет events{} или http{})
nginx_restore_if_broken() {
# Считаем повреждённым если нет блока events{}
if grep -q 'events[[:space:]]*{' "$NGINX_CONF_MAIN" 2>/dev/null; then
return 0 # файл в порядке
fi
warn "nginx.conf повреждён (отсутствует блок events{}) — восстанавливаю..."
cat > "$NGINX_CONF_MAIN" << 'NGINX_DEFAULT'
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 768;
}
http {
sendfile on;
tcp_nopush on;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
gzip on;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
NGINX_DEFAULT
ok "nginx.conf восстановлен"
}
# Добавляем блок stream{} в nginx.conf (идемпотентно)
nginx_ensure_stream_block() {
# Сначала проверяем целостность файла
nginx_restore_if_broken
# Проверяем через grep -E (Extended regex) — поддерживает |
if grep -qE 'proxy-stream-block|telemt-stream-block|xray-stream-block' "$NGINX_CONF_MAIN" 2>/dev/null; then
ok "Блок stream{} уже есть в nginx.conf"
return
fi
# Дополнительно — вдруг stream{} добавлен вручную
if grep -q 'stream[[:space:]]*{' "$NGINX_CONF_MAIN" 2>/dev/null; then
ok "Блок stream{} уже есть в nginx.conf"
return
fi
info "Добавляю блок stream{} в конец nginx.conf..."
mkdir -p "$NGINX_STREAM_DIR"
printf '\n# proxy-stream-block\nstream {\n include /etc/nginx/stream.d/*.conf;\n}\n# /proxy-stream-block\n' \
>> "$NGINX_CONF_MAIN"
}
# Перезапуск nginx с валидацией
nginx_reload() {
info "Проверяю конфигурацию nginx..."
if ! nginx -t 2>&1; then
die "Конфигурация nginx невалидна — см. вывод выше"
fi
# Если внешний порт уже занят НЕ nginx (частый случай — легаси telemt,
# держащий 443 напрямую), nginx упадёт с 'Address already in use'.
# Ловим это заранее и даём понятную диагностику вместо туманной ошибки.
if port_in_use "$NGINX_EXT_PORT"; then
local holder
holder="$(ss -tlnpH "( sport = :${NGINX_EXT_PORT} )" 2>/dev/null | grep -oE 'users:\(\("[^"]+"' | grep -oE '"[^"]+"' | tr -d '"' | head -1 || true)"
if [[ -n "$holder" && "$holder" != "nginx" ]]; then
warn "Порт ${NGINX_EXT_PORT} уже занят процессом '${holder}' — nginx не сможет его забиндить."
if [[ "$holder" == telemt* ]]; then
warn "Похоже, это telemt в direct-режиме (он сам слушает ${NGINX_EXT_PORT})."
warn "На этой ноде xray ходит через nginx:${NGINX_EXT_PORT} — нельзя держать оба на одном порту."
warn "Останови/удали telemt: systemctl stop telemt* ; либо запусти 'bash $0 purge' для очистки легаси."
fi
die "Освободи порт ${NGINX_EXT_PORT} и повтори (см. предупреждения выше)."
fi
fi
svc_enable nginx
svc_restart nginx
ok "nginx перезапущен, слушает :${NGINX_EXT_PORT}"
}
# Проверяем доступность сервиса через nginx
nginx_check_port() {
local sni="$1"
info "Проверяю доступность ${sni}:${NGINX_EXT_PORT}..."
local code
code="$(curl -sk -o /dev/null -w '%{http_code}' \
--connect-timeout 5 \
--resolve "${sni}:${NGINX_EXT_PORT}:127.0.0.1" \
"https://${sni}:${NGINX_EXT_PORT}" 2>/dev/null || true)"
if [[ "$code" =~ ^[0-9]+$ ]]; then
ok "nginx отвечает на :${NGINX_EXT_PORT} (HTTP ${code}) ✓"
else
warn "nginx не ответил на :${NGINX_EXT_PORT} — проверьте: ufw status, systemctl status nginx"
fi
}
# Открываем внешний порт в файрволе
firewall_open() {
local port="$1" label="${2:-proxy}"
if command -v ufw &>/dev/null; then
ufw allow "${port}/tcp" comment "$label" > /dev/null 2>&1 || true
ok "ufw: порт ${port}/tcp открыт"
elif command -v firewall-cmd &>/dev/null; then
firewall-cmd --permanent --add-port="${port}/tcp" > /dev/null 2>&1 || true
firewall-cmd --reload > /dev/null 2>&1 || true
ok "firewalld: порт ${port}/tcp открыт"
elif command -v iptables &>/dev/null; then
iptables -C INPUT -p tcp --dport "$port" -j ACCEPT 2>/dev/null || \
iptables -A INPUT -p tcp --dport "$port" -j ACCEPT 2>/dev/null || true
ok "iptables: порт ${port}/tcp открыт"
else
warn "Файрвол не найден — откройте порт ${port} вручную"
fi
}
# Блокируем внутренний порт снаружи (только lo)
firewall_block_internal() {
local port="$1"
if command -v iptables &>/dev/null; then
iptables -C INPUT -p tcp --dport "$port" -i lo -j ACCEPT 2>/dev/null || \
iptables -I INPUT 1 -p tcp --dport "$port" -i lo -j ACCEPT 2>/dev/null || true
iptables -C INPUT -p tcp --dport "$port" -j DROP 2>/dev/null || \
iptables -A INPUT -p tcp --dport "$port" -j DROP 2>/dev/null || true
ok "Внутренний порт ${port} доступен только через lo"
fi
}
# Записываем stream-конфиг с map по SNI (один server на порту 443)
# Принимает: файл назначения, лог-файл, массив пар "sni:backend"
nginx_write_stream_conf() {
local dest="$1" logfile="$2"
shift 2
local pairs=("$@") # "sni1:backend1" "sni2:backend2" ...
mkdir -p "$NGINX_STREAM_DIR" "$(dirname "$logfile")"
{
cat << 'HEADER'
# nginx stream: SNI-роутинг (авто-генерация setup.sh)
# Редактируйте /etc/nginx/stream.d/*.conf или перезапустите setup.sh
log_format proxy_stream '$remote_addr [$time_local] '
'$protocol $status $bytes_sent $bytes_received '
'$session_time "$ssl_preread_server_name" -> $upstream_addr';
HEADER
echo "access_log ${logfile} proxy_stream;"
echo ""
echo "map \$ssl_preread_server_name \$proxy_backend {"
echo ' default "";'
for pair in "${pairs[@]}"; do
local sni="${pair%%:*}"
local backend="${pair#*:}"
printf ' "%s" "%s";\n' "${sni}" "${backend}"
done
echo "}"
echo ""
cat << 'SERVER'
server {
listen 443;
ssl_preread on;
proxy_connect_timeout 5s;
proxy_timeout 600s;
proxy_pass $proxy_backend;
}
SERVER
} > "$dest"
ok "Stream конфиг записан → ${dest}"
}
# Добавляем или обновляем одну запись в существующем map
nginx_upsert_map_entry() {
local conf="$1" sni="$2" backend="$3"
if grep -qF "\"${sni}\"" "$conf" 2>/dev/null; then
# Обновляем существующую
sed -i "s|\"${sni}\".*|\"${sni}\" \"${backend}\";|" "$conf"
info "Обновлена запись SNI '${sni}' → ${backend}"
else
# Вставляем новую после строки с default
sed -i "/default.*\"\";/a\\ \"${sni}\" \"${backend}\";" "$conf"
ok "Добавлена запись SNI '${sni}' → ${backend}"
fi
}
# ══════════════════════════════════════════════════════════════════════════════
# FAIL2BAN (для telemt — баним пустой/неверный SNI)
# ══════════════════════════════════════════════════════════════════════════════
configure_fail2ban() {
section "fail2ban"
if ! command -v fail2ban-client &>/dev/null; then
info "Устанавливаю fail2ban..."
case "$OS_FAMILY" in
debian) pkg_install fail2ban ;;
rhel)
pkg_install fail2ban 2>/dev/null || {
pkg_install epel-release 2>/dev/null || true
pkg_install fail2ban 2>/dev/null || { warn "fail2ban недоступен"; return; }
} ;;
alpine) pkg_install fail2ban 2>/dev/null || { warn "fail2ban недоступен"; return; } ;;
esac
fi
mkdir -p "$(dirname "$F2B_FILTER")" "$(dirname "$F2B_JAIL")"
tee "$F2B_FILTER" > /dev/null << 'EOF'
[Definition]
failregex = ^<HOST> \[.*\] \S+ \S+ \S+ \S+ \S+ ".*" -> -$
ignoreregex =
datepattern = \[%%d/%%b/%%Y:%%H:%%M:%%S %%z\]
EOF
cat > "$F2B_JAIL" << EOF
[nginx-stream-empty-sni]
enabled = true
filter = nginx-stream-empty-sni
logpath = ${NGINX_STREAM_LOG}
backend = auto
findtime = 300
maxretry = 10
bantime = 21600
action = iptables-multiport[name=nginx-stream-empty-sni, port="${NGINX_EXT_PORT}", protocol=tcp]
EOF
svc_enable fail2ban
svc_restart fail2ban
ok "fail2ban: 10 попыток / 5мин → бан 6ч"
}
# ══════════════════════════════════════════════════════════════════════════════
# МОДУЛЬ TELEMT
# ══════════════════════════════════════════════════════════════════════════════
# client_mss: "" (не трогать MSS) | пресеты extreme-low|tspu|2in8 | число 88..4096
# Дефолт "tspu" (=92) — быстрый фикс под агрессивный профиль ТСПУ.
# Требует telemt >= 3.4.15.
# В direct-схеме (без nginx) MSS реально доходит до клиента — здесь он работает.
TELEMT_CLIENT_MSS="${TELEMT_CLIENT_MSS:-tspu}"
TELEMT_MIN_MSS_VERSION="3.4.15"
# ── Direct-инстансы (без nginx): порт|домен|api_порт ──
# Каждая пара порт+домен подобрана под повседневный фоновый трафик ОС/сервисов:
# 443 + cloudflare → обычный HTTPS/CDN
# 5223 + apple → Apple Push Notification Service (APNs)
# 8530 + microsoft → Windows Update / WSUS
# Несколько инстансов = запас на точечную блокировку пары порт+домен.
# Формат строки: "ИМЯ:ПОРТ:ДОМЕН:API_ПОРТ"
TELEMT_DIRECT_INSTANCES=(
"telemt1:443:www.cloudflare.com:9091"
"telemt2:5223:www.apple.com:9092"
"telemt3:8530:www.microsoft.com:9093"
)
# Карта «основной порт → запасной», если основной занят (конфликт bind).
# 443 → 853: оба не шейпятся, 853 = DNS-over-TLS. Для остальных запасного нет
# (5223/8530 редко заняты). Это про доступность порта, не про обход DPI.
declare -A TELEMT_PORT_FALLBACK=(
[443]=853
)
# Фактические порты инстансов после возможной подмены (заполняется при установке).
TELEMT_DIRECT_PORTS=()
# ── Бэкенд запуска: "systemd" (нативный бинарь + юниты) или "docker" ──
# Можно задать заранее через переменную окружения, иначе спросим в меню.
TELEMT_BACKEND="${TELEMT_BACKEND:-}"
# Официальный образ telemt (можно переопределить в env).
TELEMT_IMAGE="${TELEMT_IMAGE:-ghcr.io/telemt/telemt:latest}"
# docker-compose файл для 3 инстансов.
TELEMT_COMPOSE="/etc/telemt/docker-compose.yml"
# ── SOCKS5 upstream (РФ-нода) ──
# Если заданы — telemt отправляет исходящий трафик к Telegram через SOCKS5
# (xray/sing-box/ssh -D на localhost). По FAQ telemt для этого режима ОБЯЗАТЕЛЕН
# use_middle_proxy=false (он у нас и так false). Пусто = direct (выход прямой).
TELEMT_SOCKS5_HOST=""
TELEMT_SOCKS5_PORT=""
TELEMT_SOCKS5_USER=""
TELEMT_SOCKS5_PASS=""
# Валидация значения client_mss. Печатает нормализованное значение, либо die.
telemt_validate_mss() {
local v="$1"
case "$v" in
""|extreme-low|tspu|2in8) printf '%s' "$v"; return 0 ;;
*)
if [[ "$v" =~ ^[0-9]+$ ]] && (( v >= 88 && v <= 4096 )); then
printf '%s' "$v"; return 0
fi
die "Недопустимое значение client_mss: '${v}' (ожидается пусто|extreme-low|tspu|2in8|число 88..4096)"
;;
esac
}
# Сравнение версий: возвращает 0, если $1 >= $2 (semver x.y.z).
telemt_version_ge() {
local a="$1" b="$2"
[[ "$(printf '%s\n%s\n' "$a" "$b" | sort -V | head -1)" == "$b" ]]
}
telemt_detect_arch() {
local m; m="$(uname -m)"
case "$m" in
x86_64|amd64)
if grep -qE 'avx2.*bmi2|bmi2.*avx2' /proc/cpuinfo 2>/dev/null; then echo "x86_64-v3"
else echo "x86_64"; fi ;;
aarch64|arm64) echo "aarch64" ;;
*) die "Неподдерживаемая архитектура: $m" ;;
esac
}
telemt_detect_libc() {
for f in /lib/ld-musl-*.so.* /lib64/ld-musl-*.so.*; do [[ -e "$f" ]] && { echo musl; return; }; done
grep -qE '^ID="?alpine"?' /etc/os-release 2>/dev/null && { echo musl; return; }
echo gnu
}
telemt_gen_secret() {
local s=""
s="$(openssl rand -hex 16 2>/dev/null || true)"
[[ ${#s} -eq 32 ]] && { echo "$s"; return; }
s="$(dd if=/dev/urandom bs=16 count=1 2>/dev/null | od -An -tx1 | tr -d ' \n')"
[[ ${#s} -eq 32 ]] && { echo "$s"; return; }
die "Не удалось сгенерировать секрет"
}
telemt_ensure_user() {
mkdir -p /opt/telemt /etc/telemt
if [[ "$TELEMT_BACKEND" == "docker" ]]; then
# Контейнер бежит от root (user: "0:0"); системный юзер telemt на хосте
# не нужен. telemt-API пишет config.toml через write-temp → rename в той
# же директории, поэтому каталог должен быть доступен на запись владельцу.
chown root:root /opt/telemt /etc/telemt
chmod 755 /opt/telemt /etc/telemt
return
fi
# systemd: telemt бежит под непривилегированным юзером telemt
local nologin; nologin="$(command -v nologin 2>/dev/null || echo /bin/false)"
getent group telemt &>/dev/null || grep -q '^telemt:' /etc/group 2>/dev/null || \
groupadd -r telemt 2>/dev/null || addgroup -S telemt 2>/dev/null || true
getent passwd telemt &>/dev/null || grep -q '^telemt:' /etc/passwd 2>/dev/null || \
useradd -r -g telemt -d /opt/telemt -s "$nologin" -c "Telemt" telemt 2>/dev/null || \
adduser -S -D -H -h /opt/telemt -s "$nologin" -G telemt telemt 2>/dev/null || true
chown telemt:telemt /opt/telemt && chmod 750 /opt/telemt
chown telemt:telemt /etc/telemt && chmod 750 /etc/telemt
}
# Генерация конфигов всех инстансов (общее для systemd и docker).
# Заполняет TELEMT_DIRECT_PORTS и TELEMT_DIRECT_SECRETS, пишет /etc/telemt/telemtN.toml.
TELEMT_DIRECT_SECRETS=()
TELEMT_INSTALLED_VERSION=""
_telemt_generate_configs() {
telemt_ensure_user
local mss; mss="$(telemt_validate_mss "$TELEMT_CLIENT_MSS")"
local client_mss_line=""
[[ -n "$mss" ]] && client_mss_line="client_mss = \"${mss}\""
# Блок upstream: SOCKS5 (РФ-нода) либо direct (прямой выход).
# type="socks5" требует use_middle_proxy=false (у нас уже так).
local upstream_block
if [[ -n "$TELEMT_SOCKS5_HOST" && -n "$TELEMT_SOCKS5_PORT" ]]; then
upstream_block=$'\n[[upstreams]]\ntype = "socks5"\naddress = "'"${TELEMT_SOCKS5_HOST}:${TELEMT_SOCKS5_PORT}"$'"'
if [[ -n "$TELEMT_SOCKS5_USER" && -n "$TELEMT_SOCKS5_PASS" ]]; then
upstream_block+=$'\nusername = "'"${TELEMT_SOCKS5_USER}"$'"\npassword = "'"${TELEMT_SOCKS5_PASS}"$'"'
fi
upstream_block+=$'\nweight = 1\nenabled = true'
else
upstream_block=$'\n[[upstreams]]\ntype = "direct"\nweight = 1\nenabled = true'
fi
local idx=0
TELEMT_DIRECT_SECRETS=()
local entry name port domain api secret escaped_domain
for entry in "${TELEMT_DIRECT_INSTANCES[@]}"; do
IFS=':' read -r name port domain api <<< "$entry"
# Если основной порт занят (напр. nginx/apache/панель уже на 443) —
# переключаемся на запасной из TELEMT_PORT_FALLBACK. Это решает конфликт
# bind, а НЕ проблему DPI: по замерам мая-2026 853/8443 на федеральных
# операторах блокируются наравне с 443.
if port_in_use "$port"; then
local fb="${TELEMT_PORT_FALLBACK[$port]:-}"
if [[ -n "$fb" ]] && ! port_in_use "$fb"; then
warn "Порт ${port} занят — переключаю ${name} на запасной ${fb}"
port="$fb"
elif [[ -n "$fb" ]]; then
warn "Порт ${port} и запасной ${fb} оба заняты — ${name} может не запуститься (bind error)"
else
warn "Порт ${port} занят, запасного нет — ${name} может не запуститься"
fi
fi
TELEMT_DIRECT_PORTS[idx]="$port"
secret="$(telemt_gen_secret)"
TELEMT_DIRECT_SECRETS[idx]="$secret"
escaped_domain="$(printf '%s' "$domain" | sed 's/\\/\\\\/g; s/"/\\"/g')"
cat > "/etc/telemt/${name}.toml" << EOF
[general]
fast_mode = true
use_middle_proxy = false
[general.modes]
classic = false
secure = false
tls = true
[network]
ipv4 = true
ipv6 = false
prefer = 4
[server]
port = ${port}
listen_addr_ipv4 = "0.0.0.0"
${client_mss_line}
[server.api]
enabled = true
listen = "127.0.0.1:${api}"
whitelist = ["127.0.0.1/32"]
[censorship]
tls_domain = "${escaped_domain}"
mask = true
mask_port = 443
tls_emulation = true
unknown_sni_action = "reject_handshake"
fake_cert_len = 2048
[access]
replay_check_len = 65536
ignore_time_skew = false
[access.users]
${name} = "${secret}"
${upstream_block}
EOF
# Права на конфиг зависят от бэкенда:
# - systemd: telemt бежит под юзером telemt → 640 root:telemt (узкие права);
# - docker: внутри контейнера группы telemt нет, образ может бежать под
# другим UID → даём 644 root:root, чтобы конфиг гарантированно читался.
if [[ "$TELEMT_BACKEND" == "docker" ]]; then
chown root:root "/etc/telemt/${name}.toml"
chmod 644 "/etc/telemt/${name}.toml"
else
chown root:telemt "/etc/telemt/${name}.toml"
chmod 640 "/etc/telemt/${name}.toml"
fi
ok "${name}: порт ${port}, ${domain}, api ${api}"
idx=$((idx + 1))
done
}
# ── Бэкенд systemd: нативный бинарь + юнит на инстанс ──
install_telemt_systemd() {
section "Установка telemt (systemd, ${#TELEMT_DIRECT_INSTANCES[@]} инстанса)"
TELEMT_INSTALLED_VERSION="$(telemt_download_binary)"
[[ -n "$TELEMT_INSTALLED_VERSION" ]] || die "Не удалось установить бинарь telemt"
if [[ -n "$TELEMT_CLIENT_MSS" ]] && ! telemt_version_ge "$TELEMT_INSTALLED_VERSION" "$TELEMT_MIN_MSS_VERSION"; then
warn "telemt ${TELEMT_INSTALLED_VERSION} < ${TELEMT_MIN_MSS_VERSION}: client_mss будет проигнорирован ядром telemt."
fi
_telemt_generate_configs
local svcmgr; svcmgr="$(get_svc_mgr)"
local entry name port domain api
for entry in "${TELEMT_DIRECT_INSTANCES[@]}"; do
IFS=':' read -r name port domain api <<< "$entry"
if [[ "$svcmgr" == "systemd" ]]; then
cat > "/etc/systemd/system/${name}.service" << EOF
[Unit]
Description=Telemt MTProto Proxy (${domain})
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=telemt
Group=telemt
WorkingDirectory=/opt/telemt
ExecStart=/bin/telemt /etc/telemt/${name}.toml
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target
EOF
elif [[ "$svcmgr" == "openrc" ]]; then
cat > "/etc/init.d/${name}" << EOF
#!/sbin/openrc-run
name="${name}"
command="/bin/telemt"
command_args="/etc/telemt/${name}.toml"
command_background=true
command_user="telemt:telemt"
pidfile="/run/${name}.pid"
directory="/opt/telemt"
rc_ulimit="-n 65536"
depend() { need net; }
EOF
chmod 0755 "/etc/init.d/${name}"
fi
done
[[ "$svcmgr" == "systemd" ]] && systemctl daemon-reload
for entry in "${TELEMT_DIRECT_INSTANCES[@]}"; do
IFS=':' read -r name _ _ _ <<< "$entry"
svc_enable "$name"
svc_restart "$name"
done
ok "telemt ${TELEMT_INSTALLED_VERSION} установлен (systemd)"
}
# ── Бэкенд docker: официальный образ + 3 сервиса в одном compose ──
# network_mode: host — порты контейнеров видны на хосте напрямую (как systemd),
# что сохраняет работу UFW per-port rate-limit и client_mss (нужен NET_ADMIN).
install_telemt_docker() {
section "Установка telemt (docker, ${#TELEMT_DIRECT_INSTANCES[@]} инстанса)"
telemt_ensure_docker
_telemt_generate_configs
info "Тяну образ ${TELEMT_IMAGE}..."
docker pull "$TELEMT_IMAGE" || warn "Не удалось обновить образ (использую локальный, если есть)"
# Собираем docker-compose с 3 сервисами.
# client_mss требует CAP_NET_ADMIN; бинд привилегированных портов — NET_BIND_SERVICE.
# Конфиг монтируем как директорию /etc/telemt (требование API telemt для
# атомарного rename), каждому сервису передаём свой файл аргументом.
{
echo "services:"
local entry name port domain api
for entry in "${TELEMT_DIRECT_INSTANCES[@]}"; do
IFS=':' read -r name port domain api <<< "$entry"
cat << EOF
${name}:
image: ${TELEMT_IMAGE}
container_name: ${name}
restart: unless-stopped
network_mode: host
user: "0:0"
cap_add:
- NET_BIND_SERVICE
- NET_ADMIN
volumes:
- /etc/telemt:/etc/telemt
command: ["/etc/telemt/${name}.toml"]
ulimits:
nofile:
soft: 65536
hard: 65536
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
EOF
done
} > "$TELEMT_COMPOSE"
chmod 640 "$TELEMT_COMPOSE"
info "Запускаю контейнеры..."
(cd /etc/telemt && docker compose up -d) || die "Не удалось запустить контейнеры telemt"
TELEMT_INSTALLED_VERSION="$(docker run --rm "$TELEMT_IMAGE" --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "?")"
ok "telemt ${TELEMT_INSTALLED_VERSION} установлен (docker)"
}
# Установка Docker по официальному скрипту, если его нет.
telemt_ensure_docker() {
if command -v docker &>/dev/null && docker compose version &>/dev/null; then
return 0
fi
info "Устанавливаю Docker (get.docker.com)..."
curl -fsSL https://get.docker.com | sh || die "Не удалось установить Docker"
svc_enable docker; svc_start docker
command -v docker &>/dev/null || die "Docker не установлен"
docker compose version &>/dev/null || die "docker compose недоступен (нужен Compose v2)"
}
# Диспетчер бэкенда: вызывает нужный установщик.
install_telemt_direct() {
case "$TELEMT_BACKEND" in
docker) install_telemt_docker ;;
*) install_telemt_systemd ;;
esac
}
# Определить установленный бэкенд по артефактам на диске.
# Печатает "docker" | "systemd" | "" (не установлен).
telemt_detect_backend() {
[[ -f "$TELEMT_COMPOSE" ]] && { echo "docker"; return; }
ls /etc/telemt/telemt*.toml &>/dev/null && { echo "systemd"; return; }
echo ""
}
# Активен ли инстанс $1 — независимо от бэкенда.
telemt_instance_active() {
local name="$1"
if [[ "$(telemt_detect_backend)" == "docker" ]]; then
command -v docker &>/dev/null && docker ps --format '{{.Names}}' 2>/dev/null | grep -qx "$name"
else
svc_active "$name" 2>/dev/null
fi
}
# Перезапустить все инстансы — независимо от бэкенда.
telemt_restart_all() {
if [[ "$(telemt_detect_backend)" == "docker" ]]; then
(cd /etc/telemt && docker compose restart) 2>/dev/null || true
else
local entry name
for entry in "${TELEMT_DIRECT_INSTANCES[@]}"; do
IFS=':' read -r name _ _ _ <<< "$entry"
svc_restart "$name"
done
fi
}
# Скачать и установить актуальный бинарь telemt поверх существующего.
# Печатает установленную версию в stdout (последней строкой).
telemt_download_binary() {
local arch libc latest fn url tmpd
arch="$(telemt_detect_arch)"; libc="$(telemt_detect_libc)"
info "Архитектура: ${arch}, libc: ${libc}" >&2
latest="$(curl -fsI 'https://github.com/telemt/telemt/releases/latest' 2>/dev/null \
| grep -i '^location:' | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || true)"
[[ -z "$latest" ]] && die "Не удалось определить версию telemt"
info "Версия: ${latest}" >&2
fn="telemt-${arch}-linux-${libc}.tar.gz"
url="https://github.com/telemt/telemt/releases/download/${latest}/${fn}"
tmpd="$(mktemp -d)"
info "Скачиваю ${fn}..." >&2
if ! curl -fsSL "$url" -o "${tmpd}/${fn}" 2>/dev/null; then
if [[ "$arch" == "x86_64-v3" ]]; then
warn "x86_64-v3 недоступен, откат к x86_64..."
arch="x86_64"; fn="telemt-${arch}-linux-${libc}.tar.gz"
url="https://github.com/telemt/telemt/releases/download/${latest}/${fn}"
curl -fsSL "$url" -o "${tmpd}/${fn}" || die "Не удалось скачать telemt"
else
die "Не удалось скачать telemt: $url"
fi
fi
gzip -dc "${tmpd}/${fn}" | tar -xf - -C "$tmpd" 2>/dev/null || die "Ошибка распаковки"
local bin; bin="$(find "$tmpd" -type f -name telemt | head -1)"
[[ -n "$bin" ]] || die "Бинарник telemt не найден в архиве"
install -m 0755 "$bin" /bin/telemt
command -v setcap &>/dev/null && \
setcap cap_net_bind_service,cap_net_admin=+ep /bin/telemt 2>/dev/null || true
rm -rf "$tmpd"
printf '%s' "$latest"
}
# Идемпотентно записать client_mss в секцию [server] всех direct-конфигов.
# $1 = значение MSS (уже валидированное; пусто = удалить параметр).
telemt_set_mss_in_config() {
local mss="$1" cfg found=0
for cfg in /etc/telemt/telemt*.toml; do
[[ -f "$cfg" ]] || continue
found=1
# Удалить любые существующие строки client_mss
sed -i -E '/^[[:space:]]*client_mss[[:space:]]*=/d' "$cfg"
if [[ -n "$mss" ]]; then
if grep -qE '^\[server\][[:space:]]*$' "$cfg"; then
sed -i -E "/^\[server\][[:space:]]*$/a client_mss = \"${mss}\"" "$cfg"
else
warn "Секция [server] не найдена в $cfg — пропуск"
fi
fi
done
[[ "$found" -eq 1 ]] || die "Конфиги telemt не найдены в /etc/telemt/"
}
# Список имён установленных direct-инстансов (по конфигам на диске).
telemt_installed_instances() {
local cfg name
for cfg in /etc/telemt/telemt*.toml; do
[[ -f "$cfg" ]] || continue
name="$(basename "$cfg" .toml)"
echo "$name"
done
}
# Обновление telemt + настройка client_mss одной командой (по всем инстансам).
# Использование: update_telemt [MSS]
# MSS: extreme-low|tspu|2in8|число 88..4096|"" (снять параметр). По умолчанию tspu.
update_telemt() {
section "Обновление telemt + client_mss"
local insts; insts="$(telemt_installed_instances)"
[[ -n "$insts" ]] || \
die "telemt не установлен (нет /etc/telemt/telemt*.toml). Сначала: bash $0 telemt"
detect_os
local backend; backend="$(telemt_detect_backend)"
[[ -n "$backend" ]] || backend="systemd"
info "Бэкенд: ${backend}"
local mss; mss="$(telemt_validate_mss "${1:-$TELEMT_CLIENT_MSS}")"
# 1. Обновить telemt: образ (docker) или бинарь (systemd)
local latest
if [[ "$backend" == "docker" ]]; then
telemt_ensure_docker
info "Обновляю образ ${TELEMT_IMAGE}..."
docker pull "$TELEMT_IMAGE" || warn "Не удалось обновить образ"
latest="$(docker run --rm "$TELEMT_IMAGE" --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "?")"
else
latest="$(telemt_download_binary)"
fi
# 2. Проверка поддержки client_mss
if [[ -n "$mss" && "$latest" != "?" ]] && ! telemt_version_ge "$latest" "$TELEMT_MIN_MSS_VERSION"; then
warn "telemt ${latest} < ${TELEMT_MIN_MSS_VERSION}: client_mss не поддерживается этой версией."
fi
# 3. Прописать параметр во все конфиги (с бэкапом)
local cfg
for cfg in /etc/telemt/telemt*.toml; do
[[ -f "$cfg" ]] && cp -a "$cfg" "${cfg}.bak.$(date +%s)" 2>/dev/null || true
done
telemt_set_mss_in_config "$mss"
if [[ "$backend" == "docker" ]]; then
chown root:root /etc/telemt/telemt*.toml 2>/dev/null || true
chmod 644 /etc/telemt/telemt*.toml 2>/dev/null || true
else
chown root:telemt /etc/telemt/telemt*.toml 2>/dev/null || true
chmod 640 /etc/telemt/telemt*.toml 2>/dev/null || true
fi
# 4. Применить: docker — пересоздать контейнеры с новым образом; systemd — рестарт
if [[ "$backend" == "docker" ]]; then
(cd /etc/telemt && docker compose up -d) || warn "docker compose up -d не выполнен"
else
telemt_restart_all
fi
sleep 1
ok "telemt обновлён до ${latest} (${backend})"
if [[ -n "$mss" ]]; then
ok "client_mss = \"${mss}\" применён ко всем инстансам"
else
ok "client_mss снят — MSS определяет ядро"
fi
info "Проверка: grep client_mss /etc/telemt/telemt*.toml"
}
# ── UFW: открыть порты инстансов + per-port rate-limit (анти-зондирование) ──
# По гайду: не больше 1 нового SYN/сек с одного IP на каждый порт.
# Раздельные recent-списки на порт (mtp443, mtp5223…) — иначе Telegram при
# переключении между прокси шлёт SYN на все порты разом и лимит рвёт соединение.
telemt_ufw_ratelimit() {
section "UFW: порты + rate-limit"
if ! command -v ufw &>/dev/null; then
warn "ufw не установлен — пропускаю rate-limit. Открой порты вручную."
return
fi
# Порты инстансов — фактические (после возможной подмены 443→853).
# Если массив пуст (вызов вне install), читаем порт из конфигов на диске.
local ports=()
local entry name port
if [[ ${#TELEMT_DIRECT_PORTS[@]} -gt 0 ]]; then
ports=("${TELEMT_DIRECT_PORTS[@]}")
else
for entry in "${TELEMT_DIRECT_INSTANCES[@]}"; do
IFS=':' read -r name port _ _ <<< "$entry"
if [[ -f "/etc/telemt/${name}.toml" ]]; then
port="$(awk -F'=' '/^port[[:space:]]*=/{gsub(/[[:space:]]/,"",$2); print $2; exit}' "/etc/telemt/${name}.toml")"
fi
ports+=("$port")
done
fi
# SSH первым — критично, чтобы не отрезать себя
local ssh_port; ssh_port="$(ss -tlnpH 2>/dev/null | grep -i sshd | grep -oE ':[0-9]+' | head -1 | tr -d ':' || true)"
ssh_port="${ssh_port:-22}"
info "Разрешаю SSH-порт ${ssh_port} перед включением UFW"
ufw allow "${ssh_port}/tcp" >/dev/null 2>&1 || true
local p
for p in "${ports[@]}"; do
ufw allow "${p}/tcp" >/dev/null 2>&1 || true
done
ufw --force enable >/dev/null 2>&1 || true
# Модуль xt_recent — без него UFW молча выбросит правила -m recent
modprobe xt_recent 2>/dev/null || true
echo xt_recent > /etc/modules-load.d/xt_recent.conf 2>/dev/null || true
if ! lsmod 2>/dev/null | grep -q xt_recent; then
warn "Модуль xt_recent не загрузился — rate-limit не будет применён."
warn "Проверь вручную: modprobe xt_recent && lsmod | grep xt_recent"
ok "Порты открыты (без rate-limit)"
return
fi
# Бэкап и вставка правил в ufw-before-input (после established)
local rules="/etc/ufw/before.rules"
if [[ -f "$rules" ]]; then
cp "$rules" "${rules}.bak.$(date +%s)" 2>/dev/null || true
TELEMT_RL_PORTS="${ports[*]}" python3 - "$rules" << 'PYEOF'
import os, sys
path = sys.argv[1]
ports = os.environ["TELEMT_RL_PORTS"].split()
lines = open(path).readlines()
if any("MTProto rate-limit" in l for l in lines):
print(" правила rate-limit уже есть, пропуск"); raise SystemExit
idx = None
for i, l in enumerate(lines):
if "ufw-before-input -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT" in l:
idx = i + 1; break
if idx is None:
# запасная точка: вставка после первого ufw-before-input ACCEPT lo
for i, l in enumerate(lines):
if "ufw-before-input -i lo -j ACCEPT" in l:
idx = i + 1; break
if idx is None:
print(" ОШИБКА: точка вставки в before.rules не найдена"); raise SystemExit
block = ["\n# === MTProto rate-limit (1 SYN/сек на IP per-port) ===\n"]
for p in ports:
block.append(f"-A ufw-before-input -p tcp --dport {p} --syn -m recent --name mtp{p} --rcheck --seconds 1 -j DROP\n")
block.append(f"-A ufw-before-input -p tcp --dport {p} --syn -m recent --name mtp{p} --set -j ACCEPT\n")
block.append("# === конец MTProto rate-limit ===\n")
lines[idx:idx] = block
open(path, "w").writelines(lines)
print(f" rate-limit вставлен для портов: {', '.join(ports)}")
PYEOF
ufw reload >/dev/null 2>&1 || true
ok "UFW rate-limit активен (1 SYN/сек/IP на порт)"
else
warn "$rules не найден — rate-limit пропущен, порты открыты"
fi
}
# Получить tls-ссылку инстанса из его локального API. $1 = api-порт.
# Печатает ссылку или пусто. Делает несколько попыток (API поднимается не мгновенно).
telemt_link_from_api() {
local api="$1" i link
for i in 1 2 3 4 5; do
link="$(curl -s --max-time 3 "http://127.0.0.1:${api}/v1/users" 2>/dev/null \
| python3 -c "import sys,json
try:
d=json.load(sys.stdin)
print(d['data'][0]['links']['tls'][0])
except Exception:
pass" 2>/dev/null)"
[[ -n "$link" ]] && { printf '%s' "$link"; return 0; }
sleep 1
done
return 1
}
# Общая установка telemt (3 инстанса) + UFW + вывод ссылок.
# $1 = роль ("direct" | "rf") — влияет только на заголовок и подсказки.
# Режим выхода определяется переменными TELEMT_SOCKS5_* (заданы до вызова).
_telemt_install_and_report() {
local role="$1"
detect_os
show_ports
# Выбор бэкенда запуска, если не задан заранее (env TELEMT_BACKEND).
if [[ -z "$TELEMT_BACKEND" ]]; then
echo ""
echo -e "${BOLD} Бэкенд запуска telemt:${NC}"
echo -e " ${GREEN}1)${NC} systemd (нативный бинарь + юниты)"
echo -e " ${GREEN}2)${NC} docker (официальный образ ${TELEMT_IMAGE}, 3 сервиса)"
echo -n " Выбор [1]: "
read -r _be
case "${_be:-1}" in
2) TELEMT_BACKEND="docker" ;;
*) TELEMT_BACKEND="systemd" ;;
esac
fi
info "Бэкенд: ${TELEMT_BACKEND}"
# Зависимости. python3/jq/ufw нужны на хосте в обоих режимах (API-ссылки,
# UFW rate-limit). Для systemd дополнительно нужны tar/gzip/openssl (бинарь);
# для docker — сам docker (ставится в install_telemt_docker).
pkg_update
if [[ "$TELEMT_BACKEND" == "docker" ]]; then
pkg_install curl jq python3 iptables ufw
else
pkg_install curl wget tar gzip openssl jq python3 iptables ufw
fi
# Установка (диспетчер выберет systemd или docker)
install_telemt_direct
# UFW: открыть порты инстансов + per-port rate-limit
telemt_ufw_ratelimit
sleep 2
local server_ip; server_ip="$(get_public_ip)"
echo ""
echo -e "${BOLD}${GREEN}╔══════════════════════════════════════════════════════════════╗${NC}"
if [[ "$role" == "rf" ]]; then
echo -e "${BOLD}${GREEN}║ TELEGRAM ПРОКСИ РФ (telemt → SOCKS5) НАСТРОЕН ${NC}"
else
echo -e "${BOLD}${GREEN}║ TELEGRAM ПРОКСИ (telemt direct) НАСТРОЕН ${NC}"
fi
echo -e "${BOLD}${GREEN}╚══════════════════════════════════════════════════════════════╝${NC}"
echo ""
if [[ "$role" == "rf" ]]; then
echo -e "${BOLD}Схема:${NC} Клиент → telemt (FakeTLS) → SOCKS5 ${TELEMT_SOCKS5_HOST}:${TELEMT_SOCKS5_PORT} → туннель → Telegram"
else
echo -e "${BOLD}Схема:${NC} Клиент → telemt (FakeTLS, порт=домен) → Telegram (прямой выход)"
fi
[[ -n "$TELEMT_CLIENT_MSS" ]] && \
echo -e " client_mss: ${TELEMT_CLIENT_MSS} — фрагментация TCP клиента (доходит до клиента: nginx нет)"
echo ""
local entry name port domain api link status
local i=0
for entry in "${TELEMT_DIRECT_INSTANCES[@]}"; do
IFS=':' read -r name port domain api <<< "$entry"
# Фактический порт (после возможной подмены 443→853)
port="${TELEMT_DIRECT_PORTS[$i]:-$port}"
if telemt_instance_active "$name" 2>/dev/null; then status="${GREEN}active${NC}"; else status="${RED}не запущен${NC}"; fi
echo -e "${BOLD}${CYAN}── ${name}: порт ${port}, домен ${domain} [${status}${CYAN}]${NC}"
link="$(telemt_link_from_api "$api" || true)"
if [[ -n "$link" ]]; then
link="$(printf '%s' "$link" | sed -E "s/server=[^&]*/server=${server_ip}/")"
echo -e " ${BOLD}${CYAN}${link}${NC}"
else
local hex_domain client_secret secret
secret="${TELEMT_DIRECT_SECRETS[$i]:-}"
hex_domain="$(printf '%s' "$domain" | xxd -p 2>/dev/null | tr -d '\n' || \
printf '%s' "$domain" | od -A n -t x1 | tr -d ' \n')"
client_secret="ee${secret}${hex_domain}"
echo -e " ${YELLOW}(API не ответил, ссылка собрана вручную)${NC}"
echo -e " ${BOLD}${CYAN}tg://proxy?server=${server_ip}&port=${port}&secret=${client_secret}${NC}"
fi
echo ""
i=$((i + 1))
done
echo -e "${BOLD}Команды:${NC}"
if [[ "$TELEMT_BACKEND" == "docker" ]]; then
echo -e " Логи: docker logs -f telemt1 (telemt2 / telemt3)"
echo -e " Рестарт: cd /etc/telemt && docker compose restart"
echo -e " Обновить: cd /etc/telemt && docker compose pull && docker compose up -d"
else
echo -e " Логи: journalctl -u telemt1 -f (telemt2 / telemt3)"
echo -e " Рестарт: systemctl restart telemt1 telemt2 telemt3"
fi
echo -e " Статус: bash $0 status"
echo -e " Ссылки: for p in 9091 9092 9093; do curl -s http://127.0.0.1:\$p/v1/users | jq -r '.data[0].links.tls[0]'; done"
echo -e " Баны UFW: iptables -L ufw-before-input -n | grep recent"
echo ""
if [[ "$role" == "rf" ]]; then
echo -e "${YELLOW} ⚠️ SOCKS5 ${TELEMT_SOCKS5_HOST}:${TELEMT_SOCKS5_PORT} должен слушать ДО трафика (xray/sing-box).${NC}"
echo -e "${YELLOW} Проверка: ss -tlnp | grep ${TELEMT_SOCKS5_PORT} | логи telemt при ошибке выхода.${NC}"
fi
echo -e "${YELLOW} ⚠️ Порт 5223 (APNs) и 8530 (WSUS) должны быть открыты у провайдера/хостера.${NC}"
echo ""
}
setup_telemt() {
section "Telegram MTProto Proxy (telemt, direct — без nginx)"
# Direct: SOCKS5 не задаём
TELEMT_SOCKS5_HOST=""; TELEMT_SOCKS5_PORT=""
TELEMT_SOCKS5_USER=""; TELEMT_SOCKS5_PASS=""
_telemt_install_and_report "direct"
}
setup_telemt_rf() {
section "Telegram MTProto Proxy РФ (telemt → SOCKS5)"
detect_os
# SOCKS5 upstream (туннель на localhost: xray/sing-box/ssh -D)
local def_sh="${MT_SOCKS5_HOST:-127.0.0.1}"
local def_sp="${MT_SOCKS5_PORT:-1080}"
echo -n " SOCKS5 host [${def_sh}]: "
read -r TELEMT_SOCKS5_HOST; TELEMT_SOCKS5_HOST="${TELEMT_SOCKS5_HOST:-$def_sh}"
echo -n " SOCKS5 port [${def_sp}]: "
read -r TELEMT_SOCKS5_PORT; TELEMT_SOCKS5_PORT="${TELEMT_SOCKS5_PORT:-$def_sp}"
echo -n " SOCKS5 username (пусто = без авторизации): "
read -r TELEMT_SOCKS5_USER
if [[ -n "$TELEMT_SOCKS5_USER" ]]; then
echo -n " SOCKS5 password: "
read -r TELEMT_SOCKS5_PASS
else
TELEMT_SOCKS5_PASS=""
fi
# Предупреждаем, если на SOCKS5-порту никто не слушает (не фатально)
if command -v ss &>/dev/null && [[ "$TELEMT_SOCKS5_HOST" == "127.0.0.1" || "$TELEMT_SOCKS5_HOST" == "localhost" ]]; then
if ! ss -tlnH "( sport = :${TELEMT_SOCKS5_PORT} )" 2>/dev/null | grep -q ":${TELEMT_SOCKS5_PORT}"; then
warn "На ${TELEMT_SOCKS5_HOST}:${TELEMT_SOCKS5_PORT} сейчас никто не слушает."
warn "Подними туннель (xray/sing-box) до старта трафика, иначе выход к Telegram не заработает."
fi
fi
_telemt_install_and_report "rf"
}
# ══════════════════════════════════════════════════════════════════════════════
# МОДУЛЬ XRAY (direct, без nginx — Reality сам делает TLS-камуфляж)
# ══════════════════════════════════════════════════════════════════════════════
# Внешний порт прослушивания xray выбирается из цепочки кандидатов: берём первый
# свободный. 443 предпочтителен (сливается с HTTPS-фоном); 853/8443 — запас на
# случай, если 443 занят (напр. telemt уже сидит на нём на той же машине).
# Это про доступность порта, а НЕ про обход DPI: Reality маскирует трафик
# независимо от порта, а 853/8443 на федеральных операторах режутся как 443.
# Можно переопределить: XRAY_PORT=<число> жёстко задаёт порт (без перебора).
XRAY_PORT_CANDIDATES=(443 853 8443)
XRAY_PORT="${XRAY_PORT:-}" # пусто = выбрать из цепочки; число = жёстко
XRAY_SNI=""
# Выбрать порт для xray: если XRAY_PORT задан жёстко (env) — проверить его;
# иначе перебрать XRAY_PORT_CANDIDATES и взять первый свободный.
# Результат пишется в XRAY_PORT. die, если ничего не свободно.
xray_resolve_port() {
if [[ -n "$XRAY_PORT" ]]; then
if port_in_use "$XRAY_PORT"; then
warn "Порт ${XRAY_PORT} (задан жёстко) занят."
warn "Освободи порт: ss -tlnp '( sport = :${XRAY_PORT} )'"
die "Порт ${XRAY_PORT} занят."
fi
info "Xray порт: ${XRAY_PORT} (задан явно)"
return
fi
local p
for p in "${XRAY_PORT_CANDIDATES[@]}"; do
if ! port_in_use "$p"; then
XRAY_PORT="$p"
info "Xray порт: ${XRAY_PORT} (первый свободный из: ${XRAY_PORT_CANDIDATES[*]})"
return
fi
warn "Порт ${p} занят — пробую следующий..."
done
die "Все порты-кандидаты заняты (${XRAY_PORT_CANDIDATES[*]}). Освободи один или задай XRAY_PORT=<порт>."
}
install_xray_bin() {
section "Установка Xray-core"
local ver tmpd
ver="$(curl -sI 'https://github.com/XTLS/Xray-core/releases/latest' \
| grep -i '^location:' | grep -oP 'v[\d.]+')"
[[ -z "$ver" ]] && ver="v26.2.6"
info "Версия: ${ver}"
tmpd="$(mktemp -d)"
wget -q -O "${tmpd}/xray.zip" \
"https://github.com/XTLS/Xray-core/releases/download/${ver}/Xray-linux-64.zip"
unzip -q "${tmpd}/xray.zip" -d "${tmpd}/xray"
install -m 755 "${tmpd}/xray/xray" "$XRAY_BIN"
mkdir -p "$XRAY_DIR" /usr/local/share/xray /var/log/xray
info "Скачиваю geo-базы..."
wget -q -O /usr/local/share/xray/geoip.dat \
'https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat'
wget -q -O /usr/local/share/xray/geosite.dat \
'https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat'
rm -rf "$tmpd"
cat > /etc/systemd/system/xray.service << 'UNIT'
[Unit]
Description=Xray Service
After=network.target nss-lookup.target
[Service]
User=root
ExecStart=/usr/local/bin/xray run -config /usr/local/etc/xray/config.json
Restart=on-failure
LimitNOFILE=1000000
[Install]
WantedBy=multi-user.target
UNIT
systemctl daemon-reload
ok "Xray установлен: $("$XRAY_BIN" version | head -1)"
}
# Возвращает «лояльный» TLS fingerprint для Reality (uTLS).
# По разбору «сибирской» схемы (июнь 2026) Сигнал 2 ловит МАССОВЫЕ фингерпринты,
# под которые чаще всего маскируются прокси: chrome, safari, ios — они исключены.
# randomized тоже исключён: это СИНТЕТИЧЕСКАЯ генерация (может дать комбинацию
# расширений/шифров, которой нет ни у одного реального браузера, — сама по себе
# аномалия). Оставляем редкие валидные пресеты + безопасный random
# (random берёт один из РЕАЛЬНЫХ пресетов браузера, почерк всегда валидный).
random_fp() {
local fps=("firefox" "edge" "android" "360" "qq" "random")
local idx=$(( RANDOM % ${#fps[@]} ))
echo "${fps[$idx]}"
}
setup_xray_euro() {
section "Xray Евро-нода (VLESS+Reality, direct — порт из ${XRAY_PORT_CANDIDATES[*]})"
detect_os
show_ports
# SNI-домен
local default_sni="www.amd.com"
echo -n " SNI-домен для Reality [${default_sni}]: "
read -r XRAY_SNI
XRAY_SNI="${XRAY_SNI:-$default_sni}"
# Выбор внешнего порта из цепочки кандидатов (443→853→8443)
xray_resolve_port
info "Xray будет слушать 0.0.0.0:${XRAY_PORT} напрямую (без nginx)"
pkg_update
pkg_install curl wget unzip uuid-runtime openssl python3
install_xray_bin
# Ключи
info "Генерирую ключи Reality..."
local keys; keys="$("$XRAY_BIN" x25519)"
local priv_key pub_key
priv_key="$(echo "$keys" | grep -i 'private' | awk '{print $NF}')"
pub_key="$(echo "$keys" | grep -i 'public' | awk '{print $NF}')"
[[ -z "$priv_key" || -z "$pub_key" ]] && die "Ошибка генерации ключей: $keys"
local uuid short_id fp
uuid="$(uuidgen)"
short_id="$(openssl rand -hex 8)"
fp="$(random_fp)"
info "TLS fingerprint: ${fp}"
# Конфиг xray — слушает напрямую на 0.0.0.0:XRAY_PORT
cat > "${XRAY_DIR}/config.json" << EOF
{
"log": { "loglevel": "warning" },
"dns": { "servers": ["1.1.1.1", "8.8.8.8"] },
"inbounds": [{
"listen": "0.0.0.0",
"port": ${XRAY_PORT},
"protocol": "vless",
"settings": {
"clients": [{ "id": "${uuid}" }],
"decryption": "none"
},
"streamSettings": {
"network": "xhttp",
"security": "reality",
"realitySettings": {
"show": false,
"dest": "${XRAY_SNI}:443",
"xver": 0,
"serverNames": ["${XRAY_SNI}"],
"privateKey": "${priv_key}",
"shortIds": ["${short_id}"]
},
"xhttpSettings": { "path": "/", "mode": "auto" }
}
}],
"outbounds": [{
"protocol": "freedom",
"tag": "direct",
"settings": { "domainStrategy": "UseIPv4v6" }
}]
}
EOF
# PQ шифрование (если поддерживается)
local pq_enc="none"
pq_enc="$(XRAY_UUID="$uuid" XRAY_DIR="$XRAY_DIR" python3 - << 'PYEOF'
import subprocess, json, os
try:
out = subprocess.check_output(["/usr/local/bin/xray", "vlessenc"]).decode()
enc = [l for l in out.split("\n") if '"encryption"' in l]
pq = enc[1].split('"encryption": "')[1].rstrip('"') if len(enc) > 1 else "none"
if pq != "none":
dec = [l for l in out.split("\n") if '"decryption"' in l]
pq_dec = dec[1].split('"decryption": "')[1].rstrip('"') if len(dec) > 1 else "none"
with open(os.environ["XRAY_DIR"] + "/config.json") as f:
cfg = json.load(f)
cfg["inbounds"][0]["settings"]["decryption"] = pq_dec
with open(os.environ["XRAY_DIR"] + "/config.json", "w") as f:
json.dump(cfg, f, indent=2)
print(pq)
except:
print("none")
PYEOF
)" || pq_enc="none"
# Файрвол: открываем внешний порт xray
firewall_open "$XRAY_PORT" "xray-direct"
# systemd
systemctl enable xray > /dev/null 2>&1
systemctl restart xray
sleep 2
svc_active xray && ok "Xray запущен" || die "Xray не запустился — journalctl -xe -u xray"
# Сохраняем для РФ-ноды
local server_ip; server_ip="$(get_public_ip)"
cat > "$XRAY_STATE_FILE" << EOF
EURO_IP=${server_ip}
EURO_PORT=${XRAY_PORT}
EURO_UUID=${uuid}
EURO_PUBLIC_KEY=${pub_key}
EURO_SHORT_ID=${short_id}
EURO_SNI=${XRAY_SNI}
EURO_ENCRYPTION=${pq_enc}
EOF
ok "Данные евро-ноды → ${XRAY_STATE_FILE}"
# Итог
echo ""
echo -e "${BOLD}${GREEN}╔══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${BOLD}${GREEN}ЕВРО-НОДА НАСТРОЕНА — СОХРАНИ ДАННЫЕ ${NC}"
echo -e "${BOLD}${GREEN}╚══════════════════════════════════════════════════════════════╝${NC}"
echo ""
echo -e "${BOLD}Схема:${NC}"
echo -e " Клиент → xray:${XRAY_PORT} (Reality, SNI: ${XRAY_SNI}) → интернет (без nginx)"
echo ""
echo -e "${BOLD}Данные для РФ-ноды:${NC}"
echo -e " EURO_IP=${server_ip} EURO_PORT=${XRAY_PORT}"
echo -e " EURO_UUID=${uuid}"
echo -e " EURO_PUBLIC_KEY=${pub_key}"
echo -e " EURO_SHORT_ID=${short_id} EURO_SNI=${XRAY_SNI}"
echo -e " (сохранено в ${XRAY_STATE_FILE})"
echo ""
echo -e "${BOLD}VLESS URI (прямое подключение к :${XRAY_PORT}):${NC}"
echo -e "${GREEN}vless://${uuid}@${server_ip}:${XRAY_PORT}?encryption=${pq_enc}&security=reality&sni=${XRAY_SNI}&fp=${fp}&pbk=${pub_key}&sid=${short_id}&type=xhttp&path=%2F&mode=auto#EURO-Reality${NC}"
echo ""
echo -e "${BOLD}Проверка:${NC}"
echo -e " curl -v https://${server_ip}:${XRAY_PORT} --resolve ${XRAY_SNI}:${XRAY_PORT}:${server_ip}"
echo -e " journalctl -fu xray"
echo ""
echo -e "${YELLOW} ⚠️ Порт ${XRAY_PORT} должен быть открыт у хостера. Следующий шаг: bash setup.sh xray-rf${NC}"
echo ""
}
setup_xray_rf() {
section "Xray РФ-нода (мост → евро)"
detect_os
show_ports
# Загружаем данные евро-ноды. EURO_PORT — порт ВЫХОДНОЙ евро-ноды (куда
# форвардим), не путать с портом прослушивания РФ-ноды (XRAY_PORT).
# Точное значение приходит из state-файла или ручного ввода; дефолт 443.
local EURO_IP="" EURO_PORT="443" EURO_UUID="" EURO_PUBLIC_KEY=""
local EURO_SHORT_ID="" EURO_SNI="www.amd.com" EURO_ENCRYPTION="none"
local RF_SNI="vkvideo.ru"
if [[ -f "$XRAY_STATE_FILE" ]]; then
echo -e "${GREEN} Найден файл ${XRAY_STATE_FILE}${NC}"
echo -n " Загрузить данные евро-ноды из файла? [Y/n]: "
read -r ans
if [[ -z "$ans" || "$ans" =~ ^[Yy]$ ]]; then
# shellcheck disable=SC1090
source "$XRAY_STATE_FILE"
ok "Данные загружены: ${EURO_IP}:${EURO_PORT}"
fi
fi
if [[ -z "$EURO_IP" ]]; then
echo -e "${BOLD} Введите данные Евро-ноды вручную:${NC}"
read -rp " EURO_IP: " EURO_IP
read -rp " EURO_PORT [443]: " EURO_PORT; EURO_PORT="${EURO_PORT:-443}"
read -rp " EURO_UUID: " EURO_UUID
read -rp " EURO_PUBLIC_KEY: " EURO_PUBLIC_KEY
read -rp " EURO_SHORT_ID: " EURO_SHORT_ID
read -rp " EURO_SNI [www.amd.com]: " EURO_SNI; EURO_SNI="${EURO_SNI:-www.amd.com}"
read -rp " EURO_ENCRYPTION [none]: " EURO_ENCRYPTION; EURO_ENCRYPTION="${EURO_ENCRYPTION:-none}"
fi
echo -n " SNI-домен для РФ-ноды [${RF_SNI}]: "
read -r inp_sni; XRAY_SNI="${inp_sni:-$RF_SNI}"
# Выбор внешнего порта прослушивания из цепочки (443→853→8443).
# Это порт, на котором РФ-нода принимает клиентов — НЕ EURO_PORT (порт евро).
xray_resolve_port
info "Xray РФ будет слушать 0.0.0.0:${XRAY_PORT} напрямую (без nginx)"
pkg_update
pkg_install curl wget unzip uuid-runtime openssl python3
install_xray_bin
# Ключи для клиентского inbound
info "Генерирую ключи Reality для клиентов..."
local keys; keys="$("$XRAY_BIN" x25519)"
local priv_key pub_key
priv_key="$(echo "$keys" | grep -i 'private' | awk '{print $NF}')"
pub_key="$(echo "$keys" | grep -i 'public' | awk '{print $NF}')"
local uuid short_id fp
uuid="$(uuidgen)"
short_id="$(openssl rand -hex 8)"
fp="$(random_fp)"
info "TLS fingerprint: ${fp}"
# Конфиг РФ-ноды через Python (PQ + chain)
export UUID="$uuid" PRIV_KEY="$priv_key" SHORT_ID="$short_id" FP="$fp"
export XRAY_SNI XRAY_PORT
export EURO_IP EURO_PORT EURO_UUID EURO_PUBLIC_KEY EURO_SHORT_ID EURO_SNI EURO_ENCRYPTION
export XRAY_DIR
python3 - << 'PYEOF'
import json, os, subprocess
e = os.environ
# PQ ключи
try:
out = subprocess.check_output(["/usr/local/bin/xray", "vlessenc"]).decode()
dec_lines = [l for l in out.split("\n") if '"decryption"' in l]
enc_lines = [l for l in out.split("\n") if '"encryption"' in l]
pq_dec = dec_lines[1].split('"decryption": "')[1].rstrip('"') if len(dec_lines) > 1 else "none"
pq_enc = enc_lines[1].split('"encryption": "')[1].rstrip('"') if len(enc_lines) > 1 else "none"
except:
pq_dec = pq_enc = "none"
cfg = {
"log": {"loglevel": "warning"},
"dns": {"servers": [
{"address": "223.5.5.5", "domains": ["geosite:category-ru", "geosite:yandex"]},
"1.1.1.1", "8.8.8.8"
]},
"inbounds": [{
"tag": "inbound-clients",
"listen": "0.0.0.0",
"port": int(e["XRAY_PORT"]),
"protocol": "vless",
"settings": {
"clients": [{"id": e["UUID"]}],
"decryption": pq_dec
},
"streamSettings": {
"network": "xhttp",
"security": "reality",
"realitySettings": {
"show": False,
"dest": e["XRAY_SNI"] + ":443",
"xver": 0,
"serverNames": [e["XRAY_SNI"]],
"privateKey": e["PRIV_KEY"],
"shortIds": [e["SHORT_ID"]]
},
"xhttpSettings": {
"path": "/", "mode": "packet-up",
"extra": {"xPaddingBytes": "100-1000"}
}
},
"sniffing": {"enabled": True, "destOverride": ["http","tls","quic","fakedns"]}
}],
"outbounds": [
{
"tag": "chain-to-euro",
"protocol": "vless",
"settings": {"vnext": [{
"address": e["EURO_IP"],
"port": int(e["EURO_PORT"]),
"users": [{"id": e["EURO_UUID"], "encryption": e["EURO_ENCRYPTION"]}]
}]},
"streamSettings": {
"network": "xhttp",
"security": "reality",
"realitySettings": {
"fingerprint": e["FP"],
"serverName": e["EURO_SNI"],
"publicKey": e["EURO_PUBLIC_KEY"],
"shortId": e["EURO_SHORT_ID"]
},
"xhttpSettings": {"path": "/", "mode": "auto"}
},
"mux": {
"enabled": True,
"concurrency": 8,
"xudpConcurrency": 16,
"xudpProxyUDP443": "reject"
}
},
{"protocol": "freedom", "tag": "direct", "settings": {"domainStrategy": "UseIPv4v6"}},
{"protocol": "blackhole", "tag": "block"}
],
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{"type":"field","outboundTag":"block","ip":["geoip:private"],"domain":["geosite:category-ads-all"]},
{"type":"field","outboundTag":"direct","domain":["geosite:category-ru","geosite:yandex","regexp:\\.ru$","full:cp.cloudflare.com"]},
{"type":"field","outboundTag":"direct","ip":["geoip:ru"]},
{"type":"field","inboundTag":["inbound-clients"],"outboundTag":"chain-to-euro"}
]
}
}
with open(e["XRAY_DIR"] + "/config.json", "w") as f:
json.dump(cfg, f, indent=2, ensure_ascii=False)
with open("/tmp/pq_enc_rf.txt", "w") as f:
f.write(pq_enc)
print(f" Config OK | PQ: {pq_dec[:30]}...")
PYEOF
local pq_enc_rf; pq_enc_rf="$(cat /tmp/pq_enc_rf.txt 2>/dev/null || echo none)"
# Файрвол: открываем внешний порт xray
firewall_open "$XRAY_PORT" "xray-direct"
# systemd
systemctl enable xray > /dev/null 2>&1
systemctl restart xray
sleep 2
svc_active xray && ok "Xray запущен" || die "Xray не запустился — journalctl -xe -u xray"
# Итог
local server_ip; server_ip="$(get_public_ip)"
echo ""
echo -e "${BOLD}${GREEN}╔══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${BOLD}${GREEN}║ РФ-НОДА НАСТРОЕНА УСПЕШНО ${NC}"
echo -e "${BOLD}${GREEN}╚══════════════════════════════════════════════════════════════╝${NC}"
echo ""
echo -e "${BOLD}Схема трафика:${NC}"
echo -e " Клиент → xray:${XRAY_PORT} (Reality, SNI: ${XRAY_SNI}) → Евро (${EURO_IP}):${EURO_PORT} → Интернет (без nginx)"
echo ""
echo -e "${BOLD}VLESS URI для клиента (прямое подключение к :${XRAY_PORT}):${NC}"
echo -e "${GREEN}vless://${uuid}@${server_ip}:${XRAY_PORT}?encryption=${pq_enc_rf}&security=reality&sni=${XRAY_SNI}&fp=${fp}&pbk=${pub_key}&sid=${short_id}&type=xhttp&path=%2F&mode=packet-up#RF-Bridge${NC}"
echo ""
echo -e "${BOLD}Проверка:${NC}"
echo -e " curl -v https://${server_ip}:${XRAY_PORT} --resolve ${XRAY_SNI}:${XRAY_PORT}:${server_ip}"
echo -e " journalctl -fu xray"
echo ""
echo -e "${BOLD}Клиентские приложения:${NC}"
echo -e " Android: v2rayNG https://github.com/2dust/v2rayNG"
echo -e " Windows: v2rayN https://github.com/2dust/v2rayN"
echo -e " iOS: Streisand https://apps.apple.com/app/id6450534064"
echo ""
echo -e "${BOLD}Роутинг:${NC}"
echo -e " ✅ .ru, geoip:ru, yandex → DIRECT 🚫 ads → BLOCK 🌍 остальное → Евро"
echo ""
echo -e "${YELLOW} ⚠️ Порт ${XRAY_PORT} должен быть открыт у хостера РФ-ноды.${NC}"
echo ""
}
# ══════════════════════════════════════════════════════════════════════════════
# СТАТУС
# ══════════════════════════════════════════════════════════════════════════════
show_status() {
echo ""
echo -e "${BOLD}${CYAN}━━━ Сервисы ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
# telemt теперь мульти-инстанс (telemt1/2/3) — проверяем каждый отдельно
for svc in nginx xray fail2ban; do
if command -v "$svc" &>/dev/null || systemctl list-units --type=service 2>/dev/null | grep -q "$svc"; then
if svc_active "$svc" 2>/dev/null; then
echo -e " ${GREEN}${svc}: активен${NC}"
else
echo -e " ${RED}${svc}: не запущен${NC}"
fi
fi
done
local tname tentry
for tentry in "${TELEMT_DIRECT_INSTANCES[@]}"; do
IFS=':' read -r tname _ _ _ <<< "$tentry"
if [[ -f "/etc/telemt/${tname}.toml" ]]; then
if telemt_instance_active "$tname" 2>/dev/null; then
echo -e " ${GREEN}${tname}: активен${NC}"
else
echo -e " ${RED}${tname}: не запущен${NC}"
fi
fi
done
echo ""
echo -e "${BOLD}${CYAN}━━━ nginx stream конфиги ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
for f in "$NGINX_STREAM_DIR"/*.conf; do
[[ -f "$f" ]] || continue
echo -e " ${CYAN}$(basename "$f")${NC}"
grep -E 'listen|"[^"]+"|default' "$f" 2>/dev/null | head -8 | sed 's/^/ /'
done
echo ""
local tbackend; tbackend="$(telemt_detect_backend)"
echo -e "${BOLD}${CYAN}━━━ Telemt${tbackend:+ (${tbackend})} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
local found_telemt=0 cfg
for cfg in /etc/telemt/telemt*.toml; do
[[ -f "$cfg" ]] || continue
found_telemt=1
echo -e " ${CYAN}$(basename "$cfg")${NC}"
awk -F'"' '/tls_domain/{print " Домен: "$2} /client_mss/{print " MSS: "$2}' "$cfg" 2>/dev/null
grep -E '^port' "$cfg" 2>/dev/null | sed 's/^/ /'
# Режим выхода: socks5-upstream или direct
if grep -q 'type = "socks5"' "$cfg" 2>/dev/null; then
local up_addr; up_addr="$(awk -F'"' '/address =/{print $2; exit}' "$cfg" 2>/dev/null)"
echo -e " Выход: ${YELLOW}SOCKS5 ${up_addr}${NC}"
else
echo -e " Выход: direct"
fi
done
[[ "$found_telemt" -eq 0 ]] && echo -e " ${YELLOW}telemt не установлен${NC}"
echo ""
echo -e "${BOLD}${CYAN}━━━ Xray ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
if [[ -f "${XRAY_DIR}/config.json" ]]; then
python3 - << 'PYEOF'
import json
try:
with open("/usr/local/etc/xray/config.json") as f:
c = json.load(f)
ib = c["inbounds"][0]
print(f" Listen: {ib.get('listen','0.0.0.0')}:{ib['port']}")
print(f" Протокол: {ib['protocol']}")
ob_tags = [o["tag"] for o in c.get("outbounds",[])]
print(f" Outbounds: {ob_tags}")
except Exception as e:
print(f" Ошибка чтения конфига: {e}")
PYEOF
else
echo -e " ${YELLOW}xray не установлен${NC}"
fi
echo ""
show_ports
}
# ══════════════════════════════════════════════════════════════════════════════
# УДАЛЕНИЕ
# ══════════════════════════════════════════════════════════════════════════════
do_purge() {
section "Полное удаление"
echo -n " Удалить telemt, xray, nginx stream конфиги и fail2ban? [y/N]: "
read -r ans
[[ "$ans" =~ ^[Yy]$ ]] || { echo "Отменено."; exit 0; }
# ── ШАГ 1. Сначала ОСТАНАВЛИВАЕМ все сервисы ─────────────────────────────
# Это делается ДО удаления любых файлов: процессы не должны держать порты,
# сокеты и конфиги, которые мы собираемся стереть. nginx тоже глушим —
# иначе он продолжит слушать :443 с уже удалёнными stream-конфигами.
section "Остановка сервисов"
for svc in fail2ban xray nginx; do
if svc_active "$svc" 2>/dev/null; then
info "Останавливаю ${svc}..."
svc_stop "$svc" 2>/dev/null || true
fi
done
# telemt — мульти-инстанс (systemd или docker)
local tname tentry
if [[ -f "$TELEMT_COMPOSE" ]] && command -v docker &>/dev/null; then
info "Останавливаю telemt-контейнеры..."
(cd /etc/telemt && docker compose down 2>/dev/null) || true
for tentry in "${TELEMT_DIRECT_INSTANCES[@]}"; do
IFS=':' read -r tname _ _ _ <<< "$tentry"
docker rm -f "$tname" 2>/dev/null || true
done
fi
for tentry in "${TELEMT_DIRECT_INSTANCES[@]}"; do
IFS=':' read -r tname _ _ _ <<< "$tentry"
svc_active "$tname" 2>/dev/null && { info "Останавливаю ${tname}..."; svc_stop "$tname" 2>/dev/null || true; }
done
# Легаси: одиночный telemt.service из ранних версий скрипта (имя без цифры).
# Его нет в TELEMT_DIRECT_INSTANCES, поэтому гасим отдельно — иначе он
# продолжит держать 443 после удаления файла юнита.
if svc_active telemt 2>/dev/null; then
info "Останавливаю легаси telemt.service..."
svc_stop telemt 2>/dev/null || true
fi
# Контрольно добиваем процессы telemt/xray, если systemd-юнит уже снят
pkill -x telemt 2>/dev/null || true
pkill -x xray 2>/dev/null || true
sleep 1
ok "Сервисы остановлены"
# ── ШАГ 2. Отключаем автозапуск ──────────────────────────────────────────
for svc in xray fail2ban telemt; do
svc_disable "$svc" 2>/dev/null || true
done
for tentry in "${TELEMT_DIRECT_INSTANCES[@]}"; do
IFS=':' read -r tname _ _ _ <<< "$tentry"
svc_disable "$tname" 2>/dev/null || true
done
# ── ШАГ 3. Удаляем файлы ─────────────────────────────────────────────────
# nginx stream конфиги (сам nginx оставляем — он мог быть до нас)
rm -f "$TELEMT_STREAM_CONF" "$XRAY_STREAM_CONF"
sed -i '/# proxy-stream-block/,/# \/proxy-stream-block/d' "$NGINX_CONF_MAIN" 2>/dev/null || true
sed -i '/# telemt-stream-block/,/# \/telemt-stream-block/d' "$NGINX_CONF_MAIN" 2>/dev/null || true
sed -i '/# xray-stream-block/,/# \/xray-stream-block/d' "$NGINX_CONF_MAIN" 2>/dev/null || true
# nginx запускаем обратно только если его конфиг валиден и сам бинарь остаётся
if command -v nginx &>/dev/null && nginx -t &>/dev/null; then
svc_start nginx || true
fi
# fail2ban
rm -f "$F2B_FILTER" "$F2B_JAIL"
# telemt — docker: удаляем образ (compose-файл уйдёт с /etc/telemt ниже)
if command -v docker &>/dev/null; then
docker rmi "$TELEMT_IMAGE" 2>/dev/null || true
fi
# telemt — мульти-инстанс: юниты, конфиги, бинарь, юзер
for tentry in "${TELEMT_DIRECT_INSTANCES[@]}"; do
IFS=':' read -r tname _ _ _ <<< "$tentry"
rm -f "/etc/systemd/system/${tname}.service" "/etc/init.d/${tname}"
done
# на всякий случай — старый одиночный юнит из прежней версии скрипта
rm -f /etc/systemd/system/telemt.service /etc/init.d/telemt
rm -f /bin/telemt /usr/bin/telemt
rm -rf /etc/telemt /opt/telemt
pkill -u telemt 2>/dev/null || true
sleep 1; pkill -9 -u telemt 2>/dev/null || true
userdel telemt 2>/dev/null || deluser telemt 2>/dev/null || true
groupdel telemt 2>/dev/null || delgroup telemt 2>/dev/null || true
# UFW rate-limit правила (вставленные telemt_ufw_ratelimit)
if [[ -f /etc/ufw/before.rules ]] && grep -q "MTProto rate-limit" /etc/ufw/before.rules 2>/dev/null; then
cp /etc/ufw/before.rules "/etc/ufw/before.rules.bak.$(date +%s)" 2>/dev/null || true
sed -i '/# === MTProto rate-limit/,/# === конец MTProto rate-limit ===/d' /etc/ufw/before.rules 2>/dev/null || true
command -v ufw &>/dev/null && ufw reload >/dev/null 2>&1 || true
info "UFW rate-limit правила удалены"
fi
# xray
rm -f /etc/systemd/system/xray.service "$XRAY_BIN"
rm -rf "$XRAY_DIR" /var/log/xray
systemctl daemon-reload 2>/dev/null || true
echo -e "\n${GREEN}${BOLD}Удаление завершено.${NC}\n"
}
# ══════════════════════════════════════════════════════════════════════════════
# МЕНЮ
# ══════════════════════════════════════════════════════════════════════════════
show_menu() {
banner
echo -e "${BOLD} Выберите действие:${NC}\n"
echo -e " ${GREEN}1)${NC} Установить ${BOLD}Telegram прокси (direct)${NC} (telemt: 3 инстанса + UFW rate-limit, выход прямой)"
echo -e " ${GREEN}2)${NC} Установить ${BOLD}Xray Евро-ноду${NC} (VLESS+Reality direct, порт 443→853→8443, выходная)"
echo -e " ${GREEN}3)${NC} Установить ${BOLD}Xray РФ-ноду${NC} (VLESS+Reality direct, порт 443→853→8443, мост → евро)"
echo -e " ${GREEN}4)${NC} Статус всех сервисов"
echo -e " ${GREEN}5)${NC} Удалить всё"
echo -e " ${GREEN}6)${NC} Установить ${BOLD}Telegram прокси РФ (через SOCKS5)${NC} (telemt → туннель на localhost)"
echo -e " ${GREEN}0)${NC} Выход\n"
echo -n " Ваш выбор [0-6]: "
read -r choice
case "$choice" in
1) setup_telemt ;;
2) setup_xray_euro ;;
3) setup_xray_rf ;;
4) show_status ;;
5) do_purge ;;
6) setup_telemt_rf ;;
0) exit 0 ;;
*) echo -e "${RED}Неверный выбор${NC}"; show_menu ;;
esac
}
# ══════════════════════════════════════════════════════════════════════════════
# ТОЧКА ВХОДА
# ══════════════════════════════════════════════════════════════════════════════
case "${1:-}" in
telemt) setup_telemt ;;
telemt-rf) setup_telemt_rf ;;
update) shift; update_telemt "${1:-}" ;;
xray-euro) setup_xray_euro ;;
xray-rf) setup_xray_rf ;;
status) show_status ;;
purge) do_purge ;;
*) show_menu ;;
esac