#!/usr/bin/env bash # Run with the agenix-backed Cloudflare EnvironmentFile, never with a token # on the command line. Default is read-only; --apply creates missing records. set -euo pipefail set +x mode=${1:---check} [[ "$mode" == --check || "$mode" == --apply ]] || exit 2 : "${CLOUDFLARE_DNS_API_TOKEN:?Cloudflare environment required}" api() { printf 'Authorization: Bearer %s\n' "$CLOUDFLARE_DNS_API_TOKEN" | curl --fail --silent --show-error --max-time 30 -H @- \ -H 'Content-Type: application/json' "$@" } base=https://api.cloudflare.com/client/v4 zone=$(api "$base/zones?name=lukasrosier.com" | jq -er 'select(.success) | .result | select(length == 1) | .[0].id') target=ipv6-test.0ff.space missing=() # Preflight all identity names before writing any of them. Never replace an existing # service, delete a record, or touch the wildcard/mail/NS/0ff.space records. for name in dex.lukasrosier.com head.lukasrosier.com lldap.lukasrosier.com; do records=$(api "$base/zones/$zone/dns_records?name=$name&per_page=100" | jq -ec 'select(.success) | .result') if [[ $(jq length <<< "$records") == 0 ]]; then missing+=("$name") printf 'Missing: %s CNAME %s (DNS-only)\n' "$name" "$target" elif jq -e --arg target "$target" \ 'length == 1 and .[0].type == "CNAME" and (.[0].content | rtrimstr(".")) == $target and .[0].proxied == false' \ <<< "$records" >/dev/null; then printf 'Already correct: %s\n' "$name" else printf 'Refusing to replace existing records at %s\n' "$name" >&2 exit 1 fi done [[ "$mode" == --apply ]] || exit 0 for name in "${missing[@]}"; do payload=$(jq -nc --arg name "$name" --arg target "$target" \ '{type:"CNAME",name:$name,content:$target,proxied:false,ttl:300}') api -X POST "$base/zones/$zone/dns_records" --data "$payload" | jq -e 'select(.success) | .result | {id,name,type,content,proxied,ttl}' done