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>
This commit is contained in:
74
deploy/cloudflare-ddns-update.sh
Normal file
74
deploy/cloudflare-ddns-update.sh
Normal file
@@ -0,0 +1,74 @@
|
||||
#!/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"
|
||||
Reference in New Issue
Block a user