# TP23 Stufe 5 (Redesign 2026-09-12) — DAU-tauglicher, personenbezogener DNS-Self-Service > 🔴 **Status: Bau-Spec, noch nicht deployt.** Die alte Fassung (single-domain, temp-only, > HA-Neustart bei neuer Domain) war „funktional ein kastriertes Pi-hole" (User-Befund 2026-09-12) > und ist ersetzt. Bau + Abnahme laufen **on-network** (HAOS `10.40.20.105`, AGH `.36/.37`) — beim > Anlegen den **home-assistant-best-practices-Skill** konsultieren und via **ha-mcp** in `.storage` > anlegen (native Helper, `entity_id` statt `device_id`). Diese Datei ist die Repro-/SSoT-Quelle. ## Ziel (User-Requirements 2026-09-12) Ein **einfaches, klar verständliches** End-User-UI zum Entsperren: - **alles was den Nutzer betrifft, nichts** von anderen / Admin / Power-User; - Policy nur sichtbar, wo sie für den einfachen Nutzer Relevanz hat. | # | Requirement | Umsetzung | |---|---|---| | 1 | **Pro Person** (Login), Sicht über alle Geräte | HA-User = Person; Person → AGH-Client (Name), dessen `ids` = ihre Geräte-IPs | | 2 | **„zuletzt geblockt" → 1 Klick** | `recent-blocked --client ` aus dem AGH-Query-Log, auf die Geräte der Person gefiltert | | 3 | **temporär UND dauerhaft**, Modus umschaltbar | Dauer-Select inkl. „dauerhaft"; `input_boolean` self-permanent ⇄ Anfrage. **Start: self-permanent** | | 4 | **HA-Dashboard, scoped** pro Person | ein View je Person, nur für ihre HA-User-ID sichtbar | ## Identitätsmodell — AGH-Client je Person ist die SSoT - Pro Person **ein AGH-Client** (`/control/clients`), `name` = Personenname, `ids` = **alle** Geräte-IPs der Person (die per DHCP faktisch statisch sind). Damit ist Person→Gerät **an einem Ort** gepflegt (kein zweites Mapping-File), und Ausnahmen sind stabil bei Gerätewechsel. - Ausnahme-Regel = `@@||domain^$client='Personenname'` (statt globalem `@@||domain^`). 🔴 **Vor Scharfschalten on-network verifizieren, dass AGH `$client=` auf `@@`-Regeln wirkt** (s. `agh-exception.py` Kopf). Fällt der Test negativ aus: Fallback = `$client=|` mit den Geräte-IPs statt dem Namen (Code kann beides, `scoped_rule()`). - Die bestehenden **VLAN-CIDR-Gruppen** bleiben die Policy-Grenze (Filter an/aus je VLAN); Personen-Clients sind spezifischer und tragen nur die *Ausnahmen*. ## CLI (deployt nach `/config/agh-exception.py`, wirkt auf `.36`+`.37`) ``` allow-domain --client # Ausnahme setzen (temp: HA revertiert; perm: kein Timer) unallow-domain --client # dieselbe Ausnahme entfernen list-exceptions --client --json # „meine Ausnahmen" (für Übersicht + Entfernen) recent-blocked --client --json # „zuletzt geblockt" (nur Geräte der Person) ``` `--client` nimmt einen AGH-Client-Namen (→ IPs via `/control/clients`) ODER eine Komma-IP/CIDR-Liste. ## `configuration.yaml` — `shell_command` (einziger YAML-Teil, Backup `.bak-tp23-*`) Domain **und** Client sind Parameter → **kein HA-Neustart mehr** bei neuer Domain (das war der alte DAU-Killer: pro Domain ein eigener `shell_command`). ```yaml shell_command: agh_allow: "python3 /config/agh-exception.py allow-domain '{{ domain }}' --client '{{ client }}'" agh_unallow: "python3 /config/agh-exception.py unallow-domain '{{ domain }}' --client '{{ client }}'" ``` ## Helfer / Entities (via ha-mcp in .storage) Pro Person (Beispiel `christoph`) — bei mehreren Personen je einmal, Suffix = Personen-Slug: | Typ | entity_id | Zweck | |---|---|---| | command_line sensor | `sensor.dns_recent_blocked_` | `recent-blocked --client --json` → `json_attributes: items` (Liste {domain,count,last}); Scan-Intervall ~60 s | | command_line sensor | `sensor.dns_exceptions_` | `list-exceptions --client --json` → aktive Ausnahmen der Person | | input_select | `input_select.dns_dauer_` | 1 Stunde / 4 Stunden / **dauerhaft** | | timer | `timer.dns_ausnahme_` | JIT-Countdown je temporärer Freigabe (restore=false) | | input_boolean | `input_boolean.dns_permanent_ohne_anfrage` | **global**: an = „dauerhaft" wirkt sofort (Start); aus = erzeugt Admin-Genehmigung | | script | `script.dns_freigeben_` | nimmt `domain` als Feld → `agh_allow`; bei „dauerhaft"+Switch-aus → `todo`/Notification statt Freigabe; sonst Timer starten | | automation | `automation.dns_ablauf_` | `timer.finished`/`.cancelled` → `agh_unallow`, Aufräumen | ## Dashboard — ein scoped View je Person - Eigener storage-Dashboard-View, **nur für die HA-User-ID der Person sichtbar** (`visible:` mit der user-id; `require_admin:false`). Kleiner Haushalt → ein View je Person ist praktikabel und umgeht das „Template kennt den aktuellen Betrachter nicht"-Problem. - **Karte 1 „Zuletzt geblockt (antippen zum Freigeben)":** rendert `sensor.dns_recent_blocked_` Attribut `items`; jede Zeile hat eine `tap_action` → `script.dns_freigeben_` mit `domain` der Zeile + aktueller `input_select`-Dauer. (Dynamische Per-Zeilen-tap_action braucht i.d.R. `auto-entities` + `template`/`button-card` **oder** ein kleines Custom-List-Template — **beim Bau on-network entscheiden**, das ist der Teil, der Live-Verifikation braucht.) - **Karte 2 „Meine Ausnahmen":** `sensor.dns_exceptions_` als Liste, je Eintrag Entfernen (`agh_unallow`). - **Karte 3 „Dauer":** `input_select.dns_dauer_` (inkl. „dauerhaft"). - **Kein** globaler Status, keine fremden Clients, keine Policy-Interna. Nur wenn relevant ein kurzer Hinweis („durch den Familien-Filter blockiert"). ## Genehmigungs-Variante (Modus-Switch aus) `input_boolean.dns_permanent_ohne_anfrage = off` → „dauerhaft"-Klick legt statt der Freigabe einen Eintrag in einer **`todo`-Liste** „DNS-Freigaben zu prüfen" an (+ Notification an Christoph). Beim Abhaken führt eine Admin-Automation `agh_allow` ohne Timer aus. Beide Varianten sind gebaut; der Switch entscheidet zur Laufzeit. ## Alt-Reste, die beim Bau wegfallen/fixen - Single-domain-Flow (`input_text.dns_ausnahme_domain/_aktiv`, `input_button…_starten`, `automation…_starten/_ablauf`) und der `agh_allow_domain`/`_unallow_domain`-**pro-Domain**-Zwang. - Statuskachel-Sensor `binary_sensor.adguard_bf1_status` hing am toten `.16` → auf `.36`/`.37` neu. ## Abnahme (on-network, = neue echte Stufe-6-Vorbedingung) Mit einem **echten Nicht-Admin-Login**: geblockte Seite → erscheint in *ihrer* Fläche → 1 Klick temporär (revertiert nach Ablauf) **und** 1 Klick dauerhaft (bleibt) → wirkt **nur** für diese Person (Gegentest: anderer Client sieht die Domain weiter geblockt). `.36 == .37` (`agh-sync.py check`). Modus-Switch beide Wege einmal durchgespielt. ## Verweise - CLI: `adguard/agh-exception.py` (Subcommands + `selftest`). Deploy-Weg unverändert (base64→scp, s. unten). Creds `/config/agh-exception.secrets` (chmod 600). - Redeploy: `base64 < adguard/agh-exception.py | ssh admin@10.40.20.105 "base64 -d | sudo tee /config/agh-exception.py >/dev/null"`. - Personen-Client-Modell: `adguard/README.md`. Kontext/Reihenfolge: TP23 in `docs/teilprojekte.md`.