deee59ddba
- templates/index.html: вкладка SSL — замена самоподписного сертификата на SSH-туннель, авто-ID при установке бота - app.py: API /api/panel/access (get/set ip/ssh), /api/bot/detect-id, правка парсинга трафика - bot.py: позиционный парсинг mita get users (8 колонок) - install.sh, mita-ctl.sh: автоопределение Telegram ID через getUpdates API, исправлен unbound variable BOLD, SCRIPT_DIR - install-panel.sh: копирование bot.py и install-bot.sh в /opt/mita-panel/ - README: обновлена дорожная карта
856 lines
37 KiB
Bash
Executable File
856 lines
37 KiB
Bash
Executable File
#!/bin/bash
|
||
# =============================================================================
|
||
# mita + WARP Setup Script
|
||
# Ubuntu / Debian VPS
|
||
# =============================================================================
|
||
set -euo pipefail
|
||
|
||
# Директория, из которой запущен скрипт (нужна для поиска install-panel.sh рядом)
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
|
||
# ========================= ЦВЕТА ДЛЯ ВЫВОДА =========================
|
||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'; CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
|
||
|
||
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
|
||
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||
error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; }
|
||
section() { echo -e "\n${CYAN}══════ $* ══════${NC}"; }
|
||
|
||
# =============================================================================
|
||
# ██████████████████████████████████████████████████████████████████████████
|
||
# !! НАСТРОЙКИ — РЕДАКТИРОВАТЬ ЗДЕСЬ !!
|
||
# ██████████████████████████████████████████████████████████████████████████
|
||
# =============================================================================
|
||
|
||
# ---- Порты mita (можно указать диапазон или одиночные порты) ----
|
||
MITA_PORT_RANGE="2100-2110" # диапазон портов mita
|
||
MITA_PROTOCOL="TCP" # TCP или UDP
|
||
|
||
# ---- Пользователи mita ----
|
||
# Формат: "имя:пароль"
|
||
# Добавляйте сколько угодно строк
|
||
# ВНИМАНИЕ: перед использованием ОБЯЗАТЕЛЬНО замените пароли ниже на свои (минимум 8 символов)!
|
||
MITA_USERS=(
|
||
"alice:qwerty1234"
|
||
"bob:qwerty1234"
|
||
)
|
||
|
||
# ---- WARP ----
|
||
WARP_ENABLED=true # true — включить WARP; false — только mita без WARP
|
||
WARP_PORT=40000 # локальный порт SOCKS5 от WARP (не менять без причины)
|
||
|
||
# ---- Логирование mita ----
|
||
MITA_LOG_LEVEL="INFO" # INFO или DEBUG
|
||
|
||
# =============================================================================
|
||
# !! КОНЕЦ НАСТРОЕК !!
|
||
# =============================================================================
|
||
|
||
MITA_CONFIG_DIR="/etc/mita"
|
||
MITA_CONFIG_FILE="$MITA_CONFIG_DIR/server_config.json"
|
||
LISTS_DIR="/etc/mita/lists"
|
||
CUSTOM_DOMAINS_FILE="$LISTS_DIR/custom_domains.txt"
|
||
CUSTOM_IPS_FILE="$LISTS_DIR/custom_ips.txt"
|
||
AUTO_DOMAINS_FILE="$LISTS_DIR/auto_domains.txt"
|
||
AUTO_IPS_FILE="$LISTS_DIR/auto_ips.txt"
|
||
UPDATE_SCRIPT="/usr/local/bin/mita-update-lists"
|
||
CRON_FILE="/etc/cron.d/mita-update"
|
||
|
||
# =============================================================================
|
||
# ПРОВЕРКИ
|
||
# =============================================================================
|
||
section "Проверка окружения"
|
||
|
||
[[ $EUID -ne 0 ]] && error "Запустите скрипт от root: sudo bash install.sh"
|
||
|
||
OS=$(. /etc/os-release && echo "$ID")
|
||
[[ "$OS" != "ubuntu" && "$OS" != "debian" ]] && error "Поддерживается только Ubuntu/Debian"
|
||
ok "ОС: $OS"
|
||
|
||
# Проверка пользователей
|
||
for entry in "${MITA_USERS[@]}"; do
|
||
name="${entry%%:*}"; pass="${entry##*:}"
|
||
[[ "$pass" == *"ЗАМЕНИТЕ"* ]] && error "Замените пароль для пользователя '$name' в начале скрипта!"
|
||
[[ ${#pass} -lt 8 ]] && error "Пароль пользователя '$name' слишком короткий (минимум 8 символов)"
|
||
done
|
||
ok "Пользователи настроены корректно (${#MITA_USERS[@]} шт.)"
|
||
|
||
# =============================================================================
|
||
# ВЫБОР ПОРТОВ MITA
|
||
# =============================================================================
|
||
section "Порты mita"
|
||
|
||
echo -e " Текущее значение: ${YELLOW}${MITA_PORT_RANGE}${NC} (${MITA_PROTOCOL})"
|
||
echo -e " Допустимые форматы:"
|
||
echo -e " диапазон: ${CYAN}2100-2110${NC} — mita случайно выбирает порт из диапазона при каждом соединении"
|
||
echo -e " одиночный: ${CYAN}2100${NC} — фиксированный порт"
|
||
echo -e " Рекомендуется диапазон из 5-20 портов."
|
||
echo ""
|
||
read -r -p " Введите порт или диапазон [${MITA_PORT_RANGE}]: " _PORT_INPUT
|
||
|
||
if [[ -n "$_PORT_INPUT" ]]; then
|
||
if [[ "$_PORT_INPUT" =~ ^([0-9]+)-([0-9]+)$ ]]; then
|
||
_P_START="${BASH_REMATCH[1]}"
|
||
_P_END="${BASH_REMATCH[2]}"
|
||
if [[ $_P_START -lt 1024 || $_P_END -gt 65535 ]]; then
|
||
error "Порты должны быть в диапазоне 1024–65535. Получено: $_P_START-$_P_END"
|
||
fi
|
||
if [[ $_P_START -ge $_P_END ]]; then
|
||
error "Начало диапазона ($_P_START) должно быть меньше конца ($_P_END)"
|
||
fi
|
||
MITA_PORT_RANGE="$_PORT_INPUT"
|
||
ok "Диапазон портов: $MITA_PORT_RANGE"
|
||
elif [[ "$_PORT_INPUT" =~ ^[0-9]+$ ]]; then
|
||
if [[ $_PORT_INPUT -lt 1024 || $_PORT_INPUT -gt 65535 ]]; then
|
||
error "Порт $_PORT_INPUT вне допустимого диапазона (1024–65535)"
|
||
fi
|
||
MITA_PORT_RANGE="$_PORT_INPUT"
|
||
ok "Порт: $MITA_PORT_RANGE"
|
||
else
|
||
error "Неверный формат: '$_PORT_INPUT'. Ожидается число (2100) или диапазон (2100-2110)"
|
||
fi
|
||
else
|
||
ok "Оставлен по умолчанию: $MITA_PORT_RANGE"
|
||
fi
|
||
|
||
# =============================================================================
|
||
# ЗАВИСИМОСТИ
|
||
# =============================================================================
|
||
section "Установка зависимостей"
|
||
|
||
apt-get update -qq || { warn "apt-get update завершился с ошибкой, продолжаем..."; true; }
|
||
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \
|
||
curl wget jq cron \
|
||
ca-certificates gnupg lsb-release \
|
||
iptables ufw \
|
||
fail2ban openssl \
|
||
net-tools \
|
||
python3 python3-yaml \
|
||
|| error "Ошибка установки зависимостей. Запустите вручную: apt-get install -y curl wget jq cron iptables ufw fail2ban python3 python3-yaml"
|
||
# ipset и iptables-persistent — опциональные, не прерываем при ошибке
|
||
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq ipset iptables-persistent 2>/dev/null || true
|
||
ok "Базовые пакеты установлены (включая fail2ban, ufw, ipset, iptables-persistent, python3-yaml)"
|
||
|
||
# =============================================================================
|
||
# УСТАНОВКА MITA
|
||
# =============================================================================
|
||
section "Установка mita"
|
||
|
||
MITA_VERSION=$(curl -s https://api.github.com/repos/enfein/mieru/releases/latest \
|
||
| jq -r '.tag_name' | tr -d 'v')
|
||
[[ -z "$MITA_VERSION" ]] && error "Не удалось получить версию mita с GitHub"
|
||
info "Последняя версия mita: $MITA_VERSION"
|
||
|
||
ARCH=$(uname -m)
|
||
case "$ARCH" in
|
||
x86_64) DEB_ARCH="amd64" ;;
|
||
aarch64) DEB_ARCH="arm64" ;;
|
||
armv7l) DEB_ARCH="armhf" ;;
|
||
*) error "Неподдерживаемая архитектура: $ARCH" ;;
|
||
esac
|
||
|
||
DEB_FILE="mita_${MITA_VERSION}_${DEB_ARCH}.deb"
|
||
DL_URL="https://github.com/enfein/mieru/releases/download/v${MITA_VERSION}/${DEB_FILE}"
|
||
|
||
info "Загрузка: $DL_URL"
|
||
wget -q --show-progress "$DL_URL" -O /tmp/"$DEB_FILE" || error "Ошибка загрузки mita"
|
||
dpkg -i /tmp/"$DEB_FILE" || apt-get install -f -y -qq
|
||
rm -f /tmp/"$DEB_FILE"
|
||
ok "mita $MITA_VERSION установлен"
|
||
|
||
# =============================================================================
|
||
# УСТАНОВКА DOCKER + WARP
|
||
# =============================================================================
|
||
if $WARP_ENABLED; then
|
||
section "Установка Docker и Cloudflare WARP"
|
||
|
||
if ! command -v docker &>/dev/null; then
|
||
info "Установка Docker..."
|
||
curl -fsSL https://get.docker.com | sh -s -- -q
|
||
systemctl enable docker --now
|
||
ok "Docker установлен"
|
||
else
|
||
ok "Docker уже установлен"
|
||
fi
|
||
|
||
info "Запуск Cloudflare WARP в Docker..."
|
||
docker rm -f cloudflare-warp 2>/dev/null || true
|
||
docker run -d \
|
||
--name cloudflare-warp \
|
||
--restart unless-stopped \
|
||
-p 127.0.0.1:${WARP_PORT}:40000 \
|
||
seiry/cloudflare-warp-proxy
|
||
|
||
# Подождать пока WARP поднимется
|
||
info "Ожидание запуска WARP (до 30 сек)..."
|
||
for i in $(seq 1 30); do
|
||
if curl -s --max-time 3 --proxy "socks5h://127.0.0.1:${WARP_PORT}" \
|
||
"https://www.cloudflare.com/cdn-cgi/trace" 2>/dev/null | grep -q "warp=on"; then
|
||
ok "WARP активен и работает"
|
||
break
|
||
fi
|
||
sleep 1
|
||
[[ $i -eq 30 ]] && warn "WARP не ответил за 30 сек — продолжаем, проверьте позже: docker logs cloudflare-warp"
|
||
done
|
||
fi
|
||
|
||
# =============================================================================
|
||
# СОЗДАНИЕ ДИРЕКТОРИЙ И ФАЙЛОВ СПИСКОВ
|
||
# =============================================================================
|
||
section "Создание файлов конфигурации"
|
||
|
||
mkdir -p "$MITA_CONFIG_DIR" "$LISTS_DIR"
|
||
|
||
# ---- custom_domains.txt ----
|
||
cat > "$CUSTOM_DOMAINS_FILE" << 'EOF'
|
||
# ================================================================
|
||
# ПОЛЬЗОВАТЕЛЬСКИЙ СПИСОК ДОМЕНОВ → трафик пойдёт через WARP
|
||
# ================================================================
|
||
# Добавляйте домены по одному на строку.
|
||
# Строки начинающиеся с # — комментарии, игнорируются.
|
||
# Субдомены включаются автоматически (openai.com → *.openai.com).
|
||
#
|
||
# Примеры:
|
||
# openai.com
|
||
# chatgpt.com
|
||
# anthropic.com
|
||
# claude.ai
|
||
# ================================================================
|
||
|
||
EOF
|
||
ok "Создан: $CUSTOM_DOMAINS_FILE"
|
||
|
||
# ---- custom_ips.txt ----
|
||
cat > "$CUSTOM_IPS_FILE" << 'EOF'
|
||
# ================================================================
|
||
# ПОЛЬЗОВАТЕЛЬСКИЙ СПИСОК IP/CIDR → трафик пойдёт через WARP
|
||
# ================================================================
|
||
# Формат: одна подсеть CIDR или IP на строку.
|
||
# Примеры:
|
||
# 104.16.0.0/12
|
||
# 172.64.0.0/13
|
||
# ================================================================
|
||
|
||
EOF
|
||
ok "Создан: $CUSTOM_IPS_FILE"
|
||
|
||
# ---- auto_domains.txt (источники для автообновления) ----
|
||
cat > "$AUTO_DOMAINS_FILE" << 'EOF'
|
||
# ================================================================
|
||
# ИСТОЧНИКИ АВТООБНОВЛЯЕМЫХ ДОМЕНОВ (ссылки или geosite-категории)
|
||
# ================================================================
|
||
# Поддерживаемые форматы строк:
|
||
#
|
||
# 1) URL на текстовый файл (один домен на строку):
|
||
# https://raw.githubusercontent.com/v2fly/domain-list-community/release/openai.txt
|
||
#
|
||
# 2) geosite-категория из репозитория v2fly:
|
||
# geosite:openai
|
||
# geosite:google
|
||
# geosite:netflix
|
||
# geosite:category-ru ← российские сайты
|
||
# (полный список: github.com/v2fly/domain-list-community/tree/master/data)
|
||
#
|
||
# Строки с # — комментарии.
|
||
# ================================================================
|
||
|
||
# --- OpenAI / AI сервисы ---
|
||
geosite:openai
|
||
|
||
# --- Примеры (раскомментируйте нужное) ---
|
||
# geosite:google
|
||
# geosite:netflix
|
||
# https://raw.githubusercontent.com/nicholaswilde/blocklists/main/domain-lists/streaming.txt
|
||
EOF
|
||
ok "Создан: $AUTO_DOMAINS_FILE"
|
||
|
||
# ---- auto_ips.txt ----
|
||
cat > "$AUTO_IPS_FILE" << 'EOF'
|
||
# ================================================================
|
||
# ИСТОЧНИКИ АВТООБНОВЛЯЕМЫХ IP-ДИАПАЗОНОВ
|
||
# ================================================================
|
||
# Формат строк:
|
||
#
|
||
# 1) URL на файл с CIDR:
|
||
# https://raw.githubusercontent.com/herrbischoff/country-ip-blocks/master/ipv4/us.cidr
|
||
#
|
||
# 2) geoip-страна (ISO-код):
|
||
# geoip:us
|
||
# geoip:gb
|
||
# geoip:de
|
||
#
|
||
# ================================================================
|
||
|
||
# Пример: американские IP через WARP
|
||
# geoip:us
|
||
EOF
|
||
ok "Создан: $AUTO_IPS_FILE"
|
||
|
||
# =============================================================================
|
||
# СКРИПТ ОБНОВЛЕНИЯ СПИСКОВ
|
||
# =============================================================================
|
||
section "Создание скрипта обновления списков"
|
||
|
||
cat > "$UPDATE_SCRIPT" << UPDATEEOF
|
||
#!/bin/bash
|
||
# Автообновление списков доменов/IP и перегенерация конфига mita
|
||
# Запускается cron ежедневно
|
||
|
||
set -euo pipefail
|
||
LISTS_DIR="$LISTS_DIR"
|
||
MITA_CONFIG_FILE="$MITA_CONFIG_FILE"
|
||
WARP_ENABLED=$WARP_ENABLED
|
||
WARP_PORT=$WARP_PORT
|
||
MITA_PORT_RANGE="$MITA_PORT_RANGE"
|
||
MITA_PROTOCOL="$MITA_PROTOCOL"
|
||
MITA_LOG_LEVEL="$MITA_LOG_LEVEL"
|
||
GEOSITE_YML_URL="https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat_plain.yml"
|
||
GEOSITE_CACHE="/var/cache/mita-geosite.yml"
|
||
GEOIP_BASE="https://raw.githubusercontent.com/herrbischoff/country-ip-blocks/master/ipv4"
|
||
|
||
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] \$*" | tee -a /var/log/mita-update.log; }
|
||
|
||
# ---------- Сбор доменов ----------
|
||
log "Сбор доменов..."
|
||
TMP_DOMAINS=\$(mktemp)
|
||
|
||
# Пользовательские домены
|
||
grep -v '^\s*#' "\$LISTS_DIR/custom_domains.txt" | grep -v '^\s*$' >> "\$TMP_DOMAINS" || true
|
||
|
||
# Проверяем, нужен ли geosite YAML (есть хотя бы одна строка geosite: в auto_domains.txt)
|
||
NEED_GEOSITE=false
|
||
grep -q '^geosite:' "\$LISTS_DIR/auto_domains.txt" 2>/dev/null && NEED_GEOSITE=true
|
||
|
||
if \$NEED_GEOSITE; then
|
||
mkdir -p "\$(dirname "\$GEOSITE_CACHE")"
|
||
CACHE_AGE_DAYS=999
|
||
if [[ -f "\$GEOSITE_CACHE" ]]; then
|
||
CACHE_AGE_SEC=\$(( \$(date +%s) - \$(stat -c %Y "\$GEOSITE_CACHE" 2>/dev/null || echo 0) ))
|
||
CACHE_AGE_DAYS=\$(( CACHE_AGE_SEC / 86400 ))
|
||
fi
|
||
if [[ \$CACHE_AGE_DAYS -ge 7 ]]; then
|
||
log "Загрузка базы geosite (dlc.dat_plain.yml, ~10-15 МБ, кэш обновляется раз в 7 дней)..."
|
||
if curl -sfL --max-time 60 "\$GEOSITE_YML_URL" -o "\$GEOSITE_CACHE.tmp" 2>/dev/null; then
|
||
mv "\$GEOSITE_CACHE.tmp" "\$GEOSITE_CACHE"
|
||
log "База geosite обновлена"
|
||
else
|
||
log "WARN: не удалось загрузить базу geosite — используется старый кэш (если есть)"
|
||
rm -f "\$GEOSITE_CACHE.tmp"
|
||
fi
|
||
else
|
||
log "База geosite в кэше актуальна (обновлена \$CACHE_AGE_DAYS дн. назад)"
|
||
fi
|
||
fi
|
||
|
||
# Автоматические источники
|
||
while IFS= read -r line; do
|
||
line="\$(echo "\$line" | xargs)"
|
||
[[ -z "\$line" || "\$line" == \#* ]] && continue
|
||
|
||
if [[ "\$line" == geosite:* ]]; then
|
||
category="\${line#geosite:}"
|
||
log " geosite:\$category"
|
||
if [[ -f "\$GEOSITE_CACHE" ]]; then
|
||
python3 -c "
|
||
import yaml, sys
|
||
try:
|
||
with open('\$GEOSITE_CACHE') as f:
|
||
data = yaml.safe_load(f)
|
||
found = False
|
||
for entry in data.get('lists', []):
|
||
if entry.get('name','').lower() == '\$category'.lower():
|
||
found = True
|
||
for rule in entry.get('rules', []):
|
||
d = rule
|
||
for prefix in ('domain:', 'full:'):
|
||
if d.startswith(prefix):
|
||
d = d[len(prefix):]
|
||
print(d)
|
||
break
|
||
else:
|
||
# regexp: и include: пропускаем — не доменные правила
|
||
pass
|
||
if not found:
|
||
sys.stderr.write('NOTFOUND\\n')
|
||
except Exception as e:
|
||
sys.stderr.write(f'ERROR:{e}\\n')
|
||
" >> "\$TMP_DOMAINS" 2>/tmp/geosite_err.log
|
||
if grep -q "NOTFOUND" /tmp/geosite_err.log 2>/dev/null; then
|
||
log " WARN: категория geosite:\$category не найдена в базе"
|
||
elif grep -q "ERROR" /tmp/geosite_err.log 2>/dev/null; then
|
||
log " WARN: ошибка парсинга geosite:\$category — $(cat /tmp/geosite_err.log)"
|
||
fi
|
||
rm -f /tmp/geosite_err.log
|
||
else
|
||
log " WARN: база geosite недоступна, пропускаю geosite:\$category"
|
||
fi
|
||
|
||
elif [[ "\$line" == http* ]]; then
|
||
log " URL: \$line"
|
||
curl -sf "\$line" 2>/dev/null \
|
||
| grep -v '^\s*#' | grep -v '^\s*$' \
|
||
>> "\$TMP_DOMAINS" || log " WARN: не удалось загрузить \$line"
|
||
fi
|
||
done < "\$LISTS_DIR/auto_domains.txt"
|
||
|
||
# Дедупликация доменов
|
||
FINAL_DOMAINS=\$(sort -u "\$TMP_DOMAINS" | grep -v '^\s*$')
|
||
rm -f "\$TMP_DOMAINS"
|
||
DOMAIN_COUNT=\$(echo "\$FINAL_DOMAINS" | grep -c . || echo 0)
|
||
log "Доменов собрано: \$DOMAIN_COUNT"
|
||
|
||
# ---------- Сбор IP-диапазонов ----------
|
||
log "Сбор IP-диапазонов..."
|
||
TMP_IPS=\$(mktemp)
|
||
|
||
grep -v '^\s*#' "\$LISTS_DIR/custom_ips.txt" | grep -v '^\s*$' >> "\$TMP_IPS" || true
|
||
|
||
while IFS= read -r line; do
|
||
line="\$(echo "\$line" | xargs)"
|
||
[[ -z "\$line" || "\$line" == \#* ]] && continue
|
||
|
||
if [[ "\$line" == geoip:* ]]; then
|
||
country="\${line#geoip:}"
|
||
log " geoip:\$country"
|
||
curl -sf "\$GEOIP_BASE/\${country}.cidr" 2>/dev/null \
|
||
>> "\$TMP_IPS" || log " WARN: не удалось загрузить geoip:\$country"
|
||
|
||
elif [[ "\$line" == http* ]]; then
|
||
log " URL: \$line"
|
||
curl -sf "\$line" 2>/dev/null | grep -v '^\s*#' | grep -v '^\s*$' \
|
||
>> "\$TMP_IPS" || log " WARN: не удалось загрузить \$line"
|
||
fi
|
||
done < "\$LISTS_DIR/auto_ips.txt"
|
||
|
||
FINAL_IPS=\$(sort -u "\$TMP_IPS" | grep -v '^\s*$')
|
||
rm -f "\$TMP_IPS"
|
||
IP_COUNT=\$(echo "\$FINAL_IPS" | grep -c . || echo 0)
|
||
log "IP-диапазонов собрано: \$IP_COUNT"
|
||
|
||
# ---------- Сборка JSON egress-правил ----------
|
||
build_egress() {
|
||
local domains="\$1"
|
||
local ips="\$2"
|
||
|
||
if ! \$WARP_ENABLED; then
|
||
echo '{"rules": [{"ipRanges": ["*"], "domainNames": ["*"], "action": "DIRECT"}]}'
|
||
return
|
||
fi
|
||
|
||
local domain_arr="[]"
|
||
local ip_arr="[]"
|
||
|
||
[[ -n "\$domains" ]] && domain_arr=\$(echo "\$domains" | jq -R . | jq -s .)
|
||
[[ -n "\$ips" ]] && ip_arr=\$(echo "\$ips" | jq -R . | jq -s .)
|
||
|
||
# Строим правила: сначала WARP-правило (если есть домены/IP), потом DIRECT для всего остального
|
||
local rules="[]"
|
||
|
||
if [[ \$DOMAIN_COUNT -gt 0 || \$IP_COUNT -gt 0 ]]; then
|
||
warp_rule=\$(jq -n \
|
||
--argjson d "\$domain_arr" \
|
||
--argjson i "\$ip_arr" \
|
||
'{action: "PROXY", proxyNames: ["warp"], domainNames: \$d, ipRanges: \$i}')
|
||
rules=\$(echo "[\$warp_rule]")
|
||
fi
|
||
|
||
# Финальное правило — всё остальное напрямую
|
||
direct_rule='{"ipRanges": ["*"], "domainNames": ["*"], "action": "DIRECT"}'
|
||
rules=\$(echo "\$rules" | jq ". + [\$direct_rule]" --argjson direct "\$direct_rule")
|
||
|
||
jq -n \
|
||
--argjson rules "\$rules" \
|
||
'{
|
||
proxies: [{
|
||
name: "warp",
|
||
protocol: "SOCKS5_PROXY_PROTOCOL",
|
||
host: "127.0.0.1",
|
||
port: \$ENV.WARP_PORT|tonumber
|
||
}],
|
||
rules: \$rules
|
||
}' <<< "" 2>/dev/null || jq -n \
|
||
--argjson rules "\$rules" \
|
||
--argjson port "\$WARP_PORT" \
|
||
'{
|
||
proxies: [{name: "warp", protocol: "SOCKS5_PROXY_PROTOCOL", host: "127.0.0.1", port: \$port}],
|
||
rules: \$rules
|
||
}'
|
||
}
|
||
|
||
EGRESS_JSON=\$(build_egress "\$FINAL_DOMAINS" "\$FINAL_IPS")
|
||
|
||
# ---------- Чтение текущих пользователей ----------
|
||
CURRENT_USERS=\$(jq '.users' "\$MITA_CONFIG_FILE" 2>/dev/null || echo '[]')
|
||
|
||
# ---------- Генерация итогового конфига ----------
|
||
log "Генерация конфига mita..."
|
||
|
||
# Разбор диапазона портов
|
||
if [[ "\$MITA_PORT_RANGE" == *"-"* ]]; then
|
||
PORT_BINDING=\$(jq -n --arg r "\$MITA_PORT_RANGE" --arg p "\$MITA_PROTOCOL" \
|
||
'[{portRange: \$r, protocol: \$p}]')
|
||
else
|
||
PORT_BINDING=\$(jq -n --arg port "\$MITA_PORT_RANGE" --arg p "\$MITA_PROTOCOL" \
|
||
'[{port: (\$port|tonumber), protocol: \$p}]')
|
||
fi
|
||
|
||
jq -n \
|
||
--argjson portBindings "\$PORT_BINDING" \
|
||
--argjson users "\$CURRENT_USERS" \
|
||
--arg logLevel "\$MITA_LOG_LEVEL" \
|
||
--argjson egress "\$EGRESS_JSON" \
|
||
'{
|
||
portBindings: \$portBindings,
|
||
users: \$users,
|
||
loggingLevel: \$logLevel,
|
||
egress: \$egress
|
||
}' > "\${MITA_CONFIG_FILE}.tmp"
|
||
|
||
mv "\${MITA_CONFIG_FILE}.tmp" "\$MITA_CONFIG_FILE"
|
||
log "Конфиг сохранён: \$MITA_CONFIG_FILE"
|
||
|
||
# ---------- Применение конфига ----------
|
||
log "Применение конфига mita..."
|
||
# Запустить mita если не запущен (нужен для apply config)
|
||
MITA_WAS_STOPPED=false
|
||
if ! systemctl is-active --quiet mita 2>/dev/null; then
|
||
log "mita не запущен — запускаем для применения конфига..."
|
||
systemctl reset-failed mita 2>/dev/null || true
|
||
/usr/bin/mita run &
|
||
MITA_BG_PID=\$!
|
||
sleep 3
|
||
MITA_WAS_STOPPED=true
|
||
fi
|
||
|
||
mita apply config "\$MITA_CONFIG_FILE" && log "Конфиг применён успешно" \
|
||
|| { log "ОШИБКА применения конфига"; exit 1; }
|
||
|
||
if \$MITA_WAS_STOPPED; then
|
||
kill \$MITA_BG_PID 2>/dev/null || true
|
||
sleep 1
|
||
systemctl reset-failed mita 2>/dev/null || true
|
||
systemctl start mita && log "mita запущен" || log "WARN: не удалось запустить mita"
|
||
else
|
||
systemctl restart mita
|
||
log "mita перезапущен"
|
||
fi
|
||
|
||
log "Обновление завершено. Доменов: \$DOMAIN_COUNT, IP: \$IP_COUNT"
|
||
UPDATEEOF
|
||
|
||
chmod +x "$UPDATE_SCRIPT"
|
||
ok "Скрипт обновления создан: $UPDATE_SCRIPT"
|
||
|
||
# =============================================================================
|
||
# ПЕРВОНАЧАЛЬНАЯ ГЕНЕРАЦИЯ КОНФИГА С ПОЛЬЗОВАТЕЛЯМИ
|
||
# =============================================================================
|
||
section "Генерация начального конфига mita"
|
||
|
||
# Собрать JSON пользователей
|
||
USERS_JSON="["
|
||
for i in "${!MITA_USERS[@]}"; do
|
||
entry="${MITA_USERS[$i]}"
|
||
name="${entry%%:*}"
|
||
pass="${entry##*:}"
|
||
sep=$([[ $i -lt $((${#MITA_USERS[@]}-1)) ]] && echo "," || echo "")
|
||
USERS_JSON+=$(jq -n --arg n "$name" --arg p "$pass" \
|
||
'{name: $n, password: $p}')
|
||
USERS_JSON+="$sep"
|
||
done
|
||
USERS_JSON+="]"
|
||
|
||
# Разбор диапазона портов для начального конфига
|
||
if [[ "$MITA_PORT_RANGE" == *"-"* ]]; then
|
||
PORT_BINDING=$(jq -n --arg r "$MITA_PORT_RANGE" --arg p "$MITA_PROTOCOL" \
|
||
'[{portRange: $r, protocol: $p}]')
|
||
else
|
||
PORT_BINDING=$(jq -n --arg port "$MITA_PORT_RANGE" --arg p "$MITA_PROTOCOL" \
|
||
'[{port: ($port|tonumber), protocol: $p}]')
|
||
fi
|
||
|
||
# Базовый конфиг без egress (применим после первого запуска update-lists)
|
||
jq -n \
|
||
--argjson portBindings "$PORT_BINDING" \
|
||
--argjson users "$USERS_JSON" \
|
||
--arg logLevel "$MITA_LOG_LEVEL" \
|
||
'{
|
||
portBindings: $portBindings,
|
||
users: $users,
|
||
loggingLevel: $logLevel,
|
||
egress: {}
|
||
}' > "$MITA_CONFIG_FILE"
|
||
|
||
ok "Начальный конфиг записан: $MITA_CONFIG_FILE"
|
||
|
||
# Применить конфиг
|
||
mita apply config "$MITA_CONFIG_FILE"
|
||
ok "Конфиг mita применён"
|
||
|
||
# =============================================================================
|
||
# ЗАПУСК MITA
|
||
# =============================================================================
|
||
section "Запуск mita"
|
||
|
||
systemctl enable mita 2>/dev/null || true
|
||
systemctl restart mita
|
||
sleep 2
|
||
if systemctl is-active --quiet mita; then
|
||
ok "mita запущен и работает"
|
||
else
|
||
warn "mita не запустился — проверьте: journalctl -u mita -n 50"
|
||
fi
|
||
|
||
# =============================================================================
|
||
# НАСТРОЙКА CRON
|
||
# =============================================================================
|
||
section "Настройка автообновления (cron)"
|
||
|
||
cat > "$CRON_FILE" << EOF
|
||
# Обновление списков доменов/IP для mita — каждый день в 04:00
|
||
0 4 * * * root $UPDATE_SCRIPT >> /var/log/mita-update.log 2>&1
|
||
EOF
|
||
|
||
ok "Cron задача создана: $CRON_FILE"
|
||
|
||
# =============================================================================
|
||
# ПЕРВЫЙ ЗАПУСК ОБНОВЛЕНИЯ СПИСКОВ
|
||
# =============================================================================
|
||
section "Первое обновление списков"
|
||
|
||
info "Запуск $UPDATE_SCRIPT..."
|
||
bash "$UPDATE_SCRIPT" && ok "Списки обновлены, конфиг применён" \
|
||
|| warn "Ошибка при обновлении списков, проверьте /var/log/mita-update.log"
|
||
|
||
# =============================================================================
|
||
# ОТКРЫТИЕ ПОРТОВ В FIREWALL
|
||
# =============================================================================
|
||
section "Настройка firewall"
|
||
|
||
if command -v ufw &>/dev/null && ufw status | grep -q "Status: active"; then
|
||
# UFW требует двоеточие для диапазонов: 3000:3010/tcp, а не 3000-3010/tcp
|
||
if [[ "$MITA_PORT_RANGE" == *"-"* ]]; then
|
||
_UFW_START="${MITA_PORT_RANGE%-*}"; _UFW_END="${MITA_PORT_RANGE#*-}"
|
||
ufw allow "${_UFW_START}:${_UFW_END}/${MITA_PROTOCOL,,}" comment "mita proxy" 2>/dev/null || true
|
||
else
|
||
ufw allow "${MITA_PORT_RANGE}/${MITA_PROTOCOL,,}" comment "mita proxy" 2>/dev/null || true
|
||
fi
|
||
ok "UFW: порты $MITA_PORT_RANGE/$MITA_PROTOCOL открыты"
|
||
echo ""
|
||
warn "ВАЖНО: UFW управляет только локальным фаерволом ОС."
|
||
warn "Если ваш VPS у облачного провайдера (Hetzner, AWS, GCP, DigitalOcean, Vultr и др.),"
|
||
warn "откройте порты ${MITA_PORT_RANGE} (${MITA_PROTOCOL}) ТАКЖЕ в консоли провайдера"
|
||
warn "(Security Groups / Firewall / Network Rules) — иначе подключения не будет."
|
||
echo ""
|
||
elif command -v iptables &>/dev/null; then
|
||
if [[ "$MITA_PORT_RANGE" == *"-"* ]]; then
|
||
START_PORT="${MITA_PORT_RANGE%-*}"; END_PORT="${MITA_PORT_RANGE#*-}"
|
||
iptables -I INPUT -p "${MITA_PROTOCOL,,}" --dport "$START_PORT:$END_PORT" -j ACCEPT 2>/dev/null || true
|
||
else
|
||
iptables -I INPUT -p "${MITA_PROTOCOL,,}" --dport "$MITA_PORT_RANGE" -j ACCEPT 2>/dev/null || true
|
||
fi
|
||
ok "iptables: порты открыты"
|
||
fi
|
||
|
||
# =============================================================================
|
||
# ИТОГОВЫЙ ОТЧЁТ
|
||
# =============================================================================
|
||
section "Установка завершена!"
|
||
|
||
SERVER_IP=$(curl -s --max-time 5 ifconfig.me 2>/dev/null || hostname -I | awk '{print $1}')
|
||
|
||
echo ""
|
||
echo -e "${GREEN}╔══════════════════════════════════════════════════════╗"
|
||
echo -e "║ УСТАНОВКА ЗАВЕРШЕНА УСПЕШНО ║"
|
||
echo -e "╚══════════════════════════════════════════════════════╝${NC}"
|
||
echo ""
|
||
echo -e "${CYAN}Параметры сервера для клиентов:${NC}"
|
||
echo -e " IP сервера: ${YELLOW}$SERVER_IP${NC}"
|
||
echo -e " Порты mita: ${YELLOW}$MITA_PORT_RANGE ($MITA_PROTOCOL)${NC}"
|
||
echo ""
|
||
echo -e "${CYAN}Пользователи:${NC}"
|
||
for entry in "${MITA_USERS[@]}"; do
|
||
name="${entry%%:*}"; pass="${entry##*:}"
|
||
echo -e " ${YELLOW}$name${NC} / $pass"
|
||
done
|
||
echo ""
|
||
echo -e "${CYAN}Файлы конфигурации:${NC}"
|
||
echo -e " Конфиг mita: ${YELLOW}$MITA_CONFIG_FILE${NC}"
|
||
echo -e " Свои домены → WARP: ${YELLOW}$CUSTOM_DOMAINS_FILE${NC}"
|
||
echo -e " Свои IP → WARP: ${YELLOW}$CUSTOM_IPS_FILE${NC}"
|
||
echo -e " Авто-источники (dom): ${YELLOW}$AUTO_DOMAINS_FILE${NC}"
|
||
echo -e " Авто-источники (IP): ${YELLOW}$AUTO_IPS_FILE${NC}"
|
||
echo -e " Лог обновлений: ${YELLOW}/var/log/mita-update.log${NC}"
|
||
echo ""
|
||
echo -e "${CYAN}Полезные команды:${NC}"
|
||
echo -e " ${YELLOW}mita status${NC} — статус сервера"
|
||
echo -e " ${YELLOW}mita get users${NC} — список пользователей и их трафик"
|
||
echo -e " ${YELLOW}mita describe config${NC} — текущий конфиг"
|
||
echo -e " ${YELLOW}$UPDATE_SCRIPT${NC} — обновить списки вручную"
|
||
echo -e " ${YELLOW}journalctl -u mita -f${NC} — логи mita в реальном времени"
|
||
if $WARP_ENABLED; then
|
||
echo -e " ${YELLOW}docker logs cloudflare-warp${NC} — логи WARP"
|
||
fi
|
||
echo ""
|
||
echo -e "${CYAN}Следующий шаг — добавьте нужные домены в:${NC}"
|
||
echo -e " ${YELLOW}$CUSTOM_DOMAINS_FILE${NC} — ваши домены"
|
||
echo -e " ${YELLOW}$AUTO_DOMAINS_FILE${NC} — geosite/URL источники"
|
||
echo -e "Затем запустите: ${YELLOW}$UPDATE_SCRIPT${NC}"
|
||
echo ""
|
||
|
||
# =============================================================================
|
||
# УСТАНОВКА ВЕБ-ПАНЕЛИ (опционально)
|
||
# =============================================================================
|
||
section "Веб-панель управления"
|
||
|
||
# Ищем install-panel.sh в нескольких возможных местах:
|
||
# 1) Прямо рядом с install.sh (актуальная структура архива)
|
||
# 2) В подпапке mita-panel-pkg/ (старая структура архива)
|
||
PANEL_INSTALLER=""
|
||
for candidate in \
|
||
"$SCRIPT_DIR/install-panel.sh" \
|
||
"$SCRIPT_DIR/mita-panel-pkg/install-panel.sh"
|
||
do
|
||
if [[ -f "$candidate" ]]; then
|
||
PANEL_INSTALLER="$candidate"
|
||
break
|
||
fi
|
||
done
|
||
|
||
if [[ -z "$PANEL_INSTALLER" ]]; then
|
||
warn "Файл install-panel.sh не найден рядом со скриптом."
|
||
warn "Искал в: $SCRIPT_DIR/ и $SCRIPT_DIR/mita-panel-pkg/"
|
||
warn "Убедитесь что install-panel.sh, app.py и templates/ распакованы рядом с install.sh."
|
||
else
|
||
echo -e "Установить веб-панель управления mita? (логин/пароль и секретный путь генерируются автоматически)"
|
||
echo -e " ${GREEN}y${NC} — да, установить сейчас"
|
||
echo -e " ${YELLOW}n${NC} — пропустить (можно установить позже: bash $PANEL_INSTALLER)"
|
||
echo ""
|
||
read -r -p "Установить панель? [y/N]: " INSTALL_PANEL
|
||
echo ""
|
||
|
||
if [[ "${INSTALL_PANEL,,}" == "y" || "${INSTALL_PANEL,,}" == "yes" ]]; then
|
||
info "Запуск установки веб-панели..."
|
||
echo ""
|
||
bash "$PANEL_INSTALLER"
|
||
else
|
||
info "Пропускаем установку панели."
|
||
echo -e " Запустить позже: ${YELLOW}bash $PANEL_INSTALLER${NC}"
|
||
fi
|
||
fi
|
||
|
||
# ═══════════════════════════════════════════════════════════════════════
|
||
# УСТАНОВКА TELEGRAM-БОТА (опционально)
|
||
# ═══════════════════════════════════════════════════════════════════════
|
||
section "Telegram-бот"
|
||
|
||
BOT_INSTALLER=""
|
||
for candidate in \
|
||
"$SCRIPT_DIR/install-bot.sh" \
|
||
"$SCRIPT_DIR/mita-bot-pkg/install-bot.sh"
|
||
do
|
||
if [[ -f "$candidate" ]]; then
|
||
BOT_INSTALLER="$candidate"
|
||
break
|
||
fi
|
||
done
|
||
|
||
if [[ -z "$BOT_INSTALLER" ]]; then
|
||
warn "install-bot.sh не найден рядом со скриптом."
|
||
warn "Поместите bot.py и install-bot.sh рядом с install.sh для установки бота."
|
||
else
|
||
echo -e "Установить Telegram-бота для управления сервером?"
|
||
echo -e " Бот позволит: создавать/удалять пользователей, смотреть трафик,"
|
||
echo -e " включать WARP, мониторить CPU/RAM/диск — всё через Telegram."
|
||
echo ""
|
||
echo -e " ${GREEN}y${NC} — да, установить сейчас (потребуется токен и Telegram ID)"
|
||
echo -e " ${YELLOW}n${NC} — пропустить (установить позже: bash $BOT_INSTALLER или через веб-панель)"
|
||
echo ""
|
||
read -r -p "Установить бота? [y/N]: " INSTALL_BOT
|
||
echo ""
|
||
|
||
if [[ "${INSTALL_BOT,,}" == "y" || "${INSTALL_BOT,,}" == "yes" ]]; then
|
||
echo ""
|
||
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||
echo -e "${CYAN} Инструкция по созданию бота${NC}"
|
||
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||
echo ""
|
||
echo -e " ${BOLD}1.${NC} Откройте Telegram и найдите ${GREEN}@BotFather${NC}"
|
||
echo -e " Отправьте команду ${YELLOW}/newbot${NC}"
|
||
echo -e " Следуйте инструкциям: придумайте имя и username бота"
|
||
echo -e " В ответ вы получите ${BOLD}токен${NC} — длинную строку вида:"
|
||
echo -e " ${YELLOW}1234567890:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw${NC}"
|
||
echo ""
|
||
echo -e " ${BOLD}2.${NC} Откройте своего бота в Telegram и отправьте любое сообщение"
|
||
echo -e " (например «привет»). ID определится автоматически."
|
||
echo ""
|
||
echo -e " ${BOLD}3.${NC} Когда токен готов — нажмите Enter для продолжения"
|
||
|
||
while true; do
|
||
echo ""
|
||
read -r -p " Впишите токен от @BotFather: " BOT_TOKEN
|
||
if [[ -z "$BOT_TOKEN" ]]; then
|
||
warn "Токен не может быть пустым"
|
||
continue
|
||
fi
|
||
break
|
||
done
|
||
|
||
echo ""
|
||
info "Определение вашего Telegram ID..."
|
||
echo -e " Отправьте ${YELLOW}любое сообщение${NC} своему боту в Telegram прямо сейчас."
|
||
echo -e " Ожидание сообщения (макс. 60 сек)..."
|
||
|
||
BOT_ADMIN=""
|
||
for i in $(seq 1 30); do
|
||
BOT_ADMIN=$(curl -s --max-time 5 "https://api.telegram.org/bot${BOT_TOKEN}/getUpdates" 2>/dev/null | \
|
||
python3 -c "
|
||
import sys,json
|
||
try:
|
||
d=json.load(sys.stdin)
|
||
ids=[r['message']['from']['id'] for r in d.get('result',[]) if 'message' in r and 'from' in r['message']]
|
||
print(ids[-1] if ids else '')
|
||
except: pass
|
||
" 2>/dev/null)
|
||
[[ -n "$BOT_ADMIN" ]] && break
|
||
sleep 2
|
||
done
|
||
|
||
if [[ -z "$BOT_ADMIN" || ! "$BOT_ADMIN" =~ ^[0-9]+$ ]]; then
|
||
warn "Не удалось определить ID автоматически."
|
||
while true; do
|
||
echo ""
|
||
read -r -p " Впишите ваш Telegram ID вручную (число): " BOT_ADMIN
|
||
if [[ -n "$BOT_ADMIN" && "$BOT_ADMIN" =~ ^[0-9]+$ ]]; then
|
||
break
|
||
fi
|
||
warn "Telegram ID должен быть числом"
|
||
done
|
||
else
|
||
ok "Telegram ID определён: ${GREEN}${BOT_ADMIN}${NC}"
|
||
fi
|
||
|
||
echo ""
|
||
info "Запуск установки Telegram-бота..."
|
||
BOT_TOKEN_NONINTERACTIVE="$BOT_TOKEN" BOT_ADMIN_NONINTERACTIVE="$BOT_ADMIN" bash "$BOT_INSTALLER"
|
||
else
|
||
info "Пропускаем установку бота."
|
||
echo -e " Установить позже: ${YELLOW}bash $BOT_INSTALLER${NC}"
|
||
echo -e " Или через веб-панель: раздел «Telegram бот»"
|
||
fi
|
||
fi
|
||
|
||
# ── Установка mita-ctl как системной команды ──────────────────────
|
||
section "Установка утилиты управления mita-ctl"
|
||
CTL_SRC="$SCRIPT_DIR/mita-ctl.sh"
|
||
if [[ -f "$CTL_SRC" ]]; then
|
||
cp "$CTL_SRC" /usr/local/bin/mita-ctl
|
||
chmod +x /usr/local/bin/mita-ctl
|
||
ok "mita-ctl установлен!"
|
||
echo -e " Управление сервером: ${YELLOW}sudo mita-ctl${NC}"
|
||
else
|
||
warn "mita-ctl.sh не найден рядом с install.sh"
|
||
warn "Установите вручную: cp mita-ctl.sh /usr/local/bin/mita-ctl && chmod +x /usr/local/bin/mita-ctl"
|
||
fi
|