Files
playbull/deploy/cloudflare-ddns-update.sh
Mike 94b6cc3be9 Initial PlayBull release with Trilogy Hub integration.
Includes homelab deploy tooling, image position/zoom manifest persistence, and full trading/casino stack.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-02 08:31:36 +02:00

75 lines
2.8 KiB
Bash

#!/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"