diff --git a/dns-monitor/monitor.sh b/dns-monitor/monitor.sh index a1f0f8fd..07f330df 100644 --- a/dns-monitor/monitor.sh +++ b/dns-monitor/monitor.sh @@ -1,15 +1,28 @@ #!/bin/sh -# Install curl and ping if not present (Alpine) +# Ensure dependencies are installed +# We check for 'dig' specifically. If missing, we try to install bind-tools. +if ! command -v dig >/dev/null 2>&1; then + echo "[DNS-MONITOR] 'dig' not found. Installing bind-tools..." + if apk add --no-cache bind-tools; then + echo "[DNS-MONITOR] bind-tools installed successfully." + else + echo "[DNS-MONITOR] ERROR: Failed to install bind-tools. Check internet connection or repo mirrors." + fi +fi + +# Ensure curl is installed (though it seems to be present based on logs) if ! command -v curl >/dev/null 2>&1; then + echo "[DNS-MONITOR] 'curl' not found. Installing curl..." apk add --no-cache curl fi echo "[DNS-MONITOR] Service started. Monitoring $SUBDOMAINS every 5 minutes..." while true; do - # Fetch Public IP - PUBLIC_IP=$(curl -s -m 10 https://api.ipify.org) + # 1. Fetch Public IP (via external service) + # We use -4 to force IPv4 + PUBLIC_IP=$(curl -s -4 -m 10 https://api.ipify.org) if [ -z "$PUBLIC_IP" ]; then PUBLIC_IP="Error: Could not fetch IP" @@ -23,20 +36,32 @@ while true; do else FULL_DOMAIN="${FIRST_SUB}.duckdns.org" - # Resolve IP using ping - DOMAIN_IP=$(ping -c 1 -W 5 "$FULL_DOMAIN" 2>/dev/null | head -n 1 | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -n 1) - - if [ -z "$DOMAIN_IP" ]; then - DOMAIN_IP="Error: Could not resolve" + # 2. Resolve Global IP (Google DNS @8.8.8.8) to check propagation + # We look for the A record (IPv4) + if command -v dig >/dev/null 2>&1; then + GLOBAL_IP=$(dig @8.8.8.8 +short A "$FULL_DOMAIN" | head -n 1) + LOCAL_IP=$(dig +short A "$FULL_DOMAIN" | head -n 1) + else + GLOBAL_IP="Error: dig missing" + LOCAL_IP="Error: dig missing" fi + if [ -z "$GLOBAL_IP" ]; then GLOBAL_IP="Unresolved"; fi + if [ -z "$LOCAL_IP" ]; then LOCAL_IP="Unresolved"; fi + TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S') - if [ "$PUBLIC_IP" == "$DOMAIN_IP" ]; then - echo "[$TIMESTAMP] [DNS-MONITOR] OK | Domain: $FULL_DOMAIN ($DOMAIN_IP) == Public: $PUBLIC_IP" - else - echo "[$TIMESTAMP] [DNS-MONITOR] ALERT | Domain: $FULL_DOMAIN ($DOMAIN_IP) != Public: $PUBLIC_IP" + # Logic to determine status + STATUS="OK" + if [ "$PUBLIC_IP" != "$GLOBAL_IP" ]; then + STATUS="ALERT" + elif [ "$PUBLIC_IP" != "$LOCAL_IP" ]; then + # If Global is correct but Local is wrong, it's a local caching issue, still an alert but specific + STATUS="CACHE_ALERT" fi + + # Log format: Time | Status | Public vs Global vs Local + echo "[$TIMESTAMP] [DNS-MONITOR] $STATUS | Public: $PUBLIC_IP | Global(8.8.8.8): $GLOBAL_IP | Local: $LOCAL_IP" fi # Check every 5 minutes diff --git a/docker-compose.yml b/docker-compose.yml index 297d7425..a998ec9f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -117,7 +117,7 @@ services: - PGID=1000 # Group ID (anpassen falls nötig) - TZ=Europe/Berlin - SUBDOMAINS=floke,floke-ai,floke-gitea,floke-ha,floke-n8n - - TOKEN=af8f6d8b-5a83-4251-9c15-d3b3e82eeef1 + - TOKEN=2e073b27-971e-4847-988c-73ad23e648d4 restart: unless-stopped # --- DNS MONITOR (Sidecar) --- diff --git a/duckdns_setup.md b/duckdns_setup.md index 331933cc..8442c552 100644 --- a/duckdns_setup.md +++ b/duckdns_setup.md @@ -1,6 +1,7 @@ # DuckDNS & DNS Monitor Setup **Erstellt am:** 05.01.2026 +**Zuletzt aktualisiert:** 06.01.2026 **Status:** Aktiv **Architektur:** Sidecar-Pattern @@ -9,11 +10,13 @@ Dieses Dokument beschreibt die Docker-basierte Lösung zur DynDNS-Aktualisierung ## Problembeschreibung Das ursprüngliche Synology-Skript im Aufgabenplaner führte zu unzuverlässigen DNS-Updates. Oft war die IP bei DuckDNS zwar aktuell, aber die Dienste waren von außen nicht erreichbar. Erst ein manuelles Ändern der IP auf einen falschen Wert und zurück (Toggle) löste das Problem. Dies deutete auf DNS-Propagation- oder Caching-Probleme hin. +Zusätzlich wurden "Zombie-Updater" (alte Konfigurationen in FritzBox oder Home Assistant) identifiziert, die die IP immer wieder auf veraltete Werte zurücksetzten ("IP Flapping"). + ## Lösung Wir haben eine robuste **Zwei-Container-Lösung** (Sidecar-Pattern) implementiert: 1. **Updater (`duckdns`):** Aktualisiert zuverlässig die IP bei DuckDNS (Image: `linuxserver/duckdns`). -2. **Monitor (`dns-monitor`):** Überwacht unabhängig, ob die Domain tatsächlich auf die aktuelle öffentliche IP auflöst. +2. **Monitor (`dns-monitor`):** Überwacht unabhängig und differenziert die DNS-Auflösung. ## Konfiguration (`docker-compose.yml`) @@ -34,7 +37,10 @@ Dieser Container sorgt dafür, dass DuckDNS immer die aktuelle IP kennt. ``` ### 2. Der Monitor (Sidecar) -Dieser Container prüft alle 5 Minuten die DNS-Auflösung und schreibt das Ergebnis ins Log. +Dieser Container prüft alle 5 Minuten die DNS-Auflösung und schreibt das Ergebnis ins Log. Er vergleicht: +1. **Public IP:** Die tatsächliche externe IP (via ipify). +2. **Global DNS:** Die IP laut Google DNS (8.8.8.8) -> Prüft Internet-Propagation. +3. **Local DNS:** Die IP laut Container-Resolver -> Prüft lokales Caching. ```yaml dns-monitor: @@ -49,7 +55,7 @@ Dieser Container prüft alle 5 Minuten die DNS-Auflösung und schreibt das Ergeb restart: unless-stopped ``` -**Wichtig:** Das Skript `monitor.sh` liegt im Host-Verzeichnis `/app/dns-monitor/`. +**Wichtig:** Das Skript `monitor.sh` liegt im Host-Verzeichnis `/app/dns-monitor/` und installiert zur Laufzeit fehlende Pakete (`bind-tools`, `curl`). ## Logs & Monitoring @@ -57,7 +63,6 @@ Dieser Container prüft alle 5 Minuten die DNS-Auflösung und schreibt das Ergeb ```bash docker logs duckdns ``` -*Erwartet:* `Your IP was updated at ...` oder `IP(s) unchanged`. ### DNS-Konsistenz prüfen (WICHTIG!) Hier sehen wir, ob die Welt (bzw. der Container) die Domain korrekt auflöst. @@ -68,15 +73,32 @@ docker logs dns-monitor **Szenario OK:** ```text -[2026-01-05 21:26:26] [DNS-MONITOR] OK | Domain: floke.duckdns.org (87.x.x.x) == Public: 87.x.x.x +[TIMESTAMP] [DNS-MONITOR] OK | Public: 87.x.x.x | Global(8.8.8.8): 87.x.x.x | Local: 87.x.x.x ``` +Alles synchron. -**Szenario FEHLER (Alarm):** +**Szenario ALERT (Zombie-Updater / Propagation):** ```text -[2026-01-05 21:30:00] [DNS-MONITOR] ALERT | Domain: floke.duckdns.org (Error) != Public: 87.x.x.x +[TIMESTAMP] [DNS-MONITOR] ALERT | Public: 87.x.x.x | Global(8.8.8.8): 87.y.y.y | Local: 87.y.y.y ``` -Dies deutet darauf hin, dass DuckDNS zwar vielleicht aktualisiert wurde, aber die DNS-Server die Änderung noch nicht propagiert haben. +Dies deutet darauf hin, dass entweder die Propagation noch läuft oder ein anderes Gerät (FritzBox, HA) die IP überschreibt. + +**Szenario CACHE_ALERT (Lokales Problem):** +```text +[TIMESTAMP] [DNS-MONITOR] CACHE_ALERT | Public: 87.x.x.x | Global(8.8.8.8): 87.x.x.x | Local: 87.y.y.y +``` +Das Internet kennt die neue IP schon, aber lokal wird noch die alte gecacht. + +## Troubleshooting: "IP Flapping" (Zombie-Updater) +Wenn die IP im Monitor ständig zwischen der aktuellen (`...252`) und einer alten (`...49`) springt, gibt es einen zweiten Updater im Netzwerk (z.B. Router, Home Assistant, alte Skripte). + +**Lösung:** +1. Login bei [duckdns.org](https://www.duckdns.org). +2. **Token rotieren** (Button "update token"). +3. Neuen Token **nur** in der `docker-compose.yml` eintragen. +4. `docker-compose up -d duckdns` ausführen. +5. Damit werden alle alten Updater ausgesperrt. ## Dateien - `/app/docker-compose.yml`: Definition der Services. -- `/app/dns-monitor/monitor.sh`: Das Shell-Skript für den Monitor-Container. \ No newline at end of file +- `/app/dns-monitor/monitor.sh`: Das Shell-Skript für den Monitor-Container.