This commit is contained in:
Ground-Zerro
2025-11-01 21:25:15 +11:00
parent b8ec267ae3
commit e11e5696cf
14 changed files with 1001 additions and 752 deletions

View File

@@ -2,6 +2,7 @@
<details> <details>
<summary>Что нового (нажать, чтобы открыть)</summary> <summary>Что нового (нажать, чтобы открыть)</summary>
- Реворк работы с DNS серверами. Прогрессбар. Разделение файла на части для некоторых форматов. Обновлена утилита Сonvert.
- Keenetic BAT формат сохранения. Небольшие изменения в интерфейсе. Некоторые доработки/улучшения. - Keenetic BAT формат сохранения. Небольшие изменения в интерфейсе. Некоторые доработки/улучшения.
- Доабвлены некоторые [оналйн кинотеатры](https://github.com/Ground-Zerro/DomainMapper/blob/main/platforms/dns-onlinetheater.txt). Запрос @Andrey_schumacher - Доабвлены некоторые [оналйн кинотеатры](https://github.com/Ground-Zerro/DomainMapper/blob/main/platforms/dns-onlinetheater.txt). Запрос @Andrey_schumacher
- Добавлены списки от [ITDog](https://t.me/itdoginfo/36). - Добавлены списки от [ITDog](https://t.me/itdoginfo/36).
@@ -66,6 +67,7 @@
- Агрегация маршрутов в /16 (255.255.0.0) и /24 (255.255.255.0) подсети. Комбинированный режим /24 + /32. - Агрегация маршрутов в /16 (255.255.0.0) и /24 (255.255.255.0) подсети. Комбинированный режим /24 + /32.
- Фильтрация IP-адресов Cloudflare (опционально). - Фильтрация IP-адресов Cloudflare (опционально).
- Множество форматов сохранения результата. - Множество форматов сохранения результата.
- Разделение больших файлов на части для некоторых форматов.
**Ключевые особенности** **Ключевые особенности**
@@ -173,6 +175,6 @@ curl -L -s "https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/refs/hea
# ☕ Поддержка # ☕ Поддержка
Если проект оказался Вам полезен — можно поддержать автора: Если проект оказался Вам полезен — можно поблагодарить автора:
- [Поддержать на Boosty](https://boosty.to/ground_zerro) - [Поддержать на Boosty](https://boosty.to/ground_zerro)

View File

@@ -1,96 +1,112 @@
@echo off @echo off
setlocal enabledelayedexpansion setlocal enabledelayedexpansion
chcp 65001 > NUL chcp 65001 > NUL
REM Проверка Python 3 REM Проверка Python 3
:CheckPython :CheckPython
python --version 2>NUL | findstr /I "Python 3" >NUL python --version 2>NUL | findstr /I "Python 3" >NUL
if ERRORLEVEL 1 ( if ERRORLEVEL 1 (
echo Python 3 не установлен. echo Python 3 не установлен.
choice /C YN /M "Установить?" choice /C YN /M "Установить?"
if ERRORLEVEL 2 ( if ERRORLEVEL 2 (
echo Без Python 3 ничего не получится... echo Без Python 3 ничего не получится...
pause pause
exit /b 1 exit /b 1
) else ( ) else (
call :InstallPython call :InstallPython
) )
) else ( ) else (
echo Python 3 установлен. echo Python 3 установлен.
) )
goto :CheckModules goto :CheckModules
REM Инсталляция Python 3 REM Инсталляция Python 3
:InstallPython :InstallPython
echo Загрузка дистрибутива... echo Загрузка дистрибутива...
powershell -Command "if ($PSVersionTable.PSVersion.Major -ge 3) {Invoke-WebRequest -Uri 'https://www.python.org/ftp/python/3.12.5/python-3.12.5-amd64.exe' -OutFile 'python_installer.exe'} else {Start-BitsTransfer -Source 'https://www.python.org/ftp/python/3.12.5/python-3.12.5-amd64.exe' -Destination 'python_installer.exe'}" powershell -Command "if ($PSVersionTable.PSVersion.Major -ge 3) {Invoke-WebRequest -Uri 'https://www.python.org/ftp/python/3.12.5/python-3.12.5-amd64.exe' -OutFile 'python_installer.exe'} else {Start-BitsTransfer -Source 'https://www.python.org/ftp/python/3.12.5/python-3.12.5-amd64.exe' -Destination 'python_installer.exe'}"
REM Проверяем успешность загрузки REM Проверяем успешность загрузки
if not exist "python_installer.exe" ( if not exist "python_installer.exe" (
echo Ошибка загрузки установщика Python 3. echo Ошибка загрузки установщика Python 3.
pause pause
exit /b 1 exit /b 1
) )
REM Установка Python 3 REM Установка Python 3
echo Установка... echo Установка...
echo PS - не забудьте ее разрешить в соседнем окне echo PS - не забудьте ее разрешить в соседнем окне
python_installer.exe /quiet InstallAllUsers=1 PrependPath=1 python_installer.exe /quiet InstallAllUsers=1 PrependPath=1
del /q /f python_installer.exe del /q /f python_installer.exe
REM Оповещение о перезапуске REM Оповещение о перезапуске
echo. echo.
echo Установка завершена, но требуется обновить окружение. echo Установка завершена, но требуется обновить окружение.
echo - закройте это окно и запустите скрипт снова. echo - закройте это окно и запустите скрипт снова.
pause pause
exit /b 0 exit /b 0
REM Проверка и установка необходимых модулей Python REM Проверка и установка необходимых модулей Python
:CheckModules :CheckModules
set "modules=requests dnspython ipaddress configparser httpx colorama" set "modules=dnspython httpx colorama tqdm"
echo. echo.
echo Проверка необходимых библиотек... echo Проверка необходимых библиотек...
for %%m in (%modules%) do ( for %%m in (%modules%) do (
pip show %%m >NUL 2>&1 pip show %%m >NUL 2>&1
if ERRORLEVEL 1 ( if ERRORLEVEL 1 (
echo Установка библиотеки %%m... echo Установка библиотеки %%m...
pip install %%m pip install %%m
if ERRORLEVEL 1 ( if ERRORLEVEL 1 (
echo Не удалось установить библиотеку %%m. Проверьте pip. echo Не удалось установить библиотеку %%m. Проверьте pip.
exit /b 1 exit /b 1
) )
) )
) )
goto :DownloadMain goto :DownloadMain
REM Загрузка и запуск main.py REM Загрузка и запуск main.py
:DownloadMain :DownloadMain
echo Загрузка Domain Mapper... echo Загрузка Domain Mapper...
powershell -Command "if ($PSVersionTable.PSVersion.Major -ge 3) {Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/main/main.py' -OutFile 'main.py'} else {Start-BitsTransfer -Source 'https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/main/main.py' -Destination 'main.py'}" powershell -Command "if ($PSVersionTable.PSVersion.Major -ge 3) {Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/main/main.py' -OutFile 'main.py'} else {Start-BitsTransfer -Source 'https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/main/main.py' -Destination 'main.py'}"
if not exist "main.py" ( if not exist "main.py" (
echo Ошибка загрузки Domain Mapper. echo Ошибка загрузки Domain Mapper.
pause pause
exit /b 1 exit /b 1
) )
cls cls
REM Запуск main.py REM Запуск main.py
echo Запускаем... echo Запускаем...
python main.py python main.py
if ERRORLEVEL 1 ( if ERRORLEVEL 1 (
echo Ошибка выполнения main.py. echo Ошибка выполнения main.py.
pause pause
del /q /f main.py del /q /f main.py
exit /b 1 exit /b 1
) )
move /y domain-ip-resolve.txt %UserProfile%\Desktop\domain-ip-resolve.txt echo Копирование файлов на рабочий стол...
echo Программа завершена.
del /q /f main.py if exist domain-ip-resolve.txt (
endlocal move /y domain-ip-resolve.txt %UserProfile%\Desktop\domain-ip-resolve.txt
echo файл скопирован в %UserProfile%\Desktop\domain-ip-resolve.txt echo Файл скопирован в %UserProfile%\Desktop\domain-ip-resolve.txt
pause ) else (
exit /b 0 echo Поиск разделенных файлов...
set "found=0"
for %%f in (domain-ip-resolve_p*.txt) do (
move /y "%%f" "%UserProfile%\Desktop\%%f"
echo Файл %%f скопирован на рабочий стол
set "found=1"
)
if "!found!"=="0" (
echo Не найдено файлов для копирования.
)
)
echo Программа завершена.
del /q /f main.py
endlocal
pause
exit /b 0

View File

@@ -10,7 +10,7 @@ service =
dnsserver = dnsserver =
# Исключить Cloudflare IP (yes/no) # Исключить Cloudflare IP (yes/no)
cloudflare = cloudflare =
# Агрегация подсетей (16, 24, mix, no) # Агрегация подсетей (16, 24, mix, no)
subnet = subnet =
@@ -18,8 +18,9 @@ subnet =
# Имя выходного файла # Имя выходного файла
filename = domain-ip-resolve.txt filename = domain-ip-resolve.txt
# Количество потоков (по умолчанию 20) # Лимит запросов к каждому DNS серверу (запросов в секунду, по умолчанию 50)
threads = # Контролирует максимальное количество DNS запросов к одному серверу в секунду
rate_limit = 50
# Формат результата (ip, unix, win, mikrotik, ovpn, wireguard, cidr, keenetic bat и т.д.) # Формат результата (ip, unix, win, mikrotik, ovpn, wireguard, cidr, keenetic bat и т.д.)
filetype = filetype =

325
domain-ip-resolve.txt Normal file
View File

@@ -0,0 +1,325 @@
103.136.41.171
104.192.140.24
104.192.140.25
104.192.140.26
104.244.43.131
104.247.81.99
107.22.247.231
108.181.60.131
116.202.120.165
116.202.120.166
12.5.163.52
128.150.221.244
13.107.21.200
13.107.213.46
13.107.246.46
13.107.253.51
13.216.68.41
13.227.180.4
13.248.188.196
13.249.91.105
13.249.91.27
13.249.91.55
13.249.91.77
13.33.252.118
13.33.252.123
13.33.252.125
13.33.252.128
13.33.252.129
13.33.252.35
13.33.252.55
13.33.252.61
13.33.252.62
13.33.252.7
13.33.252.87
13.33.252.90
13.35.93.102
13.35.93.103
13.35.93.12
13.35.93.125
13.83.13.216
13.91.95.74
138.68.89.1
140.82.112.21
141.95.92.64
142.229.226.127
142.229.246.30
142.250.196.104
142.250.196.110
142.250.207.46
143.166.136.12
143.166.30.172
145.239.35.18
146.75.112.157
146.75.112.159
150.171.22.11
150.171.27.11
150.171.28.11
151.101.0.81
151.101.1.194
151.101.1.224
151.101.128.81
151.101.129.194
151.101.129.224
151.101.131.42
151.101.192.81
151.101.193.194
151.101.193.224
151.101.195.1
151.101.195.42
151.101.3.1
151.101.3.42
151.101.64.81
151.101.65.194
151.101.65.224
151.101.67.42
157.240.31.35
16.15.178.207
16.15.181.233
160.79.104.10
161.35.220.135
162.55.241.153
165.22.91.219
172.105.192.79
172.105.69.103
172.233.219.123
172.233.219.49
172.233.219.78
172.237.146.25
172.237.146.38
172.237.146.8
178.248.237.68
179.43.150.83
179.43.151.32
179.43.166.40
18.154.132.120
18.154.132.30
18.154.132.61
18.154.132.68
18.154.132.83
18.154.132.86
18.154.132.87
18.154.132.9
18.154.144.104
18.154.144.18
18.154.144.70
18.154.144.88
18.158.198.204
18.164.124.48
18.164.124.69
18.164.124.80
18.164.124.92
18.173.219.124
18.173.219.5
18.173.219.85
18.173.219.98
18.192.161.188
18.192.252.77
18.192.94.166
18.193.221.227
18.197.250.227
18.197.5.201
18.207.85.246
18.238.80.11
18.238.80.111
18.238.80.17
18.238.80.20
18.238.80.43
18.238.80.53
18.238.80.73
18.238.80.84
18.64.122.111
18.64.122.116
18.64.122.55
18.64.122.75
185.110.92.40
185.110.92.41
185.155.96.196
185.70.42.25
185.70.42.42
185.81.128.108
190.115.31.47
192.0.66.233
192.104.24.52
192.124.249.107
194.55.26.46
194.55.30.46
195.12.177.96
195.123.208.131
195.16.73.95
195.245.213.251
195.245.213.252
198.148.79.54
198.202.211.1
199.83.44.71
20.27.177.116
204.79.197.200
204.8.99.144
204.8.99.146
212.53.146.29
212.58.244.129
212.58.244.210
212.58.249.206
212.58.249.207
213.166.70.101
213.183.31.3
217.140.110.36
23.192.45.250
23.192.45.251
23.192.46.10
23.192.46.16
23.192.46.17
23.192.46.18
23.192.46.19
23.192.46.8
23.192.46.9
23.192.47.66
23.192.47.88
23.208.232.54
23.219.68.213
23.35.101.86
23.36.17.19
23.36.17.218
23.39.216.112
23.53.3.132
23.53.3.137
23.53.3.138
3.120.236.241
3.122.12.167
3.163.158.102
3.163.158.51
3.163.158.77
3.163.158.83
3.163.165.106
3.163.165.108
3.163.165.117
3.163.165.21
3.163.165.58
3.163.165.65
3.163.165.67
3.163.165.77
3.165.160.119
3.165.160.125
3.165.160.82
3.165.160.93
3.168.122.102
3.168.122.30
3.168.122.70
3.168.122.96
3.168.2.124
3.168.2.34
3.168.2.45
3.168.2.73
3.168.73.124
3.168.73.14
3.168.73.40
3.168.73.5
3.219.7.240
3.70.4.80
3.72.128.20
3.93.103.92
3.93.83.52
31.13.82.174
31.13.82.36
31.13.82.52
34.120.127.130
34.172.121.65
34.193.227.236
34.210.63.15
34.217.249.79
34.240.120.87
34.78.67.165
34.8.0.82
34.96.84.62
35.156.83.59
35.157.126.60
35.157.160.164
35.167.41.134
35.186.224.24
35.212.37.82
37.1.201.40
37.220.83.128
44.227.138.182
44.234.232.238
44.237.234.25
44.241.61.155
44.242.60.85
45.137.66.127
5.61.53.100
5.9.141.28
50.112.202.115
51.15.27.55
52.13.171.212
52.13.57.203
52.175.140.176
52.209.241.99
52.216.60.56
52.217.132.208
52.217.174.112
52.217.223.32
52.217.234.232
52.26.244.222
52.29.75.203
52.32.28.26
52.33.95.61
52.49.216.222
52.50.101.102
52.57.208.252
52.72.30.131
52.84.20.107
52.84.20.112
52.84.20.55
52.84.20.75
52.88.5.90
54.144.73.197
54.163.108.212
54.167.157.63
54.167.246.209
54.221.253.164
54.231.200.248
54.235.8.174
54.247.109.89
54.68.22.26
54.86.126.30
63.34.30.85
64.227.45.125
66.254.114.41
67.22.51.32
67.22.51.33
67.22.51.34
67.22.51.35
67.22.51.36
67.22.51.37
67.22.51.38
67.22.51.39
69.55.53.168
69.55.53.169
69.55.53.170
69.55.53.171
69.55.53.172
72.163.4.185
75.2.124.44
76.223.63.197
77.37.83.161
77.86.162.1
77.86.162.2
78.46.102.85
82.221.104.145
84.15.66.97
84.246.85.45
87.245.208.97
91.108.98.62
91.200.40.44
91.216.218.44
94.130.182.82
95.216.145.1
99.80.51.179
99.83.136.94
99.84.234.112
99.84.234.128
99.84.234.31
99.84.234.42
99.84.234.5
99.84.234.61
99.84.234.89
99.84.234.96

462
main.py
View File

@@ -3,16 +3,129 @@ import asyncio
import configparser import configparser
import ipaddress import ipaddress
import os import os
from asyncio import Semaphore import time
from collections import defaultdict from collections import defaultdict, deque
from typing import Dict, List, Set, Tuple, Optional from typing import Dict, List, Set, Tuple, Optional
import dns.asyncresolver import dns.asyncresolver
import httpx import httpx
from colorama import Fore, Style, init from colorama import Fore, Style, init
from tqdm import tqdm
init(autoreset=True) init(autoreset=True)
class ProgressTracker:
def __init__(self, total: int, stats: Dict, unique_ips_set: Set[str],
num_dns_servers: int = 1, rate_limit: int = 10, domains_count: int = 0):
self.total = total
self.stats = stats
self.unique_ips = unique_ips_set
self.pbar = None
self.lock = asyncio.Lock()
self.num_dns_servers = num_dns_servers
self.rate_limit = rate_limit
self.domains_count = domains_count
self.effective_rate = num_dns_servers * rate_limit
self.start_time = time.time()
def start(self):
self.pbar = tqdm(
total=self.total,
bar_format='[{bar:40}] {percentage:3.1f}% | Прошло: {elapsed} | Осталось (примерно): {desc}',
unit=' запр',
ncols=120,
leave=True,
mininterval=0,
desc='расчет...'
)
async def update_progress(self):
if self.pbar:
async with self.lock:
processed = self.stats.get('total_domains_processed', 0)
remaining_time = self.calculate_remaining_time()
self.pbar.n = processed
self.pbar.set_description_str(remaining_time)
self.pbar.refresh()
def format_time(self, seconds: float) -> str:
if seconds < 0:
seconds = 0
mins = int(seconds // 60)
secs = int(seconds % 60)
return f"{mins:02d}:{secs:02d}"
def calculate_remaining_time(self) -> str:
processed = self.stats.get('total_domains_processed', 0)
remaining = self.total - processed
if self.effective_rate > 0:
time_remaining = remaining / self.effective_rate
return self.format_time(time_remaining)
return "00:00"
def close(self):
if self.pbar:
self.pbar.n = self.total
self.pbar.refresh()
self.pbar.close()
elapsed = time.time() - self.stats['start_time']
total = self.stats['total_domains']
processed = self.stats['total_domains_processed']
errors = self.stats['domain_errors']
error_pct = (errors / total * 100) if total > 0 else 0
total_ips_found = len(self.unique_ips) + self.stats['null_ips_count'] + self.stats.get('cloudflare_ips_count', 0)
null_pct = (self.stats['null_ips_count'] / total_ips_found * 100) if total_ips_found > 0 else 0
cf_pct = (self.stats.get('cloudflare_ips_count', 0) / total_ips_found * 100) if total_ips_found > 0 else 0
print(f"\n{yellow('Проверка завершена.')}")
print(f"{Style.BRIGHT}Всего обработано DNS имен:{Style.RESET_ALL} {processed} из {total}")
print(f"{Style.BRIGHT}Разрешено уникальных IP-адресов:{Style.RESET_ALL} {len(self.unique_ips)}")
print(f"{Style.BRIGHT}Ошибок разрешения доменов:{Style.RESET_ALL} {errors} ({error_pct:.1f}%)")
if self.stats['null_ips_count'] > 0:
print(f"{Style.BRIGHT}Исключено IP-адресов 'заглушек':{Style.RESET_ALL} {self.stats['null_ips_count']} ({null_pct:.1f}%)")
if self.stats.get('cloudflare_ips_count', 0) > 0:
print(f"{Style.BRIGHT}Исключено IP-адресов Cloudflare:{Style.RESET_ALL} {self.stats['cloudflare_ips_count']} ({cf_pct:.1f}%)")
class PeriodicProgressUpdater:
def __init__(self, progress_tracker: ProgressTracker, stats: Dict):
self.progress_tracker = progress_tracker
self.stats = stats
self.is_running = False
self.task = None
async def start(self):
if not self.is_running:
self.is_running = True
self.task = asyncio.create_task(self._periodic_update())
async def stop(self):
if self.is_running:
self.is_running = False
if self.task:
self.task.cancel()
try:
await self.task
except asyncio.CancelledError:
pass
async def _periodic_update(self):
await asyncio.sleep(2)
while self.is_running:
try:
await self.progress_tracker.update_progress()
await asyncio.sleep(2)
except asyncio.CancelledError:
break
except Exception as e:
print(f"Error in periodic progress update: {e}")
await asyncio.sleep(2)
def yellow(text): def yellow(text):
return f"{Fore.YELLOW}{text}{Style.RESET_ALL}" return f"{Fore.YELLOW}{text}{Style.RESET_ALL}"
@@ -60,7 +173,7 @@ def read_config(cfg_file):
config = config['DomainMapper'] config = config['DomainMapper']
service = config.get('service') or '' service = config.get('service') or ''
request_limit = int(config.get('threads') or 15) rate_limit = int(config.get('rate_limit') or 50)
filename = config.get('filename') or 'domain-ip-resolve.txt' filename = config.get('filename') or 'domain-ip-resolve.txt'
cloudflare = config.get('cloudflare') or '' cloudflare = config.get('cloudflare') or ''
filetype = config.get('filetype') or '' filetype = config.get('filetype') or ''
@@ -79,7 +192,7 @@ def read_config(cfg_file):
print(f"{yellow(f'Загружена конфигурация из {cfg_file}:')}") print(f"{yellow(f'Загружена конфигурация из {cfg_file}:')}")
print(f"{Style.BRIGHT}Сервисы для проверки:{Style.RESET_ALL} {service if service else 'спросить у пользователя'}") print(f"{Style.BRIGHT}Сервисы для проверки:{Style.RESET_ALL} {service if service else 'спросить у пользователя'}")
print(f"{Style.BRIGHT}Использовать DNS сервер:{Style.RESET_ALL} {dns_server_indices if dns_server_indices else 'спросить у пользователя'}") print(f"{Style.BRIGHT}Использовать DNS сервер:{Style.RESET_ALL} {dns_server_indices if dns_server_indices else 'спросить у пользователя'}")
print(f"{Style.BRIGHT}Количество одновременных запросов к одному DNS серверу:{Style.RESET_ALL} {request_limit}") print(f"{Style.BRIGHT}Лимит запросов к каждому DNS серверу (запросов/сек):{Style.RESET_ALL} {rate_limit}")
print(f"{Style.BRIGHT}Фильтрация IP-адресов Cloudflare:{Style.RESET_ALL} {'включена' if cloudflare in ['y', 'yes'] else 'выключена' if cloudflare in ['n', 'no'] else 'спросить у пользователя'}") print(f"{Style.BRIGHT}Фильтрация IP-адресов Cloudflare:{Style.RESET_ALL} {'включена' if cloudflare in ['y', 'yes'] else 'выключена' if cloudflare in ['n', 'no'] else 'спросить у пользователя'}")
print(f"{Style.BRIGHT}Агрегация IP-адресов:{Style.RESET_ALL} {'mix режим /24 (255.255.255.0) + /32 (255.255.255.255)' if subnet == 'mix' else 'до /16 подсети (255.255.0.0)' if subnet == '16' else 'до /24 подсети (255.255.255.0)' if subnet == '24' else 'выключена' if subnet in ['n', 'no'] else 'спросить у пользователя'}") print(f"{Style.BRIGHT}Агрегация IP-адресов:{Style.RESET_ALL} {'mix режим /24 (255.255.255.0) + /32 (255.255.255.255)' if subnet == 'mix' else 'до /16 подсети (255.255.0.0)' if subnet == '16' else 'до /24 подсети (255.255.255.0)' if subnet == '24' else 'выключена' if subnet in ['n', 'no'] else 'спросить у пользователя'}")
print(f"{Style.BRIGHT}Формат сохранения:{Style.RESET_ALL} {'только IP' if filetype == 'ip' else 'Linux route' if filetype == 'unix' else 'CIDR-нотация' if filetype == 'cidr' else 'Windows route' if filetype == 'win' else 'Mikrotik CLI' if filetype == 'mikrotik' else 'open vpn' if filetype == 'ovpn' else 'Keenetic CLI' if filetype == 'keenetic' else 'Wireguard' if filetype == 'wireguard' else 'спросить у пользователя'}") print(f"{Style.BRIGHT}Формат сохранения:{Style.RESET_ALL} {'только IP' if filetype == 'ip' else 'Linux route' if filetype == 'unix' else 'CIDR-нотация' if filetype == 'cidr' else 'Windows route' if filetype == 'win' else 'Mikrotik CLI' if filetype == 'mikrotik' else 'open vpn' if filetype == 'ovpn' else 'Keenetic CLI' if filetype == 'keenetic' else 'Wireguard' if filetype == 'wireguard' else 'спросить у пользователя'}")
@@ -96,11 +209,11 @@ def read_config(cfg_file):
print(f"{Style.BRIGHT}Локальный список платформ:{Style.RESET_ALL} {'включен' if str(localplatform).strip().lower() in ('yes', 'y') else 'выключен'}") print(f"{Style.BRIGHT}Локальный список платформ:{Style.RESET_ALL} {'включен' if str(localplatform).strip().lower() in ('yes', 'y') else 'выключен'}")
print(f"{Style.BRIGHT}Локальный список DNS серверов:{Style.RESET_ALL} {'включен' if str(localdns).strip().lower() in ('yes', 'y') else 'выключен'}") print(f"{Style.BRIGHT}Локальный список DNS серверов:{Style.RESET_ALL} {'включен' if str(localdns).strip().lower() in ('yes', 'y') else 'выключен'}")
return service, request_limit, filename, cloudflare, filetype, gateway, run_command, dns_server_indices, mk_list_name, subnet, ken_gateway, localplatform, localdns, mk_comment return service, rate_limit, filename, cloudflare, filetype, gateway, run_command, dns_server_indices, mk_list_name, subnet, ken_gateway, localplatform, localdns, mk_comment
except Exception as e: except Exception as e:
print(f"{yellow(f'Ошибка загрузки {cfg_file}:')} {e}\n{Style.BRIGHT}Используются настройки 'по умолчанию'.{Style.RESET_ALL}") print(f"{yellow(f'Ошибка загрузки {cfg_file}:')} {e}\n{Style.BRIGHT}Используются настройки 'по умолчанию'.{Style.RESET_ALL}")
return '', 20, 'domain-ip-resolve.txt', '', '', '', '', [], '', '', '', '', '', 'off' return '', 50, 'domain-ip-resolve.txt', '', '', '', '', [], '', '', '', '', '', 'off'
def gateway_input(gateway): def gateway_input(gateway):
if not gateway: if not gateway:
@@ -116,11 +229,107 @@ def ken_gateway_input(ken_gateway):
else: else:
return ken_gateway return ken_gateway
def get_semaphore(request_limit): class DNSServerWorker:
return defaultdict(lambda: Semaphore(request_limit)) def __init__(self, name: str, nameservers: List[str], rate_limit: int = 10, stats_lock=None):
self.name = name
self.nameservers = nameservers
self.rate_limit = rate_limit
self.queue = asyncio.Queue()
self.request_times = deque()
self.results = []
self.stats = {
'processed': 0,
'errors': 0,
'success': 0
}
self.stats_lock = stats_lock or asyncio.Lock()
self.rate_limit_lock = asyncio.Lock()
def init_semaphores(request_limit): async def add_domain(self, domain: str):
return get_semaphore(request_limit) await self.queue.put(domain)
async def _enforce_rate_limit(self):
async with self.rate_limit_lock:
now = time.monotonic()
while self.request_times and now - self.request_times[0] >= 1.0:
self.request_times.popleft()
if len(self.request_times) >= self.rate_limit:
sleep_time = 1.0 - (now - self.request_times[0])
if sleep_time > 0:
await asyncio.sleep(sleep_time)
now = time.monotonic()
while self.request_times and now - self.request_times[0] >= 1.0:
self.request_times.popleft()
self.request_times.append(now)
async def process_queue(self, global_stats: Dict[str, int]):
resolver = dns.asyncresolver.Resolver()
resolver.nameservers = self.nameservers
resolver.timeout = 10.0
resolver.lifetime = 15.0
domains = []
while not self.queue.empty():
domain = await self.queue.get()
domains.append(domain)
async def process_single_domain(domain):
await self._enforce_rate_limit()
try:
response = await resolver.resolve(domain)
ips = [ip.address for ip in response]
async with self.stats_lock:
global_stats['total_domains_processed'] += 1
self.stats['processed'] += 1
self.stats['success'] += 1
return ips
except dns.resolver.NoNameservers:
async with self.stats_lock:
global_stats['total_domains_processed'] += 1
global_stats['domain_errors'] += 1
self.stats['processed'] += 1
self.stats['errors'] += 1
return []
except dns.resolver.Timeout:
async with self.stats_lock:
global_stats['total_domains_processed'] += 1
global_stats['domain_errors'] += 1
self.stats['processed'] += 1
self.stats['errors'] += 1
return []
except dns.resolver.NXDOMAIN:
async with self.stats_lock:
global_stats['total_domains_processed'] += 1
global_stats['domain_errors'] += 1
self.stats['processed'] += 1
self.stats['errors'] += 1
return []
except dns.resolver.NoAnswer:
async with self.stats_lock:
global_stats['total_domains_processed'] += 1
global_stats['domain_errors'] += 1
self.stats['processed'] += 1
self.stats['errors'] += 1
return []
except Exception:
async with self.stats_lock:
global_stats['total_domains_processed'] += 1
global_stats['domain_errors'] += 1
self.stats['processed'] += 1
self.stats['errors'] += 1
return []
results = await asyncio.gather(*[process_single_domain(domain) for domain in domains], return_exceptions=True)
for result in results:
if isinstance(result, list):
self.results.extend(result)
async def load_urls(url: str) -> Dict[str, str]: async def load_urls(url: str) -> Dict[str, str]:
try: try:
@@ -224,73 +433,49 @@ async def load_dns_names(url_or_file: str) -> List[str]:
print(f"Ошибка при чтении файла {url_or_file}: {e}") print(f"Ошибка при чтении файла {url_or_file}: {e}")
return [] return []
async def resolve_domain_batch(domains: List[str], resolver: dns.asyncresolver.Resolver, async def resolve_dns_with_workers(service: str, dns_names: List[str],
semaphore: Semaphore, dns_server_name: str, dns_servers: List[Tuple[str, List[str]]],
stats: Dict[str, int], cloudflare_ips: Set[str], cloudflare_ips: Set[str], unique_ips_all_services: Set[str],
include_cloudflare: bool) -> List[str]: stats: Dict[str, int], include_cloudflare: bool,
async with semaphore: rate_limit: int, stats_lock: asyncio.Lock = None) -> str:
resolved_ips = []
for domain in domains:
try:
stats['total_domains_processed'] += 1
response = await resolver.resolve(domain)
ips = [ip.address for ip in response]
for ip_address in ips:
if ip_address in ('127.0.0.1', '0.0.0.0') or ip_address in resolver.nameservers:
stats['null_ips_count'] += 1
elif include_cloudflare and ip_address in cloudflare_ips:
stats['cloudflare_ips_count'] += 1
else:
resolved_ips.append(ip_address)
print(f"{Fore.BLUE}{domain} IP-адрес: {ip_address} - {dns_server_name}{Style.RESET_ALL}")
except Exception:
stats['domain_errors'] += 1
return resolved_ips
async def resolve_dns_optimized(service: str, dns_names: List[str],
dns_servers: List[Tuple[str, List[str]]],
cloudflare_ips: Set[str], unique_ips_all_services: Set[str],
semaphore_dict: Dict, stats: Dict[str, int],
include_cloudflare: bool, batch_size: int = 50) -> str:
try: try:
print(f"{Fore.YELLOW}Загрузка DNS имен платформы {service}...{Style.RESET_ALL}") if stats_lock is None:
stats_lock = asyncio.Lock()
domain_batches = [dns_names[i:i + batch_size] for i in range(0, len(dns_names), batch_size)]
workers = []
tasks = [] for server_name, servers in dns_servers:
worker = DNSServerWorker(server_name, servers, rate_limit, stats_lock)
for batch in domain_batches: workers.append(worker)
for server_name, servers in dns_servers:
resolver = dns.asyncresolver.Resolver() for domain in dns_names:
resolver.nameservers = servers for worker in workers:
await worker.add_domain(domain)
tasks.append(resolve_domain_batch(
batch, resolver, semaphore_dict[server_name], tasks = [worker.process_queue(stats) for worker in workers]
server_name, stats, cloudflare_ips, include_cloudflare
)) await asyncio.gather(*tasks)
max_concurrent_tasks = min(len(tasks), 100) all_nameservers = set()
for _, servers in dns_servers:
results = [] all_nameservers.update(servers)
for i in range(0, len(tasks), max_concurrent_tasks):
batch_tasks = tasks[i:i + max_concurrent_tasks]
batch_results = await asyncio.gather(*batch_tasks, return_exceptions=True)
for result in batch_results:
if not isinstance(result, Exception):
results.extend(result)
unique_ips_current_service = set() unique_ips_current_service = set()
for ip_address in results: for worker in workers:
if ip_address not in unique_ips_all_services: for ip_address in worker.results:
unique_ips_current_service.add(ip_address) if ip_address in ('127.0.0.1', '0.0.0.0') or ip_address in all_nameservers:
unique_ips_all_services.add(ip_address) stats['null_ips_count'] += 1
continue
if include_cloudflare and ip_address in cloudflare_ips:
stats['cloudflare_ips_count'] += 1
continue
if ip_address not in unique_ips_all_services:
unique_ips_current_service.add(ip_address)
unique_ips_all_services.add(ip_address)
return '\n'.join(sorted(unique_ips_current_service)) + '\n' if unique_ips_current_service else '' return '\n'.join(sorted(unique_ips_current_service)) + '\n' if unique_ips_current_service else ''
except Exception as e: except Exception as e:
print(f"Не удалось сопоставить IP адреса {service} его доменным именам: {e}") print(f"Не удалось сопоставить IP адреса {service} его доменным именам: {e}")
return "" return ""
@@ -465,6 +650,46 @@ def group_ips_in_subnets_optimized(filename: str, subnet: str):
except Exception as e: except Exception as e:
print(f"Ошибка при обработке файла: {e}") print(f"Ошибка при обработке файла: {e}")
def split_file_by_lines(filename: str, max_lines: int = 999):
try:
with open(filename, 'r', encoding='utf-8') as file:
lines = file.readlines()
total_lines = len(lines)
if total_lines <= max_lines:
return False
base_name = filename.rsplit('.', 1)[0] if '.' in filename else filename
extension = '.' + filename.rsplit('.', 1)[1] if '.' in filename else '.txt'
num_parts = (total_lines + max_lines - 1) // max_lines
print(f"\n{Style.BRIGHT}Результаты сохранены в файлы:{Style.RESET_ALL}")
for part in range(num_parts):
start_index = part * max_lines
end_index = min((part + 1) * max_lines, total_lines)
part_filename = f"{base_name}_p{part + 1}{extension}"
with open(part_filename, 'w', encoding='utf-8') as file:
file.writelines(lines[start_index:end_index])
print(f"{Style.BRIGHT}{part_filename} ({end_index - start_index} строк){Style.RESET_ALL}")
print(f"{Style.BRIGHT}Разделение завершено. Создано {num_parts} частей{Style.RESET_ALL}")
try:
os.remove(filename)
except Exception as e:
print(f"{red('Не удалось удалить исходный файл:')} {e}")
return True
except Exception as e:
print(f"{red('Ошибка при разделении файла:')} {e}")
return False
def process_file_format(filename, filetype, gateway, selected_service, mk_list_name, mk_comment, subnet, ken_gateway): def process_file_format(filename, filetype, gateway, selected_service, mk_list_name, mk_comment, subnet, ken_gateway):
def read_file(filename): def read_file(filename):
try: try:
@@ -554,6 +779,11 @@ def process_file_format(filename, filetype, gateway, selected_service, mk_list_n
if filetype.lower() in formatters: if filetype.lower() in formatters:
write_file(filename, ips, formatters[filetype.lower()]) write_file(filename, ips, formatters[filetype.lower()])
if filetype.lower() == 'keenetic bat':
return split_file_by_lines(filename, max_lines=999)
return False
async def main(): async def main():
parser = argparse.ArgumentParser(description="DNS resolver script with custom config file.") parser = argparse.ArgumentParser(description="DNS resolver script with custom config file.")
parser.add_argument( parser.add_argument(
@@ -566,7 +796,7 @@ async def main():
try: try:
config_file = args.config config_file = args.config
(service, request_limit, filename, cloudflare, filetype, gateway, run_command, (service, rate_limit, filename, cloudflare, filetype, gateway, run_command,
dns_server_indices, mk_list_name, subnet, ken_gateway, localplatform, dns_server_indices, mk_list_name, subnet, ken_gateway, localplatform,
localdns, mk_comment) = read_config(config_file) localdns, mk_comment) = read_config(config_file)
@@ -596,37 +826,70 @@ async def main():
cloudflare_ips = set() cloudflare_ips = set()
unique_ips_all_services = set() unique_ips_all_services = set()
semaphore = init_semaphores(request_limit)
stats = { stats = {
'null_ips_count': 0, 'null_ips_count': 0,
'cloudflare_ips_count': 0, 'cloudflare_ips_count': 0,
'total_domains_processed': 0, 'total_domains_processed': 0,
'domain_errors': 0 'domain_errors': 0
} }
total_domains = 0
for service_name in selected_services:
if service_name == 'Custom DNS list':
total_domains += len(local_dns_names)
else:
url_or_file = urls[service_name]
print(f"{Style.BRIGHT}Загрузка DNS имен платформы{Style.RESET_ALL} {service_name}...")
dns_names = await load_dns_names(url_or_file)
total_domains += len(dns_names)
domains_count = total_domains
total_domains *= len(selected_dns_servers)
stats['total_domains'] = total_domains
stats['start_time'] = time.time()
print(f"{Style.BRIGHT}Загружено {domains_count} DNS имен.{Style.RESET_ALL}\n{yellow('Резолвинг...')}")
progress_tracker = ProgressTracker(
total=total_domains,
stats=stats,
unique_ips_set=unique_ips_all_services,
num_dns_servers=len(selected_dns_servers),
rate_limit=rate_limit,
domains_count=domains_count
)
progress_tracker.start()
stats_lock = asyncio.Lock()
periodic_updater = PeriodicProgressUpdater(progress_tracker, stats)
await periodic_updater.start()
tasks = [] tasks = []
for service_name in selected_services: for service_name in selected_services:
if service_name == 'Custom DNS list': if service_name == 'Custom DNS list':
tasks.append(resolve_dns_optimized( tasks.append(resolve_dns_with_workers(
service_name, local_dns_names, selected_dns_servers, service_name, local_dns_names, selected_dns_servers,
cloudflare_ips, unique_ips_all_services, semaphore, cloudflare_ips, unique_ips_all_services,
stats, include_cloudflare stats, include_cloudflare, rate_limit,
stats_lock
)) ))
else: else:
url_or_file = urls[service_name] url_or_file = urls[service_name]
dns_names = await load_dns_names(url_or_file) dns_names = await load_dns_names(url_or_file)
if dns_names: if dns_names:
tasks.append(resolve_dns_optimized( tasks.append(resolve_dns_with_workers(
service_name, dns_names, selected_dns_servers, service_name, dns_names, selected_dns_servers,
cloudflare_ips, unique_ips_all_services, semaphore, cloudflare_ips, unique_ips_all_services,
stats, include_cloudflare stats, include_cloudflare, rate_limit,
stats_lock
)) ))
if tasks: if tasks:
results = await asyncio.gather(*tasks, return_exceptions=True) results = await asyncio.gather(*tasks, return_exceptions=True)
with open(filename, 'w', encoding='utf-8') as file: with open(filename, 'w', encoding='utf-8') as file:
for result in results: for result in results:
if isinstance(result, str) and result.strip(): if isinstance(result, str) and result.strip():
@@ -635,32 +898,31 @@ async def main():
with open(filename, 'w', encoding='utf-8') as file: with open(filename, 'w', encoding='utf-8') as file:
pass pass
print(f"\n{yellow('Проверка завершена.')}") await periodic_updater.stop()
print(f"{Style.BRIGHT}Всего обработано DNS имен:{Style.RESET_ALL} {stats['total_domains_processed']}") progress_tracker.close()
print(f"{Style.BRIGHT}Разрешено IP-адресов из DNS имен:{Style.RESET_ALL} {len(unique_ips_all_services)}")
print(f"{Style.BRIGHT}Ошибок разрешения доменов:{Style.RESET_ALL} {stats['domain_errors']}")
if stats['null_ips_count'] > 0:
print(f"{Style.BRIGHT}Исключено IP-адресов 'заглушек':{Style.RESET_ALL} {stats['null_ips_count']}")
if include_cloudflare:
print(f"{Style.BRIGHT}Исключено IP-адресов Cloudflare:{Style.RESET_ALL} {stats['cloudflare_ips_count']}")
print(f"{Style.BRIGHT}Использовались DNS серверы:{Style.RESET_ALL} " + ', '.join(
[f'{pair[0]} ({", ".join(pair[1])})' for pair in selected_dns_servers]))
print(f"{Style.BRIGHT}Использовались DNS серверы:{Style.RESET_ALL} " + ', '.join(
[pair[0] for pair in selected_dns_servers]))
print(f"\n{yellow('Обработка результатов...')}")
subnet = subnet_input(subnet) subnet = subnet_input(subnet)
if subnet != '32': if subnet != '32':
group_ips_in_subnets_optimized(filename, subnet) group_ips_in_subnets_optimized(filename, subnet)
process_file_format(filename, filetype, gateway, selected_services, mk_list_name, mk_comment, subnet, ken_gateway) file_was_split = process_file_format(filename, filetype, gateway, selected_services, mk_list_name, mk_comment, subnet, ken_gateway)
if run_command: if run_command:
print("\nВыполнение команды после завершения скрипта...") print("\nВыполнение команды после завершения скрипта...")
os.system(run_command) os.system(run_command)
else: else:
print(f"\n{Style.BRIGHT}Результаты сохранены в файл:{Style.RESET_ALL}", filename) if not file_was_split:
print(f"\n{Style.BRIGHT}Результаты сохранены в файл:{Style.RESET_ALL}", filename)
if os.name == 'nt': if os.name == 'nt':
input(f"Нажмите {green('Enter')} для выхода...") input(f"Нажмите {green('Enter')} для выхода...")
print(f"\n{Style.BRIGHT}Если есть желание, можно угостить автора чашечкой какао:{Style.RESET_ALL} {green('https://boosty.to/ground_zerro')}")
except KeyboardInterrupt: except KeyboardInterrupt:
print(f"\n{red('Программа прервана пользователем')}") print(f"\n{red('Программа прервана пользователем')}")
except Exception as e: except Exception as e:
@@ -674,4 +936,4 @@ if __name__ == "__main__":
except KeyboardInterrupt: except KeyboardInterrupt:
print(f"\n{red('Программа прервана пользователем')}") print(f"\n{red('Программа прервана пользователем')}")
except Exception as e: except Exception as e:
print(f"\n{red('Критическая ошибка:')} {e}") print(f"\n{red('Критическая ошибка:')} {e}")

View File

@@ -1,7 +1,7 @@
configparser~=7.0.1 dnspython>=2.6.1
ipaddress~=1.0.23 httpx>=0.27.0
dnspython~=2.6.1 colorama>=0.4.6
httpx~=0.27.0 tqdm>=4.66.0
colorama~=0.4.6
requests~=2.31.0 requests>=2.31.0
beautifulsoup4~=4.12.3 beautifulsoup4>=4.12.3

View File

@@ -71,17 +71,25 @@
### Функции ### Функции
- Загрузка списка IP-адресов из файла. - Извлечение IP-адресов из файла (файл может содержать любой текст - IP автоматически извлекаются).
- Агрегация IP-адресов в подсети с масками `/16`, `/24`, или объединение нескольких подсетей. - Исключение IP-адресов Cloudflare из итогового списка (опционально).
- Исключение IP-адресов Cloudflare из итогового списка (при необходимости). - Агрегация IP-адресов в подсети:
- `/16` (255.255.0.0)
- `/24` (255.255.255.0)
- Mix режим (`/24` + `/32`)
- Поддержка различных форматов маршрутизации: - Поддержка различных форматов маршрутизации:
- Windows (`route add`) - Только IP-адреса
- Unix (`ip route`) - Windows route (`route add`)
- Keenetic (`ip route` с интерфейсом) - Linux route (`ip route`)
- Mikrotik (`/ip firewall`) - Keenetic BAT (`route add` для bat-файлов)
- Keenetic CLI (`ip route` с интерфейсом)
- Mikrotik firewall (`/ip/firewall/address-list`)
- WireGuard - WireGuard
- OpenVPN - OpenVPN (`push "route"`)
- CIDR (с указанием маски) - CIDR нотация
- Автоматическое разделение больших файлов на части (для Keenetic BAT формата, max 999 строк).
- Удаление исходного файла после разделения на части.
- Проверка наличия входного файла с выводом инструкций при его отсутствии.
### Использование ### Использование
@@ -91,7 +99,14 @@
pip install -r requirements.txt pip install -r requirements.txt
``` ```
2. Поместите файл c IP-адресами `ip.txt` в корневую директорию проекта. Файл может содержать любой текст и IP-адреса в любом виде - лишнее будет убрано автоматически. 2. Создайте файл `ip.txt` в директории со скриптом и добавьте в него IP-адреса (по одному на строку) или любой текст содержащий IP-адреса.
Пример содержимого `ip.txt`:
```
192.168.1.1
10.0.0.1
Какой-то текст с IP: 172.16.0.1
```
3. Запустите скрипт: 3. Запустите скрипт:
@@ -99,7 +114,18 @@
python convert.py python convert.py
``` ```
4. Следуйте подсказкам на экране. 4. Следуйте интерактивным подсказкам на экране:
- Выберите, нужно ли исключить IP-адреса Cloudflare (1 - да, Enter - нет)
- Выберите агрегацию подсетей (1 - /16, 2 - /24, 3 - mix, Enter - без агрегации)
- Выберите формат сохранения (1-8 или Enter для простого списка IP)
- При необходимости укажите шлюз/интерфейс/имя списка
5. Результат будет сохранен в файл `ip.txt` (или в несколько файлов, если был выбран формат с автоматическим разделением).
### Примечания
- Если файл `ip.txt` не найден, скрипт выведет подробную инструкцию по его созданию и корректно завершится.
- Для формата Keenetic BAT файл автоматически разделяется на части по 999 строк, исходный файл удаляется.
## split ## split

View File

@@ -1,11 +1,12 @@
import asyncio import asyncio
import ipaddress import ipaddress
import os
import re import re
from collections import defaultdict
import httpx import httpx
from colorama import Fore, Style, init from colorama import Fore, Style, init
# Цвета
init(autoreset=True) init(autoreset=True)
@@ -28,12 +29,9 @@ def red(text):
def magneta(text): def magneta(text):
return f"{Fore.MAGENTA}{text}{Style.RESET_ALL}" return f"{Fore.MAGENTA}{text}{Style.RESET_ALL}"
def blue(text): def blue(text):
return f"{Fore.BLUE}{text}{Style.RESET_ALL}" return f"{Fore.BLUE}{text}{Style.RESET_ALL}"
# IP шлюза для win и unix
def gateway_input(gateway): def gateway_input(gateway):
if not gateway: if not gateway:
input_gateway = input(f"Укажите {green('IP шлюза')} или {green('имя интерфейса')}: ") input_gateway = input(f"Укажите {green('IP шлюза')} или {green('имя интерфейса')}: ")
@@ -41,8 +39,6 @@ def gateway_input(gateway):
else: else:
return gateway return gateway
# IP шлюза и имя интерфейса для keenetic
def ken_gateway_input(ken_gateway): def ken_gateway_input(ken_gateway):
if not ken_gateway: if not ken_gateway:
input_ken_gateway = input( input_ken_gateway = input(
@@ -51,8 +47,6 @@ def ken_gateway_input(ken_gateway):
else: else:
return ken_gateway return ken_gateway
# Загрузка IP-адресов cloudflare
async def get_cloudflare_ips(): async def get_cloudflare_ips():
try: try:
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
@@ -74,17 +68,22 @@ async def get_cloudflare_ips():
print("Ошибка при получении IP адресов Cloudflare:", e) print("Ошибка при получении IP адресов Cloudflare:", e)
return set() return set()
# Промт cloudflare фильтр
def check_include_cloudflare(cloudflare): def check_include_cloudflare(cloudflare):
if cloudflare in ['yes', 'y', 'no', 'n']: if cloudflare in ['yes', 'y', 'no', 'n']:
return cloudflare in ['yes', 'y'] return cloudflare in ['yes', 'y']
return input(f"\n{yellow('Исключить IP адреса Cloudflare из итогового списка?')}"
f"\n{green('yes')} - исключить"
f"\n{green('Enter')} - оставить: ").strip().lower() in ['yes', 'y']
user_input = input(
f"\n{yellow('Исключить IP адреса Cloudflare из итогового списка?')}"
f"\n1. исключить"
f"\n{green('Enter')} - оставить"
f"\nВаш выбор: "
).strip()
if user_input == '1':
return True
else:
return False
# комментарий для microtik firewall
def mk_list_name_input(mk_list_name): def mk_list_name_input(mk_list_name):
if not mk_list_name: if not mk_list_name:
input_mk_list_name = input(f"Введите {green('LIST_NAME')} для Mikrotik firewall: ") input_mk_list_name = input(f"Введите {green('LIST_NAME')} для Mikrotik firewall: ")
@@ -92,80 +91,117 @@ def mk_list_name_input(mk_list_name):
else: else:
return mk_list_name return mk_list_name
# Уплотняем имена сервисов
def comment(selected_service): def comment(selected_service):
return ",".join(["".join(word.title() for word in s.split()) for s in selected_service]) return ",".join(["".join(word.title() for word in s.split()) for s in selected_service])
# Промт на объединение IP в подсети
def subnet_input(subnet): def subnet_input(subnet):
if not subnet: if not subnet:
subnet = input( choice = input(
f"\n{yellow('Объединить IP-адреса в подсети?')} " f"\n{yellow('Объединить IP-адреса в подсети?')}"
f"\n{green('16')} - сократить до /16 (255.255.0.0)" f"\n1. сократить до {green('/16')} (255.255.0.0)"
f"\n{green('24')} - сократить до /24 (255.255.255.0)" f"\n2. сократить до {green('/24')} (255.255.255.0)"
f"\n{green('mix')} - сократить до /24 (255.255.255.0) и /32 (255.255.255.255)" f"\n3. сократить до {green('/24')} + {green('/32')} (255.255.255.0 и 255.255.255.255)"
f"\n{green('Enter')} - пропустить: " f"\n{green('Enter')} - пропустить"
).strip().lower() f"\nВаш выбор: "
).strip()
if choice == '1':
subnet = '16'
elif choice == '2':
subnet = '24'
elif choice == '3':
subnet = 'mix'
else:
subnet = '32'
return subnet if subnet in {'16', '24', 'mix'} else '32' return subnet if subnet in {'16', '24', 'mix'} else '32'
def group_ips_in_subnets_optimized(filename: str, subnet: str):
# Агрегация маршрутов
def group_ips_in_subnets(filename, subnet):
try: try:
with open(filename, 'r', encoding='utf-8') as file: with open(filename, 'r', encoding='utf-8') as file:
ips = {line.strip() for line in file if line.strip()} # Собираем уникальные IP адреса ips = {line.strip() for line in file if line.strip()}
subnets = set() subnets = set()
def process_ips(subnet): if subnet == "16":
for ip in ips: for ip in ips:
try: try:
if subnet == "16": network = ipaddress.IPv4Network(f"{ip}/16", strict=False)
# Преобразуем в /16 (два последних октета заменяются на 0.0) subnets.add(str(network.network_address))
network = ipaddress.IPv4Network(f"{ip}/16", strict=False) except ValueError:
subnets.add(f"{network.network_address}") continue
elif subnet == "24": print(f"{Style.BRIGHT}IP-адреса агрегированы до /16 подсети{Style.RESET_ALL}")
# Преобразуем в /24 (последний октет заменяется на 0)
network = ipaddress.IPv4Network(f"{ip}/24", strict=False)
subnets.add(f"{network.network_address}")
except ValueError as e:
print(f"Ошибка в IP адресе: {ip} - {e}")
if subnet in ["24", "16"]: elif subnet == "24":
process_ips(subnet) for ip in ips:
print(f"{Style.BRIGHT}IP-адреса агрегированы до /{subnet} подсети{Style.RESET_ALL}") try:
network = ipaddress.IPv4Network(f"{ip}/24", strict=False)
subnets.add(str(network.network_address))
except ValueError:
continue
print(f"{Style.BRIGHT}IP-адреса агрегированы до /24 подсети{Style.RESET_ALL}")
elif subnet == "mix": elif subnet == "mix":
octet_groups = {} octet_groups = defaultdict(list)
for ip in ips: for ip in ips:
key = '.'.join(ip.split('.')[:3]) # Группировка по первым трем октетам key = '.'.join(ip.split('.')[:3])
if key not in octet_groups:
octet_groups[key] = []
octet_groups[key].append(ip) octet_groups[key].append(ip)
# IP-адреса с совпадающими первыми тремя октетами for key, group in octet_groups.items():
network_24 = {key + '.0' for key, group in octet_groups.items() if if len(group) > 1:
len(group) > 1} # Базовый IP для /24 подсетей subnets.add(key + '.0')
# Удаляем IP с совпадающими первыми тремя октетами из множества else:
ips -= {ip for group in octet_groups.values() if len(group) > 1 for ip in group} subnets.update(group)
# Оставляем только IP без указания маски для /24 и одиночных IP
subnets.update(ips) # IP без маски для одиночных IP
subnets.update(network_24) # Базовые IP для /24 подсетей
print(f"{Style.BRIGHT}IP-адреса агрегированы до масок /24 и /32{Style.RESET_ALL}") print(f"{Style.BRIGHT}IP-адреса агрегированы до масок /24 и /32{Style.RESET_ALL}")
with open(filename, 'w', encoding='utf-8') as file: with open(filename, 'w', encoding='utf-8') as file:
for subnet in sorted(subnets): for subnet_ip in sorted(subnets, key=lambda x: ipaddress.IPv4Address(x.split('/')[0])):
file.write(subnet + '\n') file.write(subnet_ip + '\n')
except Exception as e: except Exception as e:
print(f"Ошибка при обработке файла: {e}") print(f"Ошибка при обработке файла: {e}")
def split_file_by_lines(filename: str, max_lines: int = 999):
try:
with open(filename, 'r', encoding='utf-8') as file:
lines = file.readlines()
# Выбор формата сохранения результатов total_lines = len(lines)
def process_file_format(filename, filetype, gateway, selected_service, mk_list_name, subnet, ken_gateway):
if total_lines <= max_lines:
return False
base_name = filename.rsplit('.', 1)[0] if '.' in filename else filename
extension = '.' + filename.rsplit('.', 1)[1] if '.' in filename else '.txt'
num_parts = (total_lines + max_lines - 1) // max_lines
print(f"\n{Style.BRIGHT}Результаты сохранены в файлы:{Style.RESET_ALL}")
for part in range(num_parts):
start_index = part * max_lines
end_index = min((part + 1) * max_lines, total_lines)
part_filename = f"{base_name}_p{part + 1}{extension}"
with open(part_filename, 'w', encoding='utf-8') as file:
file.writelines(lines[start_index:end_index])
print(f"{Style.BRIGHT}{part_filename} ({end_index - start_index} строк){Style.RESET_ALL}")
print(f"{Style.BRIGHT}Разделение завершено. Создано {num_parts} частей{Style.RESET_ALL}")
try:
os.remove(filename)
except Exception as e:
print(f"{red('Не удалось удалить исходный файл:')} {e}")
return True
except Exception as e:
print(f"{red('Ошибка при разделении файла:')} {e}")
return False
def process_file_format(filename, filetype, gateway, selected_service, mk_list_name, mk_comment, subnet, ken_gateway):
def read_file(filename): def read_file(filename):
try: try:
with open(filename, 'r', encoding='utf-8') as file: with open(filename, 'r', encoding='utf-8') as file:
@@ -182,73 +218,83 @@ def process_file_format(filename, filetype, gateway, selected_service, mk_list_n
else: else:
file.write('\n'.join(formatted_ips)) file.write('\n'.join(formatted_ips))
# Определение маски подсети
net_mask = subnet if subnet == "mix" else "255.255.0.0" if subnet == "16" else "255.255.255.0" if subnet == "24" else "255.255.255.255" net_mask = subnet if subnet == "mix" else "255.255.0.0" if subnet == "16" else "255.255.255.0" if subnet == "24" else "255.255.255.255"
if not filetype: if not filetype:
filetype = input(f""" user_input = input(f"""
{yellow('В каком формате сохранить файл?')} {yellow('В каком формате сохранить файл?')}
{green('win')} - route add {cyan('IP')} mask {net_mask} {cyan('GATEWAY')} 1. {green('win')} - route add {cyan('IP')} mask {net_mask} {cyan('GATEWAY')}
{green('unix')} - ip route {cyan('IP')}/{subnet} {cyan('GATEWAY')} 2. {green('unix')} - ip route {cyan('IP')}/{subnet} {cyan('GATEWAY')}
{green('keenetic')} - ip route {cyan('IP')}/{subnet} {cyan('GATEWAY GATEWAY_NAME')} auto !{comment(selected_service)} 3. {green('keenetic bat')} - route add {cyan('IP')} mask {net_mask} 0.0.0.0
{green('cidr')} - {cyan('IP')}/{subnet} 4. {green('keenetic cli')} - ip route {cyan('IP')}/{subnet} {cyan('GATEWAY GATEWAY_NAME')} auto !{comment(selected_service)}
{green('mikrotik')} - /ip/firewall/address-list add list={cyan("LIST_NAME")} comment="{comment(selected_service)}" address={cyan("IP")}/{subnet} 5. {green('cidr')} - {cyan('IP')}/{subnet}
{green('ovpn')} - push "route {cyan('IP')} {net_mask}" 6. {green('mikrotik')} - /ip/firewall/address-list add list={cyan("LIST_NAME")}{f' comment="{comment(selected_service)}"' if mk_comment != "off" else ""} address={cyan("IP")}/{subnet}
{green('wireguard')} - {cyan('IP')}/{subnet}, {cyan('IP')}/{subnet}, и т.д... 7. {green('ovpn')} - push "route {cyan('IP')} {net_mask}"
8. {green('wireguard')} - {cyan('IP')}/{subnet}, {cyan('IP')}/{subnet}, и т.д...
{green('Enter')} - {cyan('IP')} {green('Enter')} - {cyan('IP')}
Ваш выбор: """) Ваш выбор: """).strip()
mapping = {
'1': 'win',
'2': 'unix',
'3': 'keenetic bat',
'4': 'keenetic cli',
'5': 'cidr',
'6': 'mikrotik',
'7': 'ovpn',
'8': 'wireguard'
}
filetype = mapping.get(user_input, '')
ips = read_file(filename) ips = read_file(filename)
if not ips: if not ips:
return return
# Дополнительные запросы в зависимости от формата файла if filetype in ['win', 'unix']:
if filetype in ['win', 'unix']: # Запрашиваем IP шлюза для win и unix
gateway = gateway_input(gateway) gateway = gateway_input(gateway)
elif filetype == 'keenetic': # Запрашиваем IP шлюза и имя интерфейса для keenetic elif filetype == 'keenetic cli':
ken_gateway = ken_gateway_input(ken_gateway) ken_gateway = ken_gateway_input(ken_gateway)
elif filetype == 'mikrotik': # Запрашиваем ввод комментария для microtik firewall elif filetype == 'mikrotik':
mk_list_name = mk_list_name_input(mk_list_name) mk_list_name = mk_list_name_input(mk_list_name)
# обычный формат
formatters = { formatters = {
'win': lambda ip: f"route add {ip} mask {net_mask} {gateway}", 'win': lambda ip: f"route add {ip} mask {net_mask} {gateway}",
'unix': lambda ip: f"ip route {ip}/{subnet} {gateway}", 'unix': lambda ip: f"ip route {ip}/{subnet} {gateway}",
'keenetic': lambda ip: f"ip route {ip}/{subnet} {ken_gateway} auto !{comment(selected_service)}", 'keenetic bat': lambda ip: f"route add {ip} mask {net_mask} 0.0.0.0",
'keenetic cli': lambda ip: f"ip route {ip}/{subnet} {ken_gateway} auto !{comment(selected_service)}",
'cidr': lambda ip: f"{ip}/{subnet}", 'cidr': lambda ip: f"{ip}/{subnet}",
'ovpn': lambda ip: f'push "route {ip} {net_mask}"', 'ovpn': lambda ip: f'push "route {ip} {net_mask}"',
'mikrotik': lambda 'mikrotik': lambda ip: f'/ip/firewall/address-list add list={mk_list_name}' + (f' comment="{comment(selected_service)}"' if mk_comment != "off" else "") + f' address={ip}/{subnet}',
ip: f'/ip/firewall/address-list add list={mk_list_name} comment="{comment(selected_service)}" address={ip}/{subnet}',
'wireguard': lambda ip: f"{ip}/{subnet}" 'wireguard': lambda ip: f"{ip}/{subnet}"
} }
# mix формат
if subnet == "mix": if subnet == "mix":
if filetype.lower() == 'win': # Обработка для win if filetype in ['win', 'keenetic bat']:
mix_formatter = lambda ip: f"{ip.strip()} mask 255.255.255.0" if ip.endswith( mix_formatter = lambda ip: f"{ip.strip()} mask 255.255.255.0" if ip.endswith('.0') else f"{ip.strip()} mask 255.255.255.255"
'.0') else f"{ip.strip()} mask 255.255.255.255" elif filetype.lower() == 'ovpn':
elif filetype.lower() == 'ovpn': # Обработка для ovpn mix_formatter = lambda ip: f"{ip.strip()} 255.255.255.0" if ip.endswith('.0') else f"{ip.strip()} 255.255.255.255"
mix_formatter = lambda ip: f"{ip.strip()} 255.255.255.0" if ip.endswith( else:
'.0') else f"{ip.strip()} 255.255.255.255"
else: # Обработка для остальных форматов
mix_formatter = lambda ip: f"{ip.strip()}/24" if ip.endswith('.0') else f"{ip.strip()}/32" mix_formatter = lambda ip: f"{ip.strip()}/24" if ip.endswith('.0') else f"{ip.strip()}/32"
formatters.update({ formatters.update({
'win': lambda ip: f"route add {mix_formatter(ip)} {gateway}", 'win': lambda ip: f"route add {mix_formatter(ip)} {gateway}",
'unix': lambda ip: f"ip route {mix_formatter(ip)} {gateway}", 'unix': lambda ip: f"ip route {mix_formatter(ip)} {gateway}",
'keenetic': lambda ip: f"ip route {mix_formatter(ip)} {ken_gateway} auto !{comment(selected_service)}", 'keenetic bat': lambda ip: f"route add {mix_formatter(ip)} 0.0.0.0",
'keenetic cli': lambda ip: f"ip route {mix_formatter(ip)} {ken_gateway} auto !{comment(selected_service)}",
'cidr': lambda ip: f"{mix_formatter(ip)}", 'cidr': lambda ip: f"{mix_formatter(ip)}",
'ovpn': lambda ip: f'push "route {mix_formatter(ip)}"', 'ovpn': lambda ip: f'push "route {mix_formatter(ip)}"',
'mikrotik': lambda ip: f'/ip/firewall/address-list add list={mk_list_name} comment="{comment(selected_service)}" address={mix_formatter(ip)}', 'mikrotik': lambda ip: f'/ip/firewall/address-list add list={mk_list_name}' + (f' comment="{comment(selected_service)}"' if mk_comment != "off" else "") + f' address={mix_formatter(ip)}',
'wireguard': lambda ip: f"{mix_formatter(ip)}" 'wireguard': lambda ip: f"{mix_formatter(ip)}"
}) })
# Запись в файл
if filetype.lower() in formatters: if filetype.lower() in formatters:
write_file(filename, ips, formatters[filetype.lower()]) write_file(filename, ips, formatters[filetype.lower()])
if filetype.lower() == 'keenetic bat':
return split_file_by_lines(filename, max_lines=999)
return False
# Стартуем
async def main(): async def main():
filename = "ip.txt" filename = "ip.txt"
cloudflare = None cloudflare = None
@@ -257,42 +303,45 @@ async def main():
gateway = None gateway = None
selected_services = ["Service"] selected_services = ["Service"]
mk_list_name = None mk_list_name = None
mk_comment = 'off'
ken_gateway = None ken_gateway = None
if not os.path.exists(filename):
print(f"\n{red(f'Ошибка: файл {filename} не найден!')}")
print(f"{yellow('Инструкция:')}")
print(f"1. Создайте файл {green(filename)} в текущей директории")
print(f"2. Добавьте в него IP-адреса (по одному на строку) или текст содержащий IP-адреса")
print(f"3. Запустите скрипт снова")
return
ip_pattern = re.compile(r'\b(?:\d{1,3}\.){3}\d{1,3}\b') ip_pattern = re.compile(r'\b(?:\d{1,3}\.){3}\d{1,3}\b')
# Открываем файл и читаем строки
with open(filename, 'r') as file: with open(filename, 'r') as file:
# Создаем множество для хранения уникальных IP-адресов
ips = set() ips = set()
# Проходим по каждой строке файла
for line in file: for line in file:
# Ищем все IP-адреса в строке
found_ips = ip_pattern.findall(line) found_ips = ip_pattern.findall(line)
# Добавляем найденные IP-адреса в множество
ips.update(found_ips) ips.update(found_ips)
# Фильтр Cloudflare
include_cloudflare = check_include_cloudflare(cloudflare) include_cloudflare = check_include_cloudflare(cloudflare)
if include_cloudflare: # Загрузка IP-адресов Cloudflare if include_cloudflare:
cloudflare_ips = await get_cloudflare_ips() cloudflare_ips = await get_cloudflare_ips()
else: else:
cloudflare_ips = set() cloudflare_ips = set()
# Удаляем IP-адреса Cloudflare
ips -= cloudflare_ips ips -= cloudflare_ips
with open(filename, 'w', encoding='utf-8') as file: with open(filename, 'w', encoding='utf-8') as file:
for ip in sorted(ips): for ip in sorted(ips):
file.write(ip + '\n') file.write(ip + '\n')
# Группировка IP-адресов в подсети
subnet = subnet_input(subnet) subnet = subnet_input(subnet)
if subnet != '32': # Если не '32', вызываем функцию для агрегации if subnet != '32':
group_ips_in_subnets(filename, subnet) group_ips_in_subnets_optimized(filename, subnet)
process_file_format(filename, filetype, gateway, selected_services, mk_list_name, subnet, ken_gateway) file_was_split = process_file_format(filename, filetype, gateway, selected_services, mk_list_name, mk_comment, subnet, ken_gateway)
if not file_was_split:
print(f"\n{Style.BRIGHT}Результаты сохранены в файл:{Style.RESET_ALL} {filename}")
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -1,11 +0,0 @@
# Попытка перенести DomainMapper на WEB платформу для размещения желающими на собсвтенном хостинге.
Не уверен, что закончу начатое.
Предложения в виде **pull requests** приветствуются.
```
bash <(curl -s https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/refs/heads/main/web/web_install.sh)
```

View File

@@ -1,44 +0,0 @@
import os
import subprocess
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import uvicorn
# Определение модели для данных запроса
class RunScriptRequest(BaseModel):
config: str
userId: str
# Инициализация FastAPI приложения
app = FastAPI()
@app.post("/run")
async def run_script(request: RunScriptRequest):
config_content = request.config
user_id = request.userId
# Создание имени файла конфигурации
config_filename = f"config-id_{user_id}.ini"
try:
# Запись конфигурации в файл
with open(config_filename, 'w') as f:
f.write(config_content)
# Выполнение команды через subprocess
result = subprocess.run(
['python3', 'main.py', '-c', config_filename],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
# Возвращение результатов выполнения скрипта
return {"stdout": result.stdout, "stderr": result.stderr}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Ошибка: {str(e)}")
# Запуск приложения (для использования с Uvicorn)
if __name__ == "__main__":
# Запуск FastAPI с использованием Uvicorn
uvicorn.run(app, host="0.0.0.0", port=5000)

View File

@@ -1,233 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DNS Resolver Settings</title>
<script>
function validateForm(event) {
const services = document.querySelectorAll('input[name="services"]:checked');
const dnsServers = document.querySelectorAll('input[name="dns_servers"]:checked');
const format = document.querySelector('input[name="format"]:checked');
if (services.length === 0) {
alert("Выберите хотя бы один сервис.");
event.preventDefault();
return false;
}
if (dnsServers.length === 0) {
alert("Выберите хотя бы один DNS сервер.");
event.preventDefault();
return false;
}
if (!format) {
alert("Выберите формат сохранения.");
event.preventDefault();
return false;
}
if ((format.value === 'unix' || format.value === 'win' || format.value === 'keenetic') &&
!document.getElementById('gateway').value) {
alert("Укажите IP шлюза.");
event.preventDefault();
return false;
}
return true;
}
function generateConfig(event) {
event.preventDefault();
if (!validateForm(event)) {
return;
}
const services = Array.from(document.querySelectorAll('input[name="services"]:checked')).map(el => el.value).join(',');
const dnsServers = Array.from(document.querySelectorAll('input[name="dns_servers"]:checked')).map(el => el.value).join(' ');
const format = document.querySelector('input[name="format"]:checked').value;
const gateway = document.getElementById('gateway')?.value || '';
const commentary = document.getElementById('commentary')?.value || '';
const cloudflare = document.querySelector('input[name="cloudflare"]:checked') ? 'yes' : 'no';
const aggregation = document.querySelector('input[name="aggregation"]:checked').value;
const userId = Math.floor(Math.random() * 100000); // Случайный ID пользователя
const filename = `out-id_${userId}.txt`;
const config = `
[DomainMapper]
localplatform = no
localdns = no
service = ${services}
dnsserver = ${dnsServers}
cloudflare = ${cloudflare}
subnet = ${aggregation}
filename = ${filename}
filetype = ${format}
gateway = ${gateway}
keenetic = ${gateway}
listname = ${commentary}
mk_comment = off
cfginfo = no
run =
`;
sendConfigToServer(config, userId);
}
function sendConfigToServer(config, userId) {
fetch('/run', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ config: config, userId: userId })
})
.then(response => response.text())
.then(data => {
document.getElementById('progress').innerText = `Конфигурация создана и скрипт запущен. Результат: ${data}`;
})
.catch(error => {
console.error('Ошибка при отправке конфигурации:', error);
document.getElementById('progress').innerText = 'Ошибка при выполнении.';
});
}
function loadServices() {
const serviceUrl = 'https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/refs/heads/main/platformdb';
fetch(serviceUrl)
.then(response => response.text())
.then(data => {
const lines = data.split('\n');
const servicesContainer = document.getElementById('servicesContainer');
lines.forEach(line => {
const parts = line.split(':');
if (parts.length > 1) {
const serviceName = parts[0].trim();
const label = document.createElement('label');
const input = document.createElement('input');
input.type = 'checkbox';
input.name = 'services';
input.value = serviceName;
label.appendChild(input);
label.appendChild(document.createTextNode(` ${serviceName}`));
servicesContainer.appendChild(label);
servicesContainer.appendChild(document.createElement('br'));
}
});
})
.catch(error => console.error('Error loading services:', error));
}
function loadDNSServers() {
const dnsUrl = 'https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/refs/heads/main/dnsdb';
fetch(dnsUrl)
.then(response => response.text())
.then(data => {
const lines = data.split('\n');
const dnsServersContainer = document.getElementById('dnsServersContainer');
lines.forEach(line => {
const parts = line.split(':');
if (parts.length > 1) {
const dnsServerName = parts[0].trim();
const label = document.createElement('label');
const input = document.createElement('input');
input.type = 'checkbox';
input.name = 'dns_servers';
input.value = dnsServerName;
label.appendChild(input);
label.appendChild(document.createTextNode(` ${dnsServerName}`));
dnsServersContainer.appendChild(label);
dnsServersContainer.appendChild(document.createElement('br'));
}
});
})
.catch(error => console.error('Error loading DNS servers:', error));
}
function toggleGatewayField() {
const format = document.querySelector('input[name="format"]:checked');
const gatewayField = document.getElementById('gatewayField');
if (format && (format.value === 'unix' || format.value === 'win' || format.value === 'keenetic')) {
gatewayField.style.display = 'block';
} else {
gatewayField.style.display = 'none';
}
}
function toggleCommentField() {
const format = document.querySelector('input[name="format"]:checked');
const commentField = document.getElementById('commentaryField');
if (format && (format.value === 'mikrotik' || format.value === 'keenetic')) {
commentField.style.display = 'block';
} else {
commentField.style.display = 'none';
}
}
window.onload = function() {
loadServices();
loadDNSServers();
toggleGatewayField();
toggleCommentField();
};
document.addEventListener('change', function(event) {
if (event.target.name === 'format') {
toggleGatewayField();
toggleCommentField();
}
});
</script>
</head>
<body>
<h2>Настройки</h2>
<form id="dnsForm" onsubmit="generateConfig(event)">
<label><strong>Список сервисов:</strong></label><br>
<div id="servicesContainer"></div><br>
<label><strong>Список используемых DNS серверов:</strong></label><br>
<div id="dnsServersContainer"></div><br>
<label><strong>Фильтрация Cloudflare:</strong></label><br>
<input type="checkbox" name="cloudflare" value="yes"> Исключить Cloudflare<br><br>
<label><strong>Агрегация подсетей:</strong></label><br>
<input type="radio" name="aggregation" value="16"> До /16 (255.255.0.0)<br>
<input type="radio" name="aggregation" value="24"> До /24 (255.255.255.0)<br>
<input type="radio" name="aggregation" value="mix"> Микс /24 и /32<br>
<input type="radio" name="aggregation" value="none" checked> Не агрегировать<br><br>
<label><strong>Формат сохранения:</strong></label><br>
<input type="radio" name="format" value="ip"> IP (только IP адреса)<br>
<input type="radio" name="format" value="cidr"> CIDR (%IP%/32)<br>
<input type="radio" name="format" value="win"> Windows Route (route add %IP% mask 255.255.255.255 %gateway%)<br>
<input type="radio" name="format" value="unix"> Unix Route (ip route %IP%/32 %gateway%)<br>
<input type="radio" name="format" value="wg"> Wireguard/AmneziaWG (%IP%/32, %IP%/32, и т.д...)<br>
<input type="radio" name="format" value="openvpn"> Open VPN (push "route %IP% 255.255.255.255")<br>
<input type="radio" name="format" value="keenetic"> Keenetic CLI (ip route %IP%/32 %gateway% auto !%commentary%)<br>
<input type="radio" name="format" value="mikrotik"> Mikrotik firewall (/ip/firewall/address-list add list=%commentary% address=%IP%/32)<br><br>
<div id="gatewayField" style="display:none;">
<label><strong>Укажите IP шлюза или имя интерфейса:</strong></label><br>
<input type="text" id="gateway" name="gateway" placeholder="IP или имя шлюза"><br><br>
</div>
<div id="commentaryField" style="display:none;">
<label><strong>Укажите комментарий для списка:</strong></label><br>
<input type="text" id="commentary" name="commentary" placeholder="Комментарий для списка"><br><br>
</div>
<button type="submit">Создать конфигурацию</button>
</form>
<div id="progress"></div>
</body>
</html>

View File

@@ -1,144 +0,0 @@
#!/bin/bash
set -e # Завершение скрипта при ошибке
set -u # Завершение при использовании необъявленных переменных
# Переменные
USERNAME="test123"
APP_DIR="/home/$USERNAME/dns_resolver_app"
SERVICE_FILE="/etc/systemd/system/dns_resolver.service"
NGINX_CONF="/etc/nginx/sites-available/dns_resolver"
EMAIL_ADR="email@example.com"
DOMAIN_NAME="your-domain.com"
# Проверка существования пользователя
if ! id "$USERNAME" &>/dev/null; then
echo "Пользователь $USERNAME не существует."
read -p "Хотите создать пользователя? (y/n): " CREATE_USER
if [[ "$CREATE_USER" =~ ^[Yy]$ ]]; then
sudo useradd -m -s /bin/bash "$USERNAME"
echo "Пользователь $USERNAME успешно создан."
else
echo "Скрипт завершён, так как пользователь не существует."
exit 1
fi
fi
# Убедиться, что пользователь $USERNAME и www-data имеют общую группу
sudo usermod -aG www-data "$USERNAME"
# Обновление системы и установка зависимостей
echo "Обновляем систему и устанавливаем зависимости..."
sudo apt update && sudo apt upgrade -y
sudo apt install python3 python3-pip python3-venv gunicorn nginx certbot python3-certbot-nginx -y
# Создание директории приложения
if [[ ! -d "$APP_DIR" ]]; then
echo "Создаем директорию приложения..."
sudo mkdir -p "$APP_DIR"
sudo chown -R "$USERNAME:www-data" "$APP_DIR"
sudo chmod -R 750 "$APP_DIR"
else
echo "Директория приложения уже существует. Пропускаем."
fi
# Создание виртуального окружения от имени www-data
if [[ ! -d "$APP_DIR/venv" ]]; then
echo "Создаем виртуальное окружение..."
sudo -u www-data python3 -m venv "$APP_DIR/venv"
sudo chown -R "$USERNAME:www-data" "$APP_DIR/venv"
sudo chmod -R 750 "$APP_DIR/venv"
else
echo "Виртуальное окружение уже существует. Пропускаем."
fi
# Загрузка файла requirements.txt
REQUIREMENTS_URL="https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/refs/heads/main/requirements.txt"
if curl --head --fail "$REQUIREMENTS_URL" &>/dev/null; then
curl -o "$APP_DIR/requirements.txt" "$REQUIREMENTS_URL"
echo "Файл requirements.txt успешно загружен."
else
echo "Ошибка: Файл requirements.txt недоступен."
exit 1
fi
# Установка зависимостей Python от имени www-data
echo "Устанавливаем зависимости Python..."
sudo -u www-data bash -c "source $APP_DIR/venv/bin/activate && pip install -r $APP_DIR/requirements.txt fastapi uvicorn pydantic gunicorn"
# Загрузка файлов приложения
FILES=("index.html" "app.py" "main.py")
for FILE in "${FILES[@]}"; do
URL="https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/refs/heads/main/web/$FILE"
if curl --head --fail "$URL" &>/dev/null; then
curl -o "$APP_DIR/$FILE" "$URL"
echo "Файл $FILE успешно загружен."
sudo chown "$USERNAME:www-data" "$APP_DIR/$FILE"
sudo chmod 640 "$APP_DIR/$FILE"
else
echo "Ошибка: Файл $FILE недоступен."
fi
done
# Проверка прав доступа
sudo chown -R "$USERNAME:www-data" "$APP_DIR"
sudo chmod -R 750 "$APP_DIR"
# Создание системного сервиса
echo "Создаем системный сервис..."
sudo bash -c "cat <<EOF > $SERVICE_FILE
[Unit]
Description=DNS Resolver Web App
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=$APP_DIR
ExecStart=$APP_DIR/venv/bin/gunicorn -w 4 -k uvicorn.workers.UvicornWorker --bind 127.0.0.1:5000 app:app
[Install]
WantedBy=multi-user.target
EOF"
sudo systemctl daemon-reload
sudo systemctl enable --now dns_resolver
# Настройка Nginx
if [[ ! -f "$NGINX_CONF" ]]; then
echo "Настраиваем Nginx..."
sudo bash -c "cat <<EOF > $NGINX_CONF
server {
listen 80;
server_name $DOMAIN_NAME;
root $APP_DIR;
index index.html;
location / {
try_files \$uri /index.html;
}
location /run {
proxy_pass http://127.0.0.1:5000;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
}
error_page 404 /index.html;
}
EOF"
sudo ln -sf "$NGINX_CONF" /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl restart nginx
else
echo "Конфигурация Nginx уже существует. Пропускаем."
fi
# Настройка HTTPS
echo "Настраиваем HTTPS..."
sudo certbot --nginx -n --agree-tos --email "$EMAIL_ADR" -d "$DOMAIN_NAME"
echo "Скрипт выполнен успешно. Приложение доступно по адресу https://$DOMAIN_NAME"