19 Commits

Author SHA1 Message Date
Ground-Zerro
3eb734dd98 Update platformdb 2025-11-29 01:14:02 +11:00
Ground-Zerro
12ebef3393 Delete domain-ip-resolve.txt 2025-11-29 01:13:46 +11:00
Ground-Zerro
db18c01732 Update dns-jetbrains.txt 2025-11-22 21:13:44 +11:00
Ground-Zerro
ff01ca1738 Merge pull request #108 from drwolfik/patch-1
Update dns-youtube.txt for ios mobile app
2025-11-03 11:24:10 +11:00
drwolfik
c39753de3f Update dns-youtube.txt for ios mobile app 2025-11-03 00:05:05 +03:00
Ground-Zerro
1f1422c69d Fix line endings for .bat files to CRLF 2025-11-01 23:20:10 +11:00
Ground-Zerro
50c8910dcf Merge branch 'main' of https://github.com/Ground-Zerro/DomainMapper 2025-11-01 22:00:55 +11:00
Ground-Zerro
af64289c0c add-correct 2025-11-01 22:00:02 +11:00
Ground-Zerro
521026aff6 Add files via upload 2025-11-01 21:50:16 +11:00
Ground-Zerro
c097a7dcd5 Delete utilities/win/convert.bat 2025-11-01 21:49:59 +11:00
Ground-Zerro
64abff705f fixS 2025-11-01 21:48:45 +11:00
Ground-Zerro
487093e254 Update README.md 2025-11-01 21:31:26 +11:00
Ground-Zerro
a493beec6f Add files via upload 2025-11-01 21:29:55 +11:00
Ground-Zerro
fd893ac674 Delete Windows/Win.bat 2025-11-01 21:29:33 +11:00
Ground-Zerro
b2c50641b7 Delete old directory 2025-11-01 21:28:02 +11:00
Ground-Zerro
f90af7fec7 Add files via upload 2025-11-01 21:27:34 +11:00
Ground-Zerro
e11e5696cf update 2025-11-01 21:25:15 +11:00
Ground-Zerro
b8ec267ae3 Merge branch 'main' of https://github.com/Ground-Zerro/DomainMapper 2025-10-29 20:29:11 +11:00
Ground-Zerro
dca5e107b4 Update dns-youtube.txt 2025-10-29 20:29:06 +11:00
22 changed files with 638 additions and 1458 deletions

4
.gitattributes vendored
View File

@@ -1,3 +1,3 @@
* text=auto
*.bat text eol=crlf
*.cmd text eol=crlf
*.bat -text
*.cmd -text

View File

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

View File

@@ -13,9 +13,9 @@
**Можете попробовать эти варианты:**
- Запустить PowerShell и выполнить команду:
```
irm https://github.com/Ground-Zerro/DomainMapper/raw/main/Windows/Win.bat -OutFile "$env:TEMP\Win.bat"; cmd /c "$env:TEMP\Win.bat"
irm https://github.com/Ground-Zerro/DomainMapper/raw/refs/heads/main/Windows/Win.bat -OutFile "$env:TEMP\Win.bat"; cmd /c "$env:TEMP\Win.bat"
```
- Открыть командную строку Windows и выполнить команду:
```
powershell -Command "irm https://github.com/Ground-Zerro/DomainMapper/raw/main/Windows/Win.bat -OutFile $env:TEMP\Win.bat" && cmd /c "%TEMP%\Win.bat"
powershell -Command "irm https://github.com/Ground-Zerro/DomainMapper/raw/refs/heads/main/Windows/Win.bat -OutFile $env:TEMP\Win.bat" && cmd /c "%TEMP%\Win.bat"
```

View File

