mirror of
https://github.com/acmesh-official/acme.sh.git
synced 2025-12-26 13:42:09 +08:00
Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d24c7e977e | ||
|
|
6a1ff1c0a6 | ||
|
|
383557df61 | ||
|
|
dba4be8065 | ||
|
|
a670c07caf | ||
|
|
09cc2bdfa5 | ||
|
|
cc897cab4c | ||
|
|
7ba9597928 | ||
|
|
d2d862420e | ||
|
|
fd6a14de8a | ||
|
|
0eb40c6ce6 | ||
|
|
e321b3c75c | ||
|
|
03d8d3bc1b | ||
|
|
f85de2b0d3 | ||
|
|
5fb42b7339 | ||
|
|
1b2630dc0d | ||
|
|
3fb4c313ec | ||
|
|
65892453be | ||
|
|
cba0ff8321 | ||
|
|
b6523c2301 | ||
|
|
e92d0a7492 | ||
|
|
0e5aab346f | ||
|
|
a5ad15be02 | ||
|
|
27ebf09c5c | ||
|
|
1c65c04b54 | ||
|
|
987882ea37 | ||
|
|
00aaed1b14 | ||
|
|
e3b1bccb6a | ||
|
|
85ff92170b | ||
|
|
7ac8c6c75b | ||
|
|
6004e7f5cd | ||
|
|
4a7e5d0720 | ||
|
|
b4042d5ccb | ||
|
|
1413aa332b | ||
|
|
51b4fa0080 | ||
|
|
64a6ea68fa | ||
|
|
d97b4477b2 | ||
|
|
b8e394e76a | ||
|
|
671d542898 | ||
|
|
9980ad0fef |
2
acme.sh
2
acme.sh
@@ -5840,7 +5840,7 @@ list() {
|
||||
if [ -z "$_domain" ]; then
|
||||
printf "%s\n" "Main_Domain${_sep}KeyLength${_sep}SAN_Domains${_sep}Profile${_sep}CA${_sep}Created${_sep}Renew"
|
||||
fi
|
||||
for di in "${CERT_HOME}"/*.*/; do
|
||||
for di in "${CERT_HOME}"/{*.*,*:*}/; do
|
||||
d=$(basename "$di")
|
||||
_debug d "$d"
|
||||
(
|
||||
|
||||
@@ -83,6 +83,6 @@ _set_cdn_domain_ssl_certificate_query() {
|
||||
query=$query'&SignatureMethod=HMAC-SHA1'
|
||||
query=$query"&SignatureNonce=$(_ali_nonce)"
|
||||
query=$query'&SignatureVersion=1.0'
|
||||
query=$query'&Timestamp='$(_timestamp)
|
||||
query=$query'&Timestamp='$(_ali_timestamp)
|
||||
query=$query'&Version=2018-05-10'
|
||||
}
|
||||
|
||||
@@ -83,6 +83,6 @@ _set_dcdn_domain_ssl_certificate_query() {
|
||||
query=$query'&SignatureMethod=HMAC-SHA1'
|
||||
query=$query"&SignatureNonce=$(_ali_nonce)"
|
||||
query=$query'&SignatureVersion=1.0'
|
||||
query=$query'&Timestamp='$(_timestamp)
|
||||
query=$query'&Timestamp='$(_ali_timestamp)
|
||||
query=$query'&Version=2018-01-15'
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ strongswan_deploy() {
|
||||
return 1
|
||||
fi
|
||||
_info _confdir "${_confdir}"
|
||||
__deploy_cert "$@" "stroke" "${_confdir}"
|
||||
__deploy_cert "stroke" "${_confdir}" "$@"
|
||||
${_ipsec} reload
|
||||
fi
|
||||
# For modern vici mode
|
||||
@@ -50,7 +50,7 @@ strongswan_deploy() {
|
||||
_err "no swanctl config dir is found"
|
||||
return 1
|
||||
fi
|
||||
__deploy_cert "$@" "vici" "${_confdir}"
|
||||
__deploy_cert "vici" "${_confdir}" "$@"
|
||||
${_swanctl} --load-creds
|
||||
fi
|
||||
if [ -z "${_swanctl}" ] && [ -z "${_ipsec}" ]; then
|
||||
@@ -63,13 +63,13 @@ strongswan_deploy() {
|
||||
#################### Private functions below ##################################
|
||||
|
||||
__deploy_cert() {
|
||||
_cdomain="${1}"
|
||||
_ckey="${2}"
|
||||
_ccert="${3}"
|
||||
_cca="${4}"
|
||||
_cfullchain="${5}"
|
||||
_swan_mode="${6}"
|
||||
_confdir="${7}"
|
||||
_swan_mode="${1}"
|
||||
_confdir="${2}"
|
||||
_cdomain="${3}"
|
||||
_ckey="${4}"
|
||||
_ccert="${5}"
|
||||
_cca="${6}"
|
||||
_cfullchain="${7}"
|
||||
_debug _cdomain "${_cdomain}"
|
||||
_debug _ckey "${_ckey}"
|
||||
_debug _ccert "${_ccert}"
|
||||
|
||||
226
dnsapi/dns_exoscale.sh
Executable file → Normal file
226
dnsapi/dns_exoscale.sh
Executable file → Normal file
@@ -8,9 +8,9 @@ Options:
|
||||
EXOSCALE_SECRET_KEY API Secret key
|
||||
'
|
||||
|
||||
EXOSCALE_API=https://api.exoscale.com/dns/v1
|
||||
EXOSCALE_API="https://api-ch-gva-2.exoscale.com/v2"
|
||||
|
||||
######## Public functions #####################
|
||||
######## Public functions ########
|
||||
|
||||
# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
|
||||
# Used to add txt record
|
||||
@@ -18,159 +18,197 @@ dns_exoscale_add() {
|
||||
fulldomain=$1
|
||||
txtvalue=$2
|
||||
|
||||
if ! _checkAuth; then
|
||||
_debug "Using Exoscale DNS v2 API"
|
||||
_debug fulldomain "$fulldomain"
|
||||
_debug txtvalue "$txtvalue"
|
||||
|
||||
if ! _check_auth; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "First detect the root zone"
|
||||
if ! _get_root "$fulldomain"; then
|
||||
_err "invalid domain"
|
||||
root_domain_id=$(_get_root_domain_id "$fulldomain")
|
||||
if [ -z "$root_domain_id" ]; then
|
||||
_err "Unable to determine root domain ID for $fulldomain"
|
||||
return 1
|
||||
fi
|
||||
_debug root_domain_id "$root_domain_id"
|
||||
|
||||
_debug _sub_domain "$_sub_domain"
|
||||
_debug _domain "$_domain"
|
||||
# Always get the subdomain part first
|
||||
sub_domain=$(_get_sub_domain "$fulldomain" "$root_domain_id")
|
||||
_debug sub_domain "$sub_domain"
|
||||
|
||||
_info "Adding record"
|
||||
if _exoscale_rest POST "domains/$_domain_id/records" "{\"record\":{\"name\":\"$_sub_domain\",\"record_type\":\"TXT\",\"content\":\"$txtvalue\",\"ttl\":120}}" "$_domain_token"; then
|
||||
if _contains "$response" "$txtvalue"; then
|
||||
_info "Added, OK"
|
||||
return 0
|
||||
fi
|
||||
# Build the record name properly
|
||||
if [ -z "$sub_domain" ]; then
|
||||
record_name="_acme-challenge"
|
||||
else
|
||||
record_name="_acme-challenge.$sub_domain"
|
||||
fi
|
||||
_err "Add txt record error."
|
||||
return 1
|
||||
|
||||
payload=$(printf '{"name":"%s","type":"TXT","content":"%s","ttl":120}' "$record_name" "$txtvalue")
|
||||
_debug payload "$payload"
|
||||
|
||||
response=$(_exoscale_rest POST "/dns-domain/${root_domain_id}/record" "$payload")
|
||||
if _contains "$response" "\"id\""; then
|
||||
_info "TXT record added successfully."
|
||||
return 0
|
||||
else
|
||||
_err "Error adding TXT record: $response"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Usage: fulldomain txtvalue
|
||||
# Used to remove the txt record after validation
|
||||
dns_exoscale_rm() {
|
||||
fulldomain=$1
|
||||
txtvalue=$2
|
||||
|
||||
if ! _checkAuth; then
|
||||
_debug "Using Exoscale DNS v2 API for removal"
|
||||
_debug fulldomain "$fulldomain"
|
||||
|
||||
if ! _check_auth; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "First detect the root zone"
|
||||
if ! _get_root "$fulldomain"; then
|
||||
_err "invalid domain"
|
||||
root_domain_id=$(_get_root_domain_id "$fulldomain")
|
||||
if [ -z "$root_domain_id" ]; then
|
||||
_err "Unable to determine root domain ID for $fulldomain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug _sub_domain "$_sub_domain"
|
||||
_debug _domain "$_domain"
|
||||
|
||||
_debug "Getting txt records"
|
||||
_exoscale_rest GET "domains/${_domain_id}/records?type=TXT&name=$_sub_domain" "" "$_domain_token"
|
||||
if _contains "$response" "\"name\":\"$_sub_domain\"" >/dev/null; then
|
||||
_record_id=$(echo "$response" | tr '{' "\n" | grep "\"content\":\"$txtvalue\"" | _egrep_o "\"id\":[^,]+" | _head_n 1 | cut -d : -f 2 | tr -d \")
|
||||
record_name="_acme-challenge"
|
||||
sub_domain=$(_get_sub_domain "$fulldomain" "$root_domain_id")
|
||||
if [ -n "$sub_domain" ]; then
|
||||
record_name="_acme-challenge.$sub_domain"
|
||||
fi
|
||||
|
||||
if [ -z "$_record_id" ]; then
|
||||
_err "Can not get record id to remove."
|
||||
record_id=$(_find_record_id "$root_domain_id" "$record_name")
|
||||
if [ -z "$record_id" ]; then
|
||||
_err "TXT record not found for deletion."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "Deleting record $_record_id"
|
||||
|
||||
if ! _exoscale_rest DELETE "domains/$_domain_id/records/$_record_id" "" "$_domain_token"; then
|
||||
_err "Delete record error."
|
||||
response=$(_exoscale_rest DELETE "/dns-domain/$root_domain_id/record/$record_id")
|
||||
if _contains "$response" "\"state\":\"success\""; then
|
||||
_info "TXT record deleted successfully."
|
||||
return 0
|
||||
else
|
||||
_err "Error deleting TXT record: $response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
#################### Private functions below ##################################
|
||||
######## Private helpers ########
|
||||
|
||||
_checkAuth() {
|
||||
_check_auth() {
|
||||
EXOSCALE_API_KEY="${EXOSCALE_API_KEY:-$(_readaccountconf_mutable EXOSCALE_API_KEY)}"
|
||||
EXOSCALE_SECRET_KEY="${EXOSCALE_SECRET_KEY:-$(_readaccountconf_mutable EXOSCALE_SECRET_KEY)}"
|
||||
|
||||
if [ -z "$EXOSCALE_API_KEY" ] || [ -z "$EXOSCALE_SECRET_KEY" ]; then
|
||||
EXOSCALE_API_KEY=""
|
||||
EXOSCALE_SECRET_KEY=""
|
||||
_err "You don't specify Exoscale application key and application secret yet."
|
||||
_err "Please create you key and try again."
|
||||
_err "EXOSCALE_API_KEY and EXOSCALE_SECRET_KEY must be set."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_saveaccountconf_mutable EXOSCALE_API_KEY "$EXOSCALE_API_KEY"
|
||||
_saveaccountconf_mutable EXOSCALE_SECRET_KEY "$EXOSCALE_SECRET_KEY"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
#_acme-challenge.www.domain.com
|
||||
#returns
|
||||
# _sub_domain=_acme-challenge.www
|
||||
# _domain=domain.com
|
||||
# _domain_id=sdjkglgdfewsdfg
|
||||
# _domain_token=sdjkglgdfewsdfg
|
||||
_get_root() {
|
||||
|
||||
if ! _exoscale_rest GET "domains"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
_get_root_domain_id() {
|
||||
domain=$1
|
||||
i=2
|
||||
p=1
|
||||
i=1
|
||||
while true; do
|
||||
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
|
||||
_debug h "$h"
|
||||
if [ -z "$h" ]; then
|
||||
#not valid
|
||||
return 1
|
||||
fi
|
||||
|
||||
if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
|
||||
_domain_id=$(echo "$response" | tr '{' "\n" | grep "\"name\":\"$h\"" | _egrep_o "\"id\":[^,]+" | _head_n 1 | cut -d : -f 2 | tr -d \")
|
||||
_domain_token=$(echo "$response" | tr '{' "\n" | grep "\"name\":\"$h\"" | _egrep_o "\"token\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \")
|
||||
if [ "$_domain_token" ] && [ "$_domain_id" ]; then
|
||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
|
||||
_domain=$h
|
||||
return 0
|
||||
candidate=$(printf "%s" "$domain" | cut -d . -f "${i}-100")
|
||||
[ -z "$candidate" ] && return 1
|
||||
_debug "Trying root domain candidate: $candidate"
|
||||
domains=$(_exoscale_rest GET "/dns-domain")
|
||||
# Extract from dns-domains array
|
||||
result=$(echo "$domains" | _egrep_o '"dns-domains":\[.*\]' | _egrep_o '\{"id":"[^"]*","created-at":"[^"]*","unicode-name":"[^"]*"\}' | while read -r item; do
|
||||
name=$(echo "$item" | _egrep_o '"unicode-name":"[^"]*"' | cut -d'"' -f4)
|
||||
id=$(echo "$item" | _egrep_o '"id":"[^"]*"' | cut -d'"' -f4)
|
||||
if [ "$name" = "$candidate" ]; then
|
||||
echo "$id"
|
||||
break
|
||||
fi
|
||||
return 1
|
||||
done)
|
||||
if [ -n "$result" ]; then
|
||||
echo "$result"
|
||||
return 0
|
||||
fi
|
||||
p=$i
|
||||
i=$(_math "$i" + 1)
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# returns response
|
||||
_get_sub_domain() {
|
||||
fulldomain=$1
|
||||
root_id=$2
|
||||
root_info=$(_exoscale_rest GET "/dns-domain/$root_id")
|
||||
_debug root_info "$root_info"
|
||||
root_name=$(echo "$root_info" | _egrep_o "\"unicode-name\":\"[^\"]*\"" | cut -d\" -f4)
|
||||
sub=${fulldomain%%."$root_name"}
|
||||
|
||||
if [ "$sub" = "_acme-challenge" ]; then
|
||||
echo ""
|
||||
else
|
||||
# Remove _acme-challenge. prefix to get the actual subdomain
|
||||
echo "${sub#_acme-challenge.}"
|
||||
fi
|
||||
}
|
||||
|
||||
_find_record_id() {
|
||||
root_id=$1
|
||||
name=$2
|
||||
records=$(_exoscale_rest GET "/dns-domain/$root_id/record")
|
||||
|
||||
# Convert search name to lowercase for case-insensitive matching
|
||||
name_lower=$(echo "$name" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
echo "$records" | _egrep_o '\{[^}]*"name":"[^"]*"[^}]*\}' | while read -r record; do
|
||||
record_name=$(echo "$record" | _egrep_o '"name":"[^"]*"' | cut -d'"' -f4)
|
||||
record_name_lower=$(echo "$record_name" | tr '[:upper:]' '[:lower:]')
|
||||
if [ "$record_name_lower" = "$name_lower" ]; then
|
||||
echo "$record" | _egrep_o '"id":"[^"]*"' | _head_n 1 | cut -d'"' -f4
|
||||
break
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
_exoscale_sign() {
|
||||
k=$1
|
||||
shift
|
||||
hex_key=$(printf %b "$k" | _hex_dump | tr -d ' ')
|
||||
printf %s "$@" | _hmac sha256 "$hex_key"
|
||||
}
|
||||
|
||||
_exoscale_rest() {
|
||||
method=$1
|
||||
path="$2"
|
||||
data="$3"
|
||||
token="$4"
|
||||
request_url="$EXOSCALE_API/$path"
|
||||
_debug "$path"
|
||||
path=$2
|
||||
data=$3
|
||||
|
||||
url="${EXOSCALE_API}${path}"
|
||||
expiration=$(_math "$(date +%s)" + 300) # 5m from now
|
||||
|
||||
# Build the message with the actual body or empty line
|
||||
message=$(printf "%s %s\n%s\n\n\n%s" "$method" "/v2$path" "$data" "$expiration")
|
||||
signature=$(_exoscale_sign "$EXOSCALE_SECRET_KEY" "$message" | _base64)
|
||||
auth="EXO2-HMAC-SHA256 credential=${EXOSCALE_API_KEY},expires=${expiration},signature=${signature}"
|
||||
|
||||
_debug "API request: $method $url"
|
||||
_debug "Signed message: [$message]"
|
||||
_debug "Authorization header: [$auth]"
|
||||
|
||||
export _H1="Accept: application/json"
|
||||
|
||||
if [ "$token" ]; then
|
||||
export _H2="X-DNS-Domain-Token: $token"
|
||||
else
|
||||
export _H2="X-DNS-Token: $EXOSCALE_API_KEY:$EXOSCALE_SECRET_KEY"
|
||||
fi
|
||||
export _H2="Authorization: ${auth}"
|
||||
|
||||
if [ "$data" ] || [ "$method" = "DELETE" ]; then
|
||||
export _H3="Content-Type: application/json"
|
||||
_debug data "$data"
|
||||
response="$(_post "$data" "$request_url" "" "$method")"
|
||||
response="$(_post "$data" "$url" "" "$method")"
|
||||
else
|
||||
response="$(_get "$request_url" "" "" "$method")"
|
||||
response="$(_get "$url" "" "" "$method")"
|
||||
fi
|
||||
|
||||
if [ "$?" != "0" ]; then
|
||||
_err "error $request_url"
|
||||
# shellcheck disable=SC2181
|
||||
if [ "$?" -ne 0 ]; then
|
||||
_err "error $url"
|
||||
return 1
|
||||
fi
|
||||
_debug2 response "$response"
|
||||
echo "$response"
|
||||
return 0
|
||||
}
|
||||
|
||||
501
dnsapi/dns_hostup.sh
Normal file
501
dnsapi/dns_hostup.sh
Normal file
@@ -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
|
||||
}
|
||||
@@ -6,6 +6,7 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_inwx
|
||||
Options:
|
||||
INWX_User Username
|
||||
INWX_Password Password
|
||||
INWX_Shared_Secret 2 Factor Authentication Shared Secret (optional requires oathtool)
|
||||
'
|
||||
|
||||
# Dependencies:
|
||||
@@ -110,11 +111,17 @@ dns_inwx_rm() {
|
||||
<string>%s</string>
|
||||
</value>
|
||||
</member>
|
||||
<member>
|
||||
<name>content</name>
|
||||
<value>
|
||||
<string>%s</string>
|
||||
</value>
|
||||
</member>
|
||||
</struct>
|
||||
</value>
|
||||
</param>
|
||||
</params>
|
||||
</methodCall>' "$_domain" "$_sub_domain")
|
||||
</methodCall>' "$_domain" "$_sub_domain" "$txtvalue")
|
||||
response="$(_post "$xml_content" "$INWX_Api" "" "POST")"
|
||||
|
||||
if ! _contains "$response" "Command completed successfully"; then
|
||||
@@ -125,7 +132,7 @@ dns_inwx_rm() {
|
||||
if ! printf "%s" "$response" | grep "count" >/dev/null; then
|
||||
_info "Do not need to delete record"
|
||||
else
|
||||
_record_id=$(printf '%s' "$response" | _egrep_o '.*(<member><name>record){1}(.*)([0-9]+){1}' | _egrep_o '<name>id<\/name><value><int>[0-9]+' | _egrep_o '[0-9]+')
|
||||
_record_id=$(printf '%s' "$response" | _egrep_o '.*(<member><name>record){1}(.*)([0-9]+){1}' | _egrep_o '<name>id<\/name><value><string>[0-9]+' | _egrep_o '[0-9]+')
|
||||
_info "Deleting record"
|
||||
_inwx_delete_record "$_record_id"
|
||||
fi
|
||||
@@ -324,7 +331,7 @@ _inwx_delete_record() {
|
||||
<member>
|
||||
<name>id</name>
|
||||
<value>
|
||||
<int>%s</int>
|
||||
<string>%s</string>
|
||||
</value>
|
||||
</member>
|
||||
</struct>
|
||||
@@ -362,7 +369,7 @@ _inwx_update_record() {
|
||||
<member>
|
||||
<name>id</name>
|
||||
<value>
|
||||
<int>%s</int>
|
||||
<string>%s</string>
|
||||
</value>
|
||||
</member>
|
||||
</struct>
|
||||
|
||||
@@ -4,8 +4,8 @@ dns_omglol_info='omg.lol
|
||||
Site: omg.lol
|
||||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_omglol
|
||||
Options:
|
||||
OMG_ApiKey API Key. This is accessible from the bottom of the account page at https://home.omg.lol/account
|
||||
OMG_Address Address. This is your omg.lol address, without the preceding @ - you can see your list on your dashboard at https://home.omg.lol/dashboard
|
||||
OMG_ApiKey - API Key. This is accessible from the bottom of the account page at https://home.omg.lol/account
|
||||
OMG_Address - Address. This is your omg.lol address, without the preceding @ - you can see your list on your dashboard at https://home.omg.lol/dashboard
|
||||
Issues: github.com/acmesh-official/acme.sh/issues/5299
|
||||
Author: @Kholin <kholin+acme.omglolapi@omg.lol>
|
||||
'
|
||||
@@ -35,7 +35,7 @@ dns_omglol_add() {
|
||||
_debug "omg.lol Address" "$OMG_Address"
|
||||
|
||||
omg_validate "$OMG_ApiKey" "$OMG_Address" "$fulldomain"
|
||||
if [ ! $? ]; then
|
||||
if [ 1 = $? ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
@@ -67,7 +67,7 @@ dns_omglol_rm() {
|
||||
_debug "omg.lol Address" "$OMG_Address"
|
||||
|
||||
omg_validate "$OMG_ApiKey" "$OMG_Address" "$fulldomain"
|
||||
if [ ! $? ]; then
|
||||
if [ 1 = $? ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
@@ -100,18 +100,49 @@ omg_validate() {
|
||||
fi
|
||||
|
||||
_endswith "$fulldomain" "omg.lol"
|
||||
if [ ! $? ]; then
|
||||
if [ 1 = $? ]; then
|
||||
_err "Domain name requested is not under omg.lol"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_endswith "$fulldomain" "$omg_address.omg.lol"
|
||||
if [ ! $? ]; then
|
||||
if [ 1 = $? ]; then
|
||||
_err "Domain name is not a subdomain of provided omg.lol address $omg_address"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "Required environment parameters are all present"
|
||||
omg_testconnect "$omg_apikey" "$omg_address"
|
||||
if [ 1 = $? ]; then
|
||||
_err "Authentication to omg.lol for address $omg_address using provided API key failed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "Required environment parameters are all present and validated"
|
||||
}
|
||||
|
||||
# Validate that the address and API key are both correct and associated to each other
|
||||
omg_testconnect() {
|
||||
omg_apikey=$1
|
||||
omg_address=$2
|
||||
|
||||
_debug2 "Function" "omg_testconnect"
|
||||
_secure_debug2 "omg.lol API key" "$omg_apikey"
|
||||
_debug2 "omg.lol Address" "$omg_address"
|
||||
|
||||
authheader="$(_createAuthHeader "$omg_apikey")"
|
||||
export _H1="$authheader"
|
||||
endpoint="https://api.omg.lol/address/$omg_address/info"
|
||||
_debug2 "Endpoint for validation" "$endpoint"
|
||||
|
||||
response=$(_get "$endpoint" "" 30)
|
||||
|
||||
_jsonResponseCheck "$response" "status_code" 200
|
||||
if [ 1 = $? ]; then
|
||||
_debug2 "Failed to query omg.lol for $omg_address with provided API key"
|
||||
_secure_debug2 "API Key" "omg_apikey"
|
||||
_secure_debug3 "Raw response" "$response"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Add (or modify) an entry for a new ACME query
|
||||
|
||||
309
dnsapi/dns_sotoon.sh
Normal file
309
dnsapi/dns_sotoon.sh
Normal file
@@ -0,0 +1,309 @@
|
||||
#!/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
|
||||
Issues: github.com/acmesh-official/acme.sh/issues/6656
|
||||
Author: Erfan Gholizade
|
||||
'
|
||||
|
||||
SOTOON_API_URL="https://api.sotoon.ir/delivery/v2.1/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)}"
|
||||
|
||||
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
|
||||
|
||||
#save the info to the account conf file.
|
||||
_saveaccountconf_mutable Sotoon_Token "$Sotoon_Token"
|
||||
_saveaccountconf_mutable Sotoon_WorkspaceUUID "$Sotoon_WorkspaceUUID"
|
||||
|
||||
_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)}"
|
||||
|
||||
_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"
|
||||
|
||||
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"
|
||||
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/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]" "$@"
|
||||
}
|
||||
Reference in New Issue
Block a user