#!/bin/sh # z2k.sh - Bootstrap скрипт для z2k v2.0 # Модульный установщик zapret2 для роутеров Keenetic # https://github.com/necronicle/z2k set -e # ============================================================================== # КОНСТАНТЫ # ============================================================================== Z2K_VERSION="2.0.1" WORK_DIR="/tmp/z2k" LIB_DIR="${WORK_DIR}/lib" GITHUB_RAW="https://raw.githubusercontent.com/necronicle/z2k/master" # Экспортировать переменные для использования в функциях export WORK_DIR export LIB_DIR export GITHUB_RAW # Список модулей для загрузки MODULES="utils system_init install strategies config config_official webpanel menu" # ============================================================================== # ВСТРОЕННЫЕ FALLBACK ФУНКЦИИ # ============================================================================== # Минимальные функции для работы до загрузки модулей print_info() { printf "[i] %s\n" "$1" } print_success() { printf "[[OK]] %s\n" "$1" } print_error() { printf "[[FAIL]] %s\n" "$1" >&2 } die() { print_error "$1" [ -n "$2" ] && exit "$2" || exit 1 } clear_screen() { if [ -t 1 ]; then clear 2>/dev/null || printf "\033c" fi } print_header() { printf "\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" printf " %s\n" "$1" printf "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n" } print_separator() { printf "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" } confirm() { local prompt=${1:-"Продолжить?"} local default=${2:-"Y"} local answer="" while true; do if [ "$default" = "Y" ]; then printf "%s [Y/n]: " "$prompt" else printf "%s [y/N]: " "$prompt" fi if ! read -r answer /@
/

