Files
new-landing-page-homenas/README_Pihole.md
T
2026-06-29 13:09:54 +02:00

274 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.