4 #HETZNER_Token="sdfsdfsdfljlbjkljlkjsdfoiwje"
7 HETZNER_Api
="https://dns.hetzner.com/api/v1"
9 ######## Public functions #####################
11 # Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
12 # Used to add txt record
13 # Ref: https://dns.hetzner.com/api-docs/
18 HETZNER_Token
="${HETZNER_Token:-$(_readaccountconf_mutable HETZNER_Token)}"
20 if [ -z "$HETZNER_Token" ]; then
22 _err
"You didn't specify a Hetzner api token."
23 _err
"You can get yours from here https://dns.hetzner.com/settings/api-token."
27 #save the api key and email to the account conf file.
28 _saveaccountconf_mutable HETZNER_Token
"$HETZNER_Token"
30 _debug
"First detect the root zone"
32 if ! _get_root
"$full_domain"; then
36 _debug _domain_id
"$_domain_id"
37 _debug _sub_domain
"$_sub_domain"
38 _debug _domain
"$_domain"
40 _debug
"Getting TXT records"
41 if ! _find_record
"$_sub_domain" "$txt_value"; then
45 if [ -z "$_record_id" ]; then
47 if _hetzner_rest POST
"records" "{\"zone_id\":\"${HETZNER_Zone_ID}\",\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"value\":\"$txt_value\",\"ttl\":120}"; then
48 if _contains
"$response" "$txt_value"; then
49 _info
"Record added, OK"
54 _err
"Add txt record error${_response_error}"
57 _info
"Found record id: $_record_id."
58 _info
"Record found, do nothing."
60 # we could modify a record, if the names for txt records for *.example.com and example.com would be not the same
61 #if _hetzner_rest PUT "records/${_record_id}" "{\"zone_id\":\"${HETZNER_Zone_ID}\",\"type\":\"TXT\",\"name\":\"$full_domain\",\"value\":\"$txt_value\",\"ttl\":120}"; then
62 # if _contains "$response" "$txt_value"; then
63 # _info "Modified, OK"
67 #_err "Add txt record error (modify)."
72 # Usage: full_domain txt_value
73 # Used to remove the txt record after validation
78 HETZNER_Token
="${HETZNER_Token:-$(_readaccountconf_mutable HETZNER_Token)}"
80 _debug
"First detect the root zone"
81 if ! _get_root
"$full_domain"; then
85 _debug _domain_id
"$_domain_id"
86 _debug _sub_domain
"$_sub_domain"
87 _debug _domain
"$_domain"
89 _debug
"Getting TXT records"
90 if ! _find_record
"$_sub_domain" "$txt_value"; then
94 if [ -z "$_record_id" ]; then
95 _info
"Remove not needed. Record not found."
97 if ! _hetzner_rest DELETE
"records/$_record_id"; then
98 _err
"Delete record error${_response_error}"
102 _info
"Record deleted"
106 #################### Private functions below ##################################
108 # _record_id=a8d58f22d6931bf830eaa0ec6464bf81 if found; or 1 if error
114 if [ -z "$_record_value" ]; then
115 _record_value
='[^"]*'
118 _debug
"Getting all records"
119 _hetzner_rest GET
"records?zone_id=${_domain_id}"
121 if _response_has_error
; then
122 _err
"Error${_response_error}"
127 grep -o "{[^\{\}]*\"name\":\"$_record_name\"[^\}]*}" |
128 grep "\"value\":\"$_record_value\"" |
129 while read -r record
; do
131 if [ -n "$(echo "$record" | _egrep_o '"type":"TXT
"')" ]; then
132 echo "$record" | _egrep_o
'"id":"[^"]*"' | cut
-d : -f 2 |
tr -d \"
140 #_acme-challenge.www.domain.com
142 # _sub_domain=_acme-challenge.www
144 # _domain_id=sdjkglgdfewsdfg
150 domain_without_acme
=$
(echo "$domain" | cut
-d .
-f 2-)
151 domain_param_name
=$
(echo "HETZNER_Zone_ID_for_${domain_without_acme}" |
sed 's/[\.\-]/_/g')
153 _debug
"Reading zone_id for '$domain_without_acme' from config..."
154 HETZNER_Zone_ID
=$
(_readdomainconf
"$domain_param_name")
155 if [ "$HETZNER_Zone_ID" ]; then
156 _debug
"Found, using: $HETZNER_Zone_ID"
157 if ! _hetzner_rest GET
"zones/${HETZNER_Zone_ID}"; then
158 _debug
"Zone with id '$HETZNER_Zone_ID' does not exist."
159 _cleardomainconf
"$domain_param_name"
160 unset HETZNER_Zone_ID
162 if _contains
"$response" "\"id\":\"$HETZNER_Zone_ID\""; then
163 _domain
=$
(printf "%s\n" "$response" | _egrep_o
'"name":"[^"]*"' | cut
-d : -f 2 |
tr -d \" |
head -n 1)
164 if [ "$_domain" ]; then
165 _cut_length
=$
((${#domain} - ${#_domain} - 1))
166 _sub_domain
=$
(printf "%s" "$domain" | cut
-c "1-$_cut_length")
167 _domain_id
="$HETZNER_Zone_ID"
178 _debug
"Trying to get zone id by domain name for '$domain_without_acme'."
180 h
=$
(printf "%s" "$domain" | cut
-d .
-f $i-100)
187 _hetzner_rest GET
"zones?name=$h"
189 if _contains
"$response" "\"name\":\"$h\"" || _contains
"$response" '"total_entries":1'; then
190 _domain_id
=$
(echo "$response" | _egrep_o
"\[.\"id\":\"[^\"]*\"" | _head_n
1 | cut
-d : -f 2 |
tr -d \")
191 if [ "$_domain_id" ]; then
192 _sub_domain
=$
(printf "%s" "$domain" | cut
-d .
-f 1-$p)
194 HETZNER_Zone_ID
=$_domain_id
195 _savedomainconf
"$domain_param_name" "$HETZNER_Zone_ID"
208 _response_has_error
() {
209 unset _response_error
211 err_part
="$(echo "$response" | _egrep_o '"error
":{[^}]*}')"
213 if [ -n "$err_part" ]; then
214 err_code
=$
(echo "$err_part" | _egrep_o
'"code":[0-9]+' | cut
-d : -f 2)
215 err_message
=$
(echo "$err_part" | _egrep_o
'"message":"[^"]+"' | cut
-d : -f 2 |
tr -d \")
217 if [ -n "$err_code" ] && [ -n "$err_message" ]; then
218 _response_error
=" - message: ${err_message}, code: ${err_code}"
234 key_trimmed
=$
(echo "$HETZNER_Token" |
tr -d \")
236 export _H1
="Content-TType: application/json"
237 export _H2
="Auth-API-Token: $key_trimmed"
239 if [ "$m" != "GET" ]; then
241 response
="$(_post "$data" "$HETZNER_Api/$ep" "" "$m")"
243 response
="$(_get "$HETZNER_Api/$ep")"
246 if [ "$?" != "0" ] || _response_has_error
; then
247 _debug
"Error$_response_error"
250 _debug2 response
"$response"