bbrkn/README.md
2026-03-21 09:46:15 +03:00

239 lines
9.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# bbrkn — DNS Bypass Config Generator
Автоматизирует генерацию конфигураций **Pi-hole / dnsmasq** для маршрутизации трафика заблокированных доменов через VPN-туннель (WireGuard).
## Как это работает
```mermaid
flowchart TD
A[domains.txt] --> B[generate-configs.sh]
B -->|кэш .cache/api| B
B -->|MISS / EXPIRED| C[Chromium headless API]
C --> B
B --> D[91-ipset-bbrkn.conf]
B --> E[92-resolve-bbrkn.conf]
D --> F[deploy-to-gateway.sh]
E --> F
F -->|backup + copy| G[Pi-hole dnsmasq.d/]
F -->|docker restart| H[Pi-hole container]
F -->|DNS health check| H
H -->|rollback если упал| G
subgraph "Трафик клиента"
K[Клиент] -->|DNS запрос| H
H -->|домен в ipset → mark| I[iptables]
I --> J[WireGuard VPN]
J --> L[Интернет]
end
```
---
## Классификация доменов
Для каждого домена из `domains.txt` скрипт обращается к Chromium headless API и классифицирует результат:
| Результат API | Действие |
|---|---|
| JSON с `relatedDomains` | **Сайт** — добавляется домен + все связанные субдомены |
| JSON с `error: ERR_NAME_NOT_RESOLVED` или `Timeout` | **Мёртвый домен** — пропускается, в конфиг не попадает |
| JSON с любой другой ошибкой (`ERR_CERT_*`, `ERR_CONNECTION_REFUSED` и т.д.) | **Сервис** — добавляется только сам домен без субдоменов (не HTTP-сервис: SMTP, VPN, игровой сервер и т.п.) |
| API недоступен (не 200 / не JSON после всех попыток) | **Fallback** — домен всё равно добавляется без субдоменов; фиксируется в разделе отчёта `API FAILURES` |
> Любой домен из `domains.txt` гарантированно попадает в финальный конфиг, даже если API недоступен.
### Формат выходных файлов
Домены группируются по базовому домену с комментарием:
```
# example.com — 4 subdomains
ipset=/example.com/bbrkn
ipset=/static.example.com/bbrkn
ipset=/api.example.com/bbrkn
...
# some-service.com — 0 subdomains
ipset=/some-service.com/bbrkn
```
---
## Структура проекта
```
bbrkn/
├── domains.txt # список доменов для обхода
├── Makefile # управление сборкой и деплоем
├── scripts/
│ ├── generate-configs.sh # генерация dnsmasq-конфигов
│ └── deploy-to-gateway.sh # деплой в Pi-hole + rollback
└── .forgejo/workflows/deploy.yaml # CI/CD на self-hosted runner
```
---
## Makefile
```bash
make all # generate + deploy (полный цикл)
make generate # только сгенерировать конфиги
make deploy # только задеплоить в Pi-hole
make clean # удалить сгенерированные конфиги из /tmp
make cache-clean # очистить кэш API-ответов (.cache/api/)
make check # проверить синтаксис domains.txt
```
---
## Переменные окружения
### generate-configs.sh
| Переменная | По умолчанию | Описание |
|---|---|---|
| `DOMAINS_FILE` | `domains.txt` | Входной файл со списком доменов |
| `IPSET_CONF` | `/tmp/91-ipset-bbrkn.conf` | Выходной файл правил ipset |
| `RESOLVE_CONF` | `/tmp/92-resolve-bbrkn.conf` | Выходной файл правил server= |
| `CHROME_SERVER` | `http://127.0.0.1:3000` | Адрес Chromium headless API |
| `DNS_SERVER` | `8.8.8.8` | DNS-сервер для резолвинга через VPN |
| `IGNORE_PARTS` | _(пусто)_ | Подстроки через пробел — домены с совпадением игнорируются |
| `CACHE_DIR` | `.cache/api` | Директория кэша API-ответов |
| `CACHE_TTL_DAYS` | `15` | Время жизни кэша в днях; `0` — отключить кэш |
| `STRICT_MODE` | `0` | `1` — завершать с кодом 1 при любых ошибках API |
| `DEBUG` | `0` | `1` — включить подробный лог |
| `DEBUG_LOG` | `/tmp/generate-configs.debug.log` | Путь к файлу отладочного лога |
### deploy-to-gateway.sh
| Переменная | По умолчанию | Описание |
|---|---|---|
| `TARGET_DIR` | `/opt/appdata/pihole/etc/dnsmasq.d` | Директория dnsmasq в Pi-hole |
| `IPSET_CONF` | `/tmp/91-ipset-bbrkn.conf` | Сгенерированный файл ipset |
| `RESOLVE_CONF` | `/tmp/92-resolve-bbrkn.conf` | Сгенерированный файл server= |
| `DOCKER_CONTAINER` | `pihole` | Имя Docker-контейнера Pi-hole |
| `DNS_LISTEN_ADDR` | `127.0.0.1` | Адрес, на котором Pi-hole слушает DNS |
| `DNS_CHECK_DOMAIN` | `google.com` | Домен для DNS health check после рестарта |
---
## Кэш API-ответов
При каждом запуске ответы Chromium API сохраняются в `$CACHE_DIR/<domain>.json`. При повторном запуске:
- Если файл кэша **свежее** `CACHE_TTL_DAYS` дней → ответ берётся из кэша, Chromium не запускается.
- Если файл **старше** TTL → перезапрашивается и кэш обновляется.
- Если кэш **повреждён** (невалидный JSON) → перезапрашивается автоматически.
На self-hosted runner workspace сохраняется между запусками, поэтому повторные CI-запуски значительно быстрее.
```bash
# Сбросить кэш и запросить всё заново
make cache-clean && make generate
# Отключить кэш для одного запуска
CACHE_TTL_DAYS=0 make generate
```
---
## Деплой и автооткат
`deploy-to-gateway.sh` выполняет следующие шаги:
1. Проверяет наличие сгенерированных конфигов.
2. Создаёт timestamped-бэкапы текущих конфигов в `TARGET_DIR`.
3. Копирует новые конфиги в `TARGET_DIR`.
4. Перезапускает контейнер Pi-hole.
5. Ждёт запуска (до 30 секунд, polling через `docker inspect`).
6. Выполняет DNS health check через `dig` / `nslookup`.
7. При ошибке на шагах 5 или 6 — **автоматически откатывается**: восстанавливает бэкапы и перезапускает контейнер со старой конфигурацией.
---
## Запуск вручную
```bash
# Полный цикл
make all
# Только генерация (без деплоя)
make generate
# Dry-run — проверить что будет сгенерировано, ничего не записывать
./scripts/generate-configs.sh --dry-run
# Отладочный режим
DEBUG=1 make generate
# Строгий режим — завершить с ошибкой если часть доменов не опросилась
STRICT_MODE=1 make generate
```
---
## CI/CD
Workflow запускается при push в `main`, а также **вручную из UI** (`workflow_dispatch`).
```
push → main
└─ make clean
└─ make all
├─ generate-configs.sh (генерация с кэшем)
└─ deploy-to-gateway.sh (деплой + rollback)
└─ upload artifacts (конфиги + debug-лог, даже при падении)
```
Self-hosted runner работает непосредственно на шлюзе, поэтому деплой происходит локально без SSH.
---
## Пример отчёта
```
===== DEBUG REPORT =====
Input file: domains.txt
Raw input lines: 170
Processed lines: 170
Normalized OK: 165
Normalized skipped: 5
Ignored (IGNORE_PARTS): 12
Cache hits: 148 (TTL: 15d, dir: .cache/api)
Cache expired/refetched: 3
API success (sites): 11
API error/failed: 3
Related domains added: 87
Final unique domains: 256
---- VALID BASE SITES ----
example.com
another-site.org
...
---- API FAILURES (added without subdomains) ----
some-domain.com → api_failed_added_without_subdomains
...
===== END DEBUG REPORT =====
```
---
## Зависимости
| Инструмент | Где используется |
|---|---|
| `bash` 4.2+ | оба скрипта |
| `curl` | запросы к Chromium API |
| `jq` | парсинг JSON-ответов |
| `docker` | перезапуск Pi-hole |
| `ipset` | сброс маршрутов после деплоя |
| `dig` или `nslookup` | DNS health check (опционально) |
---
## Лицензия
MIT