StressOzz authored and GitHub committed 2026-10-06 22:33:54 +05:00
1 parent 9769c5b9bd
commit 021904ddb7
1 file changed
+395 -43
+395 -43
View File
@@ -1,10 +1,10 @@
#!/bin/sh
# Version: 2.41
# Version: 2.43
set -e
GREEN="\033[1;32m"; CYAN="\033[1;36m"; YELLOW="\033[1;33m"; MAGENTA="\033[1;35m"; BLUE="\033[0;34m"; NC="\033[0m"; DGRAY="\033[38;5;244m"
ZM_NEW_VER="2.41"
ZM_NEW_VER="2.43"
_zmi_say() { echo -e "${CYAN}==>${NC} $*"; }
_zmi_ok() { echo -e " ${GREEN}✓${NC} $*"; }
_zmi_step() { echo -e " → $*"; }
@@ -79,7 +79,7 @@ cat > '/opt/zapret-manager-luci/backend.sh.zm-new' << 'ZM_INSTALLER_EOF'
umask 022
CONF="/etc/config/zapret"
ZM_VERSION="2.41"
ZM_VERSION="2.43"
ZM_SCRIPT_URL="https://raw.githubusercontent.com/StressOzz/Zapret-Manager/refs/heads/main/ZapretManager_LuCI.sh"
GH_RAW="https://raw.githubusercontent.com"
GH_MAIN="https://github.com"
@@ -1241,6 +1241,11 @@ zapret_action() {
update) job_start install_zapret do_install_zapret ;;
remove) job_start remove_zapret do_remove_zapret ;;
start) /etc/init.d/zapret start >/dev/null 2>&1; zapret_restart; status ;;
restart)
[ -x /etc/init.d/zapret ] || { echo '{"error":"Zapret не установлен"}'; return 1; }
zapret_restart
for p in 1 2 3 4 5; do pgrep -f "/opt/zapret/" >/dev/null 2>&1 && break; sleep 1; done
status ;;
stop)
/etc/init.d/zapret stop >/dev/null 2>&1
for p in $(pgrep -f "/opt/zapret/" 2>/dev/null); do kill -9 "$p" 2>/dev/null; done
@@ -1350,6 +1355,11 @@ zapret2_action() {
remove) job_start remove_zapret2 do_remove_zapret2 ;;
start) /etc/init.d/zapret2 start >/dev/null 2>&1; status ;;
stop) /etc/init.d/zapret2 stop >/dev/null 2>&1; status ;;
restart)
[ -x /etc/init.d/zapret2 ] || { echo '{"error":"Zapret2 не установлен"}'; return 1; }
/etc/init.d/zapret2 restart >/dev/null 2>&1
for p in 1 2 3 4 5; do /etc/init.d/zapret2 status >/dev/null 2>&1 && break; sleep 1; done
status ;;
*) echo '{"error":"неизвестное действие"}' ;;
esac
}
@@ -2099,6 +2109,30 @@ _hosts_block() {
"199.232.210.248 scdn.co i.scdn.co line-up.scdn.co mosaic.scdn.co daily-mix.scdn.co lineup-images.scdn.co encore.scdn.co image-cdn-fa.scdn.co accounts.scdn.co www.scdn.co www-growth.scdn.co av.scdn.co seafoam.scdn.co" \
"23.48.23.145 heads-ak-spotify-com.akamaized.net" \
"45.155.204.190 xpui.app.spotify.com" ;;
roblox) printf '%s\n' \
"#tr.rbxcdn.com" \
"108.156.22.8 tr.rbxcdn.com" \
"108.157.32.114 tr.rbxcdn.com" \
"18.65.147.108 tr.rbxcdn.com" \
"18.65.147.112 tr.rbxcdn.com" \
"13.224.181.18 tr.rbxcdn.com" \
"13.224.181.74 tr.rbxcdn.com" \
"54.230.253.22 tr.rbxcdn.com" \
"54.230.253.81 tr.rbxcdn.com" \
"54.230.253.48 tr.rbxcdn.com" \
"54.230.253.59 tr.rbxcdn.com" \
"143.204.214.34 tr.rbxcdn.com" \
"143.204.214.67 tr.rbxcdn.com" \
"143.204.214.92 tr.rbxcdn.com" \
"99.84.181.25 tr.rbxcdn.com" \
"99.84.181.63 tr.rbxcdn.com" \
"65.8.158.45 tr.rbxcdn.com" \
"65.8.158.112 tr.rbxcdn.com" ;;
riot) printf '%s\n' \
"#Riotgames" \
"159.194.200.33 auth.riotgames.com authenticate.riotgames.com entitlements.auth.riotgames.com clientconfig.rpg.riotgames.com" \
"159.194.200.33 playerpreferences.riotgames.com login.riotgames.com riotgames.com www.riotgames.com account.riotgames.com" \
"159.194.200.33 recovery.riotgames.com riot-client.riotgames.com playvalorant.com leagueoflegends.com" ;;
rutracker) printf '%s\n' \
"#Rutracker" \
"172.67.182.196 rutracker.org" ;;
@@ -2134,7 +2168,7 @@ _hosts_has_line() {
}
hosts_status() {
local blocks="nalog ntc instagram librusec ai twitch telegram spotify spotifyext rutor rutracker scell githubraw github tapeop finland" b first=1
local blocks="nalog ntc instagram librusec ai twitch telegram spotify spotifyext rutor rutracker scell roblox riot githubraw github tapeop finland" b first=1
local geohide=""
if grep -q '^### geohide.ru: hosts file' "$HOSTS_FILE" 2>/dev/null; then
if grep -q '^# Регион серверов: US$' "$HOSTS_FILE" 2>/dev/null; then geohide="us"
@@ -2520,6 +2554,151 @@ system_toggle_flow_offloading_fix() {
}
ZM_FLASH_PATHS="/opt/zapret-manager-luci /usr/libexec/rpcd/zapret-manager /usr/share/rpcd/acl.d/luci-app-zapret-manager.json /usr/share/luci/menu.d/luci-app-zapret-manager.json /www/luci-static/resources/view/zapret-manager /www/luci-static/resources/zapret-manager /www/luci-static/resources/bytetube /www/luci-static/resources/view/bytetube /www/zm /www/zm-webui.html /usr/share/zm-redbtn"
_zm_kb() {
[ $# -gt 0 ] || { echo 0; return 0; }
du -sk "$@" 2>/dev/null | awk '{ s += $1 } END { print s + 0 }'
}
_zm_aged() {
[ -e "$1" ] || [ -L "$1" ] || return 1
[ "${2:-0}" -gt 0 ] || return 0
[ -n "$(find "$1" -maxdepth 0 -mmin +"$2" 2>/dev/null)" ]
}
_zm_junk() {
local age=0 f n d
for f in /tmp/zm-run.* /tmp/zm-wait.* /tmp/zm-quiet.* /tmp/zm-pkg.* /tmp/ytb-ct.* /tmp/ytb-diag.* /tmp/ytb-ins.* /tmp/ytb-dl /tmp/zm-zash /tmp/netshift-sbext.* \
/tmp/mihomo.gz /tmp/zashboard.zip /tmp/zashboard /tmp/metacubexd.tgz /tmp/metacubexd /tmp/tg-ws-proxy.ipk /tmp/tg-ws-proxy.apk /tmp/zm_uninstall_panel.sh \
"$JOBS_DIR"/install_tmp "$JOBS_DIR"/install_z2_tmp "$JOBS_DIR"/flowseal.zip "$JOBS_DIR"/flowseal_src "$JOBS_DIR"/tg-ws-proxy-rs.tar.gz \
"$JOBS_DIR"/netshift-src "$JOBS_DIR"/forkop-src "$JOBS_DIR"/forkop-import "$JOBS_DIR"/zm_head.* "$JOBS_DIR"/steer-list.* "$JOBS_DIR"/tgws_install.* \
"$JOBS_DIR"/geohide_hosts.tmp "$JOBS_DIR"/cleanup.* "$ST_RUN"/*.src "$ST_RUN"/cat.flat \
/opt/zapret-manager-luci/*.zm-new /www/luci-static/resources/zapret-manager/*.zm-new "$ZM_STATE_DIR"/*.tmp "$ST_DIR"/*.tmp "$ST_DIR"/lists/*.tmp \
"$ST_USER_DIR"/*.tmp "$ST_USER_DIR"/*.tmp.* "$HOSTS_FILE.zmtmp" "$HOSTS_FILE.zmdrop"; do
[ "$f" = "$JOBS_DIR/cleanup.$$" ] && continue
_zm_aged "$f" "$age" && echo "$f"
done
[ "$1" = install ] || { [ -e /tmp/zm_update_install.log ] && echo /tmp/zm_update_install.log; }
[ -e "$ZM_UP_FILE" ] && echo "$ZM_UP_FILE"
for f in /tmp/zm-rpc.*; do _zm_aged "$f" 10 && echo "$f"; done
for f in "$JOBS_DIR"/*.pid; do
[ -f "$f" ] || continue
n="$(basename "$f" .pid)"
_job_running "$n" || [ -f "$JOBS_DIR/$n.log" ] || echo "$f"
done
for f in "$JOBS_DIR"/install_z* "$JOBS_DIR"/sysinfo.json "$JOBS_DIR"/sysinfo.res "$JOBS_DIR"/sysinfo.urls "$JOBS_DIR"/tzdet.* "$JOBS_DIR"/excl_arp_tmp \
"$JOBS_DIR"/yv_* "$JOBS_DIR"/nfq_drop_old "$JOBS_DIR"/nfq_drop_new "$JOBS_DIR"/steer.hide.in "$JOBS_DIR"/mixomo.hide.in "$JOBS_DIR"/forkop-svc.out "$JOBS_DIR"/forkop-lists.out \
"$ST_RUN"/dl.* "$ST_RUN"/used.* "$ST_RUN"/srs.*.done "$ST_RUN"/lists.json "$ZM_STATE_DIR"/latest.* "$ZM_STATE_DIR"/versions.json "$ZM_STATE_DIR"/steer.latest; do
case "$f" in "$JOBS_DIR"/sysinfo.*) _job_running sysinfo && continue ;; esac
[ -e "$f" ] && echo "$f"
done
for f in /opt/zapret-manager-luci/forkop.uc /usr/lib/zapret-manager* /etc/zapret_manager_expert_mode* /usr/share/zm-redbtn/lists \
/www/luci-static/resources/view/zapret-manager/test.js /www/luci-static/resources/view/zapret-manager/redbtn.js \
/www/luci-static/resources/view/zapret-manager/youtube.js /www/luci-static/resources/view/zapret-manager/game.js \
/www/luci-static/resources/view/zapret-manager/discord.js /www/luci-static/resources/view/zapret-manager/exclusions.js \
/www/luci-static/resources/view/zapret-manager/forkop.js; do
[ -e "$f" ] || [ -L "$f" ] && echo "$f"
done
for f in "$JOBS_DIR"/*.log; do
[ -f "$f" ] || continue
n="$(basename "$f" .log)"
_job_running "$n" && continue
echo "$f"
[ -f "$JOBS_DIR/$n.pid" ] && echo "$JOBS_DIR/$n.pid"
done
if [ -d "$ZM_LISTS_DIR" ]; then
for f in "$ZM_LISTS_DIR"/.*; do
[ -f "$f" ] || continue
echo "$f"
done
if [ -s "$RB_SHARE/services.conf" ]; then
d=" $(grep -v '^#' "$RB_SHARE/services.conf" | cut -d'|' -f3,4 | tr ',|\n' ' ') "
for f in "$ZM_LISTS_DIR"/*.lst; do
[ -f "$f" ] || continue
case "$d" in *" ${f##*/} "*) ;; *) echo "$f" ;; esac
done
fi
fi
if [ -d "$ST_DIR/lists" ] && [ -s "$RB_SHARE/services.conf" ] && ! _st_running; then
ls "$ST_DIR/lists" 2>/dev/null | awk -F'|' -v sel="$ST_SEL" -v conf="$RB_SHARE/services.conf" -v rem="$ST_CAT_SVC" -v dir="$ST_DIR/lists" '
function load(file, l, a, c, m, i) { while ((getline l < file) > 0) { if (l ~ /^#/) continue; split(l, a, "|"); if (!(a[1] in on)) continue; m = split(a[8], c, ","); for (i = 1; i <= m; i++) if (c[i] != "") keep[c[i]] = 1 } close(file) }
BEGIN { while ((getline l < sel) > 0) if (l != "") { on[l] = 1; keep[l] = 1 } close(sel); load(conf); load(rem) }
{ n = $0; sub(/\..*$/, "", n); if (n == "" || (n in keep)) next; print dir "/" $0 }' | while IFS= read -r f; do
grep -qF "$f" "$STL_SPEC" "$STL_YAML" 2>/dev/null || echo "$f"
done
fi
return 0
}
_zm_mem_kb() { awk '/^MemAvailable:/ { print $2; f = 1; exit } /^MemFree:/ { m = $2 } END { if (!f) print m + 0 }' /proc/meminfo 2>/dev/null; }
_zm_df_kb() { df -kP "$1" 2>/dev/null | awk 'NR == 2 { print $4 + 0; f = 1 } END { if (!f) print 0 }'; }
_zm_usage_json() {
local list="$JOBS_DIR/cleanup.$$" jk=0 jn=0
mkdir -p "$JOBS_DIR"
_zm_junk "$1" > "$list" 2>/dev/null
if [ -s "$list" ]; then
jn="$(grep -c . "$list")"
jk="$(_zm_kb $(cat "$list"))"
fi
rm -f "$list"
printf '"flash_kb":%s,"lists_kb":%s,"tmp_kb":%s,"junk_kb":%s,"junk_n":%s,"flash_free_kb":%s,"tmp_free_kb":%s,"mem_free_kb":%s' \
"$(_zm_kb $ZM_FLASH_PATHS)" "$(_zm_kb "$ST_DIR/lists" "$ZM_LISTS_DIR")" "$(_zm_kb "$JOBS_DIR")" "${jk:-0}" "${jn:-0}" \
"$(_zm_df_kb /opt/zapret-manager-luci)" "$(_zm_df_kb /tmp)" "$(_zm_mem_kb)"
}
zm_cleanup_status() {
printf '{%s}\n' "$(_zm_usage_json)"
}
zm_cleanup() {
local mode="$1" j f k n=0 tk=0 fk=0 m0 m1 list="$JOBS_DIR/cleanup.$$"
if [ "$mode" != install ]; then
if j="$(_zm_busy_job)"; then
printf '{"error":"%s"}\n' "$(esc "идёт операция «$j» — дождитесь её окончания и запустите очистку")"
return 1
fi
if [ -f /tmp/zm_update_install.sh ]; then
echo '{"error":"идёт обновление панели — дождитесь его окончания"}'
return 1
fi
fi
mkdir -p "$JOBS_DIR"
m0="$(_zm_mem_kb)"
_zm_junk "$mode" > "$list" 2>/dev/null
while IFS= read -r f; do
case "$f" in
/tmp/zm_update_install.sh) continue ;;
/tmp/zm[-_]*|/tmp/ytb-*|/tmp/netshift-sbext.*|/tmp/mihomo.gz|/tmp/zashboard|/tmp/zashboard.zip|/tmp/metacubexd|/tmp/metacubexd.tgz|/tmp/tg-ws-proxy.ipk|/tmp/tg-ws-proxy.apk) ;;
/tmp/zapret-manager-luci/?*|/opt/zapret-manager-luci/?*|/etc/zm-steer/?*|/etc/hosts.zmtmp|/etc/hosts.zmdrop) ;;
/usr/lib/zapret-manager*|/etc/zapret_manager_expert_mode*|/usr/share/zm-redbtn/lists|/www/luci-static/resources/zapret-manager/?*|/www/luci-static/resources/view/zapret-manager/?*) ;;
*) continue ;;
esac
case "$f" in */../*|*/..) continue ;; esac
[ -e "$f" ] || [ -L "$f" ] || continue
k="$(_zm_kb "$f")"
rm -rf "$f" 2>/dev/null
[ -e "$f" ] && continue
n=$((n + 1))
case "$f" in /tmp/*) tk=$((tk + k)) ;; *) fk=$((fk + k)) ;; esac
done < "$list"
rm -f "$list"
for f in "$ZM_STATE_DIR"/*.lock; do
[ -d "$f" ] && _zm_aged "$f" 5 && rmdir "$f" 2>/dev/null
done
_st_running || rm -f "$ST_RUN"/dl.* "$ST_RUN"/used.* "$ST_RUN"/srs.*.done 2>/dev/null
rmdir "$ST_DIR/lists" "$ZM_LISTS_DIR" 2>/dev/null
rm -f /tmp/luci-indexcache* /tmp/luci-modulecache/* 2>/dev/null
if [ "$mode" != install ]; then
sync
echo 3 > /proc/sys/vm/drop_caches 2>/dev/null
fi
m1="$(_zm_mem_kb)"
k=$(( ${m1:-0} - ${m0:-0} )); [ "$k" -gt 0 ] || k=0
printf '{"ok":true,"removed":%s,"freed_flash_kb":%s,"freed_tmp_kb":%s,"freed_mem_kb":%s,%s}\n' "$n" "$fk" "$tk" "$k" "$(_zm_usage_json "$mode")"
}
system_uninstall_panel() {
local script="/tmp/zm_uninstall_panel.sh" j
if j="$(_zm_busy_job)"; then
@@ -2537,6 +2716,8 @@ ZM_PATHS="/opt/zapret-manager-luci /usr/libexec/rpcd/zapret-manager \
/www/luci-static/resources/bytetube \
/www/luci-static/resources/view/bytetube \
/tmp/zapret-manager-luci /tmp/luci-indexcache* /tmp/luci-modulecache/* \
/tmp/zm-run.* /tmp/zm-wait.* /tmp/zm-quiet.* /tmp/zm-pkg.* /tmp/zm-rpc.* /tmp/zm_update_install.log \
/usr/lib/zapret-manager* /etc/zapret_manager_expert_mode* /etc/hosts.zmtmp /etc/hosts.zmdrop \
/www/zm /www/zm-webui.html /etc/zm-warp-own.conf"
zm_try=0
while :; do
@@ -5609,7 +5790,23 @@ health() {
local fw=false v6=false
[ "$(uci -q get firewall.@defaults[0].flow_offloading)" = 1 ] && [ -f /usr/share/firewall4/templates/ruleset.uc ] && [ "$(_flow_offloading_fix_applied)" = false ] && fw=true
[ -f "$CONF" ] && [ "$(_ipv6_enabled_in_zapret)" = false ] && ip -6 route show default 2>/dev/null | grep -q . && v6=true
printf '{"zapret":%s,"zapret2":%s,"bytetube":%s,"tg":%s,"mixomo":%s,"doh":%s,"hosts":%s,"steer":%s,"steer_off":%s,"steer_exit":"%s","awg":%s,"forkop":%s,"flow_warn":%s,"ipv6_warn":%s,"reboot_hint":"%s"}\n' \
local d_zr="$zr" d_zr2="$zr2" d_bt="$bt" d_tg="$tg" d_mx="$mx" d_doh="$doh" d_sr="$sr" d_fk d_awg d_term=0 awgj
[ "$zr" = 2 ] && d_zr=4
[ "$zr2" = 2 ] && d_zr2=4
if [ "$bt" = 2 ]; then case "$(/usr/bin/bytetube status 2>/dev/null)" in *'"enabled":true'*) ;; *) d_bt=4 ;; esac; fi
[ "$tg" = 2 ] && ! ls /etc/rc.d/S*tg-ws-proxy* /etc/rc.d/S*tgws >/dev/null 2>&1 && d_tg=4
[ "$mx" = 2 ] && ! ls /etc/rc.d/S*mihomo >/dev/null 2>&1 && d_mx=4
if [ "$doh" = 2 ]; then { [ -f "$DOH_OFF_FLAG" ] || ! ls /etc/rc.d/S*https-dns-proxy >/dev/null 2>&1; } && d_doh=4; fi
[ "$sr" = 5 ] && d_sr=4
d_fk="$(_fk_health)"; [ "$d_fk" = 5 ] && d_fk=4
awgj="$(_awg_health)"; d_awg="${awgj%%,*}"
case "$awgj" in *'"awg_total":0') [ "$d_awg" = 1 ] && d_awg=4; [ "$d_awg" = 3 ] && d_awg=2 ;; esac
if _term_installed; then
if _term_running; then d_term=1; elif _term_enabled; then d_term=2; else d_term=4; fi
fi
printf '{"dots":{"zapret":%s,"zapret2":%s,"bytetube":%s,"tg":%s,"mixomo":%s,"doh":%s,"hosts":%s,"steer":%s,"forkop":%s,"awg":%s,"term":%s},' \
"$d_zr" "$d_zr2" "$d_bt" "$d_tg" "$d_mx" "$d_doh" "$hs" "$d_sr" "${d_fk:-0}" "${d_awg:-0}" "$d_term"
printf '"zapret":%s,"zapret2":%s,"bytetube":%s,"tg":%s,"mixomo":%s,"doh":%s,"hosts":%s,"steer":%s,"steer_off":%s,"steer_exit":"%s","awg":%s,"forkop":%s,"flow_warn":%s,"ipv6_warn":%s,"reboot_hint":"%s"}\n' \
"$zr" "$zr2" "$bt" "$tg" "$mx" "$doh" "$hs" "$sr" \
"$([ -f /etc/zm-steer/stopped ] && echo true || echo false)" "$sx" "$(_awg_health)" "$(_fk_health)" "$fw" "$v6" "$(esc "$(_zm_reboot_hint_get)")"
}
@@ -7492,7 +7689,7 @@ _rb_list_get() {
_rb_say() { echo "==> $*"; }
_rb_warn() { echo "!! $*"; }
_rb_svc_src() { grep -hv '^#' "$RB_SHARE/services.conf" 2>/dev/null; [ -f "$ST_CAT_OFF" ] || awk -F'|' -v skip="$ST_CAT_SKIP" '$9 != "" && $9 != "Свои списки каталога" { k = $1; sub(/^c_/, "", k); if (!index(skip, " " k " ")) print }' "$ST_CAT_SVC" 2>/dev/null; }
_rb_svc_src() { grep -hv '^#' "$RB_SHARE/services.conf" 2>/dev/null; [ -f "$ST_CAT_OFF" ] || awk -F'|' -v skip="$ST_CAT_SKIP" -v conf="$RB_SHARE/services.conf" 'BEGIN { while ((getline l < conf) > 0) { if (l ~ /^#/) continue; split(l, a, "|"); if (a[1] == "") continue; b[a[1]] = 1; m = split(a[8], c, ","); for (i = 1; i <= m; i++) if (c[i] != "") b[c[i]] = 1 } } $9 != "" && $9 != "Свои списки каталога" { k = $1; sub(/^c_/, "", k); if (index(skip, " " k " ")) next; if (k ~ /^itdoginfo_/ && (substr(k, 11) in b)) next; print }' "$ST_CAT_SVC" 2>/dev/null; }
_rb_svc_field() { _rb_svc_src | grep "^$1|" | head -n1 | cut -d'|' -f"$2"; }
_rb_svc_names() { local id; for id in $1; do printf '%s, ' "$(_rb_svc_field "$id" 2)"; done | sed 's/, $//'; }
_rb_routable() { [ "$1" = custom ] && [ -n "$(_rb_svc_field custom 1)" ] && return 0; [ -n "$(_rb_svc_field "$1" 3)$(_rb_svc_field "$1" 4)$(_rb_svc_field "$1" 8)" ]; }
@@ -8242,8 +8439,9 @@ _st_sel_remap() {
local f
for f in "$ST_SEL" "$ST_SKIP"; do
grep -q '^c_itdoginfo_' "$f" 2>/dev/null || continue
awk -v conf="$RB_SHARE/services.conf" 'BEGIN { while ((getline l < conf) > 0) { split(l, a, "|"); if (a[1] != "" && a[1] !~ /^#/) b[a[1]] = 1 } }
{ k = $0; if (k ~ /^c_itdoginfo_/ && (substr(k, 13) in b)) k = substr(k, 13); if (k != "" && !(k in s)) { s[k] = 1; print k } }' "$f" > "$f.tmp" && mv -f "$f.tmp" "$f"
awk -v conf="$RB_SHARE/services.conf" -v ex="$([ "$f" = "$ST_SEL" ] && echo 1)" 'BEGIN { while ((getline l < conf) > 0) { split(l, a, "|"); if (a[1] != "" && a[1] !~ /^#/) { b[a[1]] = 1; m = split(a[8], c, ","); for (i = 1; i <= m; i++) if (c[i] != "") bs[c[i]] = bs[c[i]] " " a[1] } } }
function put(k) { if (k != "" && !(k in s)) { s[k] = 1; print k } }
{ k = $0; if (k ~ /^c_itdoginfo_/) { x = substr(k, 13); if (x in b) { put(x); next } if (ex && (x in bs)) { m = split(bs[x], q, " "); for (i = 1; i <= m; i++) put(q[i]); next } } put(k) }' "$f" > "$f.tmp" && mv -f "$f.tmp" "$f"
done
}
_st_migrate
@@ -9252,7 +9450,7 @@ _st_cat_build() {
grp = clean(f[e, "source_name"])
if (grp == "" && fo[e]) grp = (tolower(f[e, "id"] " " f[e, "source"]) ~ /b4geoip/) ? "b4geoip" : "itdoginfo (allow-domains)"
if (grp == "") return
if (!fo[e] && f[e, "source"] == "itdoginfo/allow-domains") {
if (tolower(f[e, "id"] " " f[e, "source"]) ~ /itdoginfo/) {
u = f[e, "url"]; b = u; sub(/.*\//, "", b); sub(/\.srs$/, "", b)
if (index(bsets, " " b " ") || index(bnames, "|" tolower(nm) "|")) return
}
@@ -10414,11 +10612,6 @@ steer_list_set() {
[ "$n" -gt 0 ] || { rm -f "$tmp" "$tmp.pfx"; echo '{"error":"в списке нет ни одного домена или IP-адреса"}'; return 1; }
if [ -s "$tmp" ]; then mv "$tmp" "$f"; else rm -f "$tmp" "$f"; fi
if [ -s "$tmp.pfx" ]; then mv "$tmp.pfx" "$ST_USER_DIR/$id.pfx"; else rm -f "$tmp.pfx" "$ST_USER_DIR/$id.pfx"; fi
if [ "$id" = custom ]; then
mkdir -p "$ST_DIR"
[ -f "$ST_SKIP" ] && sed -i '/^custom$/d' "$ST_SKIP"
_rb_in custom "$ST_SEL" || echo custom >> "$ST_SEL"
fi
_st_list_changed "$id" "$n"
}
@@ -14183,6 +14376,8 @@ case "$cmd" in
system_toggle_ipv6) system_toggle_ipv6 ;;
system_toggle_flow_offloading_fix) system_toggle_flow_offloading_fix ;;
system_uninstall_panel) system_uninstall_panel ;;
zm_cleanup_status) zm_cleanup_status ;;
zm_cleanup) zm_cleanup "$1" ;;
sysinfo_run) sysinfo_run ;;
sysinfo_get) sysinfo_get ;;
mirror_status) mirror_status ;;
@@ -15089,7 +15284,7 @@ function first_addr(v) {
/* Сеть роутера: логические сети (lan, wan…) и устройства (br-lan, eth0, pppoe-wan…). */
function net_info(c) {
let nets = [], devnet = {};
let nets = [], devnet = {}, netproto = {};
try { c.load("network"); } catch (e) {}
try {
c.foreach("network", "interface", (x) => {
@@ -15098,6 +15293,7 @@ function net_info(c) {
if (proto == "pppoe" || proto == "pppoa") dev = proto + "-" + n;
if (match(dev, /^@/)) dev = "";
if (dev != "" && !devnet[dev]) devnet[dev] = n;
netproto[n] = proto;
push(nets, { name: n, proto, dev });
});
} catch (e) {}
@@ -15114,7 +15310,8 @@ function net_info(c) {
: t == "1" ? "eth" : "";
if (kind == "") continue;
let st = trim(s(fs.readfile(p + "/operstate")));
push(devs, { name: n, kind, up: st != "down", net: devnet[n] || "" });
let dn = devnet[n] || (netproto[n] != null ? n : "");
push(devs, { name: n, kind, up: st != "down", net: dn, proto: dn != "" ? s(netproto[dn]) : "" });
}
devs = sort(devs, (a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
return { lan_ip: lan, nets, devs };
@@ -15465,7 +15662,7 @@ function cmd_set() {
write_sec_ext(c, sec, mode, d.x);
set_lists(c, sec, { s: rsets, r: rsubs, l: lists }, services, domains, subnets);
if (own_on) {
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" ]);
} else {
c.set(CFG, sec, "zm_own_off", "1");
@@ -16753,6 +16950,8 @@ list_methods() {
json_add_object "system_toggle_ipv6"; json_close_object
json_add_object "system_toggle_flow_offloading_fix"; json_close_object
json_add_object "system_uninstall_panel"; json_close_object
json_add_object "zm_cleanup_status"; json_close_object
json_add_object "zm_cleanup"; json_close_object
json_add_object "sysinfo_run"; json_close_object
json_add_object "sysinfo_get"; json_close_object
json_add_object "mirror_status"; json_close_object
@@ -16860,6 +17059,8 @@ call_method() {
system_toggle_ipv6) "$BACKEND" system_toggle_ipv6 ;;
system_toggle_flow_offloading_fix) "$BACKEND" system_toggle_flow_offloading_fix ;;
system_uninstall_panel) "$BACKEND" system_uninstall_panel ;;
zm_cleanup_status) "$BACKEND" zm_cleanup_status ;;
zm_cleanup) "$BACKEND" zm_cleanup ;;
sysinfo_run) "$BACKEND" sysinfo_run ;;
sysinfo_get) "$BACKEND" sysinfo_get ;;
mirror_status) "$BACKEND" mirror_status ;;
@@ -16985,6 +17186,7 @@ cat > '/usr/share/rpcd/acl.d/luci-app-zapret-manager.json' << 'ZM_INSTALLER_EOF'
"test_status",
"test_results",
"zm_update_status",
"zm_cleanup_status",
"mixomo_status",
"mixomo_proxies",
"mixomo_config_get",
@@ -17046,6 +17248,7 @@ cat > '/usr/share/rpcd/acl.d/luci-app-zapret-manager.json' << 'ZM_INSTALLER_EOF'
"system_toggle_ipv6",
"system_toggle_flow_offloading_fix",
"system_uninstall_panel",
"zm_cleanup",
"mirror_set",
"exclusions_toggle",
"exclusions_clear",
@@ -17236,6 +17439,8 @@ var callSystemToggleQuic = zmDeclare({ object: 'zapret-manager', method: 'system
var callSystemToggleIpv6 = zmDeclare({ object: 'zapret-manager', method: 'system_toggle_ipv6', expect: {} });
var callSystemToggleFlowOffloadingFix = zmDeclare({ object: 'zapret-manager', method: 'system_toggle_flow_offloading_fix', expect: {} });
var callSystemUninstallPanel = zmDeclare({ object: 'zapret-manager', method: 'system_uninstall_panel', expect: {} });
var callZmCleanupStatus = zmDeclare({ object: 'zapret-manager', method: 'zm_cleanup_status', expect: {} });
var callZmCleanup = zmDeclare({ object: 'zapret-manager', method: 'zm_cleanup', expect: {} });
var callSysinfoRun = zmDeclare({ object: 'zapret-manager', method: 'sysinfo_run', expect: {} });
var callSysinfoGet = zmDeclare({ object: 'zapret-manager', method: 'sysinfo_get', expect: {} });
var callMirrorStatus = zmDeclare({ object: 'zapret-manager', method: 'mirror_status', expect: {} });
@@ -18618,6 +18823,8 @@ return baseclass.extend({
systemToggleIpv6: callSystemToggleIpv6,
systemToggleFlowOffloadingFix: callSystemToggleFlowOffloadingFix,
systemUninstallPanel: callSystemUninstallPanel,
zmCleanupStatus: callZmCleanupStatus,
zmCleanup: callZmCleanup,
sysinfoRun: callSysinfoRun,
sysinfoGet: callSysinfoGet,
mirrorStatus: callMirrorStatus,
@@ -19773,7 +19980,7 @@ return view.extend({
var wrap = E('div', { 'class': 'zm-wrap' });
var card = E('div', { 'class': 'zm-card' });
var headEl = E('div', {});
var viewEl = E('div', { 'style': 'display:none; margin:10px 0' });
var viewEl = E('div', { 'style': 'display:none; margin:0 0 14px' });
var actEl = E('div', {});
var logEl = E('pre', { 'class': 'zm-log' });
var busy = false, frame = null, autoTried = false;
@@ -19785,6 +19992,22 @@ return view.extend({
function addr() { return 'http://' + window.location.hostname + ':' + (data.port || 7681) + '/'; }
function termTitle() { return 'ttyd · ' + window.location.hostname + ':' + (data.port || 7681); }
function frameUrl() {
var base = addr(), bg = '';
if (window.zmTermUrl && String(window.zmTermUrl).indexOf(base) === 0) return window.zmTermUrl;
try { bg = getComputedStyle(document.documentElement).getPropertyValue('--console').trim(); } catch (e) { bg = ''; }
if (!/^#[0-9a-f]{6}$/i.test(bg)) bg = '#0d1117';
var theme = {
background: bg, foreground: '#e6edf3', cursor: '#e6edf3',
black: '#000000', red: '#d81e00', green: '#5ea702', yellow: '#cfae00', blue: '#427ab3', magenta: '#89658e', cyan: '#00a7aa', white: '#dbded8',
brightBlack: '#686a66', brightRed: '#f54235', brightGreen: '#99e343', brightYellow: '#fdeb61', brightBlue: '#84b0d8', brightMagenta: '#bc94b7', brightCyan: '#37e6e8', brightWhite: '#f1f1f0'
};
window.zmTermUrl = base + '?disableLeaveAlert=true&theme=' + encodeURIComponent(JSON.stringify(theme));
return window.zmTermUrl;
}
function refresh() {
return zm.termStatus().then(function(d) { data = d || {}; renderAll(); autoStart(); });
}
@@ -19844,14 +20067,20 @@ return view.extend({
var show = !!data.installed && !!data.running && canEmbed && !busy;
if (keep) {
viewEl.style.display = 'none';
if (!show) keep.hide(); else keep.open(addr());
if (!show) keep.hide(); else keep.open(frameUrl(), termTitle());
return;
}
if (!show) { viewEl.style.display = 'none'; return; }
if (!frame) {
frame = E('iframe', { 'src': addr(), 'style': 'width:100%; height:480px; border:0; border-radius:8px; background:#000' });
frame = E('iframe', { 'class': 'zm-term-frame', 'src': frameUrl() });
viewEl.innerHTML = '';
viewEl.appendChild(frame);
viewEl.appendChild(E('div', { 'class': 'zm-term' }, [
E('div', { 'class': 'zm-term-bar' }, [
E('span', { 'class': 'zm-term-dots' }, [ E('i', {}), E('i', {}), E('i', {}) ]),
E('span', { 'class': 'zm-term-title' }, termTitle())
]),
E('div', { 'class': 'zm-term-body' }, [ frame ])
]));
}
viewEl.style.display = '';
}
@@ -20268,13 +20497,17 @@ return view.extend({
if (data.blocker) return;
listCard.appendChild(E('p', { 'class': 'zm-hint' }, 'Нажмите на пункт, чтобы включить или выключить его, и затем «Сохранить и применить». Списки берутся из каталога списков (itdoginfo/allow-domains, b4geoip и другие) и обновляются при каждом применении.'));
var cur = currentPick(), sel = pick || cur, changed = false;
var list = data.services || [];
var all = data.services || [];
var list = all.filter(function(s) { return s.id !== 'custom'; }).map(function(s) {
if (!/^c_itdoginfo_/.test(s.id) && !/^itdoginfo/i.test(s.group || '')) return s;
return { id: s.id, name: s.name, group: '', cat: true, on: s.on, skip: s.skip };
});
list.forEach(function(s) { if (!!sel[s.id] !== !!cur[s.id]) changed = true; });
function tile(s) {
return zm.svcCard({ name: s.name, key: s.id, on: !!sel[s.id], inc: !!sel.russia_inside && s.id !== 'russia_inside' && !s.group && zm.riCovers(s.id), click: function() {
if (busy) { zm.toast('Дождитесь окончания текущей операции', 'warning'); return; }
pick = {};
for (var k in sel) if (sel[k]) pick[k] = true;
for (var k in sel) if (sel[k] && k !== 'custom') pick[k] = true;
if (pick[s.id]) delete pick[s.id]; else pick[s.id] = true;
renderLists();
} });
@@ -20334,7 +20567,7 @@ return view.extend({
saveLabel: data.installed && !data.stopped ? 'Сохранить и применить' : 'Сохранить',
onCancel: function() { pick = null; renderLists(); },
onSave: function() {
var ids = list.filter(function(s) { return sel[s.id]; }).map(function(s) { return s.id; });
var ids = all.filter(function(s) { return s.id === 'custom' ? cur.custom : sel[s.id]; }).map(function(s) { return s.id; });
act('lists', ids.join(','), 'Применяем выбор — скачиваем списки');
}
});
@@ -20384,18 +20617,19 @@ return view.extend({
customCard.style.display = data.blocker || !svc ? 'none' : '';
if (data.blocker || !svc) return;
customCard.appendChild(E('h3', {}, 'Свой список'));
customCard.appendChild(E('p', { 'class': 'zm-hint' }, 'Домены и IP-адреса из этого списка пойдут через Steer. После сохранения список сразу включается в выбор сервисов, «Очистить» — убирает его.'));
customCard.appendChild(E('p', { 'class': 'zm-hint' }, 'Домены и IP-адреса из этого списка пойдут через Steer. По умолчанию список выключен — сохраните его и включите переключателем ниже. «Очистить» — убирает его.'));
if (customLoading && !customData) { customCard.appendChild(E('p', { 'class': 'zm-hint' }, 'Загружаем список…')); return; }
var n = customData ? (parseInt(customData.count, 10) || 0) : 0;
customCard.appendChild(zm.swRow(!!svc.on, 'Свой список', svc.on ? 'Включён: домены и IP-адреса из списка идут через Steer. Выключите — они пойдут напрямую, сам список останется.' : 'Выключен: список не используется. Включите — домены и IP-адреса из него пойдут через Steer.', function() {
if (busy) { zm.toast('Дождитесь окончания текущей операции', 'warning'); return; }
if (!svc.on && !n) { zm.toast('Сначала добавьте в список домены или IP', 'warning'); return; }
if (customDirty) { zm.toast('Сначала сохраните изменения в списке', 'warning'); return; }
var ids = (data.services || []).filter(function(s) { return s.on && s.id !== 'custom'; }).map(function(s) { return s.id; });
if (!svc.on) ids.push('custom');
act('lists', ids.join(','), svc.on ? 'Выключаем свой список' : 'Включаем свой список');
}, busy));
customCard.appendChild(row('Состояние', !n ? badge('zm-off', 'список пуст')
: !svc.on ? badge('zm-warn', 'не выбран')
: !svc.on ? badge('zm-warn', 'выключен')
: !data.installed ? badge('zm-warn', 'пойдёт через Steer после установки')
: data.stopped ? badge('zm-off', 'Steer выключен')
: data.exit === 'vpn' ? badge('zm-ok', 'идёт через VPN')
@@ -21174,6 +21408,8 @@ var LABELS = {
rutor: [ 'rutor.info', 'Торренты' ],
rutracker: [ 'rutracker.org', 'Торренты' ],
scell: [ 'Supercell', 'Clash, Brawl Stars' ],
roblox: [ 'Картинки Roblox', 'tr.rbxcdn.com — аватары и превью' ],
riot: [ 'Riot Games', 'Вход в Valorant и League of Legends' ],
githubraw: [ 'GitHub Raw', 'githubusercontent.com' ],
github: [ 'GitHub', 'Код и релизы' ],
tapeop: [ 'tapeop.dev', 'Сайт' ],
@@ -22178,6 +22414,10 @@ return view.extend({
'class': 'cbi-button',
'click': function() { doZapretAction(d.zapret_running ? 'stop' : 'start'); }
}, d.zapret_running ? 'Остановить' : 'Запустить'));
if (d.zapret_running) zActions.push(E('button', {
'class': 'cbi-button',
'click': function() { doZapretAction('restart'); }
}, 'Перезапустить'));
} else {
zActions.push(E('button', {
'class': 'cbi-button cbi-button-positive',
@@ -22214,7 +22454,7 @@ return view.extend({
if (zBusy) { zm.toast('Дождитесь завершения текущей операции', 'warning'); return; }
var job = (action === 'install' || action === 'update') ? 'install_zapret'
: (action === 'remove') ? 'remove_zapret' : null;
var LABELS = { install: 'Устанавливаем и настраиваем Zapret', update: 'Обновляем Zapret', remove: 'Удаляем Zapret', start: 'Запускаем Zapret', stop: 'Останавливаем Zapret' };
var LABELS = { install: 'Устанавливаем и настраиваем Zapret', update: 'Обновляем Zapret', remove: 'Удаляем Zapret', start: 'Запускаем Zapret', stop: 'Останавливаем Zapret', restart: 'Перезапускаем Zapret' };
zm.toast(LABELS[action] || 'Выполняем', 'warning');
if (job) zBusy = true;
@@ -22232,6 +22472,7 @@ return view.extend({
});
} else if (res) {
zBusy = false;
if (action === 'restart') zm.toast(res.zapret_running ? 'Zapret перезапущен' : 'Zapret не запустился — проверьте стратегию', res.zapret_running ? 'info' : 'error');
status = res;
renderZCard(res);
renderBanner();
@@ -23616,6 +23857,10 @@ return view.extend({
'class': 'cbi-button',
'click': function() { view.doAction2(d.zapret2_running ? 'stop' : 'start'); }
}, d.zapret2_running ? 'Остановить' : 'Запустить'));
if (d.zapret2_running) z2Actions.push(E('button', {
'class': 'cbi-button',
'click': function() { view.doAction2('restart'); }
}, 'Перезапустить'));
} else {
z2Actions.push(E('button', {
'class': 'cbi-button cbi-button-positive',
@@ -23661,7 +23906,7 @@ return view.extend({
if (view.busy2) { zm.toast('Дождитесь завершения текущей операции', 'warning'); return; }
var job = (action === 'install' || action === 'update') ? 'install_zapret2'
: (action === 'remove') ? 'remove_zapret2' : null;
var LABELS = { install: 'Устанавливаем Zapret2', update: 'Обновляем Zapret2', remove: 'Удаляем Zapret2', start: 'Запускаем Zapret2', stop: 'Останавливаем Zapret2' };
var LABELS = { install: 'Устанавливаем Zapret2', update: 'Обновляем Zapret2', remove: 'Удаляем Zapret2', start: 'Запускаем Zapret2', stop: 'Останавливаем Zapret2', restart: 'Перезапускаем Zapret2' };
zm.toast(LABELS[action] || 'Выполняем', 'warning');
if (job) view.busy2 = true;
@@ -23679,6 +23924,7 @@ return view.extend({
});
} else if (res) {
view.busy2 = false;
if (action === 'restart') zm.toast(res.zapret2_running ? 'Zapret2 перезапущен' : 'Zapret2 не запустился', res.zapret2_running ? 'info' : 'error');
view.renderCards(res);
}
}).catch(function() { view.busy2 = false; });
@@ -23836,6 +24082,34 @@ html.zm-theme-dark .zm-tile:not(.zm-active):not(.zm-tile-off) {
box-shadow: inset 0 0 0 1px rgba(0,0,0,.2);
}
.zm-log.zm-show { display: block; }
.zm-term {
box-sizing: border-box; min-width: 0; overflow: hidden;
background: #0d1117; color: #8b949e;
border: 1px solid rgba(255,255,255,.10); border-radius: 10px;
box-shadow: inset 0 0 0 1px rgba(0,0,0,.2);
}
.zm-term-bar {
display: flex; align-items: center; gap: 12px;
height: 38px; padding: 0 16px; box-sizing: border-box;
border-bottom: 1px solid rgba(255,255,255,.07);
font-family: ui-monospace, "SF Mono", "Cascadia Code", Consolas, "Liberation Mono", monospace;
font-size: 12.5px; line-height: 1; color: #8b949e; user-select: none;
}
.zm-term-dots { display: inline-flex; gap: 8px; flex: 0 0 auto; }
.zm-term-dots i { display: block; width: 10px; height: 10px; border-radius: 50%; background: #ff5f57; }
.zm-term-dots i:nth-child(2) { background: #febc2e; }
.zm-term-dots i:nth-child(3) { background: #28c840; }
.zm-term-title { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.zm-term-body { padding: 8px 11px 10px; background: inherit; }
.zm-term-frame {
display: block; width: 100%; border: 0; background: #0d1117;
height: 480px; height: clamp(320px, calc(100vh - 340px), 780px);
}
@media (max-width: 600px) {
.zm-term-bar { padding: 0 12px; }
.zm-term-body { padding: 6px 5px 8px; }
.zm-term-frame { height: clamp(280px, calc(100vh - 300px), 620px); }
}
.zm-log { scrollbar-width: auto; scrollbar-color: auto; }
.zm-log::-webkit-scrollbar { width: 14px; }
.zm-log::-webkit-scrollbar-track { background: transparent; margin: 8px 0; }
@@ -24798,6 +25072,51 @@ return view.extend({
]);
wrap.appendChild(mirrorCard);
var cleanLog = E('pre', { 'class': 'zm-log' });
var cleanBusy = false;
function fmtKb(k) {
k = parseInt(k, 10) || 0;
if (k < 1024) return k + ' КБ';
return (k / 1024).toFixed(k < 10240 ? 1 : 0) + ' МБ';
}
var cleanCard = E('div', { 'class': 'zm-card' }, [
E('h3', {}, 'Очистка Zapret Manager'),
E('p', { 'class': 'zm-hint' }, 'Удаляет с роутера временные файлы и мусор панели. Настройки и установленные программы не трогает.'),
E('div', { 'class': 'zm-actions' }, [
E('button', {
'class': 'cbi-button cbi-button-action',
'click': function() {
if (cleanBusy) { zm.toast('Очистка уже идёт', 'warning'); return; }
cleanBusy = true;
cleanLog.classList.add('zm-show');
zm.renderLog(cleanLog, '==> Удаляем временные файлы и мусор панели');
zm.zmCleanup().then(function(res) {
cleanBusy = false;
if (!res || res.error) {
var why = (res && res.error) || 'роутер не ответил';
zm.renderLog(cleanLog, '==> Очистка не выполнена: ' + why);
zm.toast(why, 'error');
return;
}
var lines = [ '==> Удаляем временные файлы и мусор панели',
' ✓ Удалено файлов и папок: ' + (parseInt(res.removed, 10) || 0),
' ✓ Освобождено на флеше: ' + fmtKb(res.freed_flash_kb),
' ✓ Освобождено в ОЗУ: ' + fmtKb((parseInt(res.freed_tmp_kb, 10) || 0) + (parseInt(res.freed_mem_kb, 10) || 0)),
'==> Готово' ];
zm.renderLog(cleanLog, lines.join('\n'));
zm.toast('Очистка выполнена', 'info');
}).catch(function() {
cleanBusy = false;
zm.renderLog(cleanLog, '==> Очистка не выполнена: роутер не ответил');
zm.toast('Роутер не ответил', 'error');
});
}
}, 'Очистить')
]),
cleanLog
]);
wrap.appendChild(cleanCard);
var uninstallLog = E('pre', { 'class': 'zm-log' });
var uninstallCard = E('div', { 'class': 'zm-card' }, [
E('h3', {}, 'Удалить Zapret Manager'),
@@ -25550,7 +25869,7 @@ function fromCfg(c) {
byDomains: ((c.bypass && c.bypass.domains) || []).join('\n'), bySubnets: ((c.bypass && c.bypass.subnets) || []).join('\n'),
mode: c.mode || 'links', links: (c.links || []).join('\n'), sub: (c.subs && c.subs.length ? c.subs : c.sub ? [ c.sub ] : []).join('\n'),
sub_interval: c.sub_interval || '12h', iface: c.iface || '', fastest: true, exclude: c.exclude || '',
items: pick.items, sel: pick.sel, own_on: c.own_on !== false, domains: (c.domains || []).join('\n'), subnets: (c.subnets || []).join('\n'),
items: pick.items, sel: pick.sel, own_on: c.own_on !== false && ((c.domains || []).length + (c.subnets || []).length + pick.lists.length) > 0, domains: (c.domains || []).join('\n'), subnets: (c.subnets || []).join('\n'),
lists: pick.lists.join('\n'), full: (c.full || []).slice(), excl: (c.excl || []).slice(),
dns: { type: dns.type || (dns.server ? 'udp' : 'dot'), servers: (dns.servers && dns.servers.length ? dns.servers : [ dns.server || '9.9.9.9' ]).slice(),
bootstraps: (dns.bootstraps && dns.bootstraps.length ? dns.bootstraps : [ dns.bootstrap || '9.9.9.9' ]).slice(), detour: !!dns.detour },
@@ -25644,7 +25963,7 @@ function newCfg(c) {
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.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 = true; x.domains = []; x.subnets = []; x.lists = []; x.subs = []; x.x = {};
x.refs = { c: [], s: [], r: [] }; x.own_on = false; x.domains = []; x.subnets = []; x.lists = []; x.subs = []; x.x = {};
x.full = [];
return x;
}
@@ -26446,7 +26765,7 @@ return view.extend({
function lanIp() { return netInfo().lan_ip || 'IP-роутера'; }
function devFoot(d) {
var k = { bridge: 'мост', eth: 'порт', ppp: 'PPP', tun: 'туннель', wifi: 'Wi-Fi' }[d.kind] || '';
var k = { bridge: 'мост', eth: 'порт', ppp: 'PPP', tun: ({ wireguard: 'WireGuard', amneziawg: 'AmneziaWG' }[d.proto] || 'туннель'), wifi: 'Wi-Fi' }[d.kind] || '';
return (d.net ? 'сеть ' + d.net + (k ? ' · ' + k : '') : k) + (d.missing ? ' · не найден' : '');
}
@@ -26895,7 +27214,8 @@ return view.extend({
!ownCnt ? badge('zm-off', 'пуст') : ownOn ? badge('zm-ok', 'включён · ' + nn(ownCnt, 'строка', 'строки', 'строк')) : badge('zm-warn', 'выключен · ' + nn(ownCnt, 'строка', 'строки', 'строк')) ]));
ownCard.appendChild(zm.swRow(ownOn, 'Вести свой список через VPN',
ownOn ? 'Свои домены, адреса и внешние списки идут через подключение секции — так же, как выбранные сервисы.'
: 'Выключено: свои домены, адреса и внешние списки сохранены, но идут напрямую. Включите — и они снова пойдут через VPN.',
: ownCnt ? 'Выключено: свои домены, адреса и внешние списки сохранены, но идут напрямую. Включите — и они снова пойдут через VPN.'
: 'По умолчанию выключено. Впишите ниже домены, адреса или ссылки на списки и включите — они пойдут через VPN.',
function() { set('own_on', !ownOn); }));
var ownDim = ownOn ? '' : 'opacity:.55';
[ taDomains, taSubnets, taLists ].forEach(function(t) { t.style.opacity = ownOn ? '' : '.55'; });
@@ -27145,8 +27465,13 @@ return view.extend({
netCard.appendChild(E('h3', {}, 'Сеть'));
netCard.appendChild(E('h4', { 'style': 'margin:4px 0' }, 'Откуда принимать трафик'));
netCard.appendChild(hint('Устройства из этих сетей идут через Forkozz. Обычно это br-lan — вся домашняя сеть. Гостевую или отдельную сеть можно добавить сюда же.', 0));
var src = withMissing(devs.filter(function(d) { return (d.kind === 'bridge' || d.kind === 'eth') && !isWan(d); }), draft.src_ifs);
netCard.appendChild(hint('Устройства из этих сетей идут через Forkozz. Обычно это br-lan — вся домашняя сеть. Гостевую или отдельную сеть можно добавить сюда же. Свой VPN-сервер на роутере (WireGuard, AmneziaWG, OpenVPN) тоже можно отметить — тогда его клиенты пойдут через Forkozz.', 0));
var outIfs = {};
secs().forEach(function(s) { if (s.iface) outIfs[s.iface] = true; });
if (draft.mode === 'iface' && draft.iface) outIfs[draft.iface] = true;
(cfg && cfg.tunnels || []).forEach(function(t) { if (t.steer) outIfs[t.name] = true; });
var isSrcTun = function(d) { return d.kind === 'tun' && !outIfs[d.name] && !/^(zmwarp|zm_|zmv_)/.test(d.name); };
var src = withMissing(devs.filter(function(d) { return (d.kind === 'bridge' || d.kind === 'eth' || isSrcTun(d)) && !isWan(d); }), draft.src_ifs);
netCard.appendChild(devTiles(src, function(n) { return draft.src_ifs.indexOf(n) >= 0; }, function(n, on) {
if (on && draft.src_ifs.length === 1) { zm.toast('Нужна хотя бы одна сеть', 'warning'); return; }
set('src_ifs', on ? draft.src_ifs.filter(function(x) { return x !== n; }) : draft.src_ifs.concat([ n ]));
@@ -29481,7 +29806,7 @@ var ROUTES = [
{ id: 'hosts', title: 'Hosts', sub: 'Домены в hosts и списки GeoHide', icon: 'list', group: 'Сеть', dot: 'hosts' },
{ id: 'doh', title: 'DNS over HTTPS', sub: 'Шифрованный DNS для всей сети', icon: 'globe', group: 'Сеть', dot: 'doh' },
{ id: 'awg', title: 'AmneziaWG', sub: 'Туннели AmneziaWG и WARP: установка, ключи, интерфейсы', icon: 'anarchy', group: 'Сеть', dot: 'awg' },
{ id: 'terminal', title: 'Терминал', sub: 'Командная строка роутера в браузере (ttyd)', icon: 'terminal', group: 'Сеть' },
{ id: 'terminal', title: 'Терминал', sub: 'Командная строка роутера в браузере (ttyd)', icon: 'terminal', group: 'Сеть', dot: 'term' },
{ id: 'system', title: 'Система', sub: 'Параметры роутера, зеркала и обслуживание', icon: 'cpu', group: 'Сервис' }
];
var ROUTE_BY_ID = {};
@@ -29498,7 +29823,7 @@ var alertsEl = E('div', { 'class': 'zmw-alerts' }), alertsKey = '';
/* Терминал переживает переходы между разделами: его iframe живёт в viewEl постоянно и только прячется.
* Прячем без display:none и без смены размера — иначе ttyd сожмёт терминал до нуля колонок. */
var termKeep = { host: null, frame: null, src: '' };
var TERM_SHOW = 'max-width:1100px; margin:0 0 16px', TERM_HIDE = 'max-width:1100px; margin:0; height:0; overflow:hidden; visibility:hidden';
var TERM_SHOW = 'margin:0 0 18px', TERM_HIDE = 'margin:0; height:0; overflow:hidden; visibility:hidden; border-width:0; box-shadow:none';
function termHide() { if (termKeep.host) termKeep.host.setAttribute('style', TERM_HIDE); }
@@ -29507,13 +29832,19 @@ function termDrop() {
termKeep.host = null; termKeep.frame = null; termKeep.src = '';
}
function termOpen(src) {
function termOpen(src, title) {
if (!viewEl || !sid || currentRoute() !== 'terminal') return false;
if (termKeep.frame && termKeep.src !== src) termDrop();
if (!termKeep.host || termKeep.host.parentNode !== viewEl) {
termDrop();
termKeep.frame = E('iframe', { 'src': src, 'style': 'display:block; width:100%; height:480px; border:0; border-radius:8px; background:#000' });
termKeep.host = E('div', { 'class': 'zmw-term-keep', 'style': TERM_HIDE }, [ termKeep.frame ]);
termKeep.frame = E('iframe', { 'class': 'zm-term-frame', 'src': src });
termKeep.host = E('div', { 'class': 'zmw-term-keep zm-term', 'style': TERM_HIDE }, [
E('div', { 'class': 'zm-term-bar' }, [
E('span', { 'class': 'zm-term-dots' }, [ E('i', {}), E('i', {}), E('i', {}) ]),
E('span', { 'class': 'zm-term-title' }, title || 'ttyd')
]),
E('div', { 'class': 'zm-term-body' }, [ termKeep.frame ])
]);
termKeep.src = src;
viewEl.insertBefore(termKeep.host, viewEl.firstChild);
}
@@ -29733,7 +30064,7 @@ function refreshMemory() {
}
var statusBusy = false;
var DOT_TEXT = { 1: 'работает', 2: 'не работает', 3: 'работает частично' };
var DOT_TEXT = { 1: 'работает', 2: 'ошибка — не работает', 3: 'работает частично', 4: 'установлен, не запущен или не настроен' };
function refreshShellStatus() {
if (!shell || !sid || statusBusy) return;
statusBusy = true;
@@ -29753,13 +30084,14 @@ function refreshShellStatus() {
if (k === 'awg' && h.awg_total) title = 'туннелей работает: ' + (h.awg_up || 0) + ' из ' + h.awg_total;
else if (k === 'awg' && h.awg === 1) title = 'установлен, туннелей нет';
else if (k === 'awg' && h.awg === 3) title = 'установлен не полностью — откройте вкладку и нажмите «Доустановить»';
setDot(navDots[k], h[k], title);
if (h.dots && h.dots[k] === 4) title = null;
setDot(navDots[k], h.dots && typeof h.dots[k] === 'number' ? h.dots[k] : h[k], title);
});
}).catch(function () {}).then(function () { statusBusy = false; });
}
function setDot(el, st, title) {
if (!el) return;
el.className = 'zmw-nav-dot' + (st === 1 ? ' zmw-on' : st === 2 ? ' zmw-stop' : st === 3 ? ' zmw-part' : '');
el.className = 'zmw-nav-dot' + (st === 1 ? ' zmw-on' : st === 2 ? ' zmw-stop' : st === 3 ? ' zmw-part' : st === 4 ? ' zmw-idle' : '');
el.title = title || DOT_TEXT[st] || '';
}
@@ -30287,7 +30619,8 @@ body.zmw-locked .zmw-shell { filter: blur(6px); pointer-events: none; }
box-shadow: 0 6px 16px -6px rgba(99,102,241,.8);
}
.zmw-nav-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; display: none; }
.zmw-nav-dot.zmw-on, .zmw-nav-dot.zmw-stop, .zmw-nav-dot.zmw-part { display: block; }
.zmw-nav-dot.zmw-on, .zmw-nav-dot.zmw-stop, .zmw-nav-dot.zmw-part, .zmw-nav-dot.zmw-idle { display: block; }
.zmw-nav-dot.zmw-idle { background: #9aa3b2; box-shadow: 0 0 0 3px rgba(154,163,178,.18); }
.zmw-nav-dot.zmw-part { background: var(--warn-dot); box-shadow: 0 0 0 3px var(--warn-bg), 0 0 10px var(--warn-dot); }
.zmw-nav-dot.zmw-on { background: var(--ok-dot); box-shadow: 0 0 0 3px var(--ok-bg), 0 0 10px var(--ok-dot); }
.zmw-nav-dot.zmw-stop { background: var(--bad-dot); box-shadow: 0 0 0 3px var(--bad-bg), 0 0 10px var(--bad-dot); animation: zmw-blink 1.6s ease-in-out infinite; }
@@ -30848,6 +31181,17 @@ html[data-theme="dark"] #zmw-view .zm-tile.zm-active::before { color: #a594ff; }
#zmw-view .bt-chip { border-radius: 7px; }
#zmw-view iframe { border: 1px solid var(--border-2) !important; border-radius: 16px !important; background: #fff; box-shadow: var(--shadow); }
#zmw-view .zm-term {
background: var(--console); border: 1px solid var(--console-border); border-radius: 16px;
box-shadow: inset 0 1px 0 rgba(255,255,255,.04), var(--shadow);
}
#zmw-view .zm-term-bar { height: 40px; padding: 0 20px; font-family: var(--mono); }
#zmw-view .zm-term-body { padding: 8px 15px 12px; }
#zmw-view .zm-term iframe.zm-term-frame { border: 0 !important; border-radius: 0 !important; background: var(--console); box-shadow: none; }
@media (max-width: 600px) {
#zmw-view .zm-term-bar { padding: 0 14px; }
#zmw-view .zm-term-body { padding: 6px 9px 10px; }
}
#zmw-view pre:not(.zm-log) { font-family: var(--mono); }
#zmw-view hr { border: 0; border-top: 1px solid var(--border); }
@@ -31577,6 +31921,14 @@ if [ -x /usr/bin/netshift ] && [ -f /usr/lib/netshift/constants.sh ]; then
else _zmi_warn "Сверить не удалось — откройте вкладку Forkozz и нажмите «Перезапустить»"; fi
fi
_zmi_say "Убираем временные файлы и остатки прошлых версий"
_zm_cl="$(/opt/zapret-manager-luci/backend.sh zm_cleanup install 2>/dev/null || true)"
_zm_cl_f="$(printf '%s' "$_zm_cl" | sed -n 's/.*"freed_flash_kb":\([0-9]*\).*/\1/p')"
_zm_cl_t="$(printf '%s' "$_zm_cl" | sed -n 's/.*"freed_tmp_kb":\([0-9]*\).*/\1/p')"
_zm_cl_s="$(printf '%s' "$_zm_cl" | sed -n 's/.*"flash_kb":\([0-9]*\).*/\1/p')"
if [ -n "$_zm_cl_s" ]; then _zmi_ok "Освобождено: флеш ${_zm_cl_f:-0} КБ, /tmp ${_zm_cl_t:-0} КБ. Панель занимает на флеше ${_zm_cl_s} КБ"
else _zmi_ok "Готово"; fi
ZMW_PORT="$(uci -q get uhttpd.zmweb.listen_http | tr ' ' '\n' | head -n1 | sed 's/.*://')"
ZMW_IP="$(/opt/zapret-manager-luci/backend.sh lan_ip 2>/dev/null || true)"
[ -n "$ZMW_IP" ] || ZMW_IP="192.168.1.1"