diff --git a/scripts/hetzner/configure-cloudflare-dns.sh b/scripts/hetzner/configure-cloudflare-dns.sh index b298c56..90923a0 100755 --- a/scripts/hetzner/configure-cloudflare-dns.sh +++ b/scripts/hetzner/configure-cloudflare-dns.sh @@ -4,7 +4,8 @@ set -euo pipefail : "${CLOUDFLARE_API_TOKEN:?set CLOUDFLARE_API_TOKEN}" : "${CLOUDFLARE_ZONE_ID:?set CLOUDFLARE_ZONE_ID}" : "${BASE_DOMAIN:?set BASE_DOMAIN}" -: "${SERVER_IP:?set SERVER_IP}" +: "${PUBLIC_DOMAIN:=$BASE_DOMAIN}" +: "${DNS_MODE:=upsert}" api() { curl -fsS -X "$1" "https://api.cloudflare.com/client/v4$2" \ @@ -13,15 +14,21 @@ api() { ${3:+--data "$3"} } +lookup_record_id() { + local type="$1" + local name="$2" + curl -fsS "https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID/dns_records?type=$type&name=$name" \ + -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ + -H 'Content-Type: application/json' | python3 -c 'import sys,json; d=json.load(sys.stdin); print(d["result"][0]["id"] if d.get("result") else "")' +} + upsert_record() { local type="$1" local name="$2" local content="$3" local proxied="${4:-false}" local existing_id - existing_id=$(curl -fsS "https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID/dns_records?type=$type&name=$name" \ - -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ - -H 'Content-Type: application/json' | python3 -c 'import sys,json; d=json.load(sys.stdin); print(d["result"][0]["id"] if d.get("result") else "")') + existing_id=$(lookup_record_id "$type" "$name") local payload payload=$(printf '{"type":"%s","name":"%s","content":"%s","ttl":120,"proxied":%s}' "$type" "$name" "$content" "$proxied") @@ -33,8 +40,42 @@ upsert_record() { fi } -upsert_record A "$BASE_DOMAIN" "$SERVER_IP" false -upsert_record A "git.$BASE_DOMAIN" "$SERVER_IP" false -upsert_record A "registry.$BASE_DOMAIN" "$SERVER_IP" false +delete_record() { + local type="$1" + local name="$2" + local existing_id + existing_id=$(lookup_record_id "$type" "$name") -echo "cloudflare dns updated for $BASE_DOMAIN, git.$BASE_DOMAIN, registry.$BASE_DOMAIN" + if [[ -n "$existing_id" ]]; then + api DELETE "/zones/$CLOUDFLARE_ZONE_ID/dns_records/$existing_id" >/dev/null + echo "deleted $type $name" + else + echo "skipped missing $type $name" + fi +} + +records=( + "$PUBLIC_DOMAIN" + "git.$PUBLIC_DOMAIN" + "registry.$PUBLIC_DOMAIN" +) + +case "$DNS_MODE" in + upsert) + : "${SERVER_IP:?set SERVER_IP}" + upsert_record A "${records[0]}" "$SERVER_IP" false + upsert_record A "${records[1]}" "$SERVER_IP" false + upsert_record A "${records[2]}" "$SERVER_IP" false + echo "cloudflare dns updated for ${records[*]}" + ;; + delete) + delete_record A "${records[0]}" + delete_record A "${records[1]}" + delete_record A "${records[2]}" + echo "cloudflare dns cleanup finished for ${records[*]}" + ;; + *) + echo "unsupported DNS_MODE: $DNS_MODE" >&2 + exit 1 + ;; +esac diff --git a/scripts/hetzner/configure-porkbun-dns.sh b/scripts/hetzner/configure-porkbun-dns.sh index f549d82..29e8ac3 100755 --- a/scripts/hetzner/configure-porkbun-dns.sh +++ b/scripts/hetzner/configure-porkbun-dns.sh @@ -11,18 +11,32 @@ require python3 : "${PORKBUN_API_KEY:?set PORKBUN_API_KEY}" : "${PORKBUN_SECRET_API_KEY:?set PORKBUN_SECRET_API_KEY}" : "${BASE_DOMAIN:?set BASE_DOMAIN}" -: "${SERVER_IP:?set SERVER_IP}" +: "${PUBLIC_DOMAIN:=$BASE_DOMAIN}" +: "${DNS_MODE:=upsert}" api_base="https://api.porkbun.com/api/json/v3" -root_name="" -git_name="git" -registry_name="registry" +if [[ "$PUBLIC_DOMAIN" == "$BASE_DOMAIN" ]]; then + root_name="" +elif [[ "$PUBLIC_DOMAIN" == *".$BASE_DOMAIN" ]]; then + root_name="${PUBLIC_DOMAIN%.${BASE_DOMAIN}}" +else + echo "PUBLIC_DOMAIN must equal BASE_DOMAIN or be a subdomain of BASE_DOMAIN" >&2 + exit 1 +fi +if [[ -n "$root_name" ]]; then + git_name="git.$root_name" + registry_name="registry.$root_name" +else + git_name="git" + registry_name="registry" +fi payload() { - local content="$1" - printf '{"apikey":"%s","secretapikey":"%s","content":"%s","ttl":"600"}' \ - "$PORKBUN_API_KEY" "$PORKBUN_SECRET_API_KEY" "$content" + local name="$1" + local content="$2" + printf '{"apikey":"%s","secretapikey":"%s","name":"%s","type":"A","content":"%s","ttl":"600"}' \ + "$PORKBUN_API_KEY" "$PORKBUN_SECRET_API_KEY" "$name" "$content" } list_records() { @@ -31,13 +45,9 @@ list_records() { --data "{\"apikey\":\"$PORKBUN_API_KEY\",\"secretapikey\":\"$PORKBUN_SECRET_API_KEY\"}" } -upsert_a_record() { - local name="$1" - local fqdn="$BASE_DOMAIN" - [[ -n "$name" ]] && fqdn="$name.$BASE_DOMAIN" - - local record_id - record_id=$(python3 - "$fqdn" "$(list_records)" <<'PY' +lookup_record_id() { + local fqdn="$1" + python3 - "$fqdn" "$(list_records)" <<'PY' import json,sys fqdn=sys.argv[1] data=json.loads(sys.argv[2]) @@ -46,17 +56,31 @@ for rec in data.get('records', []): print(rec.get('id','')) break PY -) +} + +upsert_a_record() { + local name="$1" + local fqdn="$BASE_DOMAIN" + [[ -n "$name" ]] && fqdn="$name.$BASE_DOMAIN" + + local record_id + record_id=$(lookup_record_id "$fqdn") + + local body + body=$(printf '{"apikey":"%s","secretapikey":"%s","name":"%s","type":"A","content":"%s","ttl":"600"}' \ + "$PORKBUN_API_KEY" "$PORKBUN_SECRET_API_KEY" "$name" "$SERVER_IP") if [[ -n "$record_id" ]]; then - curl -fsS "$api_base/dns/edit/$BASE_DOMAIN/$record_id" \ + local delete_body + delete_body=$(printf '{"apikey":"%s","secretapikey":"%s"}' "$PORKBUN_API_KEY" "$PORKBUN_SECRET_API_KEY") + curl -fsS "$api_base/dns/delete/$BASE_DOMAIN/$record_id" \ -H 'Content-Type: application/json' \ - --data "$(payload "$SERVER_IP")" >/dev/null + --data "$delete_body" >/dev/null + curl -fsS "$api_base/dns/create/$BASE_DOMAIN" \ + -H 'Content-Type: application/json' \ + --data "$body" >/dev/null echo "updated A $fqdn -> $SERVER_IP" else - local body - body=$(printf '{"apikey":"%s","secretapikey":"%s","name":"%s","type":"A","content":"%s","ttl":"600"}' \ - "$PORKBUN_API_KEY" "$PORKBUN_SECRET_API_KEY" "$name" "$SERVER_IP") curl -fsS "$api_base/dns/create/$BASE_DOMAIN" \ -H 'Content-Type: application/json' \ --data "$body" >/dev/null @@ -64,8 +88,42 @@ PY fi } -upsert_a_record "$root_name" -upsert_a_record "$git_name" -upsert_a_record "$registry_name" +delete_a_record() { + local name="$1" + local fqdn="$BASE_DOMAIN" + [[ -n "$name" ]] && fqdn="$name.$BASE_DOMAIN" -echo "porkbun dns updated for $BASE_DOMAIN, git.$BASE_DOMAIN, registry.$BASE_DOMAIN" + local record_id + record_id=$(lookup_record_id "$fqdn") + + if [[ -n "$record_id" ]]; then + local body + body=$(printf '{"apikey":"%s","secretapikey":"%s"}' "$PORKBUN_API_KEY" "$PORKBUN_SECRET_API_KEY") + curl -fsS "$api_base/dns/delete/$BASE_DOMAIN/$record_id" \ + -H 'Content-Type: application/json' \ + --data "$body" >/dev/null + echo "deleted A $fqdn" + else + echo "skipped missing A $fqdn" + fi +} + +case "$DNS_MODE" in + upsert) + : "${SERVER_IP:?set SERVER_IP}" + upsert_a_record "$root_name" + upsert_a_record "$git_name" + upsert_a_record "$registry_name" + echo "porkbun dns updated for $PUBLIC_DOMAIN, git.$PUBLIC_DOMAIN, registry.$PUBLIC_DOMAIN" + ;; + delete) + delete_a_record "$root_name" + delete_a_record "$git_name" + delete_a_record "$registry_name" + echo "porkbun dns cleanup finished for $PUBLIC_DOMAIN, git.$PUBLIC_DOMAIN, registry.$PUBLIC_DOMAIN" + ;; + *) + echo "unsupported DNS_MODE: $DNS_MODE" >&2 + exit 1 + ;; +esac