z2k/lib/menu.sh
Necronicle 3681570fdb bootstrap: 4-layer GitHub fetch fallback (ISP-block-resistant)
Some Russian ISPs (MGTS, Rostelecom recent) intermittently block
raw.githubusercontent.com via DNS poisoning or SNI filter, making the
first curl in z2k.sh fail and the entire install abort. Borrow z4r's
DNS-override trick, stack three mirrors in front of it.

New z2k_fetch() wrapper, defined inline in z2k.sh (bootstrap-safe) and
duplicated in lib/utils.sh (for modules). Tries in order:

  1. raw.githubusercontent.com                         — primary
  2. cdn.jsdelivr.net/gh/<owner>/<repo>@<branch>/<p>   — CDN, 12h edge
     cache (purgeable via purge.jsdelivr.net/gh/...)
  3. gh-proxy.com/<raw-url>                           — reverse-proxy,
     no cache, works from RU
  4. Keenetic-only: nslookup <host> 8.8.8.8 + ndmc "ip host <host> <ip>"
     then retry layers 1+2 — mirrors zapret4rocket z4r.sh:1075-1107.

Replaces every direct curl to raw.github or ${GITHUB_RAW}/... across
z2k.sh (14 sites), lib/install.sh (4 sites: two AloofLibra/zapret4rocket
lua/orchestrator fetches + two bol-van/zapret2 custom.d examples),
lib/menu.sh (tg-mtproxy binary download), and the standalone cron
files/z2k-update-lists.sh (which gets its own inline copy of the
function — it is not source'd via utils.sh).

Smoke-tested: all three delivering layers fetch the same 29272-byte
z2k.sh, and a bogus path correctly falls through all layers and
returns 1. Layer 4 (ndmc) not tested on the Mac — semantics copied
verbatim from z4r, which proves it on Keenetic in production.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 23:26:58 +03:00

1774 lines
57 KiB
Bash
Raw 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/sh
# lib/menu.sh - Интерактивное меню управления z2k
# 9 опций для полного управления zapret2
# shellcheck disable=SC2154 # Variables assigned via read_input function
# ==============================================================================
# ВСПОМОГАТЕЛЬНАЯ ФУНКЦИЯ ДЛЯ ЧТЕНИЯ ВВОДА
# ==============================================================================
# Читать ввод пользователя (работает даже когда stdin перенаправлен через pipe)
# Очищает мусор от backspace/смены раскладки — оставляет последний введённый символ
read_input() {
local _z2k_var="$1"
local _z2k_raw=""
read -r _z2k_raw </dev/tty
# Удалить \r, backspace (\b), DEL (\177), а также любые не-ASCII байты (мусор от раскладки)
_z2k_raw=$(printf '%s' "$_z2k_raw" | tr -d "$(printf '\r\b\177')" | LC_ALL=C sed 's/[^[:print:]]//g; s/^[[:space:]]*//; s/[[:space:]]*$//')
eval "${_z2k_var}=\${_z2k_raw}"
}
# ==============================================================================
# ГЛАВНОЕ МЕНЮ
# ==============================================================================
show_main_menu() {
while true; do
clear_screen
cat <<'MENU'
+===================================================+
| z2k - Zapret2 для Keenetic |
+---------------------------------------------------+
| Огромная благодарность спонсорам проекта: |
| - SupWgeneral |
+===================================================+
MENU
# Показать текущий статус
printf "\n"
printf " Состояние: %s\n" "$(is_zapret2_installed && echo 'Установлен' || echo 'Не установлен')"
if is_zapret2_installed; then
printf " Сервис: %s\n" "$(get_service_status)"
# Проверить режим стратегий
if [ -f "$CATEGORY_STRATEGIES_CONF" ]; then
local count
count=$(grep -c ":" "$CATEGORY_STRATEGIES_CONF" 2>/dev/null || echo 0)
printf " Стратегии: %s категорий\n" "$count"
else
printf " Текущая стратегия: #%s\n" "$(get_current_strategy)"
fi
# Проверить режим ALL TCP-443
local all_tcp443_conf="${CONFIG_DIR}/all_tcp443.conf"
if [ -f "$all_tcp443_conf" ]; then
local ENABLED
ENABLED=$(safe_config_read "ENABLED" "$all_tcp443_conf" "0")
if [ "$ENABLED" = "1" ]; then
printf " Режим Austerusj: Включен (без хостлистов)\n"
fi
fi
# Показать статус RST-фильтра и silent fallback
local rst_config_file="${ZAPRET2_DIR}/config"
if [ -f "$rst_config_file" ]; then
local DROP_DPI_RST
DROP_DPI_RST=$(safe_config_read "DROP_DPI_RST" "$rst_config_file" "0")
if [ "$DROP_DPI_RST" = "1" ]; then
printf " RST-фильтр: Включен (пассивный DPI)\n"
fi
local RKN_SILENT_FALLBACK
RKN_SILENT_FALLBACK=$(safe_config_read "RKN_SILENT_FALLBACK" "$rst_config_file" "0")
if [ "$RKN_SILENT_FALLBACK" = "1" ]; then
printf " Silent fallback РКН: Включен\n"
fi
fi
# Показать статус веб-панели (если функция загружена)
if command -v webpanel_is_installed >/dev/null 2>&1 && webpanel_is_installed; then
if webpanel_is_running; then
printf " Веб-панель: работает (%s)\n" "$(webpanel_url)"
else
printf " Веб-панель: установлена, остановлена\n"
fi
fi
fi
cat <<'MENU'
[1] Установить/Переустановить zapret2
[2] Управление сервисом
[3] Обновить списки доменов
[4] Резервная копия/Восстановление
[5] Удалить zapret2
[A] Режим без хостлистов (для Austerusj)
[W] Whitelist (исключения)
[R] RST-фильтр (пассивный DPI)
[F] Silent fallback для РКН (осторожно, возможны поломки)
[G] Игровой режим (Roblox и др.)
[T] Telegram прокси (тестовая функция)
[S] Скрипты custom.d
[P] Веб-панель (дубль меню в браузере)
[B] Rollback (откат конфигурации)
[H] Health check (проверка работоспособности)
[V] Валидация конфигурации
[0] Выход
MENU
printf "Выберите опцию [0-5,A,R,F,G,T,W,S,P,B,H,V]: "
read_input choice
case "$choice" in
1)
menu_install
;;
2)
menu_service_control
;;
3)
menu_update_lists
;;
4)
menu_backup_restore
;;
5)
menu_uninstall
;;
a|A)
menu_all_tcp443
;;
r|R)
menu_rst_filter
;;
f|F)
menu_rkn_silent_fallback
;;
g|G)
menu_roblox_bypass
;;
t|T)
menu_telegram_mtproxy
;;
w|W)
menu_whitelist
;;
s|S)
menu_custom_scripts
;;
p|P)
menu_webpanel
;;
b|B)
menu_rollback
;;
h|H)
menu_healthcheck
;;
v|V)
menu_validate_config
;;
0)
print_info "Выход из меню"
return 0
;;
*)
print_error "Неверный выбор: $choice"
pause
;;
esac
done
}
# ==============================================================================
# ПОДМЕНЮ: ROLLBACK
# ==============================================================================
menu_rollback() {
clear_screen
print_header "[B] Rollback конфигурации"
if ! is_zapret2_installed; then
print_error "zapret2 не установлен"
pause
return
fi
cat <<'SUBMENU'
Rollback позволяет откатить конфигурацию к предыдущему
сохранённому состоянию.
[1] Создать snapshot текущей конфигурации
[2] Восстановить из snapshot (rollback)
[3] Подтвердить текущую конфигурацию (отменить авто-rollback)
[0] Назад
SUBMENU
printf "Выбор: "
read_input choice
case "$choice" in
1)
create_rollback_snapshot "manual"
;;
2)
rollback_to_snapshot
;;
3)
confirm_config
;;
0)
return
;;
*)
print_error "Неверный выбор"
;;
esac
pause
}
# ==============================================================================
# ПОДМЕНЮ: HEALTH CHECK
# ==============================================================================
menu_healthcheck() {
clear_screen
print_header "[H] Health Check"
if ! is_zapret2_installed; then
print_error "zapret2 не установлен"
pause
return
fi
local hc_script="${ZAPRET2_DIR}/z2k-healthcheck.sh"
if [ ! -f "$hc_script" ]; then
print_error "Скрипт healthcheck не найден: $hc_script"
print_info "Переустановите z2k для получения скрипта"
pause
return
fi
cat <<'SUBMENU'
Health Check проверяет доступность ключевых сервисов
и работоспособность DPI bypass.
[1] Запустить проверку сейчас
[2] Показать статус
[3] Показать лог
[0] Назад
SUBMENU
printf "Выбор: "
read_input choice
case "$choice" in
1)
sh "$hc_script" || true
;;
2)
sh "$hc_script" --status || true
;;
3)
if [ -f "${ZAPRET2_DIR}/healthcheck.log" ]; then
tail -30 "${ZAPRET2_DIR}/healthcheck.log"
else
print_info "Лог пуст"
fi
;;
0)
return
;;
esac
pause
}
# ==============================================================================
# ПОДМЕНЮ: ВАЛИДАЦИЯ КОНФИГУРАЦИИ
# ==============================================================================
menu_validate_config() {
clear_screen
print_header "[V] Валидация конфигурации"
if ! is_zapret2_installed; then
print_error "zapret2 не установлен"
pause
return
fi
local validator="${ZAPRET2_DIR}/z2k-config-validator.sh"
if [ ! -f "$validator" ]; then
print_error "Скрипт валидатора не найден: $validator"
print_info "Переустановите z2k для получения скрипта"
pause
return
fi
sh "$validator" "${ZAPRET2_DIR}/config"
pause
}
# ==============================================================================
# ПОДМЕНЮ: УСТАНОВКА
# ==============================================================================
menu_install() {
clear_screen
print_header "[1] Установка/Переустановка zapret2"
if is_zapret2_installed; then
print_warning "zapret2 уже установлен"
printf "\nПереустановить? [y/N]: "
read_input answer
case "$answer" in
[Yy]|[Yy][Ee][Ss])
run_full_install
;;
*)
print_info "Установка отменена"
;;
esac
else
run_full_install
fi
pause
}
# ==============================================================================
# ПОДМЕНЮ: ВЫБОР СТРАТЕГИИ
# ==============================================================================
menu_select_strategy() {
clear_screen
print_header "[2] Выбор стратегии по категориям"
if ! is_zapret2_installed; then
print_error "zapret2 не установлен"
print_info "Сначала выполните установку (опция 1)"
pause
return
fi
local total_count
total_count=$(get_strategies_count)
# Прочитать текущие стратегии
local config_file="${CONFIG_DIR}/category_strategies.conf"
local current_yt_tcp="1"
local current_yt_gv="1"
local current_rkn="1"
if [ -f "$config_file" ]; then
current_yt_tcp=$(grep "^youtube_tcp:" "$config_file" 2>/dev/null | cut -d':' -f2)
current_yt_gv=$(grep "^youtube_gv:" "$config_file" 2>/dev/null | cut -d':' -f2)
current_rkn=$(grep "^rkn:" "$config_file" 2>/dev/null | cut -d':' -f2)
[ -z "$current_yt_tcp" ] && current_yt_tcp="1"
[ -z "$current_yt_gv" ] && current_yt_gv="1"
[ -z "$current_rkn" ] && current_rkn="1"
fi
print_separator
print_info "Текущие стратегии (autocircular):"
printf " YouTube TCP: #%s\n" "$current_yt_tcp"
printf " YouTube GV: #%s\n" "$current_yt_gv"
printf " RKN: #%s\n" "$current_rkn"
printf " QUIC YouTube: #%s\n" "$(get_current_quic_strategy)"
print_separator
# Подменю выбора категории
cat <<'SUBMENU'
Выберите категорию для применения стратегии:
[1] YouTube TCP (youtube.com) -> стратегия #2
[2] YouTube GV (googlevideo CDN) -> стратегия #3
[3] RKN (заблокированные сайты) -> стратегия #1
[4] QUIC (UDP 443)
[B] Назад
SUBMENU
printf "Ваш выбор: "
read_input category_choice
case "$category_choice" in
1)
# YouTube TCP — фиксированная стратегия #2
local new_strategy=2
print_separator
print_info "Применяю autocircular стратегию #$new_strategy для YouTube TCP..."
apply_category_strategies_v2 "$new_strategy" "$current_yt_gv" "$current_rkn"
print_separator
test_category_availability "YouTube TCP" "youtube.com"
print_separator
printf "Сохранить? [Y/n]: "
read_input apply_confirm
case "$apply_confirm" in
[Nn]|[Nn][Oo])
print_info "Откатываю..."
apply_category_strategies_v2 "$current_yt_tcp" "$current_yt_gv" "$current_rkn"
print_success "Откат выполнен"
;;
*)
save_category_strategies "$new_strategy" "$current_yt_gv" "$current_rkn"
print_success "Стратегия YouTube TCP сохранена!"
;;
esac
return
;;
2)
# YouTube GV — фиксированная стратегия #3
local new_strategy=3
print_separator
print_info "Применяю autocircular стратегию #$new_strategy для YouTube GV..."
apply_category_strategies_v2 "$current_yt_tcp" "$new_strategy" "$current_rkn"
print_separator
local gv_domain
gv_domain=$(generate_gv_domain)
test_category_availability "YouTube GV" "$gv_domain"
print_separator
printf "Сохранить? [Y/n]: "
read_input apply_confirm
case "$apply_confirm" in
[Nn]|[Nn][Oo])
print_info "Откатываю..."
apply_category_strategies_v2 "$current_yt_tcp" "$current_yt_gv" "$current_rkn"
print_success "Откат выполнен"
;;
*)
save_category_strategies "$current_yt_tcp" "$new_strategy" "$current_rkn"
print_success "Стратегия YouTube GV сохранена!"
;;
esac
return
;;
3)
# RKN — фиксированная стратегия #1
local new_strategy=1
print_separator
print_info "Применяю autocircular стратегию #$new_strategy для RKN..."
apply_category_strategies_v2 "$current_yt_tcp" "$current_yt_gv" "$new_strategy"
print_separator
test_category_availability_rkn
print_separator
printf "Сохранить? [Y/n]: "
read_input apply_confirm
case "$apply_confirm" in
[Nn]|[Nn][Oo])
print_info "Откатываю..."
apply_category_strategies_v2 "$current_yt_tcp" "$current_yt_gv" "$current_rkn"
print_success "Откат выполнен"
;;
*)
save_category_strategies "$current_yt_tcp" "$current_yt_gv" "$new_strategy"
print_success "Стратегия RKN сохранена!"
;;
esac
return
;;
4)
# QUIC (UDP 443)
menu_quic_settings
return
;;
[Bb])
return
;;
*)
print_error "Неверный выбор"
pause
return
;;
esac
}
# Вспомогательная функция: проверка доступности категории
test_category_availability() {
local category_name=$1
local test_domain=$2
print_info "Проверка доступности: $category_name ($test_domain)..."
# Подождать 2 секунды для применения правил
sleep 2
# Запустить тест
if test_strategy_tls "$test_domain" 5; then
print_success "[OK] $category_name доступен! Стратегия работает."
else
print_error "[FAIL] $category_name недоступен. Попробуйте другую стратегию."
print_info "Стратегии переключаются автоматически через autocircular."
fi
}
# Вспомогательная функция: проверка доступности RKN (3 домена)
test_category_availability_rkn() {
local test_domains="meduza.io facebook.com rutracker.org"
local success_count=0
print_info "Проверка доступности: RKN (meduza.io, facebook.com, rutracker.org)..."
sleep 2
for domain in $test_domains; do
if test_strategy_tls "$domain" 5; then
success_count=$((success_count + 1))
fi
done
if [ "$success_count" -ge 2 ]; then
print_success "[OK] RKN доступен! Стратегия работает. (${success_count}/3)"
else
print_error "[FAIL] RKN недоступен. Попробуйте другую стратегию. (${success_count}/3)"
print_info "Стратегии переключаются автоматически через autocircular."
fi
}
# ==============================================================================
# ПОДМЕНЮ: АВТОТЕСТ
# ==============================================================================
menu_rutracker_blockcheck() {
clear_screen
print_header "[3] HTTP blockcheck (fast-torrent.ru)"
if ! is_zapret2_installed; then
print_error "zapret2 не установлен"
pause
return
fi
print_info "Запуск blockcheck для fast-torrent.ru (HTTP, порт 80)"
print_info "Поиск рабочей стратегии для HTTP DPI redirect"
if confirm "Продолжить?" "Y"; then
run_blockcheck_http "fast-torrent.ru"
fi
pause
}
# ==============================================================================
# ПОДМЕНЮ: УПРАВЛЕНИЕ СЕРВИСОМ
# ==============================================================================
menu_service_control() {
clear_screen
print_header "[4] Управление сервисом"
if ! is_zapret2_installed; then
print_error "zapret2 не установлен"
pause
return
fi
cat <<'SUBMENU'
[1] Запустить сервис
[2] Остановить сервис
[3] Перезапустить сервис
[4] Статус сервиса
[B] Назад
SUBMENU
printf "Выберите действие: "
read_input action
case "$action" in
1)
print_info "Запуск сервиса..."
"$INIT_SCRIPT" start
;;
2)
print_info "Остановка сервиса..."
"$INIT_SCRIPT" stop
;;
3)
print_info "Перезапуск сервиса..."
"$INIT_SCRIPT" restart
;;
4)
"$INIT_SCRIPT" status
;;
[Bb])
return
;;
*)
print_error "Неверный выбор"
;;
esac
pause
}
# ==============================================================================
# ПОДМЕНЮ: ОБНОВЛЕНИЕ СПИСКОВ
# ==============================================================================
menu_update_lists() {
clear_screen
print_header "[6] Обновление списков доменов"
if ! is_zapret2_installed; then
print_error "zapret2 не установлен"
pause
return
fi
# Показать текущие списки
show_domain_lists_stats
printf "\nОбновить списки доменов? [Y/n]: "
read_input answer
case "$answer" in
[Nn]|[Nn][Oo])
print_info "Отменено"
;;
*)
update_domain_lists
;;
esac
pause
}
# ==============================================================================
# ПОДМЕНЮ: BACKUP/RESTORE
# ==============================================================================
menu_backup_restore() {
while true; do
clear_screen
print_header "[4] Резервная копия/Восстановление"
if ! is_zapret2_installed; then
print_error "zapret2 не установлен"
pause
return
fi
cat <<'SUBMENU'
[1] Создать резервную копию
[2] Восстановить из резервной копии
[3] Сбросить конфигурацию
[B] Назад
SUBMENU
printf "Выберите действие: "
read_input action
case "$action" in
1)
backup_config
;;
2)
restore_config
;;
3)
print_warning "Это сбросит всю конфигурацию к значениям по умолчанию!"
confirm "Вы уверены?" "N" || { pause; continue; }
reset_config
;;
[Bb])
return
;;
*)
print_error "Неверный выбор"
;;
esac
pause
done
}
# ==============================================================================
# ПОДМЕНЮ: УДАЛЕНИЕ
# ==============================================================================
menu_uninstall() {
clear_screen
print_header "[9] Удаление zapret2"
if ! is_zapret2_installed; then
print_info "zapret2 не установлен"
pause
return
fi
uninstall_zapret2
pause
}
# ==============================================================================
# ПОДМЕНЮ: РЕЖИМ БЕЗ ХОСТЛИСТОВ (ДЛЯ AUSTERUSJ)
# ==============================================================================
menu_all_tcp443() {
clear_screen
print_header "Режим без хостлистов (для Austerusj)"
local conf_file="${CONFIG_DIR}/all_tcp443.conf"
if [ ! -f "$conf_file" ]; then
print_error "Файл конфигурации не найден: $conf_file"
print_info "Запустите установку сначала"
pause
return 1
fi
local current_enabled
current_enabled=$(safe_config_read "ENABLED" "$conf_file" "0")
print_separator
print_info "Статус: $([ "$current_enabled" = "1" ] && echo 'Включен' || echo 'Выключен')"
print_separator
cat <<'SUBMENU'
Простые стратегии из Zapret1, без хостлистов и автоциркуляра.
Применяются ко ВСЕМУ трафику на портах 80/443 (TCP+UDP).
Стратегии:
HTTP (TCP 80): fake(zero_256, ttl=0, badsum+badseq) + multisplit
TLS (TCP 443): 2x fake(zero_256+google_hello, ttl=0, badsum+badseq) + multidisorder
QUIC (UDP 443): fake(zero_256, ttl=0)
При включении заменяет ВСЕ профили z2k (YT/RKN/Discord/HTTP).
При выключении возвращаются стандартные автоциркуляры z2k.
[1] Включить
[2] Выключить
[B] Назад
SUBMENU
printf "Выберите опцию [1-2,B]: "
read_input sub_choice
case "$sub_choice" in
1)
sed -i "s/^ENABLED=.*/ENABLED=1/" "$conf_file"
print_success "Режим Austerusj включен"
print_info "Пересоздание конфига..."
create_official_config "/opt/zapret2/config"
if is_zapret2_running; then
print_info "Перезапуск сервиса..."
"$INIT_SCRIPT" restart
print_success "Сервис перезапущен"
else
print_warning "Сервис не запущен. Запустите через [4] Управление сервисом"
fi
pause
;;
2)
if [ "$current_enabled" != "1" ]; then
print_info "Режим уже выключен"
pause
return 0
fi
sed -i "s/^ENABLED=.*/ENABLED=0/" "$conf_file"
print_success "Режим Austerusj выключен, возврат к автоциркулярам z2k"
print_info "Пересоздание конфига..."
create_official_config "/opt/zapret2/config"
if is_zapret2_running; then
print_info "Перезапуск сервиса..."
"$INIT_SCRIPT" restart
print_success "Сервис перезапущен"
fi
pause
;;
b|B)
return 0
;;
*)
print_error "Неверный выбор: $sub_choice"
pause
;;
esac
}
# ==============================================================================
# ПОДМЕНЮ: RST-ФИЛЬТР (ПАССИВНЫЙ DPI)
# ==============================================================================
menu_rst_filter() {
clear_screen
print_header "RST-фильтр (пассивный DPI)"
local config_file="${ZAPRET2_DIR}/config"
if [ ! -f "$config_file" ]; then
print_error "Конфиг не найден: $config_file"
print_info "Запустите установку сначала"
pause
return 1
fi
local DROP_DPI_RST
DROP_DPI_RST=$(safe_config_read "DROP_DPI_RST" "$config_file" "0")
print_separator
print_info "Статус: $([ "$DROP_DPI_RST" = "1" ] && echo 'Включен' || echo 'Выключен')"
print_separator
cat <<'SUBMENU'
ТСПУ (DPI провайдера) отправляет поддельные TCP RST пакеты
раньше реального ответа сервера, чтобы разорвать соединение.
Признак: IP Identification 0x0000-0x000F (в реальных пакетах
это поле случайное).
RST-фильтр блокирует такие пакеты через iptables raw/PREROUTING.
Это помогает если сайты разрываются сразу после TLS handshake.
Требуется модуль xt_u32 (есть в Keenetic с Entware).
[1] Включить
[2] Выключить
[B] Назад
SUBMENU
printf "Выберите опцию [1-2,B]: "
read_input sub_choice
case "$sub_choice" in
1)
if grep -q '^DROP_DPI_RST=' "$config_file"; then
sed -i 's/^DROP_DPI_RST=.*/DROP_DPI_RST=1/' "$config_file"
else
echo "DROP_DPI_RST=1" >> "$config_file"
fi
print_success "RST-фильтр включен"
if is_zapret2_running; then
print_info "Перезапуск сервиса..."
"$INIT_SCRIPT" restart
print_success "Сервис перезапущен с RST-фильтром"
else
print_warning "Сервис не запущен. Запустите через [4] Управление сервисом"
fi
pause
;;
2)
if [ "$DROP_DPI_RST" != "1" ]; then
print_info "Фильтр уже выключен"
pause
return 0
fi
sed -i 's/^DROP_DPI_RST=.*/DROP_DPI_RST=0/' "$config_file"
print_success "RST-фильтр выключен"
if is_zapret2_running; then
print_info "Перезапуск сервиса..."
"$INIT_SCRIPT" restart
print_success "Сервис перезапущен"
fi
pause
;;
b|B)
return 0
;;
*)
print_error "Неверный выбор: $sub_choice"
pause
;;
esac
}
# ==============================================================================
# ПОДМЕНЮ: СКРИПТЫ CUSTOM.D
# ==============================================================================
menu_custom_scripts() {
clear_screen
print_header "Скрипты custom.d"
local zapret_config="/opt/zapret2/config"
if [ ! -f "$zapret_config" ]; then
print_error "Файл конфигурации не найден: $zapret_config"
pause
return 1
fi
# Прочитать текущее значение
local current_value
current_value=$(grep "^DISABLE_CUSTOM=" "$zapret_config" 2>/dev/null | cut -d= -f2)
[ -z "$current_value" ] && current_value="1"
print_separator
if [ "$current_value" = "1" ]; then
print_success "Скрипты custom.d: ОТКЛЮЧЕНЫ (рекомендуется)"
else
print_warning "Скрипты custom.d: ВКЛЮЧЕНЫ"
fi
print_separator
cat <<'INFO'
Скрипты custom.d (50-stun4all, 50-discord-media) запускают
дополнительные демоны nfqws2 для Discord voice/video.
ВНИМАНИЕ: Discord voice/video уже обрабатывается основными
стратегиями (профиль 6 — Discord UDP). Включение скриптов
создаст дублирующие демоны и может вызвать конфликты.
Включайте только если основные стратегии не помогают с Discord.
[1] Включить скрипты custom.d
[2] Отключить скрипты custom.d (рекомендуется)
[B] Назад
INFO
printf "Выберите опцию [1-2,B]: "
read_input sub_choice
case "$sub_choice" in
1)
sed -i 's/^DISABLE_CUSTOM=.*/DISABLE_CUSTOM=0/' "$zapret_config"
print_warning "Скрипты custom.d ВКЛЮЧЕНЫ"
if is_zapret2_running; then
print_info "Перезапуск сервиса..."
"$INIT_SCRIPT" restart
print_success "Сервис перезапущен"
fi
pause
;;
2)
sed -i 's/^DISABLE_CUSTOM=.*/DISABLE_CUSTOM=1/' "$zapret_config"
print_success "Скрипты custom.d ОТКЛЮЧЕНЫ"
if is_zapret2_running; then
print_info "Перезапуск сервиса..."
"$INIT_SCRIPT" restart
print_success "Сервис перезапущен"
fi
pause
;;
b|B)
return 0
;;
*)
print_error "Неверный выбор: $sub_choice"
pause
;;
esac
}
menu_rkn_silent_fallback() {
clear_screen
print_header "Silent fallback для РКН (осторожно!)"
local config_file="${ZAPRET2_DIR}/config"
if [ ! -f "$config_file" ]; then
print_error "Конфиг не найден: $config_file"
print_info "Запустите установку сначала"
pause
return 1
fi
local RKN_SILENT_FALLBACK
RKN_SILENT_FALLBACK=$(safe_config_read "RKN_SILENT_FALLBACK" "$config_file" "0")
print_separator
print_info "Статус: $([ "$RKN_SILENT_FALLBACK" = "1" ] && echo 'Включен' || echo 'Выключен')"
print_separator
cat <<'SUBMENU'
Детектор «тихих чёрных дыр» для РКН-списков.
Когда DPI молча блокирует соединение (не отвечая RST/alert),
circular не может определить, что стратегия не работает.
Silent fallback считает повторные ClientHello без ответа
за failure и принудительно ротирует стратегию.
По умолчанию включено только для YouTube. Включение для РКН
может вызвать ложные срабатывания на медленных сайтах — circular
будет ротировать стратегию когда сайт просто долго отвечает.
[1] Включить (возможны поломки на медленных сайтах!)
[2] Выключить
[B] Назад
SUBMENU
printf "Выберите опцию [1-2,B]: "
read_input sub_choice
case "$sub_choice" in
1)
if grep -q '^RKN_SILENT_FALLBACK=' "$config_file"; then
sed -i 's/^RKN_SILENT_FALLBACK=.*/RKN_SILENT_FALLBACK=1/' "$config_file"
else
echo "RKN_SILENT_FALLBACK=1" >> "$config_file"
fi
print_success "Silent fallback для РКН включен"
# Создать флаг-файл для Lua
local flag_dir="${ZAPRET2_DIR}/extra_strats/cache/autocircular"
touch "${flag_dir}/rkn_silent_fallback.flag" 2>/dev/null
# Перегенерировать конфиг с новыми параметрами circular
print_info "Пересоздание конфига..."
create_official_config "/opt/zapret2/config"
if is_zapret2_running; then
print_info "Перезапуск сервиса..."
"$INIT_SCRIPT" restart
print_success "Сервис перезапущен с silent fallback для РКН"
else
print_warning "Сервис не запущен. Запустите через [2] Управление сервисом"
fi
pause
;;
2)
if [ "$RKN_SILENT_FALLBACK" != "1" ]; then
print_info "Silent fallback уже выключен"
pause
return 0
fi
sed -i 's/^RKN_SILENT_FALLBACK=.*/RKN_SILENT_FALLBACK=0/' "$config_file"
# Удалить флаг-файл
rm -f "${ZAPRET2_DIR}/extra_strats/cache/autocircular/rkn_silent_fallback.flag" 2>/dev/null
print_success "Silent fallback для РКН выключен"
# Перегенерировать конфиг
print_info "Пересоздание конфига..."
create_official_config "/opt/zapret2/config"
if is_zapret2_running; then
print_info "Перезапуск сервиса..."
"$INIT_SCRIPT" restart
print_success "Сервис перезапущен"
fi
pause
;;
b|B)
return 0
;;
*)
print_error "Неверный выбор: $sub_choice"
pause
;;
esac
}
# ==============================================================================
# ПОДМЕНЮ: ИГРОВОЙ РЕЖИМ
# ==============================================================================
menu_roblox_bypass() {
clear_screen
print_header "Игровой режим"
local config_file="${ZAPRET2_DIR}/config"
if [ ! -f "$config_file" ]; then
print_error "Конфиг не найден: $config_file"
print_info "Запустите установку сначала"
pause
return 1
fi
local ROBLOX_UDP_BYPASS
ROBLOX_UDP_BYPASS=$(safe_config_read "ROBLOX_UDP_BYPASS" "$config_file" "0")
print_separator
print_info "Статус: $([ "$ROBLOX_UDP_BYPASS" = "1" ] && echo 'Включен' || echo 'Выключен')"
print_separator
cat <<'SUBMENU'
UDP+TCP игровой bypass (Roblox, Fortnite, Apex, Hypixel, и др.).
Данные о поддерживаемых играх — в files/lists/game_ips.txt и
extra_strats/TCP/GAMES/List.txt. Автоциркуляр подбирает рабочую
стратегию, правит только IP игровых AS (без шума Discord/Steam).
[1] Включить
[2] Выключить
[B] Назад
SUBMENU
printf "Выберите опцию [1-2,B]: "
read_input sub_choice
case "$sub_choice" in
1)
# New flag (GAME_MODE_ENABLED) + legacy alias (ROBLOX_UDP_BYPASS)
# written in lockstep. config_official.sh prefers GAME_MODE_ENABLED
# and falls back to the legacy name, so old routers that haven't
# updated yet still flip correctly off the same toggle.
if grep -q '^GAME_MODE_ENABLED=' "$config_file"; then
sed -i 's/^GAME_MODE_ENABLED=.*/GAME_MODE_ENABLED=1/' "$config_file"
else
echo "GAME_MODE_ENABLED=1" >> "$config_file"
fi
if grep -q '^ROBLOX_UDP_BYPASS=' "$config_file"; then
sed -i 's/^ROBLOX_UDP_BYPASS=.*/ROBLOX_UDP_BYPASS=1/' "$config_file"
else
echo "ROBLOX_UDP_BYPASS=1" >> "$config_file"
fi
# Add ephemeral UDP ports if not present. TCP game ports
# (443,2053,2083,2087,2096,8443) are already in the default
# NFQWS2_PORTS_TCP template — no action needed for TCP.
if ! grep -q "1024-65535" "$config_file"; then
sed -i 's/^NFQWS2_PORTS_UDP="\(.*\)"/NFQWS2_PORTS_UDP="\1,1024-65535"/' "$config_file"
fi
print_success "Игровой режим включен"
# Перегенерировать конфиг с game filter профилем
print_info "Пересоздание конфига..."
create_official_config "/opt/zapret2/config"
if is_zapret2_running; then
print_info "Перезапуск сервиса..."
"$INIT_SCRIPT" restart
print_success "Сервис перезапущен"
else
print_warning "Сервис не запущен. Запустите через [2] Управление сервисом"
fi
pause
;;
2)
if [ "$ROBLOX_UDP_BYPASS" != "1" ]; then
print_info "Игровой режим уже выключен"
pause
return 0
fi
# Flip both flags in lockstep (see enable branch for rationale).
sed -i 's/^GAME_MODE_ENABLED=.*/GAME_MODE_ENABLED=0/' "$config_file"
sed -i 's/^ROBLOX_UDP_BYPASS=.*/ROBLOX_UDP_BYPASS=0/' "$config_file"
# Remove ephemeral UDP ports
sed -i 's/,1024-65535//' "$config_file"
print_success "Игровой режим выключен"
# Перегенерировать конфиг без game filter
print_info "Пересоздание конфига..."
create_official_config "/opt/zapret2/config"
if is_zapret2_running; then
print_info "Перезапуск сервиса..."
"$INIT_SCRIPT" restart
print_success "Сервис перезапущен"
fi
pause
;;
[Bb])
return 0
;;
*)
print_error "Неверный выбор"
pause
;;
esac
}
# ==============================================================================
# ПОДМЕНЮ: TELEGRAM MTPROXY
# ==============================================================================
menu_telegram_mtproxy() {
local MTPROXY_BIN="/opt/sbin/tg-mtproxy-client"
while true; do
clear_screen
print_header "Telegram"
local tunnel_running=false
if pgrep -f "tg-mtproxy-client" >/dev/null 2>&1; then
tunnel_running=true
fi
print_separator
printf " Статус: %s\n" "$($tunnel_running && echo 'Включен' || echo 'Выключен')"
print_separator
cat <<'SUBMENU'
Telegram для всех устройств в сети.
Настройка на устройствах не требуется.
[1] Включить
[2] Выключить
[B] Назад
SUBMENU
printf "Выберите опцию [1-2,B]: "
read_input sub_choice
case "$sub_choice" in
1)
# Download binary if missing
if ! [ -f "$MTPROXY_BIN" ]; then
print_info "Скачиваю бинарник..."
local tg_arch=""
local _hw_arch _tg_bin_arch
_hw_arch=$(get_arch 2>/dev/null || uname -m)
_tg_bin_arch=$(map_arch_to_bin_arch "$_hw_arch" 2>/dev/null || true)
case "$_tg_bin_arch" in
linux-arm64) tg_arch="arm64" ;;
linux-arm) tg_arch="arm" ;;
linux-mipsel) tg_arch="mipsel" ;;
linux-mips64el) tg_arch="mips64el" ;;
linux-mips64) tg_arch="mips" ;;
linux-mips) tg_arch="mips" ;;
linux-x86_64) tg_arch="amd64" ;;
linux-x86) tg_arch="x86" ;;
linux-riscv64) tg_arch="riscv64" ;;
linux-ppc) tg_arch="ppc64" ;;
esac
if [ -n "$tg_arch" ]; then
local tg_bin="tg-mtproxy-client-linux-${tg_arch}"
local tg_url="${GITHUB_RAW}/mtproxy-client/builds/${tg_bin}"
rm -f "$MTPROXY_BIN"
z2k_fetch "$tg_url" "$MTPROXY_BIN"
local tg_size
tg_size=$(wc -c < "$MTPROXY_BIN" 2>/dev/null || echo 0)
if [ -f "$MTPROXY_BIN" ] && [ "$tg_size" -gt 500000 ] 2>/dev/null && head -c 4 "$MTPROXY_BIN" 2>/dev/null | grep -q "ELF"; then
chmod +x "$MTPROXY_BIN"
if "$MTPROXY_BIN" --help 2>/dev/null; [ $? -le 2 ]; then
print_success "Скачан и проверен ($tg_arch)"
else
rm -f "$MTPROXY_BIN"
print_error "Бинарник не запускается (неверная архитектура?)"
print_info "Проверьте: opkg print-architecture"
pause
continue
fi
else
rm -f "$MTPROXY_BIN"
print_error "Не удалось скачать бинарник (файл повреждён или CDN вернул ошибку)"
pause
continue
fi
else
print_error "Неизвестная архитектура: $(uname -m)"
pause
continue
fi
fi
# Stop any old processes
killall tg-mtproxy-client 2>/dev/null || true
sleep 1
# Start tunnel
"$MTPROXY_BIN" --listen=:1443 >> /tmp/tg-tunnel.log 2>&1 &
sleep 2
if pgrep -f "tg-mtproxy-client" >/dev/null 2>&1; then
print_success "Tunnel запущен"
# Setup iptables
for cidr in 149.154.160.0/20 91.108.4.0/22 91.108.8.0/22 91.108.12.0/22 91.108.16.0/22 91.108.20.0/22 91.108.56.0/22 91.105.192.0/23 95.161.64.0/20 185.76.151.0/24; do
iptables -t nat -C PREROUTING -d "$cidr" -p tcp --dport 443 -j REDIRECT --to-port 1443 2>/dev/null || \
iptables -t nat -A PREROUTING -d "$cidr" -p tcp --dport 443 -j REDIRECT --to-port 1443 2>/dev/null
done
print_success "Telegram работает для всех устройств"
else
print_error "Не удалось запустить"
tail -5 /tmp/tg-tunnel.log 2>/dev/null
rm -f "$MTPROXY_BIN"
print_info "Бинарник удалён — нажмите [1] ещё раз для перескачивания"
fi
pause
;;
2)
# Stop tunnel + cleanup
killall tg-mtproxy-client 2>/dev/null || true
for cidr in 149.154.160.0/20 91.108.4.0/22 91.108.8.0/22 91.108.12.0/22 91.108.16.0/22 91.108.20.0/22 91.108.56.0/22 91.105.192.0/23 95.161.64.0/20 185.76.151.0/24; do
iptables -t nat -D PREROUTING -d "$cidr" -p tcp --dport 443 -j REDIRECT --to-port 1443 2>/dev/null || true
done
print_success "Telegram tunnel выключен"
pause
;;
[Bb])
return
;;
*)
print_error "Неверный выбор"
pause
;;
esac
done
}
# ==============================================================================
# ПОДМЕНЮ: WHITELIST (ИСКЛЮЧЕНИЯ)
# ==============================================================================
menu_whitelist() {
clear_screen
print_header "Whitelist - Исключения из обработки"
local whitelist_file="${LISTS_DIR}/whitelist.txt"
# Проверить существование файла
if [ ! -f "$whitelist_file" ]; then
print_warning "Файл whitelist не найден: $whitelist_file"
print_info "Создаю файл..."
# Создать директорию если не существует
if ! mkdir -p "$LISTS_DIR" 2>/dev/null; then
print_error "Не удалось создать директорию: $LISTS_DIR"
print_info "Проверьте права доступа"
pause
return 1
fi
# Создать базовый whitelist
cat > "$whitelist_file" <<'EOF'
# Whitelist - домены исключенные из обработки zapret2
# Сервисы, которые могут работать некорректно с DPI bypass
# === Госуслуги РФ ===
gosuslugi.ru
esia.gosuslugi.ru
lk.gosuslugi.ru
nalog.ru
nalog.gov.ru
lkfl2.nalog.ru
pfr.gov.ru
es.pfr.gov.ru
mos.ru
mos-gorsud.ru
gov.ru
sudrf.ru
# === Российские сервисы ===
vk.com
vkcdn.net
userapi.com
vk.ru
vkvideo.ru
rutube.ru
yandex.ru
ya.ru
kinopoisk.ru
okko.tv
avito.ru
beeline.ru
beeline.tv
ottai.com
ipstream.one
vkusvill.ru
# === Steam ===
s.team
steam.tv
steamcdn.com
steamchat.com
steam-chat.com
steamgames.com
steamserver.net
steamstatic.com
steampowered.com
steamcontent.com
steamcommunity.com
steambroadcast.com
steamdeckcdn.com
steamdeckusercontent.com
steamuserimages-a.akamaihd.net
steamcdn-a.akamaihd.net
steampipe.akamaized.net
steamcdn-a.akamaized.net
steamstatic.akamaized.net
steamcommunity.akamaized.net
steamcommunity-a.akamaihd.net
steamcloudsweden.blob.core.windows.net
valve.net
valvecdn.com
valvecontent.com
valvesoftware.com
# === Epic Games ===
epicgames.com
epicgames.dev
epicgamescdn.com
unrealengine.com
easyanticheat.net
eac-cdn.com
fortnite.com
fab.com
artstation.com
# === Ubisoft ===
ubi.com
ubisoft.com
ubisoftconnect.com
# === PlayStation / Sony ===
playstation.net
playstation.com
account.sony.com
psremoteplay.com
playstationcloud.com
sonyentertainmentnetwork.com
# === Twitch ===
twitch.tv
ttvnw.net
jtvnw.net
twitchcdn.net
ext-twitch.tv
twitchsvc.net
live-video.net
twitch-shadow.net
# === Riot Games / Valorant ===
riotgames.com
riotcdn.net
valorant.com
playvalorant.com
pvp.net
vivox.com
sd-rtn.com
# === HoYoverse (Genshin, HSR) ===
hoyoverse.com
hoyolab.com
hoyo.link
yuanshen.com
genshinimpact.com
zenlesszonezero.com
# === AliExpress ===
aliexpress.com
aliexpress.ru
aliexpress.us
alicdn.com
ae.com
# === TikTok ===
tiktok.com
tiktokcdn.com
tiktokv.com
muscdn.com
byteoversea.com
ibytedtos.com
ttwstatic.com
# === Samsung ===
samsungosp.com
samsungqbe.com
samsungcloudsolution.com
# === Стриминг ===
netflix.com
vsetop.org
# === Google API ===
ogs.google.com
gstatic.com
# === Мониторинг и CDN ===
datadoghq.com
okcdn.ru
api.mycdn.me
# === Keenetic (KeenDNS, облако, обновления) ===
keenetic.pro
keenetic.com
keenetic.io
keenetic.cloud
keenetic.link
# === Разработка ===
raw.githubusercontent.com
EOF
if [ ! -f "$whitelist_file" ]; then
print_error "Не удалось создать файл whitelist"
print_info "Проверьте права доступа"
pause
return 1
fi
print_success "Файл whitelist создан: $whitelist_file"
fi
while true; do
print_separator
cat <<'INFO'
Whitelist содержит домены, которые ИСКЛЮЧЕНЫ из обработки zapret2.
Это полезно для критичных сервисов, которые могут сломаться
при обработке (госуслуги, банки, и т.д.)
По умолчанию в whitelist включены:
- Госуслуги РФ (gosuslugi, nalog, pfr, mos, gov.ru...)
- Российские сервисы (VK, Yandex, Rutube, Avito, Beeline...)
- Steam, Epic Games, Ubisoft, PlayStation
- Twitch, Riot/Valorant, HoYoverse, TikTok
- AliExpress, Samsung, Netflix
[1] Просмотреть whitelist
[2] Редактировать whitelist (vi)
[3] Добавить домен
[4] Удалить домен
[B] Назад
INFO
printf "Выберите опцию [1-4,B]: "
read_input sub_choice
case "$sub_choice" in
1)
# Просмотр
clear_screen
print_header "Текущий whitelist"
print_separator
cat "$whitelist_file"
print_separator
pause
;;
2)
# Редактирование в vi
print_info "Открытие whitelist в редакторе..."
vi "$whitelist_file"
# Перезапуск сервиса
if is_zapret2_running; then
print_info "Перезапуск сервиса для применения изменений..."
"$INIT_SCRIPT" restart
print_success "Сервис перезапущен"
fi
pause
;;
3)
# Добавить домен
printf "Введите домен для добавления (например: example.com): "
read_input new_domain
# Простая валидация домена
if ! echo "$new_domain" | grep -qE '^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'; then
print_error "Неверный формат домена: $new_domain"
pause
continue
fi
# Проверить дубликаты
if grep -qxF "$new_domain" "$whitelist_file"; then
print_warning "Домен $new_domain уже в whitelist"
pause
continue
fi
# Добавить домен
echo "$new_domain" >> "$whitelist_file"
print_success "Домен $new_domain добавлен в whitelist"
print_separator
# Перезапуск сервиса
if is_zapret2_running; then
print_info "Перезапуск сервиса для применения изменений..."
"$INIT_SCRIPT" restart
print_success "Сервис перезапущен"
fi
pause
;;
4)
# Удалить домен
printf "Введите домен для удаления: "
read_input del_domain
# Проверить наличие
if ! grep -qxF "$del_domain" "$whitelist_file"; then
print_error "Домен $del_domain не найден в whitelist"
pause
continue
fi
# Удалить домен
grep -vxF "$del_domain" "$whitelist_file" > "${whitelist_file}.tmp" && mv "${whitelist_file}.tmp" "$whitelist_file"
print_success "Домен $del_domain удален из whitelist"
print_separator
# Перезапуск сервиса
if is_zapret2_running; then
print_info "Перезапуск сервиса для применения изменений..."
"$INIT_SCRIPT" restart
print_success "Сервис перезапущен"
fi
pause
;;
b|B)
return 0
;;
*)
print_error "Неверный выбор: $sub_choice"
pause
;;
esac
done
}
# ==============================================================================
# ПОДМЕНЮ: УПРАВЛЕНИЕ QUIC
# ==============================================================================
menu_quic_settings() {
clear_screen
print_header "Настройки QUIC"
printf "\nТекущие настройки:\n"
printf " YouTube QUIC: стратегия #%s\n" "$(get_current_quic_strategy)"
cat <<'MENU'
[1] YouTube QUIC - выбрать стратегию
[B] Назад
MENU
printf "Выберите опцию: "
read_input choice
case "$choice" in
1)
menu_select_quic_strategy_youtube
;;
b|B)
return 0
;;
*)
print_error "Неверный выбор: $choice"
pause
;;
esac
}
# Выбор QUIC стратегии для YouTube
menu_select_quic_strategy_youtube() {
clear_screen
print_header "YouTube QUIC - выбор стратегии"
local total_quic
total_quic=$(get_quic_strategies_count)
if [ "$total_quic" -eq 0 ]; then
print_error "QUIC стратегии не найдены"
pause
return 1
fi
local current_quic
current_quic=$(get_current_quic_strategy)
printf "\nВсего QUIC стратегий: %s\n" "$total_quic"
printf "Текущая стратегия: #%s\n\n" "$current_quic"
printf "Введите номер стратегии [1-%s] или Enter для отмены: " "$total_quic"
read_input new_strategy
if [ -z "$new_strategy" ]; then
print_info "Отменено"
pause
return 0
fi
if ! echo "$new_strategy" | grep -qE '^[0-9]+$'; then
print_error "Неверный формат"
pause
return 1
fi
if [ "$new_strategy" -lt 1 ] || [ "$new_strategy" -gt "$total_quic" ]; then
print_error "Номер вне диапазона"
pause
return 1
fi
if ! quic_strategy_exists "$new_strategy"; then
print_error "QUIC стратегия #$new_strategy не найдена"
pause
return 1
fi
set_current_quic_strategy "$new_strategy"
# Получить текущие стратегии
local config_file="${CONFIG_DIR}/category_strategies.conf"
local current_yt_tcp=1
local current_yt_gv=1
local current_rkn=1
if [ -f "$config_file" ]; then
current_yt_tcp=$(grep "^youtube_tcp:" "$config_file" 2>/dev/null | cut -d':' -f2)
current_yt_gv=$(grep "^youtube_gv:" "$config_file" 2>/dev/null | cut -d':' -f2)
current_rkn=$(grep "^rkn:" "$config_file" 2>/dev/null | cut -d':' -f2)
[ -z "$current_yt_tcp" ] && current_yt_tcp=1
[ -z "$current_yt_gv" ] && current_yt_gv=1
[ -z "$current_rkn" ] && current_rkn=1
fi
apply_category_strategies_v2 "$current_yt_tcp" "$current_yt_gv" "$current_rkn"
print_success "YouTube QUIC стратегия #$new_strategy применена"
pause
}
# ==============================================================================
# ЭКСПОРТ ФУНКЦИЙ
# ==============================================================================
# Все функции доступны после source этого файла