From c16466a85454056f23993492000678c6c14cfafe Mon Sep 17 00:00:00 2001 From: goodvin Date: Wed, 25 Mar 2026 20:17:03 +0300 Subject: [PATCH 01/27] Update domains.txt --- domains.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/domains.txt b/domains.txt index adf0d81..8035501 100644 --- a/domains.txt +++ b/domains.txt @@ -175,4 +175,5 @@ marketplace.visualstudio.com files.oaiusercontent.com notebooklm.google notebooklm.google.com -maps-cdn.zepp.com \ No newline at end of file +maps-cdn.zepp.com +tunnelbroker.net \ No newline at end of file From 9826a98e084e48895c68c5d40f98156c0b7f3352 Mon Sep 17 00:00:00 2001 From: goodvin Date: Mon, 6 Apr 2026 21:18:20 +0300 Subject: [PATCH 02/27] ADD DOMAIN: ingage.tech --- domains.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/domains.txt b/domains.txt index 8035501..6b5520b 100644 --- a/domains.txt +++ b/domains.txt @@ -176,4 +176,5 @@ files.oaiusercontent.com notebooklm.google notebooklm.google.com maps-cdn.zepp.com -tunnelbroker.net \ No newline at end of file +tunnelbroker.net +ingage.tech \ No newline at end of file From 19d9c456e42425f6c380fda3ea4c70e15c4cbdfb Mon Sep 17 00:00:00 2001 From: goodvin Date: Mon, 6 Apr 2026 21:40:39 +0300 Subject: [PATCH 03/27] ADD DOMAIN: ex.ingage.tech --- domains.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/domains.txt b/domains.txt index 6b5520b..74a14ce 100644 --- a/domains.txt +++ b/domains.txt @@ -177,4 +177,5 @@ notebooklm.google notebooklm.google.com maps-cdn.zepp.com tunnelbroker.net -ingage.tech \ No newline at end of file +ingage.tech +ex.ingage.tech \ No newline at end of file From 23bcba9287e708ff718726459496c0d859a7631b Mon Sep 17 00:00:00 2001 From: goodvin Date: Tue, 14 Apr 2026 22:38:35 +0300 Subject: [PATCH 04/27] ADD DOMAIN: asternos --- domains.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/domains.txt b/domains.txt index 74a14ce..2641972 100644 --- a/domains.txt +++ b/domains.txt @@ -178,4 +178,6 @@ notebooklm.google.com maps-cdn.zepp.com tunnelbroker.net ingage.tech -ex.ingage.tech \ No newline at end of file +ex.ingage.tech +asternos.dev +asternos.com \ No newline at end of file From 4fdcb7c7c6c5eb7a0c0dc1fc3cdf8ac9a35c9d21 Mon Sep 17 00:00:00 2001 From: goodvin Date: Sun, 19 Apr 2026 11:25:01 +0300 Subject: [PATCH 05/27] ADD DOMAIN: synthetic.new --- domains.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/domains.txt b/domains.txt index 2641972..bcc3c92 100644 --- a/domains.txt +++ b/domains.txt @@ -180,4 +180,5 @@ tunnelbroker.net ingage.tech ex.ingage.tech asternos.dev -asternos.com \ No newline at end of file +asternos.com +synthetic.new \ No newline at end of file From d31c35b2fc8451cba18dbc5d6c5c862a5f688dde Mon Sep 17 00:00:00 2001 From: goodvin Date: Sun, 19 Apr 2026 11:50:58 +0300 Subject: [PATCH 06/27] Update .forgejo/workflows/deploy.yaml --- .forgejo/workflows/deploy.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.forgejo/workflows/deploy.yaml b/.forgejo/workflows/deploy.yaml index c677c24..ea7bc57 100644 --- a/.forgejo/workflows/deploy.yaml +++ b/.forgejo/workflows/deploy.yaml @@ -19,7 +19,10 @@ jobs: RESOLVE_CONF: /tmp/92-resolve-bbrkn.conf # DNS-сервер для резолвинга - DNS_SERVER: 10.100.1.2 + DNS_SERVER: 8.8.8.8 + + # Gekata + CHROME_SERVER: http://10.77.2.2:3000 # Директория конфигов на шлюзе TARGET_DIR: /opt/appdata/pihole/etc/dnsmasq.d From eea82a4fa5ccbee6d3db85ad905aef156ffdcc48 Mon Sep 17 00:00:00 2001 From: goodvin Date: Mon, 20 Apr 2026 23:25:39 +0300 Subject: [PATCH 07/27] ADD DOMAIN: www.nokia3310lteproject.com --- domains.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/domains.txt b/domains.txt index bcc3c92..4db89d4 100644 --- a/domains.txt +++ b/domains.txt @@ -181,4 +181,5 @@ ingage.tech ex.ingage.tech asternos.dev asternos.com -synthetic.new \ No newline at end of file +synthetic.new +www.nokia3310lteproject.com \ No newline at end of file From 3af4e6333becb1e56c716da4bf13b6bf73fe9cd2 Mon Sep 17 00:00:00 2001 From: goodvin Date: Tue, 21 Apr 2026 19:23:26 +0300 Subject: [PATCH 08/27] ADD DOMAIN: lowendbox.com --- domains.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/domains.txt b/domains.txt index 4db89d4..344a80e 100644 --- a/domains.txt +++ b/domains.txt @@ -182,4 +182,5 @@ ex.ingage.tech asternos.dev asternos.com synthetic.new -www.nokia3310lteproject.com \ No newline at end of file +www.nokia3310lteproject.com +lowendbox.com \ No newline at end of file From 3318983ccb701f940b1e8cd3c4ec932c8a2c6b4c Mon Sep 17 00:00:00 2001 From: goodvin Date: Wed, 22 Apr 2026 22:50:37 +0300 Subject: [PATCH 09/27] ADD DOMAIN: ghcr.io github.com --- domains.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/domains.txt b/domains.txt index 344a80e..9e219bf 100644 --- a/domains.txt +++ b/domains.txt @@ -183,4 +183,6 @@ asternos.dev asternos.com synthetic.new www.nokia3310lteproject.com -lowendbox.com \ No newline at end of file +lowendbox.com +ghcr.io +github.com \ No newline at end of file From c1edbc87e75c6cf77c795ea07ffe625d8bda3f1e Mon Sep 17 00:00:00 2001 From: goodvin Date: Fri, 24 Apr 2026 22:13:48 +0300 Subject: [PATCH 10/27] ADD DOMAIN: www.iplocate.io --- domains.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/domains.txt b/domains.txt index 9e219bf..b400476 100644 --- a/domains.txt +++ b/domains.txt @@ -185,4 +185,5 @@ synthetic.new www.nokia3310lteproject.com lowendbox.com ghcr.io -github.com \ No newline at end of file +github.com +www.iplocate.io \ No newline at end of file From a62a4d7674fd55fb782472de210ecad93f591d1b Mon Sep 17 00:00:00 2001 From: goodvin Date: Sat, 25 Apr 2026 19:56:11 +0300 Subject: [PATCH 11/27] ADD DOMAINS: image.tmdb.org tmdb.org --- domains.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/domains.txt b/domains.txt index b400476..18babcd 100644 --- a/domains.txt +++ b/domains.txt @@ -186,4 +186,6 @@ www.nokia3310lteproject.com lowendbox.com ghcr.io github.com -www.iplocate.io \ No newline at end of file +www.iplocate.io +image.tmdb.org +tmdb.org \ No newline at end of file From 092e2b7aeaa08bc463c717d9dde253762e102752 Mon Sep 17 00:00:00 2001 From: goodvin Date: Thu, 28 May 2026 20:41:21 +0300 Subject: [PATCH 12/27] Update .forgejo/workflows/deploy.yaml --- .forgejo/workflows/deploy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/deploy.yaml b/.forgejo/workflows/deploy.yaml index ea7bc57..5538184 100644 --- a/.forgejo/workflows/deploy.yaml +++ b/.forgejo/workflows/deploy.yaml @@ -22,7 +22,7 @@ jobs: DNS_SERVER: 8.8.8.8 # Gekata - CHROME_SERVER: http://10.77.2.2:3000 + CHROME_SERVER: http://10.77.1.2:3000 # Директория конфигов на шлюзе TARGET_DIR: /opt/appdata/pihole/etc/dnsmasq.d From 18917a90360a6ef1e144a4c3feb24bf39913fc45 Mon Sep 17 00:00:00 2001 From: goodvin Date: Thu, 28 May 2026 20:47:02 +0300 Subject: [PATCH 13/27] Update domains.txt --- domains.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/domains.txt b/domains.txt index 18babcd..62990c6 100644 --- a/domains.txt +++ b/domains.txt @@ -188,4 +188,9 @@ ghcr.io github.com www.iplocate.io image.tmdb.org -tmdb.org \ No newline at end of file +tmdb.org +netlify.com +netlifydns.com +netlifyusercontent.com +www.ubuntu-touch.io +ubports.com \ No newline at end of file From ef30817cf8464c0a2fc8e25a1c7bad2acd309ef9 Mon Sep 17 00:00:00 2001 From: goodvin Date: Thu, 28 May 2026 20:56:35 +0300 Subject: [PATCH 14/27] Update Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 396fcc4..5106c8e 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ IPSET_CONF ?= /tmp/91-ipset-bbrkn.conf RESOLVE_CONF ?= /tmp/92-resolve-bbrkn.conf CHROME_SERVER ?= http://10.100.1.2:3000 DOMAINS_FILE ?= domains.txt -DNS_SERVER ?= 10.100.1.2 +DNS_SERVER ?= 8.8.8.8 CACHE_DIR ?= .cache/api CACHE_TTL_DAYS ?= 15 From d81a4ab3b2f900ede21cebf587a93bcf2e741586 Mon Sep 17 00:00:00 2001 From: goodvin Date: Fri, 24 Jul 2026 23:55:16 +0300 Subject: [PATCH 15/27] Migrate bbrkn from legacy ipset to nftables sets The gateway (Archie) routes bbrkn domains via nft sets bbrkn_v4/bbrkn_v6 (inet filter), not the legacy ipset. bbrkn was still emitting dead `ipset=/domain/bbrkn` directives (no such ipset exists) plus a 92-resolve pointing at 8.8.8.8, both overridden by hand-maintained files on the host. This makes bbrkn the generator of record for the real scheme. generate-configs.sh: - emit `nftset=/domain/$NFTSET_SPEC` (default 4#inet#filter#bbrkn_v4, 6#inet#filter#bbrkn_v6) into 90-nftset.conf instead of ipset= into 91-ipset-bbrkn.conf - DNS_SERVER default 8.8.8.8 -> 127.0.0.1#5350 (host dnsmasq pihole delegates bbrkn domains to for VPN resolution + nftset capture) deploy-to-gateway.sh: two targets, two instances - 90-nftset.conf -> host /etc/dnsmasq.d (:5350), 92-resolve -> pihole - full restart of both (dnsmasq SIGHUP does NOT re-read nftset=/server=) - flush nft sets bbrkn_v4/bbrkn_v6 instead of `ipset flush bbrkn` - add end-to-end nftset-capture health check via :5350 - rollback restores both files and restarts both instances Makefile/workflow: rename IPSET_CONF->NFTSET_CONF, add NFTSET_TARGET_DIR and HOST_DNSMASQ_SVC, DNS_SERVER=127.0.0.1#5350 (escaped `\#` in Make, quoted in YAML), note runner is ephemeral (cold gekata crawl). Docs: README + new CLAUDE.md describe the two-dnsmasq / nft-set model; exit-node DPI failover (10.77.1.2/10.77.2.2) documented as external (wg-ha.service), not owned by bbrkn. Co-Authored-By: Claude Opus 4.8 --- .forgejo/workflows/deploy.yaml | 17 ++- CLAUDE.md | 53 ++++++++++ Makefile | 21 ++-- README.md | 89 ++++++++++------ scripts/deploy-to-gateway.sh | 186 ++++++++++++++++++++++----------- scripts/generate-configs.sh | 16 ++- 6 files changed, 271 insertions(+), 111 deletions(-) create mode 100644 CLAUDE.md diff --git a/.forgejo/workflows/deploy.yaml b/.forgejo/workflows/deploy.yaml index 5538184..842ef2f 100644 --- a/.forgejo/workflows/deploy.yaml +++ b/.forgejo/workflows/deploy.yaml @@ -15,21 +15,28 @@ jobs: DOMAINS_FILE: domains.txt # Временные конфиги (куда пишутся generate-configs.sh) - IPSET_CONF: /tmp/91-ipset-bbrkn.conf + NFTSET_CONF: /tmp/90-nftset.conf RESOLVE_CONF: /tmp/92-resolve-bbrkn.conf - # DNS-сервер для резолвинга - DNS_SERVER: 8.8.8.8 + # Резолвер, которому pihole делегирует bbrkn-домены (host dnsmasq :5350). + # Кавычки обязательны — '#' иначе может трактоваться как комментарий. + DNS_SERVER: "127.0.0.1#5350" # Gekata CHROME_SERVER: http://10.77.1.2:3000 - # Директория конфигов на шлюзе + # nftset-таргет: host-инстанс dnsmasq (:5350) грузит /etc/dnsmasq.d + NFTSET_TARGET_DIR: /etc/dnsmasq.d + + # resolve-таргет: pihole (docker) делегирует bbrkn-домены на :5350 TARGET_DIR: /opt/appdata/pihole/etc/dnsmasq.d # Имя контейнера Pi-hole DOCKER_CONTAINER: pihole + # systemd-сервис host-инстанса dnsmasq + HOST_DNSMASQ_SVC: dnsmasq + IGNORE_PARTS: "doubleclick yandex mail.ru bigsv.ru mts.ru rambler.ru yadro.ru aif.ru vkvideo.ru tns-counter.ru st.vk.com vk.com" # Кэш API-ответов (self-hosted runner сохраняет workspace между запусками) @@ -51,6 +58,6 @@ jobs: with: name: dnsmasq-configs path: | - ${{ env.IPSET_CONF }} + ${{ env.NFTSET_CONF }} ${{ env.RESOLVE_CONF }} ${{ env.DEBUG_LOG }} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..424ec83 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,53 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What this is + +Generates **Pi-hole / dnsmasq** configs that route blocked domains through a WireGuard VPN via **nftables sets**. Input is `domains.txt`; output is two dnsmasq files deployed to **two different dnsmasq instances** on the Archie gateway (see Architecture). README.md (Russian) is the authoritative reference — read it for the classification table, env-var descriptions, and report format. + +> **Routing model (Archie gateway):** client → pihole (docker, `:53`) delegates bbrkn domains via `92-resolve-bbrkn.conf` (`server=/domain/127.0.0.1#5350`) to a **host dnsmasq on `:5350`**, which resolves via VPN and applies `90-nftset.conf` (`nftset=…#bbrkn_v4,…#bbrkn_v6`) to populate nft sets `inet filter bbrkn_v4`/`bbrkn_v6`. An nft mangle chain marks packets to those set IPs (`0x1`/`0x2`) and policy-routing sends them out a WireGuard exit. Dual-exit DPI-bypass failover (`10.77.1.2`/`10.77.2.2`) is handled by `wg-ha.service`, **external to bbrkn** — bbrkn only maintains domain→set membership. + +## Commands + +```bash +make all # generate + deploy (full cycle) +make generate # generate configs into /tmp only +make deploy # deploy existing configs to Pi-hole +make check # validate domains.txt syntax (regex per line) +make clean # remove generated /tmp configs +make cache-clean # wipe .cache/api/ + +./scripts/generate-configs.sh --dry-run # generate, print report, write nothing +DEBUG=1 make generate # verbose log to $DEBUG_LOG +STRICT_MODE=1 make generate # exit 1 if any domain's API query failed +CACHE_TTL_DAYS=0 make generate # bypass API cache for one run +``` + +No test suite. Verification = `--dry-run` + reading the DEBUG REPORT (see README) and diffing generated `/tmp` configs. + +## Architecture + +Two bash scripts, driven by env vars (defaults in each script's header, overridden by Makefile and CI): + +**`scripts/generate-configs.sh`** — classifies each domain by querying a Chromium headless API (`$CHROME_SERVER/domains?domain=`), builds the ordered domain list, writes both config files in one batch. Key logic: + +- **Classification** (drives whether subdomains are added): API JSON with `relatedDomains` → *site* (base + related subdomains); `ERR_NAME_NOT_RESOLVED`/`Timeout` → *dead*, skipped entirely; any other API error → *service* (base only, no subdomains); API fully unreachable → *fallback* (base only, recorded under `API FAILURES`). **Invariant: every domain in `domains.txt` reaches the output unless it's dead.** +- **State is held in bash associative arrays** (`DOM_ROLE`, `EXPANDED`, `SOURCES`, `SITE_RELATED`, …). The main loop deliberately avoids subshells so counters survive — do not wrap counter-mutating code in `$(...)` or pipes. +- **Caching**: successful API responses cached to `$CACHE_DIR/.json`, reused while newer than `CACHE_TTL_DAYS`; corrupt or expired entries refetch. `find -mtime +N` decides freshness. +- **Ordering pass** emits each base domain, a `# domain — N subdomains` comment, then its not-yet-written related domains; a related domain is attributed to whichever base comes first alphabetically. A final uniqueness check `exit 5`s (config NOT written) if any domain would appear twice — treat that as a real bug in the ordering logic, not a data issue. +- Output lines: `nftset=//$NFTSET_SPEC` (NFTSET_CONF → `90-nftset.conf`; default `NFTSET_SPEC=4#inet#filter#bbrkn_v4,6#inet#filter#bbrkn_v6`) and `server=//$DNS_SERVER` (RESOLVE_CONF → `92-resolve-bbrkn.conf`; `DNS_SERVER` default `127.0.0.1#5350`, the host resolver pihole delegates to). + +**`scripts/deploy-to-gateway.sh`** — deploys to **two targets**: `90-nftset.conf` → host `$NFTSET_TARGET_DIR` (`/etc/dnsmasq.d`, the `:5350` instance) and `92-resolve-bbrkn.conf` → pihole `$RESOLVE_TARGET_DIR` (`/opt/appdata/pihole/etc/dnsmasq.d`). Flow: timestamped-backup both → copy → **`systemctl restart $HOST_DNSMASQ_SVC` + `docker restart $DOCKER_CONTAINER`** (a full restart is required — dnsmasq **SIGHUP does not re-read `nftset=`/`server=` directives**, only hosts files) → wait for `:5350` listening + pihole `running` → `nft flush set` on `bbrkn_v4`/`bbrkn_v6` → DNS health check via pihole `:53` → end-to-end nftset-capture check (resolve a bbrkn domain via `:5350`, assert the set populated). **On any failure it calls `rollback()`**, restoring both backups and restarting both instances. Edits must preserve that rollback path — a broken deploy can break DNS for every client on the gateway. + +## Deployment / CI + +`.forgejo/workflows/deploy.yaml` runs on push to `main` (and `workflow_dispatch`) on a **self-hosted `forgejo-runner` on the gateway itself** — no SSH, deploy is local. Note: the runner is **ephemeral** (no persisted `.cache/api` was found on the host despite the older "persists workspace" claim), so a fresh CI run performs a **cold gekata crawl of every domain** (~30-70s each, serialized) — the first run after a cache reset is slow and heavy on gekata. CI runs `make clean && make all` and uploads the generated configs + debug log as artifacts even on failure. CI-only values (`NFTSET_TARGET_DIR`, `TARGET_DIR`, `CHROME_SERVER`, `DOCKER_CONTAINER`, `HOST_DNSMASQ_SVC`, `DNS_SERVER`, `IGNORE_PARTS`) live in the workflow `env:` block, not the scripts. + +## Gotchas + +- `domains.txt` is the primary data file; commits are typically domain additions. Run `make check` after editing it. +- `IGNORE_PARTS` is space-separated substrings; any domain (base or related) containing one is dropped. +- **`#` gotcha**: `DNS_SERVER=127.0.0.1#5350` — `#` starts a comment in Makefiles (escape as `127.0.0.1\#5350`) and can start one in YAML (quote it). The scripts themselves take the plain value. +- The nft sets (`bbrkn_v4`/`bbrkn_v6`, `inet filter`) and mangle/routing rules live on the gateway (`/etc/nftables.d/`, Ansible-managed) — bbrkn only fills the sets via dnsmasq, it does not define them. +- Requires `bash` 4.2+ (associative arrays, `${var,,}`), `curl`, `jq`, `docker`, `nft`, `systemctl`; `dig`/`nslookup` optional (health checks skipped if absent). diff --git a/Makefile b/Makefile index 5106c8e..8758a4d 100644 --- a/Makefile +++ b/Makefile @@ -3,16 +3,19 @@ # =============================== # --- Переменные окружения (с дефолтами для локального запуска) --- -IPSET_CONF ?= /tmp/91-ipset-bbrkn.conf -RESOLVE_CONF ?= /tmp/92-resolve-bbrkn.conf -CHROME_SERVER ?= http://10.100.1.2:3000 -DOMAINS_FILE ?= domains.txt -DNS_SERVER ?= 8.8.8.8 -CACHE_DIR ?= .cache/api -CACHE_TTL_DAYS ?= 15 +NFTSET_CONF ?= /tmp/90-nftset.conf +RESOLVE_CONF ?= /tmp/92-resolve-bbrkn.conf +CHROME_SERVER ?= http://10.77.1.2:3000 +DOMAINS_FILE ?= domains.txt +# Резолвер, которому pihole делегирует bbrkn-домены (host dnsmasq :5350). +# ВНИМАНИЕ: '#' в Makefile — начало комментария, экранируем как '\#'. +DNS_SERVER ?= 127.0.0.1\#5350 +NFTSET_TARGET_DIR ?= /etc/dnsmasq.d +CACHE_DIR ?= .cache/api +CACHE_TTL_DAYS ?= 15 # Экспортируем переменные, чтобы они были доступны внутри shell-скриптов -export IPSET_CONF RESOLVE_CONF CHROME_SERVER DOMAINS_FILE DNS_SERVER CACHE_DIR CACHE_TTL_DAYS +export NFTSET_CONF RESOLVE_CONF CHROME_SERVER DOMAINS_FILE DNS_SERVER NFTSET_TARGET_DIR CACHE_DIR CACHE_TTL_DAYS # --- Основные цели --- .PHONY: all clean cache-clean check generate deploy @@ -22,7 +25,7 @@ all: generate deploy clean: @echo "🧹 Очистка временных файлов" - @rm -f $(IPSET_CONF) $(RESOLVE_CONF) + @rm -f $(NFTSET_CONF) $(RESOLVE_CONF) cache-clean: @echo "🗑 Очистка кэша API ($(CACHE_DIR))" diff --git a/README.md b/README.md index 2b5d65e..d63fb2b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # bbrkn — DNS Bypass Config Generator -Автоматизирует генерацию конфигураций **Pi-hole / dnsmasq** для маршрутизации трафика заблокированных доменов через VPN-туннель (WireGuard). +Автоматизирует генерацию конфигураций **Pi-hole / dnsmasq** для маршрутизации трафика заблокированных доменов через VPN-туннель (WireGuard) с помощью **nftables-сетов**. ## Как это работает @@ -8,25 +8,29 @@ flowchart TD A[domains.txt] --> B[generate-configs.sh] B -->|кэш .cache/api| B - B -->|MISS / EXPIRED| C[Chromium headless API] + B -->|MISS / EXPIRED| C[gekata: Chromium headless API] C --> B - B --> D[91-ipset-bbrkn.conf] + B --> D[90-nftset.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 + F -->|backup + copy + systemctl restart| G[host dnsmasq :5350
/etc/dnsmasq.d/] + F -->|backup + copy + docker restart| H[Pi-hole :53] + F -->|health check + nftset check| H + F -.->|rollback если упал| G subgraph "Трафик клиента" K[Клиент] -->|DNS запрос| H - H -->|домен в ipset → mark| I[iptables] - I --> J[WireGuard VPN] + H -->|"server= → 127.0.0.1#5350"| G + G -->|"резолв через VPN + nftset="| N["nft set bbrkn_v4 / bbrkn_v6"] + N -->|ip daddr @bbrkn → mark| I[nft mangle] + I -->|policy routing| J[WireGuard exit
10.77.1.2 / 10.77.2.2] J --> L[Интернет] end ``` +> **Два инстанса dnsmasq.** Pi-hole (:53) отдаёт клиентов и делегирует bbrkn-домены host-инстансу на :5350 (`92-resolve`). Инстанс :5350 резолвит их через VPN и по директиве `nftset=` (`90-nftset.conf`) кладёт полученные IP в nft-сеты `bbrkn_v4`/`bbrkn_v6`. nft-mangle маркирует пакеты к этим IP, policy-routing уводит их в WireGuard. **Выбор из двух exit-нод (DPI-обход РКН) и failover — вне bbrkn**, ими управляет `wg-ha.service` на шлюзе. + --- ## Классификация доменов @@ -46,15 +50,26 @@ flowchart TD Домены группируются по базовому домену с комментарием: +`90-nftset.conf` (наполняет nft-сеты): + ``` # example.com — 4 subdomains -ipset=/example.com/bbrkn -ipset=/static.example.com/bbrkn -ipset=/api.example.com/bbrkn +nftset=/example.com/4#inet#filter#bbrkn_v4,6#inet#filter#bbrkn_v6 +nftset=/static.example.com/4#inet#filter#bbrkn_v4,6#inet#filter#bbrkn_v6 +nftset=/api.example.com/4#inet#filter#bbrkn_v4,6#inet#filter#bbrkn_v6 ... # some-service.com — 0 subdomains -ipset=/some-service.com/bbrkn +nftset=/some-service.com/4#inet#filter#bbrkn_v4,6#inet#filter#bbrkn_v6 +``` + +`92-resolve-bbrkn.conf` (делегирует те же домены host-инстансу :5350): + +``` +# example.com — 4 subdomains +server=/example.com/127.0.0.1#5350 +server=/static.example.com/127.0.0.1#5350 +... ``` --- @@ -93,10 +108,11 @@ make check # проверить синтаксис domains.txt | Переменная | По умолчанию | Описание | |---|---|---| | `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 | +| `NFTSET_CONF` | `/tmp/90-nftset.conf` | Выходной файл директив `nftset=` | +| `NFTSET_SPEC` | `4#inet#filter#bbrkn_v4,6#inet#filter#bbrkn_v6` | Цель nftset: `<4\|6>#family#table#set` | +| `RESOLVE_CONF` | `/tmp/92-resolve-bbrkn.conf` | Выходной файл директив `server=` | +| `CHROME_SERVER` | `http://127.0.0.1:3000` | Адрес gekata (Chromium headless API) | +| `DNS_SERVER` | `127.0.0.1#5350` | Резолвер, которому pihole делегирует домены (host dnsmasq :5350) | | `IGNORE_PARTS` | _(пусто)_ | Подстроки через пробел — домены с совпадением игнорируются | | `CACHE_DIR` | `.cache/api` | Директория кэша API-ответов | | `CACHE_TTL_DAYS` | `15` | Время жизни кэша в днях; `0` — отключить кэш | @@ -108,12 +124,17 @@ make check # проверить синтаксис domains.txt | Переменная | По умолчанию | Описание | |---|---|---| -| `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= | +| `NFTSET_CONF` | `/tmp/90-nftset.conf` | Сгенерированный файл `nftset=` | +| `NFTSET_TARGET_DIR` | `/etc/dnsmasq.d` | Директория host-инстанса dnsmasq (:5350) | +| `RESOLVE_CONF` | `/tmp/92-resolve-bbrkn.conf` | Сгенерированный файл `server=` | +| `TARGET_DIR` | `/opt/appdata/pihole/etc/dnsmasq.d` | Директория dnsmasq в Pi-hole (для `92-resolve`) | | `DOCKER_CONTAINER` | `pihole` | Имя Docker-контейнера Pi-hole | -| `DNS_LISTEN_ADDR` | `127.0.0.1` | Адрес, на котором Pi-hole слушает DNS | -| `DNS_CHECK_DOMAIN` | `google.com` | Домен для DNS health check после рестарта | +| `HOST_DNSMASQ_SVC` | `dnsmasq` | systemd-сервис host-инстанса dnsmasq | +| `NFT_SETS` | `inet filter bbrkn_v4;inet filter bbrkn_v6` | Сеты для flush после деплоя (через `;`) | +| `DNS_LISTEN_ADDR` | `127.0.0.1` | Адрес, на котором Pi-hole слушает DNS (:53) | +| `DNS_CHECK_DOMAIN` | `google.com` | Домен базового DNS health check | +| `NFTSET_CHECK_DOMAIN` | `chatgpt.com` | bbrkn-домен для end-to-end проверки nftset-захвата | +| `RESOLVER_ADDR` / `RESOLVER_PORT` | `127.0.0.1` / `5350` | Адрес host-инстанса для проверки захвата | --- @@ -125,7 +146,7 @@ make check # проверить синтаксис domains.txt - Если файл **старше** TTL → перезапрашивается и кэш обновляется. - Если кэш **повреждён** (невалидный JSON) → перезапрашивается автоматически. -На self-hosted runner workspace сохраняется между запусками, поэтому повторные CI-запуски значительно быстрее. +> **Внимание:** forgejo-runner на шлюзе **эфемерный** — workspace (и `.cache/api`) между запусками не сохраняется. Поэтому первый CI-прогон после сброса кэша выполняет **холодный обход всех доменов через gekata** (~30-70 с на домен, последовательно) и работает долго. Для быстрых повторных прогонов держите тёплый `.cache/api` в постоянной директории (`CACHE_DIR`). ```bash # Сбросить кэш и запросить всё заново @@ -139,15 +160,16 @@ CACHE_TTL_DAYS=0 make generate ## Деплой и автооткат -`deploy-to-gateway.sh` выполняет следующие шаги: +`deploy-to-gateway.sh` деплоит в **два инстанса dnsmasq** и выполняет шаги: -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 — **автоматически откатывается**: восстанавливает бэкапы и перезапускает контейнер со старой конфигурацией. +1. Проверяет наличие обоих сгенерированных конфигов. +2. Создаёт timestamped-бэкапы `90-nftset.conf` (host) и `92-resolve-bbrkn.conf` (pihole). +3. Копирует `90-nftset.conf` → `NFTSET_TARGET_DIR`, `92-resolve-bbrkn.conf` → `TARGET_DIR`. +4. Перезапускает **оба** инстанса: `systemctl restart $HOST_DNSMASQ_SVC` + `docker restart $DOCKER_CONTAINER`. Полный рестарт обязателен — dnsmasq по **SIGHUP не перечитывает** директивы `nftset=`/`server=`. +5. Ждёт: host-инстанс слушает `:5350` (до 15 с) и pihole в состоянии `running` (до 30 с). +6. Сбрасывает nft-сеты `bbrkn_v4`/`bbrkn_v6` (устаревшие IP иначе висят до timeout 24 ч). +7. Health check: базовый DNS через pihole `:53` + end-to-end проверка nftset-захвата (резолв bbrkn-домена через `:5350` → сет наполнился). +8. При ошибке на шагах 5–7 — **автоматически откатывается**: восстанавливает оба бэкапа и перезапускает оба инстанса со старой конфигурацией. --- @@ -226,10 +248,11 @@ some-domain.com → api_failed_added_without_subdomains | Инструмент | Где используется | |---|---| | `bash` 4.2+ | оба скрипта | -| `curl` | запросы к Chromium API | +| `curl` | запросы к gekata API | | `jq` | парсинг JSON-ответов | | `docker` | перезапуск Pi-hole | -| `ipset` | сброс маршрутов после деплоя | +| `nft` | flush nft-сетов + проверка захвата после деплоя | +| `systemctl` | рестарт host-инстанса dnsmasq | | `dig` или `nslookup` | DNS health check (опционально) | --- diff --git a/scripts/deploy-to-gateway.sh b/scripts/deploy-to-gateway.sh index 257df44..beadb0f 100644 --- a/scripts/deploy-to-gateway.sh +++ b/scripts/deploy-to-gateway.sh @@ -4,46 +4,64 @@ set -euo pipefail # ============================== # Конфигурация через переменные окружения # ============================== -TARGET_DIR="${TARGET_DIR:-/opt/appdata/pihole/etc/dnsmasq.d}" -IPSET_CONF="${IPSET_CONF:-/tmp/91-ipset-bbrkn.conf}" +# Сгенерированные конфиги (из generate-configs.sh) +NFTSET_CONF="${NFTSET_CONF:-/tmp/90-nftset.conf}" RESOLVE_CONF="${RESOLVE_CONF:-/tmp/92-resolve-bbrkn.conf}" +# Два таргета — два инстанса dnsmasq: +# NFTSET_CONF → host dnsmasq (:5350), грузит /etc/dnsmasq.d, наполняет nft-сеты +# RESOLVE_CONF → pihole (docker), делегирует bbrkn-домены инстансу :5350 +NFTSET_TARGET_DIR="${NFTSET_TARGET_DIR:-/etc/dnsmasq.d}" +RESOLVE_TARGET_DIR="${RESOLVE_TARGET_DIR:-${TARGET_DIR:-/opt/appdata/pihole/etc/dnsmasq.d}}" + DOCKER_CONTAINER="${DOCKER_CONTAINER:-pihole}" +# systemd-сервис host-инстанса dnsmasq (:5350) +HOST_DNSMASQ_SVC="${HOST_DNSMASQ_SVC:-dnsmasq}" -# Адрес, на котором слушает Pi-hole DNS (для health check) -DNS_LISTEN_ADDR="${DNS_LISTEN_ADDR:-127.0.0.1}" -# Домен для проверки DNS после рестарта +# nft-сеты для сброса после деплоя (иначе висят до timeout 24h). +# Формат: "family table set" — перечисляются через ';' в NFT_SETS. +NFT_SETS="${NFT_SETS:-inet filter bbrkn_v4;inet filter bbrkn_v6}" + +# --- Health check --- +DNS_LISTEN_ADDR="${DNS_LISTEN_ADDR:-127.0.0.1}" # pihole :53 (клиентский) DNS_CHECK_DOMAIN="${DNS_CHECK_DOMAIN:-google.com}" +# Домен, который ЕСТЬ в bbrkn-сете — для end-to-end проверки nftset-захвата +NFTSET_CHECK_DOMAIN="${NFTSET_CHECK_DOMAIN:-chatgpt.com}" +RESOLVER_ADDR="${RESOLVER_ADDR:-127.0.0.1}" # host dnsmasq (:5350) +RESOLVER_PORT="${RESOLVER_PORT:-5350}" +NFT_CHECK_SET="${NFT_CHECK_SET:-inet filter bbrkn_v4}" -# Имена назначения (без basename — используем параметрическую подстановку) -ipset_name="${IPSET_CONF##*/}" +# Имена/пути назначения (параметрическая подстановка, без basename) +nftset_name="${NFTSET_CONF##*/}" resolve_name="${RESOLVE_CONF##*/}" -ipset_dest="$TARGET_DIR/$ipset_name" -resolve_dest="$TARGET_DIR/$resolve_name" +nftset_dest="$NFTSET_TARGET_DIR/$nftset_name" +resolve_dest="$RESOLVE_TARGET_DIR/$resolve_name" -# Пути к бэкапам (заполняются ниже, нужны для rollback) -IPSET_BACKUP="" +# Пути к бэкапам (для rollback) +NFTSET_BACKUP="" RESOLVE_BACKUP="" -echo "Deploying configuration files to $TARGET_DIR" +echo "Deploying nftset config → $nftset_dest (host dnsmasq :$RESOLVER_PORT)" +echo "Deploying resolve config → $resolve_dest ($DOCKER_CONTAINER)" # ============================== # Проверка входных файлов # ============================== -if [ ! -f "$IPSET_CONF" ] || [ ! -f "$RESOLVE_CONF" ]; then - echo "Error: one or both config files not found!" +if [ ! -f "$NFTSET_CONF" ] || [ ! -f "$RESOLVE_CONF" ]; then + echo "Error: one or both generated config files not found!" + echo " NFTSET_CONF=$NFTSET_CONF RESOLVE_CONF=$RESOLVE_CONF" exit 1 fi # ============================== -# Функция отката: восстанавливает бэкапы и перезапускает контейнер +# Функция отката: восстанавливает бэкапы и перезапускает оба инстанса # ============================== rollback() { echo "[ROLLBACK] Restoring previous configuration..." local restored=false - if [ -n "$IPSET_BACKUP" ] && [ -f "$IPSET_BACKUP" ]; then - cp "$IPSET_BACKUP" "$ipset_dest" - echo "[ROLLBACK] Restored $ipset_dest" + if [ -n "$NFTSET_BACKUP" ] && [ -f "$NFTSET_BACKUP" ]; then + cp "$NFTSET_BACKUP" "$nftset_dest" + echo "[ROLLBACK] Restored $nftset_dest" restored=true fi if [ -n "$RESOLVE_BACKUP" ] && [ -f "$RESOLVE_BACKUP" ]; then @@ -52,7 +70,8 @@ rollback() { restored=true fi if $restored; then - echo "[ROLLBACK] Restarting $DOCKER_CONTAINER with previous config..." + echo "[ROLLBACK] Restarting host dnsmasq ($HOST_DNSMASQ_SVC) and $DOCKER_CONTAINER..." + systemctl restart "$HOST_DNSMASQ_SVC" 2>/dev/null || true docker restart "$DOCKER_CONTAINER" 2>/dev/null || true else echo "[ROLLBACK] No backups found, cannot restore" @@ -64,10 +83,10 @@ rollback() { # Бэкап существующих файлов # ============================== ts=$(date +%Y%m%d-%H%M%S) -if [ -f "$ipset_dest" ]; then - IPSET_BACKUP="${ipset_dest}.backup.${ts}" - cp "$ipset_dest" "$IPSET_BACKUP" - echo "Backed up $ipset_dest → $IPSET_BACKUP" +if [ -f "$nftset_dest" ]; then + NFTSET_BACKUP="${nftset_dest}.backup.${ts}" + cp "$nftset_dest" "$NFTSET_BACKUP" + echo "Backed up $nftset_dest → $NFTSET_BACKUP" fi if [ -f "$resolve_dest" ]; then RESOLVE_BACKUP="${resolve_dest}.backup.${ts}" @@ -78,16 +97,38 @@ fi # ============================== # Копирование новых файлов # ============================== -cp "$IPSET_CONF" "$TARGET_DIR/" -cp "$RESOLVE_CONF" "$TARGET_DIR/" +cp "$NFTSET_CONF" "$nftset_dest" +cp "$RESOLVE_CONF" "$resolve_dest" # ============================== -# Перезапуск контейнера +# Перезапуск обоих инстансов. +# ВАЖНО: dnsmasq по SIGHUP НЕ перечитывает директивы конфига (nftset=/server=), +# только hosts-файлы — новые домены подхватываются лишь полным рестартом. # ============================== +echo "Restarting host dnsmasq ($HOST_DNSMASQ_SVC)..." +systemctl restart "$HOST_DNSMASQ_SVC" + echo "Restarting $DOCKER_CONTAINER container..." docker restart "$DOCKER_CONTAINER" -# Ждём запуска — polling через docker inspect вместо sleep + docker ps +# --- Ждём host dnsmasq (:5350) --- +echo "Waiting for host dnsmasq on :$RESOLVER_PORT (up to 15s)..." +host_ready=false +for i in $(seq 1 15); do + if ss -uln 2>/dev/null | grep -q ":$RESOLVER_PORT "; then + host_ready=true + break + fi + sleep 1 +done +if ! $host_ready; then + echo "Error: host dnsmasq not listening on :$RESOLVER_PORT after 15s" + rollback + exit 1 +fi +echo "host dnsmasq is listening on :$RESOLVER_PORT" + +# --- Ждём pihole --- echo "Waiting for $DOCKER_CONTAINER to be ready (up to 30s)..." ready=false for i in $(seq 1 30); do @@ -98,52 +139,79 @@ for i in $(seq 1 30); do fi sleep 1 done - if ! $ready; then - echo "Error: $DOCKER_CONTAINER failed to start after 30s (status: $status)" + echo "Error: $DOCKER_CONTAINER failed to start after 30s (status: ${status:-unknown})" rollback exit 1 fi echo "$DOCKER_CONTAINER is running" # ============================== -# DNS health check +# Сброс nft-сетов (чистим устаревшие записи удалённых доменов; timeout 24h иначе) # ============================== -dns_health_check() { - if command -v dig >/dev/null 2>&1; then - dig +short +time=3 +tries=2 "@$DNS_LISTEN_ADDR" "$DNS_CHECK_DOMAIN" >/dev/null 2>&1 - elif command -v nslookup >/dev/null 2>&1; then - nslookup "$DNS_CHECK_DOMAIN" "$DNS_LISTEN_ADDR" >/dev/null 2>&1 +echo "Flushing nft sets..." +IFS=';' read -ra _sets <<< "$NFT_SETS" +for s in "${_sets[@]}"; do + [ -z "$s" ] && continue + # shellcheck disable=SC2086 + if nft flush set $s 2>/dev/null; then + echo " flushed set $s" else - echo "Warning: neither 'dig' nor 'nslookup' found, skipping DNS health check" - return 0 + echo " WARN: could not flush set $s" + fi +done + +# ============================== +# Health check 1: базовый DNS через pihole :53 +# ============================== +dns_query() { # + local addr="$1" port="$2" dom="$3" + if command -v dig >/dev/null 2>&1; then + dig +short +time=3 +tries=2 -p "$port" "@$addr" "$dom" 2>/dev/null + elif command -v nslookup >/dev/null 2>&1; then + nslookup -port="$port" "$dom" "$addr" 2>/dev/null | awk '/^Address: /{print $2}' fi } -echo "Running DNS health check ($DNS_CHECK_DOMAIN via $DNS_LISTEN_ADDR, up to 15 attempts)..." -dns_ok=false -for i in $(seq 1 15); do - if dns_health_check; then - dns_ok=true - echo "DNS health check passed (attempt $i)" - break +if ! command -v dig >/dev/null 2>&1 && ! command -v nslookup >/dev/null 2>&1; then + echo "Warning: neither 'dig' nor 'nslookup' found, skipping DNS health checks" +else + echo "DNS health check: $DNS_CHECK_DOMAIN via $DNS_LISTEN_ADDR:53 (up to 15 attempts)..." + dns_ok=false + for i in $(seq 1 15); do + if [ -n "$(dns_query "$DNS_LISTEN_ADDR" 53 "$DNS_CHECK_DOMAIN")" ]; then + dns_ok=true + echo " passed (attempt $i)" + break + fi + echo " attempt $i failed, retry in 1s..." + sleep 1 + done + if ! $dns_ok; then + echo "Error: DNS health check failed — pihole not resolving on $DNS_LISTEN_ADDR:53" + rollback + exit 1 fi - echo " attempt $i failed, retrying in 1s..." - sleep 1 -done -if ! $dns_ok; then - echo "Error: DNS health check failed after 15 attempts — Pi-hole not responding on $DNS_LISTEN_ADDR" - rollback - exit 1 + # ============================== + # Health check 2: end-to-end nftset-захват. + # Резолвим bbrkn-домен через :5350 и убеждаемся, что nft-сет наполнился. + # ============================== + echo "nftset capture check: $NFTSET_CHECK_DOMAIN via $RESOLVER_ADDR:$RESOLVER_PORT..." + dns_query "$RESOLVER_ADDR" "$RESOLVER_PORT" "$NFTSET_CHECK_DOMAIN" >/dev/null + sleep 1 + # shellcheck disable=SC2086 + set_count=$(nft list set $NFT_CHECK_SET 2>/dev/null | grep -cE '([0-9]{1,3}\.){3}[0-9]{1,3}' || echo 0) + if [ "${set_count:-0}" -gt 0 ]; then + echo " passed — set '$NFT_CHECK_SET' populated ($set_count element line(s))" + else + echo "Error: nftset '$NFT_CHECK_SET' still empty after resolving $NFTSET_CHECK_DOMAIN" + echo " host dnsmasq likely not loading $nftset_dest" + rollback + exit 1 + fi fi -# ============================== -# Очистка IPSET -# ============================== -echo "Flushing ipset bbrkn..." -ipset flush bbrkn - echo "Deployment completed successfully" -DOMAIN_COUNT=$(grep -c '^ipset=' "$ipset_dest" || echo "0") -echo "Applied configuration for $DOMAIN_COUNT domains" +DOMAIN_COUNT=$(grep -c '^nftset=' "$nftset_dest" || echo "0") +echo "Applied nftset configuration for $DOMAIN_COUNT domain directives" diff --git a/scripts/generate-configs.sh b/scripts/generate-configs.sh index d8aece1..fc0d0f5 100644 --- a/scripts/generate-configs.sh +++ b/scripts/generate-configs.sh @@ -6,11 +6,17 @@ set -euo pipefail # ============================== INPUT_FILE="${DOMAINS_FILE:-domains.txt}" -IPSET_CONF="${IPSET_CONF:-/tmp/91-ipset-bbrkn.conf}" +NFTSET_CONF="${NFTSET_CONF:-/tmp/90-nftset.conf}" RESOLVE_CONF="${RESOLVE_CONF:-/tmp/92-resolve-bbrkn.conf}" +# nftset-цель для dnsmasq: <4|6>###[,...] +# 4# → A-записи в bbrkn_v4; 6# → AAAA в bbrkn_v6 (таблица: inet filter) +NFTSET_SPEC="${NFTSET_SPEC:-4#inet#filter#bbrkn_v4,6#inet#filter#bbrkn_v6}" + API_URL="${CHROME_SERVER:-http://127.0.0.1:3000}/domains?domain=" -DNS_SERVER="${DNS_SERVER:-8.8.8.8}" +# Куда pihole делегирует резолвинг bbrkn-доменов: инстанс dnsmasq :5350, который +# резолвит через VPN и наполняет nft-сеты. Формат dnsmasq server=: ip[#port]. +DNS_SERVER="${DNS_SERVER:-127.0.0.1#5350}" # Игнорируемые части доменов (разделитель — пробел) IGNORE_PARTS="${IGNORE_PARTS:-}" @@ -43,7 +49,7 @@ if ! command -v jq >/dev/null 2>&1; then err "jq is required"; exit 2; fi if [ "$DEBUG" != "0" ]; then : > "$DEBUG_LOG"; dbg "Debugging enabled"; fi log "Starting generate-configs.sh" -dbg "ENV: INPUT_FILE=$INPUT_FILE IPSET_CONF=$IPSET_CONF RESOLVE_CONF=$RESOLVE_CONF API_URL=$API_URL DNS_SERVER=$DNS_SERVER IGNORE_PARTS='$IGNORE_PARTS' DRY_RUN=$DRY_RUN CACHE_DIR=$CACHE_DIR CACHE_TTL_DAYS=$CACHE_TTL_DAYS STRICT_MODE=$STRICT_MODE" +dbg "ENV: INPUT_FILE=$INPUT_FILE NFTSET_CONF=$NFTSET_CONF NFTSET_SPEC=$NFTSET_SPEC RESOLVE_CONF=$RESOLVE_CONF API_URL=$API_URL DNS_SERVER=$DNS_SERVER IGNORE_PARTS='$IGNORE_PARTS' DRY_RUN=$DRY_RUN CACHE_DIR=$CACHE_DIR CACHE_TTL_DAYS=$CACHE_TTL_DAYS STRICT_MODE=$STRICT_MODE" if [ ! -f "$INPUT_FILE" ]; then err "Input file not found: $INPUT_FILE" @@ -313,9 +319,9 @@ if ! $DRY_RUN; then { for d in "${ORDERED_DOMS[@]}"; do [[ -n "${OUTPUT_COMMENT[$d]:-}" ]] && printf '%s\n' "${OUTPUT_COMMENT[$d]}" - printf 'ipset=/%s/bbrkn\n' "$d" + printf 'nftset=/%s/%s\n' "$d" "$NFTSET_SPEC" done - } > "$IPSET_CONF" + } > "$NFTSET_CONF" { for d in "${ORDERED_DOMS[@]}"; do [[ -n "${OUTPUT_COMMENT[$d]:-}" ]] && printf '%s\n' "${OUTPUT_COMMENT[$d]}" From 9acd54ff2a9cfd99d8a15284c5d45905c7d6fdc0 Mon Sep 17 00:00:00 2001 From: goodvin Date: Sat, 25 Jul 2026 12:54:15 +0300 Subject: [PATCH 16/27] Update domains.txt --- domains.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/domains.txt b/domains.txt index 62990c6..b8ed701 100644 --- a/domains.txt +++ b/domains.txt @@ -193,4 +193,5 @@ netlify.com netlifydns.com netlifyusercontent.com www.ubuntu-touch.io -ubports.com \ No newline at end of file +ubports.com +4pda.to \ No newline at end of file From a3e755c0a497004b8ce96bac6606644688c5a13e Mon Sep 17 00:00:00 2001 From: goodvin Date: Tue, 28 Jul 2026 00:50:44 +0300 Subject: [PATCH 17/27] Update domains.txt --- domains.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/domains.txt b/domains.txt index b8ed701..ab8fa96 100644 --- a/domains.txt +++ b/domains.txt @@ -194,4 +194,5 @@ netlifydns.com netlifyusercontent.com www.ubuntu-touch.io ubports.com -4pda.to \ No newline at end of file +4pda.to +www.patreon.com \ No newline at end of file From 20c484dc59aaf22b04b6caeb96ed95fa5e7f17e2 Mon Sep 17 00:00:00 2001 From: goodvin Date: Fri, 31 Jul 2026 16:54:09 +0300 Subject: [PATCH 18/27] Update domains.txt --- domains.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/domains.txt b/domains.txt index ab8fa96..6412acc 100644 --- a/domains.txt +++ b/domains.txt @@ -195,4 +195,6 @@ netlifyusercontent.com www.ubuntu-touch.io ubports.com 4pda.to -www.patreon.com \ No newline at end of file +www.patreon.com +registry-1.docker.io +docker.io \ No newline at end of file From 0865df3de3eb3d380d1af3978fe18d46779c025d Mon Sep 17 00:00:00 2001 From: goodvin Date: Sat, 1 Aug 2026 14:20:27 +0300 Subject: [PATCH 19/27] Update domains.txt --- domains.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/domains.txt b/domains.txt index 6412acc..6aa6cb2 100644 --- a/domains.txt +++ b/domains.txt @@ -197,4 +197,5 @@ ubports.com 4pda.to www.patreon.com registry-1.docker.io -docker.io \ No newline at end of file +docker.io +downloads.claude.ai \ No newline at end of file From c9c1e8b1eea7d5891e9c416e5d7f1a076b8f9769 Mon Sep 17 00:00:00 2001 From: goodvin Date: Sat, 1 Aug 2026 18:57:11 +0300 Subject: [PATCH 20/27] Update domains.txt --- domains.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/domains.txt b/domains.txt index 6aa6cb2..ff4cff3 100644 --- a/domains.txt +++ b/domains.txt @@ -198,4 +198,5 @@ ubports.com www.patreon.com registry-1.docker.io docker.io -downloads.claude.ai \ No newline at end of file +downloads.claude.ai +claude.com \ No newline at end of file From 38d5bdf0ceb59fbbc986cac098470b736c3e9c8c Mon Sep 17 00:00:00 2001 From: goodvin Date: Sat, 1 Aug 2026 22:31:10 +0300 Subject: [PATCH 21/27] Update domains.txt --- domains.txt | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/domains.txt b/domains.txt index ff4cff3..ec08c31 100644 --- a/domains.txt +++ b/domains.txt @@ -199,4 +199,71 @@ www.patreon.com registry-1.docker.io docker.io downloads.claude.ai -claude.com \ No newline at end of file +claude.com +30bf3790.obrut.show +amedia.online +anime1.best +animego.me +animevost.org +api.anilibria.app +api.anilibria.tv +api.apbugall.org +api.bhcesh.me +api.cdnlibs.org +api.filmix.tv +api.g1.plvideo.ru +api.lumex.space +api.luxembd.ws +api.moonanime.art +api.rgshows.me +api.rstprgapipt.com +api.srvkp.com +api.vokino.org +apivb.com +base.ashdi.vip +cdnmovies-stream.online +chaturbate.com +coldcdn.xyz +ee.bongacams.com +embed.su +eneyida.tv +filmixapp.cyou +getstv.com +go.zet-flix.online +hdrezka.me +iframe.video +in.tizam.info +iptv.online +kinobase.org +kinogo.luxury +kinogo.media +kinoukr.com +kinovibe.vip +kodikapi.com +m.hqporner.com +megaoblako.com +moviesapi.club +myfanserial.net +plapi.cdnvideohub.com +player.autoembed.cc +player.smashystream.com +player.videasy.net +portal.lumex.host +redheadsound.studio +rt.pornhub.com +rt.pornhubpremium.com +ru.spankbang.com +ru.xhamster.com +rus.runetki5.com +vibix.org +videoseed.tv +vidfast.pro +vidlink.pro +vidora.su +vidsrc.cc +www.ebalovo.pro +www.eporner.com +www.porntrex.com +www.xnxx.com +www.xvideos.com +www.xvideos.red \ No newline at end of file From 660a31c35e2788f0773916f79ca8e7e9a44b1e7c Mon Sep 17 00:00:00 2001 From: goodvin Date: Wed, 5 Aug 2026 20:58:33 +0300 Subject: [PATCH 22/27] =?UTF-8?q?Add=20warm-nftset.sh=20=E2=80=94=20period?= =?UTF-8?q?ic=20nftset=20warmer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Elements in bbrkn_v4/bbrkn_v6 carry a 1d timeout and are only refreshed when a query reaches the host dnsmasq on :5350, where the nftset= directives are applied. Idle domains — or ones answered from the pihole FTL cache — age out of the sets and their traffic silently falls back to the plain WAN route instead of the tunnel. warm-nftset.sh re-resolves every domain from the deployed 90-nftset.conf (base + related subdomains) directly against 127.0.0.1:5350, bypassing the FTL cache. Sourcing the domain list from the deployed config means there is no second list to keep in sync. Ping is off by default: packets originating on the gateway itself go through OUTPUT, never prerouting_mangle, so they leave unmarked and almost all time out — a misleading signal, not a real failure. Run on the gateway via `make warm` or hourly cron. Measured: 1059 domains in ~1m45s at PARALLEL=8, v4 526->2203, v6 305->1440 elements. Co-Authored-By: Claude Opus 5 --- Makefile | 9 ++- README.md | 52 ++++++++++++++++- scripts/warm-nftset.sh | 128 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 187 insertions(+), 2 deletions(-) create mode 100755 scripts/warm-nftset.sh diff --git a/Makefile b/Makefile index 8758a4d..3d814f7 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ CACHE_TTL_DAYS ?= 15 export NFTSET_CONF RESOLVE_CONF CHROME_SERVER DOMAINS_FILE DNS_SERVER NFTSET_TARGET_DIR CACHE_DIR CACHE_TTL_DAYS # --- Основные цели --- -.PHONY: all clean cache-clean check generate deploy +.PHONY: all clean cache-clean check generate deploy warm all: generate deploy @echo "✅ Конфиги успешно сгенерированы и задеплоены" @@ -55,3 +55,10 @@ deploy: @echo "🚀 Деплой конфигов в систему" @chmod +x scripts/deploy-to-gateway.sh @./scripts/deploy-to-gateway.sh + +# Прогрев nft-сетов. Запускать на шлюзе — читает задеплоенный конфиг и +# ходит в host-инстанс dnsmasq на 127.0.0.1:5350. +warm: + @echo "🔥 Прогрев nft-сетов" + @chmod +x scripts/warm-nftset.sh + @./scripts/warm-nftset.sh diff --git a/README.md b/README.md index d63fb2b..2c08766 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,8 @@ bbrkn/ ├── Makefile # управление сборкой и деплоем ├── scripts/ │ ├── generate-configs.sh # генерация dnsmasq-конфигов -│ └── deploy-to-gateway.sh # деплой в Pi-hole + rollback +│ ├── deploy-to-gateway.sh # деплой в Pi-hole + rollback +│ └── warm-nftset.sh # прогрев nft-сетов по cron (на шлюзе) └── .forgejo/workflows/deploy.yaml # CI/CD на self-hosted runner ``` @@ -97,6 +98,7 @@ make deploy # только задеплоить в Pi-hole make clean # удалить сгенерированные конфиги из /tmp make cache-clean # очистить кэш API-ответов (.cache/api/) make check # проверить синтаксис domains.txt +make warm # прогреть nft-сеты (запускать на шлюзе) ``` --- @@ -173,6 +175,54 @@ CACHE_TTL_DAYS=0 make generate --- +## Прогрев nft-сетов (`warm-nftset.sh`) + +Элементы сетов `bbrkn_v4`/`bbrkn_v6` живут по `timeout` (сейчас `1d`) и обновляются **только** когда запрос доходит до host-инстанса `:5350` — именно там применяются директивы `nftset=`. Если домен долго никто не открывал (или ответ отдал кэш pihole FTL), элемент истекает, и трафик к этому домену уходит мимо туннеля напрямую в WAN. + +`scripts/warm-nftset.sh` закрывает эту дыру: заново резолвит **все** домены из задеплоенного `90-nftset.conf` (база + связанные субдомены) **напрямую** через `dig @127.0.0.1 -p 5350`, минуя кэш FTL, — каждый запрос гарантированно доходит до тегирующего инстанса. + +> **Запускать только на шлюзе.** Инстанс `:5350` слушает `listen-address=127.0.0.1` и с других хостов LAN недоступен. Список доменов берётся из задеплоенного конфига, так что скрипт всегда работает с актуальным после последнего деплоя списком. + +```bash +# разово, вручную +make warm +./scripts/warm-nftset.sh + +# ежечасно по cron (пользователь на шлюзе, sudo без пароля для nft) +crontab -e +17 * * * * /path/to/bbrkn/scripts/warm-nftset.sh >/dev/null 2>&1 +``` + +### Переменные окружения + +| Переменная | По умолчанию | Описание | +|---|---|---| +| `NFTSET_CONF` | `/etc/dnsmasq.d/90-nftset.conf` | Задеплоенный конфиг — источник списка доменов | +| `RESOLVER` / `RESOLVER_PORT` | `127.0.0.1` / `5350` | Host-инстанс dnsmasq, применяющий `nftset=` | +| `PARALLEL` | `8` | Одновременных запросов к `:5350` | +| `DIG_TIMEOUT` / `DIG_TRIES` | `3` / `2` | Таймаут и число попыток на домен | +| `WARM_PING` | `0` | `1` — дополнительно пинговать первый A-адрес (только для отладки) | +| `STATE_DIR` | `~/.local/state/bbrkn` | Лог `warm.log` и lock-файл | +| `LOG_MAX_BYTES` | `4194304` | Порог ротации лога в `warm.log.1` | + +Параллельные запуски блокируются через `flock` — медленный прогон не наложится на следующий по cron. + +### Лог + +``` +2026-08-05T20:46:57+03:00 START domains=1059 conf=/etc/dnsmasq.d/90-nftset.conf v4=526 v6=305 +2026-08-05T20:48:44+03:00 DONE ok=1036 noping=0 dnsfail=23 v4=526->2203 v6=305->1440 +2026-08-05T20:48:44+03:00 dnsfail: azureedge.net cdn-telegram.org kinozal.tv ... +``` + +`v4=X->Y` — число элементов в сете до и после прогона (считается через `sudo -n nft list set`; без прав sudo вместо числа будет `?`, на сам прогрев это не влияет). Элементов больше, чем доменов — у CDN на один домен приходится несколько адресов. Домены в `dnsfail` не резолвятся вообще (мёртвые записи, NXDOMAIN) — кандидаты на удаление из `domains.txt`. + +> `WARM_PING=1` на шлюзе почти всегда даёт `noping`: пакеты, рождённые на самом шлюзе, идут через `OUTPUT` и не проходят цепочку `prerouting_mangle`, то есть уходят в WAN без метки. Это диагностический режим, не показатель проблемы. + +Полный прогон 1059 доменов при `PARALLEL=8` занимает ~1 мин 45 с. + +--- + ## Запуск вручную ```bash diff --git a/scripts/warm-nftset.sh b/scripts/warm-nftset.sh new file mode 100755 index 0000000..52295c6 --- /dev/null +++ b/scripts/warm-nftset.sh @@ -0,0 +1,128 @@ +#!/bin/bash +set -uo pipefail + +# ============================== +# Прогрев nft-сетов (запускать НА ШЛЮЗЕ) +# ============================== +# Заново резолвит все домены, которые dnsmasq помечает директивами nftset=, +# обращаясь напрямую к host-инстансу 127.0.0.1#5350. Прямой запрос обходит +# кэш pihole FTL: запрос, попавший в кэш FTL, до :5350 не доходит и элементы +# в inet filter bbrkn_v4/bbrkn_v6 не обновляет — они молча истекают по timeout +# и домен уходит мимо туннеля. +# +# Источник списка доменов — сам задеплоенный конфиг, поэтому отдельный список +# синхронизировать не нужно: скрипт всегда работает с тем, что реально +# загружен в dnsmasq после последнего успешного деплоя. + +# --- Конфигурация через переменные окружения --- +# Задеплоенный конфиг с директивами nftset= (источник списка доменов) +NFTSET_CONF="${NFTSET_CONF:-/etc/dnsmasq.d/90-nftset.conf}" +# Host-инстанс dnsmasq, который применяет nftset= +RESOLVER="${RESOLVER:-127.0.0.1}" +RESOLVER_PORT="${RESOLVER_PORT:-5350}" + +# Состояние и лог +STATE_DIR="${STATE_DIR:-${HOME}/.local/state/bbrkn}" +LOG="${LOG:-${STATE_DIR}/warm.log}" +LOCK="${LOCK:-${STATE_DIR}/warm.lock}" +LOG_MAX_BYTES="${LOG_MAX_BYTES:-4194304}" # ротация лога, 4 МиБ + +# Параметры опроса +PARALLEL="${PARALLEL:-8}" # одновременных запросов к :5350 +DIG_TIMEOUT="${DIG_TIMEOUT:-3}" # секунд на попытку +DIG_TRIES="${DIG_TRIES:-2}" + +# Пинг выключен по умолчанию: пакеты, рождённые на самом шлюзе, идут через +# OUTPUT и не проходят цепочку prerouting_mangle — уходят в обычный WAN без +# метки и почти все отваливаются по таймауту. Включать только для отладки. +WARM_PING="${WARM_PING:-0}" +PING_TIMEOUT="${PING_TIMEOUT:-2}" + +NFT="${NFT:-/usr/sbin/nft}" + +mkdir -p "$STATE_DIR" + +# Один инстанс: медленный прогон не должен наложиться на следующий по cron. +exec 9>"$LOCK" +if ! flock -n 9; then + echo "$(date -Is) SKIP предыдущий запуск ещё выполняется" >>"$LOG" + exit 0 +fi + +log() { echo "$(date -Is) $*" >>"$LOG"; } + +# Количество элементов в сете. У каждого элемента есть timeout, поэтому +# "expires" встречается ровно один раз на элемент. Ошибка здесь не фатальна — +# это только метрика прогресса. +set_count() { + sudo -n "$NFT" list set inet filter "$1" 2>/dev/null | grep -c 'expires' || echo "?" +} + +if [ ! -r "$NFTSET_CONF" ]; then + log "FATAL не читается $NFTSET_CONF" + echo "Ошибка: не читается $NFTSET_CONF (скрипт запускается на шлюзе)" >&2 + exit 1 +fi + +mapfile -t HOSTS < <(sed -n 's|^nftset=/\([^/]*\)/.*|\1|p' "$NFTSET_CONF" | sort -u) +if [ "${#HOSTS[@]}" -eq 0 ]; then + log "FATAL в $NFTSET_CONF не найдено ни одной директивы nftset=" + echo "Ошибка: в $NFTSET_CONF не найдено ни одной директивы nftset=" >&2 + exit 1 +fi + +BEFORE_V4=$(set_count bbrkn_v4) +BEFORE_V6=$(set_count bbrkn_v6) +log "START domains=${#HOSTS[@]} conf=$NFTSET_CONF v4=$BEFORE_V4 v6=$BEFORE_V6" + +warm_one() { + local host="$1" a aaaa + a=$(dig +short +timeout="$DIG_TIMEOUT" +tries="$DIG_TRIES" \ + @"$RESOLVER" -p "$RESOLVER_PORT" A "$host" 2>/dev/null \ + | grep -E '^[0-9]+\.' | head -1) + aaaa=$(dig +short +timeout="$DIG_TIMEOUT" +tries="$DIG_TRIES" \ + @"$RESOLVER" -p "$RESOLVER_PORT" AAAA "$host" 2>/dev/null \ + | grep -E '^[0-9a-fA-F]*:' | head -1) + + if [ -z "$a" ] && [ -z "$aaaa" ]; then + echo "dnsfail $host" + return + fi + + # Пинг только подтверждает проходимость маршрута; в сет IP попал уже на + # шаге резолва выше. + if [ "$WARM_PING" = "1" ] && [ -n "$a" ]; then + if ping -n -q -c1 -W "$PING_TIMEOUT" "$a" >/dev/null 2>&1; then + echo "ok $host $a" + else + echo "noping $host $a" + fi + else + echo "ok $host ${a:-$aaaa}" + fi +} +export -f warm_one +export RESOLVER RESOLVER_PORT DIG_TIMEOUT DIG_TRIES WARM_PING PING_TIMEOUT + +RESULTS="$(printf '%s\n' "${HOSTS[@]}" \ + | xargs -r -P "$PARALLEL" -I{} bash -c 'warm_one "$@"' _ {})" + +OK=$(grep -c '^ok ' <<<"$RESULTS" || true) +NOPING=$(grep -c '^noping ' <<<"$RESULTS" || true) +DNSFAIL=$(grep -c '^dnsfail ' <<<"$RESULTS" || true) + +AFTER_V4=$(set_count bbrkn_v4) +AFTER_V6=$(set_count bbrkn_v6) +log "DONE ok=$OK noping=$NOPING dnsfail=$DNSFAIL v4=${BEFORE_V4}->${AFTER_V4} v6=${BEFORE_V6}->${AFTER_V6}" +if [ "$DNSFAIL" -gt 0 ]; then + log "dnsfail: $(grep '^dnsfail ' <<<"$RESULTS" | cut -d' ' -f2 | tr '\n' ' ')" +fi + +echo "✔ Прогрето доменов: $OK, не резолвится: $DNSFAIL; bbrkn_v4 ${BEFORE_V4}->${AFTER_V4}, bbrkn_v6 ${BEFORE_V6}->${AFTER_V6}" + +# Ротация лога +if [ -f "$LOG" ] && [ "$(stat -c%s "$LOG")" -gt "$LOG_MAX_BYTES" ]; then + mv -f "$LOG" "${LOG}.1" +fi + +exit 0 From 0ac0aa0cc4b78801b064649351a51c07e43ef200 Mon Sep 17 00:00:00 2001 From: goodvin Date: Sat, 8 Aug 2026 12:24:13 +0300 Subject: [PATCH 23/27] Update domains.txt --- domains.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/domains.txt b/domains.txt index ec08c31..f6f2f5c 100644 --- a/domains.txt +++ b/domains.txt @@ -266,4 +266,5 @@ www.eporner.com www.porntrex.com www.xnxx.com www.xvideos.com -www.xvideos.red \ No newline at end of file +www.xvideos.red +gemini.google.com \ No newline at end of file From 5643ba5615bc1e5be0ac372e09ca0fc70feb9e2a Mon Sep 17 00:00:00 2001 From: goodvin Date: Sat, 8 Aug 2026 15:24:08 +0300 Subject: [PATCH 24/27] Update domains.txt --- domains.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/domains.txt b/domains.txt index f6f2f5c..38efac8 100644 --- a/domains.txt +++ b/domains.txt @@ -267,4 +267,5 @@ www.porntrex.com www.xnxx.com www.xvideos.com www.xvideos.red -gemini.google.com \ No newline at end of file +gemini.google.com +nakarte.me \ No newline at end of file From 47f91b519179caca46ffb4e42f0171f41bc76ff4 Mon Sep 17 00:00:00 2001 From: goodvin Date: Sun, 16 Aug 2026 20:49:35 +0300 Subject: [PATCH 25/27] Update domains.txt --- domains.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/domains.txt b/domains.txt index 38efac8..d5d07dc 100644 --- a/domains.txt +++ b/domains.txt @@ -268,4 +268,5 @@ www.xnxx.com www.xvideos.com www.xvideos.red gemini.google.com -nakarte.me \ No newline at end of file +nakarte.me +a-api.anthropic.com \ No newline at end of file From 19fbb5c1a25509ee5fcf4191fa6d469049cc0417 Mon Sep 17 00:00:00 2001 From: goodvin Date: Mon, 17 Aug 2026 07:39:55 +0300 Subject: [PATCH 26/27] ADD Google Video domains --- domains.txt | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/domains.txt b/domains.txt index d5d07dc..5734b1b 100644 --- a/domains.txt +++ b/domains.txt @@ -269,4 +269,19 @@ www.xvideos.com www.xvideos.red gemini.google.com nakarte.me -a-api.anthropic.com \ No newline at end of file +a-api.anthropic.com +youtube.com +youtu.be +youtube-nocookie.com +ytimg.com +ggpht.com +googlevideo.com +gvt1.com +gvt2.com +gvt3.com +googleusercontent.com +gstatic.com +jnn-pa.googleapis.com +play.googleapis.com +youtubei.googleapis.com +googleapis.com \ No newline at end of file From ae8295546bacbdf9a38b7ebc09ea388c28bb3265 Mon Sep 17 00:00:00 2001 From: goodvin Date: Fri, 28 Aug 2026 20:59:23 +0300 Subject: [PATCH 27/27] Update domains.txt --- domains.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/domains.txt b/domains.txt index 5734b1b..f2ddaa5 100644 --- a/domains.txt +++ b/domains.txt @@ -284,4 +284,7 @@ gstatic.com jnn-pa.googleapis.com play.googleapis.com youtubei.googleapis.com -googleapis.com \ No newline at end of file +googleapis.com +api.bambulab.com +bambulab.com +e.bambulab.com \ No newline at end of file