|
|
@ -12,270 +12,119 @@ Author: ARNik <arnik@arnik.ru> |
|
|
|
|
|
|
|
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" |
|
|
|
} |