# 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=&myip=` | | Domainname | z. B. `mypc.example.com` | | Benutzername | der angelegte DynDNS-Benutzername | | Passwort | das zugehörige Passwort | Die Platzhalter `` und `` 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 ``` 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 ` | Record erfolgreich gesetzt | | `nochg ` | 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 ```