v4.38
This commit is contained in:
1 parent
a62bd1e7fd
commit
4cc235e292
1 file changed
+480
-61
+480
-61
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
read -r _ _ ZM_NEW_VER <<'ZM_VERSION_EOF'
|
read -r _ _ ZM_NEW_VER <<'ZM_VERSION_EOF'
|
||||||
# Version: 4.34
|
# Version: 4.38
|
||||||
ZM_VERSION_EOF
|
ZM_VERSION_EOF
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
@@ -1544,6 +1544,8 @@ zm_watch() {
|
|||||||
_fk_mlkem_patch
|
_fk_mlkem_patch
|
||||||
_fk_serial_patch
|
_fk_serial_patch
|
||||||
_fk_listsvia_patch
|
_fk_listsvia_patch
|
||||||
|
_fk_ghdl_patch
|
||||||
|
_fk_b4_watch
|
||||||
fi
|
fi
|
||||||
_st_colo_guard
|
_st_colo_guard
|
||||||
_st_vpn_watch
|
_st_vpn_watch
|
||||||
@@ -6944,15 +6946,26 @@ _zo_doh_hosts() {
|
|||||||
}'
|
}'
|
||||||
}
|
}
|
||||||
_zo_doh_doms() { local d; d="$(_zo_doh_hosts)"; echo "${d:-$ZO_DOH_DEF}"; }
|
_zo_doh_doms() { local d; d="$(_zo_doh_hosts)"; echo "${d:-$ZO_DOH_DEF}"; }
|
||||||
|
# Блок DoH по умолчанию. Прежний (маскировка под ya.ru) у тех, кто его не менял, панель сама
|
||||||
|
# заменяет на новый (под common.dot.dns.yandex.net); изменённый вручную блок не трогаем
|
||||||
|
ZO_DOH_OLD="--filter-tcp=443 --dpi-desync=hostfakesplit --dpi-desync-hostfakesplit-mod=host=ya.ru --dpi-desync-hostfakesplit-midhost=host-2 --dpi-desync-split-seqovl=726 --dpi-desync-fooling=badsum,badseq --dpi-desync-badseq-increment=0"
|
||||||
|
ZO_DOH_NEW="--filter-tcp=443 --dpi-desync=hostfakesplit --dpi-desync-hostfakesplit-mod=host=common.dot.dns.yandex.net --dpi-desync-hostfakesplit-midhost=host-2 --dpi-desync-split-seqovl=726 --dpi-desync-fooling=badsum,badseq --dpi-desync-badseq-increment=0"
|
||||||
_zo_doh_tpl() {
|
_zo_doh_tpl() {
|
||||||
local t
|
local t
|
||||||
t="$(grep '^--' "$ZO_DOH_FLAG" 2>/dev/null | grep -v '^--hostlist-domains=' | grep -vx -- '--new')"
|
t="$(grep '^--' "$ZO_DOH_FLAG" 2>/dev/null | grep -v '^--hostlist-domains=' | grep -vx -- '--new')"
|
||||||
if ! printf '%s\n' "$t" | grep -q '^--filter-tcp=' || ! printf '%s\n' "$t" | grep -q '^--dpi-desync='; then
|
if ! printf '%s\n' "$t" | grep -q '^--filter-tcp=' || ! printf '%s\n' "$t" | grep -q '^--dpi-desync=' || [ "$(echo $t)" = "$ZO_DOH_OLD" ]; then
|
||||||
t="$(printf '%s\n' --filter-tcp=443 --dpi-desync=hostfakesplit --dpi-desync-hostfakesplit-mod=host=ya.ru --dpi-desync-hostfakesplit-midhost=host-2 \
|
t="$(printf '%s\n' $ZO_DOH_NEW)"
|
||||||
--dpi-desync-split-seqovl=726 --dpi-desync-fooling=badsum,badseq --dpi-desync-badseq-increment=0)"
|
|
||||||
fi
|
fi
|
||||||
printf '%s\n' "$t"
|
printf '%s\n' "$t"
|
||||||
}
|
}
|
||||||
|
# В стратегии стоит прежний блок DoH по умолчанию — его пора заменить новым
|
||||||
|
_zo_doh_stale() {
|
||||||
|
[ "$(_nfq_opt_body | awk -v M="$ZO_DOH_MK" '
|
||||||
|
$0 ~ M { c++; d = (c == 1); next }
|
||||||
|
d && $0 == "--new" { d = 0 }
|
||||||
|
d && /^[ \t]*--/ && !/^[ \t]*--hostlist-domains=/ { l = $0; gsub(/^[ \t]+|[ \t\r]+$/, "", l); o = o (o == "" ? "" : " ") l }
|
||||||
|
END { print o }')" = "$ZO_DOH_OLD" ]
|
||||||
|
}
|
||||||
_zo_doh_scan() {
|
_zo_doh_scan() {
|
||||||
_nfq_opt_body | awk -v M="$ZO_DOH_MK" '
|
_nfq_opt_body | awk -v M="$ZO_DOH_MK" '
|
||||||
NR == 1 { next }
|
NR == 1 { next }
|
||||||
@@ -6992,7 +7005,7 @@ _zo_doh_sync() {
|
|||||||
grep -q "^[[:space:]]*option NFQWS_OPT '[[:space:]]*\$" "$CONF" || return 1
|
grep -q "^[[:space:]]*option NFQWS_OPT '[[:space:]]*\$" "$CONF" || return 1
|
||||||
_nochange_strategy && return 1
|
_nochange_strategy && return 1
|
||||||
doms="$(_zo_doh_doms)"
|
doms="$(_zo_doh_doms)"
|
||||||
[ "$(ZM_NFQ_BODY=""; _zo_doh_scan)" = "top $doms" ] && return 1
|
[ "$(ZM_NFQ_BODY=""; _zo_doh_scan)" = "top $doms" ] && ! (ZM_NFQ_BODY=""; _zo_doh_stale) && return 1
|
||||||
cap="$ZO_DOH_FLAG.cap"
|
cap="$ZO_DOH_FLAG.cap"
|
||||||
rm -f "$cap"
|
rm -f "$cap"
|
||||||
_zo_doh_cut "$cap"
|
_zo_doh_cut "$cap"
|
||||||
@@ -11360,6 +11373,32 @@ _doh_cur_id() {
|
|||||||
p="$(_doh_provider_of "$u")"
|
p="$(_doh_provider_of "$u")"
|
||||||
echo "${p:-$u}"
|
echo "${p:-$u}"
|
||||||
}
|
}
|
||||||
|
# Переход на сервер, через который DNS на роутере так и не заработал (напрямую сервер отвечает, а через
|
||||||
|
# https-dns-proxy — нет), не повторяем каждые 2 минуты: каждая такая попытка перезапускает DNS роутера и
|
||||||
|
# рвёт его на 10–15 секунд. Пауза 10 минут, затем 20, 40… до 2 часов; ручной выбор сервера её сбрасывает.
|
||||||
|
_doh_bad_key() { printf 'bad%s' "$(printf '%s' "$1" | md5sum | cut -c1-10)"; }
|
||||||
|
_doh_bad_wait() {
|
||||||
|
local v n w=600
|
||||||
|
v="$(_doh_st_get "$(_doh_bad_key "$1")" "")"; n="${v#*:}"
|
||||||
|
case "$n" in ''|*[!0-9]*) echo 0; return ;; esac
|
||||||
|
while [ "$n" -gt 1 ] && [ "$w" -lt 7200 ]; do w=$((w * 2)); n=$((n - 1)); done
|
||||||
|
[ "$w" -gt 7200 ] && w=7200
|
||||||
|
echo "$w"
|
||||||
|
}
|
||||||
|
_doh_bad_skip() {
|
||||||
|
local v t w
|
||||||
|
v="$(_doh_st_get "$(_doh_bad_key "$1")" "")"; [ -n "$v" ] || return 1
|
||||||
|
t="${v%%:*}"; w="$(_doh_bad_wait "$1")"
|
||||||
|
case "$t" in ''|*[!0-9]*) return 1 ;; esac
|
||||||
|
[ $(( $(date +%s) - t )) -lt "$w" ]
|
||||||
|
}
|
||||||
|
_doh_bad_mark() {
|
||||||
|
local v n
|
||||||
|
v="$(_doh_st_get "$(_doh_bad_key "$1")" "")"; n="${v#*:}"
|
||||||
|
case "$n" in ''|*[!0-9]*) n=0 ;; esac
|
||||||
|
_doh_st_put "$(_doh_bad_key "$1")" "$(date +%s):$((n + 1))"
|
||||||
|
}
|
||||||
|
_doh_bad_clear() { [ -f "$DOH_FO_STATE" ] && grep -q "^$(_doh_bad_key "$1")=" "$DOH_FO_STATE" && _doh_st_put "$(_doh_bad_key "$1")" ""; return 0; }
|
||||||
_doh_ok() { case "$(_doh_probe "$(_doh_url_of "$1")" "$(_doh_boots_for "$1")" "/tmp/zm-dohfo.$$")" in "ok "*) return 0 ;; esac; return 1; }
|
_doh_ok() { case "$(_doh_probe "$(_doh_url_of "$1")" "$(_doh_boots_for "$1")" "/tmp/zm-dohfo.$$")" in "ok "*) return 0 ;; esac; return 1; }
|
||||||
_doh_cur_alive() {
|
_doh_cur_alive() {
|
||||||
local port
|
local port
|
||||||
@@ -11368,6 +11407,8 @@ _doh_cur_alive() {
|
|||||||
sleep 2
|
sleep 2
|
||||||
_doh_live_real "$port" && return 0
|
_doh_live_real "$port" && return 0
|
||||||
_doh_ok "$1" || return 1
|
_doh_ok "$1" || return 1
|
||||||
|
# перезапуск уже не помог два раза подряд — не дёргаем DNS роутера снова, решает переключение
|
||||||
|
[ "${2:-0}" -lt 2 ] || return 1
|
||||||
_zm_run 30 /etc/init.d/https-dns-proxy restart >/dev/null 2>&1
|
_zm_run 30 /etc/init.d/https-dns-proxy restart >/dev/null 2>&1
|
||||||
_doh_wait_real "$port" 10 && { _doh_fo_event "$(_doh_name "$1") отвечает, а DNS на роутере завис — перезапустили https-dns-proxy"; return 0; }
|
_doh_wait_real "$port" 10 && { _doh_fo_event "$(_doh_name "$1") отвечает, а DNS на роутере завис — перезапустили https-dns-proxy"; return 0; }
|
||||||
return 1
|
return 1
|
||||||
@@ -11402,8 +11443,10 @@ doh_watch() {
|
|||||||
_doh_fo_put active "$cur"
|
_doh_fo_put active "$cur"
|
||||||
if [ "$mode" = direct ]; then
|
if [ "$mode" = direct ]; then
|
||||||
for c in "$pref" $([ "$(_doh_fo_get fo 0)" = 1 ] && _doh_fo_pool "$pref" ""); do
|
for c in "$pref" $([ "$(_doh_fo_get fo 0)" = 1 ] && _doh_fo_pool "$pref" ""); do
|
||||||
|
_doh_bad_skip "$c" && continue
|
||||||
_doh_ok "$c" || continue
|
_doh_ok "$c" || continue
|
||||||
[ "$c" = "$cur" ] || _doh_apply "$c" >/dev/null 2>&1 || continue
|
[ "$c" = "$cur" ] || _doh_apply "$c" >/dev/null 2>&1 || { _doh_bad_mark "$c"; continue; }
|
||||||
|
_doh_bad_clear "$c"
|
||||||
_doh_fo_put active "$c"
|
_doh_fo_put active "$c"
|
||||||
/etc/init.d/https-dns-proxy start >/dev/null 2>&1
|
/etc/init.d/https-dns-proxy start >/dev/null 2>&1
|
||||||
t=0; while ! _doh_local_ok 5053 && [ "$t" -lt 8 ]; do sleep 1; t=$((t + 1)); done
|
t=0; while ! _doh_local_ok 5053 && [ "$t" -lt 8 ]; do sleep 1; t=$((t + 1)); done
|
||||||
@@ -11425,18 +11468,23 @@ doh_watch() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fails="$(_doh_st_get fails 0)"; okc="$(_doh_st_get okc 0)"
|
fails="$(_doh_st_get fails 0)"; okc="$(_doh_st_get okc 0)"
|
||||||
if [ "$cur" != "$pref" ] && [ "$(_doh_fo_get fo 0)" = 1 ]; then
|
if [ "$cur" != "$pref" ] && [ "$(_doh_fo_get fo 0)" = 1 ] && ! _doh_bad_skip "$pref"; then
|
||||||
if _doh_ok "$pref"; then okc=$((okc + 1)); else okc=0; fi
|
if _doh_ok "$pref"; then okc=$((okc + 1)); else okc=0; fi
|
||||||
if [ "$okc" -ge 2 ] && _doh_apply "$pref" >/dev/null 2>&1; then
|
if [ "$okc" -ge 2 ]; then
|
||||||
|
if _doh_apply "$pref" >/dev/null 2>&1; then
|
||||||
|
_doh_bad_clear "$pref"
|
||||||
_doh_fo_put active "$pref"
|
_doh_fo_put active "$pref"
|
||||||
_doh_st_put mode normal fails 0 okc 0 since "$(date +%s)"
|
_doh_st_put mode normal fails 0 okc 0 since "$(date +%s)"
|
||||||
_doh_fo_event "$(_doh_name "$pref") снова отвечает — вернулись на него с $(_doh_name "$cur")"
|
_doh_fo_event "$(_doh_name "$pref") снова отвечает — вернулись на него с $(_doh_name "$cur")"
|
||||||
rmdir "$lk" 2>/dev/null
|
rmdir "$lk" 2>/dev/null
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
_doh_bad_mark "$pref"; okc=0
|
||||||
|
_doh_fo_event "$(_doh_name "$pref") отвечает, но DNS роутера через него не заработал — остаёмся на $(_doh_name "$cur"), снова попробуем через $(( $(_doh_bad_wait "$pref") / 60 )) мин"
|
||||||
|
fi
|
||||||
_doh_st_put okc "$okc"
|
_doh_st_put okc "$okc"
|
||||||
fi
|
fi
|
||||||
if /etc/init.d/https-dns-proxy running >/dev/null 2>&1 && _doh_cur_alive "$cur"; then
|
if /etc/init.d/https-dns-proxy running >/dev/null 2>&1 && _doh_cur_alive "$cur" "$fails"; then
|
||||||
_doh_st_put fails 0
|
_doh_st_put fails 0
|
||||||
rmdir "$lk" 2>/dev/null
|
rmdir "$lk" 2>/dev/null
|
||||||
return 0
|
return 0
|
||||||
@@ -11445,8 +11493,10 @@ doh_watch() {
|
|||||||
[ "$fails" -ge 2 ] || { rmdir "$lk" 2>/dev/null; return 0; }
|
[ "$fails" -ge 2 ] || { rmdir "$lk" 2>/dev/null; return 0; }
|
||||||
if [ "$(_doh_fo_get fo 0)" = 1 ]; then
|
if [ "$(_doh_fo_get fo 0)" = 1 ]; then
|
||||||
for c in $([ "$cur" != "$pref" ] && echo "$pref") $(_doh_fo_pool "$pref" "$cur"); do
|
for c in $([ "$cur" != "$pref" ] && echo "$pref") $(_doh_fo_pool "$pref" "$cur"); do
|
||||||
|
_doh_bad_skip "$c" && continue
|
||||||
_doh_ok "$c" || continue
|
_doh_ok "$c" || continue
|
||||||
_doh_apply "$c" >/dev/null 2>&1 || continue
|
_doh_apply "$c" >/dev/null 2>&1 || { _doh_bad_mark "$c"; continue; }
|
||||||
|
_doh_bad_clear "$c"
|
||||||
_doh_fo_put active "$c"
|
_doh_fo_put active "$c"
|
||||||
_doh_st_put mode "$([ "$c" = "$pref" ] && echo normal || echo backup)" fails 0 okc 0 since "$(date +%s)"
|
_doh_st_put mode "$([ "$c" = "$pref" ] && echo normal || echo backup)" fails 0 okc 0 since "$(date +%s)"
|
||||||
_doh_fo_event "$(_doh_name "$cur") не отвечает — DNS переключён на $(_doh_name "$c")"
|
_doh_fo_event "$(_doh_name "$cur") не отвечает — DNS переключён на $(_doh_name "$c")"
|
||||||
@@ -11523,6 +11573,12 @@ doh_failover_set() {
|
|||||||
/etc/init.d/https-dns-proxy start >/dev/null 2>&1
|
/etc/init.d/https-dns-proxy start >/dev/null 2>&1
|
||||||
_doh_fo_event "автопереключение выключено — DNS снова идёт через DoH"
|
_doh_fo_event "автопереключение выключено — DNS снова идёт через DoH"
|
||||||
fi
|
fi
|
||||||
|
# выключили, стоя на запасном: возвращаем основной, если он отвечает, — иначе запасной так и остался бы навсегда
|
||||||
|
v="$(_doh_cur_id)"; pref="$(_doh_fo_get pref "")"
|
||||||
|
if [ -n "$v" ] && [ -n "$pref" ] && [ "$v" != "$pref" ] && _doh_ok "$pref" && _doh_apply "$pref" >/dev/null 2>&1; then
|
||||||
|
_doh_fo_put active "$pref"
|
||||||
|
_doh_fo_event "автопереключение выключено — вернулись на основной $(_doh_name "$pref")"
|
||||||
|
fi
|
||||||
rm -f "$DOH_FO_STATE"
|
rm -f "$DOH_FO_STATE"
|
||||||
fi
|
fi
|
||||||
_zo_doh_follow
|
_zo_doh_follow
|
||||||
@@ -12333,7 +12389,7 @@ stl_apply() {
|
|||||||
else
|
else
|
||||||
rm -f "$new"
|
rm -f "$new"
|
||||||
echo "ОШИБКА: ядро steer отвергло настройку"
|
echo "ОШИБКА: ядро steer отвергло настройку"
|
||||||
printf '%s\n' "$err" | tail -n 5
|
printf '%s\n' "$err" | tail -n 5 | _st_outs_text
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -12345,7 +12401,7 @@ stl_apply() {
|
|||||||
if ! err="$(_stl_t 300 steer apply 2>&1 >/dev/null)"; then
|
if ! err="$(_stl_t 300 steer apply 2>&1 >/dev/null)"; then
|
||||||
if [ -s "$prev" ]; then mv -f "$prev" "$STL_SPEC"; else rm -f "$STL_SPEC"; fi
|
if [ -s "$prev" ]; then mv -f "$prev" "$STL_SPEC"; else rm -f "$STL_SPEC"; fi
|
||||||
echo "ОШИБКА: ядро steer не применило правила — остались прежние"
|
echo "ОШИБКА: ядро steer не применило правила — остались прежние"
|
||||||
printf '%s\n' "$err" | tail -n 5
|
printf '%s\n' "$err" | tail -n 5 | _st_outs_text
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
rm -f "$prev"
|
rm -f "$prev"
|
||||||
@@ -12410,19 +12466,170 @@ stl_diag() {
|
|||||||
d="$(_stl_t 30 steer diag 2>/dev/null | tr '\n' ' ')"
|
d="$(_stl_t 30 steer diag 2>/dev/null | tr '\n' ' ')"
|
||||||
case "$d" in '{'*'}'*) printf '%s' "$d" ;; *) echo null ;; esac
|
case "$d" in '{'*'}'*) printf '%s' "$d" ;; *) echo null ;; esac
|
||||||
}
|
}
|
||||||
stl_diag_lines() {
|
_stl_diag_tsv() {
|
||||||
local f="$STL_TMP/stl-diag.json" n i=0
|
local n i=0
|
||||||
mkdir -p "$STL_TMP"
|
n="$(jsonfilter -i "$1" -e '@.checks[*].id' 2>/dev/null | wc -l)"
|
||||||
_stl_t 30 steer diag > "$f" 2>/dev/null
|
|
||||||
n="$(jsonfilter -i "$f" -e '@.checks[*].id' 2>/dev/null | wc -l)"
|
|
||||||
while [ "$i" -lt "$n" ]; do
|
while [ "$i" -lt "$n" ]; do
|
||||||
printf '%s\t%s\t%s\n' "$(jsonfilter -i "$f" -e "@.checks[$i].verdict" 2>/dev/null)" \
|
printf '%s\t%s\t%s\n' "$(jsonfilter -i "$1" -e "@.checks[$i].verdict" 2>/dev/null)" \
|
||||||
"$(jsonfilter -i "$f" -e "@.checks[$i].what" 2>/dev/null)" "$(jsonfilter -i "$f" -e "@.checks[$i].why" 2>/dev/null)"
|
"$(jsonfilter -i "$1" -e "@.checks[$i].what" 2>/dev/null)" "$(jsonfilter -i "$1" -e "@.checks[$i].why" 2>/dev/null)"
|
||||||
i=$((i + 1))
|
i=$((i + 1))
|
||||||
done
|
done
|
||||||
|
}
|
||||||
|
stl_diag_lines() {
|
||||||
|
local f="$STL_TMP/stl-diag.json"
|
||||||
|
mkdir -p "$STL_TMP"
|
||||||
|
_stl_t 30 steer diag > "$f" 2>/dev/null
|
||||||
|
_stl_diag_tsv "$f"
|
||||||
rm -f "$f"
|
rm -f "$f"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Проверка ядра steer — понятными словами. Ядро называет выходы внутренними именами: zm_vpn — VPN
|
||||||
|
# секции «Основная», zm_vpn2 и zm_vpn3 — VPN секций 2 и 3, zmv2_vless и т. п. — узлы этих секций,
|
||||||
|
# zm_if2 — туннель роутера у секции 2, zm_warp — общий WARP. Вместо них пишем имя секции. Поломка
|
||||||
|
# секции, в которой ничего не выбрано (или которая выключена), — предупреждение: через неё ничего не
|
||||||
|
# идёт. К отказу узла — коротко, что это значит и что делать.
|
||||||
|
# Таблица секций: «id подпись состояние(work|idle|off) туннель»; подпись пустая, если секция одна.
|
||||||
|
_st_diag_map() {
|
||||||
|
local s many="" st
|
||||||
|
[ -n "$(_st_secs)" ] && many=1
|
||||||
|
for s in main $(_st_secs); do
|
||||||
|
if [ "$s" = main ]; then
|
||||||
|
if _st_sec_off main; then st=off; elif _st_sec_with main _st_work_main; then st=work; else st=idle; fi
|
||||||
|
printf '%s\t%s\t%s\t%s\n' main "${many:+Секция «$(_st_sec_name main)»}" "$st" "$(_st_main_iface)"
|
||||||
|
else
|
||||||
|
if _st_sec_off "$s"; then st=off; elif _st_sec_work "$s"; then st=work; else st=idle; fi
|
||||||
|
printf '%s\t%s\t%s\t%s\n' "$s" "Секция «$(_st_sec_name "$s")»" "$st" "$(_st_sec_iface "$s")"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
# stdin: «verdict what why» от ядра; stdout через \037 (не табуляция: пустые поля не должны слипаться при read):
|
||||||
|
# «verdict, what, why, что это и что делать, заметка, исходный verdict, исходный what»
|
||||||
|
ZM_ST_DIAG_AWK='
|
||||||
|
function secof(o) {
|
||||||
|
if (o == "zm_vpn" || o == "zm_if" || o ~ /^zmv_/) return "main"
|
||||||
|
if (o ~ /^zm_(vpn|if)[2-9]$/) return "s" substr(o, length(o), 1)
|
||||||
|
if (o ~ /^zmv[2-9]_/) return "s" substr(o, 4, 1)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
function tipof(r, c) {
|
||||||
|
if (r ~ /вместо 101/) {
|
||||||
|
c = r; sub(/^[^0-9]*/, "", c); c = substr(c, 1, 3) + 0
|
||||||
|
if (c == 400 || c == 404) return "Сервер не знает такого адреса подключения — в ссылке неверный path или host"
|
||||||
|
if (c == 403) return "Сервер или CDN перед ним не пускает — проверьте host и sni в ссылке или возьмите другой узел"
|
||||||
|
if (c >= 500) return "Сервер узла (или CDN перед ним) не смог принять подключение — обычно в ссылке неверный path или host, реже сбой у поставщика"
|
||||||
|
return "Сервер не принял подключение WebSocket — проверьте path и host в ссылке"
|
||||||
|
}
|
||||||
|
if (r ~ /Upgrade|Sec-WebSocket|кадр WebSocket/) return "По ссылке отвечает не VPN-сервер, а что-то другое (сайт-заглушка, прокси) — проверьте path, host и порт"
|
||||||
|
if (r ~ /не разрешил/) return "Адрес сервера из ссылки не находится — проверьте адрес в ссылке и DNS роутера"
|
||||||
|
if (r ~ /TCP не соединился|нет сокета/) return "До сервера не достучаться: он выключен, сменил порт или его блокирует провайдер"
|
||||||
|
if (r ~ /ClientHello|таймаут|не ответил за|узел не ответил/) return "Сервер молчит — так выглядит блокировка по имени сайта (SNI) или лежащий узел; попробуйте другой узел"
|
||||||
|
if (r ~ /не признал ключ|UUID|отверг авторизацию|отказал в авторизации|авторизации негоден|pbk или sid/) return "Сервер не принял ключ или пароль из ссылки — ссылка устарела: обновите подписку или ссылку"
|
||||||
|
if (r ~ /подлинност|сертификат|pinSHA256/) return "Сертификат сервера не прошёл проверку — в ссылке неверный sni, или соединение подменяют"
|
||||||
|
if (r ~ /ECH/) return "Ключ ECH в ссылке устарел — обновите ссылку или подписку"
|
||||||
|
if (r ~ /VLESS encryption/) return "Строка encryption в ссылке не совпадает с сервером — обновите ссылку"
|
||||||
|
if (r ~ /HTTP\/2|HTTP\/1\.1|gRPC/) return "Сервер не поддерживает транспорт из ссылки (ws, grpc, xhttp) — проверьте тип транспорта"
|
||||||
|
if (r ~ /QUIC не открылся/) return "Подключение по QUIC (UDP) не открылось — проверьте порт и sni в ссылке; иногда провайдер режет UDP"
|
||||||
|
if (r ~ /соединения не открываются/) return "Узел отвечает на проверку, но настоящие соединения не держит — выберите другой узел"
|
||||||
|
if (r ~ /закрыл соединение|TLS закрыт|обрыв|поток закрыт|GOAWAY|не прислал данных|ответа нет|ответ не по протоколу|соединение закрыто/) return "Сервер обрывает соединение — узел перегружен или не пускает с этого адреса; попробуйте другой узел"
|
||||||
|
return "Узел не отвечает — выберите другой узел или проверьте ссылку"
|
||||||
|
}
|
||||||
|
BEGIN {
|
||||||
|
FS = "\t"; OFS = U
|
||||||
|
while ((getline l < M) > 0) { split(l, a, "\t"); LB[a[1]] = a[2]; ST[a[1]] = a[3]; IF[a[1]] = a[4] }
|
||||||
|
close(M)
|
||||||
|
T = "(zm_vpn[2-9]?|zmv[2-9]?_[A-Za-z0-9]+|zm_warp[0-9]*|zmwarp[0-9]*)"
|
||||||
|
}
|
||||||
|
{
|
||||||
|
v = $1; w = $2; y = $3; v0 = v; tip = ""; note = ""; s = ""; lab = ""
|
||||||
|
if (w ~ /^выход [A-Za-z0-9_.-]+/) {
|
||||||
|
o = w; sub(/^выход /, "", o); rest = o; sub(/[: ].*$/, "", o)
|
||||||
|
rest = substr(rest, length(o) + 1); sub(/^:? */, "", rest)
|
||||||
|
s = secof(o)
|
||||||
|
if (s != "") {
|
||||||
|
k = (o ~ /^zm_if/) ? "туннель" (IF[s] != "" ? " " IF[s] : "") : "VPN"
|
||||||
|
lab = (LB[s] != "" ? LB[s] " · " : "") k
|
||||||
|
} else if (o ~ /^zm_warp/) lab = "WARP"
|
||||||
|
else if (o == "zm_direct") lab = "Мимо Steer"
|
||||||
|
if (lab != "") {
|
||||||
|
sub("^устройство " T " в зоне, NAT есть$", "туннель подключён, NAT есть", rest)
|
||||||
|
sub("^устройство " T " в зоне$", "туннель подключён", rest)
|
||||||
|
sub("^устройства " T " нет$", "туннель не поднят", rest)
|
||||||
|
sub("^устройство " T " выключено$", "туннель выключен", rest)
|
||||||
|
sub("^" T " вне зоны фаервола$", "туннель вне зоны фаервола", rest)
|
||||||
|
sub("^у " T " нет masquerade", "у туннеля нет masquerade", rest)
|
||||||
|
sub("^" T " не отвечает", "туннель не отвечает", rest)
|
||||||
|
cl = ""
|
||||||
|
if (match(rest, /, трафик канала /)) {
|
||||||
|
cl = substr(rest, RSTART + RLENGTH); rest = substr(rest, 1, RSTART - 1)
|
||||||
|
if (cl == "идёт напрямую") cl = "сервисы идут напрямую"
|
||||||
|
else if (cl == "остановлен") cl = "сервисы остановлены"
|
||||||
|
else if (cl == "идёт через обход") cl = "сервисы идут через обход блокировок"
|
||||||
|
}
|
||||||
|
if (rest ~ /^узел перестал отвечать/) {
|
||||||
|
rest = "узел не отвечает"
|
||||||
|
r = y; sub(/; выход вернётся сам, когда узел ответит$/, "", r); sub(/ \(проверьте path и host\)$/, "", r)
|
||||||
|
if (r == "узел не отвечает") r = ""
|
||||||
|
y = r; tip = tipof(r)
|
||||||
|
} else if (rest ~ /^туннель не отвечает/) {
|
||||||
|
y = "проверьте туннель и его сервер"
|
||||||
|
tip = "Туннель есть, но трафик через него не проходит — обычно не отвечает сервер туннеля"
|
||||||
|
} else if (rest == "туннель не поднят") sub(/^туннель не поднят — /, "", y)
|
||||||
|
if (s != "" && ST[s] != "" && ST[s] != "work") cl = ""
|
||||||
|
w = lab ": " rest (cl != "" ? ", " cl : "")
|
||||||
|
}
|
||||||
|
} else if (w ~ /^канал s[2-9]_/) {
|
||||||
|
c = w; sub(/^канал /, "", c); s = substr(c, 1, 2); sub(/^s[2-9]_/, "", c); sub(/:.*$/, "", c)
|
||||||
|
t = w; sub(/^канал s[2-9]_[^:]*/, "", t)
|
||||||
|
if (LB[s] != "") w = LB[s] " · список «" c "»" t
|
||||||
|
s = ""
|
||||||
|
}
|
||||||
|
if (v == "fail" && s != "" && ST[s] != "" && ST[s] != "work") {
|
||||||
|
v = "warn"
|
||||||
|
note = ST[s] == "off" ? "Секция выключена — на работу это сейчас не влияет" : "В секции ничего не выбрано — на работу это сейчас не влияет"
|
||||||
|
if (LB[s] == "") note = ST[s] == "off" ? "Steer выключен — на работу это сейчас не влияет" : "Сервисы не выбраны — на работу это сейчас не влияет"
|
||||||
|
} else if (v == "fail" && tip != "" && s != "") tip = tip ". Как только он ответит, всё заработает само"
|
||||||
|
print v, w, y, tip, note, v0, $2
|
||||||
|
}'
|
||||||
|
_st_diag_human() {
|
||||||
|
local m="$ST_RUN/diag.map.$$"
|
||||||
|
mkdir -p "$ST_RUN"
|
||||||
|
_st_diag_map > "$m" 2>/dev/null
|
||||||
|
awk -v M="$m" -v U="$(printf '\037')" "$ZM_ST_DIAG_AWK"
|
||||||
|
rm -f "$m"
|
||||||
|
}
|
||||||
|
# Любой текст ядра (ошибки apply, журнал): «выход zm_vpn2» → «Секция «чайна» · VPN»
|
||||||
|
_st_outs_text() {
|
||||||
|
local m="$ST_RUN/diag.map.$$"
|
||||||
|
mkdir -p "$ST_RUN"
|
||||||
|
_st_diag_map > "$m" 2>/dev/null
|
||||||
|
awk -v M="$m" '
|
||||||
|
function secof(o) {
|
||||||
|
if (o == "zm_vpn" || o == "zm_if" || o ~ /^zmv_/) return "main"
|
||||||
|
if (o ~ /^zm_(vpn|if)[2-9]$/) return "s" substr(o, length(o), 1)
|
||||||
|
if (o ~ /^zmv[2-9]_/) return "s" substr(o, 4, 1)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
BEGIN { while ((getline l < M) > 0) { split(l, a, "\t"); LB[a[1]] = a[2]; IF[a[1]] = a[4] } close(M) }
|
||||||
|
{
|
||||||
|
out = ""; t = $0
|
||||||
|
while (match(t, /выход (zm_vpn[2-9]?|zm_if[2-9]?|zmv[2-9]?_[A-Za-z0-9]+|zm_warp[0-9]*|zm_direct)/)) {
|
||||||
|
o = substr(t, RSTART, RLENGTH); sub(/^выход /, "", o); s = secof(o)
|
||||||
|
if (s != "") lab = (LB[s] != "" ? LB[s] " · " : "") ((o ~ /^zm_if/) ? "туннель" (IF[s] != "" ? " " IF[s] : "") : "VPN")
|
||||||
|
else lab = (o == "zm_direct") ? "Мимо Steer" : "WARP"
|
||||||
|
out = out substr(t, 1, RSTART - 1) lab; t = substr(t, RSTART + RLENGTH)
|
||||||
|
}
|
||||||
|
print out t
|
||||||
|
}'
|
||||||
|
rm -f "$m"
|
||||||
|
}
|
||||||
|
# Одна строка ядра → понятный текст (для списков «что сломано» перед починкой)
|
||||||
|
_st_diag_what() { printf 'fail\t%s\t\n' "$1" | _st_diag_human | awk -F"$(printf '\037')" '{ print $2 }'; }
|
||||||
|
# Для вкладки Steer → «Проверка»: [{"v","what","why","tip","note"}]
|
||||||
|
_st_diag_hjson() {
|
||||||
|
_st_diag_human | awk -F"$(printf '\037')" "$ZM_JESC_AWK"'
|
||||||
|
$1 ~ /^(ok|warn|fail)$/ { printf "%s{\"v\":\"%s\",\"what\":\"%s\",\"why\":\"%s\",\"tip\":\"%s\",\"note\":\"%s\"}", (n++ ? "," : ""), $1, jesc($2), jesc($3), jesc($4), jesc($5) }'
|
||||||
|
}
|
||||||
|
|
||||||
_stl_explain_run() {
|
_stl_explain_run() {
|
||||||
local t="" x="$STL_ETC/.zm-explain.$$.json"
|
local t="" x="$STL_ETC/.zm-explain.$$.json"
|
||||||
if [ -s "$STL_SPEC" ] && grep -q '"scope":"device"' "$STL_SPEC" 2>/dev/null; then
|
if [ -s "$STL_SPEC" ] && grep -q '"scope":"device"' "$STL_SPEC" 2>/dev/null; then
|
||||||
@@ -14090,6 +14297,9 @@ _st_tgws_warp() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ST_LISTS_MANIFEST="https://github.com/xyzmean/splify2-lists/releases/latest/download/lists.json"
|
ST_LISTS_MANIFEST="https://github.com/xyzmean/splify2-lists/releases/latest/download/lists.json"
|
||||||
|
# Тот же lists.json каталог кладёт и в ветку main. Берём его оттуда (raw.githubusercontent.com): файлы релизов
|
||||||
|
# отдаёт release-assets.githubusercontent.com, а он у части провайдеров не отвечает. Не вышло — из релиза
|
||||||
|
ST_LISTS_MANIFEST_RAW="https://raw.githubusercontent.com/xyzmean/splify2-lists/main/lists.json"
|
||||||
ST_CAT_SKIP=" mydyson itdoginfo_meta "
|
ST_CAT_SKIP=" mydyson itdoginfo_meta "
|
||||||
ST_SRS_FALLBACK="https://github.com/itdoginfo/allow-domains/releases/latest/download"
|
ST_SRS_FALLBACK="https://github.com/itdoginfo/allow-domains/releases/latest/download"
|
||||||
|
|
||||||
@@ -14109,6 +14319,28 @@ _st_fetch() {
|
|||||||
curl -fsSL --interface "$i" --connect-timeout 6 --max-time 60 -o "$2" "$1" 2>/dev/null && [ -s "$2" ]
|
curl -fsSL --interface "$i" --connect-timeout 6 --max-time 60 -o "$2" "$1" 2>/dev/null && [ -s "$2" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Набор b4geoip из релиза Greeg0ry/b4geoip-forkop → тот же файл из ветки main (srs/ИМЯ.srs); остальное — как есть
|
||||||
|
_st_b4_raw() {
|
||||||
|
case "$1" in
|
||||||
|
https://github.com/[Gg]reeg0ry/b4geoip-forkop/releases/*.srs) echo "https://raw.githubusercontent.com/Greeg0ry/b4geoip-forkop/main/srs/${1##*/}" ;;
|
||||||
|
*) echo "$1" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# .srs набора: b4geoip — сначала из ветки main, не вышло — из релиза, как раньше. Если в этом обновлении
|
||||||
|
# не ответили ни raw, ни релиз, следующие наборы b4geoip 20 минут пробуют только raw — без двойного ожидания
|
||||||
|
_st_srs_dl() {
|
||||||
|
local u r
|
||||||
|
u="$(_st_srs_url "$1")"; r="$(_st_b4_raw "$u")"
|
||||||
|
if [ "$r" != "$u" ]; then
|
||||||
|
_st_fetch "$r" "$2" && [ "$(head -c 3 "$2")" = SRS ] && return 0
|
||||||
|
[ -n "$(find "$ST_RUN/b4.down" -mmin -20 2>/dev/null)" ] && return 1
|
||||||
|
fi
|
||||||
|
_st_fetch "$u" "$2" && [ "$(head -c 3 "$2")" = SRS ] && return 0
|
||||||
|
[ "$r" != "$u" ] && { mkdir -p "$ST_RUN"; : > "$ST_RUN/b4.down"; }
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
_json_flat() {
|
_json_flat() {
|
||||||
awk 'BEGIN { RS = "\001" }
|
awk 'BEGIN { RS = "\001" }
|
||||||
function emit(v) { p = ""; for (k = 1; k <= d; k++) p = p (k > 1 ? "." : "") key[k]; print p "\t" v; if (arr[d]) key[d]++ }
|
function emit(v) { p = ""; for (k = 1; k <= d; k++) p = p (k > 1 ? "." : "") key[k]; print p "\t" v; if (arr[d]) key[d]++ }
|
||||||
@@ -14212,6 +14444,17 @@ _st_cat_build() {
|
|||||||
|
|
||||||
_st_cat_src() { [ -s "$ST_CAT_URL" ] && head -n1 "$ST_CAT_URL" || echo "$ST_LISTS_MANIFEST"; }
|
_st_cat_src() { [ -s "$ST_CAT_URL" ] && head -n1 "$ST_CAT_URL" || echo "$ST_LISTS_MANIFEST"; }
|
||||||
|
|
||||||
|
# Каталог по умолчанию — сначала из ветки main, затем из релиза; свой каталог — по его ссылке
|
||||||
|
_st_cat_dl() {
|
||||||
|
local u
|
||||||
|
u="$(_st_cat_src)"
|
||||||
|
if [ "$u" = "$ST_LISTS_MANIFEST" ]; then
|
||||||
|
_st_fetch "$ST_LISTS_MANIFEST_RAW" "$1" && _st_cat_build "$1" && return 0
|
||||||
|
rm -f "$1"
|
||||||
|
fi
|
||||||
|
_st_fetch "$u" "$1" && _st_cat_build "$1"
|
||||||
|
}
|
||||||
|
|
||||||
_st_cat_refresh() {
|
_st_cat_refresh() {
|
||||||
local m="$ST_RUN/lists.json" lk="$ST_RUN/cat.lock" rc=0 t
|
local m="$ST_RUN/lists.json" lk="$ST_RUN/cat.lock" rc=0 t
|
||||||
[ -f "$ST_CAT_OFF" ] && return 0
|
[ -f "$ST_CAT_OFF" ] && return 0
|
||||||
@@ -14230,7 +14473,7 @@ _st_cat_refresh() {
|
|||||||
rmdir "$lk" 2>/dev/null; mkdir "$lk" 2>/dev/null || return 0
|
rmdir "$lk" 2>/dev/null; mkdir "$lk" 2>/dev/null || return 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if _st_fetch "$(_st_cat_src)" "$m.tmp" && _st_cat_build "$m.tmp"; then
|
if _st_cat_dl "$m.tmp"; then
|
||||||
mv -f "$m.tmp" "$m"
|
mv -f "$m.tmp" "$m"
|
||||||
touch "$ST_CAT_SVC"
|
touch "$ST_CAT_SVC"
|
||||||
rm -f "$ST_DIR/catalog.err"
|
rm -f "$ST_DIR/catalog.err"
|
||||||
@@ -14312,7 +14555,7 @@ _st_srs_get() {
|
|||||||
echo " ✓ $set: скачан, доменов $(wc -l < "$d/$set.dom.tmp" | tr -d ' '), подсетей $(wc -l < "$d/$set.pfx.tmp" | tr -d ' ')" >&2
|
echo " ✓ $set: скачан, доменов $(wc -l < "$d/$set.dom.tmp" | tr -d ' '), подсетей $(wc -l < "$d/$set.pfx.tmp" | tr -d ' ')" >&2
|
||||||
else
|
else
|
||||||
if [ ! -f "$ST_RUN/dl.$set" ]; then
|
if [ ! -f "$ST_RUN/dl.$set" ]; then
|
||||||
if _st_fetch "$(_st_srs_url "$set")" "$f" && [ "$(head -c 3 "$f")" = SRS ]; then
|
if _st_srs_dl "$set" "$f"; then
|
||||||
mv -f "$f" "$d/$set.srs"
|
mv -f "$f" "$d/$set.srs"
|
||||||
touch "$ST_RUN/dl.$set"
|
touch "$ST_RUN/dl.$set"
|
||||||
echo " ✓ $set.srs: скачан, $(ls -l "$d/$set.srs" | awk '{ printf "%d КБ", ($5 + 1023) / 1024 }')" >&2
|
echo " ✓ $set.srs: скачан, $(ls -l "$d/$set.srs" | awk '{ printf "%d КБ", ($5 + 1023) / 1024 }')" >&2
|
||||||
@@ -14802,7 +15045,7 @@ _st_selfcheck0() {
|
|||||||
if _st_failopen; then
|
if _st_failopen; then
|
||||||
if [ "$(_st_onfail)" = drop ]; then echo "[FAIL] Туннель: ядро steer не может пустить через него трафик — выбранные сервисы остановлены"
|
if [ "$(_st_onfail)" = drop ]; then echo "[FAIL] Туннель: ядро steer не может пустить через него трафик — выбранные сервисы остановлены"
|
||||||
else echo "[FAIL] Туннель: ядро steer не может пустить через него трафик — сервисы идут напрямую, через провайдера"; fi
|
else echo "[FAIL] Туннель: ядро steer не может пустить через него трафик — сервисы идут напрямую, через провайдера"; fi
|
||||||
logread 2>/dev/null | grep -F 'steer[warn]' | tail -n 6 | sed 's/^[^]]*\]: / /'
|
logread 2>/dev/null | grep -F 'steer[warn]' | tail -n 6 | sed 's/^[^]]*\]: / /' | _st_outs_text
|
||||||
bad=1
|
bad=1
|
||||||
else
|
else
|
||||||
echo "[ OK ] Ядро steer снова ведёт трафик в туннель"
|
echo "[ OK ] Ядро steer снова ведёт трафик в туннель"
|
||||||
@@ -14814,12 +15057,11 @@ _st_selfcheck0() {
|
|||||||
while IFS="$(printf '\t')" read -r v what why; do
|
while IFS="$(printf '\t')" read -r v what why; do
|
||||||
[ "$v" = fail ] && ! _st_diag_node "$what" && core=1
|
[ "$v" = fail ] && ! _st_diag_node "$what" && core=1
|
||||||
done < "$f"
|
done < "$f"
|
||||||
if [ -z "$core" ] && [ -z "$moved" ] && grep -q '^fail' "$f"; then
|
local nodeonly=""
|
||||||
echo " · Ядро steer исправно — не отвечает узел или сервер туннеля; перезапуск и переустановка ядра тут не помогут"
|
[ -z "$core" ] && [ -z "$moved" ] && _st_diag_human < "$f" | awk -F"$(printf '\037')" '$1 == "fail" { f = 1 } END { exit !f }' && nodeonly=1
|
||||||
fi
|
|
||||||
if [ -n "$core" ] && [ -z "$ST_HEALING" ] && _st_work && [ ! -f "$ST_OFF" ]; then
|
if [ -n "$core" ] && [ -z "$ST_HEALING" ] && _st_work && [ ! -f "$ST_OFF" ]; then
|
||||||
while IFS="$(printf '\t')" read -r v what why; do
|
while IFS="$(printf '\t')" read -r v what why; do
|
||||||
[ "$v" = fail ] && ! _st_diag_node "$what" && echo "!! $what"
|
[ "$v" = fail ] && ! _st_diag_node "$what" && echo "!! $(_st_diag_what "$what")"
|
||||||
done < "$f"
|
done < "$f"
|
||||||
ST_HEALING=1
|
ST_HEALING=1
|
||||||
if _st_heal "$f"; then healed=1; fi
|
if _st_heal "$f"; then healed=1; fi
|
||||||
@@ -14827,19 +15069,29 @@ _st_selfcheck0() {
|
|||||||
did="$ST_HEAL_DID"
|
did="$ST_HEAL_DID"
|
||||||
_rb_say "Проверяем ещё раз"
|
_rb_say "Проверяем ещё раз"
|
||||||
fi
|
fi
|
||||||
while IFS="$(printf '\t')" read -r v what why; do
|
local raw tip note v0 warned=""
|
||||||
|
_st_diag_human < "$f" > "$f.h"
|
||||||
|
while IFS="$(printf '\037')" read -r v what why tip note v0 raw; do
|
||||||
case "$v" in
|
case "$v" in
|
||||||
ok) echo "[ OK ] $what" ;;
|
ok) echo "[ OK ] $what" ;;
|
||||||
warn) echo "!! $what${why:+ — $why}" ;;
|
warn)
|
||||||
|
[ -n "$moved" ] && [ "$v0" = fail ] && _st_diag_node "$raw" && continue
|
||||||
|
echo "!! $what${why:+ — $why}"; warned=1
|
||||||
|
[ -n "$tip" ] && echo " · $tip"
|
||||||
|
[ -n "$note" ] && echo " · $note" ;;
|
||||||
fail)
|
fail)
|
||||||
[ -n "$moved" ] && _st_diag_node "$what" && continue
|
[ -n "$moved" ] && _st_diag_node "$raw" && continue
|
||||||
case "$why" in *'init.d/steer'*|*ерезапустите*|*ереустановите*) [ -n "$did" ] && why="$did; пришлите журнал в чат поддержки" ;; esac
|
case "$why" in *'init.d/steer'*|*ерезапустите*|*ереустановите*) [ -n "$did" ] && why="$did; пришлите журнал в чат поддержки" ;; esac
|
||||||
echo "[FAIL] $what${why:+ — $why}"; bad=1 ;;
|
echo "[FAIL] $what${why:+ — $why}"; bad=1
|
||||||
|
[ -n "$tip" ] && echo " · $tip" ;;
|
||||||
esac
|
esac
|
||||||
done < "$f"
|
done < "$f.h"
|
||||||
rm -f "$f"
|
rm -f "$f" "$f.h"
|
||||||
|
[ -n "$nodeonly" ] && echo " · Само ядро steer исправно — не отвечает узел или сервер туннеля; перезапуск и переустановка ядра тут не помогут"
|
||||||
[ -n "$healed" ] && [ "$bad" = 0 ] && echo " · Поломку ядра панель исправила сама"
|
[ -n "$healed" ] && [ "$bad" = 0 ] && echo " · Поломку ядра панель исправила сама"
|
||||||
if [ "$bad" = 0 ]; then _rb_say "Проверка пройдена"; else _rb_warn "Проверка нашла поломку — подробности выше"; fi
|
if [ "$bad" != 0 ]; then _rb_warn "Проверка нашла поломку — подробности выше"
|
||||||
|
elif [ -n "$warned" ]; then _rb_say "Проверка пройдена — работе ничего не мешает, замечания выше"
|
||||||
|
else _rb_say "Проверка пройдена"; fi
|
||||||
return $bad
|
return $bad
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17833,8 +18085,17 @@ steer_action() {
|
|||||||
isep=","
|
isep=","
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
stl_present && _st_owns "steer-spec" && d="$(stl_diag)"
|
local hj=""
|
||||||
printf '{"warp":"%s","colo":"%s","vpn":"%s","vpn_ip":"%s","vpn_loc":"%s","tunnels":[%s],"ifs":[%s],"diag":%s}\n' "$warp" "$(esc "$colo")" "$vpn" "$(esc "$vip")" "$(esc "$vloc")" "$tun" "$itj" "${d:-null}"
|
if stl_present && _st_owns "steer-spec"; then
|
||||||
|
d="$(stl_diag)"
|
||||||
|
if [ "$d" != null ]; then
|
||||||
|
mkdir -p "$ST_RUN"
|
||||||
|
printf '%s\n' "$d" > "$ST_RUN/diag.ui.$$"
|
||||||
|
hj="$(_stl_diag_tsv "$ST_RUN/diag.ui.$$" | _st_diag_hjson)"
|
||||||
|
rm -f "$ST_RUN/diag.ui.$$"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
printf '{"warp":"%s","colo":"%s","vpn":"%s","vpn_ip":"%s","vpn_loc":"%s","tunnels":[%s],"ifs":[%s],"diag":%s,"human":[%s]}\n' "$warp" "$(esc "$colo")" "$vpn" "$(esc "$vip")" "$(esc "$vloc")" "$tun" "$itj" "${d:-null}" "$hj"
|
||||||
;;
|
;;
|
||||||
dns_fix)
|
dns_fix)
|
||||||
do_steer_dns_fix
|
do_steer_dns_fix
|
||||||
@@ -21405,6 +21666,65 @@ ZM_FK_FAST_EOF
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Секции Forkozz со списками b4geoip по ссылкам релизов (сохранены до 4.38 или вернулись из резервной копии):
|
||||||
|
# переводим их на raw-ссылки и перезапускаем Forkozz — не чаще раза в 30 минут
|
||||||
|
_fk_b4_watch() {
|
||||||
|
local f="$JOBS_DIR/fk-b4.last"
|
||||||
|
grep -qi 'greeg0ry/b4geoip-forkop/releases/' /etc/config/netshift 2>/dev/null || return 0
|
||||||
|
[ -n "$(find "$f" -mmin -30 2>/dev/null)" ] && return 0
|
||||||
|
_job_running forkop && return 0
|
||||||
|
mkdir -p "$JOBS_DIR"; : > "$f"
|
||||||
|
( _fk_device_setup apply >/dev/null 2>&1 </dev/null & )
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
_fk_ghdl_patch() {
|
||||||
|
local f=/usr/lib/netshift/helpers.sh
|
||||||
|
[ -f "$f" ] || return 0
|
||||||
|
grep -q '^# zm-ghdl-begin 1$' "$f" && return 0
|
||||||
|
if grep -q '^# zm-ghdl-begin' "$f"; then
|
||||||
|
sed -i '/^# zm-ghdl-begin/,/^# zm-ghdl-end$/d' "$f"
|
||||||
|
else
|
||||||
|
[ "$(grep -c '^download_to_file() {$' "$f")" = 1 ] && grep -q '^redact_url_for_log() {$' "$f" || return 0
|
||||||
|
sed -i 's/^download_to_file() {$/zm_download_to_file_raw() {/' "$f"
|
||||||
|
fi
|
||||||
|
grep -q '^zm_download_to_file_raw() {$' "$f" || return 0
|
||||||
|
cat >> "$f" << 'ZM_FK_GHDL_EOF'
|
||||||
|
# zm-ghdl-begin 1
|
||||||
|
# Zapret Manager: списки с GitHub. У части провайдеров файлы релизов GitHub (их отдаёт
|
||||||
|
# release-assets.githubusercontent.com) не открываются, хотя сам github.com открыт, — и каждый список
|
||||||
|
# ждал по минуте, а потом не скачивался. Если в панели «Доступ к GitHub» стоит «Авто», такой файл
|
||||||
|
# качается через зеркало. После первого провала напрямую следующие списки этого обновления сразу идут
|
||||||
|
# через зеркало, без минуты ожидания на каждом. Через VPN (http_proxy) и не с GitHub — как раньше.
|
||||||
|
download_to_file() {
|
||||||
|
local zm_url="$1" zm_fp="$2" zm_m zm_slow=/tmp/netshift.zm-ghslow zm_skip=""
|
||||||
|
case "$zm_url" in
|
||||||
|
https://github.com/*|https://raw.githubusercontent.com/*|https://objects.githubusercontent.com/*|https://release-assets.githubusercontent.com/*|https://codeload.github.com/*) ;;
|
||||||
|
*) zm_download_to_file_raw "$@"; return ;;
|
||||||
|
esac
|
||||||
|
[ -n "$3" ] && { zm_download_to_file_raw "$@"; return; }
|
||||||
|
[ -n "${ZM_GHDL_MS+x}" ] || ZM_GHDL_MS="$(/opt/zapret-manager-luci/backend.sh gh_mirrors 2>/dev/null)"
|
||||||
|
[ -n "$ZM_GHDL_MS" ] || { zm_download_to_file_raw "$@"; return; }
|
||||||
|
if [ -n "$(find "$zm_slow" -mmin -30 2>/dev/null)" ]; then zm_skip=1
|
||||||
|
else
|
||||||
|
zm_download_to_file_raw "$@" && return 0
|
||||||
|
: > "$zm_slow"
|
||||||
|
fi
|
||||||
|
for zm_m in $ZM_GHDL_MS; do
|
||||||
|
log "Downloading via GitHub mirror $zm_m: $(redact_url_for_log "$zm_url")" "warn"
|
||||||
|
if zm_download_to_file_raw "https://$zm_m/$zm_url" "$zm_fp" "" 1 1 15 && [ -s "$zm_fp" ] && ! head -c 512 "$zm_fp" | grep -qiE '<(!doctype|html)'; then
|
||||||
|
log "Downloaded via GitHub mirror $zm_m" "info"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ -n "$zm_skip" ] && zm_download_to_file_raw "$zm_url" "$zm_fp" "" 1 1 10; then rm -f "$zm_slow"; return 0; fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
# zm-ghdl-end
|
||||||
|
ZM_FK_GHDL_EOF
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
_fk_listsvia_patch() {
|
_fk_listsvia_patch() {
|
||||||
local f=/usr/bin/netshift tmp
|
local f=/usr/bin/netshift tmp
|
||||||
[ -f "$f" ] || return 0
|
[ -f "$f" ] || return 0
|
||||||
@@ -21731,6 +22051,8 @@ _fk_lists_progress() {
|
|||||||
/Importing community subnet lists for/ { print " → Адреса встроенных сервисов — заносим в файрвол"; next }
|
/Importing community subnet lists for/ { print " → Адреса встроенных сервисов — заносим в файрвол"; next }
|
||||||
/Importing domains from URL: / { u = $0; sub(/^.*URL: /, "", u); print " → Домены из списка " base(u); next }
|
/Importing domains from URL: / { u = $0; sub(/^.*URL: /, "", u); print " → Домены из списка " base(u); next }
|
||||||
/Importing subnets from URL: / { u = $0; sub(/^.*URL: /, "", u); print " → Адреса из списка " base(u) " — заносим в файрвол"; next }
|
/Importing subnets from URL: / { u = $0; sub(/^.*URL: /, "", u); print " → Адреса из списка " base(u) " — заносим в файрвол"; next }
|
||||||
|
/^Downloading via GitHub mirror / { m = $0; sub(/^Downloading via GitHub mirror /, "", m); sub(/:.*$/, "", m); print " · GitHub не отдал файл напрямую — качаем через зеркало " m; next }
|
||||||
|
/^Downloaded via GitHub mirror / { m = $0; sub(/^Downloaded via GitHub mirror /, "", m); print " ✓ Скачано через зеркало " m; next }
|
||||||
/^Download .* list failed/ { u = $2; print " ✗ Не скачался список " base(u); next }
|
/^Download .* list failed/ { u = $2; print " ✗ Не скачался список " base(u); next }
|
||||||
/Failed to decompile binary rule set/ { print " ✗ Набор правил не раскрылся — его адреса пропущены"; next }
|
/Failed to decompile binary rule set/ { print " ✗ Набор правил не раскрылся — его адреса пропущены"; next }
|
||||||
/Lists update completed successfully/ { print " ✓ Все списки загружены"; next }
|
/Lists update completed successfully/ { print " ✓ Все списки загружены"; next }
|
||||||
@@ -21845,6 +22167,12 @@ _fk_lists_errors() {
|
|||||||
[ -n "$e" ] || return 0
|
[ -n "$e" ] || return 0
|
||||||
echo "!! Не все списки загрузились:"
|
echo "!! Не все списки загрузились:"
|
||||||
printf '%s\n' "$e" | sed 's/^/ ✗ /'
|
printf '%s\n' "$e" | sed 's/^/ ✗ /'
|
||||||
|
case "$e" in *"сервер не ответил вовремя"*|*"сервер недоступен"*)
|
||||||
|
if [ "$(uci -q get netshift.settings.download_lists_via_proxy)" != 1 ] && [ -z "$(gh_mirrors)" ]; then
|
||||||
|
echo " · Похоже, провайдер не пускает к файлам GitHub (их отдаёт release-assets.githubusercontent.com). Включите"
|
||||||
|
echo " «Скачивать списки сервисов через VPN» в настройках Forkozz или «Авто» в Система → Доступ к GitHub и нажмите «Перезапустить»"
|
||||||
|
fi ;;
|
||||||
|
esac
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22046,6 +22374,7 @@ do_fk_install() {
|
|||||||
_fk_mlkem_patch
|
_fk_mlkem_patch
|
||||||
_fk_serial_patch
|
_fk_serial_patch
|
||||||
_fk_listsvia_patch
|
_fk_listsvia_patch
|
||||||
|
_fk_ghdl_patch
|
||||||
rm -rf "$tmp"
|
rm -rf "$tmp"
|
||||||
|
|
||||||
if [ ! -s /etc/config/netshift ]; then
|
if [ ! -s /etc/config/netshift ]; then
|
||||||
@@ -22383,6 +22712,7 @@ do_fk_service() {
|
|||||||
_fk_mlkem_patch
|
_fk_mlkem_patch
|
||||||
_fk_serial_patch
|
_fk_serial_patch
|
||||||
_fk_listsvia_patch
|
_fk_listsvia_patch
|
||||||
|
_fk_ghdl_patch
|
||||||
mkdir -p /etc/sing-box
|
mkdir -p /etc/sing-box
|
||||||
_fk_log_mark
|
_fk_log_mark
|
||||||
: > "$JOBS_DIR/forkop-svc.out"
|
: > "$JOBS_DIR/forkop-svc.out"
|
||||||
@@ -22453,6 +22783,7 @@ do_fk_lists() {
|
|||||||
_fk_mlkem_patch
|
_fk_mlkem_patch
|
||||||
_fk_serial_patch
|
_fk_serial_patch
|
||||||
_fk_listsvia_patch
|
_fk_listsvia_patch
|
||||||
|
_fk_ghdl_patch
|
||||||
_fk_log_mark
|
_fk_log_mark
|
||||||
/usr/bin/netshift list_update > "$out" 2>&1 &
|
/usr/bin/netshift list_update > "$out" 2>&1 &
|
||||||
lp=$!
|
lp=$!
|
||||||
@@ -22538,6 +22869,7 @@ do_fk_subs() {
|
|||||||
_fk_mlkem_patch
|
_fk_mlkem_patch
|
||||||
_fk_serial_patch
|
_fk_serial_patch
|
||||||
_fk_listsvia_patch
|
_fk_listsvia_patch
|
||||||
|
_fk_ghdl_patch
|
||||||
_zm_quiet "" /usr/bin/netshift subscription_update all || { echo "ОШИБКА: подписка не обновилась — проверьте ссылку"; return 1; }
|
_zm_quiet "" /usr/bin/netshift subscription_update all || { echo "ОШИБКА: подписка не обновилась — проверьте ссылку"; return 1; }
|
||||||
_fk_ext_auto all "$lx"
|
_fk_ext_auto all "$lx"
|
||||||
_fk_say "Готово: подписки обновлены"
|
_fk_say "Готово: подписки обновлены"
|
||||||
@@ -25011,6 +25343,7 @@ case "$cmd" in
|
|||||||
ui_flags_get) ui_flags_get ;;
|
ui_flags_get) ui_flags_get ;;
|
||||||
ui_flag_set) ui_flag_set "$1" ;;
|
ui_flag_set) ui_flag_set "$1" ;;
|
||||||
adblock_dns_sync) _ab_dns_sync ;;
|
adblock_dns_sync) _ab_dns_sync ;;
|
||||||
|
zapret_doh_migrate) [ -f "$ZO_DOH_FLAG" ] && [ -f "$CONF" ] && _zo_doh_stale && { _zo_doh_follow; echo '{"changed":true}'; } || echo '{"changed":false}' ;;
|
||||||
multi) _zm_multi "$1" ;;
|
multi) _zm_multi "$1" ;;
|
||||||
*) echo '{"error":"неизвестная команда"}'; exit 1 ;;
|
*) echo '{"error":"неизвестная команда"}'; exit 1 ;;
|
||||||
esac
|
esac
|
||||||
@@ -25051,6 +25384,17 @@ const SRS_MAIN = "https://github.com/itdoginfo/allow-domains/releases/latest/dow
|
|||||||
const ITD_SUBNETS = { cloudflare: true, cloudfront: true, digitalocean: true, discord: true, google_meet: true, hetzner: true,
|
const ITD_SUBNETS = { cloudflare: true, cloudfront: true, digitalocean: true, discord: true, google_meet: true, hetzner: true,
|
||||||
meta: true, ovh: true, roblox: true, telegram: true, twitter: true };
|
meta: true, ovh: true, roblox: true, telegram: true, twitter: true };
|
||||||
const CAT_SKIP = { mydyson: true, itdoginfo_meta: true };
|
const CAT_SKIP = { mydyson: true, itdoginfo_meta: true };
|
||||||
|
// b4geoip (Greeg0ry/b4geoip-forkop). Файлы его релизов отдаёт release-assets.githubusercontent.com — у части
|
||||||
|
// провайдеров он не отвечает, и списки не скачивались. Те же .srs каждый день кладутся в ветку main
|
||||||
|
// репозитория — берём их оттуда, с raw.githubusercontent.com (как и списки itdoginfo).
|
||||||
|
const B4_RAW = "https://raw.githubusercontent.com/Greeg0ry/b4geoip-forkop/main/srs/";
|
||||||
|
// Что внутри наборов: twitch и xbox — только домены, остальные (сборка из geoip.dat) — только подсети.
|
||||||
|
// Каждый подключается туда, где он работает, и качается один раз. Незнакомый новый набор — как раньше:
|
||||||
|
// и доменами, и подсетями, так он сработает в любом случае.
|
||||||
|
const B4_DOMAINS = { twitch: true, xbox: true };
|
||||||
|
const B4_SUBNETS = { adobe: true, anthropic: true, apple: true, blizzard: true, bungie: true, ccp: true, electronicarts: true,
|
||||||
|
epicgames: true, google: true, nintendo: true, play2go: true, riot: true, roblox: true, sony: true, taketwo: true,
|
||||||
|
ubisoft: true, valve: true, wargaming: true };
|
||||||
const PKG_LOCAL_DIR = "/usr/share/zm-redbtn/svc/";
|
const PKG_LOCAL_DIR = "/usr/share/zm-redbtn/svc/";
|
||||||
const V2F_RAW = "https://raw.githubusercontent.com/v2fly/domain-list-community/master/data";
|
const V2F_RAW = "https://raw.githubusercontent.com/v2fly/domain-list-community/master/data";
|
||||||
const PKG_LISTS = {
|
const PKG_LISTS = {
|
||||||
@@ -25175,6 +25519,28 @@ function latest_url(u) {
|
|||||||
return replace(s(u), /^(https:\/\/github\.com\/[^\/]+\/[^\/]+\/releases\/)download\/[^\/]+\//, "$1latest/download/");
|
return replace(s(u), /^(https:\/\/github\.com\/[^\/]+\/[^\/]+\/releases\/)download\/[^\/]+\//, "$1latest/download/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Имя набора b4geoip по ссылке (релиз — последний или с датой — или ветка main), иначе null
|
||||||
|
function b4_name(u) {
|
||||||
|
let m = match(s(u), /^https:\/\/(github\.com\/greeg0ry\/b4geoip-forkop\/releases\/(latest\/download|download\/[^\/]+)|raw\.githubusercontent\.com\/greeg0ry\/b4geoip-forkop\/(refs\/heads\/)?main\/srs)\/([a-z0-9_-]+)\.srs$/i);
|
||||||
|
return m ? lc(m[4]) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Набор b4geoip → { k, u }: k — куда он подключается (s — домены, n — подсети, r — и то и другое), u — raw-ссылка
|
||||||
|
function b4_ref(n) {
|
||||||
|
return { k: B4_DOMAINS[n] ? "s" : B4_SUBNETS[n] ? "n" : "r", u: B4_RAW + n + ".srs" };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Наборы правил секции (s — домены, r — домены и подсети, n — только подсети): b4geoip — по raw-ссылке и своего типа
|
||||||
|
function refs_fix(r) {
|
||||||
|
let o = { c: r.c || [], s: [], r: [], n: [], l: r.l || [] };
|
||||||
|
for (let k in [ "s", "r", "n" ]) for (let u in (r[k] || [])) {
|
||||||
|
let b = b4_name(u), x = b ? b4_ref(b) : { k, u };
|
||||||
|
push(o[x.k], x.u);
|
||||||
|
}
|
||||||
|
o.s = uniq(o.s); o.r = uniq(o.r); o.n = uniq(o.n);
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
function kind_hint(u) {
|
function kind_hint(u) {
|
||||||
u = lc(s(u));
|
u = lc(s(u));
|
||||||
for (let w in [ "geosite", "domain", "adguard", "filter" ]) if (index(u, w) >= 0) return "domains";
|
for (let w in [ "geosite", "domain", "adguard", "filter" ]) if (index(u, w) >= 0) return "domains";
|
||||||
@@ -25204,6 +25570,8 @@ function idx_tokens(e, known) {
|
|||||||
if (n && known[n]) return [ "c:" + n ];
|
if (n && known[n]) return [ "c:" + n ];
|
||||||
if (e.fmt == "lst") return [ "l:" + e.url ];
|
if (e.fmt == "lst") return [ "l:" + e.url ];
|
||||||
if (n) return itd_plain(n);
|
if (n) return itd_plain(n);
|
||||||
|
let b4 = b4_name(e.url);
|
||||||
|
if (b4) { let x = b4_ref(b4); return [ x.k + ":" + x.u ]; }
|
||||||
let u = latest_url(e.url);
|
let u = latest_url(e.url);
|
||||||
if (e.kind == "d") return [ "s:" + u ];
|
if (e.kind == "d") return [ "s:" + u ];
|
||||||
if (kind_hint(u) == "domains") return [];
|
if (kind_hint(u) == "domains") return [];
|
||||||
@@ -25421,7 +25789,7 @@ function url_ext(u) {
|
|||||||
function is_rs(u) { let e = url_ext(u); return e == "srs" || e == "json"; }
|
function is_rs(u) { let e = url_ext(u); return e == "srs" || e == "json"; }
|
||||||
|
|
||||||
function sec_refs(m) {
|
function sec_refs(m) {
|
||||||
let r = { c: uniq(words(m.community_lists)), s: [], r: [], l: [] }, zr = rawlist(m.zm_refs);
|
let r = { c: uniq(words(m.community_lists)), s: [], r: [], n: [], l: [] }, zr = rawlist(m.zm_refs);
|
||||||
if (length(zr)) {
|
if (length(zr)) {
|
||||||
for (let t in zr) {
|
for (let t in zr) {
|
||||||
let k = substr(t, 0, 1), v = substr(t, 2);
|
let k = substr(t, 0, 1), v = substr(t, 2);
|
||||||
@@ -25434,11 +25802,11 @@ function sec_refs(m) {
|
|||||||
else if (index(sn, u) >= 0) push(r.r, u);
|
else if (index(sn, u) >= 0) push(r.r, u);
|
||||||
else push(r.s, u);
|
else push(r.s, u);
|
||||||
}
|
}
|
||||||
for (let u in sn) if (index(d, u) < 0) push(is_rs(u) ? r.r : r.l, u);
|
for (let u in sn) if (index(d, u) < 0) push(is_rs(u) ? r.n : r.l, u);
|
||||||
for (let u in [ ...words(m.local_domain_lists), ...words(m.local_subnet_lists) ]) push(r.l, u);
|
for (let u in [ ...words(m.local_domain_lists), ...words(m.local_subnet_lists) ]) push(r.l, u);
|
||||||
}
|
}
|
||||||
r.s = uniq(r.s); r.r = uniq(r.r); r.l = uniq(r.l);
|
r.l = uniq(r.l);
|
||||||
return r;
|
return refs_fix(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sec_configured(m) {
|
function sec_configured(m) {
|
||||||
@@ -25459,7 +25827,7 @@ function sec_summary(c, sec, main) {
|
|||||||
return {
|
return {
|
||||||
name: sec, main, label: sec_label(m, main), enabled: main || s(m.disabled) != "1", mode,
|
name: sec, main, label: sec_label(m, main), enabled: main || s(m.disabled) != "1", mode,
|
||||||
iface: s(m.interface), links: length(sec_links(m)),
|
iface: s(m.interface), links: length(sec_links(m)),
|
||||||
refs: { c: rf.c, s: rf.s, r: rf.r }, lists: rf.l,
|
refs: { c: rf.c, s: rf.s, r: rf.r, n: rf.n }, lists: rf.l,
|
||||||
domains: length(domain_lines(m)), subnets: length(subnet_lines(m)), own_off: own_off(m),
|
domains: length(domain_lines(m)), subnets: length(subnet_lines(m)), own_off: own_off(m),
|
||||||
full: device_ips(m), all: all_ips(m)
|
full: device_ips(m), all: all_ips(m)
|
||||||
};
|
};
|
||||||
@@ -25754,8 +26122,39 @@ function patch_device_engine() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Секции, сохранённые до 4.38 (или пришедшие из резервной копии): b4geoip — по ссылкам релизов, и каждый
|
||||||
|
// набор подключён и доменами, и подсетями. Переводим на raw-ссылки и правильный тип; остальное не трогаем
|
||||||
|
function b4_migrate() {
|
||||||
|
let c = cursor(), ch = false;
|
||||||
|
for (let x in sections(c, "section")) {
|
||||||
|
let n = x[".name"], zr = rawlist(x.zm_refs), d = rawlist(x.remote_domain_lists), sn = rawlist(x.remote_subnet_lists);
|
||||||
|
let zr2 = [], d2 = [], sn2 = [];
|
||||||
|
for (let t in zr) {
|
||||||
|
let k = substr(t, 0, 1), b = substr(t, 1, 1) == ":" && index([ "s", "r", "n" ], k) >= 0 ? b4_name(substr(t, 2)) : null;
|
||||||
|
if (b) { let y = b4_ref(b); push(zr2, y.k + ":" + y.u); } else push(zr2, t);
|
||||||
|
}
|
||||||
|
for (let u in d) {
|
||||||
|
let b = b4_name(u), y = b ? b4_ref(b) : null;
|
||||||
|
if (!y) push(d2, u); else push(y.k == "n" ? sn2 : d2, y.u);
|
||||||
|
}
|
||||||
|
for (let u in sn) {
|
||||||
|
let b = b4_name(u), y = b ? b4_ref(b) : null;
|
||||||
|
if (!y) push(sn2, u); else push(y.k == "s" ? d2 : sn2, y.u);
|
||||||
|
}
|
||||||
|
zr2 = uniq(zr2); d2 = uniq(d2); sn2 = uniq(sn2);
|
||||||
|
if (join("\n", zr2) == join("\n", zr) && join("\n", d2) == join("\n", d) && join("\n", sn2) == join("\n", sn)) continue;
|
||||||
|
if (length(zr)) set_list(c, n, "zm_refs", zr2);
|
||||||
|
set_list(c, n, "remote_domain_lists", d2);
|
||||||
|
set_list(c, n, "remote_subnet_lists", sn2);
|
||||||
|
ch = true;
|
||||||
|
}
|
||||||
|
if (ch) { c.save(CFG); c.commit(CFG); }
|
||||||
|
return ch;
|
||||||
|
}
|
||||||
|
|
||||||
function cmd_device_setup() {
|
function cmd_device_setup() {
|
||||||
out({ ok: true, changed: patch_device_engine() });
|
let eng = patch_device_engine(), lists = b4_migrate();
|
||||||
|
out({ ok: true, changed: eng || lists, lists });
|
||||||
}
|
}
|
||||||
|
|
||||||
function del_keys(c, sec, keys) { for (let k in keys) c.delete(CFG, sec, k); }
|
function del_keys(c, sec, keys) { for (let k in keys) c.delete(CFG, sec, k); }
|
||||||
@@ -25785,11 +26184,11 @@ function apply_servers(c, sec) {
|
|||||||
function set_lists(c, sec, rf, services, domains, subnets) {
|
function set_lists(c, sec, rf, services, domains, subnets) {
|
||||||
set_list(c, sec, "community_lists", services);
|
set_list(c, sec, "community_lists", services);
|
||||||
let zr = [];
|
let zr = [];
|
||||||
for (let k in [ "s", "r", "l" ]) for (let v in rf[k]) push(zr, k + ":" + v);
|
for (let k in [ "s", "r", "n", "l" ]) for (let v in (rf[k] || [])) push(zr, k + ":" + v);
|
||||||
set_list(c, sec, "zm_refs", zr);
|
set_list(c, sec, "zm_refs", zr);
|
||||||
let rl = filter(rf.l, (u) => !pkg_local(u)), ll = filter(rf.l, (u) => pkg_local(u));
|
let rl = filter(rf.l, (u) => !pkg_local(u)), ll = filter(rf.l, (u) => pkg_local(u));
|
||||||
set_list(c, sec, "remote_domain_lists", uniq([ ...rf.s, ...rf.r, ...rl ]));
|
set_list(c, sec, "remote_domain_lists", uniq([ ...rf.s, ...rf.r, ...rl ]));
|
||||||
set_list(c, sec, "remote_subnet_lists", uniq([ ...rf.r, ...rl ]));
|
set_list(c, sec, "remote_subnet_lists", uniq([ ...rf.r, ...(rf.n || []), ...rl ]));
|
||||||
del_keys(c, sec, [ "local_domain_lists", "local_subnet_lists", "user_domains", "user_subnets" ]);
|
del_keys(c, sec, [ "local_domain_lists", "local_subnet_lists", "user_domains", "user_subnets" ]);
|
||||||
set_list(c, sec, "local_domain_lists", uniq(ll));
|
set_list(c, sec, "local_domain_lists", uniq(ll));
|
||||||
if (length(domains)) {
|
if (length(domains)) {
|
||||||
@@ -26210,7 +26609,7 @@ function cmd_get(want) {
|
|||||||
exclude: s(m.zm_exclude),
|
exclude: s(m.zm_exclude),
|
||||||
hide_names: rawlist(m.zm_hide),
|
hide_names: rawlist(m.zm_hide),
|
||||||
jsons: mode == "json" ? 1 : 0,
|
jsons: mode == "json" ? 1 : 0,
|
||||||
refs: { c: rf.c, s: rf.s, r: rf.r },
|
refs: { c: rf.c, s: rf.s, r: rf.r, n: rf.n },
|
||||||
catalog: { items: catalog_items(services_list()), meta: cat_meta() },
|
catalog: { items: catalog_items(services_list()), meta: cat_meta() },
|
||||||
own_on: !own_off(m),
|
own_on: !own_off(m),
|
||||||
domains: own_view(m, rf.l).domains,
|
domains: own_view(m, rf.l).domains,
|
||||||
@@ -26285,10 +26684,11 @@ function cmd_set() {
|
|||||||
|
|
||||||
let known = services_list(), refs = type(d.refs) == "object" ? d.refs : {};
|
let known = services_list(), refs = type(d.refs) == "object" ? d.refs : {};
|
||||||
let services = filter(uniq(words(refs.c)), (x) => index(known, x) >= 0);
|
let services = filter(uniq(words(refs.c)), (x) => index(known, x) >= 0);
|
||||||
let rsets = uniq(words(refs.s)), rsubs = uniq(words(refs.r)), rplain = uniq(words(refs.l));
|
let rsets = uniq(words(refs.s)), rsubs = uniq(words(refs.r)), rnets = uniq(words(refs.n)), rplain = uniq(words(refs.l));
|
||||||
for (let x in uniq(words(refs.c))) if (index(known, x) < 0 && EXTRA_SRS[x]) push(rsets, EXTRA_SRS[x]);
|
for (let x in uniq(words(refs.c))) if (index(known, x) < 0 && EXTRA_SRS[x]) push(rsets, EXTRA_SRS[x]);
|
||||||
rsets = uniq(rsets);
|
let rfx = refs_fix({ s: rsets, r: rsubs, n: rnets });
|
||||||
for (let x in [ ...rsets, ...rsubs ])
|
rsets = rfx.s; rsubs = rfx.r; rnets = rfx.n;
|
||||||
|
for (let x in [ ...rsets, ...rsubs, ...rnets ])
|
||||||
if (!match(x, /^https?:\/\/[^ \t]+$/i)) fail("неверная ссылка на набор правил: " + substr(x, 0, 60));
|
if (!match(x, /^https?:\/\/[^ \t]+$/i)) fail("неверная ссылка на набор правил: " + substr(x, 0, 60));
|
||||||
for (let x in rplain)
|
for (let x in rplain)
|
||||||
if (!valid_url(x) && !pkg_local(x)) fail("неверная ссылка на список: " + substr(x, 0, 60));
|
if (!valid_url(x) && !pkg_local(x)) fail("неверная ссылка на список: " + substr(x, 0, 60));
|
||||||
@@ -26306,7 +26706,7 @@ function cmd_set() {
|
|||||||
let all = d.all == null ? all_ips(c.get_all(CFG, sec) || {}) : uniq(tokens(d.all));
|
let all = d.all == null ? all_ips(c.get_all(CFG, sec) || {}) : uniq(tokens(d.all));
|
||||||
for (let x in [ ...full, ...excl, ...all ]) if (!valid_ip(x)) fail("неверный адрес устройства: " + x);
|
for (let x in [ ...full, ...excl, ...all ]) if (!valid_ip(x)) fail("неверный адрес устройства: " + x);
|
||||||
if (length(all) > 64) fail("устройств с полным трафиком через VPN можно выбрать не больше 64");
|
if (length(all) > 64) fail("устройств с полным трафиком через VPN можно выбрать не больше 64");
|
||||||
if (!length(all) && !length(services) && !length(rsets) && !length(rsubs) && !length(domains) && !length(subnets) && !length(lists) && (length(full) || created || !sec_configured(c.get_all(CFG, sec) || {})))
|
if (!length(all) && !length(services) && !length(rsets) && !length(rsubs) && !length(rnets) && !length(domains) && !length(subnets) && !length(lists) && (length(full) || created || !sec_configured(c.get_all(CFG, sec) || {})))
|
||||||
fail(!own_on && (length(keep.domains) || length(keep.subnets) || length(keep.lists))
|
fail(!own_on && (length(keep.domains) || length(keep.subnets) || length(keep.lists))
|
||||||
? "«Свой список» выключен, а других сервисов в секции нет — включите его или выберите сервис"
|
? "«Свой список» выключен, а других сервисов в секции нет — включите его или выберите сервис"
|
||||||
: "выберите для секции хотя бы один сервис, домен или адрес — устройства только ограничивают её правила");
|
: "выберите для секции хотя бы один сервис, домен или адрес — устройства только ограничивают её правила");
|
||||||
@@ -26319,7 +26719,7 @@ function cmd_set() {
|
|||||||
if (!device_scopes_overlap(full, device_ips(x))) continue;
|
if (!device_scopes_overlap(full, device_ips(x))) continue;
|
||||||
let other = sec_label(x, n == p0.sec), orf = sec_refs(x);
|
let other = sec_label(x, n == p0.sec), orf = sec_refs(x);
|
||||||
for (let v in services) if (index(words(x.community_lists), v) >= 0) fail("список «" + v + "» уже идёт через секцию «" + other + "» — один список можно включить только в одной секции");
|
for (let v in services) if (index(words(x.community_lists), v) >= 0) fail("список «" + v + "» уже идёт через секцию «" + other + "» — один список можно включить только в одной секции");
|
||||||
for (let v in [ ...rsets, ...rsubs ]) if (index([ ...orf.s, ...orf.r ], v) >= 0) fail("набор правил уже идёт через секцию «" + other + "»: " + substr(v, 0, 60));
|
for (let v in [ ...rsets, ...rsubs, ...rnets ]) if (index([ ...orf.s, ...orf.r, ...orf.n ], v) >= 0) fail("набор правил уже идёт через секцию «" + other + "»: " + substr(v, 0, 60));
|
||||||
let od = domain_lines(x);
|
let od = domain_lines(x);
|
||||||
for (let v in domains) if (index(od, v) >= 0) fail("домен " + v + " уже есть в секции «" + other + "» — оставьте его в одной секции");
|
for (let v in domains) if (index(od, v) >= 0) fail("домен " + v + " уже есть в секции «" + other + "» — оставьте его в одной секции");
|
||||||
let os = subnet_lines(x);
|
let os = subnet_lines(x);
|
||||||
@@ -26378,7 +26778,7 @@ function cmd_set() {
|
|||||||
}
|
}
|
||||||
write_sec_ext(c, sec, mode, d.x);
|
write_sec_ext(c, sec, mode, d.x);
|
||||||
|
|
||||||
set_lists(c, sec, { s: rsets, r: rsubs, l: lists }, services, domains, subnets);
|
set_lists(c, sec, { s: rsets, r: rsubs, n: rnets, l: lists }, services, domains, subnets);
|
||||||
if (own_on || !(length(keep.domains) || length(keep.subnets) || length(keep.lists))) {
|
if (own_on || !(length(keep.domains) || length(keep.subnets) || length(keep.lists))) {
|
||||||
del_keys(c, sec, [ "zm_own_off", "zm_own_domains", "zm_own_subnets", "zm_own_lists" ]);
|
del_keys(c, sec, [ "zm_own_off", "zm_own_domains", "zm_own_subnets", "zm_own_lists" ]);
|
||||||
} else {
|
} else {
|
||||||
@@ -26395,7 +26795,7 @@ function cmd_set() {
|
|||||||
c.set(CFG, BYPASS, "zm_label", "Исключения");
|
c.set(CFG, BYPASS, "zm_label", "Исключения");
|
||||||
c.set(CFG, BYPASS, "connection_type", "exclusion");
|
c.set(CFG, BYPASS, "connection_type", "exclusion");
|
||||||
c.delete(CFG, BYPASS, "disabled");
|
c.delete(CFG, BYPASS, "disabled");
|
||||||
set_lists(c, BYPASS, { s: [], r: [], l: [] }, [], bdom, bsub);
|
set_lists(c, BYPASS, { s: [], r: [], n: [], l: [] }, [], bdom, bsub);
|
||||||
c.delete(CFG, BYPASS, "zm_refs");
|
c.delete(CFG, BYPASS, "zm_refs");
|
||||||
c.reorder(CFG, BYPASS, 1);
|
c.reorder(CFG, BYPASS, 1);
|
||||||
}
|
}
|
||||||
@@ -27643,7 +28043,7 @@ function where_scan(host, ip, win) {
|
|||||||
if (xe != null) add(names["c:" + cn] ?? cn, cn + "_extra.lst", xe);
|
if (xe != null) add(names["c:" + cn] ?? cn, cn + "_extra.lst", xe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (let k in [ "s", "r" ]) for (let u in rf[k]) {
|
for (let k in [ "s", "r", "n" ]) for (let u in rf[k]) {
|
||||||
used[k + ":" + u] = true;
|
used[k + ":" + u] = true;
|
||||||
if (srs_hit(url_rs(u), host, ip, on)) add(names[k + ":" + u] ?? ref_base(u), ref_base(u), "");
|
if (srs_hit(url_rs(u), host, ip, on)) add(names[k + ":" + u] ?? ref_base(u), ref_base(u), "");
|
||||||
}
|
}
|
||||||
@@ -27663,7 +28063,7 @@ function where_scan(host, ip, win) {
|
|||||||
if (e != null) { hit = { list: ref_base(v), entry: e }; break; }
|
if (e != null) { hit = { list: ref_base(v), entry: e }; break; }
|
||||||
} else if (k == "c") {
|
} else if (k == "c") {
|
||||||
if (srs_hit({ type: "remote", tag: "zmw-" + v + "-community-ruleset", url: SRS_MAIN + "/" + v + ".srs", format: "binary" }, host, ip, false)) { hit = { list: v + ".srs", entry: "" }; break; }
|
if (srs_hit({ type: "remote", tag: "zmw-" + v + "-community-ruleset", url: SRS_MAIN + "/" + v + ".srs", format: "binary" }, host, ip, false)) { hit = { list: v + ".srs", entry: "" }; break; }
|
||||||
} else if (k == "s" || k == "r") {
|
} else if (k == "s" || k == "r" || k == "n") {
|
||||||
if (srs_hit(url_rs(v), host, ip, false)) { hit = { list: ref_base(v), entry: "" }; break; }
|
if (srs_hit(url_rs(v), host, ip, false)) { hit = { list: ref_base(v), entry: "" }; break; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -33808,7 +34208,15 @@ return view.extend({
|
|||||||
if (data.stopped) return badge('zm-off', 'Steer выключен');
|
if (data.stopped) return badge('zm-off', 'Steer выключен');
|
||||||
if (secOff(id)) return badge('zm-off', 'секция выключена');
|
if (secOff(id)) return badge('zm-off', 'секция выключена');
|
||||||
if (ex === 'none') return badge('zm-warn', s && s.kind === 'vpn' ? 'нет подписки' : s && s.kind === 'iface' ? 'нет туннеля' : s ? 'WARP не подключён' : 'нет подключения');
|
if (ex === 'none') return badge('zm-warn', s && s.kind === 'vpn' ? 'нет подписки' : s && s.kind === 'iface' ? 'нет туннеля' : s ? 'WARP не подключён' : 'нет подключения');
|
||||||
if (!secWork(id)) return badge('zm-off', 'ничего не выбрано');
|
if (!secWork(id)) {
|
||||||
|
var iv = s && ex === 'vpn' && data.steer_running ? (s.vpn && s.vpn.state) || {} : null;
|
||||||
|
if (iv && !iv.up && (iv.failed || iv.why || (iv.probe && iv.probe.state && iv.probe.state !== 'probing'))) {
|
||||||
|
var ib = badge('zm-warn', 'узел не отвечает');
|
||||||
|
ib.title = 'В секции ничего не выбрано, поэтому на работу это не влияет' + (iv.why ? '. Причина: ' + iv.why : '');
|
||||||
|
return ib;
|
||||||
|
}
|
||||||
|
return badge('zm-off', 'ничего не выбрано');
|
||||||
|
}
|
||||||
if (!data.steer_running) return badge('zm-bad', 'не работает');
|
if (!data.steer_running) return badge('zm-bad', 'не работает');
|
||||||
if (ex === 'vpn') {
|
if (ex === 'vpn') {
|
||||||
if (!s) return data.failopen ? badge('zm-bad', 'VPN не принят') : data.vpn_live === false && data.vpn_up ? badge('zm-bad', 'нет связи') : data.vpn_up ? badge('zm-ok', 'работает') : badge('zm-warn', 'подключаемся');
|
if (!s) return data.failopen ? badge('zm-bad', 'VPN не принят') : data.vpn_live === false && data.vpn_up ? badge('zm-bad', 'нет связи') : data.vpn_up ? badge('zm-ok', 'работает') : badge('zm-warn', 'подключаемся');
|
||||||
@@ -34816,14 +35224,18 @@ return view.extend({
|
|||||||
else items.push([ 'fail', who + ': трафик через туннель не идёт', t.kind === 'awg' ? 'почините его на вкладке AmneziaWG' : 'проверьте подключение в Сеть → Интерфейсы' ]);
|
else items.push([ 'fail', who + ': трафик через туннель не идёт', t.kind === 'awg' ? 'почините его на вкладке AmneziaWG' : 'проверьте подключение в Сеть → Интерфейсы' ]);
|
||||||
});
|
});
|
||||||
var d = diagRes.diag;
|
var d = diagRes.diag;
|
||||||
if (d && d.checks) d.checks.forEach(function(c) {
|
if (Array.isArray(diagRes.human) && diagRes.human.length) diagRes.human.forEach(function(c) { items.push([ c.v, c.what, c.why, c.tip, c.note ]); });
|
||||||
|
else if (d && d.checks) d.checks.forEach(function(c) {
|
||||||
if (c.verdict === 'ok' || c.verdict === 'warn' || c.verdict === 'fail') items.push([ c.verdict, c.what, c.why ]);
|
if (c.verdict === 'ok' || c.verdict === 'warn' || c.verdict === 'fail') items.push([ c.verdict, c.what, c.why ]);
|
||||||
});
|
});
|
||||||
var bad = items.filter(function(i) { return i[0] === 'fail'; }).length, warn = items.filter(function(i) { return i[0] === 'warn'; }).length;
|
var bad = items.filter(function(i) { return i[0] === 'fail'; }).length, warn = items.filter(function(i) { return i[0] === 'warn'; }).length;
|
||||||
checkCard.appendChild(chkRow(E('span', { 'class': 'zm-label' }, 'Итог'), bad ? badge('zm-bad', 'есть поломка') : warn ? badge('zm-warn', 'работает, есть замечания') : badge('zm-ok', 'всё в порядке')));
|
checkCard.appendChild(chkRow(E('span', { 'class': 'zm-label' }, 'Итог'), bad ? badge('zm-bad', 'есть поломка') : warn ? badge('zm-warn', 'работает, есть замечания') : badge('zm-ok', 'всё в порядке')));
|
||||||
var CLS = { ok: 'zm-ok', warn: 'zm-warn', fail: 'zm-bad' }, TXT = { ok: 'ок', warn: 'внимание', fail: 'ошибка' };
|
var CLS = { ok: 'zm-ok', warn: 'zm-warn', fail: 'zm-bad' }, TXT = { ok: 'ок', warn: 'внимание', fail: 'ошибка' };
|
||||||
items.forEach(function(i) {
|
items.forEach(function(i) {
|
||||||
checkCard.appendChild(chkRow(badge(CLS[i[0]], TXT[i[0]]), E('span', {}, [].concat(i[1], i[2] ? ' — ' + i[2] : ''))));
|
var body = [ E('span', {}, [].concat(i[1], i[2] ? ' — ' + i[2] : '')) ];
|
||||||
|
if (i[3]) body.push(E('span', { 'class': 'zm-chk-tip' }, i[3]));
|
||||||
|
if (i[4]) body.push(E('span', { 'class': 'zm-chk-tip zm-chk-note' }, i[4]));
|
||||||
|
checkCard.appendChild(chkRow(badge(CLS[i[0]], TXT[i[0]]), body.length > 1 ? E('span', {}, body) : body[0]));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
checkCard.appendChild(E('div', { 'class': 'zm-actions' }, [
|
checkCard.appendChild(E('div', { 'class': 'zm-actions' }, [
|
||||||
@@ -40042,6 +40454,9 @@ html.zm-theme-dark .zm-card {
|
|||||||
.zm-row.zm-chk { flex-wrap: nowrap; align-items: baseline; }
|
.zm-row.zm-chk { flex-wrap: nowrap; align-items: baseline; }
|
||||||
.zm-row.zm-chk > .zm-chk-h { flex: 0 0 104px; min-width: 0; }
|
.zm-row.zm-chk > .zm-chk-h { flex: 0 0 104px; min-width: 0; }
|
||||||
.zm-row.zm-chk > .zm-chk-b { flex: 1 1 auto; min-width: 0; line-height: 1.5; }
|
.zm-row.zm-chk > .zm-chk-b { flex: 1 1 auto; min-width: 0; line-height: 1.5; }
|
||||||
|
.zm-chk-tip { display: block; margin-top: 3px; font-size: 12px; line-height: 1.45; opacity: .78; }
|
||||||
|
.zm-chk-tip::before { content: "→ "; opacity: .7; }
|
||||||
|
.zm-chk-note::before { content: "· "; }
|
||||||
|
|
||||||
.zm-badge { display: inline-flex; align-items: center; gap: 6px; padding: 3px 11px; border-radius: 999px; font-size: 12px; font-weight: 600; white-space: nowrap; }
|
.zm-badge { display: inline-flex; align-items: center; gap: 6px; padding: 3px 11px; border-radius: 999px; font-size: 12px; font-weight: 600; white-space: nowrap; }
|
||||||
.zm-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; flex-shrink: 0; }
|
.zm-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; flex-shrink: 0; }
|
||||||
@@ -44169,7 +44584,7 @@ function pickFromCfg(c) {
|
|||||||
c = c || {};
|
c = c || {};
|
||||||
var items = ((c.catalog && c.catalog.items) || []).filter(function(it) { return it && it.id && (it.targets || []).length; });
|
var items = ((c.catalog && c.catalog.items) || []).filter(function(it) { return it && it.id && (it.targets || []).length; });
|
||||||
var refs = c.refs || {}, toks = [], present = {}, sel = {}, covered = {}, extra = [];
|
var refs = c.refs || {}, toks = [], present = {}, sel = {}, covered = {}, extra = [];
|
||||||
[ 'c', 's', 'r' ].forEach(function(k) { (refs[k] || []).forEach(function(v) { toks.push(k + ':' + v); }); });
|
[ 'c', 's', 'r', 'n' ].forEach(function(k) { (refs[k] || []).forEach(function(v) { toks.push(k + ':' + v); }); });
|
||||||
var lists = (c.lists || []).slice();
|
var lists = (c.lists || []).slice();
|
||||||
toks.forEach(function(t) { present[refNorm(t)] = true; });
|
toks.forEach(function(t) { present[refNorm(t)] = true; });
|
||||||
lists.forEach(function(u) { present[refNorm('l:' + u)] = true; });
|
lists.forEach(function(u) { present[refNorm('l:' + u)] = true; });
|
||||||
@@ -44189,7 +44604,7 @@ function pickFromCfg(c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function refsOf(d) {
|
function refsOf(d) {
|
||||||
var out = { c: [], s: [], r: [], l: [] }, seen = {};
|
var out = { c: [], s: [], r: [], n: [], l: [] }, seen = {};
|
||||||
(d.items || []).forEach(function(it) {
|
(d.items || []).forEach(function(it) {
|
||||||
if (!d.sel[it.id]) return;
|
if (!d.sel[it.id]) return;
|
||||||
it.targets.forEach(function(t) {
|
it.targets.forEach(function(t) {
|
||||||
@@ -44485,7 +44900,7 @@ function validCfg(x) {
|
|||||||
function sig(d) {
|
function sig(d) {
|
||||||
var srt = function(a) { return (a || []).slice().sort(); };
|
var srt = function(a) { return (a || []).slice().sort(); };
|
||||||
var tl = function(v) { return String(v || '').split(/\n/).map(function(x) { return x.trim(); }).filter(Boolean).join('\n'); };
|
var tl = function(v) { return String(v || '').split(/\n/).map(function(x) { return x.trim(); }).filter(Boolean).join('\n'); };
|
||||||
return JSON.stringify([ extSig(d, SEC_EXT), extSig(d, SET_EXT), d.sec, d.mode, tl(d.links), tl(d.sub), d.sub_interval, d.iface, d.fastest, (function(r) { return [ srt(r.c), srt(r.s), srt(r.r), srt(r.l) ]; })(refsOf(d)), tl(d.domains), d.own_on !== false,
|
return JSON.stringify([ extSig(d, SEC_EXT), extSig(d, SET_EXT), d.sec, d.mode, tl(d.links), tl(d.sub), d.sub_interval, d.iface, d.fastest, (function(r) { return [ srt(r.c), srt(r.s), srt(r.r), srt(r.n), srt(r.l) ]; })(refsOf(d)), tl(d.domains), d.own_on !== false,
|
||||||
tl(d.subnets), tl(d.lists), srt(d.full), srt(d.all || []), srt(d.excl), d.mode === 'iface' ? '' : String(d.exclude || ''), d.dns.type, d.dns.servers, d.dns.bootstraps, d.dns.detour, d.quic_off, d.list_interval, d.lists_via,
|
tl(d.subnets), tl(d.lists), srt(d.full), srt(d.all || []), srt(d.excl), d.mode === 'iface' ? '' : String(d.exclude || ''), d.dns.type, d.dns.servers, d.dns.bootstraps, d.dns.detour, d.quic_off, d.list_interval, d.lists_via,
|
||||||
String(d.label || '').trim(), d.isMain || d.enabled, tl(d.byDomains), tl(d.bySubnets) ]);
|
String(d.label || '').trim(), d.isMain || d.enabled, tl(d.byDomains), tl(d.bySubnets) ]);
|
||||||
}
|
}
|
||||||
@@ -44555,7 +44970,7 @@ function newCfg(c) {
|
|||||||
var n = ((c && c.sections) || []).length + 1;
|
var n = ((c && c.sections) || []).length + 1;
|
||||||
x.sec = 'new'; x.is_main = false; x.exists = false; x.enabled = true; x.label = warp && /^zmwarp/.test(warp.name) && !((c && c.sections) || []).some(function(s) { return s.label === 'WARP'; }) ? 'WARP' : 'Секция ' + n;
|
x.sec = 'new'; x.is_main = false; x.exists = false; x.enabled = true; x.label = warp && /^zmwarp/.test(warp.name) && !((c && c.sections) || []).some(function(s) { return s.label === 'WARP'; }) ? 'WARP' : 'Секция ' + n;
|
||||||
x.mode = 'iface'; x.iface = warp ? warp.name : ''; x.links = []; x.sub = ''; x.sub_interval = '12h'; x.exclude = ''; x.hide_names = [];
|
x.mode = 'iface'; x.iface = warp ? warp.name : ''; x.links = []; x.sub = ''; x.sub_interval = '12h'; x.exclude = ''; x.hide_names = [];
|
||||||
x.refs = { c: [], s: [], r: [] }; x.own_on = false; x.domains = []; x.subnets = []; x.lists = []; x.subs = []; x.x = {};
|
x.refs = { c: [], s: [], r: [], n: [] }; x.own_on = false; x.domains = []; x.subnets = []; x.lists = []; x.subs = []; x.x = {};
|
||||||
x.full = [];
|
x.full = [];
|
||||||
x.all = [];
|
x.all = [];
|
||||||
return x;
|
return x;
|
||||||
@@ -44825,7 +45240,7 @@ return view.extend({
|
|||||||
}
|
}
|
||||||
var rf = refsOf(draft);
|
var rf = refsOf(draft);
|
||||||
var ownN = textLines(draft.domains).length + textLines(draft.subnets).length + textLines(draft.lists).length;
|
var ownN = textLines(draft.domains).length + textLines(draft.subnets).length + textLines(draft.lists).length;
|
||||||
if (!rf.c.length && !rf.s.length && !rf.r.length && !rf.l.length && (!ownN || draft.own_on === false) && !draft.all.length && (draft.full.length || !secExists()))
|
if (!rf.c.length && !rf.s.length && !rf.r.length && !rf.n.length && !rf.l.length && (!ownN || draft.own_on === false) && !draft.all.length && (draft.full.length || !secExists()))
|
||||||
return [ 'svc', ownN ? '«Свой список» выключен, а других сервисов в секции «' + secName() + '» нет — включите его или выберите сервис'
|
return [ 'svc', ownN ? '«Свой список» выключен, а других сервисов в секции «' + secName() + '» нет — включите его или выберите сервис'
|
||||||
: 'Выберите хотя бы один сервис, домен или адрес для секции «' + secName() + '» — устройства только ограничивают её правила' ];
|
: 'Выберите хотя бы один сервис, домен или адрес для секции «' + secName() + '» — устройства только ограничивают её правила' ];
|
||||||
var badOwn = tokLines(draft.domains).map(domainProblem).filter(Boolean)[0];
|
var badOwn = tokLines(draft.domains).map(domainProblem).filter(Boolean)[0];
|
||||||
@@ -52016,6 +52431,10 @@ if [ -x /usr/bin/netshift ] && [ -f /usr/lib/netshift/constants.sh ] && ! _zmi_h
|
|||||||
else _zmi_warn "Сверить не удалось — откройте вкладку Forkozz и нажмите «Перезапустить»"; fi
|
else _zmi_warn "Сверить не удалось — откройте вкладку Forkozz и нажмите «Перезапустить»"; fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
case "$(/opt/zapret-manager-luci/backend.sh zapret_doh_migrate 2>/dev/null || true)" in
|
||||||
|
*'"changed":true'*) _zmi_ok "Блок DoH в стратегии Zapret обновлён до новой версии" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
if [ -x /usr/bin/bytetube ]; then
|
if [ -x /usr/bin/bytetube ]; then
|
||||||
case "$(/opt/zapret-manager-luci/backend.sh bytetube_sync 2>/dev/null || true)" in
|
case "$(/opt/zapret-manager-luci/backend.sh bytetube_sync 2>/dev/null || true)" in
|
||||||
*'"restarted":true'*) _zmi_ok "Скрипты ByeTube обновлены, ByeTube перезапущен" ;;
|
*'"restarted":true'*) _zmi_ok "Скрипты ByeTube обновлены, ByeTube перезапущен" ;;
|
||||||
|
|||||||
Reference in new issue
Block a user