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 @@ [![Pi-hole](https://img.shields.io/badge/Pi--hole-v6.x-red?style=for-the-badge&logo=pihole)](https://pi-hole.net/) [![Unbound](https://img.shields.io/badge/Unbound-DNS-orange?style=for-the-badge)](https://nlnetlabs.nl/projects/unbound/) [![Debian](https://img.shields.io/badge/Debian-Bookworm%2FTrixie-red?style=for-the-badge&logo=debian)](https://debian.org/) -[![Python](https://img.shields.io/badge/Python-3.12+-blue?style=for-the-badge&logo=python)](https://python.org/) +[![Python](https://img.shields.io/badge/Python-3.10+-blue?style=for-the-badge&logo=python)](https://python.org/) [![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-support-FFDD00?logo=buymeacoffee&logoColor=000&style=for-the-badge)](https://buymeacoffee.com/timintech) Tech Stack @@ -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 ``` -![Rescue Menu](docs/assets/screenshot_rescue_menu.png) - | Option | Aktion | |--------|--------| | 1 | Systemstatus (Services, DNS, Ports, Temperatur) | @@ -140,14 +151,6 @@ sudo pihole-rescue | 11 | Letzten Report / Log anzeigen | | 0 | Beenden | -**Systemstatus:** - -![System Status](docs/assets/screenshot_status_check.png) - -**DNS-Check:** - -![DNS Check](docs/assets/screenshot_dns_check.png) - --- ### 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 @@ [![Pi-hole](https://img.shields.io/badge/Pi--hole-v6.x-red?style=for-the-badge&logo=pihole)](https://pi-hole.net/) [![Unbound](https://img.shields.io/badge/Unbound-DNS-orange?style=for-the-badge)](https://nlnetlabs.nl/projects/unbound/) [![Debian](https://img.shields.io/badge/Debian-Bookworm%2FTrixie-red?style=for-the-badge&logo=debian)](https://debian.org/) -[![Python](https://img.shields.io/badge/Python-3.12+-blue?style=for-the-badge&logo=python)](https://python.org/) +[![Python](https://img.shields.io/badge/Python-3.10+-blue?style=for-the-badge&logo=python)](https://python.org/) [![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-support-FFDD00?logo=buymeacoffee&logoColor=000&style=for-the-badge)](https://buymeacoffee.com/timintech) Tech Stack @@ -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 ``` -![Rescue Menu](docs/assets/screenshot_rescue_menu.png) - | 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:** - -![System Status](docs/assets/screenshot_status_check.png) - -**DNS Check:** - -![DNS Check](docs/assets/screenshot_dns_check.png) - --- ### 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 ``` -![Rescue Menu](assets/screenshot_rescue_menu.png) - ### 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) | -![System Status](assets/screenshot_status_check.png) - -![DNS Check](assets/screenshot_dns_check.png) - #### 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