@@ -47,7 +47,7 @@ exit /b 0
REM Проверка и установка необходимых модулей Python
:CheckModules
set "modules=requests dnspython ipaddress configparser httpx colorama"
set "modules=dnspython httpx colorama tqdm"
echo.
echo Проверка необходимых библиотек...
@@ -87,10 +87,26 @@ if ERRORLEVEL 1 (
exit /b 1
)
move /y domain-ip-resolve.txt %UserProfile%\Desktop\domain-ip-resolve.txt
echo Копирование файлов на рабочий стол...
if exist domain-ip-resolve.txt (
move /y domain-ip-resolve.txt %UserProfile%\Desktop\domain-ip-resolve.txt
echo Файл скопирован в %UserProfile%\Desktop\domain-ip-resolve.txt
) else (
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
echo файл скопирован в %UserProfile%\Desktop\domain-ip-resolve.txt
pause
exit /b 0

View File

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

462
main.py
View File

@@ -3,16 +3,129 @@ import asyncio
import configparser
import ipaddress
import os
from asyncio import Semaphore
from collections import defaultdict
import time
from collections import defaultdict, deque
from typing import Dict, List, Set, Tuple, Optional
import dns.asyncresolver
import httpx
from colorama import Fore, Style, init
from tqdm import tqdm
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):
return f"{Fore.YELLOW}{text}{Style.RESET_ALL}"
@@ -60,7 +173,7 @@ def read_config(cfg_file):
config = config['DomainMapper']
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'
cloudflare = config.get('cloudflare') or ''
filetype = config.get('filetype') or ''
@@ -79,7 +192,7 @@ def read_config(cfg_file):
print(f"{yellow(f'Загружена конфигурация из {cfg_file}:')}")
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} {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-адресов:{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 'спросить у пользователя'}")
@@ -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}Локальный список 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:
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):
if not gateway:
@@ -116,11 +229,107 @@ def ken_gateway_input(ken_gateway):
else:
return ken_gateway
def get_semaphore(request_limit):
return defaultdict(lambda: Semaphore(request_limit))
class DNSServerWorker:
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):
return get_semaphore(request_limit)
async def add_domain(self, domain: str):
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]:
try:
@@ -224,73 +433,49 @@ async def load_dns_names(url_or_file: str) -> List[str]:
print(f"Ошибка при чтении файла {url_or_file}: {e}")
return []
async def resolve_domain_batch(domains: List[str], resolver: dns.asyncresolver.Resolver,
semaphore: Semaphore, dns_server_name: str,
stats: Dict[str, int], cloudflare_ips: Set[str],
include_cloudflare: bool) -> List[str]:
async with semaphore:
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:
async def resolve_dns_with_workers(service: str, dns_names: List[str],
dns_servers: List[Tuple[str, List[str]]],
cloudflare_ips: Set[str], unique_ips_all_services: Set[str],
stats: Dict[str, int], include_cloudflare: bool,
rate_limit: int, stats_lock: asyncio.Lock = None) -> str:
try:
print(f"{Fore.YELLOW}Загрузка DNS имен платформы {service}...{Style.RESET_ALL}")
domain_batches = [dns_names[i:i + batch_size] for i in range(0, len(dns_names), batch_size)]
tasks = []
for batch in domain_batches:
for server_name, servers in dns_servers:
resolver = dns.asyncresolver.Resolver()
resolver.nameservers = servers
tasks.append(resolve_domain_batch(
batch, resolver, semaphore_dict[server_name],
server_name, stats, cloudflare_ips, include_cloudflare
))
max_concurrent_tasks = min(len(tasks), 100)
results = []
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)
if stats_lock is None:
stats_lock = asyncio.Lock()
workers = []
for server_name, servers in dns_servers:
worker = DNSServerWorker(server_name, servers, rate_limit, stats_lock)
workers.append(worker)
for domain in dns_names:
for worker in workers:
await worker.add_domain(domain)
tasks = [worker.process_queue(stats) for worker in workers]
await asyncio.gather(*tasks)
all_nameservers = set()
for _, servers in dns_servers:
all_nameservers.update(servers)
unique_ips_current_service = set()
for ip_address in results:
if ip_address not in unique_ips_all_services:
unique_ips_current_service.add(ip_address)
unique_ips_all_services.add(ip_address)
for worker in workers:
for ip_address in worker.results:
if ip_address in ('127.0.0.1', '0.0.0.0') or ip_address in all_nameservers:
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 ''
except Exception as e:
print(f"Не удалось сопоставить IP адреса {service} его доменным именам: {e}")
return ""
@@ -465,6 +650,46 @@ def group_ips_in_subnets_optimized(filename: str, subnet: str):
except Exception as 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 read_file(filename):
try:
@@ -554,6 +779,11 @@ def process_file_format(filename, filetype, gateway, selected_service, mk_list_n
if filetype.lower() in formatters:
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():
parser = argparse.ArgumentParser(description="DNS resolver script with custom config file.")
parser.add_argument(
@@ -566,7 +796,7 @@ async def main():
try:
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,
localdns, mk_comment) = read_config(config_file)
@@ -596,37 +826,70 @@ async def main():
cloudflare_ips = set()
unique_ips_all_services = set()
semaphore = init_semaphores(request_limit)
stats = {
'null_ips_count': 0,
'cloudflare_ips_count': 0,
'total_domains_processed': 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 = []
for service_name in selected_services:
if service_name == 'Custom DNS list':
tasks.append(resolve_dns_optimized(
service_name, local_dns_names, selected_dns_servers,
cloudflare_ips, unique_ips_all_services, semaphore,
stats, include_cloudflare
tasks.append(resolve_dns_with_workers(
service_name, local_dns_names, selected_dns_servers,
cloudflare_ips, unique_ips_all_services,
stats, include_cloudflare, rate_limit,
stats_lock
))
else:
url_or_file = urls[service_name]
dns_names = await load_dns_names(url_or_file)
if dns_names:
tasks.append(resolve_dns_optimized(
service_name, dns_names, selected_dns_servers,
cloudflare_ips, unique_ips_all_services, semaphore,
stats, include_cloudflare
tasks.append(resolve_dns_with_workers(
service_name, dns_names, selected_dns_servers,
cloudflare_ips, unique_ips_all_services,
stats, include_cloudflare, rate_limit,
stats_lock
))
if tasks:
results = await asyncio.gather(*tasks, return_exceptions=True)
with open(filename, 'w', encoding='utf-8') as file:
for result in results:
if isinstance(result, str) and result.strip():
@@ -635,32 +898,31 @@ async def main():
with open(filename, 'w', encoding='utf-8') as file:
pass
print(f"\n{yellow('Проверка завершена.')}")
print(f"{Style.BRIGHT}Всего обработано DNS имен:{Style.RESET_ALL} {stats['total_domains_processed']}")
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]))
await periodic_updater.stop()
progress_tracker.close()
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)
if subnet != '32':
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:
print("\nВыполнение команды после завершения скрипта...")
os.system(run_command)
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':
input(f"Нажмите {green('Enter')} для выхода...")
print(f"\n{Style.BRIGHT}Если есть желание, можно угостить автора чашечкой какао:{Style.RESET_ALL} {green('https://boosty.to/ground_zerro')}")
except KeyboardInterrupt:
print(f"\n{red('Программа прервана пользователем')}")
except Exception as e:
@@ -674,4 +936,4 @@ if __name__ == "__main__":
except KeyboardInterrupt:
print(f"\n{red('Программа прервана пользователем')}")
except Exception as e:
print(f"\n{red('Критическая ошибка:')} {e}")
print(f"\n{red('Критическая ошибка:')} {e}")

View File

@@ -1,130 +0,0 @@
[DomainMapper]
# Локальный режим - загружать список сервисов и/или DNS серверов из локального файла
# yes - включить
# no или пусто - выключить
localplatform = yes
localdns = yes
# Имена сервисов, разделенные запятыми, для разрешения доменных имен в IP-адреса без запроса у пользователя
# опции:
# пустое значение - пользователю будет выведено меню выбора
# all - проверить все сервисы
# Antifilter community edition - список заблокированных DNS имен формируемый сообществом
# ITDog Inside - ресурсы, в том числе зарубежные, которые блокируются или сами блокируют доступ из России.
# ITDog Outside - ресурсы, которые доступны только внутри России и блокируют доступ из-за рубежа.
# Youtube
# Facebook
# Openai
# Tik-Tok
# Instagram
# Twitter
# Netflix
# Bing
# Adobe
# Apple
# Google
# Torrent Trackers - торрент трекеры
# Search engines - поисковые системы
# Github Copilot - ИИ помощник от github
# Twitch
# Discord
# Jetbrains
# Xbox
# Telegram
# Online movie theaters - некоторые из популярных оналйн кинотеатров
# custom - Custom DNS list, это файл "custom-dns-list.txt" расположенный в одном каталоге со скриптом
service =
# DNS сервера (номер), разделенные пробелом, которые будут использоваться для разрешения доменных имен
# опции:
# пустое значение - пользователю будет выведено меню выбора
# 0 - использовать все доступные DNS серверы
# 1 - Системный DNS
# 2 - Google Public DNS
# 3 - Quad9
# 4 - Cloudflare DNS
# 5 - OpenDNS
# 6 - Cisco Umbrella
# 7 - DNS.Watch
# 8 - Dyn
# 9 - CleanBrowsing
# 10 - Alternate DNS
# 11 - AdGuard DNS
# 12 - Control D
# 13 - Yandex (основной)
dnsserver =
# Включить фильтрацию IP-адресов cloudflare и не записывать их в файл результатов
# опции:
# пустое значение - пользователю будет выведено меню выбора
# yes - исключить IP адреса cloudflare из итогового списка
# no - оставить IP адреса cloudflare в итоговом списке
cloudflare =
# Сгруппировать подсети
# опции:
# пустое значение - пользователю будет выведено меню выбора
# 16 - группировка подсетей до /16 (255.255.0.0)
# 24 - группировка подсетей до /24 (255.255.255.0)
# mix - /24 и /32 в одном файле
# no - оставить как есть
subnet =
# Имя конечного файла
# опции:
# пустое значение - "domain-ip-resolve.txt" в каталоге со скриптом
# имя_файла - файл с указанным именем будет сохранен в каталоге со скриптом
# полный_путь/имя_файла - файл будет сохранен с указанным именем в указанной каталоге
filename =
# Количество потоков сканирования, если не указано - будет использоваться 20 потоков
threads = 5
# Формат сохранения файла результатов
# опции:
# пустое значение - пользователю будет выведено меню выбора
# ip - только IP адрес
# unix - ip rote %IP%/32 %gateway%
# cidr - %IP%/32
# win - rote add %IP% mask 255.255.255.255 %gateway%
# mikrotik - /ip/firewall/address-list add list=%LIST_NAME% comment=%SERVICE_NAME% address=%IP%/32
# ovpn - push "route %IP% 255.255.255.255"
# wireguard - %IP%/32, и т.д...
# keenetic - ip route %IP%/32 %gateway% auto !%LIST_NAME%
filetype =
# адрес шлюза или имя интерфейса - используется при сохранении IP-адресов в 'win' и 'unix' формате
# опции:
# пустое значение - пользователю будет выведен запрос с подсказкой
# укажите IP-адрес шлюза или имя интерфейса
gateway =
# адрес шлюза или имя интерфейса - используется при сохранении IP-адресов в 'keenetic' формате
# опции:
# пустое значение - пользователю будет выведен запрос с подсказкой
# укажите IP-адрес шлюза или имя интерфейса или IP-адрес шлюза и через пробел имя интерфейса
keenetic =
# имя списка - используется при сохранении IP-адресов в 'mikrotik' формате
# опции:
# пустое значение - пользователю будет выведен запрос с подсказкой
# укажите имя
listname =
# комментарий - используется при сохранении IP-адресов в 'mikrotik' формате
# опции:
# on - если вам нужен comment=%SERVICE_NAME% в строке
# off - убрать comment=%SERVICE_NAME% из строки
mk_comment = off
# Показывать сведения о загруженной конфигурации при запуске скрипта
# опции:
# yes или пустое значение - показывать
# no - скрыть
cfginfo = yes
# Команда для консоли после завершения скриптом всех операций, может быть полезно для автоматизации и комбинирования с другим скриптом, кодом или программой
# опции:
# исполняемая_команда_для_консоли
run =

View File

@@ -1,641 +0,0 @@
import argparse
import asyncio
import configparser
import ipaddress
import os
from asyncio import Semaphore
from collections import defaultdict
import dns.asyncresolver
import httpx
from colorama import Fore, Style, init
# Цвета
init(autoreset=True)
def yellow(text):
return f"{Fore.YELLOW}{text}{Style.RESET_ALL}"
def green(text):
return f"{Fore.GREEN}{text}{Style.RESET_ALL}"
def cyan(text):
return f"{Fore.CYAN}{text}{Style.RESET_ALL}"
def red(text):
return f"{Fore.RED}{text}{Style.RESET_ALL}"
def magneta(text):
return f"{Fore.MAGENTA}{text}{Style.RESET_ALL}"
def blue(text):
return f"{Fore.BLUE}{text}{Style.RESET_ALL}"
# Читаем конфигурацию
def read_config(cfg_file):
try:
config = configparser.ConfigParser()
with open(cfg_file, 'r', encoding='utf-8') as file:
config.read_file(file)
if 'DomainMapper' in config:
config = config['DomainMapper']
service = config.get('service') or ''
request_limit = int(config.get('threads') or 20)
filename = config.get('filename') or 'domain-ip-resolve.txt'
cloudflare = config.get('cloudflare') or ''
filetype = config.get('filetype') or ''
gateway = config.get('gateway') or ''
run_command = config.get('run') or ''
dns_server_indices = list(map(int, config.get('dnsserver', '').split())) if config.get('dnsserver') else []
mk_list_name = config.get('listname') or ''
subnet = config.get('subnet') or ''
cfginfo = config.get('cfginfo') or 'yes' # Не возвращаем его в main
ken_gateway = config.get('keenetic') or ''
localplatform = config.get('localplatform') or ''
localdns = config.get('localdns') or ''
mk_comment = config.get('mk_comment') or 'off'
if cfginfo in ['yes', 'y']:
print(f"{yellow(f'Загружена конфигурация из {cfg_file}:')}")
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} {request_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-адресов:{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 'спросить у пользователя'}")
if filetype in ['win', 'unix', '']:
print(f"{Style.BRIGHT}Шлюз/Имя интерфейса для Windows и Linux route:{Style.RESET_ALL} {gateway if gateway else 'спросить у пользователя'}")
if filetype in ['keenetic', '']:
print(f"{Style.BRIGHT}Шлюз/Имя интерфейса для Keenetic CLI:{Style.RESET_ALL} {ken_gateway if ken_gateway else 'спросить у пользователя'}")
if filetype in ['mikrotik', '']:
print(f"{Style.BRIGHT}Имя списка для Mikrotik firewall:{Style.RESET_ALL} {mk_list_name if mk_list_name else 'спросить у пользователя'}")
print(f"{Style.BRIGHT}'comment=' в Mikrotik firewall:{Style.RESET_ALL} {'вЫключен' if mk_comment == 'off' else 'включен'}")
print(f"{Style.BRIGHT}Сохранить результат в файл:{Style.RESET_ALL} {filename}")
print(f"{Style.BRIGHT}Выполнить по завершению:{Style.RESET_ALL} {run_command if run_command else 'не указано'}")
if localplatform in ['yes', 'y'] or localdns in ['yes', 'y']:
print(f"\n{red('!!! Включен локальный режим !!!')}")
print(f"{Style.BRIGHT}Список сервисов будет загружен из:{Style.RESET_ALL} {'файла platformdb' if localplatform in ['yes', 'y'] else 'сети'}")
print(f"{Style.BRIGHT}Список DNS серверов будет загружен из:{Style.RESET_ALL} {'файла dnsdb' if localdns 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
except Exception as e:
print(f"{yellow(f'Ошибка загрузки {cfg_file}:')} {e}\n{Style.BRIGHT}Используются настройки 'по умолчанию'.{Style.RESET_ALL}")
return '', 20, 'domain-ip-resolve.txt', '', '', '', '', [], '', '', '', '', '', 'off'
# IP шлюза для win и unix
def gateway_input(gateway):
if not gateway:
input_gateway = input(f"Укажите {green('IP шлюза')} или {green('имя интерфейса')}: ")
return input_gateway.strip() if input_gateway else None
else:
return gateway
# IP шлюза и имя интерфейса для keenetic
def ken_gateway_input(ken_gateway):
if not ken_gateway:
input_ken_gateway = input(f"Укажите {green('IP шлюза')} или {green('имя интерфейса')} или {green('IP шлюза')} и через пробел {green('имя интерфейса')}: ")
return input_ken_gateway.strip() if input_ken_gateway else None
else:
return ken_gateway
# Ограничение числа запросов
def get_semaphore(request_limit):
return defaultdict(lambda: Semaphore(request_limit))
# Инициализация semaphore для ограничения запросов
def init_semaphores(request_limit):
return get_semaphore(request_limit)
# Загрузка списка платформ из сети
async def load_urls(url):
try:
async with httpx.AsyncClient() as client:
response = await client.get(url)
response.raise_for_status()
text = response.text
lines = text.split('\n')
urls = {}
for line in lines:
if line.strip():
service, url = line.split(': ', 1)
urls[service.strip()] = url.strip()
return urls
except Exception as e:
print(f"Ошибка при загрузке списка платформ: {e}")
return {}
# Загрузка списка платформ из локального файла
async def load_urls_from_file():
try:
with open('platformdb', 'r', encoding='utf-8') as file:
urls = {}
for line in file:
if line.strip():
service, url = line.split(': ', 1)
urls[service.strip()] = url.strip()
return urls
except Exception as e:
print(f"Ошибка при загрузке списка платформ: {e}")
return {}
# Загрузка списка DNS серверов
async def load_dns_servers(url):
try:
async with httpx.AsyncClient() as client:
response = await client.get(url)
response.raise_for_status()
text = response.text
lines = text.split('\n')
dns_servers = {}
for line in lines:
if line.strip():
service, servers = line.split(': ', 1)
dns_servers[service.strip()] = servers.strip().split()
return dns_servers
except Exception as e:
print(f"Ошибка при загрузке списка DNS серверов: {e}")
return {}
# Загрузка списка DNS серверов из локального файла
async def load_dns_from_file():
try:
with open('dnsdb', 'r') as file:
dns_servers = {}
for line in file:
if line.strip():
service, servers = line.split(': ', 1)
dns_servers[service.strip()] = servers.strip().split()
return dns_servers
except Exception as e:
print(f"Ошибка при загрузке списка DNS серверов: {e}")
return {}
# Загрузка IP-адресов cloudflare
async def get_cloudflare_ips():
try:
async with httpx.AsyncClient() as client:
response = await client.get("https://www.cloudflare.com/ips-v4/")
response.raise_for_status()
text = response.text
cloudflare_ips = set()
for line in text.splitlines():
line = line.strip()
if '/' in line:
try:
ip_network = ipaddress.ip_network(line)
for ip in ip_network:
cloudflare_ips.add(str(ip))
except ValueError:
continue
return cloudflare_ips
except Exception as e:
print("Ошибка при получении IP адресов Cloudflare:", e)
return set()
# Загрузка списков DNS имен из сети и локальных файлов
async def load_dns_names(url_or_file):
if url_or_file.startswith("http"):
async with httpx.AsyncClient() as client:
try:
response = await client.get(url_or_file)
response.raise_for_status()
return response.text.splitlines()
except httpx.HTTPStatusError as e:
print(f"Ошибка при загрузке DNS имен: {e}")
return []
else:
# Локальный файл
with open(url_or_file, 'r', encoding='utf-8') as file:
return file.read().splitlines()
async def resolve_domain(domain, resolver, semaphore, dns_server_name, null_ips_count, cloudflare_ips,
cloudflare_ips_count, total_domains_processed, include_cloudflare):
async with semaphore:
try:
total_domains_processed[0] += 1
response = await resolver.resolve(domain)
ips = [ip.address for ip in response]
filtered_ips = []
for ip_address in ips:
if ip_address in ('127.0.0.1', '0.0.0.0') or ip_address in resolver.nameservers:
null_ips_count[0] += 1
elif include_cloudflare and ip_address in cloudflare_ips:
cloudflare_ips_count[0] += 1
else:
filtered_ips.append(ip_address)
print(f"{Fore.BLUE}{domain} IP-адрес: {ip_address} - {dns_server_name}{Style.RESET_ALL}")
return filtered_ips
except Exception as e: # Ловим все ошибки чтобы код не прервался
print(f"{Fore.RED}Не удалось получить IP-адрес: {domain} - {dns_server_name}{Style.RESET_ALL}")
return []
async def resolve_dns(service, dns_names, dns_servers, cloudflare_ips, unique_ips_all_services, semaphore,
null_ips_count, cloudflare_ips_count, total_domains_processed, include_cloudflare):
try:
print(f"{Fore.YELLOW}Загрузка DNS имен платформы {service}...{Style.RESET_ALL}")
tasks = []
for server_name, servers in dns_servers:
resolver = dns.asyncresolver.Resolver()
resolver.nameservers = servers
for domain in dns_names:
domain = domain.strip()
if domain:
tasks.append(resolve_domain(domain, resolver, semaphore[server_name], server_name, null_ips_count,
cloudflare_ips, cloudflare_ips_count, total_domains_processed,
include_cloudflare))
results = await asyncio.gather(*tasks)
unique_ips_current_service = set()
for result in results:
for ip_address in result:
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(unique_ips_current_service) + '\n'
except Exception as e:
print(f"Не удалось сопоставить IP адреса {service} его доменным именам.", e)
return ""
def check_service_config(service, urls, local_dns_names):
if service:
services = [s.strip() for s in service.split(',')]
if "custom" in services:
services.remove("custom")
if local_dns_names:
services.append("Custom DNS list")
if "all" in services:
services = list(urls.keys())
if local_dns_names and "Custom DNS list" not in services:
services.append("Custom DNS list")
elif not services:
services = list(urls.keys())
if local_dns_names and "Custom DNS list" not in services:
services.append("Custom DNS list")
else:
while True:
print(f"\n{yellow('Выберите сервисы:')}")
print("0. Выбрать все")
for idx, (service, url) in enumerate(urls.items(), 1):
print(f"{idx}. {service.capitalize()}")
if local_dns_names:
print(f"{len(urls) + 1}. Custom DNS list")
selection = input(f"\nУкажите {green('номера')} платформ через пробел и нажмите {green('Enter')}: ")
if selection.strip():
selections = selection.split()
if '0' in selections:
services = list(urls.keys())
if local_dns_names and "Custom DNS list" not in services:
services.append('Custom DNS list')
break
else:
services = [list(urls.keys())[int(sel) - 1] for sel in selections if sel.isdigit()
and 1 <= int(sel) <= len(urls)]
if str(len(urls) + 1) in selections and local_dns_names:
services.append('Custom DNS list')
break
return services
# Промт cloudflare фильтр
def check_include_cloudflare(cloudflare):
if cloudflare in ['yes', 'y', 'no', 'n']:
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']
def check_dns_servers(dns_servers, dns_server_indices):
# Получение системных DNS серверов
system_dns_servers = dns.asyncresolver.Resolver().nameservers
# Формирование списка всех доступных серверов
dns_server_options = [('Системный DNS', system_dns_servers)] + list(dns_servers.items())
selected_dns_servers = []
# Если указаны индексы серверов в конфиге
if dns_server_indices:
if 0 in dns_server_indices: # Если указано 0, выбираем все доступные DNS серверы
selected_dns_servers = dns_server_options
else:
for idx in dns_server_indices:
if 1 <= idx <= len(dns_server_options): # Корректируем индекс на 1 меньше, чтобы соответствовать списку
selected_dns_servers.append(dns_server_options[idx - 1])
return selected_dns_servers
# Если индексы не указаны, запрашиваем у пользователя выбор серверов
while True:
print(f"\n{yellow('Какие DNS сервера использовать?')}")
print("0. Выбрать все")
for idx, (name, servers) in enumerate(dns_server_options, 1):
print(f"{idx}. {name}: {', '.join(servers)}")
selection = input(f"\nУкажите {green('номера')} DNS серверов через пробел и нажмите {green('Enter')}: ")
if selection.strip():
selections = selection.split()
if '0' in selections:
selected_dns_servers = dns_server_options
break
else:
for sel in selections:
if sel.isdigit():
sel = int(sel)
if 1 <= sel <= len(dns_server_options):
selected_dns_servers.append(dns_server_options[sel - 1])
break
return selected_dns_servers
# комментарй для microtik firewall
def mk_list_name_input(mk_list_name):
if not mk_list_name:
input_mk_list_name = input(f"Введите {green('LIST_NAME')} для Mikrotik firewall: ")
return input_mk_list_name.strip() if input_mk_list_name else None
else:
return mk_list_name
# Уплотняем имена сервисов
def comment(selected_service):
return ",".join(["".join(word.title() for word in s.split()) for s in selected_service])
# Промт на объединение IP в подсети
def subnet_input(subnet):
if not subnet: # Проверяем, является ли значение пустым
subnet = input(
f"\n{yellow('Объединить IP-адреса в подсети?')} "
f"\n{green('16')} - сократить до /16 (255.255.0.0)"
f"\n{green('24')} - сократить до /24 (255.255.255.0)"
f"\n{green('mix')} - сократить до /24 (255.255.255.0) и /32 (255.255.255.255)"
f"\n{green('Enter')} - пропустить: "
).strip().lower()
return subnet if subnet in {'16', '24', 'mix'} else '32'
# Агрегация маршрутов
def group_ips_in_subnets(filename, subnet):
try:
with open(filename, 'r', encoding='utf-8') as file:
ips = {line.strip() for line in file if line.strip()} # Собираем уникальные IP адреса
subnets = set()
def process_ips(subnet):
for ip in ips:
try:
if subnet == "16":
# Преобразуем в /16 (два последних октета заменяются на 0.0)
network = ipaddress.IPv4Network(f"{ip}/16", strict=False)
subnets.add(f"{network.network_address}")
elif subnet == "24":
# Преобразуем в /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"]:
process_ips(subnet)
print(f"{Style.BRIGHT}IP-адреса агрегированы до /{subnet} подсети{Style.RESET_ALL}")
elif subnet == "mix":
octet_groups = {}
for ip in ips:
key = '.'.join(ip.split('.')[:3]) # Группировка по первым трем октетам
if key not in octet_groups:
octet_groups[key] = []
octet_groups[key].append(ip)
# IP-адреса с совпадающими первыми тремя октетами
network_24 = {key + '.0' for key, group in octet_groups.items() if
len(group) > 1} # Базовый IP для /24 подсетей
# Удаляем IP с совпадающими первыми тремя октетами из множества
ips -= {ip for group in octet_groups.values() if len(group) > 1 for ip in 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}")
with open(filename, 'w', encoding='utf-8') as file:
for subnet in sorted(subnets):
file.write(subnet + '\n')
except Exception as e:
print(f"Ошибка при обработке файла: {e}")
# Выбор формата сохранения результатов
def process_file_format(filename, filetype, gateway, selected_service, mk_list_name, mk_comment, subnet, ken_gateway):
def read_file(filename):
try:
with open(filename, 'r', encoding='utf-8') as file:
return file.readlines()
except Exception as e:
print(f"Ошибка чтения файла: {e}")
return None
def write_file(filename, ips, formatter):
formatted_ips = [formatter(ip.strip()) for ip in ips]
with open(filename, 'w', encoding='utf-8') as file:
if filetype.lower() == 'wireguard':
file.write(', '.join(formatted_ips))
else:
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"
if not filetype:
filetype = input(f"""
{yellow('В каком формате сохранить файл?')}
{green('win')} - route add {cyan('IP')} mask {net_mask} {cyan('GATEWAY')}
{green('unix')} - ip route {cyan('IP')}/{subnet} {cyan('GATEWAY')}
{green('keenetic')} - ip route {cyan('IP')}/{subnet} {cyan('GATEWAY GATEWAY_NAME')} auto !{comment(selected_service)}
{green('cidr')} - {cyan('IP')}/{subnet}
{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('ovpn')} - push "route {cyan('IP')} {net_mask}"
{green('wireguard')} - {cyan('IP')}/{subnet}, {cyan('IP')}/{subnet}, и т.д...
{green('Enter')} - {cyan('IP')}
Ваш выбор: """)
ips = read_file(filename)
if not ips:
return
# Дополнительные запросы в зависимости от формата файла
if filetype in ['win', 'unix']: # Запрашиваем IP шлюза для win и unix
gateway = gateway_input(gateway)
elif filetype == 'keenetic': # Запрашиваем IP шлюза и имя интерфейса для keenetic
ken_gateway = ken_gateway_input(ken_gateway)
elif filetype == 'mikrotik': # Запрашиваем ввод комментария для microtik firewall
mk_list_name = mk_list_name_input(mk_list_name)
# обычный формат
formatters = {
'win': lambda ip: f"route add {ip} mask {net_mask} {gateway}",
'unix': lambda ip: f"ip route {ip}/{subnet} {gateway}",
'keenetic': lambda ip: f"ip route {ip}/{subnet} {ken_gateway} auto !{comment(selected_service)}",
'cidr': lambda ip: f"{ip}/{subnet}",
'ovpn': lambda ip: f'push "route {ip} {net_mask}"',
'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}',
'wireguard': lambda ip: f"{ip}/{subnet}"
}
# mix формат
if subnet == "mix":
if filetype.lower() == 'win': # Обработка для win
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"
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"
else: # Обработка для остальных форматов
mix_formatter = lambda ip: f"{ip.strip()}/24" if ip.endswith('.0') else f"{ip.strip()}/32"
formatters.update({
'win': lambda ip: f"route add {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)}",
'cidr': lambda ip: f"{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}' + (f' comment="{comment(selected_service)}"' if mk_comment != "off" else "") + f' address={mix_formatter(ip)}',
'wireguard': lambda ip: f"{mix_formatter(ip)}"
})
# Запись в файл
if filetype.lower() in formatters:
write_file(filename, ips, formatters[filetype.lower()])
# Стартуем
async def main():
# Парсинг аргументов командной строки
parser = argparse.ArgumentParser(description="DNS resolver script with custom config file.")
parser.add_argument(
'-c', '--config',
type=str,
default='config.ini',
help='Путь к конфигурационному файлу (по умолчанию: config.ini)'
)
args = parser.parse_args()
# Инициализация настроек из переданного конфигурационного файла
config_file = args.config
(service, request_limit, filename, cloudflare, filetype, gateway, run_command, dns_server_indices, mk_list_name, subnet, ken_gateway, localplatform, localdns, mk_comment) = read_config(config_file)
# Загрузка списка платформ
if localplatform in ['yes', 'y']:
urls = await load_urls_from_file()
else:
platform_db_url = "https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/main/platformdb"
urls = await load_urls(platform_db_url)
# Подхват "custom-dns-list.txt" если существует
local_dns_names = []
if os.path.exists('custom-dns-list.txt'):
with open('custom-dns-list.txt', 'r', encoding='utf-8') as file:
local_dns_names = [line.strip() for line in file if line.strip()]
# Выбор платформ
selected_services = check_service_config(service, urls, local_dns_names)
# Загрузка списка DNS-серверов
if localdns in ['yes', 'y']:
dns_servers = await load_dns_from_file()
else:
dns_db_url = "https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/main/dnsdb"
dns_servers = await load_dns_servers(dns_db_url)
# Выбор DNS-серверов
selected_dns_servers = check_dns_servers(dns_servers, dns_server_indices)
# Фильтр Cloudflare
include_cloudflare = check_include_cloudflare(cloudflare)
if include_cloudflare: # Загрузка IP-адресов Cloudflare
cloudflare_ips = await get_cloudflare_ips()
else:
cloudflare_ips = set()
unique_ips_all_services = set()
semaphore = init_semaphores(request_limit)
null_ips_count = [0]
cloudflare_ips_count = [0]
total_domains_processed = [0]
tasks = []
for service in selected_services:
if service == 'Custom DNS list':
tasks.append(resolve_dns(service, local_dns_names, selected_dns_servers, cloudflare_ips,
unique_ips_all_services, semaphore, null_ips_count, cloudflare_ips_count,
total_domains_processed, include_cloudflare))
else:
url_or_file = urls[service]
dns_names = await load_dns_names(url_or_file)
tasks.append(resolve_dns(service, dns_names, selected_dns_servers, cloudflare_ips, unique_ips_all_services,
semaphore, null_ips_count, cloudflare_ips_count, total_domains_processed,
include_cloudflare))
results = await asyncio.gather(*tasks)
with open(filename, 'w', encoding='utf-8') as file:
for result in results:
file.write(result)
print(f"\n{yellow('Проверка завершена.')}")
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} {total_domains_processed[0]}")
if include_cloudflare:
print(f"{Style.BRIGHT}Исключено IP-адресов Cloudflare:{Style.RESET_ALL} {cloudflare_ips_count[0]}")
print(f"{Style.BRIGHT}Исключено IP-адресов 'заглушек':{Style.RESET_ALL} {null_ips_count[0]}")
print(f"{Style.BRIGHT}Разрешено IP-адресов из DNS имен:{Style.RESET_ALL} {len(unique_ips_all_services)}")
# Группировка IP-адресов в подсети
subnet = subnet_input(subnet)
if subnet != '32': # Если не '32', вызываем функцию для агрегации
group_ips_in_subnets(filename, subnet)
process_file_format(filename, filetype, gateway, selected_services, mk_list_name, mk_comment, subnet, ken_gateway)
if run_command:
print("\nВыполнение команды после завершения скрипта...")
os.system(run_command)
else:
print(f"\n{Style.BRIGHT}Результаты сохранены в файл:{Style.RESET_ALL}", filename)
if os.name == 'nt':
input(f"Нажмите {green('Enter')} для выхода...")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -20,5 +20,6 @@ Discord: https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/refs/heads/
Jetbrains: https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/refs/heads/main/platforms/dns-jetbrains.txt
Xbox: https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/refs/heads/main/platforms/dns-xbox.txt
Telegram: https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/refs/heads/main/platforms/dns-telegram.txt
WhatsApp: https://raw.githubusercontent.com/HybridNetworks/whatsapp-cidr/main/WhatsApp/whatsapp_domainlist.txt
Online movie theaters: https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/refs/heads/main/platforms/dns-onlinetheater.txt
Windsurf: https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/refs/heads/main/platforms/dns-windsurf.txt

View File

@@ -113,4 +113,16 @@ view.datalore.jetbrains.com
onboard.jetbrains.com
youtube.master-zdchint.mau.jetbrains.com
youtube.staging-zdchint.mau.jetbrains.com
zillow.fls.jetbrains.com
zillow.fls.jetbrains.com
jetbrains.net
intellij.com
intellij.org
jetbrains.team
grazie.ai
grazie.aws.intellij.net
jetbrains.ai
proxy.jetbrains.ai
proxy.stgn.jetbrains.ai
datalore.io
kotlinlang.org
youtrack.cloud

View File

@@ -12,6 +12,7 @@ youtu.be
youtube.com
youtubei.googleapis.com
yt4.ggpht.com
yt3.ggpht.com
ytimg.com
ytimg.l.google.com
r1---sn-2gb7sn7r.googlevideo.com
@@ -7342,6 +7343,7 @@ rr3---sn-5hneknek.googlevideo.com
rr3---sn-5hneknes.googlevideo.com
rr3---sn-5oxmp55u-8pxe.googlevideo.com
rr3---sn-8vq54voxu-5qce.googlevideo.com
rr3---sn-8ph2xajvh-axql.googlevideo.com
rr3---sn-a5mekn6d.googlevideo.com
rr3---sn-a5mekn6l.googlevideo.com
rr3---sn-a5mekn6r.googlevideo.com
@@ -7838,6 +7840,7 @@ rr6---sn-u0g3uxax3-pnul.googlevideo.com
rr6---sn-u0g3uxax3-pnur.googlevideo.com
rr6---sn-u0g3uxax3-pnus.googlevideo.com
rr6---sn-u0g3uxax3-pnuz.googlevideo.com
rr6---sn-xguxaxjvh-8v1e.googlevideo.com
rr6---sn-xuj-5qq6.googlevideo.com
rr6---sn-xuj-5qqs.googlevideo.com
rr7---sn-5oxmp55u-8pxe.googlevideo.com

View File

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

View File

@@ -71,27 +71,44 @@
### Функции
- Загрузка списка IP-адресов из файла.
- Агрегация IP-адресов в подсети с масками `/16`, `/24`, или объединение нескольких подсетей.
- Исключение IP-адресов Cloudflare из итогового списка (при необходимости).
- Извлечение IP-адресов из файла (файл может содержать любой текст - IP автоматически извлекаются).
- Исключение IP-адресов Cloudflare из итогового списка (опционально).
- Агрегация IP-адресов в подсети:
- `/16` (255.255.0.0)
- `/24` (255.255.255.0)
- Mix режим (`/24` + `/32`)
- Поддержка различных форматов маршрутизации:
- Windows (`route add`)
- Unix (`ip route`)
- Keenetic (`ip route` с интерфейсом)
- Mikrotik (`/ip firewall`)
- Только IP-адреса
- Windows route (`route add`)
- Linux route (`ip route`)
- Keenetic BAT (`route add` для bat-файлов)
- Keenetic CLI (`ip route` с интерфейсом)
- Mikrotik firewall (`/ip/firewall/address-list`)
- WireGuard
- OpenVPN
- CIDR (с указанием маски)
- OpenVPN (`push "route"`)
- CIDR нотация
- Автоматическое разделение больших файлов на части (для Keenetic BAT формата, max 999 строк).
- Удаление исходного файла после разделения на части.
- Проверка наличия входного файла с выводом инструкций при его отсутствии.
### Использование
#### Linux/macOS
1. Установите [зависимости](https://github.com/Ground-Zerro/DomainMapper/blob/main/requirements.txt):
```bash
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. Запустите скрипт:
@@ -99,8 +116,21 @@
python convert.py
```
4. Следуйте подсказкам на экране.
4. Следуйте интерактивным подсказкам на экране:
- Выберите, нужно ли исключить IP-адреса Cloudflare (1 - да, Enter - нет)
- Выберите агрегацию подсетей (1 - /16, 2 - /24, 3 - mix, Enter - без агрегации)
- Выберите формат сохранения (1-8 или Enter для простого списка IP)
- При необходимости укажите шлюз/интерфейс/имя списка
5. Результат будет сохранен в файл `ip.txt` (или в несколько файлов, если был выбран формат с автоматическим разделением).
#### Windows
1. Скачайте файл [convert.bat](https://github.com/Ground-Zerro/DomainMapper/raw/refs/heads/main/utilities/win/convert.bat)
2. Создайте файл `ip.txt` в той же директории, где находится `convert.bat`, и добавьте в него IP-адреса
3. Запустите `convert.bat`
## split

View File

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

View File

@@ -6,15 +6,8 @@
## convert
**Использование:**
- Открыть командную строку Windows и выполнить команду:
```
powershell -Command "irm https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/refs/heads/main/utilities/win/convert.bat -OutFile $env:TEMP\convert.bat" && cmd /c "%TEMP%\convert.bat"
```
или
- Запустить PowerShell и выполнить команду:
```
irm https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/refs/heads/main/utilities/win/convert.bat -OutFile "$env:TEMP\convert.bat"; cmd /c "$env:TEMP\convert.bat"
```
1. Скачайте файл [convert.bat](https://github.com/Ground-Zerro/DomainMapper/raw/refs/heads/main/utilities/win/convert.bat)
**Можете:**
- Скачать convert.bat и запустить его.
2. Создайте файл `ip.txt` в той же директории, где находится `convert.bat`, и добавьте в него IP-адреса
3. Запустите `convert.bat`

View File

@@ -65,35 +65,49 @@ for %%m in (%modules%) do (
goto :DownloadMain
REM Загрузка и запуск main.py
REM Загрузка и запуск convert.py
:DownloadMain
echo Загрузка Domain Mapper Converter...
powershell -Command "if ($PSVersionTable.PSVersion.Major -ge 3) {Invoke-WebRequest -Uri 'https://github.com/Ground-Zerro/DomainMapper/raw/refs/heads/main/utilities/convert.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://github.com/Ground-Zerro/DomainMapper/raw/refs/heads/main/utilities/convert.py' -OutFile 'convert.py'} else {Start-BitsTransfer -Source 'https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/main/utilities/convert.py' -Destination 'convert.py'}"
if not exist "main.py" (
if not exist "convert.py" (
echo Ошибка загрузки Domain Mapper Converter.
pause
exit /b 1
)
@echo. >> ip.txt
if not exist "ip.txt" (
echo.
echo Файл ip.txt не найден.
echo Создайте файл ip.txt в текущей директории и добавьте в него IP-адреса.
echo.
choice /C YN /M "Создать пустой файл ip.txt сейчас?"
if ERRORLEVEL 2 (
echo Завершение работы.
del /q /f convert.py
pause
exit /b 1
) else (
echo. > ip.txt
echo Файл ip.txt создан. Добавьте в него IP-адреса и запустите скрипт снова.
del /q /f convert.py
pause
exit /b 0
)
)
cls
REM Запуск main.py
echo Запускаем...
python main.py
python convert.py
if ERRORLEVEL 1 (
echo Ошибка выполнения main.py.
echo Ошибка выполнения convert.py.
pause
del /q /f main.py
del /q /f convert.py
exit /b 1
)
move /y ip.txt %UserProfile%\Desktop\ip.txt
echo Программа завершена.
del /q /f main.py
del /q /f convert.py
endlocal
echo файл скопирован в %UserProfile%\Desktop\ip.txt
pause
exit /b 0

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"