committed by
GitHub
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 1003 additions and 107 deletions
-
2deploy/ali_cdn.sh
-
2deploy/ali_dcdn.sh
-
226dnsapi/dns_exoscale.sh
-
501dnsapi/dns_hostup.sh
-
15dnsapi/dns_inwx.sh
-
45dnsapi/dns_omglol.sh
-
319dnsapi/dns_sotoon.sh
@ -0,0 +1,501 @@ |
|||
#!/usr/bin/env sh |
|||
# shellcheck disable=SC2034,SC2154 |
|||
|
|||
dns_hostup_info='HostUp DNS |
|||
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_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" | _lower_case)" |
|||
_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 <<EOF |
|||
$data |
|||
EOF |
|||
|
|||
if [ -z "$HOSTUP_ZONES_CACHE" ]; then |
|||
_err "HostUp DNS API: no zones returned for the current API key." |
|||
return 1 |
|||
fi |
|||
|
|||
return 0 |
|||
} |
|||
|
|||
_hostup_lookup_zone() { |
|||
lookup_domain="$1" |
|||
_lookup_zone_id="" |
|||
_lookup_zone_domain="" |
|||
|
|||
while IFS='|' read -r domain zone_id; do |
|||
[ -z "$domain" ] && continue |
|||
if [ "$domain" = "$lookup_domain" ]; then |
|||
_lookup_zone_domain="$domain" |
|||
_lookup_zone_id="$zone_id" |
|||
HOSTUP_ZONE_DOMAIN="$domain" |
|||
HOSTUP_ZONE_ID="$zone_id" |
|||
return 0 |
|||
fi |
|||
done <<EOF |
|||
$HOSTUP_ZONES_CACHE |
|||
EOF |
|||
|
|||
return 1 |
|||
} |
|||
|
|||
_hostup_find_record() { |
|||
zone_id="$1" |
|||
fqdn="$2" |
|||
txtvalue="$3" |
|||
|
|||
if ! _hostup_rest "GET" "/dns/zones/$zone_id/records" ""; then |
|||
return 1 |
|||
fi |
|||
|
|||
HOSTUP_RECORD_ID="" |
|||
records="$(printf "%s" "$_hostup_response" | tr '{' '\n')" |
|||
|
|||
while IFS= read -r 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')" |
|||
|
|||
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")" |
|||
|
|||
_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" |
|||
|
|||
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 |
|||
fi |
|||
;; |
|||
esac |
|||
done <<EOF |
|||
$records |
|||
EOF |
|||
|
|||
return 1 |
|||
} |
|||
|
|||
_hostup_json_extract() { |
|||
key="$1" |
|||
input="${2:-$line}" |
|||
|
|||
# First try to extract quoted values (strings) |
|||
quoted_match="$(printf "%s" "$input" | _egrep_o "\"$key\":\"[^\"]*\"" | _head_n 1)" |
|||
if [ -n "$quoted_match" ]; then |
|||
printf "%s" "$quoted_match" | |
|||
cut -d : -f2- | |
|||
sed 's/^"//' | |
|||
sed 's/"$//' | |
|||
sed 's/\\"/"/g' |
|||
return 0 |
|||
fi |
|||
|
|||
# Fallback for unquoted values (e.g., numeric IDs) |
|||
unquoted_match="$(printf "%s" "$input" | _egrep_o "\"$key\":[^,}]*" | _head_n 1)" |
|||
if [ -n "$unquoted_match" ]; then |
|||
printf "%s" "$unquoted_match" | |
|||
cut -d : -f2- | |
|||
tr -d '", ' | |
|||
tr -d '\r\n' |
|||
return 0 |
|||
fi |
|||
|
|||
return 1 |
|||
} |
|||
|
|||
_hostup_json_escape() { |
|||
printf "%s" "$1" | sed 's/\\/\\\\/g; s/"/\\"/g' |
|||
} |
|||
|
|||
_hostup_record_key() { |
|||
zone_id="$1" |
|||
domain="$2" |
|||
safe_zone="$(printf "%s" "$zone_id" | sed 's/[^A-Za-z0-9]/_/g')" |
|||
safe_domain="$(printf "%s" "$domain" | _lower_case | sed 's/[^a-z0-9]/_/g')" |
|||
printf "%s_%s" "$safe_zone" "$safe_domain" |
|||
} |
|||
|
|||
_hostup_save_record_id() { |
|||
zone_id="$1" |
|||
domain="$2" |
|||
record_id="$3" |
|||
key="$(_hostup_record_key "$zone_id" "$domain")" |
|||
_saveaccountconf_mutable "HOSTUP_RECORD_$key" "$record_id" |
|||
} |
|||
|
|||
_hostup_get_saved_record_id() { |
|||
zone_id="$1" |
|||
domain="$2" |
|||
key="$(_hostup_record_key "$zone_id" "$domain")" |
|||
_readaccountconf_mutable "HOSTUP_RECORD_$key" |
|||
} |
|||
|
|||
_hostup_clear_record_id() { |
|||
zone_id="$1" |
|||
domain="$2" |
|||
key="$(_hostup_record_key "$zone_id" "$domain")" |
|||
_clearaccountconf_mutable "HOSTUP_RECORD_$key" |
|||
} |
|||
|
|||
_hostup_extract_record_id() { |
|||
record_id="$(_hostup_json_extract "id" "$1")" |
|||
if [ -n "$record_id" ]; then |
|||
printf "%s" "$record_id" |
|||
return 0 |
|||
fi |
|||
|
|||
printf "%s" "$1" | _egrep_o '"id":[0-9]+' | _head_n 1 | cut -d: -f2 |
|||
} |
|||
|
|||
_hostup_delete_record_by_id() { |
|||
zone_id="$1" |
|||
record_id="$2" |
|||
|
|||
if ! _hostup_rest "DELETE" "/dns/zones/$zone_id/records/$record_id" ""; then |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _contains "$_hostup_response" '"success":true'; then |
|||
return 1 |
|||
fi |
|||
|
|||
return 0 |
|||
} |
|||
|
|||
_hostup_rest() { |
|||
method="$1" |
|||
route="$2" |
|||
data="$3" |
|||
|
|||
_hostup_response="" |
|||
|
|||
export _H1="Authorization: Bearer $HOSTUP_API_KEY" |
|||
export _H2="Content-Type: application/json" |
|||
export _H3="Accept: application/json" |
|||
|
|||
if [ "$method" = "GET" ]; then |
|||
_hostup_response="$(_get "$HOSTUP_API_BASE$route")" |
|||
else |
|||
_hostup_response="$(_post "$data" "$HOSTUP_API_BASE$route" "" "$method" "application/json")" |
|||
fi |
|||
|
|||
ret="$?" |
|||
|
|||
unset _H1 |
|||
unset _H2 |
|||
unset _H3 |
|||
|
|||
if [ "$ret" != "0" ]; then |
|||
_err "HTTP request failed for $route" |
|||
return 1 |
|||
fi |
|||
|
|||
http_status="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")" |
|||
_debug2 "HTTP status" "$http_status" |
|||
_debug2 "_hostup_response" "$_hostup_response" |
|||
|
|||
case "$http_status" in |
|||
200 | 201 | 204) return 0 ;; |
|||
401) |
|||
_err "HostUp API returned 401 Unauthorized. Check HOSTUP_API_KEY scopes and IP restrictions." |
|||
return 1 |
|||
;; |
|||
403) |
|||
_err "HostUp API returned 403 Forbidden. The API key lacks required DNS scopes." |
|||
return 1 |
|||
;; |
|||
404) |
|||
_err "HostUp API returned 404 Not Found for $route" |
|||
return 1 |
|||
;; |
|||
429) |
|||
_err "HostUp API rate limit exceeded. Please retry later." |
|||
return 1 |
|||
;; |
|||
*) |
|||
_err "HostUp API request failed with status $http_status" |
|||
return 1 |
|||
;; |
|||
esac |
|||
} |
|||
@ -0,0 +1,319 @@ |
|||
#!/usr/bin/env sh |
|||
# shellcheck disable=SC2034 |
|||
dns_sotoon_info='Sotoon.ir |
|||
Site: Sotoon.ir |
|||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_sotoon |
|||
Options: |
|||
Sotoon_Token API Token |
|||
Sotoon_WorkspaceUUID Workspace UUID |
|||
Sotoon_WorkspaceName Workspace Name |
|||
Issues: github.com/acmesh-official/acme.sh/issues/6656 |
|||
Author: Erfan Gholizade |
|||
' |
|||
|
|||
SOTOON_API_URL="https://api.sotoon.ir/delivery/v2/global" |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#Adding the txt record for validation. |
|||
#Usage: dns_sotoon_add fulldomain TXT_record |
|||
#Usage: dns_sotoon_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns_sotoon_add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
_info_sotoon "Using Sotoon" |
|||
|
|||
Sotoon_Token="${Sotoon_Token:-$(_readaccountconf_mutable Sotoon_Token)}" |
|||
Sotoon_WorkspaceUUID="${Sotoon_WorkspaceUUID:-$(_readaccountconf_mutable Sotoon_WorkspaceUUID)}" |
|||
Sotoon_WorkspaceName="${Sotoon_WorkspaceName:-$(_readaccountconf_mutable Sotoon_WorkspaceName)}" |
|||
|
|||
if [ -z "$Sotoon_Token" ]; then |
|||
_err_sotoon "You didn't specify \"Sotoon_Token\" token yet." |
|||
_err_sotoon "You can get yours from here https://ocean.sotoon.ir/profile/tokens" |
|||
return 1 |
|||
fi |
|||
if [ -z "$Sotoon_WorkspaceUUID" ]; then |
|||
_err_sotoon "You didn't specify \"Sotoon_WorkspaceUUID\" Workspace UUID yet." |
|||
_err_sotoon "You can get yours from here https://ocean.sotoon.ir/profile/workspaces" |
|||
return 1 |
|||
fi |
|||
if [ -z "$Sotoon_WorkspaceName" ]; then |
|||
_err_sotoon "You didn't specify \"Sotoon_WorkspaceName\" Workspace Name yet." |
|||
_err_sotoon "You can get yours from here https://ocean.sotoon.ir/profile/workspaces" |
|||
return 1 |
|||
fi |
|||
|
|||
#save the info to the account conf file. |
|||
_saveaccountconf_mutable Sotoon_Token "$Sotoon_Token" |
|||
_saveaccountconf_mutable Sotoon_WorkspaceUUID "$Sotoon_WorkspaceUUID" |
|||
_saveaccountconf_mutable Sotoon_WorkspaceName "$Sotoon_WorkspaceName" |
|||
|
|||
_debug_sotoon "First detect the root zone" |
|||
if ! _get_root "$fulldomain"; then |
|||
_err_sotoon "invalid domain" |
|||
return 1 |
|||
fi |
|||
|
|||
_info_sotoon "Adding record" |
|||
|
|||
_debug_sotoon _domain_id "$_domain_id" |
|||
_debug_sotoon _sub_domain "$_sub_domain" |
|||
_debug_sotoon _domain "$_domain" |
|||
|
|||
# First, GET the current domain zone to check for existing TXT records |
|||
# This is needed for wildcard certs which require multiple TXT values |
|||
_info_sotoon "Checking for existing TXT records" |
|||
if ! _sotoon_rest GET "$_domain_id"; then |
|||
_err_sotoon "Failed to get domain zone" |
|||
return 1 |
|||
fi |
|||
|
|||
# Check if there are existing TXT records for this subdomain |
|||
_existing_txt="" |
|||
if _contains "$response" "\"$_sub_domain\""; then |
|||
_debug_sotoon "Found existing records for $_sub_domain" |
|||
# Extract existing TXT values from the response |
|||
# The format is: "_acme-challenge":[{"TXT":"value1","type":"TXT","ttl":10},{"TXT":"value2",...}] |
|||
_existing_txt=$(echo "$response" | _egrep_o "\"$_sub_domain\":\[[^]]*\]" | sed "s/\"$_sub_domain\"://") |
|||
_debug_sotoon "Existing TXT records: $_existing_txt" |
|||
fi |
|||
|
|||
# Build the new record entry |
|||
_new_record="{\"TXT\":\"$txtvalue\",\"type\":\"TXT\",\"ttl\":120}" |
|||
|
|||
# If there are existing records, append to them; otherwise create new array |
|||
if [ -n "$_existing_txt" ] && [ "$_existing_txt" != "[]" ] && [ "$_existing_txt" != "null" ]; then |
|||
# Check if this exact TXT value already exists (avoid duplicates) |
|||
if _contains "$_existing_txt" "\"$txtvalue\""; then |
|||
_info_sotoon "TXT record already exists, skipping" |
|||
return 0 |
|||
fi |
|||
# Remove the closing bracket and append new record |
|||
_combined_records="$(echo "$_existing_txt" | sed 's/]$//'),$_new_record]" |
|||
_debug_sotoon "Combined records: $_combined_records" |
|||
else |
|||
# No existing records, create new array |
|||
_combined_records="[$_new_record]" |
|||
fi |
|||
|
|||
# Prepare the DNS record data in Kubernetes CRD format |
|||
_dns_record="{\"spec\":{\"records\":{\"$_sub_domain\":$_combined_records}}}" |
|||
|
|||
_debug_sotoon "DNS record payload: $_dns_record" |
|||
|
|||
# Use PATCH to update/add the record to the domain zone |
|||
_info_sotoon "Updating domain zone $_domain_id with TXT record" |
|||
if _sotoon_rest PATCH "$_domain_id" "$_dns_record"; then |
|||
if _contains "$response" "$txtvalue" || _contains "$response" "\"$_sub_domain\""; then |
|||
_info_sotoon "Added, OK" |
|||
return 0 |
|||
else |
|||
_debug_sotoon "Response: $response" |
|||
_err_sotoon "Add txt record error." |
|||
return 1 |
|||
fi |
|||
fi |
|||
|
|||
_err_sotoon "Add txt record error." |
|||
return 1 |
|||
} |
|||
|
|||
#Remove the txt record after validation. |
|||
#Usage: dns_sotoon_rm fulldomain TXT_record |
|||
#Usage: dns_sotoon_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns_sotoon_rm() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
_info_sotoon "Using Sotoon" |
|||
_debug_sotoon fulldomain "$fulldomain" |
|||
_debug_sotoon txtvalue "$txtvalue" |
|||
|
|||
Sotoon_Token="${Sotoon_Token:-$(_readaccountconf_mutable Sotoon_Token)}" |
|||
Sotoon_WorkspaceUUID="${Sotoon_WorkspaceUUID:-$(_readaccountconf_mutable Sotoon_WorkspaceUUID)}" |
|||
Sotoon_WorkspaceName="${Sotoon_WorkspaceName:-$(_readaccountconf_mutable Sotoon_WorkspaceName)}" |
|||
|
|||
_debug_sotoon "First detect the root zone" |
|||
if ! _get_root "$fulldomain"; then |
|||
_err_sotoon "invalid domain" |
|||
return 1 |
|||
fi |
|||
_debug_sotoon _domain_id "$_domain_id" |
|||
_debug_sotoon _sub_domain "$_sub_domain" |
|||
_debug_sotoon _domain "$_domain" |
|||
|
|||
_info_sotoon "Removing TXT record" |
|||
|
|||
# First, GET the current domain zone to check for existing TXT records |
|||
if ! _sotoon_rest GET "$_domain_id"; then |
|||
_err_sotoon "Failed to get domain zone" |
|||
return 1 |
|||
fi |
|||
|
|||
# Check if there are existing TXT records for this subdomain |
|||
_existing_txt="" |
|||
if _contains "$response" "\"$_sub_domain\""; then |
|||
_debug_sotoon "Found existing records for $_sub_domain" |
|||
_existing_txt=$(echo "$response" | _egrep_o "\"$_sub_domain\":\[[^]]*\]" | sed "s/\"$_sub_domain\"://") |
|||
_debug_sotoon "Existing TXT records: $_existing_txt" |
|||
fi |
|||
|
|||
# If no existing records, nothing to remove |
|||
if [ -z "$_existing_txt" ] || [ "$_existing_txt" = "[]" ] || [ "$_existing_txt" = "null" ]; then |
|||
_info_sotoon "No TXT records found, nothing to remove" |
|||
return 0 |
|||
fi |
|||
|
|||
# Remove the specific TXT value from the array |
|||
# This handles the case where there are multiple TXT values (wildcard certs) |
|||
_remaining_records=$(echo "$_existing_txt" | sed "s/{\"TXT\":\"$txtvalue\"[^}]*},*//g" | sed 's/,]/]/g' | sed 's/\[,/[/g') |
|||
_debug_sotoon "Remaining records after removal: $_remaining_records" |
|||
|
|||
# If no records remain, set to null to remove the subdomain entirely |
|||
if [ "$_remaining_records" = "[]" ] || [ -z "$_remaining_records" ]; then |
|||
_dns_record="{\"spec\":{\"records\":{\"$_sub_domain\":null}}}" |
|||
else |
|||
_dns_record="{\"spec\":{\"records\":{\"$_sub_domain\":$_remaining_records}}}" |
|||
fi |
|||
|
|||
_debug_sotoon "Remove record payload: $_dns_record" |
|||
|
|||
# Use PATCH to remove the record from the domain zone |
|||
if _sotoon_rest PATCH "$_domain_id" "$_dns_record"; then |
|||
_info_sotoon "Record removed, OK" |
|||
return 0 |
|||
else |
|||
_debug_sotoon "Response: $response" |
|||
_err_sotoon "Error removing record" |
|||
return 1 |
|||
fi |
|||
} |
|||
|
|||
#################### Private functions below ################################## |
|||
|
|||
_get_root() { |
|||
domain=$1 |
|||
i=1 |
|||
p=1 |
|||
|
|||
_debug_sotoon "Getting root domain for: $domain" |
|||
_debug_sotoon "Sotoon WorkspaceUUID: $Sotoon_WorkspaceUUID" |
|||
_debug_sotoon "Sotoon WorkspaceName: $Sotoon_WorkspaceName" |
|||
|
|||
while true; do |
|||
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) |
|||
_debug_sotoon "Checking domain part: $h" |
|||
|
|||
if [ -z "$h" ]; then |
|||
#not valid |
|||
_err_sotoon "Could not find valid domain" |
|||
return 1 |
|||
fi |
|||
|
|||
_debug_sotoon "Fetching domain zones from Sotoon API" |
|||
if ! _sotoon_rest GET ""; then |
|||
_err_sotoon "Failed to get domain zones from Sotoon API" |
|||
_err_sotoon "Please check your Sotoon_Token, Sotoon_WorkspaceUUID, and Sotoon_WorkspaceName" |
|||
return 1 |
|||
fi |
|||
|
|||
_debug2_sotoon "API Response: $response" |
|||
|
|||
# Check if the response contains our domain |
|||
# Sotoon API uses Kubernetes CRD format with spec.origin for domain matching |
|||
if _contains "$response" "\"origin\":\"$h\""; then |
|||
_debug_sotoon "Found domain by origin: $h" |
|||
|
|||
# In Kubernetes CRD format, the metadata.name is the resource identifier |
|||
# The name can be either: |
|||
# 1. Same as origin |
|||
# 2. Origin with dots replaced by hyphens |
|||
# We check both patterns in the response to determine which one exists |
|||
|
|||
# Convert origin to hyphenated version for checking |
|||
_h_hyphenated=$(echo "$h" | tr '.' '-') |
|||
|
|||
# Check if the hyphenated name exists in the response |
|||
if _contains "$response" "\"name\":\"$_h_hyphenated\""; then |
|||
_domain_id="$_h_hyphenated" |
|||
_debug_sotoon "Found domain ID (hyphenated): $_domain_id" |
|||
# Check if the origin itself is used as name |
|||
elif _contains "$response" "\"name\":\"$h\""; then |
|||
_domain_id="$h" |
|||
_debug_sotoon "Found domain ID (same as origin): $_domain_id" |
|||
else |
|||
# Fallback: use the hyphenated version (more common) |
|||
_domain_id="$_h_hyphenated" |
|||
_debug_sotoon "Using hyphenated domain ID as fallback: $_domain_id" |
|||
fi |
|||
|
|||
if [ -n "$_domain_id" ]; then |
|||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") |
|||
_domain=$h |
|||
_debug_sotoon "Domain ID (metadata.name): $_domain_id" |
|||
_debug_sotoon "Sub domain: $_sub_domain" |
|||
_debug_sotoon "Domain (origin): $_domain" |
|||
return 0 |
|||
fi |
|||
_err_sotoon "Found domain $h but could not extract domain ID" |
|||
return 1 |
|||
fi |
|||
p=$i |
|||
i=$(_math "$i" + 1) |
|||
done |
|||
return 1 |
|||
} |
|||
|
|||
_sotoon_rest() { |
|||
mtd="$1" |
|||
resource_id="$2" |
|||
data="$3" |
|||
|
|||
token_trimmed=$(echo "$Sotoon_Token" | tr -d '"') |
|||
|
|||
# Construct the API endpoint |
|||
_api_path="$SOTOON_API_URL/workspaces/$Sotoon_WorkspaceUUID/namespaces/$Sotoon_WorkspaceName/domainzones" |
|||
|
|||
if [ -n "$resource_id" ]; then |
|||
_api_path="$_api_path/$resource_id" |
|||
fi |
|||
|
|||
_debug_sotoon "API Path: $_api_path" |
|||
_debug_sotoon "Method: $mtd" |
|||
|
|||
# Set authorization header - Sotoon API uses Bearer token |
|||
export _H1="Authorization: Bearer $token_trimmed" |
|||
|
|||
if [ "$mtd" = "GET" ]; then |
|||
# GET request |
|||
_debug_sotoon "GET" "$_api_path" |
|||
response="$(_get "$_api_path")" |
|||
elif [ "$mtd" = "PATCH" ]; then |
|||
# PATCH Request |
|||
export _H2="Content-Type: application/merge-patch+json" |
|||
_debug_sotoon data "$data" |
|||
response="$(_post "$data" "$_api_path" "" "$mtd")" |
|||
else |
|||
_err_sotoon "Unknown method: $mtd" |
|||
return 1 |
|||
fi |
|||
|
|||
_debug2_sotoon response "$response" |
|||
return 0 |
|||
} |
|||
|
|||
#Wrappers for logging |
|||
_info_sotoon() { |
|||
_info "[Sotoon]" "$@" |
|||
} |
|||
|
|||
_err_sotoon() { |
|||
_err "[Sotoon]" "$@" |
|||
} |
|||
|
|||
_debug_sotoon() { |
|||
_debug "[Sotoon]" "$@" |
|||
} |
|||
|
|||
_debug2_sotoon() { |
|||
_debug2 "[Sotoon]" "$@" |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue