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>
This commit is contained in:
Necronicle 2026-04-23 23:26:58 +03:00
parent 2feba704c2
commit 3681570fdb
5 changed files with 252 additions and 28 deletions

View file

@ -11,8 +11,70 @@ MAX_LOG_LINES=200
GITHUB_RAW="https://raw.githubusercontent.com/necronicle/z2k/master"
# Настройки
CURL_OPTS="--connect-timeout 10 --max-time 60 -fsSL"
# ==============================================================================
# z2k_fetch — загрузка файла с GitHub через цепочку зеркал.
# ==============================================================================
# Дублирует логику z2k.sh / lib/utils.sh для standalone cron-запуска (этот
# скрипт не source'ит utils.sh). Слои: raw.github → jsdelivr → gh-proxy →
# Keenetic DNS override через 8.8.8.8 + ndmc.
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
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
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
if [ -n "$gh_proxy" ] && \
curl -fsSL --connect-timeout 10 --max-time 180 -o "$dest" "$gh_proxy" 2>/dev/null; then
return 0
fi
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
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
}
# ==============================================================================
# ЛОГИРОВАНИЕ
@ -52,8 +114,8 @@ update_list() {
local tmp
tmp=$(mktemp "${dest}.XXXXXX") || return 1
if ! curl $CURL_OPTS "$url" -o "$tmp" 2>/dev/null; then
log_msg "FAIL: download $name from $url"
if ! z2k_fetch "$url" "$tmp"; then
log_msg "FAIL: download $name from $url (all mirrors failed)"
rm -f "$tmp"
return 1
fi

View file

@ -864,8 +864,8 @@ step_build_zapret2() {
/tmp/z2k-autocircular-debug.flag \
/tmp/z2k-autocircular-debug.log 2>/dev/null || true
if curl -fsSL --connect-timeout 10 --max-time 120 "https://raw.githubusercontent.com/AloofLibra/zapret4rocket/z2r/orchestra/locked.lua" \
-o "${ZAPRET2_DIR}/lua/locked.lua"; then
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 может не работать)"
@ -873,8 +873,8 @@ step_build_zapret2() {
print_info "Загрузка orchestrator.sh для управления circular_locked..."
if curl -fsSL --connect-timeout 10 --max-time 120 "https://raw.githubusercontent.com/AloofLibra/zapret4rocket/z2r/orchestra/orchestrator.sh" \
-o "${ZAPRET2_DIR}/extra_strats/cache/orchestra/orchestrator.sh"; then
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
@ -935,16 +935,16 @@ step_build_zapret2() {
local custom_dir="${ZAPRET2_DIR}/init.d/keenetic/custom.d"
mkdir -p "$custom_dir"
if curl -fsSL --connect-timeout 10 --max-time 120 "https://raw.githubusercontent.com/bol-van/zapret2/master/init.d/custom.d.examples.linux/50-stun4all" \
-o "${custom_dir}/50-stun4all"; then
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 curl -fsSL --connect-timeout 10 --max-time 120 "https://raw.githubusercontent.com/bol-van/zapret2/master/init.d/custom.d.examples.linux/50-discord-media" \
-o "${custom_dir}/50-discord-media"; then
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

View file

@ -1271,7 +1271,7 @@ SUBMENU
local tg_bin="tg-mtproxy-client-linux-${tg_arch}"
local tg_url="${GITHUB_RAW}/mtproxy-client/builds/${tg_bin}"
rm -f "$MTPROXY_BIN"
curl -fsSL --connect-timeout 10 --max-time 120 "$tg_url" -o "$MTPROXY_BIN" 2>/dev/null
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

View file

@ -60,6 +60,78 @@ else
COLOR_RESET=''
fi
# ==============================================================================
# z2k_fetch — загрузка файла с GitHub через цепочку зеркал.
# ==============================================================================
#
# Дублирует функцию из z2k.sh для модулей/скриптов, которые source'ят
# lib/utils.sh напрямую (обход ломается у части ISP на raw.github —
# jsdelivr/gh-proxy/DNS override покрывают все известные сценарии блока).
#
# Слои (пробуем по порядку, первый успех возвращает 0):
# 1. raw.githubusercontent.com
# 2. cdn.jsdelivr.net/gh/<owner>/<repo>@<branch>/<path> (edge TTL 12ч)
# 3. gh-proxy.com/<raw-url> (без кеша)
# 4. (Keenetic) nslookup 8.8.8.8 → ndmc "ip host" → ретрай 1+2.
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
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
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
if [ -n "$gh_proxy" ] && \
curl -fsSL --connect-timeout 10 --max-time 180 -o "$dest" "$gh_proxy" 2>/dev/null; then
return 0
fi
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
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
}
# ==============================================================================
# ФУНКЦИИ ВЫВОДА
# ==============================================================================

120
z2k.sh
View file

@ -96,6 +96,96 @@ confirm() {
done
}
# ==============================================================================
# z2k_fetch — загрузка файла с GitHub через цепочку зеркал.
# ==============================================================================
#
# Российские провайдеры местами режут raw.githubusercontent.com (DNS
# poisoning / SNI block), из-за чего первый curl при установке падает и
# ничего дальше не работает. Обходим тремя зеркалами + DNS-override на
# Keenetic как последним шансом:
#
# 1. raw.githubusercontent.com — прямой путь, самый свежий
# 2. cdn.jsdelivr.net/gh/<o>/<r>@<br>/<p> — CDN, 12h edge-кеш
# (purge: https://purge.jsdelivr.net/gh/<o>/<r>@<br>/<p>)
# 3. gh-proxy.com/<raw-url> — 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
}
# ==============================================================================
# ПРОВЕРКИ ОКРУЖЕНИЯ
# ==============================================================================
@ -194,7 +284,7 @@ download_modules() {
print_info "Загрузка lib/${module}.sh..."
if curl -fsSL --connect-timeout 10 --max-time 120 "$url" -o "$output"; then
if z2k_fetch "$url" "$output"; then
print_success "Загружен: ${module}.sh"
else
die "Ошибка загрузки модуля: ${module}.sh"
@ -230,7 +320,7 @@ download_strategies_source() {
local url="${GITHUB_RAW}/strats_new2.txt"
local output="${WORK_DIR}/strats_new2.txt"
if curl -fsSL --connect-timeout 10 --max-time 120 "$url" -o "$output"; then
if z2k_fetch "$url" "$output"; then
local lines
lines=$(wc -l < "$output")
print_success "Загружено: strats_new2.txt ($lines строк)"
@ -242,7 +332,7 @@ download_strategies_source() {
local quic_url="${GITHUB_RAW}/quic_strats.ini"
local quic_output="${WORK_DIR}/quic_strats.ini"
if curl -fsSL --connect-timeout 10 --max-time 120 "$quic_url" -o "$quic_output"; then
if z2k_fetch "$quic_url" "$quic_output"; then
local lines
lines=$(wc -l < "$quic_output")
print_success "Загружено: quic_strats.ini ($lines строк)"
@ -286,7 +376,7 @@ zero_256.bin
[ -z "$file" ] && continue
local url="${GITHUB_RAW}/files/fake/${file}"
local output="${fake_dir}/${file}"
if curl -fsSL --connect-timeout 10 --max-time 120 "$url" -o "$output"; then
if z2k_fetch "$url" "$output"; then
print_success "Загружено: files/fake/${file}"
else
die "Ошибка загрузки files/fake/${file}"
@ -308,7 +398,7 @@ download_init_script() {
url="${GITHUB_RAW}/files/S99zapret2.new"
output="${files_dir}/S99zapret2.new"
if curl -fsSL --connect-timeout 10 --max-time 120 "$url" -o "$output"; then
if z2k_fetch "$url" "$output"; then
print_success "Загружено: files/S99zapret2.new"
else
die "Ошибка загрузки files/S99zapret2.new"
@ -316,7 +406,7 @@ download_init_script() {
url="${GITHUB_RAW}/files/000-zapret2.sh"
output="${files_dir}/000-zapret2.sh"
if curl -fsSL --connect-timeout 10 --max-time 120 "$url" -o "$output"; then
if z2k_fetch "$url" "$output"; then
print_success "Загружено: files/000-zapret2.sh"
else
die "Ошибка загрузки files/000-zapret2.sh"
@ -324,7 +414,7 @@ download_init_script() {
url="${GITHUB_RAW}/files/z2k-blocked-monitor.sh"
output="${files_dir}/z2k-blocked-monitor.sh"
if curl -fsSL --connect-timeout 10 --max-time 120 "$url" -o "$output"; then
if z2k_fetch "$url" "$output"; then
print_success "Загружено: files/z2k-blocked-monitor.sh"
else
die "Ошибка загрузки files/z2k-blocked-monitor.sh"
@ -334,7 +424,7 @@ download_init_script() {
for tool_name in z2k-healthcheck.sh z2k-config-validator.sh z2k-update-lists.sh z2k-fix-tg-iptables.sh; do
url="${GITHUB_RAW}/files/${tool_name}"
output="${files_dir}/${tool_name}"
if curl -fsSL --connect-timeout 10 --max-time 120 "$url" -o "$output"; then
if z2k_fetch "$url" "$output"; then
print_success "Загружено: files/${tool_name}"
else
print_warning "Не удалось загрузить files/${tool_name} (необязательный)"
@ -345,7 +435,7 @@ download_init_script() {
mkdir -p "${files_dir}/ndm"
url="${GITHUB_RAW}/files/ndm/90-z2k-tg-redirect.sh"
output="${files_dir}/ndm/90-z2k-tg-redirect.sh"
if curl -fsSL --connect-timeout 10 --max-time 120 "$url" -o "$output"; then
if z2k_fetch "$url" "$output"; then
print_success "Загружено: files/ndm/90-z2k-tg-redirect.sh"
else
print_warning "Не удалось загрузить ndm hook (iptables не будут авто-восстанавливаться)"
@ -363,7 +453,7 @@ download_init_script() {
do
url="${GITHUB_RAW}/webpanel/${wp_file}"
output="${webpanel_dir}/${wp_file}"
if curl -fsSL --connect-timeout 10 --max-time 120 "$url" -o "$output" 2>/dev/null; then
if z2k_fetch "$url" "$output"; then
: # ok
else
print_warning "Не удалось загрузить webpanel/${wp_file} (опциональный компонент)"
@ -376,7 +466,7 @@ download_init_script() {
url="${GITHUB_RAW}/files/lua/z2k-autocircular.lua"
output="${lua_dir}/z2k-autocircular.lua"
if curl -fsSL --connect-timeout 10 --max-time 120 "$url" -o "$output"; then
if z2k_fetch "$url" "$output"; then
print_success "Загружено: files/lua/z2k-autocircular.lua"
else
die "Ошибка загрузки files/lua/z2k-autocircular.lua"
@ -384,7 +474,7 @@ download_init_script() {
url="${GITHUB_RAW}/files/lua/z2k-modern-core.lua"
output="${lua_dir}/z2k-modern-core.lua"
if curl -fsSL --connect-timeout 10 --max-time 120 "$url" -o "$output"; then
if z2k_fetch "$url" "$output"; then
print_success "Загружено: files/lua/z2k-modern-core.lua"
else
die "Ошибка загрузки files/lua/z2k-modern-core.lua"
@ -409,7 +499,7 @@ roblox_ips.txt
local list_out="${lists_dir}/${list_file}"
mkdir -p "$(dirname "$list_out")"
if curl -fsSL --connect-timeout 10 --max-time 120 "$list_url" -o "$list_out"; then
if z2k_fetch "$list_url" "$list_out"; then
print_success "Загружено: files/lists/${list_file}"
else
die "Ошибка загрузки files/lists/${list_file}"
@ -615,7 +705,7 @@ update_z2k() {
local temp_file
temp_file=$(mktemp)
if curl -fsSL --connect-timeout 10 --max-time 120 "$latest_url" -o "$temp_file"; then
if z2k_fetch "$latest_url" "$temp_file"; then
# Получить версию из нового файла
local new_version
new_version=$(grep '^Z2K_VERSION=' "$temp_file" | cut -d'"' -f2)
@ -667,7 +757,7 @@ update_z2k() {
local tg_url="${GITHUB_RAW}/mtproxy-client/builds/tg-mtproxy-client-linux-${tg_arch}"
local tg_tmp
tg_tmp=$(mktemp)
if curl -fsSL --connect-timeout 10 --max-time 120 "$tg_url" -o "$tg_tmp" && \
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