v2.4.8: preflight port-conflict check

Добавлена предустановочная проверка свободы портов перед установкой
telemt. На вход приходит режим (lite/pro) и выбранный порт, проверяются
443 (lite: выбранный), 80 и 8443 (pro). Известный proxy/VPN/веб-софт
(xray, sing-box, v2ray, trojan, hysteria, mtg, shadowsocks, x-ui,
marzban, amneziawg, caddy, apache, haproxy, wireguard, openvpn)
распознаётся по имени процесса и показывается отдельным блоком.

При конфликте пользователь видит список занятых портов, получает
рекомендацию удалить конфликтующий софт или взять чистый VPS, и
может либо форсировать установку, либо отказаться — тогда показывается
15-секундный промо-блок с QR для донатов и возврат в меню.

Override для автоматизированных сценариев: GOTELEGRAM_SKIP_PREFLIGHT=1.

Files:
  lib/common.sh      — get_port_process, match_known_conflict, preflight_check
  lib/lang/ru.sh     — preflight_* i18n keys (ru)
  lib/lang/en.sh     — preflight_* i18n keys (en)
  install.sh         — preflight_check вызов в install_lite_mode / install_pro_mode;
                        show_promo_with_qr теперь принимает countdown arg
  gotelegram-bot/bot.py — version bump 2.4.7 → 2.4.8
This commit is contained in:
anten-ka
2026-04-11 23:48:53 +03:00
parent 8f249c35e5
commit 9d9d12e150
5 changed files with 207 additions and 4 deletions

View File

@@ -100,7 +100,7 @@ logger = logging.getLogger(__name__)
# CONFIGURATION # CONFIGURATION
# ============================================================================ # ============================================================================
GOTELEGRAM_VERSION = "2.4.7" GOTELEGRAM_VERSION = "2.4.8"
GOTELEGRAM_CONFIG = "/opt/gotelegram/config.json" GOTELEGRAM_CONFIG = "/opt/gotelegram/config.json"
TELEMT_CONFIG = "/etc/telemt/config.toml" TELEMT_CONFIG = "/etc/telemt/config.toml"
TELEMT_SERVICE = "telemt" TELEMT_SERVICE = "telemt"

18
install.sh Executable file → Normal file
View File

