From 36dd246360f6141084bc02a24ce15753be3380cf Mon Sep 17 00:00:00 2001 From: Hans-Christian Payer Date: Mon, 29 Jun 2026 13:09:54 +0200 Subject: [PATCH] Pi Hole Readme --- README_Pihole.md | 273 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 README_Pihole.md diff --git a/README_Pihole.md b/README_Pihole.md new file mode 100644 index 0000000..1a0da37 --- /dev/null +++ b/README_Pihole.md @@ -0,0 +1,273 @@ +# Pi-hole-Steuerung + +Eigene Seite in der Landingpage zum Steuern von Pi-hole (v6) über dessen +REST-API: Blocking ein-/ausschalten, für 30 Minuten deaktivieren und den +aktuellen Status anzeigen (rot = Blocking aktiv, grün = kein Blocking). + +--- + +## Funktionsumfang + +- **Status-Anzeige**: farbiges Badge + - rot = Blocking aktiv + - grün = kein Blocking + - grau = unbekannt / nicht abrufbar +- **Schalter Blocking an/aus**: aktiviert bzw. deaktiviert das Blocking dauerhaft. +- **Schalter "30 Min deaktivieren"**: deaktiviert das Blocking temporär für + 30 Minuten (Pi-hole-Timer = 1800 s). Live-Countdown (mm:ss) bis zur + automatischen Reaktivierung. +- Erreichbar unter der Route `/#/pihole` (Hash-Routing). + +--- + +## Architektur + +``` +Browser (Angular SPA) + │ relative Calls: /pihole/api/... + ▼ +Reverse-Proxy (Dev: ng serve proxy.conf.json | Prod: Nginx Proxy Manager) + │ strippt /pihole/ -> /api/... + ▼ +Pi-hole v6 REST-API http://10.0.0.60:8088/api/... +``` + +**Wichtigstes Designprinzip — Same-Origin:** +Die App ruft die API **relativ** unter `/pihole/api/...` auf, also immer auf +demselben Host/Port, von dem die Seite geladen wurde. Dadurch gibt es **kein +CORS**. Ein vorgeschalteter Reverse-Proxy leitet `/pihole/...` an die echte +Pi-hole-API weiter und entfernt dabei das `/pihole`-Präfix. + +--- + +## Dateien + +| Datei | Zweck | +|---|---| +| `src/app/pihole.interface.ts` | TypeScript-Typen für Status/Auth-Antworten | +| `src/app/pihole.service.ts` | API-Service: Auth (SID), Status lesen, Blocking setzen | +| `src/app/pihole-control/pihole-control.component.ts` | Komponentenlogik, Status, Countdown | +| `src/app/pihole-control/pihole-control.component.html` | Template (Badge, Schalter) | +| `src/app/pihole-control/pihole-control.component.css` | Styles (rot/grün/grau) | +| `src/app/app.module.ts` | Deklaration der Komponente | +| `src/app/app-routing.module.ts` | Route `{ path: 'pihole', component: PiholeControlComponent }` | +| `src/app/app.component.html` | Navigationslink `Pi-hole` | +| `src/environments/environment.ts` | Dev: `piholePassword` | +| `src/environments/environment.prod.ts` | Prod: `piholePassword` | +| `proxy.conf.json` | Dev-Proxy für `/pihole` | +| `webserver-docker/nginx.conf` | Prod-Nginx (nur relevant, wenn NICHT über NPM proxyt wird) | + +> Das Feature folgt demselben Muster wie die bestehende `tasmota-control`- +> Komponente (Service + Interface + Component). + +--- + +## Konfiguration + +Das Pi-hole-Passwort steht in den Environment-Dateien: + +```ts +// src/environments/environment.ts (Dev) +// src/environments/environment.prod.ts (Prod) +export const environment = { + production: true, // bzw. false in Dev + // ... bestehende Felder ... + piholePassword: 'DEIN_PIHOLE_PASSWORT' +}; +``` + +> **Sicherheitshinweis:** Das Passwort wird ins JS-Bundle einkompiliert und ist +> damit im Browser lesbar. Das ist für ein reines LAN/VPN-Tool vertretbar. +> Für breitere Exposition: dediziertes Pi-hole **App-Passwort** +> (Pi-hole → Settings → API → App password) statt des Admin-Passworts, oder +> serverseitige Authentifizierung im Reverse-Proxy (Secret verlässt den +> Server nicht). + +Die Pi-hole-Adresse (`http://10.0.0.60:8088`) ist im Reverse-Proxy +konfiguriert, **nicht** im Frontend. + +--- + +## Entwicklung (Dev) + +`ng serve` proxyt `/pihole` über `proxy.conf.json` an die Pi-hole-API. + +```bash +ng serve --proxy-config proxy.conf.json +``` + +`proxy.conf.json` (relevanter Block): + +```json +{ + "/pihole": { + "target": "http://10.0.0.60:8088", + "secure": false, + "changeOrigin": true, + "pathRewrite": { "^/pihole": "" } + } +} +``` + +> Änderungen an `proxy.conf.json` oder den Environment-Dateien erfordern einen +> Neustart von `ng serve`. + +Aufruf: `http://localhost:4200/#/pihole` + +--- + +## Produktion (Prod) + +### Setup-Überblick + +- Auslieferung: Container `angular-web` (`nginx:stable-alpine`) auf der + Synology-NAS. Fertiges Bundle in `/volume1/docker/landingpage/dist` + (read-only gemountet), Host-Port `9999` → Container `80`. +- Reverse-Proxy für `/pihole`: **Nginx Proxy Manager (NPM)** auf separatem Host + (Variante A — ein Host, Custom Location). + +### Bundle bauen und ausrollen + +```bash +# 1. Passwort in src/environments/environment.prod.ts eintragen (siehe Konfiguration) + +# 2. Prod-Build +ng build --configuration production + +# 3. dist nach /volume1 kopieren (Pfad an angular.json anpassen) +rsync -a --delete dist// /volume1/docker/landingpage/dist/ + +# 4. Container neu laden (statische Files sind sofort aktuell; Reload nur bei conf-Änderung nötig) +docker exec angular-web nginx -s reload +``` + +### Reverse-Proxy via Nginx Proxy Manager (Variante A) + +Landingpage und Pi-hole-API laufen über **denselben** NPM-Host (same-origin), +damit der relative `/pihole/api`-Aufruf ohne CORS funktioniert. + +**Proxy Host (Details):** +- Domain Names: z. B. `homenas.deine.domain` +- Scheme: `http` +- Forward Hostname / IP: `` +- Forward Port: `9999` +- Block Common Exploits: an + +**Advanced → Custom Nginx Configuration:** + +```nginx +location /pihole/ { + proxy_pass http://10.0.0.60:8088/; + + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-FTL-SID $http_x_ftl_sid; + + proxy_buffering off; + proxy_read_timeout 30s; + proxy_connect_timeout 5s; +} +``` + +> Der **trailing slash** in `proxy_pass http://10.0.0.60:8088/;` strippt das +> `/pihole/`-Präfix → Pi-hole sieht `/api/...` (äquivalent zum Dev +> `pathRewrite "^/pihole" -> ""`). Ohne den trailing slash gibt es 404. + +> Da NPM das `/pihole`-Proxying übernimmt, ist **kein** `/pihole`-Block in +> `webserver-docker/nginx.conf` nötig. Der Block dort wird nur gebraucht, wenn +> der NAS-Nginx selbst proxyt (ohne NPM). + +Aufruf: `https://homenas.deine.domain/#/pihole` + +--- + +## Pi-hole v6 REST-API (Referenz) + +Basis: `http://10.0.0.60:8088/api` + +### Authentifizierung + +``` +POST /api/auth +Body: {"password":"..."} +``` + +Antwort: + +```json +{ "session": { "valid": true, "sid": "", "validity": 1800, "message": null } } +``` + +> **Fallstrick:** Bei falschem Passwort liefert Pi-hole `valid:true`, aber +> `sid:null` und `message:"password incorrect"`. Der Service prüft daher auf +> das Vorhandensein einer **`sid`**, nicht nur auf `valid`. + +Die `sid` wird bei Folge-Requests im Header `X-FTL-SID` mitgeschickt. + +### Status lesen + +``` +GET /api/dns/blocking +-> { "blocking": "enabled" | "disabled", "timer": | null } +``` + +### Blocking setzen + +``` +POST /api/dns/blocking +Body: { "blocking": true|false, "timer": |null } +``` + +- Blocking an: `{"blocking": true, "timer": null}` +- Blocking aus: `{"blocking": false, "timer": null}` +- 30 Min aus: `{"blocking": false, "timer": 1800}` + +Offizielle Doku: https://docs.pi-hole.net/api/ + +--- + +## Service-Methoden (`pihole.service.ts`) + +- `authenticate()` – holt eine SID; wirft bei Fehler mit der echten + Pi-hole-Meldung (z. B. "password incorrect"). +- `ensureSid()` – stellt sicher, dass eine gültige SID vorliegt. +- `getStatus()` – aktueller Blocking-Status inkl. Timer. +- `isBlockingEnabled()` – Bool-Kurzform. +- `setBlocking(enabled, timerSeconds?)` – setzt den Status. +- `enableBlocking()` / `disableBlocking()` – Komfort-Wrapper. +- `disableFor30Minutes()` – deaktiviert mit Timer 1800 s. +- SID wird bei `401` einmal automatisch erneuert. + +--- + +## Troubleshooting + +| Symptom | Ursache | Lösung | +|---|---|---| +| 404 beim Statusabruf | `/pihole`-Proxy fehlt oder trailing slash falsch | Custom Location in NPM prüfen; `proxy_pass ...:8088/` mit trailing slash | +| "kein gültiges SID" / "password incorrect" | falsches Passwort | korrektes Admin-/App-Passwort in `environment(.prod).ts`; per `pihole setpassword` neu setzen | +| Status bleibt grau / nicht abrufbar | Proxy erreicht Pi-hole nicht | Erreichbarkeit `10.0.0.60:8088` aus dem Proxy-Container testen | +| Funktioniert in Dev, nicht in Prod | NPM-Host erreicht NAS/Pi-hole nicht | vom NPM-Container `wget` auf `:9999` und `10.0.0.60:8088` testen | +| Falsche Port-Annahme (80) | API läuft auf `:8088`, nicht `:80` | überall `http://10.0.0.60:8088` verwenden | + +**Erreichbarkeitstest aus dem Reverse-Proxy-Container:** + +```bash +docker exec sh -c \ + 'wget -qO- http://10.0.0.60:8088/api/auth; echo " exit=$?"' +``` + +Eine JSON-Antwort (auch "password incorrect") beweist, dass das Netz steht. + +--- + +## Sicherheit + +- Passwort im Bundle ist im Browser lesbar → nur für LAN/VPN geeignet. +- Den `/pihole`-Proxy bei breiterer Erreichbarkeit absichern + (IP-Whitelist `allow/deny` oder Basic-Auth in NPM). +- Pi-hole-API möglichst nur über den Reverse-Proxy erreichbar machen, + `10.0.0.60:8088` nicht direkt nach außen exponieren.