From e8d51df00512ed069baf26a267643d355fbf2985 Mon Sep 17 00:00:00 2001 From: StressOzz <131419324+StressOzz@users.noreply.github.com> Date: Mon, 20 Jul 2026 21:43:37 +0500 Subject: [PATCH] Delete Mixomo directory --- Mixomo/AllowDomain_to_MagiTrickle.sh | 223 ---- Mixomo/WARP_to_conf.sh | 180 ---- Mixomo/gen_WARP.sh | 183 ---- Mixomo/mixomo_openwrt_delete.sh | 198 ---- Mixomo/mixomo_openwrt_install.sh | 1488 -------------------------- 5 files changed, 2272 deletions(-) delete mode 100644 Mixomo/AllowDomain_to_MagiTrickle.sh delete mode 100644 Mixomo/WARP_to_conf.sh delete mode 100644 Mixomo/gen_WARP.sh delete mode 100644 Mixomo/mixomo_openwrt_delete.sh delete mode 100644 Mixomo/mixomo_openwrt_install.sh diff --git a/Mixomo/AllowDomain_to_MagiTrickle.sh b/Mixomo/AllowDomain_to_MagiTrickle.sh deleted file mode 100644 index 59bad89..0000000 --- a/Mixomo/AllowDomain_to_MagiTrickle.sh +++ /dev/null @@ -1,223 +0,0 @@ -#!/bin/sh -set -eu - -OUT="/root/AllowDomainsList.mtrickle" -WORK="/tmp/mihomo_groups.$$" -mkdir -p "$WORK" -trap 'rm -rf "$WORK"' EXIT - -REPO_TGZ="$WORK/repo.tgz" -REPO_DIR="$WORK/repo" - -RED="$(printf '\033[31m')" -GRN="$(printf '\033[32m')" -YEL="$(printf '\033[33m')" -CYN="$(printf '\033[36m')" -MAG="$(printf '\033[35m')" -RST="$(printf '\033[0m')" - -say() { color="$1"; shift; echo -e "${color}$*${RST}"; } - -fetch() { - u="$1"; d="$2" - if command -v uclient-fetch >/dev/null 2>&1; then - uclient-fetch -q -O "$d" "$u" - elif command -v wget >/dev/null 2>&1; then - wget -q -O "$d" "$u" - elif command -v curl >/dev/null 2>&1; then - curl -fsSL -o "$d" "$u" - else - say "$RED" "ОШИБКА: нет uclient-fetch/wget/curl" - exit 1 - fi -} - -count_lines_human() { - f="$1" - bytes="$(wc -c < "$f" 2>/dev/null || echo 0)" - nl="$(wc -l < "$f" 2>/dev/null || echo 0)" - if [ "$bytes" -gt 0 ] && [ "$nl" -eq 0 ]; then echo 1; else echo "$nl"; fi -} - -merge_lst_dir() { - src="$1" - [ -d "$src" ] || return 0 - find "$src" -maxdepth 1 -type f -name '*.lst' -print | while IFS= read -r f; do - base="$(basename "$f")" - out="$WORK/$base" - awk '{ sub(/\r$/,""); print }' "$f" >> "$out" - done -} - -clear -say "$MAG" "Создаём список ItDog Allow Domains для MagiTrickle" - -fetch "https://api.github.com/repos/itdoginfo/allow-domains/tarball/main" "$REPO_TGZ" - -mkdir -p "$REPO_DIR" -tar -xzf "$REPO_TGZ" -C "$REPO_DIR" - -ROOTDIR="$(find "$REPO_DIR" -mindepth 1 -maxdepth 1 -type d | head -n 1)" -[ -n "${ROOTDIR:-}" ] || { say "$RED" "ОШИБКА: не нашёл корневую папку в архиве"; exit 1; } - -say "$YEL" "Вытаскивем списки Categories / Services / Subnets/IPv4/IPv6" -merge_lst_dir "$ROOTDIR/Categories" -merge_lst_dir "$ROOTDIR/Services" -merge_lst_dir "$ROOTDIR/Subnets/IPv4" -merge_lst_dir "$ROOTDIR/Subnets/IPv6" - -LIST_COUNT="$(find "$WORK" -maxdepth 1 -name '*.lst' | wc -l | tr -d ' ')" -say "$MAG" "Всего файлов в работе: $YEL$LIST_COUNT" - -awk ' -function trim(s){ sub(/^[ \t\r\n]+/,"",s); sub(/[ \t\r\n]+$/,"",s); return s } -function tolower_ascii(s, i,c,r,up,lo){ - up="ABCDEFGHIJKLMNOPQRSTUVWXYZ"; lo="abcdefghijklmnopqrstuvwxyz"; r="" - for(i=1;i<=length(s);i++){ c=substr(s,i,1); p=index(up,c); if(p) c=substr(lo,p,1); r=r c } - return r -} -function titlecase(s){ return (s==""?s:toupper(substr(s,1,1)) substr(s,2)) } -FNR==1{ - fn=FILENAME - sub(/^.*\//,"",fn); sub(/\.lst$/,"",fn) - grp=titlecase(tolower_ascii(fn)) -} -{ - line=$0 - sub(/\r$/,"",line) - sub(/#.*/,"",line); sub(/;.*/,"",line) - line=trim(line) - if(line=="") next - - if (line ~ /^[a-zA-Z]+:\/\//) { - gsub(/^([a-zA-Z]+:\/\/)/,"",line) - sub(/\/.*$/,"",line) - } - - if(line ~ /^\*\./) line=substr(line,2) - if(line ~ /^\./) line=substr(line,2) - - line=trim(line) - if(line=="") next - - print grp "\t" line -} -' "$WORK"/*.lst > "$WORK/tagged.tsv" - -TAGGED_TOTAL="$(wc -l < "$WORK/tagged.tsv" 2>/dev/null || echo 0)" -say "$MAG" "Всего строк: $YEL$TAGGED_TOTAL" -say "$CYN" "Создаём общий список. Ждите..." - -awk -F '\t' ' -function is_ipv4(s){ return (s ~ /^([0-9]{1,3}\.){3}[0-9]{1,3}$/) } -function is_ipv4_cidr(s){ return (s ~ /^([0-9]{1,3}\.){3}[0-9]{1,3}\/[0-9]{1,2}$/) } -function is_ipv6(s){ return (s ~ /^[0-9a-fA-F:]+$/) } -function is_ipv6_cidr(s){ return (s ~ /^[0-9a-fA-F:]+\/[0-9]{1,3}$/) } -function is_domainish(s){ return (s ~ /^[A-Za-z0-9][A-Za-z0-9.-]*\.[A-Za-z0-9.-]+$/) } -function dotcount(s, i,c,n){ n=0; for(i=1;i<=length(s);i++){ c=substr(s,i,1); if(c==".") n++ } return n } - -function json_escape(s, i,c,o){ - o="" - for(i=1;i<=length(s);i++){ - c=substr(s,i,1) - if(c=="\\") o=o "\\\\" - else if(c=="\"") o=o "\\\"" - else if(c=="\r" || c=="\n") o=o "" - else o=o c - } - return o -} - -function rid( x){ - "hexdump -n 4 -e \x27/1 \"%02x\"\x27 /dev/urandom 2>/dev/null" | getline x - close("hexdump -n 4 -e \x27/1 \"%02x\"\x27 /dev/urandom 2>/dev/null") - if(x=="") x="00000000" - return x -} - -function hsv_to_hex(h, s, v, c,x,m,r,g,b,hh){ - c=v*s - hh=h/60 - x=c*(1-((hh%2)-1<0?-(hh%2-1):(hh%2-1))) - - if(hh<1){r=c;g=x;b=0} - else if(hh<2){r=x;g=c;b=0} - else if(hh<3){r=0;g=c;b=x} - else if(hh<4){r=0;g=x;b=c} - else if(hh<5){r=x;g=0;b=c} - else {r=c;g=0;b=x} - - m=v-c - r=int((r+m)*255) - g=int((g+m)*255) - b=int((b+m)*255) - - return sprintf("#%02x%02x%02x",r,g,b) -} - -BEGIN{ - OFS="" - print "{\"groups\":[" - first_group=1 -} - -{ - g=$1; v=$2 - key=g SUBSEP v - if(seen[key]++) next - groups[g]=1 - vals[g, ++cnt[g]] = v -} - -END{ - golden=137.508 - - n=0 - for(g in groups){ glist[++n]=g } - - for(i=1;i<=n;i++) for(j=i+1;j<=n;j++) if(glist[i] > glist[j]){ t=glist[i]; glist[i]=glist[j]; glist[j]=t } - - for(gi=1; gi<=n; gi++){ - g=glist[gi] - - if(!first_group) printf(",\n") - first_group=0 - - gid=rid() - - hue=(gi*golden)%360 - color=hsv_to_hex(hue,0.65,0.95) - - print "{\"id\":\"",gid,"\",\"name\":\"",json_escape(g),"\",\"color\":\"",color,"\",\"interface\":\"Mihomo\",\"enable\":true,\"rules\":[" - first_rule=1 - - m=cnt[g] - for(i=1;i<=m;i++) vlist[i]=vals[g,i] - for(i=1;i<=m;i++) for(j=i+1;j<=m;j++) if(vlist[i] > vlist[j]){ t=vlist[i]; vlist[i]=vlist[j]; vlist[j]=t } - - for(i=1;i<=m;i++){ - v=vlist[i] - if(v=="") continue - - if(is_ipv6_cidr(v) || is_ipv6(v)) typ="subnet6" - else if(is_ipv4_cidr(v) || is_ipv4(v)) typ="subnet" - else if(is_domainish(v)){ - if(dotcount(v)>=2) typ="domain"; else typ="namespace" - } else typ="namespace" - - if(!first_rule) printf(",\n") - first_rule=0 - - printf("{\"enable\":true,\"id\":\"%s\",\"name\":\"\",\"rule\":\"%s\",\"type\":\"%s\"}",rid(),json_escape(v),typ) - } - - print "]}" - delete vlist - } - - print "]}" -} -' "$WORK/tagged.tsv" > "$OUT" - -say "$GRN" "Готово!" -say "$YEL" "Файл сохранён:" "$RST""$OUT" diff --git a/Mixomo/WARP_to_conf.sh b/Mixomo/WARP_to_conf.sh deleted file mode 100644 index 93b6bce..0000000 --- a/Mixomo/WARP_to_conf.sh +++ /dev/null @@ -1,180 +0,0 @@ -#!/bin/sh -set -eu - -GREEN="\033[1;32m" -RED="\033[1;31m" -NC="\033[0m" - -IN="/root/WARP.conf" -OUT="/etc/mihomo/config.yaml" -TMP="$(mktemp)" - -[ -r "$IN" ] || { echo -e "\n${RED}Файл ${NC}$IN ${RED}отсутвтвует!${NC}"; exit 1; } - -awk -v OUT="$TMP" ' -function trim(s){ gsub(/^[ \t\r\n]+|[ \t\r\n]+$/, "", s); return s } -function lc(s){ return tolower(s) } -function yaml_quote(s){ - gsub(/\\/,"\\\\",s) - gsub(/"/,"\\\"",s) - gsub(/\r/,"",s) - return "\"" s "\"" -} -function split_endpoint(s, a,n){ - s=trim(s) - n=split(s,a,":") - if(n<2){ host=s; port="" } else { - port=a[n] - host=a[1] - for(i=2;i OUT - print "allow-lan: false" >> OUT - print "tcp-concurrent: true" >> OUT - print "mode: rule" >> OUT - print "log-level: error" >> OUT - print "ipv6: false" >> OUT - print "external-controller: 0.0.0.0:9090" >> OUT - print "external-ui: ./ui" >> OUT - print "secret:" >> OUT - print "unified-delay: true" >> OUT - print "profile:" >> OUT - print " store-selected: true" >> OUT - print " store-fake-ip: true" >> OUT - print "" >> OUT - - print "proxy-groups:" >> OUT - print " - name: GLOBAL" >> OUT - print " type: select" >> OUT - print " proxies:" >> OUT - print " - WARP" >> OUT - print " - REJECT" >> OUT - print "" >> OUT - - print "rules:" >> OUT - print " - \"MATCH,GLOBAL\"" >> OUT - print "" >> OUT - - print "proxies:" >> OUT - print " - name: WARP" >> OUT - print " type: wireguard" >> OUT - print " server: " host >> OUT - if(port!="") print " port: " port >> OUT - print " private-key: " yaml_quote(priv) >> OUT - print " udp: true" >> OUT - if(ip!="") print " ip: " ip >> OUT - if(ipv6!="") print " ipv6: " ipv6 >> OUT - print " public-key: " yaml_quote(pub) >> OUT - if(psk!="") print " pre-shared-key: " yaml_quote(psk) >> OUT - print " allowed-ips:" >> OUT - printf "%s", allowed_block >> OUT - if(mtu!="") print " mtu: " mtu >> OUT - if(keep!="") print " persistent-keepalive: " keep >> OUT - - if(s1!="" || s2!="" || s3!="" || s4!="" || jc!="" || jmin!="" || jmax!="" || h1!="" || h2!="" || h3!="" || h4!="" || i1!="" || i2!="" || i3!="" || i4!="" || i5!=""){ - print " amnezia-wg-option:" >> OUT - if(s1!="") print " s1: " s1 >> OUT - if(s2!="") print " s2: " s2 >> OUT - if(s3!="") print " s3: " s3 >> OUT - if(s4!="") print " s4: " s4 >> OUT - if(jc!="") print " jc: " jc >> OUT - if(jmin!="")print " jmin: " jmin >> OUT - if(jmax!="")print " jmax: " jmax >> OUT - if(h1!="") print " h1: " h1 >> OUT - if(h2!="") print " h2: " h2 >> OUT - if(h3!="") print " h3: " h3 >> OUT - if(h4!="") print " h4: " h4 >> OUT - if(i1!="") print " i1: " yaml_quote(i1) >> OUT - if(i2!="") print " i2: " yaml_quote(i2) >> OUT - if(i3!="") print " i3: " yaml_quote(i3) >> OUT - if(i4!="") print " i4: " yaml_quote(i4) >> OUT - if(i5!="") print " i5: " yaml_quote(i5) >> OUT - } -} -' "$IN" - -chmod 600 "$TMP" -mkdir -p "$(dirname "$OUT")" -mv -f "$TMP" "$OUT" - -/etc/init.d/mihomo reload -/etc/init.d/mihomo restart -echo -e "\n${NC}WARP${GREEN} интегрирован в ${NC}Mihomo${GREEN}!${NC}" diff --git a/Mixomo/gen_WARP.sh b/Mixomo/gen_WARP.sh deleted file mode 100644 index 3d2ca12..0000000 --- a/Mixomo/gen_WARP.sh +++ /dev/null @@ -1,183 +0,0 @@ -#!/bin/sh - -EP_LIST='Россия |engage.cloudflareclient.com:4500 -Россия #2 |engage.cloudflareclient.com:2408 -Россия #3 |engage.cloudflareclient.com:500' - -GREEN='\033[0;32m' -RED='\033[0;31m' -YELLOW='\033[1;33m' -NC='\033[0m' -MAGENTA="\033[1;35m" -CYAN="\033[1;36m" - -clear - -chose_endpoint() { -while true; do - echo -e "\n${MAGENTA}Выберите endpoint${NC}" - - i=1 - while IFS='|' read -r country ep; do - printf "${CYAN}%d) ${GREEN}%-10s${MAGENTA}| ${CYAN}%s${NC}\n" "$i" "$country" "$ep" - i=$((i+1)) - done </dev/null 2>&1; then -PKG="apk" -elif command -v opkg >/dev/null 2>&1; then -PKG="opkg" -else -echo -e "${RED}Не найден пакетный менеджер!${NC}" -exit 1 -fi - -echo -e "${CYAN}Проверяем зависимости${NC}" - -deps="wireguard-tools curl jq" -missing="" - -for pkg in $deps; do -if [ "$PKG" = "apk" ]; then -apk info -e "$pkg" >/dev/null 2>&1 || missing="$missing $pkg" -else -opkg list-installed 2>/dev/null | grep -q "^$pkg " || missing="$missing $pkg" -fi -done - -if [ -n "$missing" ]; then -echo -e "${CYAN}Обновляем пакеты${NC}" -if [ "$PKG" = "apk" ]; then -apk update >/dev/null 2>&1 || { echo -e "\n${RED}Ошибка обновления пакетов!${NC}"; exit 1; } -else -opkg update >/dev/null 2>&1 || { echo -e "\n${RED}Ошибка обновления пакетов!${NC}"; exit 1; } -fi - -install_pkg() { -pkg="$1" -if [ "$PKG" = "apk" ]; then -apk info -e "$pkg" >/dev/null 2>&1 && return -echo -e "${GREEN}Устанавливаем:${NC} $pkg" -apk add "$pkg" >/dev/null 2>&1 || { echo -e "\n${RED}Ошибка установки${NC} $pkg"; exit 1; } -else -opkg list-installed 2>/dev/null | grep -qF "^$pkg " && return -echo -e "${GREEN}Устанавливаем:${NC} $pkg" -opkg install "$pkg" >/dev/null 2>&1 || { echo -e "\n${RED}Ошибка установки${NC} $pkg"; exit 1; } -fi -} - -for pkg in $missing; do -install_pkg "$pkg" -done -fi - -echo -e "${CYAN}Генерируем ключи${NC}" -priv="$(wg genkey)" -pub="$(printf "%s" "$priv" | wg pubkey)" - -api="https://api.cloudflareclient.com/v0i1909051800" - -ins() { -curl -s \ --H "User-Agent: okhttp/3.12.1" \ --H "Content-Type: application/json" \ --X "$1" "$api/$2" "${@:3}" -} - -sec() { -ins "$1" "$2" -H "Authorization: Bearer $3" "${@:4}" -} - -echo -e "${CYAN}Регистрируем устройство в ${NC}Cloudflare" - -response=$(ins POST "reg" \ --d "{\"install_id\":\"\",\"tos\":\"$(date -u +%FT%TZ)\",\"key\":\"${pub}\",\"fcm_token\":\"\",\"type\":\"ios\",\"locale\":\"en_US\"}") - -id=$(echo "$response" | jq -r '.result.id') -token=$(echo "$response" | jq -r '.result.token') - -if [ -z "$id" ] || [ "$id" = "null" ]; then - echo -e "${RED}Ошибка регистрации${NC} $response" - if [ "$(/etc/init.d/zapret status 2>/dev/null)" = "running" ]; then - echo -e "${YELLOW}Zapret запущен. Направьте через него ${NC}cloudflareclient.com${YELLOW} и повторите попытку.${NC}" - else - echo -e "${YELLOW}Требуется установленный и запущенный ${NC}Zapret${YELLOW} с маршрутизацией ${NC}cloudflareclient.com${YELLOW} через него.${NC}" - fi - exit 1 -fi - -################################################################################################ -chose_endpoint -################################################################################################ - -echo -e "${GREEN}Активируем и генерируем ${NC}WARP${NC}" - -response=$(sec PATCH "reg/${id}" "$token" -d '{"warp_enabled":true}') - -peer_pub=$(echo "$response" | jq -r '.result.config.peers[0].public_key') -client_ipv4=$(echo "$response" | jq -r '.result.config.interface.addresses.v4') -client_ipv6=$(echo "$response" | jq -r '.result.config.interface.addresses.v6') - -if [ -z "$peer_pub" ] || [ "$peer_pub" = "null" ]; then -echo -e "\n${RED}Ошибка получения конфигурации${NC}" -exit 1 -fi - -conf=$(cat < - -[Peer] -PublicKey = ${peer_pub} -AllowedIPs = 0.0.0.0/0, ::/0 -Endpoint = ${ENDPOINT} -PersistentKeepalive = 25 -EOF -) - -echo -echo -e "${GREEN}========== ${YELLOW}WARP CONFIG${GREEN} ==========${NC}" -echo "$conf" -echo -e "${GREEN}=================================${NC}" -echo "$conf" > /root/WARP.conf -echo -e "\n${YELLOW}Файл сохранён:${NC} /root/WARP.conf" diff --git a/Mixomo/mixomo_openwrt_delete.sh b/Mixomo/mixomo_openwrt_delete.sh deleted file mode 100644 index e8bc99f..0000000 --- a/Mixomo/mixomo_openwrt_delete.sh +++ /dev/null @@ -1,198 +0,0 @@ -#!/bin/sh - -SCRIPT_VERSION="v0.1.2-alpha" - -GREEN='\033[0;32m' -RED='\033[0;31m' -YELLOW='\033[1;33m' -NC='\033[0m' - -log_info() { echo -e "${GREEN}[INFO]${NC} $*"; } -log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } -log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } -log_step() { echo -e "${GREEN}=== $* ===${NC}"; } -log_done() { echo -e "${GREEN}$*${NC}"; } - -USE_APK=0 -if command -v apk > /dev/null 2>&1; then - USE_APK=1 -fi - -is_pkg_installed() { - if [ "$USE_APK" -eq 1 ]; then - apk info "$1" > /dev/null 2>&1 - else - opkg list-installed 2>/dev/null | grep -q "^$1 " - fi -} - -remove_pkg() { - if [ "$USE_APK" -eq 1 ]; then - apk del "$1" > /dev/null 2>&1 || true - else - opkg remove "$1" > /dev/null 2>&1 || true - fi -} - -remove_mihomo() { - log_info "Проверка наличия Mihomo..." - local CLEANED=0 - - if [ -f "/etc/init.d/mihomo" ]; then - /etc/init.d/mihomo stop 2>/dev/null || true - /etc/init.d/mihomo disable 2>/dev/null || true - rm -f /etc/init.d/mihomo - CLEANED=1 - fi - - if [ -f "/usr/bin/mihomo" ]; then - rm -f /usr/bin/mihomo - CLEANED=1 - fi - - if [ -d "/etc/mihomo" ] || [ -d "/www/luci-static/resources/view/mihomo" ]; then - rm -rf /etc/mihomo - rm -f /usr/share/luci/menu.d/luci-app-mihomo.json - rm -f /usr/share/rpcd/acl.d/luci-app-mihomo.json - rm -rf /www/luci-static/resources/view/mihomo - CLEANED=1 - fi - - if [ "$CLEANED" -eq 1 ]; then - log_done "Mihomo и его файлы успешно удалены." - else - log_done "Mihomo не найден (уже удалён)." - fi -} - -remove_hev_tunnel() { - log_info "Проверка наличия Hev-Socks5-Tunnel..." - local ACTION_TAKEN=0 - - if [ -f "/etc/init.d/hev-socks5-tunnel" ]; then - /etc/init.d/hev-socks5-tunnel stop 2>/dev/null || true - fi - - if is_pkg_installed hev-socks5-tunnel; then - remove_pkg hev-socks5-tunnel - ACTION_TAKEN=1 - fi - - if [ -d "/etc/hev-socks5-tunnel" ] || [ -f "/etc/config/hev-socks5-tunnel" ]; then - rm -rf /etc/hev-socks5-tunnel - rm -f /etc/config/hev-socks5-tunnel - ACTION_TAKEN=1 - fi - - echo "--> Очистка UCI..." - uci delete network.Mihomo 2>/dev/null || true - - local fw_section - for fw_section in $(uci show firewall 2>/dev/null \ - | grep -E "\.name='Mihomo'" \ - | sed "s/\.name.*//"); do - uci delete "$fw_section" 2>/dev/null || true - done - - for fw_section in $(uci show firewall 2>/dev/null \ - | grep -E "\.(src|dest)='Mihomo'" \ - | sed -E "s/\.(src|dest).*//"); do - uci delete "$fw_section" 2>/dev/null || true - done - - uci delete firewall.Mihomo 2>/dev/null || true - uci delete firewall.lan_to_Mihomo 2>/dev/null || true - - uci commit network - uci commit firewall - - /etc/init.d/network reload 2>/dev/null || true - /etc/init.d/firewall restart 2>/dev/null || true - - if [ "$ACTION_TAKEN" -eq 1 ]; then - log_done "Hev-Socks5-Tunnel и настройки удалены." - else - log_done "Hev-Socks5-Tunnel не найден (уже удалён)." - fi -} - -remove_magitrickle() { - log_info "Проверка наличия MagiTrickle..." - local PKG_REMOVED=0 - - if [ -f "/etc/init.d/magitrickle" ]; then - /etc/init.d/magitrickle stop 2>/dev/null || true - /etc/init.d/magitrickle disable 2>/dev/null || true - fi - - if is_pkg_installed magitrickle_mod; then - log_info "Найден MagiTrickle Mod. Удаление..." - remove_pkg magitrickle_mod - PKG_REMOVED=1 - fi - - if is_pkg_installed magitrickle; then - log_info "Найден MagiTrickle. Удаление..." - remove_pkg magitrickle - PKG_REMOVED=1 - fi - - local FILES_REMOVED=0 - if [ -d "/www/luci-static/resources/view/magitrickle" ] || \ - [ -f "/usr/share/luci/menu.d/luci-app-magitrickle.json" ]; then - rm -rf /www/luci-static/resources/view/magitrickle - rm -f /usr/share/luci/menu.d/luci-app-magitrickle.json - FILES_REMOVED=1 - fi - - if [ -f "/etc/magitrickle/state/config.yaml" ]; then - rm -f /etc/magitrickle/state/config.yaml - rm -f /etc/magitrickle/state/config.yaml.backup - FILES_REMOVED=1 - fi - - if [ "$PKG_REMOVED" -eq 1 ] || [ "$FILES_REMOVED" -eq 1 ]; then - log_done "MagiTrickle и его файлы удалены." - else - log_done "MagiTrickle не найден (уже удалён)." - fi -} - -cleanup_system() { - log_info "Очистка кэша и перезапуск служб..." - rm -rf /tmp/luci-indexcache /tmp/luci-modulecache/ - /etc/init.d/firewall stop >/dev/null 2>&1 - sleep 2 - /etc/init.d/firewall start >/dev/null 2>&1 - conntrack -F 2>/dev/null - /etc/init.d/dnsmasq restart >/dev/null 2>&1 - /etc/init.d/odhcpd restart >/dev/null 2>&1 - /etc/init.d/rpcd restart >/dev/null 2>&1 - /etc/init.d/uhttpd restart >/dev/null 2>&1 -} - -main() { - clear - log_done "Скрипт удаления Mixomo OpenWrt $SCRIPT_VERSION от Internet Helper" - echo "" - - log_step "[1/4] Удаление Mihomo" - remove_mihomo - echo "" - - log_step "[2/4] Удаление Hev-Tunnel" - remove_hev_tunnel - echo "" - - log_step "[3/4] Удаление MagiTrickle" - remove_magitrickle - echo "" - - log_step "[4/4] Завершение" - cleanup_system - echo "" - - log_done "Удаление успешно завершено!" -} - -main diff --git a/Mixomo/mixomo_openwrt_install.sh b/Mixomo/mixomo_openwrt_install.sh deleted file mode 100644 index 18b6451..0000000 --- a/Mixomo/mixomo_openwrt_install.sh +++ /dev/null @@ -1,1488 +0,0 @@ -#!/bin/sh - -SCRIPT_VERSION="v0.2.0-alpha" - -MIHOMO_INSTALL_DIR="/etc/mihomo" -MIHOMO_BIN="/usr/bin/mihomo" - -GREEN='\033[0;32m' -RED='\033[0;31m' -YELLOW='\033[1;33m' -CYAN='\033[0;36m' -NC='\033[0m' - -log_online() { echo -e "${GREEN}[ONLINE]${NC} $*"; } -log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } -log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } -log_done() { echo -e "${GREEN}$*${NC}"; } -step_fail() { echo -e "${RED}[FAIL]${NC}"; exit 1; } - -USE_APK=0 -if command -v apk > /dev/null 2>&1; then - USE_APK=1 -fi - -manage_pkg() { - local action="$1" - shift - if [ "$USE_APK" -eq 1 ]; then - case "$action" in - update) apk update ;; - install) apk add "$@" ;; - remove) apk del "$@" ;; - esac - else - case "$action" in - update) opkg update ;; - install) opkg install "$@" ;; - remove) opkg remove "$@" ;; - esac - fi -} - -detect_mihomo_arch() { - local arch - arch=$(uname -m) - local endian_byte - endian_byte=$(hexdump -s 5 -n 1 -e '1/1 "%d"' /bin/busybox 2>/dev/null || echo "0") - - case "$arch" in - x86_64) - if grep -q "avx2" /proc/cpuinfo; then - echo "amd64" - else - echo "amd64-compatible" - fi - ;; - i?86) echo "386" ;; - aarch64|arm64) echo "arm64" ;; - armv7*) echo "armv7" ;; - armv5*|armv4*) echo "armv5" ;; - mips*) - local fpu - fpu=$(grep -c "FPU" /proc/cpuinfo 2>/dev/null || echo 0) - local floattype="softfloat" - [ "$fpu" -gt 0 ] && floattype="hardfloat" - if [ "$endian_byte" = "1" ]; then - echo "mipsle-${floattype}" - else - echo "mips-${floattype}" - fi - ;; - riscv64) echo "riscv64" ;; - *) - log_error "Архитектура $arch не распознана" - exit 1 - ;; - esac -} - -verify_required_deps() { - local missing=0 - - if ! command -v curl >/dev/null 2>&1; then - log_error "Пакет curl не найден!" - missing=1 - fi - - if [ ! -f /etc/ssl/certs/ca-certificates.crt ] && [ ! -f /etc/ssl/certs/ca-bundle.crt ]; then - log_error "Пакет ca-certificates не найден!" - missing=1 - fi - - if [ ! -c /dev/net/tun ]; then - modprobe tun >/dev/null 2>&1 || true - if [ ! -c /dev/net/tun ]; then - log_error "В ядре нет поддержки TUN (/dev/net/tun)!" - missing=1 - fi - fi - - if [ "$missing" -eq 1 ]; then - return 1 - fi - - return 0 -} - -install_deps() { - log_online "Установка зависимостей" - - local PKG_LOG="/tmp/install_deps.log" - - if [ "$USE_APK" -eq 1 ]; then - apk update > "$PKG_LOG" 2>&1 || true - local AVAIL_PKG - AVAIL_PKG=$(grep -o '[0-9]* distinct packages available' "$PKG_LOG" | grep -o '^[0-9]*') - if [ -z "$AVAIL_PKG" ] || [ "$AVAIL_PKG" -eq 0 ]; then - log_warn "apk update не вернул доступных пакетов, повторная попытка..." - sleep 3 - apk update > "$PKG_LOG" 2>&1 || true - AVAIL_PKG=$(grep -o '[0-9]* distinct packages available' "$PKG_LOG" | grep -o '^[0-9]*') - if [ -z "$AVAIL_PKG" ] || [ "$AVAIL_PKG" -eq 0 ]; then - log_error "apk update завершился без доступных пакетов:" - cat "$PKG_LOG" - rm -f "$PKG_LOG" - return 1 - fi - fi - - apk add unzip ca-certificates kmod-tun kmod-nft-tproxy kmod-nft-nat curl >> "$PKG_LOG" 2>&1 || true - else - if ! opkg update > "$PKG_LOG" 2>&1; then - log_error "Ошибка обновления списков пакетов (opkg update):" - cat "$PKG_LOG" - rm -f "$PKG_LOG" - return 1 - fi - - opkg install unzip ca-certificates kmod-tun kmod-nft-tproxy kmod-nft-nat curl libcurl4 ca-bundle >> "$PKG_LOG" 2>&1 || true - fi - - rm -f "$PKG_LOG" - - if ! verify_required_deps; then - log_error "Не удалось подтвердить наличие обязательных компонентов!" - return 1 - fi -} - -install_mihomo() { - local REQ_TMP_KB=16000 - local REQ_ROOT_KB=18000 - - local AVAIL_TMP_KB - AVAIL_TMP_KB=$(df -k /tmp | awk 'NR==2 {print $4}') - if [ "$AVAIL_TMP_KB" -lt "$REQ_TMP_KB" ]; then - log_error "Недостаточно места в /tmp: доступно $((AVAIL_TMP_KB/1024)) MB, требуется $((REQ_TMP_KB/1024)) MB" - return 1 - fi - - local INSTALL_DIR_PATH - INSTALL_DIR_PATH=$(dirname "$MIHOMO_BIN") - local AVAIL_ROOT_KB - AVAIL_ROOT_KB=$(df -k "$INSTALL_DIR_PATH" | awk 'NR==2 {print $4}') - - if [ "$AVAIL_ROOT_KB" -lt "$REQ_ROOT_KB" ]; then - log_error "Недостаточно места на диске: доступно $((AVAIL_ROOT_KB/1024)) MB, требуется $((REQ_ROOT_KB/1024)) MB" - if [ -f "$MIHOMO_BIN" ]; then - log_warn "Найдена установленная версия: $MIHOMO_BIN" - printf "Удалить старую версию для освобождения места? [y/+/д или n/-/н]: " - read -r response - case "$response" in - [yY+дД]*) - rm -f "$MIHOMO_BIN" - AVAIL_ROOT_KB=$(df -k "$INSTALL_DIR_PATH" | awk 'NR==2 {print $4}') - if [ "$AVAIL_ROOT_KB" -lt "$REQ_ROOT_KB" ]; then - log_error "Места всё равно недостаточно после удаления." - return 1 - fi - ;; - *) - log_warn "Установка отменена." - return 1 - ;; - esac - else - log_warn "Старая версия не найдена. Удалите лишние пакеты вручную." - return 1 - fi - fi - - if [ -f "/etc/init.d/mihomo" ]; then - /etc/init.d/mihomo stop 2>/dev/null || true - fi - - if [ -z "${MIHOMO_ARCH+x}" ]; then - MIHOMO_ARCH=$(detect_mihomo_arch) - fi - echo "Архитектура системы: $(uname -m) -> выбран файл: $MIHOMO_ARCH" - - mkdir -p "$MIHOMO_INSTALL_DIR" \ - /etc/mihomo/proxy-providers \ - /etc/mihomo/rule-providers \ - /etc/mihomo/rule-files - - echo "$MIHOMO_ARCH" > /etc/mihomo/.arch - - echo "Получение номера последней версии" - local RELEASE_TAG - RELEASE_TAG=$(curl -Ls -o /dev/null -w '%{url_effective}' https://github.com/MetaCubeX/mihomo/releases/latest | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' | head -1) - if [ -z "$RELEASE_TAG" ]; then - log_error "Не удалось определить версию. Проверьте интернет." - return 1 - fi - echo "Последняя версия: $RELEASE_TAG" - - local FILENAME="mihomo-linux-${MIHOMO_ARCH}-${RELEASE_TAG}.gz" - local DOWNLOAD_URL="https://github.com/MetaCubeX/mihomo/releases/download/${RELEASE_TAG}/${FILENAME}" - local TMP_FILE="/tmp/mihomo.gz" - - log_online "Скачивание архива $FILENAME" - log_online "$DOWNLOAD_URL" - if ! curl -Lf --retry 3 --retry-delay 2 "$DOWNLOAD_URL" -o "$TMP_FILE" >/dev/null 2>&1; then - log_error "Ошибка скачивания! Проверьте, существует ли файл $FILENAME в релизах." - return 1 - fi - - echo "Распаковка архива" - if ! gunzip -c "$TMP_FILE" > "$MIHOMO_BIN" 2>/dev/null; then - log_error "Ошибка распаковки архива" - rm -f "$TMP_FILE" - return 1 - fi - chmod +x "$MIHOMO_BIN" - rm -f "$TMP_FILE" - - echo "Проверка работы ядра Mihomo" - if ! "$MIHOMO_BIN" -v >/dev/null 2>&1; then - log_error "Ядро не запускается! Возможно, выбрана неверная архитектура." - return 1 - fi - - local CONFIG_FILE="/etc/mihomo/config.yaml" - local WRITE_NEW_CONFIG=1 - - if [ -f "$CONFIG_FILE" ]; then - if grep -q "mixed-port: 7890" "$CONFIG_FILE"; then - echo "Использование существующей конфигурации" - WRITE_NEW_CONFIG=0 - else - log_warn "Конфигурация найдена, но без 'mixed-port: 7890'. Создаём резервную копию" - cp "$CONFIG_FILE" "${CONFIG_FILE}.bak" - fi - fi - - if [ "$WRITE_NEW_CONFIG" -eq 1 ]; then - echo "Создание новой конфигурации /etc/mihomo/config.yaml..." - cat > "$CONFIG_FILE" <<'EOF' -mode: rule -ipv6: false -mixed-port: 7890 -log-level: error -allow-lan: true -unified-delay: true -tcp-concurrent: false -find-process-mode: off -external-controller: 0.0.0.0:9090 -external-ui: ./ui -routing-mark: 2 -profile: - store-selected: true - store-fake-ip: true - tracing: true -sniffer: - enable: true - force-dns-mapping: true - parse-pure-ip: true - sniff: - HTTP: - ports: [80] - override-destination: true - TLS: - ports: [443, 8443] - QUIC: - ports: [443, 8443] - skip-domain: - - Mijia Cloud - - +.lan - - +.local - - +.msftconnecttest.com - - +.msftncsi.com - - +.3gppnetwork.org - - +.openwrt.org - - +.vsean.net - - cudy.net - -dns: - enable: true - listen: 0.0.0.0:7880 - ipv6: false - nameserver: - - https://8.8.8.8/dns-query - - https://8.8.4.4/dns-query - - https://1.1.1.1/dns-query - - https://1.0.0.1/dns-query - - https://9.9.9.9/dns-query - - https://149.112.112.112/dns-query - - https://94.140.14.140/dns-query - - https://94.140.14.141/dns-query - - https://77.88.8.8/dns-query - - https://77.88.8.1/dns-query - -proxies: - - name: Домашний интернет - type: direct - -proxy-groups: - -rule-providers: - -rules: - - MATCH,Домашний интернет -EOF - fi - - echo "Создание службы /etc/init.d/mihomo" - cat > /etc/init.d/mihomo <<'EOF' -#!/bin/sh /etc/rc.common -START=99 -USE_PROCD=1 - -MIHOMO_BIN="/usr/bin/mihomo" -MIHOMO_DIR="/etc/mihomo" -MIHOMO_CONF="/etc/mihomo/config.yaml" - -start_service() { - [ -x "$MIHOMO_BIN" ] || return 1 - [ -s "$MIHOMO_CONF" ] || return 1 - - procd_open_instance "main" - procd_set_param command "$MIHOMO_BIN" -d "$MIHOMO_DIR" -f "$MIHOMO_CONF" - procd_set_param stdout 1 - procd_set_param stderr 1 - procd_set_param respawn - procd_close_instance -} - -service_triggers() { - procd_add_reload_trigger "mihomo" -} -EOF - chmod +x /etc/init.d/mihomo - /etc/init.d/mihomo enable || log_warn "Не удалось включить автозапуск" - - echo "Настройка страницы LuCI для управления Mihomo" - mkdir -p /usr/share/luci/menu.d - cat > /usr/share/luci/menu.d/luci-app-mihomo.json <<'EOF' -{ - "admin/services/mihomo": { - "title": "Mihomo", - "order": 60, - "action": { "type": "view", "path": "mihomo/config" }, - "depends": { "acl": [ "luci-app-mihomo" ] } - } -} -EOF - - mkdir -p /usr/share/rpcd/acl.d - cat > /usr/share/rpcd/acl.d/luci-app-mihomo.json <<'EOF' -{ - "luci-app-mihomo": { - "description": "Mihomo control", - "read": { - "file": { - "/etc/mihomo/config.yaml": ["read"], - "/etc/mihomo/rule-files/": ["list"], - "/etc/mihomo/rule-files/*": ["read"] - }, - "ubus": { - "file": ["read", "list"], - "service": ["list"] - } - }, - "write": { - "file": { - "/etc/mihomo/config.yaml": ["write"], - "/etc/mihomo/rule-files/*": ["write"], - "/usr/bin/mihomo": ["exec"], - "/etc/init.d/mihomo": ["exec"], - "/sbin/logread": ["exec"], - "/bin/sh": ["exec"], - "/bin/ash": ["exec"], - "/usr/bin/curl": ["exec"], - "/usr/bin/wget": ["exec"], - "/bin/gzip": ["exec"], - "/bin/chmod": ["exec"], - "/bin/mv": ["exec"], - "/bin/rm": ["exec"] - }, - "ubus": { - "file": ["write"], - "service": ["list"] - } - } - } -} -EOF - - local VIEW_PATH="/www/luci-static/resources/view/mihomo" - local ACE_PATH="$VIEW_PATH/ace" - mkdir -p "$ACE_PATH" - - echo "Определение актуальной версии ACE Editor" - local LATEST_ACE_VER - LATEST_ACE_VER=$(curl -s "https://api.cdnjs.com/libraries/ace" | grep -o '"version":"[^"]*"' | cut -d'"' -f4 | head -1) - if [ -z "$LATEST_ACE_VER" ]; then - log_warn "cdnjs API недоступен, пробуем GitHub API" - LATEST_ACE_VER=$(curl -s "https://api.github.com/repos/ajaxorg/ace/releases/latest" | grep -o '"tag_name": *"[^"]*"' | cut -d'"' -f4 | sed 's/^v//' | head -1) - fi - if [ -z "$LATEST_ACE_VER" ]; then - log_warn "Используем фиксированную версию ACE Editor" - LATEST_ACE_VER="1.43.3" - else - echo "Актуальная версия ACE Editor: $LATEST_ACE_VER" - fi - - log_online "Скачивание файлов для ACE Editor $LATEST_ACE_VER:" - local CDNJS_ACE_VER="1.43.6" - for file in ace.js theme-merbivore_soft.js theme-tomorrow.js mode-yaml.js worker-yaml.js; do - local dest="${ACE_PATH}/${file}" - local success=0 - - for url in "https://cdn.jsdelivr.net/npm/ace-builds@${LATEST_ACE_VER}/src-min-noconflict/${file}" \ - "https://raw.githubusercontent.com/ajaxorg/ace-builds/master/src-min-noconflict/${file}" \ - "https://cdnjs.cloudflare.com/ajax/libs/ace/${CDNJS_ACE_VER}/${file}"; do - - log_online "Скачивание $file" - if curl -Lf -s --connect-timeout 5 --max-time 30 -o "$dest" "$url" || wget -q -T 5 -O "$dest" "$url"; then - if [ -s "$dest" ]; then - success=1 - break - fi - fi - echo "FAIL" - done - - if [ "$success" -eq 0 ]; then - log_error "Не удалось скачать $file ни из одного источника." - return 1 - fi - done - - echo "Создание config.js" - cat > "$VIEW_PATH/config.js" <<'EOF' -'use strict'; -'require view'; -'require fs'; -'require ui'; -'require rpc'; - -var ACE_DIR = '/luci-static/resources/view/mihomo/ace/'; -var RELOAD_DELAY = 1000; -var MAIN_CONFIG = '/etc/mihomo/config.yaml'; -var RULE_DIR = '/etc/mihomo/rule-files/'; - -var editor = null; -var currentFile = MAIN_CONFIG; -var cachedRuleFiles = []; -var mainConfigContent = ''; -var loadedScripts = {}; -var VALID_ACTIONS = ['start', 'stop', 'restart', 'check', 'logs']; - -var callServiceList = rpc.declare({ - object: 'service', - method: 'list', - params: ['name'] -}); - -function escapeHtml(text) { - if (typeof text !== 'string') return text; - return text.replace(/[&<>"']/g, function(m) { - return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[m]; - }); -} - -function validatePath(path, allowedBase) { - if (!path || typeof path !== 'string') return false; - if (path.includes('..') || path.includes('\0') || path.includes('~')) return false; - var resolved = path.replace(/\/+/g, '/'); - if (!resolved.startsWith(allowedBase)) return false; - if (resolved.length > 1024) return false; - return true; -} - -function isSafeRulePath(path) { - return validatePath(path, RULE_DIR) && path !== MAIN_CONFIG; -} - -function validateFilename(filename) { - if (!filename || typeof filename !== 'string') return false; - if (!/^[a-zA-Z0-9._-]+$/.test(filename)) return false; - if (filename.length > 255) return false; - var reservedNames = ['con', 'prn', 'aux', 'nul', 'com1', 'lpt1', '.']; - if (reservedNames.includes(filename.toLowerCase())) return false; - return true; -} - -function sanitizeTabName(name) { - if (!name) return ''; - return name.replace(/[<>"'`]/g, ''); -} - -function loadScript(src) { - return new Promise(function(resolve, reject) { - if (loadedScripts[src]) { resolve(); return; } - var script = document.createElement('script'); - script.src = src; - script.onload = function() { loadedScripts[src] = true; resolve(); }; - script.onerror = reject; - document.head.appendChild(script); - }); -} - -function detectRuleType(line) { - line = line.trim(); - if (line.includes(':') && !line.match(/http(s)?:\/\//)) return 'IP-CIDR6'; - if (/^\d{1,3}(\.\d{1,3}){3}\/\d+$/.test(line)) return 'IP-CIDR'; - if (/^\d{1,3}(\.\d{1,3}){3}$/.test(line)) return 'IP-CIDR'; - if (line.startsWith('.')) return 'DOMAIN-WILDCARD'; - var cleanDomain = line.replace(/^\./, ''); - var dots = (cleanDomain.match(/\./g) || []).length; - if (dots >= 2) return 'DOMAIN'; - if (dots === 1) return 'DOMAIN-SUFFIX'; - return 'DOMAIN-KEYWORD'; -} - -function generateProviderSnippet(filename) { - if (filename === MAIN_CONFIG) return ''; - var baseName = filename.split('/').pop(); - if (!validateFilename(baseName)) throw new Error('Invalid filename'); - var nameNoExt = baseName.replace(/\.(yaml|txt)$/, ''); - var isTxt = baseName.endsWith('.txt'); - var behavior = isTxt ? 'domain' : 'classical'; - var format = isTxt ? 'text' : 'yaml'; - return `${nameNoExt}-list:\n type: file\n behavior: ${behavior}\n format: ${format}\n path: ./rule-files/${baseName}`; -} - -function isLuciDarkMode() { - try { - var rgb = window.getComputedStyle(document.body).backgroundColor.match(/\d+/g); - if (rgb) { - var luma = 0.2126 * parseInt(rgb[0]) + 0.7152 * parseInt(rgb[1]) + 0.0722 * parseInt(rgb[2]); - return luma < 128; - } - } catch(e) {} - return false; -} - -return view.extend({ - isProcessing: false, - currentVersion: 'Неизвестно', - latestVersion: null, - updateButton: null, - latestVersionEl: null, - - getMihomoVersion: function() { - return fs.stat('/usr/bin/mihomo') - .then(function() { return fs.exec('/usr/bin/mihomo', ['--v']); }) - .then(function(res) { - if (res.code === 0 && res.stdout) { - var match = res.stdout.match(/v(\d+\.\d+\.\d+)/); - return match ? match[0] : 'Неизвестно'; - } - return 'Неизвестно'; - }) - .catch(function(err) { - console.error('Error getting version:', err); - return 'Неизвестно'; - }); - }, - - renderUpdateStatus: function(latestVersion, isManual) { - var currentVersion = this.currentVersion || 'Неизвестно'; - this.latestVersion = latestVersion; - - if (this.latestVersionEl) { - this.latestVersionEl.textContent = _('(актуальное ядро %s)').format(latestVersion.replace('v', '')); - this.latestVersionEl.style.display = 'inline'; - // Используем стандартный зеленый через opacity/filter или оставляем для привлечения внимания - this.latestVersionEl.style.color = (latestVersion !== currentVersion) ? '#5cb85c' : ''; - this.latestVersionEl.style.opacity = (latestVersion !== currentVersion) ? '1' : '0.6'; - } - - if (latestVersion === currentVersion) { - this.updateButton.textContent = _('Проверить обновление'); - this.updateButton.className = 'btn cbi-button-neutral'; - this.updateButton.disabled = false; - this.updateButton.onclick = function() { window.location.reload(); }; - if (isManual) window.location.reload(); - } else { - this.updateButton.textContent = _('Установить обновление'); - this.updateButton.className = 'btn cbi-button-action'; - this.updateButton.disabled = false; - this.updateButton.onclick = ui.createHandlerFn(this, 'handleUpdateMihomo'); - } - }, - - checkForUpdates: function(isManual) { - var self = this; - var CACHE_KEY = 'mihomo_update_cache'; - var CACHE_TIME = 3600 * 1000; - - if (!isManual) { - try { - var cachedRaw = localStorage.getItem(CACHE_KEY); - if (cachedRaw) { - var cached = JSON.parse(cachedRaw); - if (cached.version && (Date.now() - cached.timestamp < CACHE_TIME)) { - this.renderUpdateStatus(cached.version, false); - return; - } - } - } catch (e) {} - } - - if (isManual) ui.showModal(null, [E('p', { 'class': 'spinning' }, _('Проверка обновлений...'))]); - - var cmd = 'wget -q -O - "https://api.github.com/repos/MetaCubeX/mihomo/releases/latest" 2>/dev/null | grep -m1 \'"tag_name":\' | sed \'s/.*"\\(v[0-9.]*\\)".*/\\1/\''; - - fs.exec('/bin/sh', ['-c', cmd]) - .then(function(res) { - if (isManual) ui.hideModal(); - if (!res || typeof res !== 'object') throw new Error('Bad response'); - var latestVersion = (res.stdout || '').trim().replace(/["'\s]/g, ''); - if (!latestVersion || !latestVersion.match(/^v\d+\.\d+\.\d+$/)) { - if (isManual) ui.addNotification(null, E('p', _('Ошибка: ') + latestVersion), 'error'); - return; - } - try { localStorage.setItem(CACHE_KEY, JSON.stringify({ version: latestVersion, timestamp: Date.now() })); } catch (e) {} - self.renderUpdateStatus(latestVersion, isManual); - }) - .catch(function(err) { - if (isManual) { - ui.hideModal(); - ui.addNotification(null, E('p', _('Ошибка: ') + err.message), 'error'); - } - }); - }, - - handleUpdateMihomo: function() { - var self = this; - var latestVersion = this.latestVersion; - if (!latestVersion) return; - this.updateButton.textContent = _('Подождите...'); - this.updateButton.disabled = true; - var arch = 'arm64'; - var downloadUrl = 'https://github.com/MetaCubeX/mihomo/releases/download/' + latestVersion + '/mihomo-linux-' + arch + '-' + latestVersion + '.gz'; - var steps = [ - { msg: _('Создание бэкапа...'), shell: 'cp -f /usr/bin/mihomo /tmp/mihomo.backup' }, - { msg: _('Остановка Mihomo...'), shell: '/etc/init.d/mihomo stop' }, - { msg: _('Скачивание архива %s...').format(latestVersion), shell: 'wget -q -O /tmp/mihomo.gz "' + downloadUrl + '" && test -s /tmp/mihomo.gz' }, - { msg: _('Распаковка архива...'), shell: '/bin/gzip -d -c /tmp/mihomo.gz > /tmp/mihomo_new 2>/dev/null && test -s /tmp/mihomo_new' }, - { msg: _('Выдача временных прав...'), shell: '/bin/chmod 755 /tmp/mihomo_new' }, - { msg: _('Проверка ядра...'), shell: '/tmp/mihomo_new -v 2>&1 || true' }, - { msg: _('Установка ядра...'), shell: '/bin/mv -f /tmp/mihomo_new /usr/bin/mihomo' }, - { msg: _('Выдача постоянных прав...'), shell: '/bin/chmod 755 /usr/bin/mihomo' }, - { msg: _('Запуск Mihomo...'), shell: '/etc/init.d/mihomo start' }, - { msg: _('Удаление бэкапа...'), shell: 'rm -f /tmp/mihomo.gz /tmp/mihomo.backup' } - ]; - var executeStep = function(index) { - if (index >= steps.length) { - self.showOutput(_('Обновлено успешно! Перезагрузка...'), false); - window.location.reload(); - return Promise.resolve(); - } - var currentStep = steps[index]; - self.showOutput(currentStep.msg, false); - return fs.exec('/bin/sh', ['-c', currentStep.shell]) - .then(function(res) { - if (!res || res.code !== 0) throw new Error('Err: ' + (res ? res.code : 'unknown')); - return executeStep(index + 1); - }); - }; - executeStep(0).catch(function(err) { - if (self.updateButton) { - self.updateButton.textContent = _('Ошибка. Повторить обновление?'); - self.updateButton.disabled = false; - self.updateButton.onclick = ui.createHandlerFn(self, 'handleUpdateMihomo'); - } - self.showOutput(_('Ошибка: %s').format(err.message), true); - fs.exec('/bin/sh', ['-c', 'cp -f /tmp/mihomo.backup /usr/bin/mihomo && /etc/init.d/mihomo start']).catch(function() {}); - }); - }, - - load: function() { - return Promise.all([ - fs.read(MAIN_CONFIG).catch(function() { return ''; }), - callServiceList('mihomo').catch(function() { return {}; }), - fs.list(RULE_DIR).catch(function() { return []; }) - ]); - }, - - render: function(data) { - data = data || []; - mainConfigContent = data[0] || ''; - var serviceInfo = data[1] || {}; - cachedRuleFiles = (data[2] || []).sort(function(a, b) { return a.name.localeCompare(b.name); }); - var isRunning = !!(serviceInfo.mihomo && serviceInfo.mihomo.instances.main.running); - - var versionContainer = E('span', { 'id': 'mihomo-version', 'style': 'margin-left: 10px; font-size: 0.9em; opacity: 0.7;' }, _('Загрузка...')); - var latestVersionEl = E('span', { 'id': 'mihomo-latest-version', 'style': 'margin-left: 4px; font-size: 0.9em; opacity: 0.7; display: none;' }, ''); - this.latestVersionEl = latestVersionEl; - var updateButton = E('button', { 'id': 'mihomo-update-btn', 'class': 'btn cbi-button-neutral', 'style': 'margin-left: 10px; padding: 0 0.6em; font-size: 0.9em;', 'disabled': true }, _('Проверить обновление')); - this.updateButton = updateButton; - - var statusBadge = isRunning - ? E('span', { - 'class': 'label success', - 'style': 'margin-left: 14px; font-size: 0.85em; min-height: 1.7rem; padding: 0 1.9em; display: inline-flex; align-items: center; vertical-align: middle;' - }, _('работает')) - : E('span', { - 'class': 'label', - 'style': 'margin-left: 14px; font-size: 0.85em; min-height: 1.7rem; padding: 0 1.9em; display: inline-flex; align-items: center; vertical-align: middle;' - }, _('остановлен')); - - var serviceButton = isRunning - ? E('button', { 'class': 'btn cbi-button-reset', 'style': 'margin-left: 16px;', 'click': ui.createHandlerFn(this, 'handleServiceAction', 'stop') }, _('Остановить')) - : E('button', { 'class': 'btn cbi-button-positive btn-save-custom', 'style': 'margin-left: 16px;', 'click': ui.createHandlerFn(this, 'handleServiceAction', 'start') }, _('Запустить')); - - var header = E('div', { 'style': 'display: flex; align-items: center; margin-bottom: 1rem; flex-wrap: wrap;' }, [ - E('h2', { 'style': 'margin: 0;' }, _('Mihomo')), - statusBadge, - serviceButton, - versionContainer, - latestVersionEl, - updateButton - ]); - - var self = this; - this.getMihomoVersion().then(function(version) { - self.currentVersion = version; - var versionEl = document.getElementById('mihomo-version'); - if (versionEl) versionEl.textContent = _('%s').format(version.replace('v', '')); - var updateBtn = document.getElementById('mihomo-update-btn'); - if (updateBtn) { - updateBtn.disabled = false; - updateBtn.onclick = function() { self.checkForUpdates(true); }; - } - self.checkForUpdates(false); - }); - - var isDark = isLuciDarkMode(); - var cssVariables = isDark ? ` - :root { - --bg-tab: #2d2d2d; - --bg-tab-active: #1C1C1C; - --bg-toolbar: #1C1C1C; - --bg-input: #2d2d2d; - --text-main: #e0e0e0; - --text-dim: #969696; - --border-color: #444444; - --border-active: #444444; - --bg-output: #222222; - --bg-output-header: #333333; - --text-output: #f8f8f2; - } - ` : ` - :root { - --bg-tab: #e0e0e0; - --bg-tab-active: #ffffff; - --bg-toolbar: #f5f5f5; - --bg-input: #ffffff; - --text-main: #333333; - --text-dim: #666666; - --border-color: #E0E0E0; - --border-active: #E0E0E0; - --bg-output: #ffffff; - --bg-output-header: #eeeeee; - --text-output: #333333; - } - `; - - var style = E('style', {}, cssVariables + ` - .btn, .cbi-button { - min-height: 1.8rem !important; - display: inline-flex !important; - align-items: center; - justify-content: center; - vertical-align: middle; - box-sizing: border-box !important; /* Чтобы padding не раздувал кнопку */ - padding: 0 1rem !important; - line-height: 1 !important; - } - #output-text { - font-size: 0.8rem !important; - } - .cbi-page-actions { display: none !important; } - .custom-actions { display: flex; gap: 0.5rem; } - .tab-bar { display: flex; flex-wrap: nowrap; background-color: var(--bg-tab); } - .tab-item { display: flex; align-items: center; padding: 0.6em 1.2em; cursor: pointer; background-color: var(--bg-tab); color: var(--text-dim); margin-right: 1px; font-size: 0.9em; border-top: 1px solid transparent; white-space: nowrap; user-select: none; box-sizing: border-box } - .tab-item:hover { background-color: var(--bg-toolbar); color: var(--text-main); } - .tab-item.active { background-color: var(--bg-tab-active); color: var(--text-main); border: 1px solid var(--border-active); } - .tab-close { margin-left: 0.6em; border-radius: 3px; padding: 0 0.3em; color: #999; font-weight: bold; } - .tab-close:hover { background-color: #c0392b; color: white; } - .tab-new { font-weight: bold; font-size: 1.2em; padding: 0.5em 0.8em; } - .toolbar { background-color: var(--bg-toolbar); border: 1px solid var(--border-color); padding: 0.8rem; color: var(--text-main); } - .toolbar-row { display: flex; gap: 0.8rem; align-items: center; } - .toolbar textarea { width: 100%; height: 6em; background: var(--bg-input); color: var(--text-main); border: 1px solid var(--border-color); font-family: monospace; font-size: 0.9em; padding: 0.4em; } - .toolbar select { background: var(--bg-input); color: var(--text-main); border: 1px solid var(--border-color); padding: 0.4em; } - .toolbar-col { display: flex; flex-direction: column; } - .btn-save-custom { border-color: #5cb85c !important; color: #5cb85c !important; } - .btn-save-custom:hover { border-color: #5cb85c !important; } - .btn.cbi-button-action:hover { border-color: #5cb85c !important; } - .btn.cbi-button-reset:hover { border-color: #F62B12 !important; color: #F62B12 !important; } - .btn-generate { border-color: #5cb85c !important; color: #5cb85c !important; margin: auto 0; display: block; background: var(--bg-input); } - .btn-generate:hover { border-color: #5cb85c !important; } - .snippet-container { margin-top: 0; border: 1px solid var(--border-color); background: var(--bg-toolbar); padding: 0.8rem; display: none; } - .snippet-header { margin-bottom: 0.4rem; color: var(--text-main); font-size: 0.85em; } - .snippet-text { width: 100%; height: 9.5em; background: var(--bg-tab-active); color: var(--text-main); border: 1px solid var(--border-color); font-family: monospace; font-size: 0.9em; padding: 0.8em; resize: none; } - .output-box-close { background: transparent; border: none; color: var(--text-main); font-size: 1.5em; line-height: 1; cursor: pointer; margin-left: 1rem; padding: 0 0.4rem; } - .output-box-close:hover { color: #e74c3c !important; } - #ace_editor_container { width: 100%; height: 60vh; border: 1px solid var(--border-color); border-top: none; } - `); - - var tabBar = E('div', { 'id': 'mihomo-tab-bar', 'class': 'tab-bar' }); - var toolbarContainer = E('div', { 'id': 'mihomo-toolbar' }); - var editorContainer = E('div', { 'id': 'ace_editor_container' }); - - var snippetContainer = E('div', { 'id': 'snippet-box', 'class': 'snippet-container', 'style': 'margin-top: 0.8rem' }, [ - E('div', { 'class': 'snippet-header', 'style': 'opacity: 0.7' }, _('Чтобы Mihomo увидел файл, добавьте эту секцию в rule-providers:')), - E('textarea', { 'id': 'snippet-area', 'class': 'snippet-text', 'readonly': 'readonly', 'style': 'opacity: 0.8' }), - E('div', { 'style': 'margin-top: 0.8rem; display: flex; gap: 0.6rem;' }, [ - E('button', { 'class': 'btn cbi-button-apply', 'click': ui.createHandlerFn(this, 'handleAutoAddSnippet') }, _('Добавить автоматически')), - E('button', { 'class': 'btn cbi-button-neutral', 'click': ui.createHandlerFn(this, 'handleCopySnippet') }, _('Скопировать текст')) - ]) - ]); - - var buttonContainer = E('div', { 'id': 'bottom-buttons', 'class': 'custom-actions', 'style': 'margin-top: 1rem;' }, [ - E('button', { 'class': 'btn cbi-button-neutral', 'click': ui.createHandlerFn(this, 'handleCheck') }, _('Проверить конфигурацию')), - E('button', { 'class': 'btn cbi-button-positive btn-save-custom', 'click': ui.createHandlerFn(this, 'handleSaveAndApply', isRunning) }, _('Сохранить')), - E('button', { 'class': 'btn cbi-button-neutral', 'click': ui.createHandlerFn(this, 'handleOpenDashboard', mainConfigContent) }, _('Открыть панель управления')), - E('button', { 'class': 'btn cbi-button-neutral', 'click': ui.createHandlerFn(this, 'handleShowLogs') }, _('Показать журнал')) - ]); - - var middleActions = E('div', { 'id': 'middle-actions', 'style': 'display: none; margin-top: 0.8rem;' }, [ - E('button', { 'class': 'btn cbi-button-positive btn-save-custom', 'click': ui.createHandlerFn(this, 'handleSaveAndApply', isRunning) }, _('Сохранить')) - ]); - - var outputBox = E('div', { 'id': 'output-box', 'style': 'display: none; margin-top: 1.2rem; border: 1px solid var(--border-color); border-radius: 4px; overflow: hidden;' }, [ - E('div', { 'style': 'background: var(--bg-output-header); color: var(--text-output); padding: 0.6rem 0.8rem; border-bottom: 1px solid var(--border-color); display: flex; align-items: center;' }, [ - E('strong', { 'style': 'font-size: 0.9em' }, _('Вывод:')), - E('button', { 'class': 'output-box-close', 'click': function() { document.getElementById('output-box').style.display = 'none'; } }, '×') - ]), - E('pre', { 'id': 'output-text', 'style': 'margin: 0; padding: 1rem; background: var(--bg-output); color: var(--text-output); font-family: monospace; font-size: 1em; white-space: pre-wrap; word-wrap: break-word; max-height: 25rem; overflow-y: auto;' }, '') - ]); - - loadScript(ACE_DIR + 'ace.js').then(function() { - ace.config.set('basePath', ACE_DIR); - editor = ace.edit("ace_editor_container"); - var theme = isDark ? "ace/theme/merbivore_soft" : "ace/theme/tomorrow"; - editor.setTheme(theme); - editor.session.setMode("ace/mode/yaml"); - editor.setOptions({ - fontSize: "0.95em", - showPrintMargin: false, - wrap: true, - tabSize: 2, - useSoftTabs: true, - highlightActiveLine: false - }); - editor.setValue(mainConfigContent, -1); - setTimeout(function() { editor.resize(); }, 100); - }).catch(console.error); - - this.renderTabBar(tabBar); - this.renderToolbar(toolbarContainer, MAIN_CONFIG); - setTimeout(function() { this.updateVisibility(MAIN_CONFIG); }.bind(this), 100); - - return E('div', { 'class': 'cbi-map' }, [ - header, style, tabBar, toolbarContainer, editorContainer, - middleActions, snippetContainer, buttonContainer, outputBox - ]); - }, - - updateVisibility: function(filePath) { - var isMain = (filePath === MAIN_CONFIG); - document.getElementById('bottom-buttons').style.display = isMain ? 'flex' : 'none'; - document.getElementById('middle-actions').style.display = isMain ? 'none' : 'block'; - var snippetBox = document.getElementById('snippet-box'); - if (isMain) { - snippetBox.style.display = 'none'; - } else { - var baseName = filePath.split('/').pop().replace(/\.(yaml|txt)$/, ''); - var providerName = baseName + '-list:'; - if (mainConfigContent.includes(providerName)) { - snippetBox.style.display = 'none'; - } else { - document.getElementById('snippet-area').value = generateProviderSnippet(filePath); - snippetBox.style.display = 'block'; - } - } - }, - - handleAutoAddSnippet: function() { - var self = this; - var snippet = generateProviderSnippet(currentFile); - if (!snippet) return; - ui.showModal(null, [E('p', { 'class': 'spinning' }, _('Добавление...'))]); - var indentedSnippet = snippet.split('\n').map(function(line) { return ' ' + line; }).join('\n'); - fs.read(MAIN_CONFIG).then(function(content) { - var newContent = content || ''; - var sectionMatch = newContent.match(/^rule-providers:\s*$/m); - if (!sectionMatch) { - var proxiesMatch = newContent.match(/^proxies:\s*$/m); - var pgMatch = newContent.match(/^proxy-groups:\s*$/m); - var lastIdx = Math.max(proxiesMatch ? proxiesMatch.index + proxiesMatch[0].length : -1, pgMatch ? pgMatch.index + pgMatch[0].length : -1); - if (lastIdx > -1) { - var textAfter = newContent.substring(lastIdx); - var nextMatch = textAfter.match(/\n(?![ \t])[a-z][^:\n]*:/i); - if (nextMatch) { - var insIdx = lastIdx + nextMatch.index; - newContent = newContent.substring(0, insIdx) + '\nrule-providers:\n\n' + indentedSnippet + '\n' + newContent.substring(insIdx); - } else { - newContent = newContent.trimEnd() + '\n\nrule-providers:\n\n' + indentedSnippet + '\n'; - } - } else { - if (newContent && !newContent.endsWith('\n')) newContent += '\n'; - newContent += '\nrule-providers:\n\n' + indentedSnippet + '\n'; - } - } else { - var secEnd = sectionMatch.index + sectionMatch[0].length; - var textAfter = newContent.substring(secEnd); - var nextMatch = textAfter.match(/\n(?![ \t])[a-z][^:\n]*:/i); - if (nextMatch) { - var insIdx = secEnd + nextMatch.index; - newContent = newContent.substring(0, insIdx) + '\n' + indentedSnippet + '\n' + newContent.substring(insIdx); - } else { - newContent = newContent.substring(0, secEnd).replace(/\n+$/, '\n') + '\n' + indentedSnippet + '\n'; - } - } - mainConfigContent = newContent; - return fs.write(MAIN_CONFIG, newContent); - }).then(function() { - self.updateVisibility(currentFile); - ui.hideModal(); - }).catch(function(err) { - ui.hideModal(); - ui.addNotification(null, E('p', _('Ошибка: ') + err.message), 'error'); - }); - }, - - handleCopySnippet: function() { - var area = document.getElementById('snippet-area'); - if (area) { area.select(); document.execCommand('copy'); } - }, - - renderToolbar: function(container, filePath) { - L.dom.content(container, []); - if (filePath === MAIN_CONFIG) { container.style.display = 'none'; return; } - - container.style.display = 'block'; - container.className = 'toolbar'; - var self = this; - - if (filePath.endsWith('.txt')) { - var input = E('textarea', { 'placeholder': 'google.com\nyoutube.com' }); - var suffixCheck = E('input', { 'type': 'checkbox', 'id': 'suffixCheck', 'checked': true }); - - var row = E('div', { 'class': 'toolbar-row' }, [ - E('div', { 'style': 'flex-grow: 1;' }, input), - E('div', { 'class': 'toolbar-col', 'style': 'min-width: 10rem; display: flex; flex-direction: column; justify-content: space-between;' }, [ - E('label', { 'for': 'suffixCheck', 'style': 'align-self: flex-start; font-size: 0.85em;' }, [ suffixCheck, ' . (дубликаты с точкой)' ]), - E('button', { 'class': 'btn btn-generate', 'style': 'align-self: center;', 'click': function() { self.handleAppendList(input.value, suffixCheck.checked); input.value = ''; } }, _('Добавить')) - ]) - ]); - container.appendChild(row); - } else { - var input = E('textarea', { 'placeholder': 'google.com\n104.28.0.0/16\n*.example.com' }); - var typeSelect = E('select', { 'style': 'font-size: 0.9em' }, [ - E('option', { 'value': 'Auto' }, 'Auto'), - E('option', { 'value': 'DOMAIN-SUFFIX' }, 'DOMAIN-SUFFIX'), - E('option', { 'value': 'DOMAIN' }, 'DOMAIN'), - E('option', { 'value': 'DOMAIN-KEYWORD' }, 'DOMAIN-KEYWORD'), - E('option', { 'value': 'DOMAIN-WILDCARD' }, 'DOMAIN-WILDCARD'), - E('option', { 'value': 'IP-CIDR' }, 'IP-CIDR'), - E('option', { 'value': 'IP-CIDR6' }, 'IP-CIDR6') - ]); - var row = E('div', { 'class': 'toolbar-row' }, [ - E('div', { 'style': 'flex-grow: 1;' }, input), - E('div', { 'class': 'toolbar-col' }, [ typeSelect ]), - E('div', { 'class': 'toolbar-col', 'style': 'min-width: 8rem; justify-content: flex-end;' }, [ - E('button', { 'class': 'btn btn-generate', 'click': function() { self.handleGenerateRules(input.value, typeSelect.value); input.value = ''; } }, _('Создать')) - ]) - ]); - container.appendChild(row); - } - }, - - handleAppendList: function(text, addSuffix) { - if (!editor || !text.trim()) return; - var lines = text.trim().split('\n'); - var result = []; - lines.forEach(function(line) { - line = line.trim(); - if (!line) return; - result.push(line); - if (addSuffix && !line.startsWith('.')) result.push('.' + line); - }); - if (result.length > 0) { - editor.navigateFileEnd(); - var doc = editor.getValue(); - var prefix = (doc.length > 0 && !doc.endsWith('\n')) ? '\n' : ''; - editor.insert(prefix + result.join('\n') + '\n'); - editor.focus(); - } - }, - - handleGenerateRules: function(text, type) { - if (!editor || !text.trim()) return; - var lines = text.trim().split('\n'); - var newRules = []; - lines.forEach(function(line) { - line = line.trim(); - if (!line) return; - var currentType = type === 'Auto' ? detectRuleType(line) : type; - if (currentType === 'IP-CIDR' && !line.includes('/')) line += '/32'; - newRules.push(` - ${currentType},${line}`); - }); - if (newRules.length === 0) return; - var content = editor.getValue(); - var linesContent = content.split('\n'); - var payloadIndex = linesContent.findIndex(function(l) { return l.trim() === 'payload:'; }); - if (payloadIndex !== -1) { - editor.gotoLine(linesContent.length + 1, 0); - editor.insert(newRules.join('\n') + '\n'); - } else { - var prefix = (content.length > 0 && !content.endsWith('\n')) ? '\n\n' : ''; - editor.navigateFileEnd(); - editor.insert(prefix + 'payload:\n' + newRules.join('\n') + '\n'); - } - editor.focus(); - }, - - renderTabBar: function(container) { - L.dom.content(container, []); - var self = this; - var mainTab = E('div', { 'class': (currentFile === MAIN_CONFIG) ? 'tab-item active' : 'tab-item', 'click': ui.createHandlerFn(this, 'handleTabClick', MAIN_CONFIG) }, E('span', {}, 'Конфигурация')); - container.appendChild(mainTab); - - cachedRuleFiles.forEach(function(file) { - if (file.type === 'file') { - var fullPath = RULE_DIR + file.name; - if (!validatePath(fullPath, RULE_DIR)) return; - var isActive = (currentFile === fullPath); - var safeName = escapeHtml(sanitizeTabName(file.name)); - var tabContent = [E('span', {}, safeName)]; - if (isActive) { - tabContent.push(E('span', { 'class': 'tab-close', 'title': _('Удалить'), 'click': ui.createHandlerFn(self, 'handleDeleteFile', fullPath) }, '×')); - } - var tab = E('div', { 'class': isActive ? 'tab-item active' : 'tab-item', 'click': ui.createHandlerFn(self, 'handleTabClick', fullPath) }, tabContent); - container.appendChild(tab); - } - }); - var newTab = E('div', { 'class': 'tab-item tab-new', 'title': _('Создать новый'), 'click': ui.createHandlerFn(this, 'handleCreateFile') }, '+'); - container.appendChild(newTab); - }, - - handleTabClick: function(path, ev) { - if (!validatePath(path, '/etc/mihomo/')) { ui.addNotification(null, E('p', _('Недопустимый путь')), 'error'); return; } - if (ev && ev.target.classList.contains('tab-close')) { ev.stopPropagation(); return; } - if (path === currentFile) return; - - var self = this; - ui.showModal(null, [E('p', { 'class': 'spinning' }, _('Загрузка...'))]); - fs.read(path).then(function(content) { - currentFile = path; - if (editor) { - editor.setValue(content || '', -1); - editor.session.setMode(path.endsWith('.txt') ? "ace/mode/text" : "ace/mode/yaml"); - } - self.renderTabBar(document.getElementById('mihomo-tab-bar')); - self.renderToolbar(document.getElementById('mihomo-toolbar'), path); - self.updateVisibility(path); - ui.hideModal(); - }).catch(function(err) { - if (err && err.message === 'Данные не получены') { - currentFile = path; - if (editor) { editor.setValue('', -1); editor.session.setMode(path.endsWith('.txt') ? "ace/mode/text" : "ace/mode/yaml"); } - self.renderTabBar(document.getElementById('mihomo-tab-bar')); - self.renderToolbar(document.getElementById('mihomo-toolbar'), path); - self.updateVisibility(path); - } else { - ui.addNotification(null, E('p', _('Ошибка: ') + (err.message || 'Error')), 'error'); - } - ui.hideModal(); - }); - }, - - handleCreateFile: function() { - var self = this; - var nameInput = E('input', { 'type': 'text', 'style': 'width: 100%;', 'placeholder': 'my-rules' }); - var typeSelect = E('select', { 'style': 'width: 100%;' }, [ - E('option', { 'value': '.yaml' }, 'Набор правил (.yaml)'), - E('option', { 'value': '.txt' }, 'Простой список (.txt)') - ]); - var footer = E('div', { 'class': 'right', 'style': 'margin-top: 1.5rem;' }, [ - E('button', { 'class': 'btn', 'click': ui.hideModal }, _('Отмена')), ' ', - E('button', { 'class': 'btn cbi-button-positive btn-save-custom', 'click': function() { - var filename = nameInput.value.trim(); - if (!filename || !validateFilename(filename)) { ui.addNotification(null, E('p', _('Некорректное имя')), 'error'); return; } - var fullPath = RULE_DIR + filename + typeSelect.value; - if (!validatePath(fullPath, RULE_DIR)) { ui.addNotification(null, E('p', _('Недопустимый путь')), 'error'); return; } - ui.showModal(null, [E('p', { 'class': 'spinning' }, _('Создание...'))]); - fs.stat(fullPath).then(function() { - ui.hideModal(); ui.addNotification(null, E('p', _('Файл уже существует')), 'error'); - }).catch(function() { - fs.write(fullPath, '').then(function() { return fs.list(RULE_DIR); }).then(function(files) { - cachedRuleFiles = (files || []).sort(function(a, b) { return a.name.localeCompare(b.name); }); - self.handleTabClick(fullPath); - }).catch(function(err) { ui.hideModal(); ui.addNotification(null, E('p', _('Ошибка: ') + err.message), 'error'); }); - }); - }}, _('Создать')) - ]); - ui.showModal(_('Новый файл правил'), [ - E('div', {}, [ - E('div', { 'style': 'display: flex; align-items: center; margin-bottom: 0.8rem;' }, [ E('label', { 'style': 'min-width: 10rem; margin-right: 0.8rem;' }, _('Имя файла:')), nameInput ]), - E('div', { 'style': 'display: flex; align-items: center;' }, [ E('label', { 'style': 'min-width: 10rem; margin-right: 0.8rem;' }, _('Тип файла:')), typeSelect ]) - ]), footer - ]); - nameInput.focus(); - }, - - handleDeleteFile: function(path) { - if (!isSafeRulePath(path)) { ui.addNotification(null, E('p', _('Ошибка пути')), 'error'); return; } - if (!confirm(_('Удалить %s?').format(path.split('/').pop()))) return; - var self = this; - ui.showModal(null, [E('p', { 'class': 'spinning' }, _('Удаление...'))]); - fs.remove(path).then(function() { return fs.list(RULE_DIR); }).then(function(files) { - cachedRuleFiles = (files || []).sort(function(a, b) { return a.name.localeCompare(b.name); }); - if (currentFile === path) self.handleTabClick(MAIN_CONFIG); - else { self.renderTabBar(document.getElementById('mihomo-tab-bar')); ui.hideModal(); } - }).catch(function(err) { ui.hideModal(); ui.addNotification(null, E('p', _('Ошибка: ') + err.message), 'error'); }); - }, - - handleSaveAndApply: function(wasRunning) { - if (this.isProcessing) return Promise.reject(new Error('Busy')); - if (!editor) return; - this.isProcessing = true; - var self = this; - var content = editor.getValue(); - if (currentFile === MAIN_CONFIG) mainConfigContent = content; - ui.showModal(null, [E('p', { 'class': 'spinning' }, _('Сохранение...'))]); - fs.write(currentFile, content).then(function() { - if (currentFile === MAIN_CONFIG) { - return fs.exec('/usr/bin/mihomo', ['-d', '/etc/mihomo', '-t', MAIN_CONFIG]).then(function(res) { - if (res.code !== 0) throw new Error((res.stdout || '') + (res.stderr || '')); - if (wasRunning) return fs.exec('/etc/init.d/mihomo', ['restart']); - }); - } - }).then(function() { - ui.hideModal(); - if (currentFile === MAIN_CONFIG) setTimeout(function() { window.location.reload(); }, RELOAD_DELAY); - }).catch(function(err) { self.showOutput(err.message, true); ui.hideModal(); }).finally(function() { self.isProcessing = false; }); - }, - - handleCheck: function() { - if (currentFile !== MAIN_CONFIG || !editor) return; - var self = this; - ui.showModal(null, [E('p', { 'class': 'spinning' }, _('Проверка...'))]); - fs.write(MAIN_CONFIG, editor.getValue()) - .then(function() { return fs.exec('/usr/bin/mihomo', ['-d', '/etc/mihomo', '-t']); }) - .then(function(res) { self.showOutput((res.stdout || '') + (res.stderr || ''), res.code !== 0); ui.hideModal(); }) - .catch(function(e) { self.showOutput(e.message, true); ui.hideModal(); }); - }, - - showOutput: function(text, isError) { - var box = document.getElementById('output-box'); - var out = document.getElementById('output-text'); - if (box && out) { - out.textContent = text ? text.trim() : '(Пусто)'; - out.style.color = isError ? '#f92672' : 'var(--text-output)'; - box.style.display = 'block'; - box.scrollIntoView({ behavior: 'smooth', block: 'end' }); - } - }, - - handleServiceAction: function(act) { - if (!VALID_ACTIONS.includes(act)) return; - var self = this; - ui.showModal(null, [E('p', { 'class': 'spinning' }, _('Выполнение...'))]); - fs.exec('/etc/init.d/mihomo', [act]).then(function() { window.location.reload(); }) - .catch(function(e) { ui.hideModal(); ui.addNotification(null, E('p', e.message), 'error'); }); - }, - - handleShowLogs: function() { - var self = this; - fs.exec('/sbin/logread', ['-e', 'mihomo']).then(function(res) { - var logContent = res.stdout; - if (!logContent && res.code !== 0) { - logContent = "Записей о 'mihomo' в системном журнале не найдено.\nВозможно, служба не запущена."; - } else if (!logContent) { - logContent = "Журнал пуст."; - } - - self.showOutput(logContent, false); - }).catch(function(err) { - self.showOutput("Ошибка чтения журнала: " + err.message, true); - }); - }, - - handleOpenDashboard: function(content) { - var hostname = window.location.hostname; - var port = '9090'; - try { - var match = content.match(/external-controller:\s*([0-9\.]+):(\d+)/); - if (match && match[1] && match[2]) { - var extractedIp = match[1].trim(); - if (/^(\d{1,3}\.){3}\d{1,3}$/.test(extractedIp) && extractedIp !== '0.0.0.0') hostname = extractedIp; - var portNum = parseInt(match[2].trim(), 10); - if (!isNaN(portNum) && portNum >= 1 && portNum <= 65535) port = match[2].trim(); - } - } catch (e) {} - window.open(`http://${hostname}:${port}/ui/`, '_blank'); - } -}); -EOF -} - -install_hev_tunnel() { - log_online "Установка hev-socks5-tunnel" - - if [ "$USE_APK" -eq 1 ]; then - apk cache clean - apk add hev-socks5-tunnel >/dev/null 2>&1 - else - manage_pkg install hev-socks5-tunnel >/dev/null 2>&1 - fi - - rm -f /etc/hev-socks5-tunnel/main.yml - mkdir -p /etc/hev-socks5-tunnel - cat > /etc/hev-socks5-tunnel/main.yml <<'EOF' -tunnel: - name: Mihomo - mtu: 8500 - multi-queue: false - ipv4: 198.18.0.1 -socks5: - port: 7890 - address: 127.0.0.1 - udp: 'udp' -EOF - chmod 600 /etc/hev-socks5-tunnel/main.yml - - echo "Очистка старых настроек UCI" - uci delete network.Mihomo 2>/dev/null || true - - local fw_section - for fw_section in $(uci show firewall 2>/dev/null \ - | grep -E "\.name='Mihomo'" \ - | sed "s/\.name.*//"); do - uci delete "$fw_section" 2>/dev/null || true - done - - for fw_section in $(uci show firewall 2>/dev/null \ - | grep -E "\.(src|dest)='Mihomo'" \ - | sed -E "s/\.(src|dest).*//"); do - uci delete "$fw_section" 2>/dev/null || true - done - - uci delete firewall.Mihomo 2>/dev/null || true - uci delete firewall.lan_to_Mihomo 2>/dev/null || true - uci commit firewall - /etc/init.d/firewall restart 2>/dev/null || true - sleep 1 - - echo "Настройка UCI-сервиса hev-socks5-tunnel" - uci set hev-socks5-tunnel.config.enabled='1' - uci set hev-socks5-tunnel.config.configfile='/etc/hev-socks5-tunnel/main.yml' - uci commit hev-socks5-tunnel - /etc/init.d/hev-socks5-tunnel restart - sleep 2 - - echo "Настройка сетевого интерфейса" - uci set network.Mihomo=interface - uci set network.Mihomo.proto='none' - uci set network.Mihomo.device='Mihomo' - uci commit network - /etc/init.d/network reload - - echo "Настройка firewall" - local FW_ZONE - FW_ZONE=$(uci add firewall zone) - uci set "firewall.${FW_ZONE}.name=Mihomo" - uci set "firewall.${FW_ZONE}.input=REJECT" - uci set "firewall.${FW_ZONE}.output=REJECT" - uci set "firewall.${FW_ZONE}.forward=REJECT" - uci set "firewall.${FW_ZONE}.masq=1" - uci set "firewall.${FW_ZONE}.mtu_fix=1" - uci add_list "firewall.${FW_ZONE}.network=Mihomo" - - local FW_FWD - FW_FWD=$(uci add firewall forwarding) - uci set "firewall.${FW_FWD}.src=lan" - uci set "firewall.${FW_FWD}.dest=Mihomo" - - uci commit firewall - /etc/init.d/firewall restart -} - -install_magitrickle() { - -################################################################################################### - -PAUSE() { echo -ne "Нажмите Enter..."; read dummy; } -INSTALL="opkg install"; RAZ="ipk"; SUF="" -command -v apk >/dev/null 2>&1 && INSTALL="apk add --allow-untrusted" && RAZ="apk" && SUF="r" -ARCH_MT=$(grep "^OPENWRT_ARCH=" /etc/os-release | cut -d'"' -f2) -MT_VERSION="$(curl -Ls -o /dev/null -w '%{url_effective}' https://github.com/MagiTrickle/MagiTrickle/releases/latest | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)" -# MT_VERSION="0.7.0" -URL="https://github.com/MagiTrickle/MagiTrickle/releases/download/${MT_VERSION}/magitrickle_${MT_VERSION}-${SUF}1_openwrt_${ARCH_MT}.$RAZ" -FILE_MT="/tmp/$(basename "$URL")"; echo -e "Скачиваем и устанавливаем:\n${CYAN}$URL${NC}" -curl -Lf --retry 3 --retry-delay 2 -o "$FILE_MT" "$URL" >/dev/null 2>&1 || { echo -e "\n${RED}Ошибка скачивания${NC}\n"; PAUSE; exit 1; } -$INSTALL "$FILE_MT" >/dev/null 2>&1 || { echo -e "\n${RED}Ошибка установки${NC}\n"; PAUSE; rm -f "$FILE_MT"; exit 1; } -rm -f "$FILE_MT" - -################################################################################################### - - echo "Создание страницы MagiTrickle в LuCI" - mkdir -p /www/luci-static/resources/view/magitrickle - - cat > /www/luci-static/resources/view/magitrickle/magitrickle.js <<'EOF' -'use strict'; -'require view'; - -return view.extend({ - handleSave: null, - handleSaveApply: null, - handleReset: null, - - render: function() { - var hostname = window.location.hostname; - var port = '8080'; - var url = 'http://' + hostname + ':' + port; - - if (window.location.protocol === 'https:') { - return E('div', { style: 'padding: 20px; text-align: center;' }, [ - E('p', _('HTTPS соединение блокирует встроенный интерфейс MagiTrickle через LuCI.')), - E('p', { style: 'margin-bottom: 20px;' }, _('Пожалуйста, откройте MagiTrickle в новой вкладке для полноценного управления.')), - E('a', { - 'class': 'btn cbi-button-action', - 'href': url, - 'target': '_blank', - 'style': 'padding: 10px 20px; font-size: 1.1em;' - }, _('Открыть MagiTrickle')) - ]); - } - - return E('div', { - style: 'width:100%; height:92vh; margin: -20px -20px 0 -20px; overflow: hidden;' - }, E('iframe', { - src: url, - style: 'width:100%; height:100%; border: none;' - })); - } -}); -EOF - - cat > /usr/share/luci/menu.d/luci-app-magitrickle.json <<'EOF' -{ - "admin/services/magitrickle": { - "title": "MagiTrickle", - "order": 60, - "action": { - "type": "view", - "path": "magitrickle/magitrickle" - } - } -} -EOF - - rm -rf /tmp/luci-indexcache /tmp/luci-modulecache/ -} - -finalize_install() { - echo "Выставление прав доступа" - chmod -R 755 /www/luci-static/resources/view/mihomo 2>/dev/null || true - find /www/luci-static/resources/view/mihomo -type f -exec chmod 644 {} \; 2>/dev/null || true - chmod 644 /www/luci-static/resources/view/magitrickle/magitrickle.js 2>/dev/null || true - - echo "Очистка кэша LuCI и перезапуск сервисов" - rm -rf /tmp/luci-indexcache /tmp/luci-modulecache/ - /etc/init.d/rpcd restart > /dev/null 2>&1 - /etc/init.d/uhttpd restart > /dev/null 2>&1 - /etc/init.d/mihomo restart > /dev/null 2>&1 -} - -main() { - clear - log_done "=== Mixomo OpenWrt от Internet Helper (StressOzz Remix) ===" - echo "" - - log_done "[1/5] Установка зависимостей" - install_deps || step_fail - echo "" - - log_done "[2/5] Установка Mihomo" - install_mihomo || step_fail - echo "" - - log_done "[3/5] Установка Hev-Socks5-Tunnel" - install_hev_tunnel || step_fail - echo "" - - log_done "[4/5] Установка MagiTrickle" - install_magitrickle || step_fail -################################################################################################### -CONFIG_PATH="/etc/magitrickle/state/config.yaml" -confGIT="https://raw.githubusercontent.com/StressOzz/Zapret-Manager/refs/heads/mixomo/files/MagiTrickle/configAD.yaml" - -echo "Установка списка для MagiTrickle" - -for i in 1 2 3; do - curl -fL --connect-timeout 3 --max-time 7 -o "$CONFIG_PATH" "$confGIT" >/dev/null 2>&1 && break - [ "$i" -eq 3 ] && { - echo -e "\n${RED}Не удалось скачать список!${NC}\n" - } - sleep 1 -done - - /etc/init.d/magitrickle enable >/dev/null 2>&1 - /etc/init.d/magitrickle reload >/dev/null 2>&1 - /etc/init.d/magitrickle start >/dev/null 2>&1 - /etc/init.d/magitrickle restart >/dev/null 2>&1 -################################################################################################### - echo "" - - log_done "[5/5] Завершение" - finalize_install || step_fail - -################################################################################################### - -TMP1="/tmp/zashboard.zip" -TMP2="/tmp/zashboard" -DIR1="/etc/mihomo/ui" -URL1="https://github.com/Zephyruso/zashboard/releases/latest/download/dist-cdn-fonts.zip" - -echo "Устанавка панели Zashboard для Mihomo" - -for i in 1 2 3; do - curl -fL --connect-timeout 3 --max-time 7 -o "$TMP1" "$URL1" >/dev/null 2>&1 && break - [ "$i" -eq 3 ] && { - echo -e "\n${RED}Ошибка скачивания WEB UI!${NC}\n" - rm -f "$TMP1" - } - sleep 1 -done - -rm -rf "$TMP2" -mkdir -p "$TMP2" - -unzip -oq "$TMP1" -d "$TMP2" || { - echo -e "\n${RED}Ошибка распаковки WEB UI!${NC}\n" - rm -rf "$TMP1" "$TMP2" -} - -rm -rf "$DIR1" -mkdir -p "$DIR1" - -cp -r /tmp/zashboard/dist/* "$DIR1"/ 2>/dev/null || \ -cp -r /tmp/zashboard/* "$DIR1"/ 2>/dev/null || { - echo -e "\n${RED}Не удалось установить WEB UI!${NC}\n" - rm -rf "$TMP1" "$TMP2" -} - -rm -rf "$TMP1" "$TMP2" - -/etc/init.d/mihomo restart >/dev/null 2>&1 -/etc/init.d/hev-socks5-tunnel restart >/dev/null 2>&1 - -################################################################################################### - - - echo "" - log_done "Установка Mixomo OpenWrt прошла успешно!" - echo "" -} - -main