portal/scripts/github-actions/ensure-frontend-dns.sh
2026-03-19 08:04:00 +08:00

141 lines
3.9 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
DRY_RUN=false
if [[ "${1-}" == "--dry-run" ]]; then
DRY_RUN=true
shift
fi
TARGET_IP="${1:?usage: ensure-frontend-dns.sh [--dry-run] <target-ip> <domain> [domain...]}"
shift
if [[ "$#" -lt 1 ]]; then
echo "usage: ensure-frontend-dns.sh [--dry-run] <target-ip> <domain> [domain...]" >&2
exit 1
fi
PROXIED="${CLOUDFLARE_PROXIED:-true}"
require_env() {
local key="$1"
local value="${!key-}"
if [[ -z "${value}" ]]; then
echo "Missing required environment variable: ${key}" >&2
exit 1
fi
}
json_get() {
local expression="$1"
python3 -c "import json,sys; data=json.load(sys.stdin); ${expression}"
}
cloudflare_api_get() {
local path="$1"
curl -fsS \
-H "Authorization: Bearer ${CLOUDFLARE_DNS_API_TOKEN}" \
-H "Content-Type: application/json" \
"https://api.cloudflare.com/client/v4${path}"
}
resolve_zone_for_domain() {
local domain="$1"
local candidate="${domain%.}"
local response
local zone_id
if [[ -n "${CLOUDFLARE_DNS_ZONE_TAG-}" ]]; then
printf '%s\t%s\n' "${CLOUDFLARE_DNS_ZONE_TAG}" "override"
return 0
fi
while [[ "${candidate}" == *.* ]]; do
response="$(cloudflare_api_get "/zones?name=${candidate}")"
if [[ "$(printf '%s' "${response}" | json_get 'print("true" if data.get("success") else "false")')" != "true" ]]; then
echo "Failed to query Cloudflare zones for ${candidate}" >&2
exit 1
fi
zone_id="$(printf '%s' "${response}" | json_get 'result=data.get("result", []); print(result[0]["id"] if result else "")')"
if [[ -n "${zone_id}" ]]; then
printf '%s\t%s\n' "${zone_id}" "${candidate}"
return 0
fi
candidate="${candidate#*.}"
done
echo "Unable to resolve Cloudflare zone for ${domain}" >&2
exit 1
}
dns_payload() {
local domain="$1"
DOMAIN="${domain}" TARGET_IP="${TARGET_IP}" PROXIED="${PROXIED}" python3 -c \
'import json, os; print(json.dumps({"type": "A", "name": os.environ["DOMAIN"], "content": os.environ["TARGET_IP"], "ttl": 1, "proxied": os.environ["PROXIED"].lower() == "true"}))'
}
upsert_record() {
local domain="$1"
local payload
local response
local record_id
local zone_id
local zone_name
payload="$(dns_payload "${domain}")"
if [[ "${DRY_RUN}" == "true" ]]; then
printf 'dry-run: upsert %s -> %s\n' "${domain}" "${TARGET_IP}"
printf '%s\n' "${payload}"
return 0
fi
IFS=$'\t' read -r zone_id zone_name <<EOF
$(resolve_zone_for_domain "${domain}")
EOF
response="$(cloudflare_api_get "/zones/${zone_id}/dns_records?type=A&name=${domain}")"
if [[ "$(printf '%s' "${response}" | json_get 'print("true" if data.get("success") else "false")')" != "true" ]]; then
echo "Failed to query Cloudflare DNS records for ${domain}" >&2
exit 1
fi
record_id="$(printf '%s' "${response}" | json_get 'result=data.get("result", []); print(result[0]["id"] if result else "")')"
if [[ -n "${record_id}" ]]; then
response="$(
curl -fsS -X PUT \
-H "Authorization: Bearer ${CLOUDFLARE_DNS_API_TOKEN}" \
-H "Content-Type: application/json" \
--data "${payload}" \
"https://api.cloudflare.com/client/v4/zones/${zone_id}/dns_records/${record_id}"
)"
else
response="$(
curl -fsS -X POST \
-H "Authorization: Bearer ${CLOUDFLARE_DNS_API_TOKEN}" \
-H "Content-Type: application/json" \
--data "${payload}" \
"https://api.cloudflare.com/client/v4/zones/${zone_id}/dns_records"
)"
fi
if [[ "$(printf '%s' "${response}" | json_get 'print("true" if data.get("success") else "false")')" != "true" ]]; then
echo "Failed to upsert Cloudflare DNS record for ${domain}" >&2
exit 1
fi
printf 'updated: %s -> %s (zone %s)\n' "${domain}" "${TARGET_IP}" "${zone_name}"
}
if [[ "${DRY_RUN}" != "true" ]]; then
require_env CLOUDFLARE_DNS_API_TOKEN
fi
for domain in "$@"; do
upsert_record "${domain}"
done