z2k/lib/install.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

2312 lines
102 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/install.sh - Полный процесс установки zapret2 для Keenetic
# 12-шаговая установка с интеграцией списков доменов и стратегий
# ==============================================================================
# ШАГ 0: ПРОВЕРКА ROOT ПРАВ (КРИТИЧНО)
# ==============================================================================
step_check_root() {
print_header "Шаг 0/12: Проверка прав доступа"
print_info "Проверка root прав..."
if [ "$(id -u)" -ne 0 ]; then
print_error "Требуются root права для установки zapret2"
print_separator
print_info "Запустите установку с правами root:"
printf " sudo sh z2k.sh install\n\n"
print_warning "Без root прав невозможно:"
print_warning " - Установить пакеты через opkg"
print_warning " - Создать init скрипт в /opt/etc/init.d/"
print_warning " - Настроить iptables правила"
print_warning " - Загрузить модули ядра"
return 1
fi
print_success "Root права подтверждены (UID=$(id -u))"
return 0
}
# ==============================================================================
# ШАГ 1: ОБНОВЛЕНИЕ ПАКЕТОВ
# ==============================================================================
step_update_packages() {
print_header "Шаг 1/12: Обновление пакетов"
print_info "Обновление списка пакетов Entware..."
# Попытка обновления с полным перехватом вывода
local opkg_output
opkg_output=$(opkg update 2>&1)
local exit_code=$?
# Показать вывод opkg
echo "$opkg_output"
if [ "$exit_code" -eq 0 ]; then
print_success "Список пакетов обновлен"
return 0
else
print_error "Не удалось обновить список пакетов (код: $exit_code)"
# Проверка на Illegal instruction - типичная проблема на Keenetic из-за блокировки РКН
if echo "$opkg_output" | grep -qi "illegal instruction"; then
print_warning "Обнаружена ошибка 'Illegal instruction'"
print_info "Это часто связано с блокировкой РКН репозитория bin.entware.net"
print_separator
# Попытка переключения на альтернативное зеркало (метод от zapret4rocket)
print_info "Попытка переключения на альтернативное зеркало Entware..."
local current_mirror
current_mirror=$(grep -m1 "^src" /opt/etc/opkg.conf | awk '{print $3}' | grep -o 'bin.entware.net')
if [ -n "$current_mirror" ]; then
print_info "Меняю bin.entware.net → entware.diversion.ch"
# Создать backup конфига
cp /opt/etc/opkg.conf /opt/etc/opkg.conf.backup
# Заменить зеркало
sed -i 's|bin.entware.net|entware.diversion.ch|g' /opt/etc/opkg.conf
print_info "Повторная попытка обновления с новым зеркалом..."
# Повторить opkg update
opkg_output=$(opkg update 2>&1)
exit_code=$?
echo "$opkg_output"
if [ "$exit_code" -eq 0 ]; then
print_success "Список пакетов обновлен через альтернативное зеркало!"
print_info "Backup старого конфига: /opt/etc/opkg.conf.backup"
return 0
else
print_error "Не помогло - ошибка осталась"
print_info "Восстанавливаю оригинальный конфиг..."
mv /opt/etc/opkg.conf.backup /opt/etc/opkg.conf
fi
else
print_info "Зеркало bin.entware.net не найдено в конфиге"
fi
printf "\n"
fi
# Диагностика причины ошибки
print_info "Углубленная диагностика проблемы..."
print_separator
# Анализ вывода opkg для определения точного места ошибки
if echo "$opkg_output" | grep -q "Illegal instruction"; then
# Попробовать найти контекст
local error_context
error_context=$(echo "$opkg_output" | grep -B2 "Illegal instruction" | head -5)
if [ -n "$error_context" ]; then
print_info "Контекст ошибки:"
echo "$error_context"
fi
fi
printf "\n"
# 1. Проверка архитектуры системы
local sys_arch
sys_arch=$(uname -m)
print_info "Архитектура системы: $sys_arch"
# 2. Проверка архитектуры Entware
if [ -f "/opt/etc/opkg.conf" ]; then
local entware_arch
entware_arch=$(grep -m1 "^arch" /opt/etc/opkg.conf | awk '{print $2}')
print_info "Архитектура Entware: ${entware_arch:-не определена}"
local repo_url
repo_url=$(grep -m1 "^src" /opt/etc/opkg.conf | awk '{print $3}')
print_info "Репозиторий: $repo_url"
# 3. Проверка доступности репозитория
if [ -n "$repo_url" ]; then
print_info "Проверка доступности репозитория..."
if curl -s -m 5 --head "$repo_url/Packages.gz" >/dev/null 2>&1; then
print_success "[OK] Репозиторий доступен"
else
print_error "[FAIL] Репозиторий недоступен"
fi
fi
fi
# 4. Проверка самого opkg
print_info "Проверка opkg бинарника..."
if opkg --version 2>&1 | grep -qi "illegal"; then
print_error "[FAIL] opkg --version падает (Illegal instruction)"
print_warning "ПРИЧИНА: opkg установлен для неправильной архитектуры CPU!"
elif opkg --version >/dev/null 2>&1; then
local opkg_version
opkg_version=$(opkg --version 2>&1 | head -1)
print_success "[OK] opkg бинарник запускается: $opkg_version"
print_warning "Но 'opkg update' падает - возможно проблема в зависимости или скрипте"
else
print_error "[FAIL] opkg не работает по неизвестной причине"
fi
# 5. Проверка файла opkg
if command -v file >/dev/null 2>&1; then
if [ -f "/opt/bin/opkg" ]; then
local opkg_file_info
opkg_file_info=$(file /opt/bin/opkg 2>&1 | head -1)
print_info "Бинарник opkg: $opkg_file_info"
fi
fi
print_separator
# 6. Рекомендации по дополнительной диагностике
print_info "Для детальной диагностики попробуйте вручную:"
printf " opkg update --verbosity=2\n\n"
# Определяем основную причину на основе диагностики
if opkg --version 2>&1 | grep -qi "illegal"; then
cat <<'EOF'
[WARN] КРИТИЧЕСКАЯ ПРОБЛЕМА: НЕПРАВИЛЬНАЯ АРХИТЕКТУРА ENTWARE
Диагностика показала: opkg не может выполниться на этом роутере.
Это означает что Entware установлен для НЕПРАВИЛЬНОЙ архитектуры CPU.
ПРИЧИНА:
Ваш роутер имеет процессор одной архитектуры, а установлен Entware
для другой архитектуры. Это как пытаться запустить программу для
Intel на процессоре ARM.
ЧТО ДЕЛАТЬ:
1. Удалите текущий Entware:
- Зайдите в веб-интерфейс роутера
- Система → Компоненты → Entware → Удалить
2. Установите ПРАВИЛЬНУЮ версию Entware:
- Скачайте installer.sh с официального сайта
- Убедитесь что выбрана версия для ВАШЕЙ модели роутера
- https://help.keenetic.com/hc/ru/articles/360021888880
3. После переустановки запустите z2k снова
ВАЖНО: z2k не может работать с неправильной версией Entware!
EOF
elif echo "$opkg_output" | grep -qi "illegal instruction"; then
cat <<'EOF'
[WARN] СЛОЖНАЯ ПРОБЛЕМА: opkg update падает с "Illegal instruction"
Диагностика и попытки исправления:
- [OK] opkg бинарник запускается (opkg --version работает)
- [OK] Архитектура системы корректная
- [OK] Репозиторий доступен (curl тест успешен)
- [OK] Попробовали альтернативное зеркало (entware.diversion.ch)
- [FAIL] НО "opkg update" всё равно падает с "Illegal instruction"
Это редкая проблема, которая может быть связана с:
1. Поврежденной зависимой библиотекой (libcurl, libssl, и др.)
2. Несовместимостью конкретной версии пакета с вашим CPU
3. Поврежденной базой данных opkg
4. Проблемой с самой установкой Entware
РЕКОМЕНДАЦИИ ПО УСТРАНЕНИЮ:
1. Проверьте какая библиотека вызывает ошибку:
ldd /opt/bin/opkg
(покажет все зависимые библиотеки)
2. Попробуйте детальную диагностику:
opkg update --verbosity=2 2>&1 | tee /tmp/opkg_debug.log
(сохранит полный вывод в файл)
3. Очистите кэш и попробуйте снова:
rm -rf /opt/var/opkg-lists/*
opkg update
4. Проверьте место на диске:
df -h /opt
(убедитесь что есть свободное место)
5. Если ничего не помогает - переустановите Entware:
https://help.keenetic.com/hc/ru/articles/360021888880
Убедитесь что выбираете версию Entware для вашей архитектуры!
ПРОДОЛЖИТЬ БЕЗ ОБНОВЛЕНИЯ?
Можно попробовать продолжить установку z2k.
Если нужные пакеты (iptables, ipset, curl) уже установлены -
всё может заработать и без обновления списков пакетов.
EOF
else
cat <<'EOF'
[WARN] ОШИБКА ПРИ ОБНОВЛЕНИИ ПАКЕТОВ
Проверьте результаты диагностики выше.
Если репозиторий недоступен:
- Проблемы с сетью, DNS или блокировка
- Проверьте: curl -I http://bin.entware.net/
Если другая проблема:
- Попробуйте вручную: opkg update --verbosity=2
- Проверьте логи: cat /opt/var/log/opkg.log
ПРОДОЛЖИТЬ БЕЗ ОБНОВЛЕНИЯ?
Установка продолжится с текущими пакетами.
Обычно это безопасно, если пакеты уже установлены.
EOF
fi
printf "\nПродолжить без opkg update? [Y/n]: "
read -r answer </dev/tty
case "$answer" in
[Nn]|[Nn][Oo])
print_info "Установка прервана"
print_info "Исправьте проблему и запустите снова"
return 1
;;
*)
print_warning "Продолжаем без обновления пакетов..."
print_info "Будет использована текущая локальная база пакетов"
return 0
;;
esac
fi
}
# ==============================================================================
# ШАГ 2: ПРОВЕРКА DNS (ВАЖНО)
# ==============================================================================
step_check_dns() {
print_header "Шаг 2/12: Проверка DNS"
print_info "Проверка работы DNS и доступности интернета..."
# Проверить несколько серверов
local test_hosts="github.com google.com cloudflare.com"
local dns_works=0
for host in $test_hosts; do
if nslookup "$host" >/dev/null 2>&1; then
print_success "DNS работает ($host разрешён)"
dns_works=1
break
fi
done
if [ $dns_works -eq 0 ]; then
print_error "DNS не работает!"
print_separator
print_warning "Возможные причины:"
print_warning " 1. Нет подключения к интернету"
print_warning " 2. DNS сервер не настроен"
print_warning " 3. Блокировка РКН (bin.entware.net, github.com)"
print_separator
printf "Продолжить установку без работающего DNS? [y/N]: "
read -r answer </dev/tty
case "$answer" in
[Yy]*)
print_warning "Продолжаем без DNS..."
print_info "Установка может не удаться при загрузке файлов"
return 0
;;
*)
print_info "Установка прервана"
print_info "Исправьте DNS и запустите снова"
return 1
;;
esac
fi
return 0
}
# ==============================================================================
# ШАГ 3: УСТАНОВКА ЗАВИСИМОСТЕЙ (РАСШИРЕНО)
# ==============================================================================
step_install_dependencies() {
print_header "Шаг 3/12: Установка зависимостей"
# Список необходимых пакетов для Entware (только runtime)
local packages="
libmnl
libnetfilter-queue
libnfnetlink
libcap
zlib
curl
unzip
cron
"
print_info "Установка пакетов..."
for pkg in $packages; do
if opkg list-installed | grep -q "^${pkg} "; then
print_info "$pkg уже установлен"
else
print_info "Установка $pkg..."
opkg install "$pkg" || print_warning "Не удалось установить $pkg"
fi
done
# Создать симлинки для библиотек (нужно для линковки)
print_info "Создание симлинков библиотек..."
# Создание симлинков в подоболочке (не меняет рабочую директорию)
(
cd /opt/lib || exit 1
# libmnl
if [ ! -e libmnl.so ] && [ -e libmnl.so.0 ]; then
ln -sf libmnl.so.0 libmnl.so
fi
# libnetfilter_queue
if [ ! -e libnetfilter_queue.so ] && [ -e libnetfilter_queue.so.1 ]; then
ln -sf libnetfilter_queue.so.1 libnetfilter_queue.so
fi
# libnfnetlink
if [ ! -e libnfnetlink.so ] && [ -e libnfnetlink.so.0 ]; then
ln -sf libnfnetlink.so.0 libnfnetlink.so
fi
) || print_warning "Не удалось создать симлинки в /opt/lib"
print_info "Симлинки библиотек проверены"
# =========================================================================
# КРИТИЧНЫЕ ПАКЕТЫ ДЛЯ ZAPRET2 (из check_prerequisites_openwrt)
# =========================================================================
print_separator
print_info "Установка критичных пакетов для zapret2..."
local critical_packages=""
# ipset - КРИТИЧНО для фильтрации по спискам доменов
if ! opkg list-installed | grep -q "^ipset "; then
print_info "ipset требуется для фильтрации трафика"
critical_packages="$critical_packages ipset"
else
print_success "ipset уже установлен"
fi
# Проверка kernel модулей (на Keenetic встроены в ядро, не требуют установки)
# xt_NFQUEUE - КРИТИЧНО для перенаправления в NFQUEUE
if [ -f "/lib/modules/$(uname -r)/xt_NFQUEUE.ko" ] || lsmod | grep -q "xt_NFQUEUE" || modinfo xt_NFQUEUE >/dev/null 2>&1; then
print_success "Модуль xt_NFQUEUE доступен"
else
print_warning "Модуль xt_NFQUEUE не найден (может быть встроен в ядро)"
fi
# xt_connbytes, xt_multiport - для фильтрации пакетов
if modinfo xt_connbytes >/dev/null 2>&1 || grep -q "xt_connbytes" /proc/modules 2>/dev/null; then
print_success "Модуль xt_connbytes доступен"
else
print_warning "Модуль xt_connbytes не найден (может быть встроен в ядро)"
fi
if modinfo xt_multiport >/dev/null 2>&1 || grep -q "xt_multiport" /proc/modules 2>/dev/null; then
print_success "Модуль xt_multiport доступен"
else
print_warning "Модуль xt_multiport не найден (может быть встроен в ядро)"
fi
# Установить критичные пакеты если нужно (только ipset для Keenetic)
if [ -n "$critical_packages" ]; then
print_info "Установка:$critical_packages"
if opkg install $critical_packages; then
print_success "Критичные пакеты установлены"
else
print_error "Не удалось установить критичные пакеты"
print_warning "zapret2 может не работать без этих пакетов!"
printf "Продолжить без них? [y/N]: "
read -r answer </dev/tty
case "$answer" in
[Yy]*) print_warning "Продолжаем на свой страх и риск..." ;;
*) return 1 ;;
esac
fi
else
print_success "Все критичные пакеты уже установлены"
fi
print_separator
print_info "ПРИМЕЧАНИЕ: На Keenetic модули iptables (xt_NFQUEUE, xt_connbytes,"
print_info "xt_multiport) встроены в ядро и не требуют отдельной установки."
# =========================================================================
# ОПЦИОНАЛЬНЫЕ ОПТИМИЗАЦИИ (GNU gzip/sort)
# =========================================================================
print_separator
print_info "Проверка опциональных оптимизаций..."
# Проверить busybox gzip
if command -v gzip >/dev/null 2>&1; then
if readlink "$(command -v gzip)" 2>/dev/null | grep -q busybox; then
print_info "Обнаружен busybox gzip (медленный, ~3x медленнее GNU)"
printf "Установить GNU gzip для ускорения обработки списков? [y/N]: "
read -r answer </dev/tty
case "$answer" in
[Yy]*)
if opkg install --force-overwrite gzip; then
print_success "GNU gzip установлен"
else
print_warning "Не удалось установить GNU gzip"
fi
;;
*)
print_info "Пропускаем установку GNU gzip"
;;
esac
fi
fi
# Проверить busybox sort
if command -v sort >/dev/null 2>&1; then
if readlink "$(command -v sort)" 2>/dev/null | grep -q busybox; then
print_info "Обнаружен busybox sort (медленный, использует много RAM)"
printf "Установить GNU sort для ускорения? [y/N]: "
read -r answer </dev/tty
case "$answer" in
[Yy]*)
if opkg install --force-overwrite coreutils-sort; then
print_success "GNU sort установлен"
else
print_warning "Не удалось установить GNU sort"
fi
;;
*)
print_info "Пропускаем установку GNU sort"
;;
esac
fi
fi
print_success "Зависимости установлены"
return 0
}
# ==============================================================================
# ШАГ 4: ЗАГРУЗКА МОДУЛЕЙ ЯДРА
# ==============================================================================
step_load_kernel_modules() {
print_header "Шаг 4/12: Загрузка модулей ядра"
local modules="xt_multiport xt_connbytes xt_NFQUEUE nfnetlink_queue"
for module in $modules; do
load_kernel_module "$module" || print_warning "Модуль $module не загружен"
done
# Load ip_set_bitmap_port from system modules (Entware modprobe cannot find it)
if ! lsmod | grep -q "ip_set_bitmap_port"; then
insmod "/lib/modules/$(uname -r)/ip_set_bitmap_port.ko" 2>/dev/null || true
fi
print_success "Модули ядра загружены"
return 0
}
# ==============================================================================
# ШАГ 5: УСТАНОВКА ZAPRET2 (ИСПОЛЬЗУЯ ОФИЦИАЛЬНЫЙ install_bin.sh)
# ==============================================================================
step_build_zapret2() {
print_header "Шаг 5/12: Установка zapret2"
# Сохранить пользовательские данные перед удалением
local backup_tmp="/tmp/z2k_upgrade_backup"
rm -rf "$backup_tmp"
if [ -d "$ZAPRET2_DIR" ]; then
print_info "Сохранение пользовательских настроек..."
mkdir -p "$backup_tmp"
# Config (содержит DROP_DPI_RST, RKN_SILENT_FALLBACK и др.)
[ -f "$ZAPRET2_DIR/config" ] && cp -f "$ZAPRET2_DIR/config" "$backup_tmp/config"
# Whitelist (пользовательские исключения)
[ -f "$ZAPRET2_DIR/lists/whitelist.txt" ] && cp -f "$ZAPRET2_DIR/lists/whitelist.txt" "$backup_tmp/whitelist.txt"
# Autocircular state (найденные рабочие стратегии)
[ -f "$ZAPRET2_DIR/extra_strats/cache/autocircular/state.tsv" ] && \
cp -f "$ZAPRET2_DIR/extra_strats/cache/autocircular/state.tsv" "$backup_tmp/state.tsv"
# Strategy.txt файлы
for cat_dir in TCP/YT TCP/YT_GV TCP/RKN UDP/YT; do
local sfile="$ZAPRET2_DIR/extra_strats/$cat_dir/Strategy.txt"
if [ -f "$sfile" ]; then
mkdir -p "$backup_tmp/strats/$cat_dir"
cp -f "$sfile" "$backup_tmp/strats/$cat_dir/Strategy.txt"
fi
done
# Silent fallback flag
[ -f "$ZAPRET2_DIR/extra_strats/cache/autocircular/rkn_silent_fallback.flag" ] && \
touch "$backup_tmp/rkn_silent_fallback.flag"
print_success "Настройки сохранены"
print_info "Удаление старой установки..."
rm -rf "$ZAPRET2_DIR"
print_success "Старая установка удалена"
fi
# Создать временную директорию
local build_dir="/tmp/zapret2_build"
rm -rf "$build_dir"
mkdir -p "$build_dir"
cd "$build_dir" || return 1
# ===========================================================================
# ШАГ 4.1: Скачать OpenWrt embedded релиз (содержит всё необходимое)
# ===========================================================================
print_info "Загрузка zapret2 OpenWrt embedded релиза..."
# GitHub API для получения последней версии
local api_url="https://api.github.com/repos/bol-van/zapret2/releases/latest"
local release_data
release_data=$(curl -fsSL --connect-timeout 10 --max-time 120 "$api_url" 2>&1)
local openwrt_url
if [ $? -ne 0 ]; then
print_warning "API недоступен, использую fallback версию v0.9.5..."
openwrt_url="https://github.com/bol-van/zapret2/releases/download/v0.9.5/zapret2-v0.9.5-openwrt-embedded.tar.gz"
else
# Парсим URL из JSON
openwrt_url=$(echo "$release_data" | grep -o 'https://github.com/bol-van/zapret2/releases/download/[^"]*openwrt-embedded\.tar\.gz' | head -1)
if [ -z "$openwrt_url" ]; then
print_warning "Не найден в API, использую fallback v0.9.5..."
openwrt_url="https://github.com/bol-van/zapret2/releases/download/v0.9.5/zapret2-v0.9.5-openwrt-embedded.tar.gz"
fi
fi
print_info "URL релиза: $openwrt_url"
# Скачать релиз
if ! curl -fsSL --connect-timeout 10 --max-time 120 "$openwrt_url" -o openwrt-embedded.tar.gz; then
print_error "Не удалось загрузить zapret2 OpenWrt embedded"
return 1
fi
print_success "Релиз загружен ($(du -h openwrt-embedded.tar.gz | cut -f1))"
# ===========================================================================
# ШАГ 4.2: Распаковать полную структуру релиза
# ===========================================================================
print_info "Распаковка релиза..."
tar -xzf openwrt-embedded.tar.gz || {
print_error "Ошибка распаковки архива"
return 1
}
# Найти корневую директорию релиза (zapret2-vX.Y.Z)
local release_dir
release_dir=$(find . -maxdepth 1 -type d -name "zapret2-v*" | head -1)
if [ -z "$release_dir" ] || [ ! -d "$release_dir" ]; then
print_error "Не найдена директория релиза в архиве"
ls -la
return 1
fi
print_success "Релиз распакован: $release_dir"
# ===========================================================================
# ШАГ 4.3: Использовать install_bin.sh для установки бинарников
# ===========================================================================
print_info "Определение архитектуры и установка бинарников..."
cd "$release_dir" || return 1
# Установить переменные окружения для install_bin.sh
export ZAPRET_BASE="$PWD"
# Проверить наличие install_bin.sh
if [ ! -f "install_bin.sh" ]; then
print_error "install_bin.sh не найден в релизе"
return 1
fi
# Вызвать install_bin.sh для автоматической установки бинарников
print_info "Запуск официального install_bin.sh..."
if sh install_bin.sh; then
print_success "Бинарники установлены через install_bin.sh"
else
print_error "install_bin.sh завершился с ошибкой"
print_info "Попытка ручной установки..."
# Fallback: ручная установка если install_bin.sh не сработал
local sys_arch entware_arch arch bin_arch opkg_bin
sys_arch=$(uname -m)
entware_arch=""
opkg_bin="opkg"
[ -x /opt/bin/opkg ] && opkg_bin="/opt/bin/opkg"
if command -v "$opkg_bin" >/dev/null 2>&1; then
entware_arch=$("$opkg_bin" print-architecture 2>/dev/null | awk '
$1 == "arch" && $2 != "all" {
prio = ($3 ~ /^[0-9]+$/) ? $3 + 0 : 0
if (prio >= max) { max = prio; arch = $2 }
}
END { if (arch != "") print arch }
')
fi
arch="${entware_arch:-$sys_arch}"
bin_arch=""
case "$arch" in
aarch64|arm64|*aarch64*|*arm64*) bin_arch="linux-arm64" ;;
armv7l|armv6l|arm|*armv7*|*armv6*|arm*) bin_arch="linux-arm" ;;
x86_64|amd64|*x86_64*|*amd64*) bin_arch="linux-x86_64" ;;
i386|i486|i586|i686|x86) bin_arch="linux-x86" ;;
*mipsel64*|*mips64el*) bin_arch="linux-mipsel" ;;
*mips64*) bin_arch="linux-mips64" ;;
*mipsel*) bin_arch="linux-mipsel" ;;
*mips*) bin_arch="linux-mips" ;;
*lexra*) bin_arch="linux-lexra" ;;
*ppc*) bin_arch="linux-ppc" ;;
*riscv64*) bin_arch="linux-riscv64" ;;
*)
print_error "Unsupported architecture: $arch (uname=$sys_arch${entware_arch:+, opkg=$entware_arch})"
return 1
;;
esac
print_info "Auto-detected architecture: uname=$sys_arch${entware_arch:+, opkg=$entware_arch} -> $bin_arch"
if [ ! -d "binaries/$bin_arch" ]; then
print_error "Бинарники для $bin_arch не найдены"
return 1
fi
# Создать директории и установить бинарники вручную
mkdir -p nfq2 ip2net mdig
cp "binaries/$bin_arch/nfqws2" nfq2/ || return 1
cp "binaries/$bin_arch/ip2net" ip2net/ || return 1
cp "binaries/$bin_arch/mdig" mdig/ || return 1
chmod +x nfq2/nfqws2 ip2net/ip2net mdig/mdig
print_success "Бинарники установлены вручную для $bin_arch"
fi
# Проверить что nfqws2 исполняемый и работает
if [ ! -x "nfq2/nfqws2" ]; then
print_error "nfqws2 не найден или не исполняемый после установки"
return 1
fi
# Проверить запуск
if ! ./nfq2/nfqws2 --version >/dev/null 2>&1; then
print_warning "nfqws2 не может быть запущен (возможно не та архитектура)"
print_info "Вывод --version:"
./nfq2/nfqws2 --version 2>&1 | head -5 || true
else
local version
version=$(./nfq2/nfqws2 --version 2>&1 | head -1)
print_success "nfqws2 работает: $version"
fi
# ===========================================================================
# ШАГ 4.4: Переместить в финальную директорию
# ===========================================================================
print_info "Установка в $ZAPRET2_DIR..."
cd "$build_dir" || return 1
cp -a "$release_dir" "$ZAPRET2_DIR" && rm -rf "$release_dir" || return 1
# ВАЖНО: Обновить ZAPRET_BASE на финальный путь (был /tmp/zapret2_build/...)
export ZAPRET_BASE="$ZAPRET2_DIR"
# ===========================================================================
# ШАГ 4.5: Добавить кастомные файлы из z2k репозитория
# ===========================================================================
print_info "Копирование дополнительных файлов..."
# Скопировать strats_new2.txt если есть в z2k репозитории
if [ -f "${WORK_DIR}/strats_new2.txt" ]; then
cp -f "${WORK_DIR}/strats_new2.txt" "${ZAPRET2_DIR}/" || \
print_warning "Не удалось скопировать strats_new2.txt"
fi
# Скопировать quic_strats.ini если есть
if [ -f "${WORK_DIR}/quic_strats.ini" ]; then
cp -f "${WORK_DIR}/quic_strats.ini" "${ZAPRET2_DIR}/" || \
print_warning "Не удалось скопировать quic_strats.ini"
fi
# Скопировать кастомные lua-хелперы z2k (например, персистентность autocircular)
if [ -d "${WORK_DIR}/files/lua" ]; then
mkdir -p "${ZAPRET2_DIR}/lua"
cp -f "${WORK_DIR}/files/lua/"*.lua "${ZAPRET2_DIR}/lua/" 2>/dev/null || true
fi
# Обновить fake blobs если есть более свежие в z2k
if [ -d "${WORK_DIR}/files/fake" ]; then
print_info "Updating fake blobs from z2k..."
cp -f "${WORK_DIR}/files/fake/"* "${ZAPRET2_DIR}/files/fake/" 2>/dev/null || true
fi
# Create blob aliases: strategies use short names, files have full names
local fakedir="${ZAPRET2_DIR}/files/fake"
if [ -d "$fakedir" ]; then
# tls_max_ru → tls_clienthello_max_ru.bin
[ ! -e "$fakedir/tls_max_ru" ] && [ -f "$fakedir/tls_clienthello_max_ru.bin" ] && \
ln -sf tls_clienthello_max_ru.bin "$fakedir/tls_max_ru"
# quic short aliases → quic_N.bin
[ ! -e "$fakedir/quic1" ] && [ -f "$fakedir/quic_1.bin" ] && ln -sf quic_1.bin "$fakedir/quic1"
[ ! -e "$fakedir/quic4" ] && [ -f "$fakedir/quic_4.bin" ] && ln -sf quic_4.bin "$fakedir/quic4"
[ ! -e "$fakedir/quic5" ] && [ -f "$fakedir/quic_5.bin" ] && ln -sf quic_5.bin "$fakedir/quic5"
[ ! -e "$fakedir/quic6" ] && [ -f "$fakedir/quic_6.bin" ] && ln -sf quic_6.bin "$fakedir/quic6"
# quic_google → quic_initial_google_com.bin
[ ! -e "$fakedir/quic_google" ] && [ -f "$fakedir/quic_initial_google_com.bin" ] && \
ln -sf quic_initial_google_com.bin "$fakedir/quic_google"
# quic_rutracker → quic_initial_rutracker_org.bin
[ ! -e "$fakedir/quic_rutracker" ] && [ -f "$fakedir/quic_initial_rutracker_org.bin" ] && \
ln -sf quic_initial_rutracker_org.bin "$fakedir/quic_rutracker"
fi
# Install blocked-monitor helper (runtime diagnostics for blocked domains).
if [ -f "${WORK_DIR}/files/z2k-blocked-monitor.sh" ]; then
cp -f "${WORK_DIR}/files/z2k-blocked-monitor.sh" "${ZAPRET2_DIR}/z2k-blocked-monitor.sh" 2>/dev/null || true
chmod +x "${ZAPRET2_DIR}/z2k-blocked-monitor.sh" 2>/dev/null || true
fi
# Install z2k tools (healthcheck, config validator, list updater)
for tool_script in z2k-healthcheck.sh z2k-config-validator.sh z2k-update-lists.sh; do
if [ -f "${WORK_DIR}/files/${tool_script}" ]; then
cp -f "${WORK_DIR}/files/${tool_script}" "${ZAPRET2_DIR}/${tool_script}" 2>/dev/null || true
chmod +x "${ZAPRET2_DIR}/${tool_script}" 2>/dev/null || true
print_info "Установлен: ${tool_script}"
fi
done
# Web panel is now installed on-demand via menu [P] → [1].
# Files live in webpanel/ in the repo and are copied to /tmp/z2k/webpanel
# by z2k.sh bootstrap; install.sh leaves the router filesystem alone.
# Copy snapshot domain lists for local install flow (no external list repos)
if [ -d "${WORK_DIR}/files/lists" ]; then
print_info "Copying snapshot domain lists..."
mkdir -p "${ZAPRET2_DIR}/files/lists"
cp -Rf "${WORK_DIR}/files/lists/"* "${ZAPRET2_DIR}/files/lists/" 2>/dev/null || true
# Strip CRLF from list files
find "${ZAPRET2_DIR}" -name "*.txt" -path "*/extra_strats/*" -exec sed -i 's/\r$//' {} + 2>/dev/null || true
fi
# Copy IP lists (Roblox, Telegram)
mkdir -p "${ZAPRET2_DIR}/lists"
for iplist in game_ips.txt roblox_ips.txt telegram_ips.txt ipset-exclude.txt; do
if [ -f "${WORK_DIR}/files/lists/${iplist}" ]; then
cp -f "${WORK_DIR}/files/lists/${iplist}" "${ZAPRET2_DIR}/lists/${iplist}" 2>/dev/null || true
fi
done
# Decompress lua.gz files (if any are shipped by embedded builds)
if [ -d "${ZAPRET2_DIR}/lua" ]; then
if command -v gzip >/dev/null 2>&1; then
for f in "${ZAPRET2_DIR}/lua/"*.lua.gz; do
[ -f "$f" ] || continue
local out="${f%.gz}"
print_info "Decompressing $(basename "$f")..."
if gzip -dc "$f" > "${out}.tmp" 2>/dev/null; then
mv -f "${out}.tmp" "$out"
rm -f "$f"
else
rm -f "${out}.tmp"
print_warning "Failed to decompress $f"
fi
done
else
print_warning "gzip not found, skipping lua.gz decompression"
fi
fi
# ===========================================================================
# ШАГ 4.6: Скачать locked.lua для circular_locked (Discord voice/video)
# ===========================================================================
print_info "Загрузка locked.lua для circular_locked..."
mkdir -p "${ZAPRET2_DIR}/lua"
mkdir -p "${ZAPRET2_DIR}/extra_strats/cache/orchestra"
mkdir -p "${ZAPRET2_DIR}/extra_strats/cache/autocircular"
chmod 755 "${ZAPRET2_DIR}/extra_strats/cache/autocircular" 2>/dev/null || true
chown nobody "${ZAPRET2_DIR}/extra_strats/cache/autocircular" 2>/dev/null || true
: > "${ZAPRET2_DIR}/extra_strats/cache/autocircular/state.tsv" 2>/dev/null || true
chmod 644 "${ZAPRET2_DIR}/extra_strats/cache/autocircular/state.tsv" 2>/dev/null || true
chown nobody "${ZAPRET2_DIR}/extra_strats/cache/autocircular/state.tsv" 2>/dev/null || true
: > "${ZAPRET2_DIR}/extra_strats/cache/autocircular/telemetry.tsv" 2>/dev/null || true
chmod 644 "${ZAPRET2_DIR}/extra_strats/cache/autocircular/telemetry.tsv" 2>/dev/null || true
chown nobody "${ZAPRET2_DIR}/extra_strats/cache/autocircular/telemetry.tsv" 2>/dev/null || true
# Debug is opt-in. Keep log file prepared, but do not enable verbose logging by default.
rm -f "${ZAPRET2_DIR}/extra_strats/cache/autocircular/debug.flag" 2>/dev/null || true
: > "${ZAPRET2_DIR}/extra_strats/cache/autocircular/debug.log" 2>/dev/null || true
chmod 644 "${ZAPRET2_DIR}/extra_strats/cache/autocircular/debug.log" 2>/dev/null || true
chown nobody "${ZAPRET2_DIR}/extra_strats/cache/autocircular/debug.log" 2>/dev/null || true
rm -f "${ZAPRET2_DIR}/extra_strats/cache/autocircular/state.tsv.lock" \
"${ZAPRET2_DIR}/extra_strats/cache/autocircular/state.tsv.tmp" \
"${ZAPRET2_DIR}/extra_strats/cache/autocircular/telemetry.tsv.lock" \
"${ZAPRET2_DIR}/extra_strats/cache/autocircular/telemetry.tsv.tmp" 2>/dev/null || true
# Fresh install/reinstall must not inherit stale fallback cache from /tmp.
rm -f /tmp/z2k-autocircular-state.tsv \
/tmp/z2k-autocircular-telemetry.tsv \
/tmp/z2k-autocircular-debug.flag \
/tmp/z2k-autocircular-debug.log 2>/dev/null || true
if z2k_fetch "https://raw.githubusercontent.com/AloofLibra/zapret4rocket/z2r/orchestra/locked.lua" \
"${ZAPRET2_DIR}/lua/locked.lua"; then
print_success "locked.lua загружен"
else
print_warning "Не удалось загрузить locked.lua (Discord voice может не работать)"
fi
print_info "Загрузка orchestrator.sh для управления circular_locked..."
if z2k_fetch "https://raw.githubusercontent.com/AloofLibra/zapret4rocket/z2r/orchestra/orchestrator.sh" \
"${ZAPRET2_DIR}/extra_strats/cache/orchestra/orchestrator.sh"; then
chmod +x "${ZAPRET2_DIR}/extra_strats/cache/orchestra/orchestrator.sh"
print_success "orchestrator.sh загружен"
else
print_warning "Не удалось загрузить orchestrator.sh (circular_locked будет без ротации)"
fi
# ===========================================================================
# Восстановление пользовательских данных после переустановки
# ===========================================================================
if [ -d "$backup_tmp" ]; then
print_info "Восстановление пользовательских настроек..."
# Восстановить config (содержит DROP_DPI_RST, RKN_SILENT_FALLBACK)
if [ -f "$backup_tmp/config" ]; then
cp -f "$backup_tmp/config" "$ZAPRET2_DIR/config"
print_success "Конфигурация восстановлена"
fi
# Восстановить whitelist
if [ -f "$backup_tmp/whitelist.txt" ]; then
mkdir -p "$ZAPRET2_DIR/lists"
cp -f "$backup_tmp/whitelist.txt" "$ZAPRET2_DIR/lists/whitelist.txt"
print_success "Whitelist восстановлен"
fi
# Восстановить autocircular state (рабочие стратегии)
if [ -f "$backup_tmp/state.tsv" ]; then
cp -f "$backup_tmp/state.tsv" "$ZAPRET2_DIR/extra_strats/cache/autocircular/state.tsv"
chown nobody "$ZAPRET2_DIR/extra_strats/cache/autocircular/state.tsv" 2>/dev/null || true
print_success "Стратегии autocircular восстановлены"
fi
# Восстановить Strategy.txt файлы
for cat_dir in TCP/YT TCP/YT_GV TCP/RKN UDP/YT; do
if [ -f "$backup_tmp/strats/$cat_dir/Strategy.txt" ]; then
mkdir -p "$ZAPRET2_DIR/extra_strats/$cat_dir"
cp -f "$backup_tmp/strats/$cat_dir/Strategy.txt" "$ZAPRET2_DIR/extra_strats/$cat_dir/Strategy.txt"
fi
done
print_success "Стратегии категорий восстановлены"
# Восстановить silent fallback flag
if [ -f "$backup_tmp/rkn_silent_fallback.flag" ]; then
touch "$ZAPRET2_DIR/extra_strats/cache/autocircular/rkn_silent_fallback.flag"
chown nobody "$ZAPRET2_DIR/extra_strats/cache/autocircular/rkn_silent_fallback.flag" 2>/dev/null || true
fi
rm -rf "$backup_tmp"
print_success "Все пользовательские настройки восстановлены"
fi
# ===========================================================================
# ШАГ 4.7: Установить custom.d скрипты (STUN + Discord media backup)
# ===========================================================================
print_info "Установка custom.d скриптов для STUN/Discord media..."
local custom_dir="${ZAPRET2_DIR}/init.d/keenetic/custom.d"
mkdir -p "$custom_dir"
if z2k_fetch "https://raw.githubusercontent.com/bol-van/zapret2/master/init.d/custom.d.examples.linux/50-stun4all" \
"${custom_dir}/50-stun4all"; then
chmod +x "${custom_dir}/50-stun4all"
print_success "50-stun4all установлен"
else
print_warning "Не удалось загрузить 50-stun4all"
fi
if z2k_fetch "https://raw.githubusercontent.com/bol-van/zapret2/master/init.d/custom.d.examples.linux/50-discord-media" \
"${custom_dir}/50-discord-media"; then
chmod +x "${custom_dir}/50-discord-media"
print_success "50-discord-media установлен"
else
print_warning "Не удалось загрузить 50-discord-media"
fi
# ===========================================================================
# ЗАВЕРШЕНИЕ
# ===========================================================================
# Очистка
cd / || return 1
rm -rf "$build_dir"
print_success "zapret2 установлен"
print_info "Структура:"
print_info " - Бинарники: nfq2/nfqws2, ip2net/ip2net, mdig/mdig"
print_info " - Lua библиотеки: lua/"
print_info " - Fake файлы: files/fake/"
print_info " - Модули: common/"
print_info " - Документация: docs/"
return 0
}
# ==============================================================================
# ШАГ 6: ПРОВЕРКА УСТАНОВКИ
# ==============================================================================
step_verify_installation() {
print_header "Шаг 6/12: Проверка установки"
# Проверить структуру директорий
local required_paths="
${ZAPRET2_DIR}
${ZAPRET2_DIR}/nfq2
${ZAPRET2_DIR}/nfq2/nfqws2
${ZAPRET2_DIR}/ip2net
${ZAPRET2_DIR}/mdig
${ZAPRET2_DIR}/lua
${ZAPRET2_DIR}/files
${ZAPRET2_DIR}/common
${ZAPRET2_DIR}/binaries
"
print_info "Проверка структуры директорий..."
local missing=0
for path in $required_paths; do
if [ -e "$path" ]; then
print_info "[OK] $path"
else
print_warning "[FAIL] $path не найден"
missing=$((missing + 1))
fi
done
if [ $missing -gt 0 ]; then
print_warning "Некоторые компоненты отсутствуют, но это может быть нормально"
fi
# Проверить все бинарники (установленные через install_bin.sh)
print_info "Проверка бинарников..."
# nfqws2 - основной бинарник
if [ -x "${ZAPRET2_DIR}/nfq2/nfqws2" ]; then
if verify_binary "${ZAPRET2_DIR}/nfq2/nfqws2"; then
print_success "[OK] nfqws2 работает"
else
print_error "[FAIL] nfqws2 не запускается"
return 1
fi
else
print_error "[FAIL] nfqws2 не найден или не исполняемый"
return 1
fi
# ip2net - вспомогательный (может быть симлинком)
if [ -e "${ZAPRET2_DIR}/ip2net/ip2net" ]; then
print_info "[OK] ip2net установлен"
else
print_warning "[FAIL] ip2net не найден (необязательный)"
fi
# mdig - DNS утилита (может быть симлинком)
if [ -e "${ZAPRET2_DIR}/mdig/mdig" ]; then
print_info "[OK] mdig установлен"
else
print_warning "[FAIL] mdig не найден (необязательный)"
fi
# Посчитать компоненты
print_info "Статистика компонентов:"
# Lua файлы
if [ -d "${ZAPRET2_DIR}/lua" ]; then
local lua_count
lua_count=$(find "${ZAPRET2_DIR}/lua" -name "*.lua" 2>/dev/null | wc -l)
print_info " - Lua файлов: $lua_count"
fi
# Fake файлы
if [ -d "${ZAPRET2_DIR}/files/fake" ]; then
local fake_count
fake_count=$(find "${ZAPRET2_DIR}/files/fake" -name "*.bin" 2>/dev/null | wc -l)
print_info " - Fake файлов: $fake_count"
fi
# Модули common/
if [ -d "${ZAPRET2_DIR}/common" ]; then
local common_count
common_count=$(find "${ZAPRET2_DIR}/common" -name "*.sh" 2>/dev/null | wc -l)
print_info " - Модули common/: $common_count"
fi
# install_bin.sh присутствует?
if [ -f "${ZAPRET2_DIR}/install_bin.sh" ]; then
print_info " - install_bin.sh: установлен"
fi
print_success "Установка проверена успешно"
return 0
}
# ==============================================================================
# ШАГ 7: ОПРЕДЕЛЕНИЕ ТИПА FIREWALL (КРИТИЧНО)
# ==============================================================================
step_check_and_select_fwtype() {
print_header "Шаг 7/12: Определение типа firewall"
print_info "Автоопределение типа firewall системы..."
# ВАЖНО: Загрузить base.sh ПЕРЕД fwtype.sh, т.к. нужна функция exists()
if [ -f "${ZAPRET2_DIR}/common/base.sh" ]; then
. "${ZAPRET2_DIR}/common/base.sh"
else
print_error "Модуль base.sh не найден в ${ZAPRET2_DIR}/common/"
return 1
fi
# Source модуль fwtype из zapret2
if [ -f "${ZAPRET2_DIR}/common/fwtype.sh" ]; then
. "${ZAPRET2_DIR}/common/fwtype.sh"
else
print_error "Модуль fwtype.sh не найден в ${ZAPRET2_DIR}/common/"
return 1
fi
# ВАЖНО: Восстановить Z2K путь к init скрипту (он перезаписывается модулями zapret2)
INIT_SCRIPT="$Z2K_INIT_SCRIPT"
# Переопределить linux_ipt_avail для Keenetic (IPv4-only режим).
# Официальная функция требует iptables И ip6tables, но Keenetic с
# DISABLE_IPV6=1 не имеет ip6tables, поэтому проверяем только iptables.
# Не использовать upstream `exists` — она зависит от $PATH в момент
# установки, а на Keenetic под root часто нет /usr/sbin в PATH, и
# iptables «не находится», хотя физически на месте. Сергей @innotcommercial
# 2026-04-18: linux_fwtype вернул "unsupported", хотя iptables работал.
linux_ipt_avail()
{
[ -x /usr/sbin/iptables ] || [ -x /sbin/iptables ] || \
[ -x /opt/sbin/iptables ] || command -v iptables >/dev/null 2>&1
}
# Автоопределение через функцию из zapret2
linux_fwtype
# linux_fwtype может вернуть "unsupported" если ни iptables, ни nftables
# не нашлись (как раз случай с битым PATH выше). Старая проверка ловила
# только пустую строку — добавлено явное "unsupported".
if [ -z "$FWTYPE" ] || [ "$FWTYPE" = "unsupported" ]; then
print_warning "linux_fwtype вернул '$FWTYPE' — используем fallback iptables"
FWTYPE="iptables"
fi
print_success "Обнаружен firewall: $FWTYPE"
# Показать информацию
case "$FWTYPE" in
iptables)
print_info "iptables - традиционный firewall Linux"
print_info "Keenetic обычно использует iptables"
;;
nftables)
print_info "nftables - современный firewall Linux (kernel 3.13+)"
print_info "Более эффективен чем iptables"
;;
*)
print_warning "Неизвестный тип firewall: $FWTYPE"
;;
esac
# Записать FWTYPE в config файл (если он уже существует)
local config="${ZAPRET2_DIR}/config"
if [ -f "$config" ]; then
# Проверить есть ли уже FWTYPE в config
if grep -q "^#*FWTYPE=" "$config"; then
# Обновить существующую строку
sed -i "s|^#*FWTYPE=.*|FWTYPE=$FWTYPE|" "$config"
print_info "FWTYPE=$FWTYPE записан в config"
else
# Добавить в конец FIREWALL SETTINGS секции
sed -i "/# FIREWALL SETTINGS/a FWTYPE=$FWTYPE" "$config"
print_info "FWTYPE=$FWTYPE добавлен в config"
fi
else
print_info "Config файл ещё не создан, FWTYPE будет установлен позже"
fi
# Экспортировать для использования в других функциях
export FWTYPE
return 0
}
# ==============================================================================
# ШАГ 8: ЗАГРУЗКА СПИСКОВ ДОМЕНОВ
# ==============================================================================
step_download_domain_lists() {
print_header "Шаг 8/12: Загрузка списков доменов"
# Использовать функцию из lib/config.sh
download_domain_lists || {
print_error "Не удалось загрузить списки доменов"
return 1
}
# Доп. проверка: список QUIC YT (zapret4rocket)
local yt_quic_list="/opt/zapret2/extra_strats/UDP/YT/List.txt"
if [ ! -s "$yt_quic_list" ]; then
print_warning "QUIC YT list not found after local snapshot copy: $yt_quic_list"
print_warning "Install snapshot files first (files/lists/extra_strats/UDP/YT/List.txt)"
fi
create_base_config || {
print_error "Не удалось создать конфигурацию"
return 1
}
print_success "Списки доменов и конфигурация установлены"
return 0
}
# ==============================================================================
# ШАГ 9: ОТКЛЮЧЕНИЕ HARDWARE NAT
# ==============================================================================
step_disable_hwnat_and_offload() {
print_header "Шаг 9/12: Отключение Hardware NAT и Flow Offloading"
# =========================================================================
# 9.1: Hardware NAT (fastnat на Keenetic)
# =========================================================================
print_info "Проверка Hardware NAT (fastnat)..."
# Проверить наличие системы управления HWNAT
if [ -f "/sys/kernel/fastnat/mode" ]; then
local current_mode
current_mode=$(cat /sys/kernel/fastnat/mode 2>/dev/null || echo "unknown")
print_info "Текущий режим fastnat: $current_mode"
if [ "$current_mode" != "0" ] && [ "$current_mode" != "unknown" ]; then
print_warning "Hardware NAT включен - может конфликтовать с DPI bypass"
# Попытка отключения
if echo 0 > /sys/kernel/fastnat/mode 2>/dev/null; then
print_success "Hardware NAT отключен"
else
print_warning "Не удалось отключить Hardware NAT"
print_info "Возможно требуются дополнительные права"
print_info "Попробуйте вручную: echo 0 > /sys/kernel/fastnat/mode"
fi
else
print_success "Hardware NAT уже отключен или недоступен"
fi
else
print_info "Hardware NAT (fastnat) не обнаружен на этой системе"
fi
# =========================================================================
# 9.2: Flow Offloading (критично для nfqws)
# =========================================================================
print_separator
print_info "Проверка Flow Offloading..."
# На Keenetic flow offloading управляется через другие механизмы
# В основном через iptables/nftables правила
# Проверка через sysctl (если доступно)
if [ -f "/proc/sys/net/netfilter/nf_conntrack_tcp_be_liberal" ]; then
print_info "Проверка conntrack liberal mode..."
# zapret2 может требовать liberal mode для обработки invalid RST пакетов
local liberal_mode
liberal_mode=$(cat /proc/sys/net/netfilter/nf_conntrack_tcp_be_liberal 2>/dev/null || echo "0")
if [ "$liberal_mode" = "0" ]; then
print_info "conntrack liberal mode выключен (будет включен при старте zapret2)"
else
print_info "conntrack liberal mode уже включен"
fi
fi
# Записать FLOWOFFLOAD=none в config (безопасный вариант)
print_info "Установка FLOWOFFLOAD=none в config (рекомендуется для Keenetic)"
# Это будет использовано при создании config файла
export FLOWOFFLOAD=none
print_separator
print_info "Информация о flow offloading:"
print_info " - Flow offloading ускоряет routing но может ломать DPI bypass"
print_info " - nfqws трафик ДОЛЖЕН быть исключен из offloading"
print_info " - На Keenetic используется FLOWOFFLOAD=none (безопасно)"
print_info " - Официальный init скрипт автоматически настроит exemption rules"
print_success "Hardware NAT и Flow Offloading проверены"
return 0
}
# ==============================================================================
# ШАГ 9.5: НАСТРОЙКА TMPDIR ДЛЯ LOW RAM СИСТЕМ
# ==============================================================================
step_configure_tmpdir() {
print_header "Шаг 9.5/12: Настройка TMPDIR для low RAM систем"
# Получить объём RAM. /proc/meminfo — основной путь (есть всегда на
# Linux и не зависит от того, что в нём определил upstream zapret).
# Раньше первичным был get_ram_mb из common/base.sh, но в части upstream-
# сборок этой функции нет, и установка падала с
# "sh: get_ram_mb: not found" + "Обнаружено RAM: MB" + "sh: bad number"
# (Сергей @innotcommercial 2026-04-18, master).
local ram_mb=""
if [ -r /proc/meminfo ]; then
ram_mb=$(awk '/^MemTotal:/ {print int($2/1024); exit}' /proc/meminfo)
fi
# Fallback на upstream get_ram_mb, если /proc/meminfo не дался
if [ -z "$ram_mb" ] && [ -f "${ZAPRET2_DIR}/common/base.sh" ]; then
_saved_linux_ipt_avail="$(type linux_ipt_avail 2>/dev/null)"
. "${ZAPRET2_DIR}/common/base.sh"
if [ -n "$_saved_linux_ipt_avail" ]; then
linux_ipt_avail() { true; }
fi
if command -v get_ram_mb >/dev/null 2>&1; then
ram_mb=$(get_ram_mb)
fi
fi
# Если оба пути не сработали — предполагаем достаточно RAM
[ -z "$ram_mb" ] && ram_mb=999
print_info "Обнаружено RAM: ${ram_mb}MB"
# АВТОМАТИЧЕСКИЙ выбор TMPDIR на основе RAM
if [ "$ram_mb" -le 400 ]; then
print_warning "Low RAM система - используем диск для временных файлов"
local disk_tmpdir="/opt/zapret2/tmp"
# Создать директорию
mkdir -p "$disk_tmpdir" || {
print_error "Не удалось создать $disk_tmpdir"
return 1
}
export TMPDIR="$disk_tmpdir"
print_success "TMPDIR установлен: $disk_tmpdir (защита от OOM)"
# Проверить свободное место на диске
if command -v df >/dev/null 2>&1; then
local free_mb
free_mb=$(df -m "$disk_tmpdir" | tail -1 | awk '{print $4}')
print_info "Свободно на диске: ${free_mb}MB"
if [ "$free_mb" -lt 200 ]; then
print_warning "Мало свободного места (<200MB)"
fi
fi
else
print_success "Достаточно RAM (${ram_mb}MB) - используем /tmp (быстрее)"
export TMPDIR=""
fi
return 0
}
# ==============================================================================
# ШАГ 10: СОЗДАНИЕ ОФИЦИАЛЬНОГО CONFIG И INIT СКРИПТА
# ==============================================================================
step_create_config_and_init() {
print_header "Шаг 10/12: Создание config и init скрипта"
# ========================================================================
# 10.0: Создать дефолтные файлы стратегий
# ========================================================================
# Source функции для работы со стратегиями
. "${LIB_DIR}/strategies.sh" || {
print_error "Не удалось загрузить strategies.sh"
return 1
}
# Создать директории и дефолтные файлы стратегий
create_default_strategy_files || {
print_error "Не удалось создать файлы стратегий"
return 1
}
# ========================================================================
# 10.1: Создать официальный config файл
# ========================================================================
print_info "Создание официального config файла..."
local zapret_config="${ZAPRET2_DIR}/config"
# Source функции для генерации config
. "${LIB_DIR}/config_official.sh" || {
print_error "Не удалось загрузить config_official.sh"
return 1
}
# Создать config файл (с автогенерацией NFQWS2_OPT из стратегий)
create_official_config "$zapret_config" || {
print_error "Не удалось создать config файл"
return 1
}
print_success "Config файл создан: $zapret_config"
# ========================================================================
# 8.2: Установить новый init скрипт
# ========================================================================
print_info "Установка init скрипта..."
# Создать директорию если не существует
mkdir -p "$(dirname "$INIT_SCRIPT")"
# Скопировать init скрипт из дистрибутива
print_info "Копирование init скрипта..."
if [ -f "${WORK_DIR}/files/S99zapret2.new" ]; then
cp -f "${WORK_DIR}/files/S99zapret2.new" "$INIT_SCRIPT" || {
print_error "Не удалось скопировать init скрипт"
return 1
}
else
print_error "Init скрипт не найден: ${WORK_DIR}/files/S99zapret2.new"
return 1
fi
chmod +x "$INIT_SCRIPT" || {
print_error "Не удалось установить права на init скрипт"
return 1
}
print_success "Init скрипт установлен: $INIT_SCRIPT"
# Показать информацию о новом подходе
print_info "Init скрипт использует:"
print_info " - Модули из $ZAPRET2_DIR/common/"
print_info " - Config файл: $zapret_config"
print_info " - Стратегии из config (config-driven, не hardcoded)"
print_info " - PID файлы для graceful shutdown"
print_info " - Разделение firewall/daemons"
return 0
}
# ==============================================================================
# ШАГ 11: УСТАНОВКА NETFILTER ХУКА
# ==============================================================================
step_install_netfilter_hook() {
print_header "Шаг 11/12: Установка netfilter хука"
print_info "Установка хука для автоматического восстановления правил..."
# Создать директорию для NDM хуков
local hook_dir="/opt/etc/ndm/netfilter.d"
mkdir -p "$hook_dir" || {
print_error "Не удалось создать $hook_dir"
return 1
}
local hook_file="${hook_dir}/000-zapret2.sh"
# Скопировать хук из files/
if [ -f "${WORK_DIR}/files/000-zapret2.sh" ]; then
cp "${WORK_DIR}/files/000-zapret2.sh" "$hook_file" || {
print_error "Не удалось скопировать хук"
return 1
}
else
print_warning "Файл хука не найден в ${WORK_DIR}/files/"
print_info "Создание хука вручную..."
# Создать хук напрямую
cat > "$hook_file" <<'HOOK'
#!/bin/sh
# Keenetic NDM netfilter hook для автоматического восстановления правил zapret2
# Вызывается при изменениях в netfilter (iptables)
INIT_SCRIPT="/opt/etc/init.d/S99zapret2"
ZAPRET_CONFIG="/opt/zapret2/config"
# Обрабатываем только изменения в таблицах mangle/nat.
# zapret2 использует mangle (NFQUEUE), но Keenetic при переподключении может дергать hook и на nat.
[ "$table" != "mangle" ] && [ "$table" != "nat" ] && exit 0
# Проверить что init скрипт существует
[ ! -f "$INIT_SCRIPT" ] && exit 0
# Проверить что zapret2 включен (ENABLED=1 в конфиге)
if ! grep -q "^ENABLED=1" "$ZAPRET_CONFIG" 2>/dev/null; then
exit 0
fi
# Не восстанавливать NFQUEUE-правила, если nfqws2 не запущен.
# Иначе трафик может уйти в очередь без потребителя.
is_nfqws2_running() {
if command -v pidof >/dev/null 2>&1; then
pidof nfqws2 >/dev/null 2>&1 && return 0
fi
# Fallback: check common pidfile locations (our init uses nfqws2_*.pid).
for pidfile in /opt/var/run/nfqws2_*.pid /opt/var/run/nfqws2.pid; do
[ -f "$pidfile" ] || continue
pid="$(cat "$pidfile" 2>/dev/null)"
[ -n "$pid" ] || continue
kill -0 "$pid" 2>/dev/null && return 0
done
return 1
}
is_nfqws2_running || exit 0
# Небольшая задержка для стабильности
sleep 2
# Восстановить только firewall-правила (НЕ restart!)
# restart убивает nfqws2, обнуляя Lua-состояние autocircular (per-domain стратегии).
# restart_fw пересоздаёт только NFQUEUE правила в mangle, демоны продолжают работу.
"$INIT_SCRIPT" restart_fw >/dev/null 2>&1 &
exit 0
HOOK
fi
# Сделать исполняемым
chmod +x "$hook_file" || {
print_error "Не удалось установить права на хук"
return 1
}
print_success "Netfilter хук установлен: $hook_file"
print_info "Хук будет восстанавливать правила при переподключении интернета"
return 0
}
# ==============================================================================
# ШАГ 12: ФИНАЛИЗАЦИЯ
# ==============================================================================
step_finalize() {
print_header "Шаг 12/12: Финализация установки"
# Проверить бинарник перед запуском
print_info "Проверка nfqws2 перед запуском..."
if [ ! -x "${ZAPRET2_DIR}/nfq2/nfqws2" ]; then
print_error "nfqws2 не найден или не исполняемый"
return 1
fi
# Проверить зависимости бинарника (если ldd доступен)
if command -v ldd >/dev/null 2>&1; then
print_info "Проверка библиотек..."
if ldd "${ZAPRET2_DIR}/nfq2/nfqws2" 2>&1 | grep -q "not found"; then
print_warning "Отсутствуют некоторые библиотеки:"
ldd "${ZAPRET2_DIR}/nfq2/nfqws2" | grep "not found"
else
print_success "Все библиотеки найдены"
fi
fi
# Попробовать запустить напрямую для диагностики
print_info "Тест запуска nfqws2..."
local version_output
version_output=$("${ZAPRET2_DIR}/nfq2/nfqws2" --version 2>&1 | head -1)
if echo "$version_output" | grep -q "github version"; then
print_success "nfqws2 исполняется корректно: $version_output"
else
print_error "nfqws2 не может быть запущен"
print_info "Вывод ошибки:"
"${ZAPRET2_DIR}/nfq2/nfqws2" --version 2>&1 | head -10
return 1
fi
# =========================================================================
# НАСТРОЙКА АВТООБНОВЛЕНИЯ СПИСКОВ ДОМЕНОВ (КРИТИЧНО)
# =========================================================================
print_separator
print_info "Настройка автообновления списков доменов..."
# На Keenetic (Entware) cron работает через /opt/etc/crontab
# НЕ используем installer.sh из zapret2 — он ищет /etc/init.d/cron (OpenWrt)
local cron_line="0 6 * * * ZAPRET_BASE=${ZAPRET2_DIR} ${ZAPRET2_DIR}/ipset/get_config.sh"
local crontab_file="/opt/etc/crontab"
local cron_ok=0
# cron устанавливается в step_install_dependencies
# Метод 1: /opt/etc/crontab (Entware cron)
if [ -f "$crontab_file" ] || [ -d "/opt/etc" ]; then
# Удалить старые записи zapret2
if [ -f "$crontab_file" ]; then
grep -v "get_config.sh" "$crontab_file" > "${crontab_file}.tmp" 2>/dev/null
mv "${crontab_file}.tmp" "$crontab_file"
fi
# Добавить новую задачу
echo "$cron_line" >> "$crontab_file"
print_success "Автообновление настроено в $crontab_file (ежедневно в 06:00)"
cron_ok=1
fi
# Метод 2: crontab -l / crontab - (если Entware crontab не работает)
if [ "$cron_ok" = "0" ] && command -v crontab >/dev/null 2>&1; then
# Удалить старые записи zapret2 и добавить новую
(crontab -l 2>/dev/null | grep -v "get_config.sh"; echo "$cron_line") | crontab -
if [ $? -eq 0 ]; then
print_success "Автообновление настроено через crontab (ежедневно в 06:00)"
cron_ok=1
fi
fi
if [ "$cron_ok" = "0" ]; then
print_warning "Не удалось настроить crontab"
print_info "Списки нужно будет обновлять вручную:"
print_info " ZAPRET_BASE=${ZAPRET2_DIR} ${ZAPRET2_DIR}/ipset/get_config.sh"
fi
# Запустить cron демон если есть init скрипт Entware
local cron_init="/opt/etc/init.d/S10cron"
if [ -x "$cron_init" ]; then
"$cron_init" start >/dev/null 2>&1
if pgrep -f "cron" >/dev/null 2>&1; then
print_info "Cron демон запущен"
else
print_warning "Не удалось запустить cron демон"
fi
elif pgrep -f "cron" >/dev/null 2>&1; then
print_info "Cron демон уже запущен"
else
print_warning "Cron демон не найден"
print_info "Установите: opkg install cron"
fi
# Instagram DNS redirect (Keenetic static DNS)
# Прописывает рабочие IP для Instagram если записи ещё не заданы.
# Решает проблему DNS-отравления провайдером.
if command -v ndmc >/dev/null 2>&1; then
if ! ndmc -c "show running-config" 2>/dev/null | grep -q "ip host instagram.com"; then
print_info "Настройка DNS для Instagram..."
ndmc -c "ip host instagram.com 157.240.251.174" 2>/dev/null
ndmc -c "ip host www.instagram.com 157.240.9.174" 2>/dev/null
ndmc -c "ip host graph.instagram.com 157.240.0.63" 2>/dev/null
ndmc -c "ip host api.instagram.com 157.240.253.63" 2>/dev/null
ndmc -c "ip host instagram.c10r.instagram.com 157.240.214.63" 2>/dev/null
ndmc -c "ip host static.cdninstagram.com 163.70.147.63" 2>/dev/null
ndmc -c "ip host scontent.cdninstagram.com 163.70.147.63" 2>/dev/null
ndmc -c "ip host instagram.com 157.240.9.174" 2>/dev/null
ndmc -c "ip host static.cdninstagram.com 57.144.112.192" 2>/dev/null
ndmc -c "ip host scontent.cdninstagram.com 57.144.112.192" 2>/dev/null
ndmc -c "system configuration save" 2>/dev/null
print_success "DNS записи для Instagram добавлены"
else
print_info "DNS записи для Instagram уже настроены"
fi
fi
# Telegram transparent proxy (tg-mtproxy-client)
if true; then
print_info "Установка/обновление Telegram прокси..."
# Use same arch detection as zapret2 install (get_arch → map_arch_to_bin_arch)
local tg_arch=""
local hw_arch
hw_arch=$(get_arch 2>/dev/null || uname -m)
local tg_bin_arch
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_dest="/opt/sbin/tg-mtproxy-client"
local tg_url="${GITHUB_RAW}/mtproxy-client/builds/${tg_bin}"
rm -f "$tg_dest"
curl -fsSL --connect-timeout 10 --max-time 120 "$tg_url" -o "$tg_dest" 2>/dev/null
local tg_size
tg_size=$(wc -c < "$tg_dest" 2>/dev/null || echo 0)
# Validate: exists, >500KB, starts with ELF magic (\x7fELF), runs without crash
local tg_valid=false
if [ -f "$tg_dest" ] && [ "$tg_size" -gt 500000 ] 2>/dev/null; then
# Check ELF magic (works on any busybox — no od/hexdump needed)
if head -c 4 "$tg_dest" 2>/dev/null | grep -q "ELF"; then
chmod +x "$tg_dest"
# Test run — if wrong arch, kernel will fail and exit non-zero
if "$tg_dest" --help 2>/dev/null; [ $? -le 2 ]; then
tg_valid=true
fi
fi
fi
if $tg_valid; then
print_success "Telegram прокси установлен ($tg_arch)"
else
rm -f "$tg_dest"
if [ "$tg_size" -le 500000 ] 2>/dev/null; then
print_warning "Файл слишком маленький (${tg_size} байт) — скачивание прервалось"
else
print_warning "Бинарник не запускается на этой архитектуре ($tg_arch). Проверьте: opkg print-architecture"
fi
fi
else
print_warning "Неизвестная архитектура $hw_arch, пропускаем Telegram прокси"
fi
else
print_info "Telegram прокси уже установлен"
fi
# Cleanup legacy WS proxy init script (replaced by tunnel)
rm -f /opt/etc/init.d/S97tg-mtproxy 2>/dev/null
# Auto-start Telegram tunnel
if [ -x "/opt/sbin/tg-mtproxy-client" ]; then
killall tg-mtproxy-client 2>/dev/null || true
sleep 1
# Start tunnel mode. -v enables stream-level logs needed by the
# watchdog's stale-detection mode.
/opt/sbin/tg-mtproxy-client --listen=:1443 --timeout=15m -v >> /tmp/tg-tunnel.log 2>&1 &
sleep 2
if pgrep -f "tg-mtproxy-client" >/dev/null 2>&1; then
# Setup iptables REDIRECT for Telegram DC IPs.
# Use -I ... 1 (insert at top) so our rules precede Keenetic's
# _NDM_* chains, which intercept packets when using -A.
# Both PREROUTING (LAN clients) and OUTPUT (router-local
# processes, e.g. the watchdog probe) get the redirect.
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 -I PREROUTING 1 -d "$cidr" -p tcp --dport 443 -j REDIRECT --to-port 1443 2>/dev/null
iptables -t nat -C OUTPUT -d "$cidr" -p tcp --dport 443 -j REDIRECT --to-port 1443 2>/dev/null || \
iptables -t nat -I OUTPUT 1 -d "$cidr" -p tcp --dport 443 -j REDIRECT --to-port 1443 2>/dev/null
done
# Install Keenetic netfilter.d hook so NDM re-inserts our
# REDIRECT rules automatically after every regen (WAN flap,
# tunnel up/down, reboot, etc). Without this, rules get
# silently wiped and Android Telegram (which doesn't use
# MTProxy Premium like desktop does) stops connecting.
mkdir -p /opt/etc/ndm/netfilter.d
if [ -f "${WORK_DIR}/files/ndm/90-z2k-tg-redirect.sh" ]; then
cp -f "${WORK_DIR}/files/ndm/90-z2k-tg-redirect.sh" \
/opt/etc/ndm/netfilter.d/90-z2k-tg-redirect.sh
chmod +x /opt/etc/ndm/netfilter.d/90-z2k-tg-redirect.sh
print_success "Keenetic NDM hook установлен (auto-restore iptables)"
fi
# Install watchdog — active end-to-end probe + CONNECT_FAIL storm
# detector. Runs every minute via cron. Restarts the tunnel via
# the init script (handles iptables + pid file properly).
cat > /opt/zapret2/tg-tunnel-watchdog.sh << 'WDSCRIPT'
#!/bin/sh
# tg-tunnel watchdog
# 1. Restart on CONNECT_FAIL storm (legacy passive check)
# 2. Restart when an end-to-end HTTPS probe through the tunnel fails 3x
# in a row. The probe targets a Telegram-owned IP that is REDIRECTed
# to local :1443, so the request transits the tunnel. Catches the
# "tunnel process alive but silently dead after WS reconnect" mode
# that the CONNECT_FAIL detector misses entirely.
LOG=/tmp/tg-tunnel.log
BIN=/opt/sbin/tg-mtproxy-client
INIT=/opt/etc/init.d/S98tg-tunnel
STATE=/tmp/tg-tunnel-watchdog.state
PROBE_URL=https://core.telegram.org/
[ -x "$BIN" ] || exit 0
restart_tunnel() {
local reason="$1"
logger -t tg-watchdog "restart: $reason"
if [ -x "$INIT" ]; then
"$INIT" stop >/dev/null 2>&1
sleep 1
# belt-and-suspenders kill in case init script left a leftover
killall -9 tg-mtproxy-client 2>/dev/null
sleep 1
# Truncate log to exactly one marker line. Without this, the very
# CONNECT_FAIL storm that triggered the restart is still sitting in
# tail -40 when cron runs again in a minute, and the detector fires
# a second restart before the new session has time to stabilise —
# classic restart loop.
echo "$(date) watchdog restart: $reason" > "$LOG"
"$INIT" start >/dev/null 2>&1
else
killall -9 tg-mtproxy-client 2>/dev/null
sleep 1
echo "$(date) watchdog restart: $reason" > "$LOG"
"$BIN" --listen=:1443 -v >> "$LOG" 2>&1 &
fi
echo 0 > "$STATE"
}
# 1) CONNECT_FAIL storm (legacy)
if [ -f "$LOG" ] && pgrep -f "tg-mtproxy-client" >/dev/null 2>&1; then
FAILS=$(tail -40 "$LOG" 2>/dev/null | grep -c "CONNECT_FAIL")
if [ "$FAILS" -ge 10 ]; then
restart_tunnel "CONNECT_FAIL storm ($FAILS in last 40 lines)"
exit 0
fi
fi
# If the binary isn't running at all, just start it and reset state.
if ! pgrep -f "tg-mtproxy-client" >/dev/null 2>&1; then
restart_tunnel "tunnel process not running"
exit 0
fi
# 2) Active end-to-end probe through the tunnel.
# core.telegram.org resolves into 149.154.0.0/16, which our PREROUTING
# REDIRECT bounces to 127.0.0.1:1443 → tg-mtproxy-client → cf worker →
# Telegram. Successful TLS + HTTP response = full path is healthy.
if curl --connect-timeout 8 --max-time 15 -sf -o /dev/null "$PROBE_URL" 2>/dev/null; then
echo 0 > "$STATE"
exit 0
fi
# Probe failed — increment consecutive-failure counter.
FAIL_CNT=0
[ -f "$STATE" ] && FAIL_CNT=$(head -1 "$STATE" 2>/dev/null)
case "$FAIL_CNT" in ''|*[!0-9]*) FAIL_CNT=0 ;; esac
FAIL_CNT=$((FAIL_CNT + 1))
echo "$FAIL_CNT" > "$STATE"
# Restart only after 3 consecutive failures (~3 minutes) to avoid
# flapping when Telegram itself or the upstream worker has a brief blip.
if [ "$FAIL_CNT" -ge 3 ]; then
restart_tunnel "active probe failed ${FAIL_CNT}x in a row"
fi
WDSCRIPT
chmod +x /opt/zapret2/tg-tunnel-watchdog.sh
# Add to cron (every minute)
WDCRON="* * * * * /opt/zapret2/tg-tunnel-watchdog.sh"
crontab -l 2>/dev/null | grep -q "tg-tunnel-watchdog" || \
(crontab -l 2>/dev/null; echo "$WDCRON") | crontab -
# Install init script for autostart on reboot
cat > /opt/etc/init.d/S98tg-tunnel << 'INITEOF'
#!/bin/sh
BIN="/opt/sbin/tg-mtproxy-client"
LOG="/tmp/tg-tunnel.log"
PIDFILE="/var/run/tg-tunnel.pid"
CIDRS="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"
start() {
[ -x "$BIN" ] || exit 0
if pgrep -f "tg-mtproxy-client" >/dev/null 2>&1; then
echo "tg-tunnel already running"
return 0
fi
echo "Starting tg-tunnel..."
$BIN --listen=:1443 --timeout=15m -v >> "$LOG" 2>&1 &
echo $! > "$PIDFILE"
sleep 2
# Insert REDIRECT rules at TOP of both PREROUTING and OUTPUT (-I 1) so
# they precede Keenetic's _NDM_* chains. PREROUTING catches forwarded
# traffic from LAN clients; OUTPUT catches locally-originated traffic
# from the router itself (e.g. the watchdog probe).
for cidr in $CIDRS; do
iptables -t nat -C PREROUTING -d "$cidr" -p tcp --dport 443 -j REDIRECT --to-port 1443 2>/dev/null || \
iptables -t nat -I PREROUTING 1 -d "$cidr" -p tcp --dport 443 -j REDIRECT --to-port 1443 2>/dev/null
iptables -t nat -C OUTPUT -d "$cidr" -p tcp --dport 443 -j REDIRECT --to-port 1443 2>/dev/null || \
iptables -t nat -I OUTPUT 1 -d "$cidr" -p tcp --dport 443 -j REDIRECT --to-port 1443 2>/dev/null
done
}
stop() {
echo "Stopping tg-tunnel..."
killall tg-mtproxy-client 2>/dev/null
rm -f "$PIDFILE"
# Idempotent removal: drop every matching copy of the rule from both chains.
for cidr in $CIDRS; do
while iptables -t nat -C PREROUTING -d "$cidr" -p tcp --dport 443 -j REDIRECT --to-port 1443 2>/dev/null; do
iptables -t nat -D PREROUTING -d "$cidr" -p tcp --dport 443 -j REDIRECT --to-port 1443 2>/dev/null || break
done
while iptables -t nat -C OUTPUT -d "$cidr" -p tcp --dport 443 -j REDIRECT --to-port 1443 2>/dev/null; do
iptables -t nat -D OUTPUT -d "$cidr" -p tcp --dport 443 -j REDIRECT --to-port 1443 2>/dev/null || break
done
done
}
case "$1" in
start) start ;;
stop) stop ;;
restart) stop; sleep 1; start ;;
*) echo "Usage: $0 {start|stop|restart}" ;;
esac
INITEOF
chmod +x /opt/etc/init.d/S98tg-tunnel
# Cleanup legacy cron entry for S97tg-mtproxy
crontab -l 2>/dev/null | grep -v "S97tg-mtproxy" | crontab - 2>/dev/null
print_success "Telegram tunnel запущен автоматически"
else
print_warning "Не удалось запустить Telegram tunnel (можно включить позже через меню [T])"
fi
fi
# Показать итоговую информацию
print_separator
print_success "Установка zapret2 завершена!"
print_separator
printf "Установлено:\n"
printf " %-25s: %s\n" "Директория" "$ZAPRET2_DIR"
printf " %-25s: %s\n" "Бинарник" "${ZAPRET2_DIR}/nfq2/nfqws2"
printf " %-25s: %s\n" "Init скрипт" "$INIT_SCRIPT"
printf " %-25s: %s\n" "Конфигурация" "$CONFIG_DIR"
printf " %-25s: %s\n" "Списки доменов" "$LISTS_DIR"
printf " %-25s: %s\n" "Стратегии" "$STRATEGIES_CONF"
printf " %-25s: %s\n" "Tools" "${ZAPRET2_DIR}/ip2net, ${ZAPRET2_DIR}/mdig"
# Save local z2k entrypoint for future runs without curl.
local local_z2k_script="${ZAPRET2_DIR}/z2k.sh"
local local_z2k_url="${GITHUB_RAW}/z2k.sh"
if curl -fsSL --connect-timeout 10 --max-time 120 "$local_z2k_url" -o "$local_z2k_script"; then
chmod +x "$local_z2k_script" 2>/dev/null || true
printf " %-25s: %s\n" "z2k script" "$local_z2k_script"
print_info "Открыть меню позже: sh ${local_z2k_script} menu"
else
print_warning "Не удалось сохранить z2k.sh в ${local_z2k_script}"
print_info "Для повторного запуска используйте curl-команду из README"
fi
print_separator
return 0
}
# ==============================================================================
# ПОЛНАЯ УСТАНОВКА (9 ШАГОВ)
# ==============================================================================
run_full_install() {
print_header "Установка zapret2 для Keenetic"
print_info "Процесс установки: 12 шагов (расширенная проверка)"
print_separator
# Выполнить все шаги последовательно
step_check_root || return 1 # ← НОВОЕ (0/12)
step_update_packages || return 1 # 1/12
step_check_dns || return 1 # ← НОВОЕ (2/12)
step_install_dependencies || return 1 # 3/12 (расширено)
step_load_kernel_modules || return 1 # 4/12
step_build_zapret2 || return 1 # 5/12
step_verify_installation || return 1 # 6/12
step_check_and_select_fwtype || return 1 # ← НОВОЕ (7/12)
step_download_domain_lists || return 1 # 8/12
step_disable_hwnat_and_offload || return 1 # 9/12 (расширено)
step_configure_tmpdir || return 1 # ← НОВОЕ (9.5/12)
step_create_config_and_init || return 1 # 10/12
step_install_netfilter_hook || return 1 # 11/12
step_finalize || return 1 # 12/12
# После установки - без вопросов применяем autocircular стратегии по умолчанию
print_separator
print_info "Установка завершена успешно!"
print_separator
printf "\nНастройка стратегий DPI bypass:\n\n"
print_info "Автоматически применяю autocircular стратегии (без запроса выбора)..."
apply_autocircular_strategies --auto
print_info "Открываю меню управления..."
sleep 1
show_main_menu
return 0
}
# ==============================================================================
# ROLLBACK МЕХАНИЗМ
# ==============================================================================
ROLLBACK_DIR="/opt/zapret2/.rollback"
# Создать snapshot перед изменениями
create_rollback_snapshot() {
local reason=${1:-"manual"}
print_info "Создание rollback-snapshot..."
# Очистить предыдущий snapshot (хранится только последний)
rm -rf "$ROLLBACK_DIR"
mkdir -p "$ROLLBACK_DIR" || {
print_warning "Не удалось создать директорию rollback"
return 1
}
# Сохранить config
[ -f "${ZAPRET2_DIR}/config" ] && cp -f "${ZAPRET2_DIR}/config" "$ROLLBACK_DIR/config"
# Сохранить init script
[ -f "$INIT_SCRIPT" ] && cp -f "$INIT_SCRIPT" "$ROLLBACK_DIR/S99zapret2"
# Сохранить стратегии
for cat_dir in TCP/YT TCP/YT_GV TCP/RKN UDP/YT; do
local sfile="${ZAPRET2_DIR}/extra_strats/$cat_dir/Strategy.txt"
if [ -f "$sfile" ]; then
mkdir -p "$ROLLBACK_DIR/strats/$cat_dir"
cp -f "$sfile" "$ROLLBACK_DIR/strats/$cat_dir/Strategy.txt"
fi
done
# Сохранить whitelist
[ -f "${ZAPRET2_DIR}/lists/whitelist.txt" ] && \
cp -f "${ZAPRET2_DIR}/lists/whitelist.txt" "$ROLLBACK_DIR/whitelist.txt"
# Сохранить autocircular state
[ -f "${ZAPRET2_DIR}/extra_strats/cache/autocircular/state.tsv" ] && \
cp -f "${ZAPRET2_DIR}/extra_strats/cache/autocircular/state.tsv" "$ROLLBACK_DIR/state.tsv"
# Записать метаданные
cat > "$ROLLBACK_DIR/metadata" <<ROLLBACK_META
SNAPSHOT_TIME=$(date +%Y%m%d_%H%M%S)
REASON=$reason
Z2K_VERSION=${Z2K_VERSION:-unknown}
NFQWS2_VERSION=$(get_nfqws2_version 2>/dev/null || echo unknown)
ROLLBACK_META
print_success "Rollback snapshot создан: $ROLLBACK_DIR"
return 0
}
# Восстановить из rollback snapshot
rollback_to_snapshot() {
if [ ! -d "$ROLLBACK_DIR" ] || [ ! -f "$ROLLBACK_DIR/metadata" ]; then
print_error "Rollback snapshot не найден"
return 1
fi
print_header "Восстановление из rollback snapshot"
# Показать информацию о snapshot (без source — безопасный парсинг)
local SNAPSHOT_TIME REASON SNAP_Z2K_VERSION SNAP_NFQWS2_VERSION
SNAPSHOT_TIME=$(safe_config_read "SNAPSHOT_TIME" "$ROLLBACK_DIR/metadata" "unknown")
REASON=$(safe_config_read "REASON" "$ROLLBACK_DIR/metadata" "unknown")
SNAP_Z2K_VERSION=$(safe_config_read "Z2K_VERSION" "$ROLLBACK_DIR/metadata" "unknown")
SNAP_NFQWS2_VERSION=$(safe_config_read "NFQWS2_VERSION" "$ROLLBACK_DIR/metadata" "unknown")
print_info "Snapshot от: ${SNAPSHOT_TIME}"
print_info "Причина: ${REASON}"
print_info "Версия: z2k ${SNAP_Z2K_VERSION}, nfqws2 ${SNAP_NFQWS2_VERSION}"
if ! confirm "Восстановить эту конфигурацию?" "N"; then
print_info "Rollback отменён"
return 0
fi
# Остановить сервис
if [ -x "$INIT_SCRIPT" ]; then
"$INIT_SCRIPT" stop 2>/dev/null || true
fi
# Восстановить config
[ -f "$ROLLBACK_DIR/config" ] && cp -f "$ROLLBACK_DIR/config" "${ZAPRET2_DIR}/config"
# Восстановить init script
[ -f "$ROLLBACK_DIR/S99zapret2" ] && cp -f "$ROLLBACK_DIR/S99zapret2" "$INIT_SCRIPT" && chmod +x "$INIT_SCRIPT"
# Восстановить стратегии
for cat_dir in TCP/YT TCP/YT_GV TCP/RKN UDP/YT; do
local sfile="$ROLLBACK_DIR/strats/$cat_dir/Strategy.txt"
if [ -f "$sfile" ]; then
mkdir -p "${ZAPRET2_DIR}/extra_strats/$cat_dir"
cp -f "$sfile" "${ZAPRET2_DIR}/extra_strats/$cat_dir/Strategy.txt"
fi
done
# Восстановить whitelist
[ -f "$ROLLBACK_DIR/whitelist.txt" ] && \
cp -f "$ROLLBACK_DIR/whitelist.txt" "${ZAPRET2_DIR}/lists/whitelist.txt"
# Восстановить autocircular state
[ -f "$ROLLBACK_DIR/state.tsv" ] && \
cp -f "$ROLLBACK_DIR/state.tsv" "${ZAPRET2_DIR}/extra_strats/cache/autocircular/state.tsv"
# Перезапустить сервис
if [ -x "$INIT_SCRIPT" ]; then
"$INIT_SCRIPT" start 2>/dev/null || true
fi
print_success "Конфигурация восстановлена из rollback snapshot"
return 0
}
# Автоматический rollback по таймеру
# Вызывается после применения новой конфигурации
auto_rollback_timer() {
local timeout=${1:-300} # 5 минут по умолчанию
if [ ! -d "$ROLLBACK_DIR" ]; then
return 0
fi
print_warning "Авто-rollback активен: $timeout секунд"
print_info "Если сервис работает — подтвердите в меню"
print_info "Иначе конфигурация будет автоматически восстановлена"
# Создать таймер-файл
local timer_file="${ROLLBACK_DIR}/auto_timer"
echo "$(($(date +%s) + timeout))" > "$timer_file"
return 0
}
# Проверить истёк ли таймер авто-rollback (вызывается из cron)
check_auto_rollback() {
local timer_file="${ROLLBACK_DIR}/auto_timer"
[ -f "$timer_file" ] || return 0
local deadline
deadline=$(cat "$timer_file" 2>/dev/null)
[ -z "$deadline" ] && return 0
local now
now=$(date +%s)
if [ "$now" -ge "$deadline" ]; then
print_warning "Авто-rollback: таймер истёк, восстанавливаю конфигурацию..."
rm -f "$timer_file"
# Не-интерактивный rollback
if [ -x "$INIT_SCRIPT" ]; then
"$INIT_SCRIPT" stop 2>/dev/null || true
fi
[ -f "$ROLLBACK_DIR/config" ] && cp -f "$ROLLBACK_DIR/config" "${ZAPRET2_DIR}/config"
[ -f "$ROLLBACK_DIR/S99zapret2" ] && cp -f "$ROLLBACK_DIR/S99zapret2" "$INIT_SCRIPT"
for cat_dir in TCP/YT TCP/YT_GV TCP/RKN UDP/YT; do
local sfile="$ROLLBACK_DIR/strats/$cat_dir/Strategy.txt"
[ -f "$sfile" ] && cp -f "$sfile" "${ZAPRET2_DIR}/extra_strats/$cat_dir/Strategy.txt"
done
if [ -x "$INIT_SCRIPT" ]; then
"$INIT_SCRIPT" start 2>/dev/null || true
fi
logger -t z2k "Auto-rollback executed: timer expired"
fi
return 0
}
# Подтвердить новую конфигурацию (отменяет авто-rollback)
confirm_config() {
local timer_file="${ROLLBACK_DIR}/auto_timer"
if [ -f "$timer_file" ]; then
rm -f "$timer_file"
print_success "Конфигурация подтверждена, авто-rollback отключён"
else
print_info "Авто-rollback не активен"
fi
return 0
}
# ==============================================================================
# УДАЛЕНИЕ ZAPRET2
# ==============================================================================
uninstall_zapret2() {
print_header "Удаление zapret2"
# Проверить наличие хоть чего-то от zapret2
if ! is_zapret2_installed && [ ! -d "$ZAPRET2_DIR" ] && [ ! -d "$CONFIG_DIR" ] && [ ! -f "$INIT_SCRIPT" ]; then
print_info "zapret2 не установлен"
return 0
fi
print_warning "Это удалит:"
print_warning " - Все файлы zapret2 ($ZAPRET2_DIR)"
print_warning " - Конфигурацию ($CONFIG_DIR)"
print_warning " - Init скрипт ($INIT_SCRIPT)"
print_warning " - Правила iptables и netfilter хуки"
printf "\n"
if ! confirm "Вы уверены? Это действие необратимо!" "N"; then
print_info "Удаление отменено"
return 0
fi
# Остановить сервис через init-скрипт (мягкая попытка)
if [ -x "$INIT_SCRIPT" ]; then
print_info "Остановка сервиса..."
"$INIT_SCRIPT" stop 2>/dev/null || true
fi
# Принудительно убить оставшиеся процессы nfqws2 И nfqws (legacy от
# старых установок zapret до миграции на z2k).
local pids proc_name
for proc_name in nfqws2 nfqws; do
pids=$(pidof "$proc_name" 2>/dev/null || true)
if [ -n "$pids" ]; then
print_info "Завершение зависших процессов ${proc_name} (pidof)..."
kill -9 $pids 2>/dev/null || true
fi
# ps-fallback на случай если pidof не находит (бывает на busybox)
local ps_pids
ps_pids=$(ps w 2>/dev/null | awk -v name="$proc_name" '$0 ~ "/"name"( |$)" || $0 ~ " "name"( |$)" {print $1}' || true)
if [ -n "$ps_pids" ]; then
print_info "Завершение зависших процессов ${proc_name} (ps)..."
kill -9 $ps_pids 2>/dev/null || true
fi
done
sleep 1
# Очистка iptables цепочек zapret2
# Все команды с || true — скрипт запущен под set -e
print_info "Очистка правил iptables..."
local chain parent
for chain in ZAPRET ZAPRET2 z2k_connmark z2k_dpi_rst; do
for parent in POSTROUTING PREROUTING FORWARD; do
while iptables -t mangle -C "$parent" -j "$chain" 2>/dev/null; do
iptables -t mangle -D "$parent" -j "$chain" 2>/dev/null || break
done
while ip6tables -t mangle -C "$parent" -j "$chain" 2>/dev/null; do
ip6tables -t mangle -D "$parent" -j "$chain" 2>/dev/null || break
done
done
iptables -t mangle -F "$chain" 2>/dev/null || true
iptables -t mangle -X "$chain" 2>/dev/null || true
ip6tables -t mangle -F "$chain" 2>/dev/null || true
ip6tables -t mangle -X "$chain" 2>/dev/null || true
done
# raw table (RST filter)
while iptables -t raw -C PREROUTING -j z2k_dpi_rst 2>/dev/null; do
iptables -t raw -D PREROUTING -j z2k_dpi_rst 2>/dev/null || break
done
iptables -t raw -F z2k_dpi_rst 2>/dev/null || true
iptables -t raw -X z2k_dpi_rst 2>/dev/null || true
# nat table (masquerade fix)
while iptables -t nat -C POSTROUTING -j z2k_masq_fix 2>/dev/null; do
iptables -t nat -D POSTROUTING -j z2k_masq_fix 2>/dev/null || break
done
iptables -t nat -F z2k_masq_fix 2>/dev/null || true
iptables -t nat -X z2k_masq_fix 2>/dev/null || true
# Удалить init скрипт + legacy init-скрипты от старых установок
# zapret/nfqws (если миграция шла с предыдущих версий и они остались).
local _init
for _init in "$INIT_SCRIPT" \
/opt/etc/init.d/S99zapret \
/opt/etc/init.d/S99nfqws \
/opt/etc/init.d/S99nfqws2; do
if [ -f "$_init" ]; then
rm -f "$_init"
print_info "Удален init скрипт: $_init"
fi
done
# Удалить netfilter хуки (все связанные с zapret/z2k)
local hook
for hook in /opt/etc/ndm/netfilter.d/*zapret* \
/opt/etc/ndm/netfilter.d/*z2k*; do
if [ -f "$hook" ]; then
rm -f "$hook"
print_info "Удален netfilter хук: $(basename "$hook")"
fi
done
# Удалить zapret2 и legacy zapret (от старых установок)
local _dir
for _dir in "$ZAPRET2_DIR" /opt/zapret; do
if [ -d "$_dir" ]; then
rm -rf "$_dir"
print_info "Удалена директория: $_dir"
fi
done
# Удалить конфигурацию
if [ -d "$CONFIG_DIR" ]; then
rm -rf "$CONFIG_DIR"
print_info "Удалена конфигурация"
fi
# Очистить временные файлы (включая legacy /tmp/zapret)
rm -rf /tmp/z2k /tmp/zapret /tmp/zapret2 /tmp/blockcheck* 2>/dev/null
# Очистить ipset
local setname ipset_names
ipset_names=$(ipset list -n 2>/dev/null | grep -i "zapret\|z2k" || true)
for setname in $ipset_names; do
ipset destroy "$setname" 2>/dev/null || true
done
# Подметание оставшихся NFQUEUE/zapret/z2k правил во всех таблицах,
# на случай если specific-цепочки выше что-то пропустили (старые
# инсталлы, нестандартные правила, и т.п.).
local _table _parent _num _rule_nums
for _table in mangle nat raw filter; do
for _parent in PREROUTING INPUT FORWARD OUTPUT POSTROUTING; do
_rule_nums=$(iptables -t "$_table" -L "$_parent" --line-numbers -n 2>/dev/null \
| grep -iE "NFQUEUE|zapret|nfqws|z2k" | awk '{print $1}' | sort -rn || true)
for _num in $_rule_nums; do
iptables -t "$_table" -D "$_parent" "$_num" 2>/dev/null || true
done
done
done
# Финальная проверка: что-то осталось?
local _leftover=0
for _proc in nfqws2 nfqws; do
if pidof "$_proc" >/dev/null 2>&1; then
print_warning "Остались процессы $_proc — попробуй снова с правами root"
_leftover=$((_leftover + 1))
fi
done
for _dir in /opt/zapret2 /opt/zapret; do
if [ -d "$_dir" ]; then
print_warning "Осталась директория: $_dir"
_leftover=$((_leftover + 1))
fi
done
if [ "$_leftover" -gt 0 ]; then
print_warning "Осталось ${_leftover} артефакт(ов). Если переустановка падает,"
print_warning "запусти добивающую зачистку: curl -fsSL https://raw.githubusercontent.com/necronicle/z2k/master/z2k_cleanup.sh | sh"
else
print_success "zapret2 полностью удален"
fi
return 0
}
# ==============================================================================
# ЭКСПОРТ ФУНКЦИЙ
# ==============================================================================
# Все функции доступны после source этого файла