123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256 |
- #!/usr/bin/env sh
- # shellcheck disable=SC2034
- dns_hetzner_info='Hetzner.com
- Site: Hetzner.com
- Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_hetzner
- Options:
- HETZNER_Token API Token
- Issues: github.com/acmesh-official/acme.sh/issues/2943
- '
- HETZNER_Api="https://dns.hetzner.com/api/v1"
- ######## Public functions #####################
- # Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
- # Used to add txt record
- # Ref: https://dns.hetzner.com/api-docs/
- dns_hetzner_add() {
- full_domain=$1
- txt_value=$2
- HETZNER_Token="${HETZNER_Token:-$(_readaccountconf_mutable HETZNER_Token)}"
- if [ -z "$HETZNER_Token" ]; then
- HETZNER_Token=""
- _err "You didn't specify a Hetzner api token."
- _err "You can get yours from here https://dns.hetzner.com/settings/api-token."
- return 1
- fi
- #save the api key and email to the account conf file.
- _saveaccountconf_mutable HETZNER_Token "$HETZNER_Token"
- _debug "First detect the root zone"
- if ! _get_root "$full_domain"; then
- _err "Invalid domain"
- return 1
- fi
- _debug _domain_id "$_domain_id"
- _debug _sub_domain "$_sub_domain"
- _debug _domain "$_domain"
- _debug "Getting TXT records"
- if ! _find_record "$_sub_domain" "$txt_value"; then
- return 1
- fi
- if [ -z "$_record_id" ]; then
- _info "Adding record"
- if _hetzner_rest POST "records" "{\"zone_id\":\"${HETZNER_Zone_ID}\",\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"value\":\"$txt_value\",\"ttl\":120}"; then
- if _contains "$response" "$txt_value"; then
- _info "Record added, OK"
- _sleep 2
- return 0
- fi
- fi
- _err "Add txt record error${_response_error}"
- return 1
- else
- _info "Found record id: $_record_id."
- _info "Record found, do nothing."
- return 0
- # we could modify a record, if the names for txt records for *.example.com and example.com would be not the same
- #if _hetzner_rest PUT "records/${_record_id}" "{\"zone_id\":\"${HETZNER_Zone_ID}\",\"type\":\"TXT\",\"name\":\"$full_domain\",\"value\":\"$txt_value\",\"ttl\":120}"; then
- # if _contains "$response" "$txt_value"; then
- # _info "Modified, OK"
- # return 0
- # fi
- #fi
- #_err "Add txt record error (modify)."
- #return 1
- fi
- }
- # Usage: full_domain txt_value
- # Used to remove the txt record after validation
- dns_hetzner_rm() {
- full_domain=$1
- txt_value=$2
- HETZNER_Token="${HETZNER_Token:-$(_readaccountconf_mutable HETZNER_Token)}"
- _debug "First detect the root zone"
- if ! _get_root "$full_domain"; then
- _err "Invalid domain"
- return 1
- fi
- _debug _domain_id "$_domain_id"
- _debug _sub_domain "$_sub_domain"
- _debug _domain "$_domain"
- _debug "Getting TXT records"
- if ! _find_record "$_sub_domain" "$txt_value"; then
- return 1
- fi
- if [ -z "$_record_id" ]; then
- _info "Remove not needed. Record not found."
- else
- if ! _hetzner_rest DELETE "records/$_record_id"; then
- _err "Delete record error${_response_error}"
- return 1
- fi
- _sleep 2
- _info "Record deleted"
- fi
- }
- #################### Private functions below ##################################
- #returns
- # _record_id=a8d58f22d6931bf830eaa0ec6464bf81 if found; or 1 if error
- _find_record() {
- unset _record_id
- _record_name=$1
- _record_value=$2
- if [ -z "$_record_value" ]; then
- _record_value='[^"]*'
- fi
- _debug "Getting all records"
- _hetzner_rest GET "records?zone_id=${_domain_id}"
- if _response_has_error; then
- _err "Error${_response_error}"
- return 1
- else
- _record_id=$(
- echo "$response" |
- grep -o "{[^\{\}]*\"name\":\"$_record_name\"[^\}]*}" |
- grep "\"value\":\"$_record_value\"" |
- while read -r record; do
- # test for type and
- if [ -n "$(echo "$record" | _egrep_o '"type":"TXT"')" ]; then
- echo "$record" | _egrep_o '"id":"[^"]*"' | cut -d : -f 2 | tr -d \"
- break
- fi
- done
- )
- fi
- }
- #_acme-challenge.www.domain.com
- #returns
- # _sub_domain=_acme-challenge.www
- # _domain=domain.com
- # _domain_id=sdjkglgdfewsdfg
- _get_root() {
- domain=$1
- i=1
- p=1
- domain_without_acme=$(echo "$domain" | cut -d . -f 2-)
- domain_param_name=$(echo "HETZNER_Zone_ID_for_${domain_without_acme}" | sed 's/[\.\-]/_/g')
- _debug "Reading zone_id for '$domain_without_acme' from config..."
- HETZNER_Zone_ID=$(_readdomainconf "$domain_param_name")
- if [ "$HETZNER_Zone_ID" ]; then
- _debug "Found, using: $HETZNER_Zone_ID"
- if ! _hetzner_rest GET "zones/${HETZNER_Zone_ID}"; then
- _debug "Zone with id '$HETZNER_Zone_ID' does not exist."
- _cleardomainconf "$domain_param_name"
- unset HETZNER_Zone_ID
- else
- if _contains "$response" "\"id\":\"$HETZNER_Zone_ID\""; then
- _domain=$(printf "%s\n" "$response" | _egrep_o '"name":"[^"]*"' | cut -d : -f 2 | tr -d \" | head -n 1)
- if [ "$_domain" ]; then
- _cut_length=$((${#domain} - ${#_domain} - 1))
- _sub_domain=$(printf "%s" "$domain" | cut -c "1-$_cut_length")
- _domain_id="$HETZNER_Zone_ID"
- return 0
- else
- return 1
- fi
- else
- return 1
- fi
- fi
- fi
- _debug "Trying to get zone id by domain name for '$domain_without_acme'."
- while true; do
- h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
- if [ -z "$h" ]; then
- #not valid
- return 1
- fi
- _debug h "$h"
- _hetzner_rest GET "zones?name=$h"
- if _contains "$response" "\"name\":\"$h\"" || _contains "$response" '"total_entries":1'; then
- _domain_id=$(echo "$response" | _egrep_o "\[.\"id\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \")
- if [ "$_domain_id" ]; then
- _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
- _domain=$h
- HETZNER_Zone_ID=$_domain_id
- _savedomainconf "$domain_param_name" "$HETZNER_Zone_ID"
- return 0
- fi
- return 1
- fi
- p=$i
- i=$(_math "$i" + 1)
- done
- return 1
- }
- #returns
- # _response_error
- _response_has_error() {
- unset _response_error
- err_part="$(echo "$response" | _egrep_o '"error":\{[^\}]*\}')"
- if [ -n "$err_part" ]; then
- err_code=$(echo "$err_part" | _egrep_o '"code":[0-9]+' | cut -d : -f 2)
- err_message=$(echo "$err_part" | _egrep_o '"message":"[^"]+"' | cut -d : -f 2 | tr -d \")
- if [ -n "$err_code" ] && [ -n "$err_message" ]; then
- _response_error=" - message: ${err_message}, code: ${err_code}"
- return 0
- fi
- fi
- return 1
- }
- #returns
- # response
- _hetzner_rest() {
- m=$1
- ep="$2"
- data="$3"
- _debug "$ep"
- key_trimmed=$(echo "$HETZNER_Token" | tr -d \")
- export _H1="Content-TType: application/json"
- export _H2="Auth-API-Token: $key_trimmed"
- if [ "$m" != "GET" ]; then
- _debug data "$data"
- response="$(_post "$data" "$HETZNER_Api/$ep" "" "$m")"
- else
- response="$(_get "$HETZNER_Api/$ep")"
- fi
- if [ "$?" != "0" ] || _response_has_error; then
- _debug "Error$_response_error"
- return 1
- fi
- _debug2 response "$response"
- return 0
- }
|