#!/usr/bin/env bash # Cloudflare Dynamic DNS — aktualisiert A/AAAA wenn sich die Fritz!Box-WAN-IP aendert. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ENV_FILE="${SCRIPT_DIR}/env.homelab" STATE_FILE="${SCRIPT_DIR}/.cf-ddns-last-ip" [[ -f "${ENV_FILE}" ]] || { echo "cloudflare-ddns: ${ENV_FILE} fehlt" >&2; exit 1; } # shellcheck source=/dev/null source <(sed 's/\r$//' "${ENV_FILE}") : "${CLOUDFLARE_API_TOKEN:?CLOUDFLARE_API_TOKEN fehlt}" : "${CLOUDFLARE_ZONE_ID:?CLOUDFLARE_ZONE_ID fehlt}" APP_DOMAIN="${APP_DOMAIN:-trilogyhub.de}" DDNS_RECORD="${DDNS_RECORD_NAME:-home.${APP_DOMAIN}}" DDNS_PROXIED="${DDNS_PROXIED:-true}" IPV4="$(curl -4 -fsS --max-time 15 https://api.ipify.org 2>/dev/null || true)" IPV6="$(curl -6 -fsS --max-time 15 https://api64.ipify.org 2>/dev/null || true)" [[ -n "${IPV4}" || -n "${IPV6}" ]] || { echo "cloudflare-ddns: keine IP" >&2; exit 1; } CURRENT="${IPV4}|${IPV6}" LAST="$(cat "${STATE_FILE}" 2>/dev/null || true)" if [[ "${CURRENT}" == "${LAST}" ]]; then echo "$(date -Is) cloudflare-ddns ${DDNS_RECORD}: unveraendert v4=${IPV4:-none}" exit 0 fi export CF_API="https://api.cloudflare.com/client/v4" export CF_ZONE="${CLOUDFLARE_ZONE_ID}" export CF_TOKEN="${CLOUDFLARE_API_TOKEN}" export CF_PROXIED="${DDNS_PROXIED}" upsert() { local rtype="$1" ip="$2" [[ -n "${ip}" ]] || return 0 python3 - "${DDNS_RECORD}" "${rtype}" "${ip}" <<'PY' import json, os, sys, urllib.request name, rtype, ip = sys.argv[1:4] api = os.environ["CF_API"] zone = os.environ["CF_ZONE"] token = os.environ["CF_TOKEN"] proxied = os.environ.get("CF_PROXIED", "true").lower() == "true" headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} list_url = f"{api}/zones/{zone}/dns_records?type={rtype}&name={name}" with urllib.request.urlopen(urllib.request.Request(list_url, headers=headers), timeout=20) as r: data = json.load(r) if not data.get("success"): raise SystemExit(f"list failed: {data}") body = json.dumps({"type": rtype, "name": name, "content": ip, "ttl": 120, "proxied": proxied}).encode() recs = data.get("result", []) if recs: url = f"{api}/zones/{zone}/dns_records/{recs[0]['id']}" req = urllib.request.Request(url, data=body, headers=headers, method="PUT") act = "aktualisiert" else: url = f"{api}/zones/{zone}/dns_records" req = urllib.request.Request(url, data=body, headers=headers, method="POST") act = "angelegt" with urllib.request.urlopen(req, timeout=20) as r: out = json.load(r) if not out.get("success"): raise SystemExit(f"write failed: {out}") print(f"{rtype} {name} -> {ip} ({act})") PY } upsert A "${IPV4}" upsert AAAA "${IPV6}" echo "${CURRENT}" > "${STATE_FILE}" chmod 600 "${STATE_FILE}" 2>/dev/null || true echo "$(date -Is) cloudflare-ddns fertig"