From c16466a85454056f23993492000678c6c14cfafe Mon Sep 17 00:00:00 2001 From: goodvin Date: Wed, 25 Mar 2026 20:17:03 +0300 Subject: [PATCH 01/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] 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/35] =?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/35] 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/35] 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/35] 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/35] 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/35] 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 From 76a941f0df64fb3b12c89513af70cc92ef00034f Mon Sep 17 00:00:00 2001 From: goodvin Date: Sat, 5 Sep 2026 16:53:55 +0300 Subject: [PATCH 28/35] Update domains.txt --- domains.txt | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/domains.txt b/domains.txt index f2ddaa5..2e5d212 100644 --- a/domains.txt +++ b/domains.txt @@ -23,7 +23,6 @@ claude.ai dell.com download.freebsd.org duckduckgo.com -echo.msk.ru fast.com flibusta.is forum.xda-developers.com @@ -63,15 +62,6 @@ seasonvar.ru sketchup.com snap.licdn.com sonic-net.github.io -spoti.fi -spotifycharts.com -spotifycodes.com -spotify.com -spotify.design -spotify-everywhere.com -spotifyforbrands.com -spotifyjobs.com -spotify.link static.cdninstagram.com static.rutracker.cc store.akamai.steamstatic.com @@ -250,23 +240,12 @@ 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 gemini.google.com nakarte.me a-api.anthropic.com @@ -287,4 +266,8 @@ youtubei.googleapis.com googleapis.com api.bambulab.com bambulab.com -e.bambulab.com \ No newline at end of file +e.bambulab.com +public-cdn.bblmw.com +makerworld.com +makerworld.bblmw.com +bblmw.com \ No newline at end of file From ed9fa34daeff31eff07b680a99beb88088313bd8 Mon Sep 17 00:00:00 2001 From: goodvin Date: Mon, 7 Sep 2026 23:34:23 +0300 Subject: [PATCH 29/35] Update domains.txt --- domains.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/domains.txt b/domains.txt index 2e5d212..1ee0f16 100644 --- a/domains.txt +++ b/domains.txt @@ -270,4 +270,6 @@ e.bambulab.com public-cdn.bblmw.com makerworld.com makerworld.bblmw.com -bblmw.com \ No newline at end of file +bblmw.com +blog.3mdeb.com +3mdeb.com \ No newline at end of file From 1166f4200e59ec2f6b87d426cf8a758d77160060 Mon Sep 17 00:00:00 2001 From: goodvin Date: Sun, 11 Oct 2026 15:22:41 +0300 Subject: [PATCH 30/35] Update domains.txt --- domains.txt | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/domains.txt b/domains.txt index 1ee0f16..3c99654 100644 --- a/domains.txt +++ b/domains.txt @@ -264,12 +264,4 @@ jnn-pa.googleapis.com play.googleapis.com youtubei.googleapis.com googleapis.com -api.bambulab.com -bambulab.com -e.bambulab.com -public-cdn.bblmw.com -makerworld.com -makerworld.bblmw.com -bblmw.com -blog.3mdeb.com -3mdeb.com \ No newline at end of file +printables.com \ No newline at end of file From 28f5c01dde1d7edb03ea78022a89a4b9353a4e52 Mon Sep 17 00:00:00 2001 From: goodvin Date: Sun, 11 Oct 2026 15:24:04 +0300 Subject: [PATCH 31/35] Update domains.txt --- domains.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/domains.txt b/domains.txt index 3c99654..aaf41d4 100644 --- a/domains.txt +++ b/domains.txt @@ -264,4 +264,5 @@ jnn-pa.googleapis.com play.googleapis.com youtubei.googleapis.com googleapis.com -printables.com \ No newline at end of file +printables.com +files.printables.com \ No newline at end of file From 1dff606865181ede531e6a4c22734328bf05f6d5 Mon Sep 17 00:00:00 2001 From: goodvin Date: Sun, 11 Oct 2026 18:37:04 +0300 Subject: [PATCH 32/35] Update domains.txt --- domains.txt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/domains.txt b/domains.txt index aaf41d4..be5006e 100644 --- a/domains.txt +++ b/domains.txt @@ -265,4 +265,13 @@ play.googleapis.com youtubei.googleapis.com googleapis.com printables.com -files.printables.com \ No newline at end of file +files.printables.com +api.bambulab.com +bambulab.com +e.bambulab.com +public-cdn.bblmw.com +makerworld.com +makerworld.bblmw.com +bblmw.com +blog.3mdeb.com +3mdeb.com \ No newline at end of file From a5ee057c593937022d777538f8153a4c8bd6b16b Mon Sep 17 00:00:00 2001 From: goodvin Date: Tue, 13 Oct 2026 20:54:19 +0300 Subject: [PATCH 33/35] deploy: keep config backups out of dnsmasq conf dirs, cap at 10 Backups were written as .backup. next to the live file. Both dnsmasq instances load every file in their conf dir (the host excludes only .dpkg-*), so each backup was parsed as live config, resurrecting removed domains and piling up one stale copy per deploy. Write them to BACKUP_DIR (/var/backups/bbrkn) instead and keep the newest BACKUP_KEEP (10) per file. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01DR7zpSsdX28T4Kn6v4jPSS --- README.md | 2 ++ scripts/deploy-to-gateway.sh | 24 ++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2c08766..a5cb7db 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,8 @@ make warm # прогреть nft-сеты (запускать на шл | `TARGET_DIR` | `/opt/appdata/pihole/etc/dnsmasq.d` | Директория dnsmasq в Pi-hole (для `92-resolve`) | | `DOCKER_CONTAINER` | `pihole` | Имя Docker-контейнера Pi-hole | | `HOST_DNSMASQ_SVC` | `dnsmasq` | systemd-сервис host-инстанса dnsmasq | +| `BACKUP_DIR` | `/var/backups/bbrkn` | Куда класть бэкапы конфигов перед деплоем (НЕ в `dnsmasq.d` — оттуда грузится всё) | +| `BACKUP_KEEP` | `10` | Сколько последних бэкапов хранить на каждый файл | | `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 | diff --git a/scripts/deploy-to-gateway.sh b/scripts/deploy-to-gateway.sh index beadb0f..89763c7 100644 --- a/scripts/deploy-to-gateway.sh +++ b/scripts/deploy-to-gateway.sh @@ -14,6 +14,11 @@ RESOLVE_CONF="${RESOLVE_CONF:-/tmp/92-resolve-bbrkn.conf}" NFTSET_TARGET_DIR="${NFTSET_TARGET_DIR:-/etc/dnsmasq.d}" RESOLVE_TARGET_DIR="${RESOLVE_TARGET_DIR:-${TARGET_DIR:-/opt/appdata/pihole/etc/dnsmasq.d}}" +# Бэкапы — ВНЕ conf-dir: dnsmasq (и pihole) грузит из dnsmasq.d все файлы, +# кроме исключённых расширений, так что *.backup.* там стал бы живым конфигом. +BACKUP_DIR="${BACKUP_DIR:-/var/backups/bbrkn}" +BACKUP_KEEP="${BACKUP_KEEP:-10}" # сколько последних копий хранить на каждый файл + DOCKER_CONTAINER="${DOCKER_CONTAINER:-pihole}" # systemd-сервис host-инстанса dnsmasq (:5350) HOST_DNSMASQ_SVC="${HOST_DNSMASQ_SVC:-dnsmasq}" @@ -82,16 +87,31 @@ rollback() { # ============================== # Бэкап существующих файлов # ============================== +# Оставляет BACKUP_KEEP самых свежих копий .backup. +# (ts вида YYYYmmdd-HHMMSS сортируется лексикографически). +prune_backups() { # + local name="$1" old + find "$BACKUP_DIR" -maxdepth 1 -type f -name "${name}.backup.*" -printf '%f\n' \ + | sort -r | tail -n +"$((BACKUP_KEEP + 1))" \ + | while read -r old; do + rm -f -- "$BACKUP_DIR/$old" + echo " pruned old backup $old" + done +} + +mkdir -p "$BACKUP_DIR" ts=$(date +%Y%m%d-%H%M%S) if [ -f "$nftset_dest" ]; then - NFTSET_BACKUP="${nftset_dest}.backup.${ts}" + NFTSET_BACKUP="$BACKUP_DIR/${nftset_name}.backup.${ts}" cp "$nftset_dest" "$NFTSET_BACKUP" echo "Backed up $nftset_dest → $NFTSET_BACKUP" + prune_backups "$nftset_name" fi if [ -f "$resolve_dest" ]; then - RESOLVE_BACKUP="${resolve_dest}.backup.${ts}" + RESOLVE_BACKUP="$BACKUP_DIR/${resolve_name}.backup.${ts}" cp "$resolve_dest" "$RESOLVE_BACKUP" echo "Backed up $resolve_dest → $RESOLVE_BACKUP" + prune_backups "$resolve_name" fi # ============================== From 8598a1a48f920005cf9a5d6583fb93d72ea70cd0 Mon Sep 17 00:00:00 2001 From: goodvin Date: Tue, 13 Oct 2026 21:26:49 +0300 Subject: [PATCH 34/35] warm: self-heal wedged dnsmasq, run after deploy, shared state dir dnsmasq adds IPs to an nft set only on a fresh upstream reply; a cache hit never refreshes the set. On Archie the :5350 instance also wedged: TCP child processes first, then the main process, logged 'nftset ... netlink: Error: cache initialization failed: Protocol error' on every add and stopped filling bbrkn_v4/v6 until restarted. warm-nftset.sh now counts those errors for the main PID during the run (journalctl _PID=) and, if any, restarts the service and warms again; exit 1 if it still fails. The state dir defaults to /var/lib/bbrkn so the root cron job and the deploy share one lock (LOCK_WAIT lets deploy wait). deploy-to-gateway.sh runs the warmer right after flushing the sets, since clients keep old IPs cached and would bypass the tunnel until the next fresh resolve. A warm failure only warns; it does not roll back. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01DR7zpSsdX28T4Kn6v4jPSS --- CLAUDE.md | 3 +- README.md | 23 +++++++--- scripts/deploy-to-gateway.sh | 17 ++++++++ scripts/warm-nftset.sh | 82 +++++++++++++++++++++++++++++++----- 4 files changed, 107 insertions(+), 18 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 424ec83..9854d86 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -38,7 +38,7 @@ Two bash scripts, driven by env vars (defaults in each script's header, overridd - **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. +**`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). Then it runs `warm-nftset.sh` (non-fatal) to refill the just-flushed sets. Backups go to `$BACKUP_DIR` (`/var/backups/bbrkn`, newest `BACKUP_KEEP`=10) — never into a dnsmasq conf dir, which would load them as live config. **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 @@ -46,6 +46,7 @@ Two bash scripts, driven by env vars (defaults in each script's header, overridd ## Gotchas +- **dnsmasq only adds IPs to an nft set on a fresh upstream reply**, never on a cache hit — hence `cache-size=0` on the `:5350` instance and the hourly warmer (`/etc/cron.d/bbrkn-warm` → `/usr/local/sbin/bbrkn-warm`, a manually installed copy of `scripts/warm-nftset.sh`). The `:5350` main process can also wedge (`nftset … cache initialization failed: Protocol error` on every add until restart); the warmer detects that via `journalctl _PID=` and restarts it. - `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. diff --git a/README.md b/README.md index a5cb7db..57c09d7 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,7 @@ CACHE_TTL_DAYS=0 make generate 6. Сбрасывает nft-сеты `bbrkn_v4`/`bbrkn_v6` (устаревшие IP иначе висят до timeout 24 ч). 7. Health check: базовый DNS через pihole `:53` + end-to-end проверка nftset-захвата (резолв bbrkn-домена через `:5350` → сет наполнился). 8. При ошибке на шагах 5–7 — **автоматически откатывается**: восстанавливает оба бэкапа и перезапускает оба инстанса со старой конфигурацией. +9. После успешного деплоя запускает `warm-nftset.sh` — сеты только что очищены, а клиенты держат старые IP в кэше. Ошибка прогрева деплой не откатывает (только `WARN`). --- @@ -183,6 +184,10 @@ CACHE_TTL_DAYS=0 make generate `scripts/warm-nftset.sh` закрывает эту дыру: заново резолвит **все** домены из задеплоенного `90-nftset.conf` (база + связанные субдомены) **напрямую** через `dig @127.0.0.1 -p 5350`, минуя кэш FTL, — каждый запрос гарантированно доходит до тегирующего инстанса. +> **У `:5350` должен быть выключен кэш** (`cache-size=0` в `/etc/dnsmasq.conf` на шлюзе). dnsmasq добавляет IP в сет **только при свежем ответе upstream** — ответ из собственного кэша сет не обновляет. Кэширует pihole перед ним. + +**Самовосстановление.** dnsmasq иногда «клинит» на записи в сеты: основной процесс на каждую попытку пишет в журнал `nftset inet filter bbrkn_v4 netlink: Error: cache initialization failed: Protocol error` и больше ничего в сеты не добавляет до рестарта (наблюдалось под нагрузкой; начинается с дочерних TCP-процессов). Скрипт после прогона считает такие ошибки у основного процесса (`journalctl _PID=…`); если они есть — `systemctl restart $HOST_DNSMASQ_SVC` и повторный прогрев. Если не помогло — `exit 1` и `FAIL` в логе. + > **Запускать только на шлюзе.** Инстанс `:5350` слушает `listen-address=127.0.0.1` и с других хостов LAN недоступен. Список доменов берётся из задеплоенного конфига, так что скрипт всегда работает с актуальным после последнего деплоя списком. ```bash @@ -190,11 +195,14 @@ CACHE_TTL_DAYS=0 make generate make warm ./scripts/warm-nftset.sh -# ежечасно по cron (пользователь на шлюзе, sudo без пароля для nft) -crontab -e -17 * * * * /path/to/bbrkn/scripts/warm-nftset.sh >/dev/null 2>&1 +# ежечасно по cron — так стоит на Archie: +install -m 0755 scripts/warm-nftset.sh /usr/local/sbin/bbrkn-warm +cat /etc/cron.d/bbrkn-warm +17 * * * * root /usr/local/sbin/bbrkn-warm >/dev/null 2>&1 ``` +Копия в `/usr/local/sbin` **не обновляется деплоем** — после правок скрипта переустановить вручную. Деплой (CI) запускает скрипт из репо, `STATE_DIR` у них общий, поэтому lock тоже общий. + ### Переменные окружения | Переменная | По умолчанию | Описание | @@ -204,8 +212,11 @@ crontab -e | `PARALLEL` | `8` | Одновременных запросов к `:5350` | | `DIG_TIMEOUT` / `DIG_TRIES` | `3` / `2` | Таймаут и число попыток на домен | | `WARM_PING` | `0` | `1` — дополнительно пинговать первый A-адрес (только для отладки) | -| `STATE_DIR` | `~/.local/state/bbrkn` | Лог `warm.log` и lock-файл | +| `STATE_DIR` | `/var/lib/bbrkn` | Лог `warm.log` и lock-файл (общие для cron и деплоя) | | `LOG_MAX_BYTES` | `4194304` | Порог ротации лога в `warm.log.1` | +| `LOCK_WAIT` | `0` | Секунд ждать идущий прогон (`0` — сразу выйти с `SKIP`; деплой ждёт `WARM_LOCK_WAIT=300`) | +| `HOST_DNSMASQ_SVC` | `dnsmasq` | systemd-сервис `:5350` — для детектора заклинивания и рестарта | +| `SELF_HEAL` | `1` | `0` — только сообщать о заклинивании, не перезапускать dnsmasq | Параллельные запуски блокируются через `flock` — медленный прогон не наложится на следующий по cron. @@ -213,11 +224,11 @@ crontab -e ``` 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 DONE ok=1036 noping=0 dnsfail=23 nftset_errors=0 healed=0 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`. +`v4=X->Y` — число элементов в сете до и после прогона (считается через `nft list set`; не под root — через `sudo -n`, без прав будет `0`, на сам прогрев это не влияет). `nftset_errors` — ошибки записи в сет у основного процесса dnsmasq, `healed=1` — был рестарт. Элементов больше, чем доменов — у CDN на один домен приходится несколько адресов. Домены в `dnsfail` не резолвятся вообще (мёртвые записи, NXDOMAIN) — кандидаты на удаление из `domains.txt`. > `WARM_PING=1` на шлюзе почти всегда даёт `noping`: пакеты, рождённые на самом шлюзе, идут через `OUTPUT` и не проходят цепочку `prerouting_mangle`, то есть уходят в WAN без метки. Это диагностический режим, не показатель проблемы. diff --git a/scripts/deploy-to-gateway.sh b/scripts/deploy-to-gateway.sh index 89763c7..ca82ac9 100644 --- a/scripts/deploy-to-gateway.sh +++ b/scripts/deploy-to-gateway.sh @@ -232,6 +232,23 @@ else fi fi +# ============================== +# Прогрев nft-сетов: после flush сеты пусты, а у клиентов в кэше (Edge, +# Windows) остаются старые IP — без прогрева их трафик идёт мимо туннеля до +# первого свежего резолва. Ошибка прогрева деплой не откатывает: конфиги уже +# проверены, а cron-прогрев повторит попытку. +# ============================== +WARM_SCRIPT="${WARM_SCRIPT:-$(dirname "$0")/warm-nftset.sh}" +if [ -f "$WARM_SCRIPT" ]; then + echo "Warming nft sets ($WARM_SCRIPT)..." + if ! NFTSET_CONF="$nftset_dest" HOST_DNSMASQ_SVC="$HOST_DNSMASQ_SVC" \ + LOCK_WAIT="${WARM_LOCK_WAIT:-300}" bash "$WARM_SCRIPT"; then + echo " WARN: warm failed — sets will refill on client queries / hourly cron" + fi +else + echo " WARN: $WARM_SCRIPT not found, skipping warm" +fi + echo "Deployment completed successfully" DOMAIN_COUNT=$(grep -c '^nftset=' "$nftset_dest" || echo "0") echo "Applied nftset configuration for $DOMAIN_COUNT domain directives" diff --git a/scripts/warm-nftset.sh b/scripts/warm-nftset.sh index 52295c6..8e26669 100755 --- a/scripts/warm-nftset.sh +++ b/scripts/warm-nftset.sh @@ -13,6 +13,13 @@ set -uo pipefail # Источник списка доменов — сам задеплоенный конфиг, поэтому отдельный список # синхронизировать не нужно: скрипт всегда работает с тем, что реально # загружен в dnsmasq после последнего успешного деплоя. +# +# Самовосстановление: dnsmasq добавляет IP в сет только при свежем ответе +# upstream и иногда "клинит" — основной процесс начинает отвечать на каждую +# попытку записи "nftset ... netlink: Error: cache initialization failed: +# Protocol error" и больше сеты не наполняет до рестарта. Если за прогон в +# журнале основного процесса появилась такая ошибка — рестартуем dnsmasq и +# прогреваем ещё раз. # --- Конфигурация через переменные окружения --- # Задеплоенный конфиг с директивами nftset= (источник списка доменов) @@ -21,11 +28,12 @@ NFTSET_CONF="${NFTSET_CONF:-/etc/dnsmasq.d/90-nftset.conf}" RESOLVER="${RESOLVER:-127.0.0.1}" RESOLVER_PORT="${RESOLVER_PORT:-5350}" -# Состояние и лог -STATE_DIR="${STATE_DIR:-${HOME}/.local/state/bbrkn}" +# Состояние и лог. Общий каталог для cron и деплоя — lock у них один. +STATE_DIR="${STATE_DIR:-/var/lib/bbrkn}" LOG="${LOG:-${STATE_DIR}/warm.log}" LOCK="${LOCK:-${STATE_DIR}/warm.lock}" LOG_MAX_BYTES="${LOG_MAX_BYTES:-4194304}" # ротация лога, 4 МиБ +LOCK_WAIT="${LOCK_WAIT:-0}" # секунд ждать чужой прогон (0 — сразу выйти) # Параметры опроса PARALLEL="${PARALLEL:-8}" # одновременных запросов к :5350 @@ -40,11 +48,19 @@ PING_TIMEOUT="${PING_TIMEOUT:-2}" NFT="${NFT:-/usr/sbin/nft}" +# systemd-сервис host-инстанса dnsmasq (:5350) и самовосстановление +HOST_DNSMASQ_SVC="${HOST_DNSMASQ_SVC:-dnsmasq}" +SELF_HEAL="${SELF_HEAL:-1}" + +# Под root (cron.d, CI) sudo не нужен +SUDO="" +[ "$(id -u)" -eq 0 ] || SUDO="sudo -n" + mkdir -p "$STATE_DIR" # Один инстанс: медленный прогон не должен наложиться на следующий по cron. exec 9>"$LOCK" -if ! flock -n 9; then +if ! flock -w "$LOCK_WAIT" 9; then echo "$(date -Is) SKIP предыдущий запуск ещё выполняется" >>"$LOG" exit 0 fi @@ -55,7 +71,19 @@ log() { echo "$(date -Is) $*" >>"$LOG"; } # "expires" встречается ровно один раз на элемент. Ошибка здесь не фатальна — # это только метрика прогресса. set_count() { - sudo -n "$NFT" list set inet filter "$1" 2>/dev/null | grep -c 'expires' || echo "?" + $SUDO "$NFT" list set inet filter "$1" 2>/dev/null | grep -c 'expires' || true +} + +main_pid() { + systemctl show -p MainPID --value "$HOST_DNSMASQ_SVC" 2>/dev/null +} + +# Ошибки записи в nft-сет у основного процесса dnsmasq с момента . +# Ошибки дочерних (TCP) процессов не считаем — они разовые; заклинивание +# видно именно по основному. +nftset_errors() { # + $SUDO journalctl -q --no-pager _PID="$1" --since "$2" 2>/dev/null \ + | grep -c 'nftset.*Protocol error' || true } if [ ! -r "$NFTSET_CONF" ]; then @@ -71,10 +99,6 @@ if [ "${#HOSTS[@]}" -eq 0 ]; then 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" \ @@ -104,8 +128,37 @@ warm_one() { 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 "$@"' _ {})" +# Один прогон по всем доменам. Заполняет RESULTS и ERRORS (ошибки записи в +# сет у основного процесса dnsmasq за время прогона). +warm_all() { + local since pid + since=$(date '+%Y-%m-%d %H:%M:%S') + pid=$(main_pid) + RESULTS="$(printf '%s\n' "${HOSTS[@]}" \ + | xargs -r -P "$PARALLEL" -I{} bash -c 'warm_one "$@"' _ {})" + sleep 1 # журнал пишется асинхронно + ERRORS=0 + if [ -n "$pid" ] && [ "$pid" != "0" ]; then + ERRORS=$(nftset_errors "$pid" "$since") + 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_all +HEALED=0 +if [ "$ERRORS" -gt 0 ] && [ "$SELF_HEAL" = "1" ]; then + log "WEDGE $HOST_DNSMASQ_SVC pid=$(main_pid) nftset_errors=$ERRORS — рестарт и повторный прогрев" + if $SUDO systemctl restart "$HOST_DNSMASQ_SVC"; then + sleep 2 + HEALED=1 + warm_all + else + log "FATAL не удалось перезапустить $HOST_DNSMASQ_SVC" + fi +fi OK=$(grep -c '^ok ' <<<"$RESULTS" || true) NOPING=$(grep -c '^noping ' <<<"$RESULTS" || true) @@ -113,16 +166,23 @@ 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}" +log "DONE ok=$OK noping=$NOPING dnsfail=$DNSFAIL nftset_errors=$ERRORS healed=$HEALED 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}" +[ "$HEALED" = "1" ] && echo "⚠ dnsmasq заклинило на записи в nft-сет — перезапущен ($HOST_DNSMASQ_SVC)" # Ротация лога if [ -f "$LOG" ] && [ "$(stat -c%s "$LOG")" -gt "$LOG_MAX_BYTES" ]; then mv -f "$LOG" "${LOG}.1" fi +# Не ноль, если сеты так и не наполняются — деплой покажет предупреждение. +if [ "$ERRORS" -gt 0 ]; then + log "FAIL nftset_errors=$ERRORS после прогрева" + echo "Ошибка: dnsmasq не пишет в nft-сеты (nftset_errors=$ERRORS), см. journalctl -u $HOST_DNSMASQ_SVC" >&2 + exit 1 +fi exit 0 From 00c70090002698d83ad7047610af8da317e7f535 Mon Sep 17 00:00:00 2001 From: goodvin Date: Fri, 30 Oct 2026 08:51:51 +0300 Subject: [PATCH 35/35] Update domains.txt --- domains.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/domains.txt b/domains.txt index be5006e..bbd336a 100644 --- a/domains.txt +++ b/domains.txt @@ -274,4 +274,5 @@ makerworld.com makerworld.bblmw.com bblmw.com blog.3mdeb.com -3mdeb.com \ No newline at end of file +3mdeb.com +withpersona.com \ No newline at end of file