Pi Hole Readme

This commit is contained in:
2026-06-29 13:09:54 +02:00
parent 3d670b4839
commit 36dd246360
+273
View File
@@ -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 `<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:
```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/<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:**
```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": "<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": <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 `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 <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-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.