From 0d53d29f7efa6b40c6193c1b9b338da180cd24f7 Mon Sep 17 00:00:00 2001 From: hostup <52465293+hostup@users.noreply.github.com> Date: Wed, 1 Jul 2026 14:44:21 +0200 Subject: [PATCH] Update dns_hostup.sh to v2 API (#7014) * Update dns_hostup.sh Update to v2 api support; developer.hostup.se * Update dns_hostup.sh --- dnsapi/dns_hostup.sh | 326 ++++++++++++++++++++++++++----------------- 1 file changed, 201 insertions(+), 125 deletions(-) diff --git a/dnsapi/dns_hostup.sh b/dnsapi/dns_hostup.sh index b3211069..73189189 100644 --- a/dnsapi/dns_hostup.sh +++ b/dnsapi/dns_hostup.sh @@ -6,13 +6,13 @@ Site: hostup.se Docs: https://developer.hostup.se/ Options: HOSTUP_API_KEY Required. HostUp API key with read:dns + write:dns + read:domains scopes. - HOSTUP_API_BASE Optional. Override API base URL (default: https://cloud.hostup.se/api). + HOSTUP_API_BASE Optional. Override API base URL (default: https://cloud.hostup.se/api/v2). HOSTUP_TTL Optional. TTL for TXT records (default: 60 seconds). - HOSTUP_ZONE_ID Optional. Force a specific zone ID (skip auto-detection). + HOSTUP_ZONE_ID Optional. Force a specific v2 zone ID (zone_...) and skip auto-detection. Author: HostUp (https://cloud.hostup.se/contact/en) ' -HOSTUP_API_BASE_DEFAULT="https://cloud.hostup.se/api" +HOSTUP_API_BASE_DEFAULT="https://cloud.hostup.se/api/v2" HOSTUP_DEFAULT_TTL=60 # Public: add TXT record @@ -20,6 +20,7 @@ HOSTUP_DEFAULT_TTL=60 dns_hostup_add() { fulldomain="$1" txtvalue="$2" + hostup_add_txtvalue="$2" _info "Using HostUp DNS API" @@ -34,31 +35,34 @@ dns_hostup_add() { record_name="$(_hostup_record_name "$fulldomain" "$HOSTUP_ZONE_DOMAIN")" record_name="$(_hostup_sanitize_name "$record_name")" - record_value="$(_hostup_json_escape "$txtvalue")" + hostup_add_record_value="$(_hostup_json_escape "$hostup_add_txtvalue")" - ttl="${HOSTUP_TTL:-$HOSTUP_DEFAULT_TTL}" + raw_ttl="${HOSTUP_TTL:-$HOSTUP_DEFAULT_TTL}" + ttl="$(_hostup_normalize_ttl "$raw_ttl")" + if [ -z "$ttl" ]; then + _err "HOSTUP_TTL must be a whole number between 60 and 86400 seconds." + return 1 + fi + if [ -n "$HOSTUP_TTL" ]; then + HOSTUP_TTL="$ttl" + _saveaccountconf_mutable HOSTUP_TTL "$HOSTUP_TTL" + fi _debug "zone_id" "$HOSTUP_ZONE_ID" _debug "zone_domain" "$HOSTUP_ZONE_DOMAIN" _debug "record_name" "$record_name" _debug "ttl" "$ttl" - request_body="{\"name\":\"$record_name\",\"type\":\"TXT\",\"value\":\"$record_value\",\"ttl\":$ttl}" - - if ! _hostup_rest "POST" "/dns/zones/$HOSTUP_ZONE_ID/records" "$request_body"; then - return 1 + record_name_fqdn="$(_hostup_fqdn "$fulldomain")" + if _hostup_find_record "$HOSTUP_ZONE_ID" "$record_name_fqdn" "$hostup_add_txtvalue"; then + _info "TXT record already exists for $fulldomain" + return 0 fi - if ! _contains "$_hostup_response" '"success":true'; then - _err "HostUp DNS API: failed to create TXT record for $fulldomain" - _debug2 "_hostup_response" "$_hostup_response" - return 1 - fi + request_body="{\"name\":\"$record_name\",\"type\":\"TXT\",\"value\":\"$hostup_add_record_value\",\"ttl\":$ttl}" - record_id="$(_hostup_extract_record_id "$_hostup_response")" - if [ -n "$record_id" ]; then - _hostup_save_record_id "$HOSTUP_ZONE_ID" "$fulldomain" "$record_id" - _debug "hostup_saved_record_id" "$record_id" + if ! _hostup_rest "POST" "/dns-zones/$HOSTUP_ZONE_ID/records" "$request_body"; then + return 1 fi _info "Added TXT record for $fulldomain" @@ -85,20 +89,9 @@ dns_hostup_rm() { record_name_fqdn="$(_hostup_fqdn "$fulldomain")" record_value="$txtvalue" - record_id_cached="$(_hostup_get_saved_record_id "$HOSTUP_ZONE_ID" "$fulldomain")" - if [ -n "$record_id_cached" ]; then - _debug "hostup_record_id_cached" "$record_id_cached" - if _hostup_delete_record_by_id "$HOSTUP_ZONE_ID" "$record_id_cached"; then - _info "Deleted TXT record $record_id_cached" - _hostup_clear_record_id "$HOSTUP_ZONE_ID" "$fulldomain" - HOSTUP_ZONE_ID="" - return 0 - fi - fi - if ! _hostup_find_record "$HOSTUP_ZONE_ID" "$record_name_fqdn" "$record_value"; then _info "TXT record not found for $record_name_fqdn. Skipping removal." - _hostup_clear_record_id "$HOSTUP_ZONE_ID" "$fulldomain" + _hostup_clear_record_id "$HOSTUP_ZONE_ID" "$fulldomain" "$record_value" return 0 fi @@ -109,7 +102,7 @@ dns_hostup_rm() { fi _info "Deleted TXT record $HOSTUP_RECORD_ID" - _hostup_clear_record_id "$HOSTUP_ZONE_ID" "$fulldomain" + _hostup_clear_record_id "$HOSTUP_ZONE_ID" "$fulldomain" "$record_value" HOSTUP_ZONE_ID="" return 0 } @@ -127,21 +120,18 @@ _hostup_init() { if [ -z "$HOSTUP_API_BASE" ]; then HOSTUP_API_BASE="$HOSTUP_API_BASE_DEFAULT" fi + HOSTUP_API_BASE="$(_hostup_normalize_api_base "$HOSTUP_API_BASE")" if [ -z "$HOSTUP_API_KEY" ]; then HOSTUP_API_KEY="" _err "HOSTUP_API_KEY is not set." - _err "Please export your HostUp API key with read:dns and write:dns scopes." + _err "Please export your HostUp API key with read:dns, write:dns, and read:domains scopes." return 1 fi _saveaccountconf_mutable HOSTUP_API_KEY "$HOSTUP_API_KEY" _saveaccountconf_mutable HOSTUP_API_BASE "$HOSTUP_API_BASE" - if [ -n "$HOSTUP_TTL" ]; then - _saveaccountconf_mutable HOSTUP_TTL "$HOSTUP_TTL" - fi - if [ -n "$HOSTUP_ZONE_ID" ]; then _saveaccountconf_mutable HOSTUP_ZONE_ID "$HOSTUP_ZONE_ID" fi @@ -149,11 +139,80 @@ _hostup_init() { return 0 } +_hostup_normalize_api_base() { + api_base="${1%/}" + + case "$api_base" in + */api/v2) + printf "%s" "$api_base" + ;; + */api) + printf "%s/v2" "$api_base" + ;; + *) + printf "%s" "$api_base" + ;; + esac +} + +_hostup_normalize_ttl() { + ttl_value="$1" + + case "$ttl_value" in + "" | *[!0-9]*) + return 1 + ;; + esac + + while [ "${ttl_value#0}" != "$ttl_value" ]; do + ttl_value="${ttl_value#0}" + done + [ -z "$ttl_value" ] && ttl_value=0 + + case "$ttl_value" in + ??????*) + return 1 + ;; + esac + + if [ "$ttl_value" -lt 60 ] || [ "$ttl_value" -gt 86400 ]; then + return 1 + fi + + printf "%s" "$ttl_value" +} + +_hostup_domain_in_zone() { + host="$(printf "%s" "${1%.}" | _lower_case)" + zone="$(printf "%s" "${2%.}" | _lower_case)" + + if [ -z "$host" ] || [ -z "$zone" ]; then + return 1 + fi + + if [ "$host" = "$zone" ]; then + return 0 + fi + + case "$host" in + *."$zone") + return 0 + ;; + esac + + return 1 +} + _hostup_detect_zone() { fulldomain="$1" if [ -n "$HOSTUP_ZONE_ID" ] && [ -n "$HOSTUP_ZONE_DOMAIN" ]; then - return 0 + if _hostup_domain_in_zone "$fulldomain" "$HOSTUP_ZONE_DOMAIN"; then + return 0 + fi + _debug "hostup_cached_zone_mismatch" "$HOSTUP_ZONE_DOMAIN" + HOSTUP_ZONE_ID="" + HOSTUP_ZONE_DOMAIN="" fi HOSTUP_ZONE_DOMAIN="" @@ -162,16 +221,16 @@ _hostup_detect_zone() { if [ -n "$HOSTUP_ZONE_ID" ] && [ -z "$HOSTUP_ZONE_DOMAIN" ]; then # Attempt to fetch domain name for provided zone ID if _hostup_fetch_zone_details "$HOSTUP_ZONE_ID"; then - return 0 + if _hostup_domain_in_zone "$fulldomain" "$HOSTUP_ZONE_DOMAIN"; then + return 0 + fi + _debug "hostup_forced_zone_mismatch" "$HOSTUP_ZONE_DOMAIN" fi HOSTUP_ZONE_ID="" + HOSTUP_ZONE_DOMAIN="" fi - if ! _hostup_load_zones; then - return 1 - fi - - _domain_candidate="$(printf "%s" "$fulldomain" | _lower_case)" + _domain_candidate="$(printf "%s" "${fulldomain%.}" | _lower_case)" _debug "hostup_initial_candidate" "$_domain_candidate" while [ -n "$_domain_candidate" ]; do @@ -240,11 +299,11 @@ _hostup_fqdn() { _hostup_fetch_zone_details() { zone_id="$1" - if ! _hostup_rest "GET" "/dns/zones/$zone_id/records" ""; then + if ! _hostup_rest "GET" "/dns-zones/$zone_id/records" ""; then return 1 fi - zonedomain="$(printf "%s" "$_hostup_response" | _egrep_o '"domain":"[^"]*"' | sed -n '1p' | cut -d ':' -f 2 | tr -d '"')" + zonedomain="$(_hostup_json_extract "name" "$_hostup_response")" if [ -n "$zonedomain" ]; then HOSTUP_ZONE_DOMAIN="$zonedomain" return 0 @@ -254,7 +313,7 @@ _hostup_fetch_zone_details() { } _hostup_load_zones() { - if ! _hostup_rest "GET" "/dns/zones" ""; then + if ! _hostup_rest "GET" "/dns-zones?limit=1000" ""; then return 1 fi @@ -263,9 +322,9 @@ _hostup_load_zones() { while IFS= read -r line; do case "$line" in - *'"domain_id"'*'"domain"'*) - zone_id="$(printf "%s" "$line" | _hostup_json_extract "domain_id")" - zone_domain="$(printf "%s" "$line" | _hostup_json_extract "domain")" + *'"id"'*'"name"'*) + zone_id="$(_hostup_json_extract "id" "$line")" + zone_domain="$(_hostup_json_extract "name" "$line")" if [ -n "$zone_id" ] && [ -n "$zone_domain" ]; then HOSTUP_ZONES_CACHE="${HOSTUP_ZONES_CACHE}${zone_domain}|${zone_id} " @@ -290,9 +349,30 @@ _hostup_lookup_zone() { _lookup_zone_id="" _lookup_zone_domain="" + encoded_domain="$(printf "%s" "$lookup_domain" | _url_encode)" + if _hostup_rest "GET" "/dns-zones?name=$encoded_domain&limit=1" ""; then + zone_id="$(_hostup_json_extract "id" "$_hostup_response")" + zone_domain="$(_hostup_json_extract "name" "$_hostup_response")" + if [ -n "$zone_id" ] && [ -n "$zone_domain" ]; then + zone_domain_lower="$(printf "%s" "$zone_domain" | _lower_case)" + if [ "$zone_domain_lower" = "$lookup_domain" ]; then + _lookup_zone_domain="$zone_domain" + _lookup_zone_id="$zone_id" + HOSTUP_ZONE_DOMAIN="$zone_domain" + HOSTUP_ZONE_ID="$zone_id" + return 0 + fi + fi + fi + + if [ -z "$HOSTUP_ZONES_CACHE" ] && ! _hostup_load_zones; then + return 1 + fi + while IFS='|' read -r domain zone_id; do [ -z "$domain" ] && continue - if [ "$domain" = "$lookup_domain" ]; then + domain_lower="$(printf "%s" "$domain" | _lower_case)" + if [ "$domain_lower" = "$lookup_domain" ]; then _lookup_zone_domain="$domain" _lookup_zone_id="$zone_id" HOSTUP_ZONE_DOMAIN="$domain" @@ -307,50 +387,50 @@ EOF } _hostup_find_record() { - zone_id="$1" - fqdn="$2" - txtvalue="$3" + _hostup_find_zone_id="$1" + _hostup_find_fqdn="$2" + _hostup_find_txtvalue="$3" - if ! _hostup_rest "GET" "/dns/zones/$zone_id/records" ""; then + _hostup_find_encoded_name="$(printf "%s" "$_hostup_find_fqdn" | _url_encode)" + if ! _hostup_rest "GET" "/dns-zones/$_hostup_find_zone_id/records?type=TXT&name=$_hostup_find_encoded_name" ""; then return 1 fi HOSTUP_RECORD_ID="" - records="$(printf "%s" "$_hostup_response" | tr '{' '\n')" + _hostup_find_records="$(printf "%s" "$_hostup_response" | tr '{' '\n')" - while IFS= read -r line; do + while IFS= read -r _hostup_find_line; do # Normalize line to make TXT value matching reliable - line_clean="$(printf "%s" "$line" | tr -d '\r\n')" - line_value_clean="$(printf "%s" "$line_clean" | sed 's/\\"//g')" + _hostup_find_line_clean="$(printf "%s" "$_hostup_find_line" | tr -d '\r\n')" + _hostup_find_line_value_clean="$(printf "%s" "$_hostup_find_line_clean" | sed 's/\\"//g')" - case "$line_clean" in - *'"type":"TXT"'*'"name"'*'"value"'*) - name_value="$(_hostup_json_extract "name" "$line_clean")" - record_value="$(_hostup_json_extract "value" "$line_value_clean")" + _hostup_find_record_type="$(_hostup_json_extract "type" "$_hostup_find_line_clean")" + [ "$_hostup_find_record_type" != "TXT" ] && continue - _debug "hostup_record_raw" "$record_value" - if [ "${record_value#\"}" != "$record_value" ] && [ "${record_value%\"}" != "$record_value" ]; then - record_value="${record_value#\"}" - record_value="${record_value%\"}" - fi - if [ "${record_value#\'}" != "$record_value" ] && [ "${record_value%\'}" != "$record_value" ]; then - record_value="${record_value#\'}" - record_value="${record_value%\'}" - fi - record_value="$(printf "%s" "$record_value" | tr -d '\r\n')" - _debug "hostup_record_value" "$record_value" + _hostup_find_name_value="$(_hostup_json_extract "name" "$_hostup_find_line_clean")" + _hostup_find_record_value="$(_hostup_json_extract "value" "$_hostup_find_line_value_clean")" - if [ "$name_value" = "$fqdn" ] && [ "$record_value" = "$txtvalue" ]; then - record_id="$(_hostup_json_extract "id" "$line_clean")" - if [ -n "$record_id" ]; then - HOSTUP_RECORD_ID="$record_id" - return 0 - fi + _debug "hostup_record_raw" "$_hostup_find_record_value" + if [ "${_hostup_find_record_value#\"}" != "$_hostup_find_record_value" ] && [ "${_hostup_find_record_value%\"}" != "$_hostup_find_record_value" ]; then + _hostup_find_record_value="${_hostup_find_record_value#\"}" + _hostup_find_record_value="${_hostup_find_record_value%\"}" + fi + if [ "${_hostup_find_record_value#\'}" != "$_hostup_find_record_value" ] && [ "${_hostup_find_record_value%\'}" != "$_hostup_find_record_value" ]; then + _hostup_find_record_value="${_hostup_find_record_value#\'}" + _hostup_find_record_value="${_hostup_find_record_value%\'}" + fi + _hostup_find_record_value="$(printf "%s" "$_hostup_find_record_value" | tr -d '\r\n')" + _debug "hostup_record_value" "$_hostup_find_record_value" + + if [ "$_hostup_find_name_value" = "$_hostup_find_fqdn" ] && [ "$_hostup_find_record_value" = "$_hostup_find_txtvalue" ]; then + _hostup_find_record_id="$(_hostup_json_extract "id" "$_hostup_find_line_clean")" + if [ -n "$_hostup_find_record_id" ]; then + HOSTUP_RECORD_ID="$_hostup_find_record_id" + return 0 fi - ;; - esac + fi done <