@@ -295,6 +295,12 @@ install_lite_mode() {
port=$(select_port) port=$(select_port)
[ $? -ne 0 ] && return [ $? -ne 0 ] && return
# Preflight: port conflict check (checks the external port only for lite)
if ! preflight_check "lite" "$port"; then
show_promo_with_qr 15
return
fi
# Generate secret # Generate secret
local secret local secret
secret=$(generate_hex 32) secret=$(generate_hex 32)
@@ -342,6 +348,12 @@ install_lite_mode() {
install_pro_mode() { install_pro_mode() {
log_step "$(t install_pro_step)" log_step "$(t install_pro_step)"
# Preflight: pro mode needs 443, 80 and 8443 (internal nginx mask)
if ! preflight_check "pro"; then
show_promo_with_qr 15
return
fi
# Enter domain # Enter domain
echo "" echo ""
echo -ne " ${WHITE}$(t install_enter_domain)${NC} " echo -ne " ${WHITE}$(t install_enter_domain)${NC} "
@@ -1142,6 +1154,7 @@ mark_promo_shown() {
# текстовые ссылки и промокоды (см. _promo_block) — QR-коды хостеров # текстовые ссылки и промокоды (см. _promo_block) — QR-коды хостеров
# визуально конкурировали с чаевыми и перегружали экран. # визуально конкурировали с чаевыми и перегружали экран.
show_promo_with_qr() { show_promo_with_qr() {
local countdown="${1:-5}"
_promo_block _promo_block
# QR только для чаевых # QR только для чаевых
@@ -1154,8 +1167,9 @@ show_promo_with_qr() {
mark_promo_shown mark_promo_shown
# 5-second countdown # Countdown (default 5s, caller may pass longer for preflight abort)
for i in 5 4 3 2 1; do local i
for ((i=countdown; i>0; i--)); do
echo -ne "\r ${DIM}$(tf promo_menu_in "$i")${NC} " echo -ne "\r ${DIM}$(tf promo_menu_in "$i")${NC} "
sleep 1 sleep 1
done done

View File

@@ -3,7 +3,7 @@
# Colors, logging, spinner, system helpers, v1 compat, i18n-aware # Colors, logging, spinner, system helpers, v1 compat, i18n-aware
# ── Version ─────────────────────────────────────────────────────────────────── # ── Version ───────────────────────────────────────────────────────────────────
GOTELEGRAM_VERSION="2.4.7" GOTELEGRAM_VERSION="2.4.8"
GOTELEGRAM_NAME="GoTelegram" GOTELEGRAM_NAME="GoTelegram"
# ── Пути ────────────────────────────────────────────────────────────────────── # ── Пути ──────────────────────────────────────────────────────────────────────
@@ -463,6 +463,167 @@ check_port() {
return 1 # свободен return 1 # свободен
} }
# ── Preflight: port conflict detection ───────────────────────────────────────
# Проверяет, что нужные для установки порты свободны. Если порт занят —
# определяет процесс и сопоставляет с известным списком proxy/VPN софта
# (xray, sing-box, v2ray, trojan, hysteria, mtg, shadowsocks, x-ui/3x-ui,
# marzban, amneziawg, caddy, apache, haproxy). Пользователь видит явное
# предупреждение и может либо прервать установку, либо продолжить на свой
# страх и риск (GOTELEGRAM_SKIP_PREFLIGHT=1 — полностью отключить проверку).
#
# Используемые порты GoTelegram:
# 443 — telemt (внешний, MTProxy + fake-TLS) — lite и pro
# 80 — nginx redirect + certbot ACME HTTP-01 — только pro
# 8443 — nginx internal mask (127.0.0.1:8443) — только pro
# get_port_process <port> → "<pid>|<comm>" если занят, иначе пусто
get_port_process() {
local port="$1"
local line="" pid="" proc=""
line=$(ss -tlnp 2>/dev/null | grep -E ":${port}[[:space:]]" | head -1)
if [ -z "$line" ]; then
line=$(netstat -tlnp 2>/dev/null | grep -E ":${port}[[:space:]]" | head -1)
fi
if [ -n "$line" ]; then
pid=$(echo "$line" | grep -oE 'pid=[0-9]+' | head -1 | cut -d= -f2)
if [ -z "$pid" ]; then
# netstat format: "12345/procname"
pid=$(echo "$line" | grep -oE '[0-9]+/[^ ]+' | head -1 | cut -d/ -f1)
fi
fi
if [ -z "$pid" ]; then
pid=$(fuser -n tcp "$port" 2>/dev/null | tr -s ' ' | awk '{print $1}' | head -1)
pid="${pid:-}"
fi
if [ -n "$pid" ] && [ "$pid" -gt 0 ] 2>/dev/null; then
proc=$(ps -p "$pid" -o comm= 2>/dev/null | tr -d ' \n')
[ -z "$proc" ] && proc="unknown"
echo "${pid}|${proc}"
return 0
fi
if [ -n "$line" ]; then
# Port is occupied but process cannot be identified (kernel socket / no root)
echo "0|unknown"
return 0
fi
return 1
}
# match_known_conflict <comm> → печатает человекочитаемое имя если это
# известный proxy/VPN/web софт. Возвращает 0 если нашли, 1 иначе.
match_known_conflict() {
local proc="$1"
case "$proc" in
*xray*|*Xray*) echo "Xray"; return 0 ;;
*sing-box*|*sing_box*|*singbox*) echo "sing-box"; return 0 ;;
*v2ray*|*V2Ray*) echo "V2Ray"; return 0 ;;
*trojan*) echo "Trojan"; return 0 ;;
*hysteria*) echo "Hysteria"; return 0 ;;
*mtg*) echo "mtg (old MTProxy)"; return 0 ;;
*ss-server*|*ss-local*|*shadowsocks*|*ssserver*) echo "Shadowsocks"; return 0 ;;
*x-ui*|*3x-ui*|*xui*) echo "x-ui / 3x-ui panel"; return 0 ;;
*marzban*) echo "Marzban panel"; return 0 ;;
*amneziawg*|*awg-go*|*awg*) echo "AmneziaWG"; return 0 ;;
*caddy*) echo "Caddy web server"; return 0 ;;
*apache2*|*httpd*) echo "Apache httpd"; return 0 ;;
*haproxy*) echo "HAProxy"; return 0 ;;
*nginx*) echo "nginx (already running)"; return 0 ;;
*tgproxy*|*mtproxy*|*mtproto*) echo "MTProto Proxy (other impl)"; return 0 ;;
*wireguard*|*wg-quick*) echo "WireGuard"; return 0 ;;
*openvpn*) echo "OpenVPN"; return 0 ;;
esac
return 1
}
# preflight_check <mode> [port]
# mode = "lite" | "pro"
# port = selected port for lite mode (default 443)
# Returns:
# 0 — OK to proceed (no conflicts, or user confirmed to force)
# 1 — user aborted (caller should show promo and return)
preflight_check() {
local mode="${1:-lite}"
local lite_port="${2:-443}"
# Escape hatch
if [ "${GOTELEGRAM_SKIP_PREFLIGHT:-0}" = "1" ]; then
log_dim "preflight: skipped (GOTELEGRAM_SKIP_PREFLIGHT=1)"
return 0
fi
local required_ports=()
if [ "$mode" = "pro" ]; then
required_ports=(443 80 8443)
else
# lite: проверяем только выбранный внешний порт
required_ports=("$lite_port")
fi
local known_conflicts=() unknown_conflicts=() info port pid proc label
for port in "${required_ports[@]}"; do
info=$(get_port_process "$port")
if [ -n "$info" ]; then
pid="${info%%|*}"
proc="${info##*|}"
if label=$(match_known_conflict "$proc"); then
known_conflicts+=("${port}|${label}|${pid}|${proc}")
else
unknown_conflicts+=("${port}|${pid}|${proc}")
fi
fi
done
if [ ${#known_conflicts[@]} -eq 0 ] && [ ${#unknown_conflicts[@]} -eq 0 ]; then
log_dim "preflight: ports ${required_ports[*]} свободны"
return 0
fi
# Показываем баннер конфликта
echo "" >&2
echo -e " ${BOLD}${YELLOW}$(_t_or preflight_title 'Предустановочная проверка: обнаружены конфликты портов')${NC}" >&2
echo -e " ${DIM}$(printf '─%.0s' {1..60})${NC}" >&2
local item p label2 pid2 proc2 rest
if [ ${#known_conflicts[@]} -gt 0 ]; then
echo -e " ${RED}$(_t_or preflight_known 'Известный proxy/VPN/веб-софт занимает нужные порты:')${NC}" >&2
for item in "${known_conflicts[@]}"; do
p="${item%%|*}"
rest="${item#*|}"
label2="${rest%%|*}"
rest="${rest#*|}"
pid2="${rest%%|*}"
proc2="${rest##*|}"
echo -e " ${RED}${NC} ${BOLD}:${p}${NC}${BOLD}${label2}${NC} ${DIM}(pid=${pid2}, cmd=${proc2})${NC}" >&2
done
fi
if [ ${#unknown_conflicts[@]} -gt 0 ]; then
echo -e " ${YELLOW}$(_t_or preflight_unknown 'Порты заняты неизвестными процессами:')${NC}" >&2
for item in "${unknown_conflicts[@]}"; do
p="${item%%|*}"
rest="${item#*|}"
pid2="${rest%%|*}"
proc2="${rest##*|}"
echo -e " ${YELLOW}${NC} ${BOLD}:${p}${NC} ${DIM}(pid=${pid2}, cmd=${proc2})${NC}" >&2
done
fi
echo -e " ${DIM}$(printf '─%.0s' {1..60})${NC}" >&2
echo -e " ${WHITE}$(_t_or preflight_needed 'GoTelegram нужны порты:')${NC} ${CYAN}${required_ports[*]}${NC}" >&2
echo -e " ${WHITE}$(_t_or preflight_hint_header 'Рекомендации:')${NC}" >&2
echo -e " ${DIM}$(_t_or preflight_hint1 'Остановите и удалите конфликтующие сервисы (systemctl stop ...)')${NC}" >&2
echo -e " ${DIM}$(_t_or preflight_hint2 'Либо возьмите чистый VPS без других прокси')${NC}" >&2
echo -e " ${DIM}$(_t_or preflight_hint3 'Установка поверх, скорее всего, завершится некорректно')${NC}" >&2
echo -e " ${DIM}$(_t_or preflight_skip_hint 'Override: GOTELEGRAM_SKIP_PREFLIGHT=1 gotelegram')${NC}" >&2
echo "" >&2
if confirm "$(_t_or preflight_proceed 'Продолжить установку всё равно (скорее всего не заработает)?')"; then
log_warning "$(_t_or preflight_forced 'Установка продолжена вопреки конфликтам — возможны ошибки')"
return 0
fi
log_info "$(_t_or preflight_aborted 'Установка отменена из-за конфликтов портов')"
return 1
}
check_disk_space() { check_disk_space() {
local min_mb="${1:-500}" local min_mb="${1:-500}"
local avail_mb local avail_mb

14
lib/lang/en.sh Executable file → Normal file
View File

@@ -347,6 +347,20 @@ I18N[backup_pass_short]="Password too short (minimum 6 characters)"
I18N[backup_pick_prompt]="Backup number (or path to file)" I18N[backup_pick_prompt]="Backup number (or path to file)"
I18N[backup_not_found]="Backup not found" I18N[backup_not_found]="Backup not found"
# ── Preflight (v2.4.8) ──────────────────────────────────────────────────
I18N[preflight_title]="Preflight: port conflicts detected"
I18N[preflight_known]="Known proxy/VPN/web software is using required ports:"
I18N[preflight_unknown]="Required ports are held by unknown processes:"
I18N[preflight_needed]="GoTelegram requires ports:"
I18N[preflight_hint_header]="Recommended actions:"
I18N[preflight_hint1]="Stop and remove the conflicting services (systemctl stop ...)"
I18N[preflight_hint2]="Or use a clean VPS without other proxies"
I18N[preflight_hint3]="Installing on top will most likely fail"
I18N[preflight_skip_hint]="Bypass: GOTELEGRAM_SKIP_PREFLIGHT=1 gotelegram"
I18N[preflight_proceed]="Continue installation anyway (likely to fail)?"
I18N[preflight_forced]="Installation continued despite conflicts — errors likely"
I18N[preflight_aborted]="Installation aborted due to port conflicts"
# ── Errors / misc ─────────────────────────────────────────────────────── # ── Errors / misc ───────────────────────────────────────────────────────
I18N[err_need_root]="Run the script with sudo / as root" I18N[err_need_root]="Run the script with sudo / as root"
I18N[err_os_unknown]="Failed to detect OS. Linux is required." I18N[err_os_unknown]="Failed to detect OS. Linux is required."

14
lib/lang/ru.sh Executable file → Normal file
View File

@@ -347,6 +347,20 @@ I18N[backup_pass_short]="Пароль слишком короткий (мини
I18N[backup_pick_prompt]="Номер бекапа (или путь к файлу)" I18N[backup_pick_prompt]="Номер бекапа (или путь к файлу)"
I18N[backup_not_found]="Бекап не найден" I18N[backup_not_found]="Бекап не найден"
# ── Preflight (v2.4.8) ──────────────────────────────────────────────────
I18N[preflight_title]="Предустановочная проверка: обнаружены конфликты портов"
I18N[preflight_known]="Известный proxy/VPN/веб-софт занимает нужные порты:"
I18N[preflight_unknown]="Порты заняты неизвестными процессами:"
I18N[preflight_needed]="GoTelegram нужны порты:"
I18N[preflight_hint_header]="Рекомендации:"
I18N[preflight_hint1]="Остановите и удалите конфликтующие сервисы (systemctl stop ...)"
I18N[preflight_hint2]="Либо возьмите чистый VPS без других прокси"
I18N[preflight_hint3]="Установка поверх, скорее всего, завершится некорректно"
I18N[preflight_skip_hint]="Обойти проверку: GOTELEGRAM_SKIP_PREFLIGHT=1 gotelegram"
I18N[preflight_proceed]="Продолжить установку всё равно (скорее всего не заработает)?"
I18N[preflight_forced]="Установка продолжена вопреки конфликтам — возможны ошибки"
I18N[preflight_aborted]="Установка отменена из-за конфликтов портов"
# ── Errors / misc ─────────────────────────────────────────────────────── # ── Errors / misc ───────────────────────────────────────────────────────
I18N[err_need_root]="Запустите скрипт с sudo / от root" I18N[err_need_root]="Запустите скрипт с sudo / от root"
I18N[err_os_unknown]="Не удалось определить ОС. Требуется Linux." I18N[err_os_unknown]="Не удалось определить ОС. Требуется Linux."