#!/usr/bin/env bash set -euo pipefail require() { command -v "$1" >/dev/null 2>&1 || { echo "missing command: $1" >&2; exit 1; } } require curl require python3 : "${PORKBUN_API_KEY:?set PORKBUN_API_KEY}" : "${PORKBUN_SECRET_API_KEY:?set PORKBUN_SECRET_API_KEY}" : "${BASE_DOMAIN:?set BASE_DOMAIN}" : "${PUBLIC_DOMAIN:=$BASE_DOMAIN}" : "${DNS_MODE:=upsert}" api_base="https://api.porkbun.com/api/json/v3" 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" grafana_name="grafana.$root_name" else git_name="git" registry_name="registry" grafana_name="grafana" fi payload() { 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() { curl -sSf "$api_base/dns/retrieve/$BASE_DOMAIN" \ -H 'Content-Type: application/json' \ --data "{\"apikey\":\"$PORKBUN_API_KEY\",\"secretapikey\":\"$PORKBUN_SECRET_API_KEY\"}" } lookup_record_id() { local fqdn="$1" python3 - "$fqdn" "$(list_records)" <<'PY' import json,sys fqdn=sys.argv[1] data=json.loads(sys.argv[2]) for rec in data.get('records', []): if rec.get('type') == 'A' and rec.get('name') == fqdn: 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 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 "$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 curl -fsS "$api_base/dns/create/$BASE_DOMAIN" \ -H 'Content-Type: application/json' \ --data "$body" >/dev/null echo "created A $fqdn -> $SERVER_IP" fi } delete_a_record() { local name="$1" local fqdn="$BASE_DOMAIN" [[ -n "$name" ]] && fqdn="$name.$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" upsert_a_record "$grafana_name" echo "porkbun dns updated for $PUBLIC_DOMAIN, git.$PUBLIC_DOMAIN, registry.$PUBLIC_DOMAIN, grafana.$PUBLIC_DOMAIN" ;; delete) delete_a_record "$root_name" delete_a_record "$git_name" delete_a_record "$registry_name" delete_a_record "$grafana_name" echo "porkbun dns cleanup finished for $PUBLIC_DOMAIN, git.$PUBLIC_DOMAIN, registry.$PUBLIC_DOMAIN, grafana.$PUBLIC_DOMAIN" ;; *) echo "unsupported DNS_MODE: $DNS_MODE" >&2 exit 1 ;; esac