#!/usr/bin/env sh # shellcheck disable=SC2034,SC2154 dns_hostup_info='HostUp DNS Site: hostup.se Docs: https://hostup.se/en/support/api-autentisering/ 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_TTL Optional. TTL for TXT records (default: 60 seconds). HOSTUP_ZONE_ID Optional. Force a specific zone ID (skip auto-detection). Author: HostUp (https://cloud.hostup.se/contact/en) ' HOSTUP_API_BASE_DEFAULT="https://cloud.hostup.se/api" HOSTUP_DEFAULT_TTL=60 # Public: add TXT record # Usage: dns_hostup_add _acme-challenge.example.com "txt-value" dns_hostup_add() { fulldomain="$1" txtvalue="$2" _info "Using HostUp DNS API" if ! _hostup_init; then return 1 fi if ! _hostup_detect_zone "$fulldomain"; then _err "Unable to determine HostUp zone for $fulldomain" return 1 fi record_name="$(_hostup_record_name "$fulldomain" "$HOSTUP_ZONE_DOMAIN")" record_name="$(_hostup_sanitize_name "$record_name")" record_value="$(_hostup_json_escape "$txtvalue")" ttl="${HOSTUP_TTL:-$HOSTUP_DEFAULT_TTL}" _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 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 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" fi _info "Added TXT record for $fulldomain" return 0 } # Public: remove TXT record # Usage: dns_hostup_rm _acme-challenge.example.com "txt-value" dns_hostup_rm() { fulldomain="$1" txtvalue="$2" _info "Using HostUp DNS API" if ! _hostup_init; then return 1 fi if ! _hostup_detect_zone "$fulldomain"; then _err "Unable to determine HostUp zone for $fulldomain" return 1 fi 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" return 0 fi _debug "Deleting record" "$HOSTUP_RECORD_ID" if ! _hostup_delete_record_by_id "$HOSTUP_ZONE_ID" "$HOSTUP_RECORD_ID"; then return 1 fi _info "Deleted TXT record $HOSTUP_RECORD_ID" _hostup_clear_record_id "$HOSTUP_ZONE_ID" "$fulldomain" HOSTUP_ZONE_ID="" return 0 } ########################## # Private helper methods # ########################## _hostup_init() { HOSTUP_API_KEY="${HOSTUP_API_KEY:-$(_readaccountconf_mutable HOSTUP_API_KEY)}" HOSTUP_API_BASE="${HOSTUP_API_BASE:-$(_readaccountconf_mutable HOSTUP_API_BASE)}" HOSTUP_TTL="${HOSTUP_TTL:-$(_readaccountconf_mutable HOSTUP_TTL)}" HOSTUP_ZONE_ID="${HOSTUP_ZONE_ID:-$(_readaccountconf_mutable HOSTUP_ZONE_ID)}" if [ -z "$HOSTUP_API_BASE" ]; then HOSTUP_API_BASE="$HOSTUP_API_BASE_DEFAULT" fi 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." 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 return 0 } _hostup_detect_zone() { fulldomain="$1" if [ -n "$HOSTUP_ZONE_ID" ] && [ -n "$HOSTUP_ZONE_DOMAIN" ]; then return 0 fi HOSTUP_ZONE_DOMAIN="" _debug "hostup_full_domain" "$fulldomain" 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 fi HOSTUP_ZONE_ID="" fi if ! _hostup_load_zones; then return 1 fi _domain_candidate="$(printf "%s" "$fulldomain" | tr '[:upper:]' '[:lower:]')" _debug "hostup_initial_candidate" "$_domain_candidate" while [ -n "$_domain_candidate" ]; do _debug "hostup_zone_candidate" "$_domain_candidate" if _hostup_lookup_zone "$_domain_candidate"; then HOSTUP_ZONE_DOMAIN="$_lookup_zone_domain" HOSTUP_ZONE_ID="$_lookup_zone_id" return 0 fi case "$_domain_candidate" in *.*) ;; *) break ;; esac _domain_candidate="${_domain_candidate#*.}" done HOSTUP_ZONE_ID="" return 1 } _hostup_record_name() { fulldomain="$1" zonedomain="$2" # Remove trailing dot, if any fulldomain="${fulldomain%.}" zonedomain="${zonedomain%.}" if [ "$fulldomain" = "$zonedomain" ]; then printf "%s" "@" return 0 fi suffix=".$zonedomain" case "$fulldomain" in *"$suffix") printf "%s" "${fulldomain%"$suffix"}" ;; *) # Domain not within zone, fall back to full host printf "%s" "$fulldomain" ;; esac } _hostup_sanitize_name() { name="$1" if [ -z "$name" ] || [ "$name" = "." ]; then printf "%s" "@" return 0 fi # Remove any trailing dot name="${name%.}" printf "%s" "$name" } _hostup_fqdn() { domain="$1" printf "%s" "${domain%.}" } _hostup_fetch_zone_details() { zone_id="$1" 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 '"')" if [ -n "$zonedomain" ]; then HOSTUP_ZONE_DOMAIN="$zonedomain" return 0 fi return 1 } _hostup_load_zones() { if ! _hostup_rest "GET" "/dns/zones" ""; then return 1 fi HOSTUP_ZONES_CACHE="" data="$(printf "%s" "$_hostup_response" | tr '{' '\n')" 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")" if [ -n "$zone_id" ] && [ -n "$zone_domain" ]; then HOSTUP_ZONES_CACHE="${HOSTUP_ZONES_CACHE}${zone_domain}|${zone_id} " _debug "hostup_zone_loaded" "$zone_domain|$zone_id" fi ;; esac done <