8.7 KiB
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 <a routerLink="/pihole">Pi-hole</a> |
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:
// 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.
ng serve --proxy-config proxy.conf.json
proxy.conf.json (relevanter Block):
{
"/pihole": {
"target": "http://10.0.0.60:8088",
"secure": false,
"changeOrigin": true,
"pathRewrite": { "^/pihole": "" }
}
}
Änderungen an
proxy.conf.jsonoder den Environment-Dateien erfordern einen Neustart vonng 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-Port9999→ Container80. - Reverse-Proxy für
/pihole: Nginx Proxy Manager (NPM) auf separatem Host (Variante A — ein Host, Custom Location).
Bundle bauen und ausrollen
# 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/<projektname>/ /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:
<NAS-IP> - Forward Port:
9999 - Block Common Exploits: an
Advanced → Custom Nginx Configuration:
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 DevpathRewrite "^/pihole" -> ""). Ohne den trailing slash gibt es 404.
Da NPM das
/pihole-Proxying übernimmt, ist kein/pihole-Block inwebserver-docker/nginx.confnö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:
{ "session": { "valid": true, "sid": "<SID>", "validity": 1800, "message": null } }
Fallstrick: Bei falschem Passwort liefert Pi-hole
valid:true, abersid:nullundmessage:"password incorrect". Der Service prüft daher auf das Vorhandensein einersid, nicht nur aufvalid.
Die sid wird bei Folge-Requests im Header X-FTL-SID mitgeschickt.
Status lesen
GET /api/dns/blocking
-> { "blocking": "enabled" | "disabled", "timer": <sek> | null }
Blocking setzen
POST /api/dns/blocking
Body: { "blocking": true|false, "timer": <sek>|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
401einmal 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:
docker exec <proxy-container> 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-Whitelistallow/denyoder Basic-Auth in NPM). - Pi-hole-API möglichst nur über den Reverse-Proxy erreichbar machen,
10.0.0.60:8088nicht direkt nach außen exponieren.