From d73d68ced48d9c3ff45e18f6bbe78c3c11c31746 Mon Sep 17 00:00:00 2001 From: AmadeusAbion Date: Wed, 14 Oct 2026 06:39:44 +0200 Subject: [PATCH] Add Abion.com DNS API (#7311) * Add Abion.com DNS API * Update dns_abion.sh * dns_abion: keep existing TXT records on add and rm Continues acmesh-official/acme.sh#6885 by @stelar7 and addresses the review comments there. - add: append to the existing TXT values at the name instead of replacing them, so wildcard + base certificates work - rm: remove only the matching value, keep the TTL and comments of the other records, and clear the name with null when it is empty - only read the TXT records of the target name; the old parser also picked up TXT records from other names in the zone - find the zone with GET zones/ instead of the paginated zone list, and stop with the API error on anything but "Zone not found" - add Docs line and progress messages, fix shfmt Co-authored-by: stelar7 * dns_abion: lowercase the domain so rm finds the record * dns_abion: add tracking issue to info block * dns_abion: align info block with the structural info format --------- Co-authored-by: stelar7 --- dnsapi/dns_abion.sh | 198 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 dnsapi/dns_abion.sh diff --git a/dnsapi/dns_abion.sh b/dnsapi/dns_abion.sh new file mode 100644 index 00000000..acd12229 --- /dev/null +++ b/dnsapi/dns_abion.sh @@ -0,0 +1,198 @@ +#!/usr/bin/env sh +# shellcheck disable=SC2034 +dns_abion_info='Abion.com +Site: abion.com +Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_abion +Options: + ABION_API_KEY API Key. Created in the Abion Core web interface. +Issues: github.com/acmesh-official/acme.sh/issues/7325 +' + +ABION_Api_Endpoint="https://api.abion.com/v1" + +##################### Public functions ##################### + +# Usage: dns_abion_add +# Example: dns_abion_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_abion_add() { + fulldomain="$(echo "$1" | _lower_case)" + txtvalue=$2 + + ABION_API_KEY="${ABION_API_KEY:-$(_readaccountconf_mutable ABION_API_KEY)}" + + if [ -z "$ABION_API_KEY" ]; then + ABION_API_KEY="" + _err "You need to specify ABION_API_KEY." + return 1 + fi + + _saveaccountconf_mutable ABION_API_KEY "$ABION_API_KEY" + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + if ! _abion_get_txt_records; then + return 1 + fi + + if _contains "$_txt_records" "\"rdata\":\"$txtvalue\""; then + _info "TXT record already exists" + return 0 + fi + + _records=$(echo "$_txt_records" | tr '\n' ',' | sed 's/,*$//') + if [ -n "$_records" ]; then + _records="$_records," + fi + + _info "Adding record" + if ! _abion_patch_txt "[$_records{\"rdata\":\"$txtvalue\",\"ttl\":60}]"; then + return 1 + fi + + _info "Added, OK" + return 0 +} + +# Usage: dns_abion_rm +dns_abion_rm() { + fulldomain="$(echo "$1" | _lower_case)" + txtvalue=$2 + + ABION_API_KEY="${ABION_API_KEY:-$(_readaccountconf_mutable ABION_API_KEY)}" + + if [ -z "$ABION_API_KEY" ]; then + ABION_API_KEY="" + _err "You need to specify ABION_API_KEY." + return 1 + fi + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + if ! _abion_get_txt_records; then + return 1 + fi + + if ! _contains "$_txt_records" "\"rdata\":\"$txtvalue\""; then + _info "TXT record not found, nothing to remove" + return 0 + fi + + _records=$(echo "$_txt_records" | grep -v "\"rdata\":\"$txtvalue\"" | tr '\n' ',' | sed 's/,*$//') + + if [ -n "$_records" ]; then + _records="[$_records]" + else + _records="null" + fi + + _info "Deleting record" + if ! _abion_patch_txt "$_records"; then + return 1 + fi + + _info "Deleted, OK" + return 0 +} + +##################### Private functions ##################### + +_get_root() { + domain=$1 + i=1 + p=0 + while true; do + h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + _debug h "$h" + if [ -z "$h" ]; then + return 1 + fi + + if ! _abion_rest GET "zones/$h"; then + return 1 + fi + + if _contains "$response" '"type" *: *"zone"'; then + if [ "$p" = "0" ]; then + _sub_domain="@" + else + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + fi + _domain="$h" + return 0 + fi + + if ! _contains "$response" "Zone not found"; then + _err "$response" + return 1 + fi + + p="$i" + i=$(_math "$i" + 1) + done +} + +_abion_get_txt_records() { + if ! _abion_rest GET "zones/$_domain"; then + return 1 + fi + + _sub_re=$(printf "%s" "$_sub_domain" | sed 's/[.]/\\./g') + _txt_records=$(echo "$response" | _normalizeJson | + sed -n "s/.*\"$_sub_re\":{//p" | + sed 's/\] *}.*/]/' | + _egrep_o '"TXT":\[[^]]*\]' | + sed 's/^"TXT":\[ *//;s/ *\]$//;s/} *, *{/}\ +{/g') + _debug2 _txt_records "$_txt_records" + return 0 +} + +_abion_patch_txt() { + if ! _abion_rest PATCH "zones/$_domain" "{\"data\":{\"type\":\"zone\",\"id\":\"$_domain\",\"attributes\":{\"records\":{\"$_sub_domain\":{\"TXT\":$1}}}}}"; then + return 1 + fi + + if ! _contains "$response" '"type" *: *"zone"'; then + _err "Unexpected API response: $response" + return 1 + fi + + return 0 +} + +_abion_rest() { + method=$1 + endpoint=$2 + data=$3 + + export _H1="X-API-KEY: $ABION_API_KEY" + export _H2="Content-Type: application/json" + + if [ "$method" = "GET" ]; then + response="$(_get "$ABION_Api_Endpoint/$endpoint")" + else + response="$(_post "$data" "$ABION_Api_Endpoint/$endpoint" "" "$method")" + fi + + if [ "$?" != "0" ]; then + _err "error $endpoint" + return 1 + fi + + _debug2 response "$response" + return 0 +}