You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

252 lines
6.7 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. #!/usr/bin/env sh
  2. #
  3. #HETZNER_Token="sdfsdfsdfljlbjkljlkjsdfoiwje"
  4. #
  5. HETZNER_Api="https://dns.hetzner.com/api/v1"
  6. ######## Public functions #####################
  7. # Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
  8. # Used to add txt record
  9. # Ref: https://dns.hetzner.com/api-docs/
  10. dns_hetzner_add() {
  11. full_domain=$1
  12. txt_value=$2
  13. HETZNER_Token="${HETZNER_Token:-$(_readaccountconf_mutable HETZNER_Token)}"
  14. if [ -z "$HETZNER_Token" ]; then
  15. HETZNER_Token=""
  16. _err "You didn't specify a Hetzner api token."
  17. _err "You can get yours from here https://dns.hetzner.com/settings/api-token."
  18. return 1
  19. fi
  20. #save the api key and email to the account conf file.
  21. _saveaccountconf_mutable HETZNER_Token "$HETZNER_Token"
  22. _debug "First detect the root zone"
  23. if ! _get_root "$full_domain"; then
  24. _err "Invalid domain"
  25. return 1
  26. fi
  27. _debug _domain_id "$_domain_id"
  28. _debug _sub_domain "$_sub_domain"
  29. _debug _domain "$_domain"
  30. _debug "Getting TXT records"
  31. if ! _find_record "$_sub_domain" "$txt_value"; then
  32. return 1
  33. fi
  34. if [ -z "$_record_id" ]; then
  35. _info "Adding record"
  36. if _hetzner_rest POST "records" "{\"zone_id\":\"${HETZNER_Zone_ID}\",\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"value\":\"$txt_value\",\"ttl\":120}"; then
  37. if _contains "$response" "$txt_value"; then
  38. _info "Record added, OK"
  39. _sleep 2
  40. return 0
  41. fi
  42. fi
  43. _err "Add txt record error${_response_error}"
  44. return 1
  45. else
  46. _info "Found record id: $_record_id."
  47. _info "Record found, do nothing."
  48. return 0
  49. # we could modify a record, if the names for txt records for *.example.com and example.com would be not the same
  50. #if _hetzner_rest PUT "records/${_record_id}" "{\"zone_id\":\"${HETZNER_Zone_ID}\",\"type\":\"TXT\",\"name\":\"$full_domain\",\"value\":\"$txt_value\",\"ttl\":120}"; then
  51. # if _contains "$response" "$txt_value"; then
  52. # _info "Modified, OK"
  53. # return 0
  54. # fi
  55. #fi
  56. #_err "Add txt record error (modify)."
  57. #return 1
  58. fi
  59. }
  60. # Usage: full_domain txt_value
  61. # Used to remove the txt record after validation
  62. dns_hetzner_rm() {
  63. full_domain=$1
  64. txt_value=$2
  65. HETZNER_Token="${HETZNER_Token:-$(_readaccountconf_mutable HETZNER_Token)}"
  66. _debug "First detect the root zone"
  67. if ! _get_root "$full_domain"; then
  68. _err "Invalid domain"
  69. return 1
  70. fi
  71. _debug _domain_id "$_domain_id"
  72. _debug _sub_domain "$_sub_domain"
  73. _debug _domain "$_domain"
  74. _debug "Getting TXT records"
  75. if ! _find_record "$_sub_domain" "$txt_value"; then
  76. return 1
  77. fi
  78. if [ -z "$_record_id" ]; then
  79. _info "Remove not needed. Record not found."
  80. else
  81. if ! _hetzner_rest DELETE "records/$_record_id"; then
  82. _err "Delete record error${_response_error}"
  83. return 1
  84. fi
  85. _sleep 2
  86. _info "Record deleted"
  87. fi
  88. }
  89. #################### Private functions below ##################################
  90. #returns
  91. # _record_id=a8d58f22d6931bf830eaa0ec6464bf81 if found; or 1 if error
  92. _find_record() {
  93. unset _record_id
  94. _record_name=$1
  95. _record_value=$2
  96. if [ -z "$_record_value" ]; then
  97. _record_value='[^"]*'
  98. fi
  99. _debug "Getting all records"
  100. _hetzner_rest GET "records?zone_id=${_domain_id}"
  101. if _response_has_error; then
  102. _err "Error${_response_error}"
  103. return 1
  104. else
  105. _record_id=$(
  106. echo "$response" |
  107. grep -o "{[^\{\}]*\"name\":\"$_record_name\"[^\}]*}" |
  108. grep "\"value\":\"$_record_value\"" |
  109. while read -r record; do
  110. # test for type and
  111. if [ -n "$(echo "$record" | _egrep_o '"type":"TXT"')" ]; then
  112. echo "$record" | _egrep_o '"id":"[^"]*"' | cut -d : -f 2 | tr -d \"
  113. break
  114. fi
  115. done
  116. )
  117. fi
  118. }
  119. #_acme-challenge.www.domain.com
  120. #returns
  121. # _sub_domain=_acme-challenge.www
  122. # _domain=domain.com
  123. # _domain_id=sdjkglgdfewsdfg
  124. _get_root() {
  125. domain=$1
  126. i=1
  127. p=1
  128. domain_without_acme=$(echo "$domain" | cut -d . -f 2-)
  129. domain_param_name=$(echo "HETZNER_Zone_ID_for_${domain_without_acme}" | sed 's/[\.\-]/_/g')
  130. _debug "Reading zone_id for '$domain_without_acme' from config..."
  131. HETZNER_Zone_ID=$(_readdomainconf "$domain_param_name")
  132. if [ "$HETZNER_Zone_ID" ]; then
  133. _debug "Found, using: $HETZNER_Zone_ID"
  134. if ! _hetzner_rest GET "zones/${HETZNER_Zone_ID}"; then
  135. _debug "Zone with id '$HETZNER_Zone_ID' does not exist."
  136. _cleardomainconf "$domain_param_name"
  137. unset HETZNER_Zone_ID
  138. else
  139. if _contains "$response" "\"id\":\"$HETZNER_Zone_ID\""; then
  140. _domain=$(printf "%s\n" "$response" | _egrep_o '"name":"[^"]*"' | cut -d : -f 2 | tr -d \" | head -n 1)
  141. if [ "$_domain" ]; then
  142. _cut_length=$((${#domain} - ${#_domain} - 1))
  143. _sub_domain=$(printf "%s" "$domain" | cut -c "1-$_cut_length")
  144. _domain_id="$HETZNER_Zone_ID"
  145. return 0
  146. else
  147. return 1
  148. fi
  149. else
  150. return 1
  151. fi
  152. fi
  153. fi
  154. _debug "Trying to get zone id by domain name for '$domain_without_acme'."
  155. while true; do
  156. h=$(printf "%s" "$domain" | cut -d . -f $i-100)
  157. if [ -z "$h" ]; then
  158. #not valid
  159. return 1
  160. fi
  161. _debug h "$h"
  162. _hetzner_rest GET "zones?name=$h"
  163. if _contains "$response" "\"name\":\"$h\"" || _contains "$response" '"total_entries":1'; then
  164. _domain_id=$(echo "$response" | _egrep_o "\[.\"id\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \")
  165. if [ "$_domain_id" ]; then
  166. _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
  167. _domain=$h
  168. HETZNER_Zone_ID=$_domain_id
  169. _savedomainconf "$domain_param_name" "$HETZNER_Zone_ID"
  170. return 0
  171. fi
  172. return 1
  173. fi
  174. p=$i
  175. i=$(_math "$i" + 1)
  176. done
  177. return 1
  178. }
  179. #returns
  180. # _response_error
  181. _response_has_error() {
  182. unset _response_error
  183. err_part="$(echo "$response" | _egrep_o '"error":{[^}]*}')"
  184. if [ -n "$err_part" ]; then
  185. err_code=$(echo "$err_part" | _egrep_o '"code":[0-9]+' | cut -d : -f 2)
  186. err_message=$(echo "$err_part" | _egrep_o '"message":"[^"]+"' | cut -d : -f 2 | tr -d \")
  187. if [ -n "$err_code" ] && [ -n "$err_message" ]; then
  188. _response_error=" - message: ${err_message}, code: ${err_code}"
  189. return 0
  190. fi
  191. fi
  192. return 1
  193. }
  194. #returns
  195. # response
  196. _hetzner_rest() {
  197. m=$1
  198. ep="$2"
  199. data="$3"
  200. _debug "$ep"
  201. key_trimmed=$(echo "$HETZNER_Token" | tr -d \")
  202. export _H1="Content-TType: application/json"
  203. export _H2="Auth-API-Token: $key_trimmed"
  204. if [ "$m" != "GET" ]; then
  205. _debug data "$data"
  206. response="$(_post "$data" "$HETZNER_Api/$ep" "" "$m")"
  207. else
  208. response="$(_get "$HETZNER_Api/$ep")"
  209. fi
  210. if [ "$?" != "0" ] || _response_has_error; then
  211. _debug "Error$_response_error"
  212. return 1
  213. fi
  214. _debug2 response "$response"
  215. return 0
  216. }