#!/bin/bash
# xmodz-check — автопроверка MTProto-прокси на MTProxyL. Только читает, ничего не меняет.
# Инструкция: https://xomodz.fit · проверено на MTProxyL 1.6.31 и telemt 3.5.13
ok(){ echo -e " \e[32m✅ $1\e[0m"; }
bad(){ echo -e " \e[31m❌ $1\e[0m"; }
warn(){ echo -e " \e[33m⚠️ $1\e[0m"; }
sec(){ echo -e "\n\e[36m── $1 ──\e[0m"; }
val(){ sed -n "s/^$1='\(.*\)'$/\1/p" "$2" 2>/dev/null | tail -1; }
S=/opt/mtproxyl/settings.conf
N=/opt/mtproxyl/nft-rules.conf
DOMAIN=$(val SELFMASK_DOMAIN $S); [ -z "$DOMAIN" ] && DOMAIN=$(val CUSTOM_IP $S)
WEBD=$(val WEB_DOMAIN $S)
WEBON=$(val WEB_ENABLED $S)
ADTAG=$(val AD_TAG $S)
sec "A. ЧТО ЗА НОДА"
if [ ! -f "$S" ]; then bad "MTProxyL не установлен ($S нет)"; fi
IP=$(curl -4 -s -m8 ifconfig.me 2>/dev/null | grep -oxE '[0-9]{1,3}(\.[0-9]{1,3}){3}')
[ -n "$IP" ] && ok "внешний IPv4: $IP" || bad "внешний IPv4 не определился"
for d in $DOMAIN $WEBD; do
R=$(getent ahostsv4 "$d" 2>/dev/null | awk '{print $1; exit}')
if [ -n "$IP" ] && [ "$R" = "$IP" ]; then ok "$d → $R — этот сервер"
else bad "$d → ${R:-не резолвится}, а сервер $IP — A-запись не сюда"; fi
done
[ -z "$DOMAIN" ] && bad "домен не прочитался из настроек"
sec "B. ДВИЖОК"
ACT=no; systemctl is-active --quiet mtproxyl-telemt 2>/dev/null && ACT=yes
[ "$ACT" = yes ] && ok "mtproxyl-telemt работает" || bad "mtproxyl-telemt не работает: systemctl status mtproxyl-telemt"
V=$(mtproxyl status 2>/dev/null | sed 's/\x1b\[[0-9;]*m//g' | grep -o 'telemt v[0-9.]*' | head -1)
[ -n "$V" ] && ok "$V" || warn "версия движка не прочиталась"
T0=$(systemctl show mtproxyl-telemt -p ActiveEnterTimestamp --value 2>/dev/null)
TS=$(date -d "$T0" +%s 2>/dev/null || true)
if [ "$ACT" = yes ] && [ -n "$T0" ] && [ -n "$TS" ]; then UP=$(( $(date +%s) - TS )); ok "работает без перезапуска $((UP/60)) мин"; else UP=0; fi
ss -tln 2>/dev/null | grep -qE '[:.]443 ' && ok "443 слушается" || bad "443 не слушается"
SMD=$(val SELFMASK_DOMAIN $S)
if [ -n "$SMD" ]; then
if ss -tln 2>/dev/null | grep -E '[:.]8444 ' | grep -q '127.0.0.1'; then ok "заглушка 8444 только на localhost"
elif ss -tln 2>/dev/null | grep -qE '[:.]8444 '; then bad "8444 торчит наружу"
else bad "заглушка на 8444 не слушается"; fi
systemctl is-active --quiet mtproxyl-pq-nginx 2>/dev/null && ok "PQ nginx работает" || bad "PQ nginx не работает"
fi
sec "C. СПОНСОРСКИЙ КАНАЛ"
if [ -z "$ADTAG" ]; then warn "AD_TAG не задан — спонсорского канала нет. Не нужен — блок можно не смотреть"; else ok "AD_TAG задан"; fi
NR=$(journalctl -u mtproxyl-telemt --since '10 min ago' --no-pager 2>/dev/null | grep -c 'is not ready yet')
if [ "$ACT" != yes ]; then bad "движок не работает — пул ME не проверить"
elif [ "$UP" -lt 300 ]; then warn "движок запущен меньше 5 мин назад — пул ME ещё собирается, повтори позже"
elif [ "$NR" -eq 0 ]; then ok "пул middle proxy собран (not ready = 0 за 10 мин)"
else bad "not ready = $NR за 10 мин — пул ME не собран, спонсорки нет"; fi
sec "D. МАСКИРОВКА И СЕРТИФИКАТ"
if [ -z "$SMD" ]; then warn "установка без своего домена — блок пропущен"; else
C=$(curl -4 -s -m10 -o /dev/null -w '%{http_code}' "https://$DOMAIN/" 2>/dev/null)
[ "$C" = "200" ] && ok "https://$DOMAIN → 200, сайт открывается" || bad "https://$DOMAIN → ${C:-нет ответа}"
TT=$(curl -4 -s -m10 "https://$DOMAIN/" 2>/dev/null | grep -o '
[^<]*' | head -1)
[ -n "$TT" ] && ok "заголовок сайта: $(echo "$TT" | sed 's/<[^>]*>//g')" || warn "у сайта нет "
CERT=/etc/letsencrypt/live/$DOMAIN/cert.pem
if [ -f "$CERT" ]; then
E=$(date -d "$(openssl x509 -in "$CERT" -noout -enddate | cut -d= -f2)" +%s)
D=$(( (E - $(date +%s)) / 86400 ))
[ "$D" -gt 20 ] && ok "сертификат ещё $D дн." || bad "сертификат истекает через $D дн. — продление не работает"
if [ -n "$WEBD" ]; then
openssl x509 -in "$CERT" -noout -ext subjectAltName 2>/dev/null | grep -q "DNS:$WEBD" && ok "в сертификате есть $WEBD" || bad "в сертификате нет $WEBD"
fi
else bad "сертификат не найден: $CERT"; fi
if [ "$WEBON" = "true" ]; then
W=$(mtproxyl web status 2>/dev/null | sed 's/\x1b\[[0-9;]*m//g')
echo "$W" | grep -q 'включён' && ok "WEB включён" || bad "WEB выключен"
echo "$W" | grep -q "public_addr *$IP:443" && ok "WEB public_addr = $IP:443" || bad "WEB public_addr не совпадает с $IP — mtproxyl web enable"
echo "$W" | grep -q 'пустой ответ' && ok "WEB-домен отдаёт пустой ответ — так по умолчанию"
fi
fi
sec "E. ЛИМИТЕР"
if nft list table inet mtproxyl_limit >/dev/null 2>&1; then
L=$(nft list table inet mtproxyl_limit)
R=$(echo "$L" | grep -oE 'limit rate [0-9]+/[a-z]+ burst [0-9]+' | head -1)
case "$R" in
*"15/second burst 30") ok "мягкий лимит: $R" ;;
*"burst 1") bad "жёсткий лимит: $R — медленный коннект. раздел «Донастройка» инструкции" ;;
*) warn "лимит: ${R:-не прочитался}" ;;
esac
NIP=$(val NFT_SERVER_IP $N)
if [ -z "$IP" ]; then warn "внешний IP неизвестен — привязку лимитера не проверить"
elif [ -z "$NIP" ] || [ "$NIP" = "$IP" ]; then ok "лимитер привязан к ${NIP:-любому адресу}"
else bad "лимитер привязан к старому IP $NIP — после смены IP: xmodz-newip"; fi
A=$(echo "$L" | grep smart_other_accept | grep -oE 'packets [0-9]+' | grep -oE '[0-9]+')
J=$(echo "$L" | grep smart_other_reject | grep -oE 'packets [0-9]+' | grep -oE '[0-9]+')
A=${A:-0}; J=${J:-0}; TOT=$((A+J))
if [ "$TOT" -gt 0 ]; then P=$((J*100/TOT))
[ "$P" -lt 10 ] && ok "отклонено $J из $TOT ($P%)" || warn "отклонено $J из $TOT ($P%) — кого-то режет"
else ok "подключений ещё не было"; fi
systemctl is-enabled --quiet mtproxyl-syn-limit 2>/dev/null && ok "служба лимитера включена — переживёт перезагрузку" || warn "служба лимитера выключена"
else warn "лимитер не стоит"; fi
sec "F. СИСТЕМА"
[ "$(sysctl -n net.ipv4.tcp_congestion_control)" = "bbr" ] && ok "BBR включён" || warn "BBR выключен"
NF=$(systemctl show mtproxyl-telemt -p LimitNOFILE --value 2>/dev/null)
[ "${NF:-0}" -ge 65535 ] 2>/dev/null && ok "LimitNOFILE $NF" || warn "LimitNOFILE ${NF:-не прочитался}"
CC=$(sysctl -n net.netfilter.nf_conntrack_count 2>/dev/null); CM=$(sysctl -n net.netfilter.nf_conntrack_max 2>/dev/null)
if [ -n "$CM" ]; then [ $((CC*100/CM)) -lt 70 ] && ok "conntrack $CC из $CM" || bad "conntrack $CC из $CM — таблица почти полна"; fi
RA=$(free -m | awk '/^Mem:/{print $7}')
[ "$RA" -gt 300 ] && ok "свободно памяти $RA МБ" || warn "свободно памяти $RA МБ"
RT=$(free -m | awk '/^Mem:/{print $2}'); SW=$(free -m | awk '/^Swap:/{print $2}')
if [ "${SW:-0}" -gt 0 ]; then ok "подкачка $SW МБ"
elif [ "${RT:-0}" -le 1100 ]; then warn "подкачки нет при $RT МБ памяти — при нехватке памяти прокси может упасть"
else ok "подкачки нет — при $RT МБ памяти не обязательна"; fi
if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q '^Status: active'; then
ufw status 2>/dev/null | grep -qE '^(22|OpenSSH)[/ ]' && ok "UFW включён, SSH разрешён" || bad "UFW включён, а правила для SSH (22) нет — потеряешь вход"
fi
DF=$(df -P / | awk 'NR==2{print $5}' | tr -d '%')
[ "$DF" -lt 80 ] && ok "диск занят $DF%" || warn "диск занят $DF%"
LA=$(cut -d' ' -f1 /proc/loadavg)
ok "нагрузка CPU (load 1 мин): $LA"
CN=$(mtproxyl status 2>/dev/null | sed 's/\x1b\[[0-9;]*m//g' | grep -o 'Соединения: *[0-9]* *Уник. IP: *[0-9]*' | tr -s ' ')
[ -n "$CN" ] && ok "$CN"
sec "G. МОНИТОРИНГ"
if [ -f /etc/systemd/system/mtproxyl-tgbot.service ] || systemctl cat mtproxyl-tgbot >/dev/null 2>&1; then
systemctl is-active --quiet mtproxyl-tgbot && ok "бот работает" || bad "бот установлен, но остановлен: systemctl status mtproxyl-tgbot"
else warn "бот не установлен — если не нужен, всё в порядке"; fi
systemctl is-active --quiet mtproxyl-availability.timer 2>/dev/null && ok "проверка доступности из России по расписанию" || warn "проверка доступности выключена: mtproxyl availability on 15"
[ "$(val DC_RESTART_ENABLED $S)" = "true" ] && ok "автоперезапуск при падении DC включён" || warn "автоперезапуск при падении DC выключен: mtproxyl dc autorestart on"
sec "H. НА МЕСЯЦЫ ВПЕРЁД"
if [ -n "$SMD" ]; then
systemctl is-active --quiet certbot.timer 2>/dev/null && ok "certbot.timer продлевает сертификат" || bad "certbot.timer не работает"
[ -f /etc/letsencrypt/renewal-hooks/deploy/mtproxyl-selfmask.sh ] && ok "после продления службы перезапустятся сами" || warn "нет хука продления"
fi
apt-config dump 2>/dev/null | grep -q 'Unattended-Upgrade "1"' && ok "обновления безопасности ставятся сами" || warn "автообновления выключены — раздел «На месяцы вперёд» инструкции"
[ -f /etc/systemd/journald.conf.d/99-xmodz.conf ] && ok "журнал ограничен: $(journalctl --disk-usage 2>/dev/null | grep -oE '[0-9.]+[KMG]' | head -1)" || warn "журнал без лимита — раздел «На месяцы вперёд» инструкции"
if command -v vnstat >/dev/null 2>&1; then
I=$(ip route show default | awk '{print $5; exit}')
M=$(vnstat -i "$I" --oneline 2>/dev/null | cut -d';' -f10)
echo "$M" | grep -qE '^ *[0-9.]+ [KMGT]i?B' && ok "отдано за месяц: $M" || ok "отдано за месяц: ещё считается (vnstat копит данные первые минуты)"
else warn "vnstat не стоит — трафик не считается, раздел «На месяцы вперёд» инструкции"; fi
B=$(ls /opt/mtproxyl/backups/*.tar.gz 2>/dev/null | wc -l)
[ "$B" -gt 0 ] && ok "бэкапов на сервере: $B" || warn "бэкапов нет: mtproxyl backup"
U=$(mtproxyl update-check 2>/dev/null)
if echo "$U" | grep -q '"update_available":true'; then warn "вышел MTProxyL $(echo "$U" | grep -oE '"latest":"[^"]*"' | cut -d'"' -f4) — раздел «Обновления» инструкции"
elif echo "$U" | grep -q '"update_available":false'; then ok "MTProxyL актуален ($(echo "$U" | grep -oE '"current":"[^"]*"' | cut -d'"' -f4))"
else warn "версию MTProxyL не проверить"; fi
echo