]> git.proxmox.com Git - mirror_acme.sh.git/blame - dnsapi/dns_hetzner.sh
Merge pull request #4532 from acmesh-official/dev
[mirror_acme.sh.git] / dnsapi / dns_hetzner.sh
CommitLineData
fa91516d
GG
1#!/usr/bin/env sh
2
3#
4#HETZNER_Token="sdfsdfsdfljlbjkljlkjsdfoiwje"
5#
6
7HETZNER_Api="https://dns.hetzner.com/api/v1"
8
9######## Public functions #####################
10
11# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
12# Used to add txt record
13# Ref: https://dns.hetzner.com/api-docs/
14dns_hetzner_add() {
15 full_domain=$1
16 txt_value=$2
17
18 HETZNER_Token="${HETZNER_Token:-$(_readaccountconf_mutable HETZNER_Token)}"
19
20 if [ -z "$HETZNER_Token" ]; then
21 HETZNER_Token=""
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."
24 return 1
25 fi
26
27 #save the api key and email to the account conf file.
28 _saveaccountconf_mutable HETZNER_Token "$HETZNER_Token"
29
30 _debug "First detect the root zone"
31
32 if ! _get_root "$full_domain"; then
33 _err "Invalid domain"
34 return 1
35 fi
36 _debug _domain_id "$_domain_id"
37 _debug _sub_domain "$_sub_domain"
38 _debug _domain "$_domain"
39
40 _debug "Getting TXT records"
41 if ! _find_record "$_sub_domain" "$txt_value"; then
42 return 1
43 fi
44
45 if [ -z "$_record_id" ]; then
46 _info "Adding record"
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"
50 _sleep 2
51 return 0
52 fi
53 fi
54 _err "Add txt record error${_response_error}"
55 return 1
56 else
57 _info "Found record id: $_record_id."
58 _info "Record found, do nothing."
b82c48b6
GG
59 return 0
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"
64 # return 0
65 # fi
66 #fi
67 #_err "Add txt record error (modify)."
68 #return 1
fa91516d
GG
69 fi
70}
71
72# Usage: full_domain txt_value
73# Used to remove the txt record after validation
74dns_hetzner_rm() {
75 full_domain=$1
76 txt_value=$2
77
78 HETZNER_Token="${HETZNER_Token:-$(_readaccountconf_mutable HETZNER_Token)}"
79
80 _debug "First detect the root zone"
81 if ! _get_root "$full_domain"; then
82 _err "Invalid domain"
83 return 1
84 fi
85 _debug _domain_id "$_domain_id"
86 _debug _sub_domain "$_sub_domain"
87 _debug _domain "$_domain"
88
89 _debug "Getting TXT records"
90 if ! _find_record "$_sub_domain" "$txt_value"; then
91 return 1
92 fi
93
94 if [ -z "$_record_id" ]; then
95 _info "Remove not needed. Record not found."
96 else
97 if ! _hetzner_rest DELETE "records/$_record_id"; then
98 _err "Delete record error${_response_error}"
99 return 1
100 fi
101 _sleep 2
102 _info "Record deleted"
103 fi
104}
105
106#################### Private functions below ##################################
107#returns
108# _record_id=a8d58f22d6931bf830eaa0ec6464bf81 if found; or 1 if error
109_find_record() {
b82c48b6 110 unset _record_id
fa91516d
GG
111 _record_name=$1
112 _record_value=$2
113
114 if [ -z "$_record_value" ]; then
b82c48b6 115 _record_value='[^"]*'
fa91516d
GG
116 fi
117
118 _debug "Getting all records"
119 _hetzner_rest GET "records?zone_id=${_domain_id}"
120
121 if _response_has_error; then
122 _err "Error${_response_error}"
123 return 1
124 else
125 _record_id=$(
19c43451 126 echo "$response" |
127 grep -o "{[^\{\}]*\"name\":\"$_record_name\"[^\}]*}" |
128 grep "\"value\":\"$_record_value\"" |
129 while read -r record; do
fa91516d
GG
130 # test for type and
131 if [ -n "$(echo "$record" | _egrep_o '"type":"TXT"')" ]; then
b82c48b6 132 echo "$record" | _egrep_o '"id":"[^"]*"' | cut -d : -f 2 | tr -d \"
fa91516d
GG
133 break
134 fi
135 done
b82c48b6 136 )
fa91516d
GG
137 fi
138}
139
140#_acme-challenge.www.domain.com
141#returns
142# _sub_domain=_acme-challenge.www
143# _domain=domain.com
144# _domain_id=sdjkglgdfewsdfg
145_get_root() {
146 domain=$1
147 i=1
148 p=1
149
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')
152
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
eca57bee 158 _debug "Zone with id '$HETZNER_Zone_ID' does not exist."
fa91516d
GG
159 _cleardomainconf "$domain_param_name"
160 unset HETZNER_Zone_ID
161 else
162 if _contains "$response" "\"id\":\"$HETZNER_Zone_ID\""; then
b82c48b6 163 _domain=$(printf "%s\n" "$response" | _egrep_o '"name":"[^"]*"' | cut -d : -f 2 | tr -d \" | head -n 1)
fa91516d
GG
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"
168 return 0
169 else
170 return 1
171 fi
172 else
173 return 1
174 fi
175 fi
176 fi
177
178 _debug "Trying to get zone id by domain name for '$domain_without_acme'."
179 while true; do
180 h=$(printf "%s" "$domain" | cut -d . -f $i-100)
181 if [ -z "$h" ]; then
182 #not valid
183 return 1
184 fi
185 _debug h "$h"
186
187 _hetzner_rest GET "zones?name=$h"
188
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)
193 _domain=$h
194 HETZNER_Zone_ID=$_domain_id
195 _savedomainconf "$domain_param_name" "$HETZNER_Zone_ID"
196 return 0
197 fi
198 return 1
199 fi
200 p=$i
201 i=$(_math "$i" + 1)
202 done
203 return 1
204}
205
206#returns
207# _response_error
208_response_has_error() {
b82c48b6 209 unset _response_error
fa91516d 210
b82c48b6 211 err_part="$(echo "$response" | _egrep_o '"error":{[^}]*}')"
fa91516d 212
b82c48b6
GG
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 \")
fa91516d 216
b82c48b6
GG
217 if [ -n "$err_code" ] && [ -n "$err_message" ]; then
218 _response_error=" - message: ${err_message}, code: ${err_code}"
219 return 0
220 fi
221 fi
fa91516d 222
b82c48b6 223 return 1
fa91516d
GG
224}
225
226#returns
227# response
228_hetzner_rest() {
229 m=$1
230 ep="$2"
231 data="$3"
232 _debug "$ep"
233
234 key_trimmed=$(echo "$HETZNER_Token" | tr -d \")
235
236 export _H1="Content-TType: application/json"
237 export _H2="Auth-API-Token: $key_trimmed"
238
239 if [ "$m" != "GET" ]; then
240 _debug data "$data"
241 response="$(_post "$data" "$HETZNER_Api/$ep" "" "$m")"
242 else
243 response="$(_get "$HETZNER_Api/$ep")"
244 fi
245
246 if [ "$?" != "0" ] || _response_has_error; then
247 _debug "Error$_response_error"
248 return 1
249 fi
250 _debug2 response "$response"
251 return 0
252}