#!/usr/bin/env bash
|
|
# dns_gcloud
|
|
# Add/Del/List TXT record using the Google Cloud DNS gcloud command
|
|
# ver 2025-09-23 # shellcheck has read, even I'll say eveything was so nice
|
|
# org. version:
|
|
# https://github.com/kshji/gitssl_gcloud
|
|
#
|
|
# Main reason to make was to support getssl using DNS validation with Google Cloud DNS
|
|
# You can use this script to any host setting TXT records, default is _acme-challenge
|
|
#
|
|
# dns_gloud -c command domain token
|
|
#
|
|
# get help:
|
|
# dns_gloud -? | --help
|
|
#
|
|
# dns_gloud -c add example.com "testN"
|
|
# dns_gloud -c list example.com
|
|
# dns_gloud -c del example.com "testN"
|
|
# dns_gloud -c add example.com "test1" "test2"
|
|
# dns_gloud -c list example.com
|
|
# dns_gloud -c del example.com "test1" "test2"
|
|
#
|
|
# options:
|
|
# -d 0|1 # debug on/off to the file /var/tmp/getssl/...log
|
|
# -s 10 # sleeptime after gcloud add/del process, default 10
|
|
# -h hostname # default is "_acme-challenge."
|
|
# # if like to use domain without host, set host empty string !!!
|
|
# -t ttlvalue # set ttl, default 60 = 1 min
|
|
#
|
|
#
|
|
|
|
PRG="$0"
|
|
BINDIR="${PRG%/*}"
|
|
[ "$PRG" = "$BINDIR" ] && BINDIR="." # - same dir as program
|
|
PRG="${PRG##*/}"
|
|
|
|
#######################################################################################
|
|
usage()
|
|
{
|
|
cat <<EOT
|
|
usage:$PRG -c COMMAND [ -d 0|1 ] [ -t ttlvalue ] [ -s sleep_sec_after_gcloud ] [ -h hostname ] DOMAIN TOKEN
|
|
or
|
|
$PRG --command COMMAND [ --debug 0|1 ] [ --ttl ttlvalue ] [ --sleep sleep_sec_after_gcloud ] [ --host hostname ] DOMAIN TOKEN
|
|
|
|
-d 1 # print some debug data and make debug file to the dir /var/tmp/getssl/
|
|
-t NNN # set TTL value, default 60. Have to be same in the ADD and DEL
|
|
-s NN # default 10 s, sleep seconds after gcloud process
|
|
-h hostname # hostname, default is _acme-challenge, empty string = use domain
|
|
|
|
EOT
|
|
|
|
}
|
|
|
|
#######################################################################################
|
|
err()
|
|
{
|
|
echo "$PRG err:$*"
|
|
}
|
|
|
|
#######################################################################################
|
|
dbgstr()
|
|
{
|
|
((DEBUG<1)) && return
|
|
echo "$PRG debug:$*"
|
|
}
|
|
|
|
#######################################################################################
|
|
dbg()
|
|
{
|
|
|
|
|
|
Xdomain="$1" # domain + command
|
|
Xcmd="$2"
|
|
[ "$Xdomain" = "" ] && Xdomain="default"
|
|
[ "$Xcmd" = "" ] && Xcmd="cmd"
|
|
Xdomain="${Xdomain%.}" # del last dot
|
|
tmpd="/var/tmp/getssl"
|
|
tmpf="$tmpd/$PRG.$Xdomain.$Xcmd.log"
|
|
mkdir -p "$tmpd" 2>/dev/null
|
|
chmod 1777 "$tmpd" 2>/dev/null
|
|
|
|
|
|
cnt=0
|
|
# save only last execute info
|
|
{
|
|
date
|
|
echo "GCLOUD_PROJECTID:$GCLOUD_PROJECTID"
|
|
echo "GCLOUD_ZONE:$GCLOUD_ZONE"
|
|
echo "GCLOUD_ACCOUNT:$GCLOUD_ACCOUNT"
|
|
echo "GCLOUD_KEYFILE:$GCLOUD_KEYFILE"
|
|
env
|
|
} > "$tmpf"
|
|
for var in $all
|
|
do
|
|
((cnt++))
|
|
echo "$cnt:<$var>" >> "$tmpf"
|
|
done
|
|
|
|
}
|
|
|
|
#######################################################################################
|
|
check_end_dot()
|
|
{
|
|
# gcloud need fulldomain ending dot = absolut domain path
|
|
# set the last dot if missing
|
|
Xorg="$1"
|
|
Xnodot="${Xorg%.}" # remove last dot if there is
|
|
echo "$Xnodot." # add dot allways
|
|
}
|
|
|
|
#######################################################################################
|
|
list_txt()
|
|
{
|
|
|
|
Xname="$1"
|
|
list=$(gcloud dns record-sets list --zone="$GCLOUD_ZONE" --name="$Xname" --type="TXT" )
|
|
stat=$?
|
|
(( stat > 0 )) && err "gcloud error to list TXT record" && exit 2
|
|
variables=variables
|
|
|
|
# Some shell checkers (ex.shellcheck) like to do next different way. I'll say both works
|
|
# you can read 1st line to var and then use {$var?} on reading
|
|
# in this case you'll get same result. This read 1st loop varianle variables, look value of variables
|
|
# on the second loop it read variables, which was on the 1st line ... command line process is so nice
|
|
# this do exactly what we need to do ... (not that what https://www.shellcheck.net/wiki/SC2229 explain)
|
|
oifs="$IFS"
|
|
cnt=0
|
|
echo "$list" | while read $variables
|
|
do
|
|
(( cnt++ ))
|
|
# 1st line is header, read next line
|
|
(( cnt == 1 )) && continue
|
|
echo "name:$NAME type:$TYPE ttl:$TTL data:$DATA"
|
|
# next line works just what we need, shellcheck not like this ...
|
|
# Wiki's last part: it's okay https://www.shellcheck.net/wiki/SC2206
|
|
IFS="," values=($DATA)
|
|
IFS="$oifs"
|
|
numOfvalues=${#values[@]}
|
|
for (( var=0; var<numOfvalues ; var++ ))
|
|
do
|
|
echo " - data($var):${values[$var]}"
|
|
done
|
|
done
|
|
sleep 1
|
|
exit 0
|
|
}
|
|
|
|
#######################################################################################
|
|
del_txt()
|
|
{
|
|
|
|
Xname="$1"
|
|
shift
|
|
#
|
|
Xtoken=""
|
|
while [ $# -gt 0 ]
|
|
do
|
|
Xtoken="$Xtoken \"$1\""
|
|
shift
|
|
done
|
|
dbgstr "<$Xtoken>"
|
|
|
|
#exit
|
|
# start transaction
|
|
gcloud dns record-sets transaction start --zone="$GCLOUD_ZONE" --project="$GCLOUD_PROJECTID"
|
|
stat=$?
|
|
(( stat > 0 )) && err "gcloud start transaction error" && exit 2
|
|
|
|
# del TXT
|
|
dbgstr gcloud dns record-sets transaction remove --name="$Xname" --ttl="$ttl" --type="TXT" \
|
|
--zone="$GCLOUD_ZONE" --project="$GCLOUD_PROJECTID" "$Xtoken"
|
|
gcloud dns record-sets transaction remove --name="$Xname" --ttl="$ttl" --type="TXT" \
|
|
--zone="$GCLOUD_ZONE" --project="$GCLOUD_PROJECTID" "$Xtoken"
|
|
stat=$?
|
|
if (( stat > 0 )) ; then
|
|
err "gcloud remove error"
|
|
gcloud dns record-sets transaction abort --zone="$GCLOUD_ZONE" --project="$GCLOUD_PROJECTID"
|
|
exit 2
|
|
fi
|
|
|
|
gcloud dns record-sets transaction execute --zone="$GCLOUD_ZONE" --project="$GCLOUD_PROJECTID"
|
|
stat=$?
|
|
(( stat > 0 )) && err "gcloud transaction execute error" && exit 2
|
|
|
|
# if not sleep, get error ???
|
|
sleep "$sleepafter"
|
|
exit 0
|
|
}
|
|
|
|
#######################################################################################
|
|
add_txt()
|
|
{
|
|
|
|
Xname="$1"
|
|
shift
|
|
# could be 1-n values
|
|
Xtoken=""
|
|
while [ $# -gt 0 ]
|
|
do
|
|
Xtoken="$Xtoken \"$1\""
|
|
shift
|
|
done
|
|
dbgstr "<$Xtoken>"
|
|
|
|
|
|
|
|
# start transaction
|
|
gcloud dns record-sets transaction start --zone="$GCLOUD_ZONE" --project="$GCLOUD_PROJECTID"
|
|
stat=$?
|
|
if (( stat > 0 )) ; then
|
|
err "gcloud start transaction error"
|
|
gcloud dns record-sets transaction abort --zone="$GCLOUD_ZONE" --project="$GCLOUD_PROJECTID"
|
|
exit 2
|
|
fi
|
|
|
|
# add TXT
|
|
dbgstr gcloud dns record-sets transaction add --name="$Xname" --ttl="$ttl" --type="TXT" \
|
|
--zone="$GCLOUD_ZONE" --project="$GCLOUD_PROJECTID" "$Xtoken"
|
|
gcloud dns record-sets transaction add --name="$Xname" --ttl="$ttl" --type="TXT" \
|
|
--zone="$GCLOUD_ZONE" --project="$GCLOUD_PROJECTID" "$Xtoken"
|
|
stat=$?
|
|
if (( stat > 0 )) ; then
|
|
err "gcloud add error"
|
|
gcloud dns record-sets transaction abort --zone="$GCLOUD_ZONE" --project="$GCLOUD_PROJECTID"
|
|
exit 2
|
|
fi
|
|
|
|
gcloud dns record-sets transaction execute --zone="$GCLOUD_ZONE" --project="$GCLOUD_PROJECTID"
|
|
stat=$?
|
|
#echo "execute stat:$stat"
|
|
(( stat > 0 )) && err "gcloud transaction execute error" && exit 2
|
|
|
|
# if not sleep, get error ???
|
|
sleep "$sleepafter"
|
|
exit 0
|
|
}
|
|
|
|
#######################################################################################
|
|
# MAIN
|
|
#######################################################################################
|
|
|
|
DEBUG=0
|
|
command=""
|
|
sleepafter=10
|
|
# default host to manipulate
|
|
host="_acme-challenge."
|
|
ttl=60
|
|
|
|
while [ $# -gt 0 ]
|
|
do
|
|
arg="$1"
|
|
case "$arg" in
|
|
-c|--command|--cmd) command="$2"; shift ;;
|
|
-d|--debug) DEBUG="$2" ; shift ;;
|
|
-s|--sleep) sleepafter="$2"; shift ;;
|
|
-h|--host) host="$2"
|
|
[ $# -lt 2 ] && usage && exit 2
|
|
host="$2"
|
|
shift
|
|
;;
|
|
-t|--ttl) ttl="$2"; shift ;;
|
|
-?|--help) usage; exit 2 ;;
|
|
-*) # unknown option
|
|
err "unknown option $arg"
|
|
usage
|
|
exit 2
|
|
;;
|
|
*) # arguments, stop the option parser
|
|
break
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
[ "$GCLOUD_PROJECTID" = "" ] && err "GCLOUD_PROJECTID is not set. Unable to set TXT records." && exit 2
|
|
[ "$GCLOUD_ZONE" = "" ] && err "GCLOUD_ZONE is not set. Unable to set TXT records." && exit 2
|
|
[ "$GCLOUD_ACCOUNT" = "" ] && err "GCLOUD_ACCOUNT is not set. Unable to set TXT records." && exit 2
|
|
[ "$GCLOUD_KEYFILE" = "" ] && err "GCLOUD_KEYFILE is not set. Unable to set TXT records." && exit 2
|
|
[ ! -f "$GCLOUD_KEYFILE" ] && err "file not usable:$GCLOUD_KEYFILE" && exit 2
|
|
|
|
|
|
all="$*"
|
|
fulldomain="$1"
|
|
shift
|
|
token="$*" # could be 1-n tokens if del
|
|
|
|
case "$command" in
|
|
add) ;;
|
|
del) ;;
|
|
list) ;;
|
|
*) command="" ;;
|
|
esac
|
|
|
|
|
|
[ "$command" = "" ] && err "need option -c add | -c del | -c list" && exit 2
|
|
[ "$fulldomain" = "" ] && err "need fulldomain argument." && exit 2
|
|
[ "$token" = "" ] && [ "$command" != "list" ] && err "need token argument." && exit 2
|
|
|
|
# dbg info to the program tmp dir
|
|
(( DEBUG>0)) && dbg "$fulldomain" "$command"
|
|
|
|
# host check ending of dot
|
|
[ "$host" != "" ] && host=$(check_end_dot "$host")
|
|
# host fullname
|
|
gname=$(check_end_dot "$host$fulldomain")
|
|
#echo "gname: $gname"
|
|
|
|
# activate google cloud account
|
|
gcloud auth activate-service-account "$GCLOUD_ACCOUNT" --key-file="$GCLOUD_KEYFILE" --project="$GCLOUD_PROJECTID"
|
|
stat=$?
|
|
(( stat > 0 )) && err "gcloud activate account error" && exit 2
|
|
|
|
case "$command" in
|
|
add) add_txt "$gname" "$token"
|
|
;;
|
|
del) del_txt "$gname" "$token"
|
|
;;
|
|
list) list_txt "$gname"
|
|
;;
|
|
*)
|
|
err "unknown command"
|
|
exit 2
|
|
;;
|
|
esac
|
|
|
|
|
|
|