Mehrere Subdomains pro Benutzer + README

- subdomains-Tabelle (n DNS-Namen je Benutzer) inkl. Migration vom alten
  Einzel-Subdomain-Schema in database.init_db()
- Benutzeranlage/Verwaltung: mehrere Subdomains hinzufuegen/entfernen
- /nic/update aktualisiert alle Subdomains des Benutzers bzw. die per
  ?hostname= gewaehlte(n); eine Antwortzeile je Subdomain
- Dashboard/Users-Templates auf das neue Modell umgestellt
- README.md mit Setup, Plesk-Konfig, Router-Einrichtung und Endpoint-Doku
- .gitignore: __pycache__/ und *.pyc

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Hacker
2026-06-06 14:13:48 +02:00
parent 2542cf5455
commit 9c631992af
7 changed files with 472 additions and 109 deletions
+185
View File
@@ -0,0 +1,185 @@
# DynDNS Manager für Plesk
Ein kleiner, selbst gehosteter DynDNS-Server mit Web-Oberfläche. Er nimmt
DynDNS-v2-Updates (wie sie z. B. ein Telekom **Speedport** unter „Anderer
Anbieter" sendet) entgegen und trägt die gemeldete IP-Adresse über die
**Plesk REST-API** als A-Record in deine DNS-Zone ein.
- Mehrere DynDNS-Benutzer, **mehrere Subdomains (DNS-Namen) pro Benutzer**
- Admin-Weboberfläche (Flask + Bootstrap) zum Verwalten von Benutzern,
Subdomains und Plesk-Einstellungen
- Update-Log pro Subdomain
- Läuft als Docker-Container hinter einem nginx-TLS-Proxy
---
## Schnellstart
```bash
# 1. Container bauen und starten
docker compose up -d --build
# 2. Weboberfläche öffnen (lokal, hinter nginx -> https)
# http://127.0.0.1:5080
# Standard-Login: admin / admin (sofort ändern!)
```
Die SQLite-Datenbank wird beim ersten Start automatisch unter `./data/dyndns.db`
angelegt (Volume-Mount in `docker-compose.yml`). Existiert die Datei nicht, wird
sie samt Tabellen erzeugt; ein Default-Admin (`admin` / `admin`) wird angelegt.
---
## Konfiguration
### Umgebungsvariablen (`docker-compose.yml`)
| Variable | Bedeutung | Default |
|--------------|------------------------------------------------------|----------------------|
| `DB_PATH` | Pfad zur SQLite-Datei **im Container** | `/data/dyndns.db` |
| `SECRET_KEY` | Flask-Session-Key — **unbedingt ändern** (`openssl rand -hex 32`) | zufällig pro Start |
> **Wichtig:** Das Volume mountet ein **Verzeichnis** (`./data:/data`), nicht die
> Datei direkt. Würde man die noch nicht existierende Datei mounten
> (`./dyndns.db:/data/dyndns.db`), legt Docker sie als *Verzeichnis* an und
> SQLite scheitert mit `unable to open database file`.
### Plesk-Einstellungen (in der Weboberfläche → *Einstellungen*)
| Feld | Beispiel | Bedeutung |
|-------------------|-----------------------------------|--------------------------------------------------|
| Plesk-URL | `https://plesk.example.com:8443` | Basis-URL der Plesk-Installation |
| API-Key | `XXXXXXXX-...` | Plesk REST-API-Key (in Plesk unter *API-Schlüssel* erzeugen) |
| Basis-Domain | `example.com` | DNS-Zone, in die die A-Records geschrieben werden |
| SSL verifizieren | ☑/☐ | bei selbstsigniertem Plesk-Zertifikat abschalten |
Mit *Verbindung testen* lässt sich die API prüfen.
---
## Benutzer & Subdomains anlegen
1. In der Weboberfläche auf **Benutzer → Benutzer anlegen**.
2. **DynDNS-Benutzername** und **Passwort** vergeben (das sind die Zugangsdaten,
die später im Router eingetragen werden).
3. Eine oder **mehrere Subdomains** eintragen — getrennt durch Komma,
Leerzeichen oder Zeilenumbruch, z. B.:
```
mypc
nas
router
```
Zusammen mit der Basis-Domain (`example.com`) ergeben sich daraus die
Hostnamen `mypc.example.com`, `nas.example.com`, `router.example.com`.
Auch **mehrstufige** Namen sind erlaubt (`pc.home` → `pc.home.example.com`),
solange die Records in der DNS-Zone der Basis-Domain liegen.
Weitere Subdomains lassen sich später jederzeit über das **+**-Symbol in der
Benutzerzeile hinzufügen oder per **×** am Badge entfernen.
> **Hinweis:** Der DNS-A-Record wird **nicht** schon beim Anlegen in Plesk
> erstellt, sondern erst beim **ersten DynDNS-Update** vom Client (lazy). Bis
> dahin zeigt das Dashboard „noch kein Update".
---
## Router / Speedport einrichten
Im Router unter DynDNS „**Anderer Anbieter**" konfigurieren:
| Feld | Wert |
|--------------|---------------------------------------------------|
| Update-URL | `https://dyndns.example.com/nic/update?hostname=<domain>&myip=<ipaddr>` |
| Domainname | z. B. `mypc.example.com` |
| Benutzername | der angelegte DynDNS-Benutzername |
| Passwort | das zugehörige Passwort |
Die Platzhalter `<domain>` und `<ipaddr>` füllt der Router automatisch.
Authentifiziert wird per **HTTP Basic Auth** (Benutzername/Passwort).
### Update-Endpoint `/nic/update`
Standard-DynDNS-v2-Protokoll:
```
GET /nic/update?hostname=mypc.example.com&myip=203.0.113.7
Authorization: Basic <user:pass>
```
Verhalten:
- **`hostname` angegeben** → nur die passende(n) Subdomain(s) dieses Benutzers
werden aktualisiert. Erlaubt ist der volle FQDN (`mypc.example.com`) **oder**
nur das Label (`mypc`); mehrere durch Komma getrennt.
- **`hostname` weggelassen** → **alle** Subdomains des Benutzers werden auf die
gemeldete IP gesetzt.
- `myip` (oder `ip`) bestimmt die Adresse; fehlt sie, wird die Quell-IP des
Requests verwendet.
Antworten (eine Zeile pro Subdomain):
| Antwort | Bedeutung |
|----------------|------------------------------------------------|
| `good <ip>` | Record erfolgreich gesetzt |
| `nochg <ip>` | IP unverändert, nichts zu tun |
| `nohost` | Benutzer hat keine (passende) Subdomain |
| `badauth` | Benutzername/Passwort falsch |
| `911` | Plesk nicht konfiguriert (URL/Key/Domain fehlt)|
| `dnserr` | Fehler beim Schreiben in Plesk (siehe Log) |
Beispiel-Test mit `curl`:
```bash
curl -u stefan:geheim \
"https://dyndns.example.com/nic/update?hostname=mypc.example.com&myip=203.0.113.7"
```
---
## TLS / Reverse Proxy
Der Container lauscht nur lokal (`127.0.0.1:5080`). Die TLS-Terminierung
übernimmt nginx — eine Beispielkonfiguration liegt in
[`nginx-subdomai-example.conf`](nginx-subdomai-example.conf). Domain in Plesk
anlegen, Let's-Encrypt-Zertifikat ausstellen, dann die Datei als zusätzliche
nginx-Direktiven einbinden.
---
## Architektur
```
app/
├── main.py Flask-Routen: Login, Dashboard, Benutzer/Subdomains, /nic/update
├── database.py SQLite-Schema, Migration, Settings-Helfer
├── plesk.py Plesk-REST-API: Verbindungstest + A-Record anlegen/aktualisieren
├── wsgi.py gunicorn-Einstieg (ruft init_db beim Start)
└── templates/ Bootstrap-Oberfläche
```
### Datenmodell
- **`dyndns_users`** — Zugangsdaten (Benutzername/Passwort), aktiv-Flag
- **`subdomains`** — beliebig viele DNS-Namen je Benutzer (`dyndns_user_id` →
`dyndns_users.id`), inkl. aktueller IP und Zeitpunkt des letzten Updates
- **`update_log`** — Verlauf pro Subdomain
- **`admin_users`**, **`settings`** — Admin-Login und Plesk-Konfiguration
Beim Start migriert `init_db()` automatisch ältere Datenbanken, die noch eine
einzelne `subdomain`-Spalte in `dyndns_users` hatten, in die neue
`subdomains`-Tabelle.
---
## Lokale Entwicklung (ohne Docker)
```bash
cd app
pip install -r requirements.txt
export DB_PATH=./dev.db
export SECRET_KEY=dev
python main.py # http://127.0.0.1:5000
```