diff --git a/dnsapi/dns_beget.sh b/dnsapi/dns_beget.sh index 5f3b1eb1..2838e5f3 100755 --- a/dnsapi/dns_beget.sh +++ b/dnsapi/dns_beget.sh @@ -12,270 +12,119 @@ Author: ARNik Beget_Api="https://api.beget.com/api" -#################### Public functions #################### - -# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -# Used to add txt record -dns_beget_add() { - fulldomain=$1 - txtvalue=$2 - _debug "dns_beget_add() $fulldomain $txtvalue" - fulldomain=$(echo "$fulldomain" | _lower_case) - - Beget_Username="${Beget_Username:-$(_readaccountconf_mutable Beget_Username)}" - Beget_Password="${Beget_Password:-$(_readaccountconf_mutable Beget_Password)}" - - if [ -z "$Beget_Username" ] || [ -z "$Beget_Password" ]; then - Beget_Username="" - Beget_Password="" - _err "You must export variables: Beget_Username, and Beget_Password" - return 1 - fi - - #save the credentials to the account conf file. - _saveaccountconf_mutable Beget_Username "$Beget_Username" - _saveaccountconf_mutable Beget_Password "$Beget_Password" - - _info "Prepare subdomain." - if ! _prepare_subdomain "$fulldomain"; then - _err "Can't prepare subdomain." - return 1 - fi - - _info "Get domain records" - data="{\"fqdn\":\"$fulldomain\"}" - res=$(_api_call "$Beget_Api/dns/getData" "$data") - if ! _is_api_reply_ok "$res"; then - _err "Can't get domain records." - return 1 - fi - - _info "Add new TXT record" - data="{\"fqdn\":\"$fulldomain\",\"records\":{" - data=${data}$(_parce_records "$res" "A") - data=${data}$(_parce_records "$res" "AAAA") - data=${data}$(_parce_records "$res" "CAA") - data=${data}$(_parce_records "$res" "MX") - data=${data}$(_parce_records "$res" "SRV") - data=${data}$(_parce_records "$res" "TXT") - data=$(echo "$data" | sed 's/,$//') - data=${data}'}}' - - str=$(_txt_to_dns_json "$txtvalue") - data=$(_add_record "$data" "TXT" "$str") - - res=$(_api_call "$Beget_Api/dns/changeRecords" "$data") - if ! _is_api_reply_ok "$res"; then - _err "Can't change domain records." - return 1 - fi +# API call function +_api_call() { + api_url="$1" + input_data="$2" + url="$api_url?login=$Beget_Username&passwd=$Beget_Password&input_format=json&output_format=json" + [ -n "$input_data" ] && url="${url}&input_data=$(echo -n "$input_data" | jq -s -R -r @uri)" - return 0 + echo "[DEBUG] _api_call url=$url" + curl -s "$url" } -# Usage: fulldomain txtvalue -# Used to remove the txt record after validation -dns_beget_rm() { +# Add TXT record (supports multiple additions without overwriting) +dns_beget_add() { fulldomain=$1 txtvalue=$2 - _debug "dns_beget_rm() $fulldomain $txtvalue" - fulldomain=$(echo "$fulldomain" | _lower_case) - - Beget_Username="${Beget_Username:-$(_readaccountconf_mutable Beget_Username)}" - Beget_Password="${Beget_Password:-$(_readaccountconf_mutable Beget_Password)}" - - _info "Get current domain records" - data="{\"fqdn\":\"$fulldomain\"}" - res=$(_api_call "$Beget_Api/dns/getData" "$data") - if ! _is_api_reply_ok "$res"; then - _err "Can't get domain records." - return 1 - fi - _info "Remove TXT record" - data="{\"fqdn\":\"$fulldomain\",\"records\":{" - data=${data}$(_parce_records "$res" "A") - data=${data}$(_parce_records "$res" "AAAA") - data=${data}$(_parce_records "$res" "CAA") - data=${data}$(_parce_records "$res" "MX") - data=${data}$(_parce_records "$res" "SRV") - data=${data}$(_parce_records "$res" "TXT") - data=$(echo "$data" | sed 's/,$//') - data=${data}'}}' + echo "[DEBUG] Starting dns_beget_add" + echo "[DEBUG] fulldomain=$fulldomain" + echo "[DEBUG] txtvalue=$txtvalue" - str=$(_txt_to_dns_json "$txtvalue") - data=$(_rm_record "$data" "$str") + Beget_Username="${Beget_Username:?Please set Beget_Username}" + Beget_Password="${Beget_Password:?Please set Beget_Password}" - res=$(_api_call "$Beget_Api/dns/changeRecords" "$data") - if ! _is_api_reply_ok "$res"; then - _err "Can't change domain records." - return 1 - fi - - return 0 -} - -#################### Private functions below #################### + fulldomain=$(echo "$fulldomain" | tr '[:upper:]' '[:lower:]') + echo "[DEBUG] fulldomain (lowercase)=$fulldomain" -# Create subdomain if needed -# Usage: _prepare_subdomain [fulldomain] -_prepare_subdomain() { - fulldomain=$1 - - _info "Detect the root zone" - if ! _get_root "$fulldomain"; then - _err "invalid domain" + # Get current DNS records + res=$(_api_call "$Beget_Api/dns/getData" "{\"fqdn\":\"$fulldomain\"}") || { + echo "[ERROR] API getData did not return a response" return 1 - fi - _debug _domain_id "$_domain_id" - _debug _sub_domain "$_sub_domain" - _debug _domain "$_domain" - - if [ -z "$_sub_domain" ]; then - _debug "$fulldomain is a root domain." - return 0 - fi + } + echo "[DEBUG] API getData response: $res" + + status=$(echo "$res" | jq -r '.answer.status' 2>/dev/null || echo "error") + + if [ "$status" = "success" ]; then + old_txts=$(echo "$res" | jq -c '.answer.result.records.TXT // []') + echo "[DEBUG] Existing TXT records from API: $old_txts" + else + echo "[WARN] Beget API error (status=$status). Try fallback with dig polling." + + old_txts="[]" + i=1 + while [ $i -le 6 ]; do # 6 раз по 20 секунд = максимум 120 + dig_txts=$(dig TXT +short "${fulldomain}" \ + @ns1.beget.com @ns2.beget.com | sed 's/^"//;s/"$//' | jq -R . | jq -s .) + + if [ "$dig_txts" != "[]" ]; then + old_txts="$dig_txts" + echo "[DEBUG] dig found TXT records on attempt $i: $old_txts" + break + else + echo "[DEBUG] dig attempt $i: no TXT yet, waiting 20s..." + if [ $i -gt 3 ]; then + sleep 40 + else + sleep 20 + fi + fi - _info "Get subdomain list" - res=$(_api_call "$Beget_Api/domain/getSubdomainList") - if ! _is_api_reply_ok "$res"; then - _err "Can't get subdomain list." - return 1 - fi + i=$((i+1)) + done - if _contains "$res" "\"fqdn\":\"$fulldomain\""; then - _debug "Subdomain $fulldomain already exist." - return 0 + if [ "$old_txts" = "[]" ]; then + echo "[DEBUG] dig found no TXT records after 120s. old_txts empty." + fi +fi + + # Prepare new TXT record + new_txt="{\"priority\":10,\"value\":\"$txtvalue\"}" + echo "[DEBUG] New TXT record: $new_txt" + + # Merge with existing TXT records + if [ "$old_txts" = "[]" ]; then + txt_records="[$new_txt]" + else + old_objs=$(jq -c --argjson p 10 '[.[] | {priority: ($p|tonumber), value: .}]' <<< "$old_txts") + txt_records=$(jq -c --argjson new "$new_txt" '. + [$new]' <<< "$old_objs") fi + echo "[DEBUG] Final TXT records set: $txt_records" + + data="{\"fqdn\":\"$fulldomain\",\"records\":{\"TXT\":$txt_records}}" + echo "[DEBUG] Sending data to changeRecords: $data" - _info "Subdomain $fulldomain does not exist. Let's create one." - data="{\"subdomain\":\"$_sub_domain\",\"domain_id\":$_domain_id}" - res=$(_api_call "$Beget_Api/domain/addSubdomainVirtual" "$data") - if ! _is_api_reply_ok "$res"; then - _err "Can't create subdomain." + _api_call "$Beget_Api/dns/changeRecords" "$data" || { + echo "[ERROR] Error calling changeRecords" return 1 - fi - - _debug "Cleanup subdomen records" - data="{\"fqdn\":\"$fulldomain\",\"records\":{}}" - res=$(_api_call "$Beget_Api/dns/changeRecords" "$data") - if ! _is_api_reply_ok "$res"; then - _debug "Can't cleanup $fulldomain records." - fi - - data="{\"fqdn\":\"www.$fulldomain\",\"records\":{}}" - res=$(_api_call "$Beget_Api/dns/changeRecords" "$data") - if ! _is_api_reply_ok "$res"; then - _debug "Can't cleanup www.$fulldomain records." - fi + } - return 0 + echo "[INFO] TXT record successfully added for $fulldomain" } -# Usage: _get_root _acme-challenge.www.domain.com -#returns -# _sub_domain=_acme-challenge.www -# _domain=domain.com -# _domain_id=32436365 -_get_root() { +# Remove all _acme-challenge TXT records +dns_beget_rm() { fulldomain=$1 - i=1 - p=1 - _debug "Get domain list" - res=$(_api_call "$Beget_Api/domain/getList") - if ! _is_api_reply_ok "$res"; then - _err "Can't get domain list." - return 1 - fi + echo "[DEBUG] Starting dns_beget_rm" + echo "[DEBUG] fulldomain=$fulldomain" - while true; do - h=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-100) - _debug h "$h" + Beget_Username="${Beget_Username:?Please set Beget_Username}" + Beget_Password="${Beget_Password:?Please set Beget_Password}" - if [ -z "$h" ]; then - return 1 - fi + fulldomain=$(echo "$fulldomain" | tr '[:upper:]' '[:lower:]') + echo "[DEBUG] fulldomain (lowercase)=$fulldomain" - if _contains "$res" "$h"; then - _domain_id=$(echo "$res" | _egrep_o "\"id\":[0-9]*,\"fqdn\":\"$h\"" | cut -d , -f1 | cut -d : -f2) - if [ "$_domain_id" ]; then - if [ "$h" != "$fulldomain" ]; then - _sub_domain=$(echo "$fulldomain" | cut -d . -f 1-"$p") - else - _sub_domain="" - fi - _domain=$h - return 0 - fi - return 1 - fi - p="$i" - i=$(_math "$i" + 1) - done - return 1 -} + # Remove all TXT records by sending empty array + data="{\"fqdn\":\"$fulldomain\",\"records\":{\"TXT\": []}}" + echo "[DEBUG] Sending data to changeRecords: $data" -# Parce DNS records from json string -# Usage: _parce_records [j_str] [record_name] -_parce_records() { - j_str=$1 - record_name=$2 - res="\"$record_name\":[" - res=${res}$(echo "$j_str" | _egrep_o "\"$record_name\":\[.*" | cut -d '[' -f2 | cut -d ']' -f1) - res=${res}"]," - echo "$res" -} - -# Usage: _add_record [data] [record_name] [record_data] -_add_record() { - data=$1 - record_name=$2 - record_data=$3 - echo "$data" | sed "s/\"$record_name\":\[/\"$record_name\":\[$record_data,/" | sed "s/,\]/\]/" -} - -# Usage: _rm_record [data] [record_data] -_rm_record() { - data=$1 - record_data=$2 - echo "$data" | sed "s/$record_data//g" | sed "s/,\+/,/g" | - sed "s/{,/{/g" | sed "s/,}/}/g" | - sed "s/\[,/\[/g" | sed "s/,\]/\]/g" -} - -_txt_to_dns_json() { - echo "{\"ttl\":600,\"txtdata\":\"$1\"}" -} - -# Usage: _api_call [api_url] [input_data] -_api_call() { - api_url="$1" - input_data="$2" - - _debug "_api_call $api_url" - _debug "Request: $input_data" - - # res=$(curl -s -L -D ./http.header \ - # "$api_url" \ - # --data-urlencode login=$Beget_Username \ - # --data-urlencode passwd=$Beget_Password \ - # --data-urlencode input_format=json \ - # --data-urlencode output_format=json \ - # --data-urlencode "input_data=$input_data") - - url="$api_url?login=$Beget_Username&passwd=$Beget_Password&input_format=json&output_format=json" - if [ -n "$input_data" ]; then - url=${url}"&input_data=" - url=${url}$(echo "$input_data" | _url_encode) - fi - res=$(_get "$url") - - _debug "Reply: $res" - echo "$res" -} + _api_call "$Beget_Api/dns/changeRecords" "$data" || { + echo "[ERROR] Error calling changeRecords" + return 1 + } -# Usage: _is_api_reply_ok [api_reply] -_is_api_reply_ok() { - _contains "$1" '^{"status":"success","answer":{"status":"success","result":.*}}$' + echo "[INFO] All _acme-challenge TXT records removed for $fulldomain" }