# AdGuard Home — `agh-bf1` (.36) / `agh-bf2` (.37) · TP23 Stufe 2/4/5 Das AGH-**Paar** (pve LXC 110/111, `10.40.20.36` / `.37`) ist der Client-Resolver des BF-Netzes (DHCP liefert beide in allen 7 VLANs). Upstream: DoH zum CF-Gateway; conditional `[/bf.emsenhuber.net/] → 10.40.20.1`. **Kein natives HA** → Sync ist Betriebsaufwand. ## Dateien | Datei | Zweck | |---|---| | `AdGuardHome.yaml` | **Repo-SSoT** der Config. **Passwort-Hashes REDIGIERT** (`''`) — keine Secrets im Git. Echte Hashes leben auf den Geräten; Klartext des API-Users in `~/.secrets`. | | `agh-sync.py` | `check` (Drift `.36`↔`.37`) · `push` (SSoT auf beide, gestaffelter Restart, reinjiziert Hashes live). | | `agh-exception.py` | Exception-Primitive (Stufe 5) gegen **beide** Instanzen. | ## Client-Policy (Stufe 4, `clients.persistent`) Ein VLAN = eine CIDR-Gruppe. Alle nutzen denselben CF-Upstream-Filter; Divergenz = per-Client-Toggle. | Gruppe | CIDR | filtering | safe_search | parental | |---|---|---|---|---| | grp-main | 10.40.10.0/24 | an (global) | – | – | | grp-infra | 10.40.20.0/24 | **aus** | – | – | | grp-iot | 10.40.30.0/24 | an | – | – | | grp-media | 10.40.40.0/24 | **aus** | – | – | | grp-untrusted | 10.40.50.0/24 | an | – | – | | grp-guest | 10.40.90.0/24 | an | **on** | **on** | | grp-mgmt | 10.40.99.0/24 | **aus** | – | – | | dns-bypass-exempt | 10.40.10.183 + .82 | **aus** | – | – | `filtering aus` bei INFRA/MEDIA/MGMT = bewusste Reduktion (Streaming/Server/Mgmt nicht brechen), per Gruppe rückholbar. ## Personen-Clients (Stufe 5 Redesign 2026-09-12) — SSoT für Person→Gerät Zusätzlich zu den VLAN-CIDR-Gruppen bekommt **jede Person einen eigenen AGH-Client** (`/control/clients`): `name` = Personenname, `ids` = **alle** Geräte-IPs der Person. Das ist der **einzige** Ort, an dem Person→Gerät gepflegt wird (kein zweites Mapping-File). Damit: - wirken Self-Service-Ausnahmen **pro Person**: `@@||domain^$client='Personenname'` statt global; - filtert `recent-blocked --client ` das Query-Log auf **ihre** Geräte; - bleiben Ausnahmen stabil bei Gerätewechsel (Name statt IP). 🔴 **On-network verifizieren, bevor Stufe 6 scharf geht:** dass AGH `$client=` auf **Allow**-Regeln (`@@`) in der laufenden Version greift. Fällt der Test negativ aus → Fallback `$client=|` (die Geräte-IPs); `agh-exception.py scoped_rule()` kann beides. Die CIDR-Gruppen (Policy-Grenze) bleiben unberührt — Personen-Clients tragen **nur** die Ausnahmen. ## Sync-Invariante AGH **schreibt die YAML beim Restart um** (normalisiert) → **nicht** gegen die Repo-Datei diffen, sondern **`.36` == `.37`**. Genau das prüft `agh-sync.py check`. ## Exception-Contract (Stufe 5 — was die HA-Schicht auslöst) Auth: Basic, User `automation` (`~/.secrets` → `AGH_AUTOMATION_*`). **Immer auf `.36` UND `.37`.** 🔴 **Wichtig für D:** die beiden AGH-Endpunkte sind **read-modify-write**, kein simples Append — `set_rules` **ersetzt die ganze Liste**, `clients/update` braucht das **volle Client-Objekt**. Ein zustandsloses HA-`rest_command` reicht deshalb **nicht**; `agh-exception.py` ist die Referenz-RMW-Implementierung, die D aufruft (Deploy-Weg auf HAOS in Etappe D zu entscheiden: `shell_command`/`command_line` gegen die deployte CLI **oder** ein dünner Wrapper-Endpunkt). | Aktion | CLI | AGH-API (RMW) | |---|---|---| | Domain **pro Person** entsperren | `allow-domain --client ` | `GET /control/filtering/status` → +`@@\|\|^$client=''` → `POST /control/filtering/set_rules` | | Domain global entsperren (Admin) | `allow-domain ` | dito, ohne `$client` | | Entsperrung zurück | `unallow-domain [--client ]` | dito, exakt dieselbe Regel entfernen | | „meine Ausnahmen" | `list-exceptions --client --json` | `GET /control/filtering/status`, `@@`-Regeln parsen/filtern | | „zuletzt geblockt" | `recent-blocked --client --json` | `GET /control/querylog?response_status=blocked`, client-seitig auf Geräte-IPs gefiltert | | Client entfiltern (grob) | `unfilter ` | `GET /control/clients` → Objekt, `filtering_enabled=false` → `POST /control/clients/update` | | Client wieder filtern | `refilter ` | dito, zurück auf global | Reine Regel-/Filter-Logik ist netzunabhängig getestet: `python3 agh-exception.py selftest`. **Kein Timing in der CLI** — Ablauf/Revert macht der Aufrufer (HA-Timer) und, ab Stufe 6, der RouterOS-Backstop. ## RouterOS-Backstop (Stufe 6, noch NICHT gebaut) Analog `jit-admin` (`routeros/tp5-etappe4-input-mgmt.rsc`): Address-List `jit-dns-bypass` mit `timeout` + Forward-Regel, die dieser Liste 53/853 nach außen erlaubt — der Fail-safe, falls HA den Revert verpasst. Gebaut via **Nautobot GC** (kein Direkt-Write), erst wenn die Bypass-Sperre scharf geschaltet wird.