diff --git a/.gitignore b/.gitignore
index 2db5fd6..15c5ac3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,6 +18,7 @@ report.md
# Python cache
__pycache__/
+.pytest_cache/
*.pyc
# VS Code
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2fc1a2a..9be1358 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
### Added
+- Authenticated read-only DNS dashboard at `/dashboard/` alongside the separate maintenance panel at `/`.
+- Production setup and API data-availability notes in both READMEs.
- **Rescue & Backup Menu** (`scripts/rescue_menu.sh` v1.0.0) — standalone interactive recovery tool with:
- System status check (services, DNS, ports, temperature)
- DNS loop / upstream check with blocking test
@@ -20,9 +22,17 @@ All notable changes to this project will be documented in this file.
- **Unified output format**: `tools/pihole_maintenance_pro.sh` `run_step()` now uses `log_ok`/`log_warn`/`log_err` from `scripts/lib/ui.sh`
### Changed
+- Existing deployments with the maintenance panel can use `--upgrade-only` without changing DNS configuration or the Python environment.
+- README platform, Python, dashboard, maintenance, and repository-structure details now match the current implementation.
- Console menu Exit moved from [7] to [8] to accommodate new Rescue entry
- `action_check_mode()` in console menu now also validates rescue_menu availability
+### Security
+- Bound DNS-log line parsing and keep the browser API key in memory only.
+- Grant the suite service read access to Pi-hole logs through its supplementary group.
+- Restrict the test workflow token to `contents: read`.
+- Removed outdated screenshots that showed failed checks or the private LAN address.
+
## [0.2.0] — 2026-01-02
### Added
diff --git a/README.de.md b/README.de.md
index 96653df..9e06051 100644
--- a/README.de.md
+++ b/README.de.md
@@ -8,7 +8,7 @@
[](https://pi-hole.net/)
[](https://nlnetlabs.nl/projects/unbound/)
[](https://debian.org/)
-[](https://python.org/)
+[](https://python.org/)
[](https://buymeacoffee.com/timintech)
@@ -30,6 +30,19 @@ Client → Pi-hole (Port 53) → Unbound (Port 5335) → Internet
Pi-hole blockiert Werbung und Tracker. Unbound löst DNS-Anfragen rekursiv auf — ohne externen DNS-Resolver, mit vollständiger DNSSEC-Validierung.
+## Projektstatus und letzte Aktualisierung
+
+**Dokumentation zuletzt geprüft:** 2026-10-28
+
+**Letztes Projektupdate:** 2026-10-28 — [PR #32](https://github.com/TimInTech/Pi-hole-Unbound-PiAlert-Setup/pull/32), [PR #33](https://github.com/TimInTech/Pi-hole-Unbound-PiAlert-Setup/pull/33) und [PR #34](https://github.com/TimInTech/Pi-hole-Unbound-PiAlert-Setup/pull/34).
+
+- Das lesende DNS-Dashboard wurde mit der authentifizierten Wartungsoberfläche integriert.
+- Updates wurden abgesichert, DNS-Logzeilen begrenzt, der API-Key aus dauerhaftem Browser-Speicher entfernt und der Logzugriff auf eine Leseberechtigung beschränkt.
+- Der Test-Workflow verwendet nur noch schreibgeschützte Repository-Berechtigungen.
+- DNS-/DNSSEC-Verhalten, beide Weboberflächen und authentifizierte API-Routen wurden produktiv geprüft.
+
+NetAlertX ist optional. Bei der letzten Produktionsprüfung war es nicht eingerichtet; `/devices` meldet daher HTTP 503, statt eine leere Geräteliste vorzutäuschen. Pi-hole-/Unbound-Monitoring und DNS funktionieren unabhängig davon. Die Änderungshistorie steht in [CHANGELOG.md](CHANGELOG.md).
+
---
## ⚡ Schnellstart
@@ -54,7 +67,7 @@ Für eine bereits laufende Suite mit Wartungsoberfläche aktualisiert `sudo ./in
| **Plattform** | Raspberry Pi 3/4/5, Debian Bookworm/Trixie (64-bit) |
| **Pi-hole** | v6.x (wird durch dieses Script installiert) |
| **Unbound** | Wird installiert und auf Port 5335 konfiguriert |
-| **Python** | 3.12+ (für optionale Suite API) |
+| **Python** | 3.10+ (für optionale Suite API und Wartungsoberfläche) |
| **Nutzer** | Normaler Nutzer mit sudo |
Abhängigkeiten manuell installieren (optional):
@@ -123,8 +136,6 @@ Eigenständiges Wiederherstellungs- und Diagnosetool. Global aufrufbar:
sudo pihole-rescue
```
-
-
| Option | Aktion |
|--------|--------|
| 1 | Systemstatus (Services, DNS, Ports, Temperatur) |
@@ -140,14 +151,6 @@ sudo pihole-rescue
| 11 | Letzten Report / Log anzeigen |
| 0 | Beenden |
-**Systemstatus:**
-
-
-
-**DNS-Check:**
-
-
-
---
### 3. Maintenance Pro (`tools/pihole_maintenance_pro.sh`)
@@ -168,23 +171,29 @@ Flags: `--no-apt`, `--no-upgrade`, `--no-gravity`, `--restart-ftl`, `--backup`,
```
Pi-hole-Unbound-PiAlert-Setup/
-├── install.sh # Haupt-Installer
-├── start_suite.py # Optionale REST-API (FastAPI/uvicorn)
+├── install.sh # Host-/Container-Installer
+├── start_suite.py # Optionale API und DNS-Dashboard
+├── maintenance_web.py # Wartungsoberfläche und Session-Sicherheit
+├── maintenance_runner.py # Privilegierter Wartungs-Runner
+├── maintenance_backups.py # Verifizierte Backups
+├── maintenance_config.py # Caddy-/LAN-Konfiguration
+├── maintenance_install.py # Einrichtung der Wartungsoberfläche
├── requirements.txt # Python-Abhängigkeiten
├── .env.example # Umgebungsvariablen-Vorlage
+├── dashboard/ # Lesendes DNS-Monitoring-Dashboard
+├── web/ # Assets der Wartungsoberfläche
+├── deploy/ # systemd-, Caddy- und sudo-Dateien
├── scripts/
+│ ├── install_maintenance_web.sh # Gestufter Installer
+│ ├── rollback_maintenance_web.sh # Geprüfter Deployment-Rollback
│ ├── console_menu.sh # Interaktives Verwaltungsmenü
-│ ├── rescue_menu.sh # Rescue & Backup Menü (sudo pihole-rescue)
-│ ├── post_install_check.sh # Post-Install-Verifikation
-│ ├── nightly_test.sh # Nächtlicher DNS/Service-Test
-│ ├── repo_selftest.sh # Repo-Integritäts-Selbsttest
-│ └── lib/
-│ └── ui.sh # Gemeinsame UI-Bibliothek
-├── tools/
-│ └── pihole_maintenance_pro.sh # Batch-Wartungsscript
-└── docs/
- ├── CONSOLE_MENU.md # Vollständige Menü-Dokumentation
- └── assets/ # Screenshots
+│ ├── rescue_menu.sh # Rescue- und Backup-Menü
+│ └── lib/ui.sh # Gemeinsame Shell-UI
+├── tools/pihole_maintenance_pro.sh # Batch-Wartungsscript
+├── tests/ # Python-, Shell-, Browser- und Upgrade-Tests
+├── docs/ # Betrieb, Entwürfe, Berichte, Screenshots
+├── CHANGELOG.md
+└── README.md
```
---
@@ -239,9 +248,11 @@ Backups werden in `/home/pi/pihole-rescue-backups/` gespeichert und enthalten:
`start_suite.py` ist eine **optionale** FastAPI-Anwendung für Monitoring.
Es enthält auch ein modernes, responsives **Dashboard** zur Überwachung Ihres DNS-Stacks.
-- **Dashboard-Zugriff**: Standardmäßig erreichbar unter `http://127.0.0.1:8090/`.
-- **Fernzugriff**: Ein sicherer Fernzugriff wird über SSH-Portweiterleitung (`ssh -L 8090:127.0.0.1:8090 user@pihole-server`) oder einen Reverse-Proxy empfohlen.
+- **Dashboard-Zugriff**: Auf einem eingerichteten Host über Caddy unter `https://:8443/dashboard/`. Lokal für die Entwicklung unter `http://127.0.0.1:8090/dashboard/`.
+- **Wartungsoberfläche**: Der Root-Pfad `https://:8443/` zeigt die separate Wartungsoberfläche, wenn sie aktiviert ist.
+- **Fernzugriff**: Die API bindet an `127.0.0.1:8090`. Diesen Port nicht direkt freigeben; Caddy-LAN-Zugriff oder SSH-Tunnel verwenden.
- **Authentifizierung**: Für das Dashboard-Login und die API-Endpunkte ist der `SUITE_API_KEY` erforderlich.
+- Der Schlüssel bleibt nur im Speicher der Browserseite. Nach einem Neuladen ist eine erneute Anmeldung nötig.
```bash
# Konfiguration
@@ -253,23 +264,45 @@ python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# Starten
+set -a
+. ./.env
+set +a
python3 start_suite.py
# Die API bleibt auf 127.0.0.1:8090; die Produktivdokumentation ist deaktiviert.
```
+Das DNS-Dashboard zeigt Gesamtstatus, Pi-hole-FTL-Status und Version, Upstreams
+und die Zahl gelesener Anfragen aus den letzten 100 Logeinträgen; Unbound-Status,
+Port und DNS-Prüfung; optionale NetAlertX-Erreichbarkeit; sowie Laufzeit, Version
+und Host-IP der Suite-API. Fehlende Daten erscheinen als nicht verfügbar, nicht
+als Null. Die API bietet `/health`, `/version`, `/urls`, `/pihole`, `/unbound`,
+`/netalertx`, `/stats`, `/dns`, `/leases` und `/devices`. Die Wartungsendpunkte
+liegen unter `/api/session` und `/api/maintenance/`. Geräteerkennung antwortet
+mit HTTP 503, solange keine NetAlertX-Integration eingerichtet ist.
+
+Die Wartungsoberfläche zeigt den letzten Auftrag, seine Prüfschritte und eine
+Systemmomentaufnahme. Sie kann Systemchecks, verifizierte Backups (bis zu 10,
+mit Download und Löschung) sowie kontrollierte Updates ausführen. Eine
+Wiederherstellung läuft lokal nach der dokumentierten Recovery-Anleitung, nicht
+im Browser.
+
**Nicht erforderlich** für den Kern-Stack (Pi-hole + Unbound).
### Sichere Wartungsoberfläche
-Die optionale Wartungsoberfläche bietet Systemcheck, verifiziertes Backup und
-kontrolliertes Update unter `https://pi.hole:8443/`. Sie bleibt standardmäßig
-deaktiviert und wird nur zusammen mit Caddy bewusst freigeschaltet. Die
-Browser-Anmeldung verwendet den bestehenden `SUITE_API_KEY`:
+Die optionale Wartungsoberfläche bietet Systemcheck, verifizierte Backups,
+kontrollierte Updates, das letzte Auftragsergebnis, eine Systemmomentaufnahme
+und Prüfschritte. Sie bleibt standardmäßig deaktiviert. Bei einer neuen
+Host-Installation wird sie so aktiviert:
```bash
sudo ./install.sh --with-maintenance-web
```
+Eine bestehende Installation mit Wartungsoberfläche wird mit
+`sudo ./install.sh --upgrade-only` aktualisiert. Die Browser-Anmeldung nutzt
+den bestehenden `SUITE_API_KEY`.
+
Siehe [docs/MAINTENANCE_WEB.md](docs/MAINTENANCE_WEB.md) für Voraussetzungen,
Zertifikatvertrauen, Sicherheitsgrenze und Rollback.
@@ -283,6 +316,9 @@ Netzwerk-Gerätemonitoring (separate Installation):
sudo ./install.sh --with-netalertx
```
+Ohne NetAlertX ist die Geräteerkennung nicht verfügbar. DNS-Monitoring und
+Pi-hole-/Unbound-Betrieb funktionieren weiterhin.
+
---
## 🔎 Manueller Update-Check
diff --git a/README.md b/README.md
index 033d16d..2b83b57 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
[](https://pi-hole.net/)
[](https://nlnetlabs.nl/projects/unbound/)
[](https://debian.org/)
-[](https://python.org/)
+[](https://python.org/)
[](https://buymeacoffee.com/timintech)
@@ -32,6 +32,19 @@ Client → Pi-hole (port 53) → Unbound (port 5335) → Internet
Pi-hole handles ad/tracker blocking; Unbound handles recursive DNS resolution with full DNSSEC validation — no third-party DNS resolver required.
+## Project Status and Latest Update
+
+**Documentation last checked:** 2026-10-28
+
+**Latest project update:** 2026-10-28 — [PR #32](https://github.com/TimInTech/Pi-hole-Unbound-PiAlert-Setup/pull/32), [PR #33](https://github.com/TimInTech/Pi-hole-Unbound-PiAlert-Setup/pull/33), and [PR #34](https://github.com/TimInTech/Pi-hole-Unbound-PiAlert-Setup/pull/34).
+
+- Integrated the read-only DNS dashboard with the authenticated maintenance panel.
+- Hardened upgrades, bounded DNS-log parsing, kept the API key out of persistent browser storage, and enabled least-privilege log access.
+- Restricted the test workflow token to read-only repository contents.
+- Rechecked production DNS and DNSSEC behavior, both web interfaces, and authenticated API routes.
+
+NetAlertX is optional. It was not configured on the production system last checked, so `/devices` reports HTTP 503 rather than inventing an empty device list. Pi-hole and Unbound monitoring and DNS are independent of this optional feature. See [CHANGELOG.md](CHANGELOG.md) for the change history.
+
---
## ⚡ Quickstart
@@ -56,7 +69,7 @@ For an existing suite with the maintenance interface, `sudo ./install.sh --upgra
| **Platform** | Raspberry Pi 3/4/5, Debian Bookworm/Trixie (64-bit) |
| **Pi-hole** | v6.x (installed by this script) |
| **Unbound** | Installed and configured to port 5335 |
-| **Python** | 3.12+ (for optional Suite API) |
+| **Python** | 3.10+ (for the optional Suite API and maintenance panel) |
| **User** | Normal user with sudo |
Install prerequisites manually (optional):
@@ -125,8 +138,6 @@ Standalone recovery and diagnostic tool. Accessible globally:
sudo pihole-rescue
```
-
-
| Option | Action |
|--------|--------|
| 1 | System status check (services, DNS, ports, temperature) |
@@ -142,14 +153,6 @@ sudo pihole-rescue
| 11 | Show last report / log |
| 0 | Exit |
-**System Status:**
-
-
-
-**DNS Check:**
-
-
-
---
### 3. Maintenance Pro (`tools/pihole_maintenance_pro.sh`)
@@ -170,23 +173,29 @@ Available flags: `--no-apt`, `--no-upgrade`, `--no-gravity`, `--restart-ftl`, `-
```
Pi-hole-Unbound-PiAlert-Setup/
-├── install.sh # Main installer
-├── start_suite.py # Optional REST API (FastAPI/uvicorn)
-├── requirements.txt # Python deps for Suite API
-├── .env.example # Environment variables template
+├── install.sh # Main host/container installer
+├── start_suite.py # Optional authenticated API and DNS dashboard
+├── maintenance_web.py # Browser maintenance panel and session security
+├── maintenance_runner.py # Privileged maintenance job runner
+├── maintenance_backups.py # Verified backup operations
+├── maintenance_config.py # Caddy and LAN configuration validation
+├── maintenance_install.py # Maintenance panel provisioning
+├── requirements.txt # Python API and test dependencies
+├── .env.example # Environment variable template
+├── dashboard/ # Read-only DNS monitoring dashboard
+├── web/ # Maintenance panel assets
+├── deploy/ # systemd, Caddy, and sudo policy files
├── scripts/
+│ ├── install_maintenance_web.sh # Staged/live maintenance installer
+│ ├── rollback_maintenance_web.sh # Validated deployment rollback
│ ├── console_menu.sh # Interactive management menu
-│ ├── rescue_menu.sh # Rescue & backup menu (sudo pihole-rescue)
-│ ├── post_install_check.sh # Post-install verification
-│ ├── nightly_test.sh # Nightly DNS/service test
-│ ├── repo_selftest.sh # Repo integrity self-test
-│ └── lib/
-│ └── ui.sh # Shared UI library (colors, log helpers)
-├── tools/
-│ └── pihole_maintenance_pro.sh # Batch maintenance script
-└── docs/
- ├── CONSOLE_MENU.md # Full menu documentation
- └── assets/ # Screenshots
+│ ├── rescue_menu.sh # Rescue and backup menu
+│ └── lib/ui.sh # Shared shell UI helpers
+├── tools/pihole_maintenance_pro.sh # Batch maintenance script
+├── tests/ # Python, shell, browser, and upgrade tests
+├── docs/ # Operations, design notes, reports, screenshots
+├── CHANGELOG.md
+└── README.de.md
```
---
@@ -241,9 +250,11 @@ Backups are stored in `/home/pi/pihole-rescue-backups/` and include:
`start_suite.py` is an **optional** FastAPI server providing a JSON API for monitoring.
It also hosts a modern, responsive, read-only web **Dashboard** for monitoring your DNS stack.
-- **Dashboard Access**: By default accessible at `http://127.0.0.1:8090/`.
-- **Remote Access**: Secure remote access is recommended via SSH port forwarding (`ssh -L 8090:127.0.0.1:8090 user@pihole-server`) or a reverse proxy.
+- **Dashboard Access**: On a configured host, open `https://:8443/dashboard/` through Caddy. For local development, use `http://127.0.0.1:8090/dashboard/`.
+- **Maintenance Panel**: The root path `https://:8443/` serves the separate maintenance panel when enabled.
+- **Remote Access**: The API binds to `127.0.0.1:8090`. Do not expose that port directly; use the configured Caddy LAN endpoint or an SSH tunnel.
- **Authentication**: Dashboard login and API endpoints require the `SUITE_API_KEY`.
+- The dashboard holds the key in page memory only. Reloading the page requires signing in again.
```bash
# Setup
@@ -255,23 +266,44 @@ python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# Start
+set -a
+. ./.env
+set +a
python3 start_suite.py
# The API stays on 127.0.0.1:8090; production API documentation is disabled.
```
+The DNS dashboard reports overall health, Pi-hole FTL status and version,
+configured upstreams and the count of parsed queries from the latest 100 log
+entries; Unbound status, port and DNS probe; optional NetAlertX reachability; and
+Suite API uptime, version and detected host IP. Missing source data is shown as
+unavailable, not as zero. The API provides `/health`, `/version`,
+`/urls`, `/pihole`, `/unbound`, `/netalertx`, `/stats`, `/dns`, `/leases`, and
+`/devices`. Maintenance endpoints are under `/api/session` and
+`/api/maintenance/`. Device discovery returns HTTP 503 until NetAlertX
+integration is configured.
+
+The maintenance panel shows the latest job result, its checks and system
+snapshot. It can run a system check, create and verify backups (up to 10, with
+download and deletion), and run a controlled update. Restore is performed locally
+through the documented recovery procedure, not from the browser.
+
**Not required** for the core Pi-hole + Unbound stack.
### Secure maintenance web panel
-The optional maintenance panel provides system checks, verified backups and
-controlled updates at `https://pi.hole:8443/`. It remains disabled by default
-and is enabled explicitly with Caddy. Browser sign-in uses the existing
-`SUITE_API_KEY`:
+The optional maintenance panel provides a system check, verified backups,
+controlled updates, the latest job result, system snapshot, and check steps. It
+remains disabled by default. On a fresh host installation, enable it with:
```bash
sudo ./install.sh --with-maintenance-web
```
+For an existing installation that already has the maintenance panel, update it
+with `sudo ./install.sh --upgrade-only`. Browser sign-in uses the existing
+`SUITE_API_KEY`.
+
See [docs/MAINTENANCE_WEB.md](docs/MAINTENANCE_WEB.md) for prerequisites,
certificate trust, security boundaries and rollback.
@@ -285,6 +317,9 @@ Network device monitoring (separate install):
sudo ./install.sh --with-netalertx
```
+Without NetAlertX, device discovery remains unavailable; DNS monitoring and
+Pi-hole/Unbound operation continue normally.
+
---
## 🔎 Manual Update Check
diff --git a/deploy/Caddyfile.maintenance b/deploy/Caddyfile.maintenance
index 90eebc1..baa31aa 100644
--- a/deploy/Caddyfile.maintenance
+++ b/deploy/Caddyfile.maintenance
@@ -1,6 +1,6 @@
-pi.hole:8443, 192.168.178.2:8443 {
+pi.hole:8443, 192.168.1.2:8443 {
tls internal
- @lan remote_ip 192.168.178.0/24 127.0.0.1 ::1
+ @lan remote_ip 192.168.1.0/24 127.0.0.1 ::1
handle @lan {
request_body {
max_size 8KB
diff --git a/docs/CONSOLE_MENU.md b/docs/CONSOLE_MENU.md
index 3a8def7..b1d8828 100644
--- a/docs/CONSOLE_MENU.md
+++ b/docs/CONSOLE_MENU.md
@@ -85,8 +85,6 @@ sudo pihole-rescue # global command (symlink in /usr/local/bin)
sudo bash scripts/rescue_menu.sh
```
-
-
### Options
#### Status & Diagnostics
@@ -97,10 +95,6 @@ sudo bash scripts/rescue_menu.sh
| 2 | DNS loop / upstream check | Detects DNS loops, tests blocking, verifies Unbound upstream |
| 3 | Nightly / diagnostic test | Runs `scripts/nightly_test.sh` (or inline fallback) |
-
-
-
-
#### Backup & Restore
| # | Option | What It Does |
diff --git a/docs/MAINTENANCE_WEB.md b/docs/MAINTENANCE_WEB.md
index 9411fc3..3d2337a 100644
--- a/docs/MAINTENANCE_WEB.md
+++ b/docs/MAINTENANCE_WEB.md
@@ -1,15 +1,14 @@
# Pi-hole-Wartungsoberfläche
Die Wartungsoberfläche stellt genau drei kontrollierte Aktionen bereit:
-Systemcheck, verifiziertes Backup und Update. Sie ist nach erfolgreicher
-Produktionsabnahme unter `https://pi.hole:8443/` sowie
-`https://192.168.178.2:8443/` erreichbar.
+Systemcheck, verifiziertes Backup und Update. Der Zugriff erfolgt über den
+konfigurierten Caddy-Host: `https://:8443/`.
## Voraussetzungen und Installation
Der Webdienst bleibt verbindlich auf `127.0.0.1:8090`; ein abweichender
`SUITE_HOST` wird beim Start abgewiesen. Caddy veröffentlicht ausschließlich
-HTTPS auf Port 8443 für `192.168.178.0/24` und Loopback. Für Caddy auf Debian
+HTTPS auf Port 8443 für das konfigurierte private LAN und Loopback. Für Caddy auf Debian
oder Raspberry Pi OS ist das offizielle Stable-Repository zu verwenden:
```bash
diff --git a/docs/assets/.gitkeep b/docs/assets/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/assets/screenshot_dns_check.png b/docs/assets/screenshot_dns_check.png
deleted file mode 100644
index 709d1b0..0000000
Binary files a/docs/assets/screenshot_dns_check.png and /dev/null differ
diff --git a/docs/assets/screenshot_rescue_menu.png b/docs/assets/screenshot_rescue_menu.png
deleted file mode 100644
index 3aeee29..0000000
Binary files a/docs/assets/screenshot_rescue_menu.png and /dev/null differ
diff --git a/docs/assets/screenshot_status_check.png b/docs/assets/screenshot_status_check.png
deleted file mode 100644
index db236db..0000000
Binary files a/docs/assets/screenshot_status_check.png and /dev/null differ
diff --git a/docs/superpowers/plans/2026-09-07-maintenance-web.md b/docs/superpowers/plans/2026-09-07-maintenance-web.md
index 7a9f67f..bbfb9f8 100644
--- a/docs/superpowers/plans/2026-09-07-maintenance-web.md
+++ b/docs/superpowers/plans/2026-09-07-maintenance-web.md
@@ -12,7 +12,7 @@
## Global Constraints
-- Public URLs are exactly https://pi.hole:8443/ and https://192.168.178.2:8443/; Uvicorn remains on 127.0.0.1:8090.
+- Public URLs use the configured Caddy host on port 8443; Uvicorn remains on 127.0.0.1:8090.
- The FastAPI service runs as pihole-suite, never as root, and accepts no arbitrary command, path, package, URL, or shell input.
- Allowed root actions are exactly check, backup, and update; they are serialized with /run/lock/pihole-maintenance-web.lock.
- Update order is verified backup, apt-get update, noninteractive apt-get -y upgrade, pihole -up, pihole -g, then full check; no autoremove and no reboot.
@@ -22,7 +22,7 @@
- All mutating API calls require an authenticated session, an exact allowed Origin, and a matching CSRF token.
- Production OpenAPI/Swagger is disabled and CORS is not enabled.
- Every Bash file added or changed uses set -euo pipefail.
-- Production deployment is not complete until all three actions have been started through the HTTPS API and independently verified on pi@192.168.178.2.
+- Production deployment is not complete until all three actions have been started through the HTTPS API and independently verified on the target Raspberry Pi.
---
diff --git a/docs/superpowers/specs/2026-09-07-maintenance-web-design.md b/docs/superpowers/specs/2026-09-07-maintenance-web-design.md
index 391baad..22d826f 100644
--- a/docs/superpowers/specs/2026-09-07-maintenance-web-design.md
+++ b/docs/superpowers/specs/2026-09-07-maintenance-web-design.md
@@ -8,7 +8,7 @@ Die vorhandene lokale Pi-hole-Suite erhält eine deutschsprachige Weboberfläche
über die ein Administrator den Systemzustand prüfen, ein verifiziertes Backup
erstellen und einen kontrollierten Update-Lauf starten kann. Die Oberfläche ist
auf dem produktiven Raspberry Pi unter `https://pi.hole:8443/` und
-`https://192.168.178.2:8443/` erreichbar.
+`https://:8443/` erreichbar.
## Plattform und Bestand
@@ -54,14 +54,14 @@ Läufe nicht gleichzeitig Pi-hole, APT oder die Backupdaten verändern.
- Cookie: `Secure`, `HttpOnly`, `SameSite=Strict`, `Path=/`.
- Zustandsänderungen erfordern `POST`, einen sitzungsgebundenen CSRF-Token sowie
einen erlaubten `Origin` (`https://pi.hole:8443` oder
- `https://192.168.178.2:8443`).
+ `https://:8443`).
- Maximal fünf fehlgeschlagene Anmeldungen je Client innerhalb von fünf Minuten;
danach Sperre bis zum Ende des Zeitfensters.
-- Kein CORS. Erlaubte Hosts: `pi.hole`, `192.168.178.2`, `127.0.0.1`, `localhost`.
+- Kein CORS. Erlaubte Hosts kommen aus der lokalen Wartungskonfiguration; Loopback bleibt für Tests erlaubt.
- Produktiv sind Swagger und OpenAPI deaktiviert.
- Sicherheitsheader: CSP ohne Fremdquellen, `frame-ancestors 'none'`,
`X-Content-Type-Options: nosniff`, `Referrer-Policy: no-referrer` und HSTS.
-- Caddy akzeptiert Port 8443 nur aus `192.168.178.0/24` und von Loopback.
+- Caddy akzeptiert Port 8443 nur aus dem konfigurierten LAN und von Loopback.
## Aktionen
diff --git a/maintenance_web.py b/maintenance_web.py
index 60cf3de..56d1204 100644
--- a/maintenance_web.py
+++ b/maintenance_web.py
@@ -34,8 +34,8 @@
START_RESERVATION_SECONDS = 60
MAX_STATE_BYTES = 64 * 1024
COOKIE_NAME = "pihole_maintenance_session"
-ALLOWED_HOSTS = frozenset({"pi.hole", "192.168.178.2", "127.0.0.1", "localhost"})
-ALLOWED_ORIGINS = frozenset({"https://pi.hole:8443", "https://192.168.178.2:8443"})
+ALLOWED_HOSTS = frozenset({"pi.hole", "127.0.0.1", "localhost"})
+ALLOWED_ORIGINS = frozenset({"https://pi.hole:8443"})
WEB_DIRECTORY = Path(__file__).resolve().parent / "web"
Dispatcher = Callable[[list[str]], None]
diff --git a/tests/test_install_maintenance_web.sh b/tests/test_install_maintenance_web.sh
index cf38e1a..1902157 100755
--- a/tests/test_install_maintenance_web.sh
+++ b/tests/test_install_maintenance_web.sh
@@ -17,14 +17,14 @@ for command in apt apt-get caddy curl service sudo systemctl; do
done
run_staged_install() {
- PATH="$GUARD_DIR:$PATH" bash "$ROOT_DIR/scripts/install_maintenance_web.sh" --destdir "$DESTDIR" --hosts pi.hole,192.168.178.2 --lan-cidr 192.168.178.0/24
+ PATH="$GUARD_DIR:$PATH" bash "$ROOT_DIR/scripts/install_maintenance_web.sh" --destdir "$DESTDIR" --hosts pi.hole,192.168.1.2 --lan-cidr 192.168.1.0/24
}
run_staged_install
ROOT_DIR="$ROOT_DIR" bash -c '
source "$ROOT_DIR/install.sh"
- run_existing_upgrade() { test "$MAINTENANCE_HOSTS" = pi.hole; test "$MAINTENANCE_LAN_CIDR" = 192.168.178.0/24; }
- main --upgrade-only --maintenance-hosts pi.hole --maintenance-lan-cidr 192.168.178.0/24
+ run_existing_upgrade() { test "$MAINTENANCE_HOSTS" = pi.hole; test "$MAINTENANCE_LAN_CIDR" = 192.168.1.0/24; }
+ main --upgrade-only --maintenance-hosts pi.hole --maintenance-lan-cidr 192.168.1.0/24
'
if ROOT_DIR="$ROOT_DIR" bash -c '
source "$ROOT_DIR/install.sh"
@@ -144,7 +144,7 @@ printf '%s\n' \
$'\troot * /usr/share/caddy' \
$'\tfile_server' \
'}' > "$PACKAGED_DESTDIR/etc/caddy/Caddyfile"
-PATH="$GUARD_DIR:$PATH" bash "$ROOT_DIR/scripts/install_maintenance_web.sh" --destdir "$PACKAGED_DESTDIR" --hosts pi.hole,192.168.178.2 --lan-cidr 192.168.178.0/24
+PATH="$GUARD_DIR:$PATH" bash "$ROOT_DIR/scripts/install_maintenance_web.sh" --destdir "$PACKAGED_DESTDIR" --hosts pi.hole,192.168.1.2 --lan-cidr 192.168.1.0/24
grep -Fqx 'import Caddyfile.d/*' "$PACKAGED_DESTDIR/etc/caddy/Caddyfile"
grep -Fqx ' auto_https disable_redirects' "$PACKAGED_DESTDIR/etc/caddy/Caddyfile"
! grep -Fqx ':80 {' "$PACKAGED_DESTDIR/etc/caddy/Caddyfile"
@@ -272,9 +272,9 @@ grep -Fq '/health /urls /pihole /unbound /netalertx /stats' "$CADDY_SNIPPET"
assert_mode "$CADDY_MAIN" 644
assert_mode "$CADDY_SNIPPET" 644
test "$(grep -Fxc 'import Caddyfile.d/*' "$CADDY_MAIN")" = 1
-grep -Fqx 'pi.hole:8443, 192.168.178.2:8443 {' "$CADDY_SNIPPET"
+grep -Fqx 'pi.hole:8443, 192.168.1.2:8443 {' "$CADDY_SNIPPET"
grep -Fqx ' tls internal' "$CADDY_SNIPPET"
-grep -Fqx ' @lan remote_ip 192.168.178.0/24 127.0.0.1 ::1' "$CADDY_SNIPPET"
+grep -Fqx ' @lan remote_ip 192.168.1.0/24 127.0.0.1 ::1' "$CADDY_SNIPPET"
grep -Fqx ' handle @lan {' "$CADDY_SNIPPET"
grep -Fqx ' reverse_proxy 127.0.0.1:8090' "$CADDY_SNIPPET"
grep -Fqx ' respond "Not found" 404' "$CADDY_SNIPPET"
diff --git a/tests/test_maintenance_config.py b/tests/test_maintenance_config.py
index ea16dad..8ca1141 100644
--- a/tests/test_maintenance_config.py
+++ b/tests/test_maintenance_config.py
@@ -5,16 +5,16 @@ def test_staging_output_does_not_write_files(tmp_path):
import sys
from pathlib import Path
script = Path(__file__).resolve().parents[1] / 'maintenance_install.py'
- result = subprocess.run([sys.executable, str(script), '--print-config', '--hosts', 'pi.hole,192.168.178.2', '--lan-cidr', '192.168.178.0/24'], cwd=tmp_path, check=True, capture_output=True, text=True)
- assert json.loads(result.stdout)['lan_cidr'] == '192.168.178.0/24'
+ result = subprocess.run([sys.executable, str(script), '--print-config', '--hosts', 'pi.hole,192.168.1.2', '--lan-cidr', '192.168.1.0/24'], cwd=tmp_path, check=True, capture_output=True, text=True)
+ assert json.loads(result.stdout)['lan_cidr'] == '192.168.1.0/24'
assert list(tmp_path.iterdir()) == []
from maintenance_config import caddy_config, configuration
def test_network_config_and_route_boundary():
- config = configuration('pi.hole,192.168.178.2', '192.168.178.0/24')
- assert config['allowed_origins'] == ['https://pi.hole:8443', 'https://192.168.178.2:8443']
+ config = configuration('pi.hole,192.168.1.2', '192.168.1.0/24')
+ assert config['allowed_origins'] == ['https://pi.hole:8443', 'https://192.168.1.2:8443']
caddy = caddy_config(config)
assert 'path / /maintenance.css /maintenance.js /api/session /api/session/logout /api/maintenance/*' in caddy
assert 'respond "Not found" 404' in caddy
diff --git a/tests/test_maintenance_web.py b/tests/test_maintenance_web.py
index 2a9110f..b4062dd 100644
--- a/tests/test_maintenance_web.py
+++ b/tests/test_maintenance_web.py
@@ -20,6 +20,13 @@
)
+def test_default_host_allowlist_does_not_embed_a_private_lan_address():
+ settings = MaintenanceSettings(api_key="test-key")
+
+ assert settings.allowed_hosts == frozenset({"pi.hole", "127.0.0.1", "localhost"})
+ assert settings.allowed_origins == frozenset({"https://pi.hole:8443"})
+
+
class RecordingDispatcher:
def __init__(self):
self.calls: list[list[str]] = []
diff --git a/tests/test_upgrade_container.sh b/tests/test_upgrade_container.sh
index 1f67e95..0197770 100644
--- a/tests/test_upgrade_container.sh
+++ b/tests/test_upgrade_container.sh
@@ -65,7 +65,7 @@ EOF
chmod 0755 /usr/bin/{systemctl,systemd-analyze,caddy,dig,curl,ss,sudo} /usr/sbin/visudo
touch /tmp/fail-caddy
-if bash /src/install.sh --upgrade-only --maintenance-hosts pi.hole,192.168.178.2 --maintenance-lan-cidr 192.168.178.0/24; then
+if bash /src/install.sh --upgrade-only --maintenance-hosts pi.hole,192.168.1.2 --maintenance-lan-cidr 192.168.1.0/24; then
printf 'injected deployment failure was ignored\n' >&2
exit 1
fi
@@ -78,7 +78,7 @@ grep -Fqx 'upstream config' /etc/pihole/pihole.toml
grep -Fqx 'unbound config' /etc/unbound/unbound.conf.d/forward.conf
test "$(sha256sum /etc/resolv.conf)" = "$resolver_checksum"
-bash /src/install.sh --upgrade-only --maintenance-hosts pi.hole,192.168.178.2 --maintenance-lan-cidr 192.168.178.0/24
+bash /src/install.sh --upgrade-only --maintenance-hosts pi.hole,192.168.1.2 --maintenance-lan-cidr 192.168.1.0/24
cmp /src/start_suite.py /var/lib/pihole-suite/app/start_suite.py
grep -Fqx custom-data /var/lib/pihole-suite/app/custom.txt
grep -Fqx old-venv /var/lib/pihole-suite/venv/preserved