1909 lines
89 KiB
Bash
1909 lines
89 KiB
Bash
#!/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
|