— CDN, 12h edge-кеш # (purge: https://purge.jsdelivr.net/gh//@
/

) # 3. gh-proxy.com/ — reverse-proxy без кеша # 4. Keenetic-only: nslookup через 8.8.8.8 → `ndmc ip host`, повтор 1+2. # # Использование: # z2k_fetch "https://raw.githubusercontent.com/owner/repo/branch/path" /tmp/dest # z2k_fetch "relative/path" /tmp/dest # тогда префикс = $GITHUB_RAW # # Возвращает 0 при успехе (файл записан), 1 — все слои не сработали. z2k_fetch() { local src="$1" local dest="$2" local url case "$src" in http://*|https://*) url="$src" ;; /*) url="${GITHUB_RAW}${src}" ;; *) url="${GITHUB_RAW}/${src}" ;; esac # Derive jsdelivr + gh-proxy URLs. Only raw.githubusercontent.com URLs # have a jsdelivr equivalent; gh-proxy works for any raw URL. local jsdelivr="" gh_proxy="" case "$url" in https://raw.githubusercontent.com/*) local _rest="${url#https://raw.githubusercontent.com/}" local _owner="${_rest%%/*}"; _rest="${_rest#*/}" local _repo="${_rest%%/*}"; _rest="${_rest#*/}" local _branch="${_rest%%/*}"; _rest="${_rest#*/}" jsdelivr="https://cdn.jsdelivr.net/gh/${_owner}/${_repo}@${_branch}/${_rest}" gh_proxy="https://gh-proxy.com/${url}" ;; esac # --- Layer 1: direct --- if curl -fsSL --connect-timeout 10 --max-time 180 -o "$dest" "$url" 2>/dev/null; then return 0 fi # --- Layer 2: jsdelivr CDN --- if [ -n "$jsdelivr" ] && \ curl -fsSL --connect-timeout 10 --max-time 180 -o "$dest" "$jsdelivr" 2>/dev/null; then return 0 fi # --- Layer 3: gh-proxy.com reverse-proxy --- if [ -n "$gh_proxy" ] && \ curl -fsSL --connect-timeout 10 --max-time 180 -o "$dest" "$gh_proxy" 2>/dev/null; then return 0 fi # --- Layer 4: Keenetic DNS override via 8.8.8.8 + ndmc ip host --- # Copied-in-spirit from zapret4rocket z4r.sh:1075-1107 — same failure # mode (ISP DNS poisoning of github hosts), same fix. if command -v ndmc >/dev/null 2>&1 && command -v nslookup >/dev/null 2>&1; then local resolved_any=0 host ip for host in raw.githubusercontent.com cdn.jsdelivr.net api.github.com; do ip=$(nslookup "$host" 8.8.8.8 2>/dev/null \ | awk '/^Name:/ {s=1; next} s && /^Address [0-9]+: [0-9]+\./ {print $3; exit}') if [ -n "$ip" ] && [ "$ip" != "127.0.0.1" ] && [ "$ip" != "8.8.8.8" ]; then ndmc -c "ip host $host $ip" >/dev/null 2>&1 && resolved_any=1 fi done if [ "$resolved_any" = "1" ]; then # Let NDM re-read DNS cache before we retry. sleep 1 if curl -fsSL --connect-timeout 10 --max-time 180 -o "$dest" "$url" 2>/dev/null; then return 0 fi if [ -n "$jsdelivr" ] && \ curl -fsSL --connect-timeout 10 --max-time 180 -o "$dest" "$jsdelivr" 2>/dev/null; then return 0 fi fi fi return 1 } # ============================================================================== # ПРОВЕРКИ ОКРУЖЕНИЯ # ============================================================================== z2k_detect_entware_arch() { local opkg_bin="opkg" [ -x /opt/bin/opkg ] && opkg_bin="/opt/bin/opkg" command -v "$opkg_bin" >/dev/null 2>&1 || return 1 "$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 } ' } # ВНИМАНИЕ: эта функция дублирует map_arch_to_bin_arch из utils.sh # Дубликат необходим т.к. вызывается до загрузки модулей. # При изменении — синхронизировать с lib/utils.sh:map_arch_to_bin_arch() z2k_map_arch_to_bin_arch() { case "$1" in aarch64|arm64|*aarch64*|*arm64*) echo "linux-arm64" ;; armv7l|armv6l|arm|*armv7*|*armv6*|arm*) echo "linux-arm" ;; x86_64|amd64|*x86_64*|*amd64*) echo "linux-x86_64" ;; i386|i486|i586|i686|x86) echo "linux-x86" ;; *mipsel64*|*mips64el*) echo "linux-mipsel" ;; *mips64*) echo "linux-mips64" ;; *mipsel*) echo "linux-mipsel" ;; *mips*) echo "linux-mips" ;; *lexra*) echo "linux-lexra" ;; *ppc*) echo "linux-ppc" ;; *riscv64*) echo "linux-riscv64" ;; *) return 1 ;; esac } check_environment() { print_info "Проверка окружения..." # Проверка Entware if [ ! -d "/opt" ] || [ ! -x "/opt/bin/opkg" ]; then die "Entware не установлен! Установите Entware перед запуском z2k." fi # Проверка curl if ! command -v curl >/dev/null 2>&1; then print_info "curl не найден, устанавливаю..." /opt/bin/opkg update || die "Не удалось обновить opkg" /opt/bin/opkg install curl || die "Не удалось установить curl" fi # Проверка архитектуры local arch entware_arch bin_arch entware_arch=$(z2k_detect_entware_arch) arch="${entware_arch:-$(uname -m)}" # uname -m returns "mips" for both mips and mipsel — detect endianness from ELF if [ "$arch" = "mips" ]; then local _ebin="" for _f in /opt/bin/opkg /opt/bin/busybox; do [ -f "$_f" ] && _ebin="$_f" && break; done if [ -n "$_ebin" ]; then local _byte _byte=$(dd if="$_ebin" bs=1 skip=5 count=1 2>/dev/null) [ "$_byte" = "$(printf '\x01')" ] && arch="mipsel" fi fi bin_arch=$(z2k_map_arch_to_bin_arch "$arch" 2>/dev/null || true) [ -n "$bin_arch" ] && print_info "Detected architecture: $arch -> $bin_arch" if [ -z "$bin_arch" ]; then print_info "ВНИМАНИЕ: z2k разработан для ARM64 Keenetic" print_info "Ваша архитектура: $arch" printf "Продолжить? [y/N]: " read -r answer /dev/null 2>&1; then generate_strategies_conf "${WORK_DIR}/strats_new2.txt" "${WORK_DIR}/strategies.conf" || \ die "Ошибка генерации strategies.conf" local count count=$(wc -l < "${WORK_DIR}/strategies.conf" | tr -d ' ') print_success "Сгенерировано стратегий: $count" else die "Функция generate_strategies_conf не найдена" fi print_info "Генерация базы QUIC стратегий (quic_strategies.conf)..." if command -v generate_quic_strategies_conf >/dev/null 2>&1; then generate_quic_strategies_conf "${WORK_DIR}/quic_strats.ini" "${WORK_DIR}/quic_strategies.conf" || \ die "Ошибка генерации quic_strategies.conf" else die "Функция generate_quic_strategies_conf не найдена" fi } # ============================================================================== # ГЛАВНОЕ МЕНЮ BOOTSTRAP # ============================================================================== show_welcome() { clear_screen cat </dev/null || true) case "$_barch" 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_url="${GITHUB_RAW}/mtproxy-client/builds/tg-mtproxy-client-linux-${tg_arch}" local tg_tmp tg_tmp=$(mktemp) if z2k_fetch "$tg_url" "$tg_tmp" && \ [ "$(wc -c < "$tg_tmp")" -gt 500000 ] && \ head -c 4 "$tg_tmp" 2>/dev/null | grep -q "ELF"; then killall tg-mtproxy-client 2>/dev/null || true sleep 1 cp "$tg_tmp" /opt/sbin/tg-mtproxy-client chmod +x /opt/sbin/tg-mtproxy-client # Respect TG_PROXY_USER_DISABLED — if user explicitly stopped # the tunnel via menu/webpanel, don't resurrect it on update. local _tg_disabled=0 if [ -f "/opt/zapret2/config" ]; then _tg_disabled=$(awk -F= '/^TG_PROXY_USER_DISABLED=/ {gsub(/[" ]/,"",$2); print $2; exit}' /opt/zapret2/config) fi if [ "$_tg_disabled" = "1" ]; then print_success "Telegram tunnel обновлён (не запущен — отключён пользователем)" else /opt/sbin/tg-mtproxy-client --listen=:1443 --timeout=15m >> /tmp/tg-tunnel.log 2>&1 & sleep 2 if pgrep -f "tg-mtproxy-client" >/dev/null 2>&1; then print_success "Telegram tunnel обновлён и перезапущен" else print_warning "Telegram tunnel обновлён, но не запустился" fi fi else print_warning "Не удалось обновить Telegram tunnel" fi rm -f "$tg_tmp" fi fi # Update watchdog script if [ -f "/opt/zapret2/tg-tunnel-watchdog.sh" ]; then cat > /opt/zapret2/tg-tunnel-watchdog.sh << 'WDEOF' #!/bin/sh LOG="/tmp/tg-tunnel.log" BIN="/opt/sbin/tg-mtproxy-client" CONFIG_FILE="/opt/zapret2/config" if [ -f "$CONFIG_FILE" ]; then user_disabled=$(awk -F= '/^TG_PROXY_USER_DISABLED=/ {gsub(/[" ]/,"",$2); print $2; exit}' "$CONFIG_FILE") if [ "$user_disabled" = "1" ]; then pidof tg-mtproxy-client >/dev/null 2>&1 && killall -9 tg-mtproxy-client 2>/dev/null exit 0 fi fi [ ! -f "$LOG" ] && exit 0 pgrep -f "tg-mtproxy-client" >/dev/null || exit 0 FAILS=$(tail -40 "$LOG" | grep -c "CONNECT_FAIL") if [ "$FAILS" -ge 10 ]; then logger -t tg-watchdog "Detected $FAILS CONNECT_FAILs, restarting tunnel" killall -9 tg-mtproxy-client 2>/dev/null sleep 2 $BIN --listen=:1443 >> "$LOG" 2>&1 & echo "$(date) watchdog: restarted ($FAILS fails)" >> "$LOG" fi WDEOF chmod +x /opt/zapret2/tg-tunnel-watchdog.sh print_success "Watchdog обновлён" fi print_info "Перезапустите z2k для применения изменений" else print_error "Не удалось загрузить обновление" rm -f "$temp_file" return 1 fi } # ============================================================================== # ГЛАВНАЯ ФУНКЦИЯ # ============================================================================== main() { # Early-exit for help/version — no downloads needed case "$1" in help|h|-h|--help) show_help exit 0 ;; version|v|--version) echo "z2k v${Z2K_VERSION}" exit 0 ;; esac # Показать приветствие show_welcome # Проверить окружение check_environment # Инициализировать рабочую директорию rm -rf "$WORK_DIR" mkdir -p "$WORK_DIR" "$LIB_DIR" # Установить обработчики сигналов (будет переопределено после загрузки utils.sh) # Также очищаем временные директории при любом выходе trap 'echo ""; print_error "Прервано пользователем"; rm -rf "$WORK_DIR" /tmp/zapret2_build; exit 130' INT TERM trap 'rm -rf /tmp/zapret2_build' EXIT # Скачать модули download_modules # Загрузить модули в память source_modules # Теперь доступны все функции из модулей # Переустановить обработчики сигналов с правильными функциями setup_signal_handlers # Инициализировать системные переменные (SYSTEM, UNAME, INIT) init_system_vars || die "Ошибка определения типа системы" # Инициализация (создание рабочей директории с проверками из utils.sh) init_work_dir || die "Ошибка инициализации" # Проверить права root (нужно для установки) if [ "$1" = "install" ] || [ "$1" = "i" ]; then check_root || die "Требуются права root для установки" fi # Скачать strats_new2.txt download_strategies_source # Скачать fake blobs download_fake_blobs # Скачать init скрипт download_init_script # Сгенерировать strategies.conf generate_strategies_database # Обработать аргументы командной строки handle_arguments "$1" # Очистка при выходе (если не удаляется автоматически) # cleanup_work_dir } # ============================================================================== # ЗАПУСК # ============================================================================== main "$@"