scripts/cfdns
2024-09-29 15:11:09 -04:00

233 lines
4.1 KiB
Bash
Executable file

#!/usr/bin/env bash
quiet=0 # should i shut up?!?!? :<
token="" # the api token
domain="" # the domain to update
record="A" # the record type
ttl="60" # the new time to live
content="" # the new record content
usage() {
printf "usage: cfdns [-hq] [-d DOMAIN] [-t TOKEN] [-r RECORD] [-l TTL] [-cC CONTENT]\n\n"
printf "\t-h\t\tshow the help message\n"
printf "\t-q\t\tquiet output\n"
printf "\t-d DOMAIN\tthe domain name to update\n"
printf "\t-t TOKEN\tthe api token\n"
printf "\t-r RECORD\tthe new record type\n"
printf "\t-l TTL\t\tthe new time to live\n"
printf "\t-c CONTENT\tthe new contents of the record\n"
printf "\t-C RAW CONTENT\tthe new conents of the record (as raw json)\n"
}
chk_command() {
if ! command -v "$1" > /dev/null; then
>&2 echo "error: command '$1' could not be found"
exit 1
fi
}
chk_command "getopts"
chk_command "curl"
chk_command "jq"
while getopts "hqd:t:r:l:c:C:" arg > /dev/null; do
case $arg in
h)
usage
exit 0
;;
q)
quiet=1
;;
d)
domain="${OPTARG}"
;;
t)
token="${OPTARG}"
;;
r)
record="${OPTARG}"
;;
l)
ttl="${OPTARG}"
;;
c)
content="\"content\": \"${OPTARG}\""
;;
C)
content="${OPTARG}"
;;
?)
exit 1
;;
esac
done
if [ ! "$cert" = "cert.pem" ] && [ "$key" = "cert.key" ]; then
name=${cert%.*}
key="$name.key"
fi
log() {
if [ $quiet = 0 ]; then
printf "$*\n" 1>&2
fi
}
step() {
if [ $quiet = 0 ]; then
printf "\033[32m>>> \033[0m$*\n" 1>&2
fi
}
error() {
printf "\033[31merror: \033[0m$*\n" > /dev/stderr
}
die() {
kill $$
}
handle_error() {
errs="$1"
count="$(echo $errs | jq -r ". | length")"
for i in $(seq 0 $(($count - 1))); do
err="$(echo $errs | jq -r ".[$i]")"
error "$(echo $err | jq -r '.message')"
chain="$(echo $err | jq -r '.error_chain')"
if ! [ "$chain" = "null" ]; then
count="$(echo $chain | jq -r ". | length")"
for j in $(seq 0 $(($count - 1))); do
error "$(echo $chain | jq -r ".[$j].message")"
done
fi
done
}
_curl() {
RESPONSE=$(curl --silent --request $1 \
--url "https://api.cloudflare.com/client/v4$2" \
--header "Content-Type: application/json" \
--header "Authorization: Bearer $token" \
--data "$3")
SUCCESS=$(echo $RESPONSE | jq -r ".success")
if [ "$SUCCESS" != "true" ]; then
errs="$(echo $RESPONSE | jq -r .errors)"
handle_error "$errs"
die
fi
echo $RESPONSE | jq ".result"
}
patch() {
_curl "PATCH" "$1" "$2"
}
post() {
_curl "POST" "$1" "$2"
}
get() {
_curl "GET" "$1" ""
}
# Step 1: get the zone the domain is in
zone=$(echo "$domain" | awk -F'.' '{print $(NF-1)"."$(NF);}' 2>/dev/null)
if [ "$zone" = "" ]; then
error "invalid domain: '$domain'"
die
fi
step "fetching zones..."
zones=$(get "/zones")
zone_id="null"
i=0
while true; do
json="$(echo "$zones" | jq ".[$i]")"
if [ "$json" == "null" ]; then
break
fi
name="$(echo "$json" | jq -r ".name")"
id="$(echo "$json" | jq -r ".id")"
log "found zone: $name ($id)"
if [ "$name" == "$zone" ]; then
zone_id="$id"
break
fi
((i=i+1))
done
if [ "$zone_id" == "null" ]; then
error "could not find zone: $zone"
die
fi
# Step 2: get the current record id if it exists
step "fetching records..."
records="$(get "/zones/$zone_id/dns_records")"
current="null"
i=0
while true; do
_record="$(echo "$records" | jq ".[$i]")"
if [ "$_record" == "null" ]; then
break
fi
name="$(echo "$_record" | jq -r ".name")"
type="$(echo "$_record" | jq -r ".type")"
_content="$(echo "$_record" | jq -r ".name")"
id="$(echo "$_record" | jq -r ".id")"
log "found record: $name $type \"$_content\" ($id)"
if [ "$name" == "$domain" ] && [ "$record" = "$type" ]; then
current="$id"
break
fi
((i=i+1))
done
# Step 3: update record
step "updating record"
update() {
patch "/zones/$zone_id/dns_records/$1" "$2"
}
create() {
post "/zones/$zone_id/dns_records" "$1"
}
json="{
\"name\": \"$domain\",
\"proxied\": false,
\"type\": \"$record\",
$content
}"
log $(echo "$json" | jq -C)
if [ "$current" == "null" ]; then
log "no record found... creating new one"
create "$json"
else
log "record found... updating"
update "$current" "$json"
fi
step "successfully updated record"